summaryrefslogtreecommitdiffstats
path: root/toolkit/components/antitracking/test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /toolkit/components/antitracking/test
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/antitracking/test')
-rw-r--r--toolkit/components/antitracking/test/browser/.eslintrc.js7
-rw-r--r--toolkit/components/antitracking/test/browser/3rdParty.html53
-rw-r--r--toolkit/components/antitracking/test/browser/3rdPartyOpen.html16
-rw-r--r--toolkit/components/antitracking/test/browser/3rdPartyOpenUI.html17
-rw-r--r--toolkit/components/antitracking/test/browser/3rdPartyPartitioned.html29
-rw-r--r--toolkit/components/antitracking/test/browser/3rdPartyRelay.html41
-rw-r--r--toolkit/components/antitracking/test/browser/3rdPartySVG.html20
-rw-r--r--toolkit/components/antitracking/test/browser/3rdPartyStorage.html44
-rw-r--r--toolkit/components/antitracking/test/browser/3rdPartyStorageWO.html8
-rw-r--r--toolkit/components/antitracking/test/browser/3rdPartyUI.html32
-rw-r--r--toolkit/components/antitracking/test/browser/3rdPartyWO.html80
-rw-r--r--toolkit/components/antitracking/test/browser/3rdPartyWorker.html55
-rw-r--r--toolkit/components/antitracking/test/browser/antitracking_head.js1448
-rw-r--r--toolkit/components/antitracking/test/browser/browser-blocking.ini72
-rw-r--r--toolkit/components/antitracking/test/browser/browser.ini226
-rw-r--r--toolkit/components/antitracking/test/browser/browser_AntiTrackingETPHeuristic.js261
-rw-r--r--toolkit/components/antitracking/test/browser/browser_PBMCookieBehavior.js108
-rw-r--r--toolkit/components/antitracking/test/browser/browser_aboutblank.js44
-rw-r--r--toolkit/components/antitracking/test/browser/browser_addonHostPermissionIgnoredInTP.js46
-rw-r--r--toolkit/components/antitracking/test/browser/browser_allowListNotifications.js141
-rw-r--r--toolkit/components/antitracking/test/browser/browser_allowListNotifications_alwaysPartition.js142
-rw-r--r--toolkit/components/antitracking/test/browser/browser_allowListSeparationInPrivateAndNormalWindows.js52
-rw-r--r--toolkit/components/antitracking/test/browser/browser_allowPermissionForTracker.js64
-rw-r--r--toolkit/components/antitracking/test/browser/browser_backgroundImageAssertion.js69
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingCookies.js183
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingDOMCache.js39
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingDOMCacheAlwaysPartition.js54
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingDOMCacheAlwaysPartitionSAA.js80
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingDOMCacheSAA.js85
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingIndexedDb.js102
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingIndexedDbInWorkers.js73
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingIndexedDbInWorkers2.js152
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingLocalStorage.js94
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingMessaging.js322
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingNoOpener.js41
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingServiceWorkers.js30
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingServiceWorkersStorageAccessAPI.js133
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingSessionStorage.js126
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingSharedWorkers.js94
-rw-r--r--toolkit/components/antitracking/test/browser/browser_contentBlockingAllowListPrincipal.js237
-rw-r--r--toolkit/components/antitracking/test/browser/browser_contentBlockingTelemetry.js404
-rw-r--r--toolkit/components/antitracking/test/browser/browser_cookieBetweenTabs.js59
-rw-r--r--toolkit/components/antitracking/test/browser/browser_denyPermissionForTracker.js64
-rw-r--r--toolkit/components/antitracking/test/browser/browser_doublyNestedTracker.js130
-rw-r--r--toolkit/components/antitracking/test/browser/browser_emailtracking.js182
-rw-r--r--toolkit/components/antitracking/test/browser/browser_existingCookiesForSubresources.js235
-rw-r--r--toolkit/components/antitracking/test/browser/browser_fileUrl.js41
-rw-r--r--toolkit/components/antitracking/test/browser/browser_firstPartyCookieRejectionHonoursAllowList.js77
-rw-r--r--toolkit/components/antitracking/test/browser/browser_fpiServiceWorkers_fingerprinting.js90
-rw-r--r--toolkit/components/antitracking/test/browser/browser_hasStorageAccess.js196
-rw-r--r--toolkit/components/antitracking/test/browser/browser_hasStorageAccess_alwaysPartition.js209
-rw-r--r--toolkit/components/antitracking/test/browser/browser_iframe_document_open.js86
-rw-r--r--toolkit/components/antitracking/test/browser/browser_imageCache4.js13
-rw-r--r--toolkit/components/antitracking/test/browser/browser_imageCache8.js13
-rw-r--r--toolkit/components/antitracking/test/browser/browser_localStorageEvents.js186
-rw-r--r--toolkit/components/antitracking/test/browser/browser_onBeforeRequestNotificationForTrackingResources.js96
-rw-r--r--toolkit/components/antitracking/test/browser/browser_onModifyRequestNotificationForTrackingResources.js96
-rw-r--r--toolkit/components/antitracking/test/browser/browser_partitionedClearSiteDataHeader.js593
-rw-r--r--toolkit/components/antitracking/test/browser/browser_partitionedConsoleMessage.js80
-rw-r--r--toolkit/components/antitracking/test/browser/browser_partitionedCookies.js137
-rw-r--r--toolkit/components/antitracking/test/browser/browser_partitionedDOMCache.js110
-rw-r--r--toolkit/components/antitracking/test/browser/browser_partitionedIndexedDB.js95
-rw-r--r--toolkit/components/antitracking/test/browser/browser_partitionedLocalStorage.js115
-rw-r--r--toolkit/components/antitracking/test/browser/browser_partitionedLocalStorage_events.js1014
-rw-r--r--toolkit/components/antitracking/test/browser/browser_partitionedMessaging.js20
-rw-r--r--toolkit/components/antitracking/test/browser/browser_partitionedServiceWorkers.js625
-rw-r--r--toolkit/components/antitracking/test/browser/browser_partitionedSharedWorkers.js74
-rw-r--r--toolkit/components/antitracking/test/browser/browser_permissionInNormalWindows.js106
-rw-r--r--toolkit/components/antitracking/test/browser/browser_permissionInNormalWindows_alwaysPartition.js109
-rw-r--r--toolkit/components/antitracking/test/browser/browser_permissionInPrivateWindows.js50
-rw-r--r--toolkit/components/antitracking/test/browser/browser_permissionInPrivateWindows_alwaysPartition.js49
-rw-r--r--toolkit/components/antitracking/test/browser/browser_permissionPropagation.js425
-rw-r--r--toolkit/components/antitracking/test/browser/browser_referrerDefaultPolicy.js634
-rw-r--r--toolkit/components/antitracking/test/browser/browser_script.js224
-rw-r--r--toolkit/components/antitracking/test/browser/browser_serviceWorkersWithStorageAccessGranted.js148
-rw-r--r--toolkit/components/antitracking/test/browser/browser_siteSpecificWorkArounds.js153
-rw-r--r--toolkit/components/antitracking/test/browser/browser_socialtracking.js147
-rw-r--r--toolkit/components/antitracking/test/browser/browser_socialtracking_save_image.js114
-rw-r--r--toolkit/components/antitracking/test/browser/browser_staticPartition_CORS_preflight.js149
-rw-r--r--toolkit/components/antitracking/test/browser/browser_staticPartition_CORS_preflight.sjs40
-rw-r--r--toolkit/components/antitracking/test/browser/browser_staticPartition_HSTS.js251
-rw-r--r--toolkit/components/antitracking/test/browser/browser_staticPartition_HSTS.sjs33
-rw-r--r--toolkit/components/antitracking/test/browser/browser_staticPartition_cache.js194
-rw-r--r--toolkit/components/antitracking/test/browser/browser_staticPartition_network.js116
-rw-r--r--toolkit/components/antitracking/test/browser/browser_staticPartition_saveAs.js532
-rw-r--r--toolkit/components/antitracking/test/browser/browser_staticPartition_tls_session.js115
-rw-r--r--toolkit/components/antitracking/test/browser/browser_staticPartition_websocket.js198
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessAutograntRequiresUserInteraction.js57
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessDeniedGivesNoUserInteraction.js30
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessDoorHanger.js372
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessFrameInteractionGrantsUserInteraction.js70
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessGrantedGivesUserInteraction.js40
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessPrivilegeAPI.js617
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessPromiseRejectHandlerUserInteraction.js36
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessPromiseRejectHandlerUserInteraction_alwaysPartition.js31
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessPromiseResolveHandlerUserInteraction.js37
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateSubframe.js46
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateSubframe_alwaysPartition.js41
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateTopframe.js44
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateTopframe_alwaysPartition.js39
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessSandboxed.js214
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessSandboxed_alwaysPartition.js214
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessScopeDifferentSite.js64
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessScopeSameOrigin.js43
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessScopeSameSiteRead.js43
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessScopeSameSiteWrite.js43
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessThirdPartyChecks.js157
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessThirdPartyChecks_alwaysPartition.js153
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessWithDynamicFpi.js612
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessWithHeuristics.js912
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Arguments.js117
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CookieBehavior.js412
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CookiePermission.js174
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CrossOriginSameSite.js162
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Doorhanger.js122
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Embed.js155
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Enable.js86
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_RequireIntermediatePermission.js61
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_StorageAccessPermission.js94
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_UserActivation.js63
-rw-r--r--toolkit/components/antitracking/test/browser/browser_subResources.js277
-rw-r--r--toolkit/components/antitracking/test/browser/browser_subResourcesPartitioned.js308
-rw-r--r--toolkit/components/antitracking/test/browser/browser_subResourcesPartitioned_alwaysPartition.js313
-rw-r--r--toolkit/components/antitracking/test/browser/browser_thirdPartyStorageRejectionForCORS.js96
-rw-r--r--toolkit/components/antitracking/test/browser/browser_urlDecorationStripping.js253
-rw-r--r--toolkit/components/antitracking/test/browser/browser_urlDecorationStripping_alwaysPartition.js255
-rw-r--r--toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping.js855
-rw-r--r--toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping_allowList.js442
-rw-r--r--toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping_nimbus.js145
-rw-r--r--toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping_pbmode.js105
-rw-r--r--toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping_telemetry.js355
-rw-r--r--toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping_telemetry_2.js159
-rw-r--r--toolkit/components/antitracking/test/browser/browser_urlQueryStrippingListService.js249
-rw-r--r--toolkit/components/antitracking/test/browser/browser_userInteraction.js124
-rw-r--r--toolkit/components/antitracking/test/browser/browser_workerPropagation.js87
-rw-r--r--toolkit/components/antitracking/test/browser/clearSiteData.sjs6
-rw-r--r--toolkit/components/antitracking/test/browser/container.html6
-rw-r--r--toolkit/components/antitracking/test/browser/container2.html11
-rw-r--r--toolkit/components/antitracking/test/browser/cookies.sjs12
-rw-r--r--toolkit/components/antitracking/test/browser/cookiesCORS.sjs9
-rw-r--r--toolkit/components/antitracking/test/browser/dedicatedWorker.js3
-rw-r--r--toolkit/components/antitracking/test/browser/dynamicfpi_head.js180
-rw-r--r--toolkit/components/antitracking/test/browser/embedder.html4
-rw-r--r--toolkit/components/antitracking/test/browser/embedder2.html9
-rw-r--r--toolkit/components/antitracking/test/browser/empty-altsvc.js1
-rw-r--r--toolkit/components/antitracking/test/browser/empty-altsvc.js^headers^1
-rw-r--r--toolkit/components/antitracking/test/browser/empty.html1
-rw-r--r--toolkit/components/antitracking/test/browser/empty.js1
-rw-r--r--toolkit/components/antitracking/test/browser/file_iframe_document_open.html19
-rw-r--r--toolkit/components/antitracking/test/browser/file_localStorage.html21
-rw-r--r--toolkit/components/antitracking/test/browser/file_saveAsImage.sjs20
-rw-r--r--toolkit/components/antitracking/test/browser/file_saveAsPageInfo.html6
-rw-r--r--toolkit/components/antitracking/test/browser/file_saveAsVideo.sjs38
-rw-r--r--toolkit/components/antitracking/test/browser/file_stripping.html20
-rw-r--r--toolkit/components/antitracking/test/browser/file_video.ogvbin0 -> 16049 bytes
-rw-r--r--toolkit/components/antitracking/test/browser/file_ws_handshake_delay_wsh.py28
-rw-r--r--toolkit/components/antitracking/test/browser/head.js149
-rw-r--r--toolkit/components/antitracking/test/browser/iframe.html8
-rw-r--r--toolkit/components/antitracking/test/browser/image.sjs22
-rw-r--r--toolkit/components/antitracking/test/browser/imageCacheWorker.js78
-rw-r--r--toolkit/components/antitracking/test/browser/localStorage.html68
-rw-r--r--toolkit/components/antitracking/test/browser/localStorageEvents.html30
-rw-r--r--toolkit/components/antitracking/test/browser/matchAll.js16
-rw-r--r--toolkit/components/antitracking/test/browser/page.html8
-rw-r--r--toolkit/components/antitracking/test/browser/partitionedSharedWorker.js17
-rw-r--r--toolkit/components/antitracking/test/browser/partitionedstorage_head.js455
-rw-r--r--toolkit/components/antitracking/test/browser/popup.html11
-rw-r--r--toolkit/components/antitracking/test/browser/raptor.jpgbin0 -> 49629 bytes
-rw-r--r--toolkit/components/antitracking/test/browser/redirect.sjs11
-rw-r--r--toolkit/components/antitracking/test/browser/referrer.sjs49
-rw-r--r--toolkit/components/antitracking/test/browser/sandboxed.html12
-rw-r--r--toolkit/components/antitracking/test/browser/sandboxed.html^headers^1
-rw-r--r--toolkit/components/antitracking/test/browser/server.sjs20
-rw-r--r--toolkit/components/antitracking/test/browser/serviceWorker.js103
-rw-r--r--toolkit/components/antitracking/test/browser/sharedWorker.js18
-rw-r--r--toolkit/components/antitracking/test/browser/storageAccessAPIHelpers.js236
-rw-r--r--toolkit/components/antitracking/test/browser/storage_access_head.js248
-rw-r--r--toolkit/components/antitracking/test/browser/subResources.sjs33
-rw-r--r--toolkit/components/antitracking/test/browser/tracker.js7
-rw-r--r--toolkit/components/antitracking/test/browser/workerIframe.html71
-rw-r--r--toolkit/components/antitracking/test/gtest/TestPartitioningExceptionList.cpp184
-rw-r--r--toolkit/components/antitracking/test/gtest/TestStoragePrincipalHelper.cpp215
-rw-r--r--toolkit/components/antitracking/test/gtest/TestURLQueryStringStripper.cpp176
-rw-r--r--toolkit/components/antitracking/test/gtest/moz.build19
-rw-r--r--toolkit/components/antitracking/test/xpcshell/data/font.woffbin0 -> 1112 bytes
-rw-r--r--toolkit/components/antitracking/test/xpcshell/head.js11
-rw-r--r--toolkit/components/antitracking/test/xpcshell/test_ExceptionListService.js107
-rw-r--r--toolkit/components/antitracking/test/xpcshell/test_cookie_behavior.js94
-rw-r--r--toolkit/components/antitracking/test/xpcshell/test_getPartitionKeyFromURL.js223
-rw-r--r--toolkit/components/antitracking/test/xpcshell/test_purge_trackers.js523
-rw-r--r--toolkit/components/antitracking/test/xpcshell/test_purge_trackers_telemetry.js175
-rw-r--r--toolkit/components/antitracking/test/xpcshell/test_rejectForeignAllowList.js116
-rw-r--r--toolkit/components/antitracking/test/xpcshell/test_staticPartition_authhttp.js125
-rw-r--r--toolkit/components/antitracking/test/xpcshell/test_staticPartition_clientAuthRemember.js129
-rw-r--r--toolkit/components/antitracking/test/xpcshell/test_staticPartition_font.js112
-rw-r--r--toolkit/components/antitracking/test/xpcshell/test_staticPartition_image.js86
-rw-r--r--toolkit/components/antitracking/test/xpcshell/test_staticPartition_prefetch.js177
-rw-r--r--toolkit/components/antitracking/test/xpcshell/test_staticPartition_preload.js187
-rw-r--r--toolkit/components/antitracking/test/xpcshell/test_tracking_db_service.js492
-rw-r--r--toolkit/components/antitracking/test/xpcshell/test_view_source.js78
-rw-r--r--toolkit/components/antitracking/test/xpcshell/xpcshell.ini51
201 files changed, 28478 insertions, 0 deletions
diff --git a/toolkit/components/antitracking/test/browser/.eslintrc.js b/toolkit/components/antitracking/test/browser/.eslintrc.js
new file mode 100644
index 0000000000..e57058ecb1
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ env: {
+ webextensions: true,
+ },
+};
diff --git a/toolkit/components/antitracking/test/browser/3rdParty.html b/toolkit/components/antitracking/test/browser/3rdParty.html
new file mode 100644
index 0000000000..7cb88ea17a
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/3rdParty.html
@@ -0,0 +1,53 @@
+<html>
+<head>
+ <title>3rd party content!</title>
+ <script type="text/javascript" src="https://example.com/browser/toolkit/components/antitracking/test/browser/storageAccessAPIHelpers.js"></script>
+</head>
+<body>
+<h1>Here the 3rd party content!</h1>
+<script>
+
+function info(msg) {
+ parent.postMessage({ type: "info", msg }, "*");
+}
+
+function ok(what, msg) {
+ parent.postMessage({ type: "ok", what: !!what, msg }, "*");
+}
+
+function is(a, b, msg) {
+ ok(a === b, msg);
+}
+
+onmessage = function(e) {
+ let data = e.data;
+ if (data.includes("!!!")) {
+ // The data argument may be packed with information about whether we are on
+ // the allow list. In that case, extract that information and prepare it
+ // for our callbacks to access it.
+ let parts = data.split("!!!");
+ // Only consider ourselves allow-listed when the cookie policy is set to
+ // 'block third-party trackers or 'block third-party trackers and partition
+ // third-party cookies', since otherwise we won't obtain storage access by
+ // default, which is what this data is used for in tests.
+ let cookieBehavior = SpecialPowers.isContentWindowPrivate(window)
+ ? SpecialPowers.Services.prefs.getIntPref("network.cookie.cookieBehavior.pbmode")
+ : SpecialPowers.Services.prefs.getIntPref("network.cookie.cookieBehavior");
+ window.allowListed =
+ parts[0] === "true" &&
+ (cookieBehavior == SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER ||
+ cookieBehavior == SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN ||
+ (cookieBehavior == SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN &&
+ SpecialPowers.Services.prefs.getBoolPref("network.cookie.rejectForeignWithExceptions.enabled")));
+ data = parts[1];
+ }
+ let runnableStr = `(() => {return (${data});})();`;
+ let runnable = eval(runnableStr); // eslint-disable-line no-eval
+ runnable.call(this, /* Phase */ 1).then(_ => {
+ parent.postMessage({ type: "finish" }, "*");
+ });
+};
+
+</script>
+</body>
+</html>
diff --git a/toolkit/components/antitracking/test/browser/3rdPartyOpen.html b/toolkit/components/antitracking/test/browser/3rdPartyOpen.html
new file mode 100644
index 0000000000..1110834c0e
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/3rdPartyOpen.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+ <title>A popup!</title>
+</head>
+<body>
+<h1>hi!</h1>
+<script>
+
+if (opener) {
+ opener.postMessage("hello!", "*");
+}
+window.close();
+
+</script>
+</body>
+</html>
diff --git a/toolkit/components/antitracking/test/browser/3rdPartyOpenUI.html b/toolkit/components/antitracking/test/browser/3rdPartyOpenUI.html
new file mode 100644
index 0000000000..5afbb5d89b
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/3rdPartyOpenUI.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+ <title>A popup!</title>
+</head>
+<body>
+<h1>hi!</h1>
+<script>
+
+SpecialPowers.wrap(document).userInteractionForTesting();
+if (window.location.search == "?messageme") {
+ window.opener.postMessage("done", "*");
+}
+window.close();
+
+</script>
+</body>
+</html>
diff --git a/toolkit/components/antitracking/test/browser/3rdPartyPartitioned.html b/toolkit/components/antitracking/test/browser/3rdPartyPartitioned.html
new file mode 100644
index 0000000000..f97ed4791f
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/3rdPartyPartitioned.html
@@ -0,0 +1,29 @@
+<html>
+<head>
+ <title>3rd party content!</title>
+</head>
+<body>
+<h1>Here the 3rd party content!</h1>
+<script>
+
+onmessage = async function(e) {
+ let cb = e.data.cb;
+ let runnableStr = `(() => {return (${cb});})();`;
+ let runnable = eval(runnableStr); // eslint-disable-line no-eval
+ let variant = (new URL(location.href)).searchParams.get("variant");
+ let win = this;
+ if (variant == "initial-aboutblank") {
+ let i = win.document.createElement("iframe");
+ i.src = "about:blank";
+ win.document.body.appendChild(i);
+ // override win to make it point to the initial about:blank window
+ win = i.contentWindow;
+ }
+
+ let result = await runnable.call(this, win, e.data.value);
+ parent.postMessage(result, "*");
+};
+
+</script>
+</body>
+</html>
diff --git a/toolkit/components/antitracking/test/browser/3rdPartyRelay.html b/toolkit/components/antitracking/test/browser/3rdPartyRelay.html
new file mode 100644
index 0000000000..64e713913b
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/3rdPartyRelay.html
@@ -0,0 +1,41 @@
+<html>
+<head>
+ <title>Tracker</title>
+ <script type="text/javascript" src="https://example.com/browser/toolkit/components/antitracking/test/browser/storageAccessAPIHelpers.js"></script>
+</head>
+<body>
+<h1>Relay</h1>
+<iframe></iframe>
+<script>
+
+function info(msg) {
+ parent.postMessage({ type: "info", msg }, "*");
+}
+
+function ok(what, msg) {
+ parent.postMessage({ type: "ok", what: !!what, msg }, "*");
+}
+
+function is(a, b, msg) {
+ ok(a === b, msg);
+}
+
+onmessage = function(e) {
+ switch (e.data.type || "") {
+ case "finish":
+ case "ok":
+ case "info":
+ parent.postMessage(e.data, "*");
+ break;
+ default:
+ let iframe = document.querySelector("iframe");
+ iframe.contentWindow.postMessage(e.data, "*");
+ break;
+ }
+};
+
+document.querySelector("iframe").src = location.search.substr(1);
+
+</script>
+</body>
+</html>
diff --git a/toolkit/components/antitracking/test/browser/3rdPartySVG.html b/toolkit/components/antitracking/test/browser/3rdPartySVG.html
new file mode 100644
index 0000000000..df791f355f
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/3rdPartySVG.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+ <title>3rd party content!</title>
+ <style>
+ body {
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path fill="context-fill" d="M28.5,8.1c0-1.1-1-1.9-2.1-2.4V3.7c-0.2-0.2-0.3-0.3-0.6-0.3c-0.6,0-1.1,0.8-1.3,2.1c-0.2,0-0.3,0-0.5,0l0,0c0-0.2,0-0.3-0.2-0.5c-0.3-1.1-0.8-1.9-1.3-2.6C22,2.6,21.7,3.2,21.7,4L22,6.3c-0.3,0.2-0.6,0.3-1,0.6l-3.5,3.7l0,0c0,0-6.3-0.8-10.9,0.2c-0.6,0-1,0.2-1.1,0.3c-0.5,0.2-0.8,0.3-1.1,0.6c-1.1-0.8-2.2-2.1-3.2-4c0-0.3-0.5-0.5-0.8-0.5s-0.5,0.6-0.3,1c0.8,2.1,2.1,3.5,3.4,4.5c-0.5,0.5-0.8,1-1,1.6c0,0-0.3,2.2-0.3,5.5l1.4,8c0,1,0.8,1.8,1.9,1.8c1,0,1.9-0.8,1.9-1.8V23l0.5-1.3h8.8l0.8,1.3v4.7c0,1,0.8,1.8,1.9,1.8c1,0,1.6-0.6,1.8-1.4l0,0l1.9-9l0,0l2.1-6.4h3c3.4,0,3.7-2.9,3.7-2.9L28.5,8.1z"/></svg>');
+ }
+ </style>
+</head>
+<body>
+<h1>3rd party content with an SVG image background</h1>
+<script>
+
+onload = function(e) {
+ parent.postMessage({ type: "finish" }, "*");
+};
+
+</script>
+</body>
+</html>
diff --git a/toolkit/components/antitracking/test/browser/3rdPartyStorage.html b/toolkit/components/antitracking/test/browser/3rdPartyStorage.html
new file mode 100644
index 0000000000..749ead7c20
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/3rdPartyStorage.html
@@ -0,0 +1,44 @@
+<html>
+<head>
+ <title>3rd party content!</title>
+ <script type="text/javascript" src="https://example.com/browser/toolkit/components/antitracking/test/browser/storageAccessAPIHelpers.js"></script>
+</head>
+<body>
+<h1>Here the 3rd party content!</h1>
+<script>
+
+function info(msg) {
+ parent.postMessage({ type: "info", msg }, "*");
+}
+
+function ok(what, msg) {
+ parent.postMessage({ type: "ok", what: !!what, msg }, "*");
+}
+
+function is(a, b, msg) {
+ ok(a === b, msg);
+}
+
+onmessage = function(e) {
+ let data = e.data;
+ let runnableStr = `(() => {return (${data});})();`;
+ let runnable = eval(runnableStr); // eslint-disable-line no-eval
+
+ let win = window.open("3rdPartyStorageWO.html");
+ win.onload = async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+
+ await runnable.call(this, this, win, false /* allowed */);
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+ await runnable.call(this, this, win, true /* allowed */);
+
+ win.close();
+ parent.postMessage({ type: "finish" }, "*");
+ };
+};
+
+</script>
+</body>
+</html>
diff --git a/toolkit/components/antitracking/test/browser/3rdPartyStorageWO.html b/toolkit/components/antitracking/test/browser/3rdPartyStorageWO.html
new file mode 100644
index 0000000000..b04916103d
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/3rdPartyStorageWO.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+ <title>1st party content!</title>
+</head>
+<body>
+<h1>Here the 1st party content!</h1>
+</body>
+</html>
diff --git a/toolkit/components/antitracking/test/browser/3rdPartyUI.html b/toolkit/components/antitracking/test/browser/3rdPartyUI.html
new file mode 100644
index 0000000000..57693fbcbd
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/3rdPartyUI.html
@@ -0,0 +1,32 @@
+<html>
+<head>
+ <title>Tracker</title>
+ <script type="text/javascript" src="https://example.com/browser/toolkit/components/antitracking/test/browser/storageAccessAPIHelpers.js"></script>
+</head>
+<body>
+<h1>Tracker</h1>
+<script>
+
+function info(msg) {
+ parent.postMessage({ type: "info", msg }, "*");
+}
+
+function ok(what, msg) {
+ parent.postMessage({ type: "ok", what: !!what, msg }, "*");
+}
+
+function is(a, b, msg) {
+ ok(a === b, msg);
+}
+
+onmessage = function(e) {
+ let runnableStr = `(() => {return (${e.data.callback});})();`;
+ let runnable = eval(runnableStr); // eslint-disable-line no-eval
+ runnable.call(this, e.data.arg || /* Phase */ 3).then(_ => {
+ parent.postMessage({ type: "finish" }, "*");
+ });
+};
+
+</script>
+</body>
+</html>
diff --git a/toolkit/components/antitracking/test/browser/3rdPartyWO.html b/toolkit/components/antitracking/test/browser/3rdPartyWO.html
new file mode 100644
index 0000000000..7986b31063
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/3rdPartyWO.html
@@ -0,0 +1,80 @@
+<html>
+<head>
+ <title>Interact with me!</title>
+ <script type="text/javascript" src="https://example.com/browser/toolkit/components/antitracking/test/browser/storageAccessAPIHelpers.js"></script>
+</head>
+<body>
+<h1>Interact with me!</h1>
+<script>
+
+function info(msg) {
+ parent.postMessage({ type: "info", msg }, "*");
+}
+
+function ok(what, msg) {
+ parent.postMessage({ type: "ok", what: !!what, msg }, "*");
+}
+
+function is(a, b, msg) {
+ ok(a === b, msg);
+}
+
+onmessage = function(e) {
+ let runnableStr = `(() => {return (${e.data.blockingCallback});})();`;
+ let runnable = eval(runnableStr); // eslint-disable-line no-eval
+ runnable.call(this, /* Phase */ 2).then(_ => {
+ info("Let's do a window.open()");
+ return new Promise(resolve => {
+ if (location.search == "?noopener") {
+ let features = "noopener";
+
+ window.open("3rdPartyOpen.html", undefined, features);
+ setTimeout(resolve, 1000);
+ } else {
+ onmessage = resolve;
+
+ window.open("3rdPartyOpen.html");
+ }
+ });
+ }).then(_ => {
+ info("The popup has been dismissed!");
+ // First time storage access should not be granted because the tracker has
+ // not had user interaction yet.
+ let runnableStr = `(() => {return (${e.data.blockingCallback});})();`;
+ let runnable = eval(runnableStr); // eslint-disable-line no-eval
+ return runnable.call(this, /* Phase */ 2);
+ }).then(_ => {
+ info("Let's interact with the tracker");
+ return new Promise(resolve => {
+ onmessage = resolve;
+
+ window.open("3rdPartyOpenUI.html?messageme");
+ });
+ }).then(_ => {
+ info("Let's do another window.open()");
+ return new Promise(resolve => {
+ if (location.search == "?noopener") {
+ let features = "noopener";
+
+ window.open("3rdPartyOpen.html", undefined, features);
+ setTimeout(resolve, 1000);
+ } else {
+ onmessage = resolve;
+
+ window.open("3rdPartyOpen.html");
+ }
+ });
+ }).then(_ => {
+ // This time the tracker must have been able to obtain first-party storage
+ // access because it has had user interaction before.
+ let runnableStr = `(() => {return (${e.data.nonBlockingCallback});})();`;
+ let runnable = eval(runnableStr); // eslint-disable-line no-eval
+ return runnable.call(this, /* Phase */ 2);
+ }).then(_ => {
+ parent.postMessage({ type: "finish" }, "*");
+ });
+};
+
+</script>
+</body>
+</html>
diff --git a/toolkit/components/antitracking/test/browser/3rdPartyWorker.html b/toolkit/components/antitracking/test/browser/3rdPartyWorker.html
new file mode 100644
index 0000000000..e79a992660
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/3rdPartyWorker.html
@@ -0,0 +1,55 @@
+<html>
+<head>
+ <title>Tracker</title>
+ <script type="text/javascript" src="https://example.com/browser/toolkit/components/antitracking/test/browser/storageAccessAPIHelpers.js"></script>
+</head>
+<body>
+<h1>Tracker</h1>
+<script>
+
+function info(msg) {
+ parent.postMessage({ type: "info", msg }, "*");
+}
+
+function ok(what, msg) {
+ parent.postMessage({ type: "ok", what: !!what, msg }, "*");
+}
+
+function is(a, b, msg) {
+ ok(a === b, msg);
+}
+
+function workerCode() {
+ onmessage = e => {
+ try {
+ indexedDB.open("test", "1");
+ postMessage(true);
+ } catch (e) {
+ postMessage(false);
+ }
+ };
+}
+
+var worker;
+function createWorker() {
+ let blob = new Blob([workerCode.toString() + "; workerCode();"]);
+ let blobURL = URL.createObjectURL(blob);
+ info("Blob created");
+
+ worker = new Worker(blobURL);
+ info("Worker created");
+}
+
+onmessage = function(e) {
+ let runnableStr = `(() => {return (${e.data.callback});})();`;
+ let runnable = eval(runnableStr); // eslint-disable-line no-eval
+ runnable.call(this, e.data.arg || /* Phase */ 3).then(_ => {
+ parent.postMessage({ type: "finish" }, "*");
+ });
+};
+
+createWorker();
+
+</script>
+</body>
+</html>
diff --git a/toolkit/components/antitracking/test/browser/antitracking_head.js b/toolkit/components/antitracking/test/browser/antitracking_head.js
new file mode 100644
index 0000000000..1b98e38f83
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/antitracking_head.js
@@ -0,0 +1,1448 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from head.js */
+
+"use strict";
+
+var gFeatures = undefined;
+var gTestTrackersCleanedUp = false;
+var gTestTrackersCleanupRegistered = false;
+
+/**
+ * Force garbage collection.
+ */
+function forceGC() {
+ SpecialPowers.gc();
+ SpecialPowers.forceShrinkingGC();
+ SpecialPowers.forceCC();
+ SpecialPowers.gc();
+ SpecialPowers.forceShrinkingGC();
+ SpecialPowers.forceCC();
+}
+
+this.AntiTracking = {
+ runTestInNormalAndPrivateMode(
+ name,
+ callbackTracking,
+ callbackNonTracking,
+ cleanupFunction,
+ extraPrefs,
+ windowOpenTest = true,
+ userInteractionTest = true,
+ expectedBlockingNotifications = Ci.nsIWebProgressListener
+ .STATE_COOKIES_BLOCKED_TRACKER,
+ iframeSandbox = null,
+ accessRemoval = null,
+ callbackAfterRemoval = null
+ ) {
+ // Normal mode
+ this.runTest(
+ name,
+ callbackTracking,
+ callbackNonTracking,
+ cleanupFunction,
+ extraPrefs,
+ windowOpenTest,
+ userInteractionTest,
+ expectedBlockingNotifications,
+ false,
+ iframeSandbox,
+ accessRemoval,
+ callbackAfterRemoval
+ );
+
+ // Private mode
+ this.runTest(
+ name,
+ callbackTracking,
+ callbackNonTracking,
+ cleanupFunction,
+ extraPrefs,
+ windowOpenTest,
+ userInteractionTest,
+ expectedBlockingNotifications,
+ true,
+ iframeSandbox,
+ accessRemoval,
+ callbackAfterRemoval
+ );
+ },
+
+ runTest(
+ name,
+ callbackTracking,
+ callbackNonTracking,
+ cleanupFunction,
+ extraPrefs,
+ windowOpenTest = true,
+ userInteractionTest = true,
+ expectedBlockingNotifications = Ci.nsIWebProgressListener
+ .STATE_COOKIES_BLOCKED_TRACKER,
+ runInPrivateWindow = false,
+ iframeSandbox = null,
+ accessRemoval = null,
+ callbackAfterRemoval = null
+ ) {
+ let runExtraTests = true;
+ let options = {};
+ if (typeof callbackNonTracking == "object" && !!callbackNonTracking) {
+ options.callback = callbackNonTracking.callback;
+ runExtraTests = callbackNonTracking.runExtraTests;
+ if ("cookieBehavior" in callbackNonTracking) {
+ options.cookieBehavior = callbackNonTracking.cookieBehavior;
+ } else {
+ options.cookieBehavior = BEHAVIOR_ACCEPT;
+ }
+ if ("expectedBlockingNotifications" in callbackNonTracking) {
+ options.expectedBlockingNotifications =
+ callbackNonTracking.expectedBlockingNotifications;
+ } else {
+ options.expectedBlockingNotifications = 0;
+ }
+ if ("blockingByAllowList" in callbackNonTracking) {
+ options.blockingByAllowList = callbackNonTracking.blockingByAllowList;
+ if (options.blockingByAllowList) {
+ // If we're on the allow list, there won't be any blocking!
+ options.expectedBlockingNotifications = 0;
+ }
+ } else {
+ options.blockingByAllowList = false;
+ }
+ callbackNonTracking = options.callback;
+ options.accessRemoval = null;
+ options.callbackAfterRemoval = null;
+ }
+
+ // Here we want to test that a 3rd party context is simply blocked.
+ this._createTask({
+ name,
+ cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+ allowList: false,
+ callback: callbackTracking,
+ extraPrefs,
+ expectedBlockingNotifications,
+ runInPrivateWindow,
+ iframeSandbox,
+ accessRemoval,
+ callbackAfterRemoval,
+ });
+ this._createCleanupTask(cleanupFunction);
+
+ if (callbackNonTracking) {
+ // Phase 1: Here we want to test that a 3rd party context is not blocked if pref is off.
+ if (runExtraTests) {
+ // There are five ways in which the third-party context may not be blocked:
+ // * If the cookieBehavior pref causes it to not be blocked.
+ // * If the contentBlocking pref causes it to not be blocked.
+ // * If both of these prefs cause it to not be blocked.
+ // * If the top-level page is on the content blocking allow list.
+ // * If the contentBlocking third-party cookies UI pref is off, the allow list will be ignored.
+ // All of these cases are tested here.
+ this._createTask({
+ name,
+ cookieBehavior: BEHAVIOR_ACCEPT,
+ allowList: false,
+ callback: callbackNonTracking,
+ extraPrefs,
+ expectedBlockingNotifications: 0,
+ runInPrivateWindow,
+ iframeSandbox,
+ accessRemoval: null, // only passed with non-blocking callback
+ callbackAfterRemoval: null,
+ });
+ this._createCleanupTask(cleanupFunction);
+
+ this._createTask({
+ name,
+ cookieBehavior: BEHAVIOR_ACCEPT,
+ allowList: true,
+ callback: callbackNonTracking,
+ extraPrefs,
+ expectedBlockingNotifications: 0,
+ runInPrivateWindow,
+ iframeSandbox,
+ accessRemoval: null, // only passed with non-blocking callback
+ callbackAfterRemoval: null,
+ });
+ this._createCleanupTask(cleanupFunction);
+
+ this._createTask({
+ name,
+ cookieBehavior: BEHAVIOR_REJECT,
+ allowList: false,
+ callback: callbackTracking,
+ extraPrefs,
+ expectedBlockingNotifications: expectedBlockingNotifications
+ ? Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_ALL
+ : 0,
+ runInPrivateWindow,
+ iframeSandbox,
+ accessRemoval: null, // only passed with non-blocking callback
+ callbackAfterRemoval: null,
+ });
+ this._createCleanupTask(cleanupFunction);
+
+ this._createTask({
+ name,
+ cookieBehavior: BEHAVIOR_LIMIT_FOREIGN,
+ allowList: true,
+ callback: callbackNonTracking,
+ extraPrefs,
+ expectedBlockingNotifications: 0,
+ runInPrivateWindow,
+ iframeSandbox,
+ accessRemoval: null, // only passed with non-blocking callback
+ callbackAfterRemoval: null,
+ });
+ this._createCleanupTask(cleanupFunction);
+
+ this._createTask({
+ name: name + " reject foreign without exception",
+ cookieBehavior: BEHAVIOR_REJECT_FOREIGN,
+ allowList: true,
+ callback: callbackNonTracking,
+ extraPrefs: [
+ ["network.cookie.rejectForeignWithExceptions.enabled", false],
+ ...(extraPrefs || []),
+ ],
+ expectedBlockingNotifications: 0,
+ runInPrivateWindow,
+ iframeSandbox,
+ accessRemoval: null, // only passed with non-blocking callback
+ callbackAfterRemoval: null,
+ });
+ this._createCleanupTask(cleanupFunction);
+
+ this._createTask({
+ name: name + " reject foreign with exception",
+ cookieBehavior: BEHAVIOR_REJECT_FOREIGN,
+ allowList: true,
+ callback: callbackNonTracking,
+ extraPrefs: [
+ ["network.cookie.rejectForeignWithExceptions.enabled", true],
+ ...(extraPrefs || []),
+ ],
+ expectedBlockingNotifications: 0,
+ runInPrivateWindow,
+ iframeSandbox,
+ accessRemoval,
+ callbackAfterRemoval,
+ });
+ this._createCleanupTask(cleanupFunction);
+
+ this._createTask({
+ name,
+ cookieBehavior: BEHAVIOR_REJECT_FOREIGN,
+ allowList: false,
+ callback: callbackTracking,
+ extraPrefs: [
+ ["network.cookie.rejectForeignWithExceptions.enabled", false],
+ ...(extraPrefs || []),
+ ],
+ expectedBlockingNotifications: expectedBlockingNotifications
+ ? Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_FOREIGN
+ : 0,
+ runInPrivateWindow,
+ iframeSandbox,
+ accessRemoval,
+ callbackAfterRemoval,
+ });
+ this._createCleanupTask(cleanupFunction);
+
+ this._createTask({
+ name,
+ cookieBehavior: BEHAVIOR_REJECT_FOREIGN,
+ allowList: false,
+ callback: callbackNonTracking,
+ extraPrefs: [
+ ["network.cookie.rejectForeignWithExceptions.enabled", true],
+ ...(extraPrefs || []),
+ ],
+ expectedBlockingNotifications: false,
+ runInPrivateWindow,
+ iframeSandbox,
+ accessRemoval: null, // only passed with non-blocking callback
+ callbackAfterRemoval: null,
+ thirdPartyPage: TEST_ANOTHER_3RD_PARTY_PAGE,
+ });
+ this._createCleanupTask(cleanupFunction);
+
+ this._createTask({
+ name,
+ cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+ allowList: true,
+ callback: callbackNonTracking,
+ extraPrefs,
+ expectedBlockingNotifications: 0,
+ runInPrivateWindow,
+ iframeSandbox,
+ accessRemoval,
+ callbackAfterRemoval,
+ });
+ this._createCleanupTask(cleanupFunction);
+
+ this._createTask({
+ name,
+ cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+ allowList: false,
+ callback: callbackNonTracking,
+ extraPrefs,
+ expectedBlockingNotifications: false,
+ runInPrivateWindow,
+ iframeSandbox,
+ accessRemoval: null, // only passed with non-blocking callback
+ callbackAfterRemoval: null,
+ thirdPartyPage: TEST_ANOTHER_3RD_PARTY_PAGE,
+ });
+ this._createCleanupTask(cleanupFunction);
+ } else {
+ // This is only used for imageCacheWorker.js tests
+ this._createTask({
+ name,
+ cookieBehavior: options.cookieBehavior,
+ allowList: options.blockingByAllowList,
+ callback: options.callback,
+ extraPrefs,
+ expectedBlockingNotifications: options.expectedBlockingNotifications,
+ runInPrivateWindow,
+ iframeSandbox,
+ accessRemoval: options.accessRemoval,
+ callbackAfterRemoval: options.callbackAfterRemoval,
+ });
+ this._createCleanupTask(cleanupFunction);
+ }
+
+ // Phase 2: Here we want to test that a third-party context doesn't
+ // get blocked with when the same origin is opened through window.open().
+ if (windowOpenTest) {
+ this._createWindowOpenTask(
+ name,
+ BEHAVIOR_REJECT_TRACKER,
+ callbackTracking,
+ callbackNonTracking,
+ runInPrivateWindow,
+ iframeSandbox,
+ false,
+ extraPrefs
+ );
+ this._createCleanupTask(cleanupFunction);
+
+ this._createWindowOpenTask(
+ name,
+ BEHAVIOR_REJECT_FOREIGN,
+ callbackTracking,
+ callbackNonTracking,
+ runInPrivateWindow,
+ iframeSandbox,
+ false,
+ [
+ ["network.cookie.rejectForeignWithExceptions.enabled", true],
+ ...(extraPrefs || []),
+ ]
+ );
+ this._createCleanupTask(cleanupFunction);
+
+ // Now, check if it works for nested iframes.
+ this._createWindowOpenTask(
+ name,
+ BEHAVIOR_REJECT_TRACKER,
+ callbackTracking,
+ callbackNonTracking,
+ runInPrivateWindow,
+ iframeSandbox,
+ true,
+ extraPrefs
+ );
+ this._createCleanupTask(cleanupFunction);
+
+ this._createWindowOpenTask(
+ name,
+ BEHAVIOR_REJECT_FOREIGN,
+ callbackTracking,
+ callbackNonTracking,
+ runInPrivateWindow,
+ iframeSandbox,
+ true,
+ [
+ ["network.cookie.rejectForeignWithExceptions.enabled", true],
+ ...(extraPrefs || []),
+ ]
+ );
+ this._createCleanupTask(cleanupFunction);
+ }
+
+ // Phase 3: Here we want to test that a third-party context doesn't
+ // get blocked with user interaction present
+ if (userInteractionTest) {
+ this._createUserInteractionTask(
+ name,
+ BEHAVIOR_REJECT_TRACKER,
+ callbackTracking,
+ callbackNonTracking,
+ runInPrivateWindow,
+ iframeSandbox,
+ false,
+ extraPrefs
+ );
+ this._createCleanupTask(cleanupFunction);
+
+ this._createUserInteractionTask(
+ name,
+ BEHAVIOR_REJECT_FOREIGN,
+ callbackTracking,
+ callbackNonTracking,
+ runInPrivateWindow,
+ iframeSandbox,
+ false,
+ [
+ ["network.cookie.rejectForeignWithExceptions.enabled", true],
+ ...(extraPrefs || []),
+ ]
+ );
+ this._createCleanupTask(cleanupFunction);
+
+ // Now, check if it works for nested iframes.
+ this._createUserInteractionTask(
+ name,
+ BEHAVIOR_REJECT_TRACKER,
+ callbackTracking,
+ callbackNonTracking,
+ runInPrivateWindow,
+ iframeSandbox,
+ true,
+ extraPrefs
+ );
+ this._createCleanupTask(cleanupFunction);
+
+ this._createUserInteractionTask(
+ name,
+ BEHAVIOR_REJECT_FOREIGN,
+ callbackTracking,
+ callbackNonTracking,
+ runInPrivateWindow,
+ iframeSandbox,
+ true,
+ [
+ ["network.cookie.rejectForeignWithExceptions.enabled", true],
+ ...(extraPrefs || []),
+ ]
+ );
+ this._createCleanupTask(cleanupFunction);
+ }
+ }
+ },
+
+ _waitObserver(targetTopic, expectedCount) {
+ let cnt = 0;
+
+ return new Promise(resolve => {
+ Services.obs.addObserver(function observer(subject, topic, data) {
+ if (topic != targetTopic) {
+ return;
+ }
+ cnt++;
+
+ if (cnt != expectedCount) {
+ return;
+ }
+
+ Services.obs.removeObserver(observer, targetTopic);
+ resolve();
+ }, targetTopic);
+ });
+ },
+
+ _waitUserInteractionPerm() {
+ return this._waitObserver(
+ "antitracking-test-user-interaction-perm-added",
+ 1
+ );
+ },
+
+ _waitStorageAccessPerm(expectedCount) {
+ return this._waitObserver(
+ "antitracking-test-storage-access-perm-added",
+ expectedCount
+ );
+ },
+
+ async interactWithTracker() {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ await BrowserTestUtils.withNewTab(
+ { gBrowser: win.gBrowser, url: TEST_3RD_PARTY_PAGE },
+ async function (browser) {
+ info("Let's interact with the tracker");
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ SpecialPowers.wrap(content.document).userInteractionForTesting();
+ });
+ }
+ );
+ await BrowserTestUtils.closeWindow(win);
+ },
+
+ async _setupTest(win, cookieBehavior, runInPrivateWindow, extraPrefs) {
+ await SpecialPowers.flushPrefEnv();
+
+ await setCookieBehaviorPref(cookieBehavior, runInPrivateWindow);
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["dom.security.https_first_pbm", false],
+ [
+ "privacy.trackingprotection.annotate_channels",
+ cookieBehavior != BEHAVIOR_ACCEPT,
+ ],
+ ["privacy.restrict3rdpartystorage.console.lazy", false],
+ [
+ "privacy.restrict3rdpartystorage.userInteractionRequiredForHosts",
+ "tracking.example.com,tracking.example.org",
+ ],
+ ["privacy.antitracking.testing", true],
+ ],
+ });
+
+ if (extraPrefs && Array.isArray(extraPrefs) && extraPrefs.length) {
+ await SpecialPowers.pushPrefEnv({ set: extraPrefs });
+
+ let enableWebcompat = Services.prefs.getBoolPref(
+ "privacy.antitracking.enableWebcompat"
+ );
+
+ // If the skip list is disabled by pref, it will always return an empty
+ // list.
+ if (enableWebcompat) {
+ for (let item of extraPrefs) {
+ // When setting up exception URLs, we need to wait to ensure our prefs
+ // actually take effect. In order to do this, we set up a exception
+ // list observer and wait until it calls us back.
+ if (item[0] == "urlclassifier.trackingAnnotationSkipURLs") {
+ info("Waiting for the exception list service to initialize...");
+ let classifier = Cc[
+ "@mozilla.org/url-classifier/dbservice;1"
+ ].getService(Ci.nsIURIClassifier);
+ let feature = classifier.getFeatureByName("tracking-annotation");
+ await TestUtils.waitForCondition(() => {
+ for (let x of item[1].toLowerCase().split(",")) {
+ if (feature.exceptionHostList.split(",").includes(x)) {
+ return true;
+ }
+ }
+ return false;
+ }, "Exception list service initialized");
+ break;
+ }
+ }
+ }
+ }
+
+ await UrlClassifierTestUtils.addTestTrackers();
+ if (!gTestTrackersCleanupRegistered) {
+ registerCleanupFunction(_ => {
+ if (gTestTrackersCleanedUp) {
+ return;
+ }
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ gTestTrackersCleanedUp = true;
+ });
+ gTestTrackersCleanupRegistered = true;
+ }
+ },
+
+ _createTask(options) {
+ add_task(async function () {
+ info(
+ "Starting " +
+ (options.cookieBehavior != BEHAVIOR_ACCEPT
+ ? "blocking"
+ : "non-blocking") +
+ " cookieBehavior (" +
+ options.cookieBehavior +
+ ") with" +
+ (options.allowList ? "" : "out") +
+ " allow list test " +
+ options.name +
+ " running in a " +
+ (options.runInPrivateWindow ? "private" : "normal") +
+ " window " +
+ " with iframe sandbox set to " +
+ options.iframeSandbox +
+ " and access removal set to " +
+ options.accessRemoval +
+ (typeof options.thirdPartyPage == "string"
+ ? " and third party page set to " + options.thirdPartyPage
+ : "") +
+ (typeof options.topPage == "string"
+ ? " and top page set to " + options.topPage
+ : "")
+ );
+
+ is(
+ !!options.callbackAfterRemoval,
+ !!options.accessRemoval,
+ "callbackAfterRemoval must be passed when accessRemoval is non-null"
+ );
+
+ let win = window;
+ if (options.runInPrivateWindow) {
+ win = OpenBrowserWindow({ private: true });
+ await TestUtils.topicObserved("browser-delayed-startup-finished");
+ }
+
+ await AntiTracking._setupTest(
+ win,
+ options.cookieBehavior,
+ options.runInPrivateWindow,
+ options.extraPrefs
+ );
+
+ let topPage;
+ if (typeof options.topPage == "string") {
+ topPage = options.topPage;
+ } else {
+ topPage = TEST_TOP_PAGE;
+ }
+
+ let thirdPartyPage, thirdPartyDomainURI;
+ if (typeof options.thirdPartyPage == "string") {
+ thirdPartyPage = options.thirdPartyPage;
+ let url = new URL(thirdPartyPage);
+ thirdPartyDomainURI = Services.io.newURI(url.origin);
+ } else {
+ thirdPartyPage = TEST_3RD_PARTY_PAGE;
+ thirdPartyDomainURI = Services.io.newURI(TEST_3RD_PARTY_DOMAIN);
+ }
+
+ // It's possible that the third-party domain has been exceptionlisted
+ // through extraPrefs, so let's try annotating it here and adjust our
+ // blocking expectations as necessary.
+ if (
+ options.expectedBlockingNotifications ==
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER
+ ) {
+ if (
+ !(await AntiTracking._isThirdPartyPageClassifiedAsTracker(
+ topPage,
+ thirdPartyDomainURI
+ ))
+ ) {
+ options.expectedBlockingNotifications = 0;
+ }
+ }
+
+ let cookieBlocked = 0;
+ let { expectedBlockingNotifications } = options;
+ if (!Array.isArray(expectedBlockingNotifications)) {
+ expectedBlockingNotifications = [expectedBlockingNotifications];
+ }
+ let listener = {
+ onContentBlockingEvent(webProgress, request, event) {
+ for (const notification of expectedBlockingNotifications) {
+ if (event & notification) {
+ ++cookieBlocked;
+ }
+ }
+ },
+ };
+ function prepareTestEnvironmentOnPage() {
+ win.gBrowser.addProgressListener(listener);
+
+ Services.console.reset();
+ }
+
+ if (!options.allowList) {
+ prepareTestEnvironmentOnPage();
+ }
+
+ let consoleWarningPromise;
+
+ if (options.expectedBlockingNotifications) {
+ consoleWarningPromise = new Promise(resolve => {
+ let consoleListener = {
+ observe(msg) {
+ if (
+ msg
+ .QueryInterface(Ci.nsIScriptError)
+ .category.startsWith("cookieBlocked")
+ ) {
+ Services.console.unregisterListener(consoleListener);
+ resolve();
+ }
+ },
+ };
+
+ Services.console.registerListener(consoleListener);
+ });
+ } else {
+ consoleWarningPromise = Promise.resolve();
+ }
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(win.gBrowser, topPage);
+ win.gBrowser.selectedTab = tab;
+
+ let browser = win.gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Check the cookieJarSettings of the browser object");
+ ok(
+ browser.cookieJarSettings,
+ "The browser object has the cookieJarSettings."
+ );
+ is(
+ browser.cookieJarSettings.cookieBehavior,
+ options.cookieBehavior,
+ "The cookieJarSettings has the correct cookieBehavior"
+ );
+
+ if (options.allowList) {
+ info("Disabling content blocking for this page");
+ win.gProtectionsHandler.disableForCurrentPage();
+
+ prepareTestEnvironmentOnPage();
+
+ // The previous function reloads the browser, so wait for it to load again!
+ await BrowserTestUtils.browserLoaded(browser);
+ }
+
+ info("Creating a 3rd party content");
+ let doAccessRemovalChecks =
+ typeof options.accessRemoval == "string" &&
+ options.cookieBehavior == BEHAVIOR_REJECT_TRACKER &&
+ !options.allowList;
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: thirdPartyPage,
+ nextPage: TEST_4TH_PARTY_PAGE,
+ callback: options.callback.toString(),
+ callbackAfterRemoval: options.callbackAfterRemoval
+ ? options.callbackAfterRemoval.toString()
+ : null,
+ accessRemoval: options.accessRemoval,
+ iframeSandbox: options.iframeSandbox,
+ allowList: options.allowList,
+ doAccessRemovalChecks,
+ },
+ ],
+ async function (obj) {
+ let id = "id" + Math.random();
+ await new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.id = id;
+ ifr.onload = function () {
+ info("Sending code to the 3rd party content");
+ let callback = obj.allowList + "!!!" + obj.callback;
+ ifr.contentWindow.postMessage(callback, "*");
+ };
+ if (typeof obj.iframeSandbox == "string") {
+ ifr.setAttribute("sandbox", obj.iframeSandbox);
+ }
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ });
+
+ if (obj.doAccessRemovalChecks) {
+ info(`Running after removal checks (${obj.accessRemoval})`);
+ switch (obj.accessRemoval) {
+ case "navigate-subframe":
+ await new content.Promise(resolve => {
+ let ifr = content.document.getElementById(id);
+ let oldWindow = ifr.contentWindow;
+ ifr.onload = function () {
+ info("Sending code to the old 3rd party content");
+ oldWindow.postMessage(obj.callbackAfterRemoval, "*");
+ };
+ if (typeof obj.iframeSandbox == "string") {
+ ifr.setAttribute("sandbox", obj.iframeSandbox);
+ }
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ ifr.src = obj.nextPage;
+ });
+ break;
+ case "navigate-topframe":
+ // pass-through
+ break;
+ default:
+ ok(
+ false,
+ "Unexpected accessRemoval code passed: " + obj.accessRemoval
+ );
+ break;
+ }
+ }
+ }
+ );
+
+ if (
+ doAccessRemovalChecks &&
+ options.accessRemoval == "navigate-topframe"
+ ) {
+ BrowserTestUtils.loadURIString(browser, TEST_4TH_PARTY_PAGE);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ let pageshow = BrowserTestUtils.waitForContentEvent(
+ tab.linkedBrowser,
+ "pageshow"
+ );
+ gBrowser.goBack();
+ await pageshow;
+
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: thirdPartyPage,
+ callbackAfterRemoval: options.callbackAfterRemoval
+ ? options.callbackAfterRemoval.toString()
+ : null,
+ iframeSandbox: options.iframeSandbox,
+ },
+ ],
+ async function (obj) {
+ let ifr = content.document.createElement("iframe");
+ ifr.onload = function () {
+ info(
+ "Sending code to the 3rd party content to verify accessRemoval"
+ );
+ ifr.contentWindow.postMessage(obj.callbackAfterRemoval, "*");
+ };
+ if (typeof obj.iframeSandbox == "string") {
+ ifr.setAttribute("sandbox", obj.iframeSandbox);
+ }
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ }
+ );
+ }
+
+ // Wait until the message appears on the console.
+ await consoleWarningPromise;
+
+ let allMessages = Services.console.getMessageArray().filter(msg => {
+ try {
+ // Select all messages that the anti-tracking backend could generate.
+ return msg
+ .QueryInterface(Ci.nsIScriptError)
+ .category.startsWith("cookieBlocked");
+ } catch (e) {
+ return false;
+ }
+ });
+ // When changing this list, please make sure to update the corresponding
+ // code in ReportBlockingToConsole().
+ let expectedCategories = [];
+ let rawExpectedCategories = options.expectedBlockingNotifications;
+ if (!Array.isArray(rawExpectedCategories)) {
+ // if given a single value to match, expect each message to match it
+ rawExpectedCategories = Array(allMessages.length).fill(
+ rawExpectedCategories
+ );
+ }
+ for (let category of rawExpectedCategories) {
+ switch (category) {
+ case Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_BY_PERMISSION:
+ expectedCategories.push("cookieBlockedPermission");
+ break;
+ case Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER:
+ expectedCategories.push("cookieBlockedTracker");
+ break;
+ case Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_ALL:
+ expectedCategories.push("cookieBlockedAll");
+ break;
+ case Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_FOREIGN:
+ expectedCategories.push("cookieBlockedForeign");
+ break;
+ }
+ }
+
+ if (!expectedCategories.length) {
+ is(allMessages.length, 0, "No console messages should be generated");
+ } else {
+ ok(!!allMessages.length, "Some console message should be generated");
+ if (options.errorMessageDomains) {
+ is(
+ allMessages.length,
+ options.errorMessageDomains.length,
+ "Enough items provided in errorMessageDomains"
+ );
+ }
+ }
+ let index = 0;
+ for (let msg of allMessages) {
+ is(
+ msg.category,
+ expectedCategories[index],
+ `Message ${index} should be of expected category`
+ );
+
+ if (options.errorMessageDomains) {
+ ok(
+ msg.errorMessage.includes(options.errorMessageDomains[index]),
+ `Error message domain ${options.errorMessageDomains[index]} (${index}) found in "${msg.errorMessage}"`
+ );
+ index++;
+ }
+ }
+
+ if (options.allowList) {
+ info("Enabling content blocking for this page");
+ win.gProtectionsHandler.enableForCurrentPage();
+
+ // The previous function reloads the browser, so wait for it to load again!
+ await BrowserTestUtils.browserLoaded(browser);
+ }
+
+ win.gBrowser.removeProgressListener(listener);
+
+ if (!!cookieBlocked != !!options.expectedBlockingNotifications) {
+ ok(false, JSON.stringify(cookieBlocked));
+ ok(false, JSON.stringify(options.expectedBlockingNotifications));
+ }
+ is(
+ !!cookieBlocked,
+ !!options.expectedBlockingNotifications,
+ "Checking cookie blocking notifications"
+ );
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ if (options.runInPrivateWindow) {
+ win.close();
+ }
+ });
+ },
+
+ _createCleanupTask(cleanupFunction) {
+ add_task(async function () {
+ info("Cleaning up.");
+ if (cleanupFunction) {
+ await cleanupFunction();
+ }
+
+ // While running these tests we typically do not have enough idle time to do
+ // GC reliably, so force it here.
+ forceGC();
+ });
+ },
+
+ _createWindowOpenTask(
+ name,
+ cookieBehavior,
+ blockingCallback,
+ nonBlockingCallback,
+ runInPrivateWindow,
+ iframeSandbox,
+ testInSubIFrame,
+ extraPrefs
+ ) {
+ add_task(async function () {
+ info(
+ `Starting window-open${
+ testInSubIFrame ? " sub iframe" : ""
+ } test ${name}`
+ );
+
+ let win = window;
+ if (runInPrivateWindow) {
+ win = OpenBrowserWindow({ private: true });
+ await TestUtils.topicObserved("browser-delayed-startup-finished");
+ }
+
+ await AntiTracking._setupTest(
+ win,
+ cookieBehavior,
+ runInPrivateWindow,
+ extraPrefs
+ );
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(win.gBrowser, TEST_TOP_PAGE);
+ win.gBrowser.selectedTab = tab;
+
+ let browser = win.gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Create a first-level iframe to test sub iframes.");
+ if (testInSubIFrame) {
+ let iframeBrowsingContext = await SpecialPowers.spawn(
+ browser,
+ [{ page: TEST_IFRAME_PAGE }],
+ async function (obj) {
+ // Add an iframe.
+ let ifr = content.document.createElement("iframe");
+ let loading = new content.Promise(resolve => {
+ ifr.onload = resolve;
+ });
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ await loading;
+
+ return ifr.browsingContext;
+ }
+ );
+
+ browser = iframeBrowsingContext;
+ }
+
+ let pageURL = TEST_3RD_PARTY_PAGE_WO;
+ if (gFeatures == "noopener") {
+ pageURL += "?noopener";
+ }
+
+ info("Creating a 3rd party content");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: pageURL,
+ blockingCallback: blockingCallback.toString(),
+ nonBlockingCallback: nonBlockingCallback.toString(),
+ iframeSandbox,
+ },
+ ],
+ async function (obj) {
+ await new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.onload = function () {
+ info("Sending code to the 3rd party content");
+ ifr.contentWindow.postMessage(obj, "*");
+ };
+ if (typeof obj.iframeSandbox == "string") {
+ ifr.setAttribute("sandbox", obj.iframeSandbox);
+ }
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ });
+ }
+ );
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ if (runInPrivateWindow) {
+ win.close();
+ }
+ });
+ },
+
+ _createUserInteractionTask(
+ name,
+ cookieBehavior,
+ blockingCallback,
+ nonBlockingCallback,
+ runInPrivateWindow,
+ iframeSandbox,
+ testInSubIFrame,
+ extraPrefs
+ ) {
+ add_task(async function () {
+ info(
+ `Starting user-interaction${
+ testInSubIFrame ? " sub iframe" : ""
+ } test ${name}`
+ );
+
+ let win = window;
+ if (runInPrivateWindow) {
+ win = OpenBrowserWindow({ private: true });
+ await TestUtils.topicObserved("browser-delayed-startup-finished");
+ }
+
+ await AntiTracking._setupTest(
+ win,
+ cookieBehavior,
+ runInPrivateWindow,
+ extraPrefs
+ );
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(win.gBrowser, TEST_TOP_PAGE);
+ win.gBrowser.selectedTab = tab;
+
+ let browser = win.gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ if (testInSubIFrame) {
+ info("Create a first-level iframe to test sub iframes.");
+ let iframeBrowsingContext = await SpecialPowers.spawn(
+ browser,
+ [{ page: TEST_IFRAME_PAGE }],
+ async function (obj) {
+ // Add an iframe.
+ let ifr = content.document.createElement("iframe");
+ let loading = new content.Promise(resolve => {
+ ifr.onload = resolve;
+ });
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ await loading;
+
+ return ifr.browsingContext;
+ }
+ );
+
+ browser = iframeBrowsingContext;
+ }
+
+ // The following test will open an popup which interacts with the tracker
+ // page. So there will be an user-interaction permission added. We wait
+ // it explicitly.
+ let promiseUIPerm = AntiTracking._waitUserInteractionPerm();
+
+ info("Creating a 3rd party content");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: TEST_3RD_PARTY_PAGE_UI,
+ popup: TEST_POPUP_PAGE,
+ blockingCallback: blockingCallback.toString(),
+ iframeSandbox,
+ },
+ ],
+ async function (obj) {
+ let ifr = content.document.createElement("iframe");
+ let loading = new content.Promise(resolve => {
+ ifr.onload = resolve;
+ });
+ if (typeof obj.iframeSandbox == "string") {
+ ifr.setAttribute("sandbox", obj.iframeSandbox);
+ }
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ await loading;
+
+ info(
+ "The 3rd party content should not have access to first party storage."
+ );
+ await new content.Promise(resolve => {
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+ ifr.contentWindow.postMessage(
+ { callback: obj.blockingCallback },
+ "*"
+ );
+ });
+
+ let windowClosed = new content.Promise(resolve => {
+ Services.ww.registerNotification(function notification(
+ aSubject,
+ aTopic,
+ aData
+ ) {
+ if (aTopic == "domwindowclosed") {
+ Services.ww.unregisterNotification(notification);
+ resolve();
+ }
+ });
+ });
+
+ info("Opening a window from the iframe.");
+ SpecialPowers.spawn(
+ ifr,
+ [{ popup: obj.popup }],
+ async function (obj) {
+ content.open(obj.popup);
+ }
+ );
+
+ info("Let's wait for the window to be closed");
+ await windowClosed;
+
+ info(
+ "First time, the 3rd party content should not have access to first party storage " +
+ "because the tracker did not have user interaction"
+ );
+ await new content.Promise(resolve => {
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+ ifr.contentWindow.postMessage(
+ { callback: obj.blockingCallback },
+ "*"
+ );
+ });
+ }
+ );
+
+ // We wait until the user-interaction permission is added.
+ await promiseUIPerm;
+
+ // We also need to wait the user-interaction permission here.
+ promiseUIPerm = AntiTracking._waitUserInteractionPerm();
+ await AntiTracking.interactWithTracker();
+ await promiseUIPerm;
+
+ // Following test will also open an popup to interact with the page. We
+ // need to explicitly wait it. Without waiting it, it could be added after
+ // we clear up the test and interfere the next test.
+ promiseUIPerm = AntiTracking._waitUserInteractionPerm();
+
+ // We have to wait until the storage access permission is added. This has
+ // the same reason as above user-interaction permission. Note that there
+ // will be two storage access permission added due to the way how we
+ // trigger the heuristic. The first permission is added due to 'Opener'
+ // heuristic and the second one is due to 'Opener after user interaction'.
+ // The page we use to trigger the heuristic will trigger both heuristic,
+ // so we have to wait 2 permissions.
+ let promiseStorageAccessPerm = AntiTracking._waitStorageAccessPerm(2);
+
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: TEST_3RD_PARTY_PAGE_UI,
+ popup: TEST_POPUP_PAGE,
+ nonBlockingCallback: nonBlockingCallback.toString(),
+ iframeSandbox,
+ },
+ ],
+ async function (obj) {
+ let ifr = content.document.createElement("iframe");
+ let loading = new content.Promise(resolve => {
+ ifr.onload = resolve;
+ });
+ if (typeof obj.iframeSandbox == "string") {
+ ifr.setAttribute("sandbox", obj.iframeSandbox);
+ }
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ await loading;
+
+ let windowClosed = new content.Promise(resolve => {
+ Services.ww.registerNotification(function notification(
+ aSubject,
+ aTopic,
+ aData
+ ) {
+ if (aTopic == "domwindowclosed") {
+ Services.ww.unregisterNotification(notification);
+ resolve();
+ }
+ });
+ });
+
+ info("Opening a window from the iframe.");
+ SpecialPowers.spawn(
+ ifr,
+ [{ popup: obj.popup }],
+ async function (obj) {
+ content.open(obj.popup);
+ }
+ );
+
+ info("Let's wait for the window to be closed");
+ await windowClosed;
+
+ info(
+ "The 3rd party content should now have access to first party storage."
+ );
+ await new content.Promise(resolve => {
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+ ifr.contentWindow.postMessage(
+ { callback: obj.nonBlockingCallback },
+ "*"
+ );
+ });
+ }
+ );
+
+ // Explicitly wait the user-interaction and storage access permission
+ // before we do the cleanup.
+ await promiseUIPerm;
+ await promiseStorageAccessPerm;
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ if (runInPrivateWindow) {
+ win.close();
+ }
+ });
+ },
+
+ async _isThirdPartyPageClassifiedAsTracker(topPage, thirdPartyDomainURI) {
+ let channel;
+ await new Promise((resolve, reject) => {
+ channel = NetUtil.newChannel({
+ uri: thirdPartyDomainURI,
+ loadingPrincipal: Services.scriptSecurityManager.createContentPrincipal(
+ thirdPartyDomainURI,
+ {}
+ ),
+ securityFlags:
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ });
+
+ channel
+ .QueryInterface(Ci.nsIHttpChannelInternal)
+ .setTopWindowURIIfUnknown(Services.io.newURI(topPage));
+
+ function Listener() {}
+ Listener.prototype = {
+ onStartRequest(request) {},
+ onDataAvailable(request, stream, off, cnt) {
+ // Consume the data to prevent hitting the assertion.
+ NetUtil.readInputStreamToString(stream, cnt);
+ },
+ onStopRequest(request, st) {
+ let status = request.QueryInterface(Ci.nsIHttpChannel).responseStatus;
+ if (status == 200) {
+ resolve();
+ } else {
+ reject();
+ }
+ },
+ };
+ let listener = new Listener();
+ channel.asyncOpen(listener);
+ });
+
+ return !!(
+ channel.QueryInterface(Ci.nsIClassifiedChannel).classificationFlags &
+ Ci.nsIClassifiedChannel.CLASSIFIED_ANY_BASIC_TRACKING
+ );
+ },
+};
diff --git a/toolkit/components/antitracking/test/browser/browser-blocking.ini b/toolkit/components/antitracking/test/browser/browser-blocking.ini
new file mode 100644
index 0000000000..1533d1273f
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser-blocking.ini
@@ -0,0 +1,72 @@
+[DEFAULT]
+skip-if = os == "linux" && (asan || tsan) # bug 1662229 - task exception
+prefs =
+ # Disable the Storage Access API prompts for all of the tests in this directory
+ dom.storage_access.prompt.testing=true
+ dom.storage_access.prompt.testing.allow=true
+ dom.testing.sync-content-blocking-notifications=true
+ # Enable the window.open() heuristics globally in this directory
+ privacy.restrict3rdpartystorage.heuristic.window_open=true
+ privacy.restrict3rdpartystorage.heuristic.opened_window_after_interaction=true
+ # Disable https-first because of explicit http/https testing
+ dom.security.https_first=false
+
+support-files =
+ head.js
+ antitracking_head.js
+ iframe.html
+ image.sjs
+ page.html
+ 3rdParty.html
+ 3rdPartyRelay.html
+ 3rdPartySVG.html
+ 3rdPartyUI.html
+ 3rdPartyWO.html
+ 3rdPartyWorker.html
+ 3rdPartyOpen.html
+ 3rdPartyOpenUI.html
+ empty.js
+ popup.html
+ server.sjs
+ storageAccessAPIHelpers.js
+ 3rdPartyStorage.html
+ 3rdPartyStorageWO.html
+ 3rdPartyPartitioned.html
+ localStorage.html
+ !/browser/modules/test/browser/head.js
+ !/browser/base/content/test/general/head.js
+ !/browser/base/content/test/protectionsUI/cookieServer.sjs
+ !/browser/base/content/test/protectionsUI/trackingPage.html
+ !/browser/base/content/test/protectionsUI/trackingAPI.js
+
+[browser_blockingCookies.js]
+skip-if = socketprocess_networking
+[browser_blockingDOMCacheSAA.js]
+skip-if = socketprocess_networking
+[browser_blockingDOMCacheAlwaysPartition.js]
+skip-if = socketprocess_networking
+[browser_blockingDOMCacheAlwaysPartitionSAA.js]
+skip-if = socketprocess_networking
+[browser_blockingDOMCache.js]
+[browser_blockingIndexedDb.js]
+skip-if = os == 'linux' && socketprocess_networking
+[browser_blockingIndexedDbInWorkers.js]
+skip-if = os == 'linux' && socketprocess_networking
+[browser_blockingIndexedDbInWorkers2.js]
+[browser_blockingLocalStorage.js]
+skip-if = os == 'linux' && socketprocess_networking
+[browser_blockingSessionStorage.js]
+[browser_blockingServiceWorkers.js]
+[browser_blockingServiceWorkersStorageAccessAPI.js]
+[browser_blockingSharedWorkers.js]
+skip-if = os == 'linux' && socketprocess_networking
+[browser_blockingMessaging.js]
+skip-if = os == "linux" && debug #bug 1627094
+[browser_blockingNoOpener.js]
+[browser_contentBlockingAllowListPrincipal.js]
+support-files =
+ sandboxed.html
+ sandboxed.html^headers^
+[browser_contentBlockingTelemetry.js]
+skip-if =
+ win10_2004 && fission && !debug # high frequency intermittent, same as bug 1727097
diff --git a/toolkit/components/antitracking/test/browser/browser.ini b/toolkit/components/antitracking/test/browser/browser.ini
new file mode 100644
index 0000000000..52b25a4b60
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser.ini
@@ -0,0 +1,226 @@
+[DEFAULT]
+skip-if =
+ os == "linux" && (asan || tsan) # bug 1662229 - task exception
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+prefs =
+ # Disable the Storage Access API prompts for all of the tests in this directory
+ dom.storage_access.prompt.testing=true
+ dom.storage_access.prompt.testing.allow=true
+ dom.testing.sync-content-blocking-notifications=true
+ # Enable the window.open() heuristics globally in this directory
+ privacy.restrict3rdpartystorage.heuristic.window_open=true
+ privacy.restrict3rdpartystorage.heuristic.opened_window_after_interaction=true
+ # Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default"
+ network.cookie.sameSite.laxByDefault=false
+ # Disable https-first because of explicit http/https testing
+ dom.security.https_first=false
+
+support-files =
+ container.html
+ container2.html
+ embedder.html
+ embedder2.html
+ head.js
+ antitracking_head.js
+ dynamicfpi_head.js
+ partitionedstorage_head.js
+ storage_access_head.js
+ cookiesCORS.sjs
+ iframe.html
+ image.sjs
+ imageCacheWorker.js
+ page.html
+ 3rdParty.html
+ 3rdPartyRelay.html
+ 3rdPartySVG.html
+ 3rdPartyUI.html
+ 3rdPartyWO.html
+ 3rdPartyWorker.html
+ 3rdPartyOpen.html
+ 3rdPartyOpenUI.html
+ empty.js
+ empty-altsvc.js
+ empty-altsvc.js^headers^
+ empty.html
+ file_iframe_document_open.html
+ file_localStorage.html
+ popup.html
+ redirect.sjs
+ server.sjs
+ storageAccessAPIHelpers.js
+ 3rdPartyStorage.html
+ 3rdPartyStorageWO.html
+ 3rdPartyPartitioned.html
+ localStorage.html
+ raptor.jpg
+ !/browser/modules/test/browser/head.js
+ !/browser/base/content/test/general/head.js
+ !/browser/base/content/test/protectionsUI/cookieServer.sjs
+ !/browser/base/content/test/protectionsUI/trackingPage.html
+ !/browser/base/content/test/protectionsUI/trackingAPI.js
+ !/toolkit/content/tests/browser/common/mockTransfer.js
+
+[browser_aboutblank.js]
+[browser_allowListNotifications.js]
+[browser_allowListNotifications_alwaysPartition.js]
+support-files = subResources.sjs
+[browser_addonHostPermissionIgnoredInTP.js]
+[browser_allowListSeparationInPrivateAndNormalWindows.js]
+skip-if = os == "mac" && !debug # Bug 1503778, 1577362
+[browser_backgroundImageAssertion.js]
+[browser_doublyNestedTracker.js]
+[browser_emailtracking.js]
+[browser_existingCookiesForSubresources.js]
+[browser_fileUrl.js]
+[browser_firstPartyCookieRejectionHonoursAllowList.js]
+[browser_fpiServiceWorkers_fingerprinting.js]
+[browser_hasStorageAccess.js]
+[browser_hasStorageAccess_alwaysPartition.js]
+[browser_iframe_document_open.js]
+[browser_imageCache4.js]
+[browser_imageCache8.js]
+[browser_onBeforeRequestNotificationForTrackingResources.js]
+[browser_onModifyRequestNotificationForTrackingResources.js]
+[browser_permissionInNormalWindows.js]
+[browser_permissionInNormalWindows_alwaysPartition.js]
+[browser_permissionInPrivateWindows.js]
+[browser_permissionInPrivateWindows_alwaysPartition.js]
+[browser_permissionPropagation.js]
+skip-if =
+ os == "linux" && bits == 64 # Bug 1645505
+ os == "win" && debug # Bug 1645505
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+[browser_referrerDefaultPolicy.js]
+support-files = referrer.sjs
+[browser_siteSpecificWorkArounds.js]
+[browser_subResources.js]
+support-files = subResources.sjs
+[browser_subResourcesPartitioned.js]
+support-files = subResources.sjs
+[browser_subResourcesPartitioned_alwaysPartition.js]
+support-files = subResources.sjs
+[browser_script.js]
+support-files = tracker.js
+[browser_userInteraction.js]
+[browser_serviceWorkersWithStorageAccessGranted.js]
+[browser_storageAccess_TopLevel_Arguments.js]
+[browser_storageAccess_TopLevel_CookieBehavior.js]
+[browser_storageAccess_TopLevel_CookiePermission.js]
+[browser_storageAccess_TopLevel_CrossOriginSameSite.js]
+[browser_storageAccess_TopLevel_Doorhanger.js]
+[browser_storageAccess_TopLevel_Embed.js]
+[browser_storageAccess_TopLevel_Enable.js]
+[browser_storageAccess_TopLevel_RequireIntermediatePermission.js]
+[browser_storageAccess_TopLevel_StorageAccessPermission.js]
+[browser_storageAccess_TopLevel_UserActivation.js]
+skip-if = debug # Bug 1700551
+[browser_storageAccessAutograntRequiresUserInteraction.js]
+[browser_storageAccessDeniedGivesNoUserInteraction.js]
+[browser_storageAccessDoorHanger.js]
+[browser_storageAccessFrameInteractionGrantsUserInteraction.js]
+[browser_storageAccessGrantedGivesUserInteraction.js]
+[browser_storageAccessPrivilegeAPI.js]
+[browser_storageAccessPromiseRejectHandlerUserInteraction.js]
+[browser_storageAccessPromiseRejectHandlerUserInteraction_alwaysPartition.js]
+[browser_storageAccessPromiseResolveHandlerUserInteraction.js]
+[browser_storageAccessRemovalNavigateSubframe.js]
+[browser_storageAccessRemovalNavigateSubframe_alwaysPartition.js]
+[browser_storageAccessRemovalNavigateTopframe.js]
+[browser_storageAccessRemovalNavigateTopframe_alwaysPartition.js]
+[browser_storageAccessSandboxed.js]
+[browser_storageAccessSandboxed_alwaysPartition.js]
+[browser_storageAccessScopeDifferentSite.js]
+[browser_storageAccessScopeSameOrigin.js]
+[browser_storageAccessScopeSameSiteRead.js]
+[browser_storageAccessScopeSameSiteWrite.js]
+[browser_storageAccessThirdPartyChecks.js]
+[browser_storageAccessThirdPartyChecks_alwaysPartition.js]
+[browser_storageAccessWithDynamicFpi.js]
+[browser_storageAccessWithHeuristics.js]
+[browser_allowPermissionForTracker.js]
+[browser_denyPermissionForTracker.js]
+[browser_localStorageEvents.js]
+[browser_partitionedLocalStorage.js]
+[browser_partitionedLocalStorage_events.js]
+support-files = localStorageEvents.html
+[browser_workerPropagation.js]
+support-files = workerIframe.html
+[browser_cookieBetweenTabs.js]
+[browser_partitionedMessaging.js]
+skip-if = true #Bug 1588241
+[browser_partitionedIndexedDB.js]
+[browser_partitionedConsoleMessage.js]
+[browser_partitionedCookies.js]
+support-files = cookies.sjs
+[browser_partitionedDOMCache.js]
+[browser_partitionedServiceWorkers.js]
+support-files = dedicatedWorker.js matchAll.js serviceWorker.js
+[browser_partitionedSharedWorkers.js]
+support-files = sharedWorker.js partitionedSharedWorker.js
+[browser_PBMCookieBehavior.js]
+[browser_socialtracking.js]
+[browser_socialtracking_save_image.js]
+[browser_thirdPartyStorageRejectionForCORS.js]
+[browser_urlDecorationStripping.js]
+[browser_urlDecorationStripping_alwaysPartition.js]
+tags = remote-settings
+[browser_urlQueryStringStripping_allowList.js]
+support-files = file_stripping.html
+[browser_urlQueryStringStripping_telemetry.js]
+support-files = file_stripping.html
+[browser_urlQueryStringStripping_telemetry_2.js]
+support-files = file_stripping.html
+[browser_urlQueryStringStripping.js]
+skip-if =
+ fission && os == "linux" && asan # Bug 1713909 - new Fission platform triage
+support-files = file_stripping.html
+[browser_urlQueryStringStripping_pbmode.js]
+support-files = file_stripping.html
+[browser_urlQueryStringStripping_nimbus.js]
+support-files = file_stripping.html
+[browser_urlQueryStrippingListService.js]
+[browser_staticPartition_cache.js]
+support-files =
+ !/browser/components/originattributes/test/browser/file_cache.html
+ !/browser/components/originattributes/test/browser/file_thirdPartyChild.audio.ogg
+ !/browser/components/originattributes/test/browser/file_thirdPartyChild.embed.png
+ !/browser/components/originattributes/test/browser/file_thirdPartyChild.fetch.html
+ !/browser/components/originattributes/test/browser/file_thirdPartyChild.iframe.html
+ !/browser/components/originattributes/test/browser/file_thirdPartyChild.img.png
+ !/browser/components/originattributes/test/browser/file_thirdPartyChild.favicon.png
+ !/browser/components/originattributes/test/browser/file_thirdPartyChild.import.js
+ !/browser/components/originattributes/test/browser/file_thirdPartyChild.link.css
+ !/browser/components/originattributes/test/browser/file_thirdPartyChild.object.png
+ !/browser/components/originattributes/test/browser/file_thirdPartyChild.request.html
+ !/browser/components/originattributes/test/browser/file_thirdPartyChild.script.js
+ !/browser/components/originattributes/test/browser/file_thirdPartyChild.sharedworker.js
+ !/browser/components/originattributes/test/browser/file_thirdPartyChild.video.ogv
+ !/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.fetch.html
+ !/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.js
+ !/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.request.html
+ !/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.xhr.html
+ !/browser/components/originattributes/test/browser/file_thirdPartyChild.xhr.html
+[browser_staticPartition_network.js]
+[browser_staticPartition_CORS_preflight.js]
+support-files = browser_staticPartition_CORS_preflight.sjs
+[browser_staticPartition_HSTS.js]
+support-files = browser_staticPartition_HSTS.sjs
+[browser_staticPartition_saveAs.js]
+skip-if =
+ os == "linux" && bits == 64 # Bug 1775746
+ win10_2004 && debug # Bug 1775746
+support-files =
+ file_saveAsImage.sjs
+ file_saveAsVideo.sjs
+ file_saveAsPageInfo.html
+ file_video.ogv
+[browser_staticPartition_tls_session.js]
+[browser_staticPartition_websocket.js]
+skip-if =
+ os == 'mac' && verify # Bug 1721210
+support-files =
+ file_ws_handshake_delay_wsh.py
+[browser_partitionedClearSiteDataHeader.js]
+support-files =
+ clearSiteData.sjs
+[browser_AntiTrackingETPHeuristic.js]
diff --git a/toolkit/components/antitracking/test/browser/browser_AntiTrackingETPHeuristic.js b/toolkit/components/antitracking/test/browser/browser_AntiTrackingETPHeuristic.js
new file mode 100644
index 0000000000..f5274d8ba9
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_AntiTrackingETPHeuristic.js
@@ -0,0 +1,261 @@
+"use strict";
+
+const TEST_PAGE = TEST_DOMAIN + TEST_PATH + "page.html";
+const TEST_REDIRECT_PAGE = TEST_DOMAIN + TEST_PATH + "redirect.sjs";
+const TEST_TRACKING_PAGE = TEST_3RD_PARTY_DOMAIN + TEST_PATH + "page.html";
+const TEST_TRACKING_REDIRECT_PAGE =
+ TEST_3RD_PARTY_DOMAIN + TEST_PATH + "redirect.sjs";
+const TEST_ANOTHER_TRACKING_REDIRECT_PAGE =
+ TEST_ANOTHER_3RD_PARTY_DOMAIN_HTTPS + TEST_PATH + "redirect.sjs";
+
+const TEST_CASES = [
+ // Tracker(Interacted) -> Non-Tracker
+ {
+ trackersHasUserInteraction: [TEST_3RD_PARTY_DOMAIN],
+ redirects: [TEST_TRACKING_REDIRECT_PAGE, TEST_PAGE],
+ expectedPermissionNumber: 1,
+ expects: [
+ [
+ TEST_DOMAIN,
+ TEST_3RD_PARTY_DOMAIN,
+ Ci.nsIPermissionManager.ALLOW_ACTION,
+ ],
+ ],
+ },
+ // Tracker(No interaction) -> Non-Tracker
+ {
+ trackersHasUserInteraction: [],
+ redirects: [TEST_TRACKING_REDIRECT_PAGE, TEST_PAGE],
+ expectedPermissionNumber: 0,
+ expects: [
+ [
+ TEST_DOMAIN,
+ TEST_3RD_PARTY_DOMAIN,
+ Ci.nsIPermissionManager.UNKNOWN_ACTION,
+ ],
+ ],
+ },
+ // Non-Tracker -> Tracker(Interacted) -> Non-Tracker
+ {
+ trackersHasUserInteraction: [TEST_3RD_PARTY_DOMAIN],
+ redirects: [TEST_REDIRECT_PAGE, TEST_TRACKING_REDIRECT_PAGE, TEST_PAGE],
+ expectedPermissionNumber: 1,
+ expects: [
+ [
+ TEST_DOMAIN,
+ TEST_3RD_PARTY_DOMAIN,
+ Ci.nsIPermissionManager.ALLOW_ACTION,
+ ],
+ ],
+ },
+ // Tracker(Interacted) -> Tracker(Interacted) -> Tracker(Interacted)
+ {
+ trackersHasUserInteraction: [TEST_3RD_PARTY_DOMAIN],
+ redirects: [
+ TEST_TRACKING_REDIRECT_PAGE,
+ TEST_TRACKING_REDIRECT_PAGE,
+ TEST_TRACKING_PAGE,
+ ],
+ expectedPermissionNumber: 0,
+ expects: [
+ [
+ TEST_3RD_PARTY_DOMAIN,
+ TEST_3RD_PARTY_DOMAIN,
+ Ci.nsIPermissionManager.UNKNOWN_ACTION,
+ ],
+ ],
+ },
+ // Tracker1(Interacted) -> Tracker2(Interacted) -> Non-Tracker
+ {
+ trackersHasUserInteraction: [
+ TEST_3RD_PARTY_DOMAIN,
+ TEST_ANOTHER_3RD_PARTY_DOMAIN_HTTPS,
+ ],
+ redirects: [
+ TEST_TRACKING_REDIRECT_PAGE,
+ TEST_ANOTHER_TRACKING_REDIRECT_PAGE,
+ TEST_PAGE,
+ ],
+ expectedPermissionNumber: 1,
+ expects: [
+ [
+ TEST_DOMAIN,
+ TEST_ANOTHER_3RD_PARTY_DOMAIN_HTTPS,
+ Ci.nsIPermissionManager.ALLOW_ACTION,
+ ],
+ ],
+ },
+ // Tracker1(Interacted) -> Non-Tracker -> Tracker2(No interaction) -> Non-Tracker
+ {
+ trackersHasUserInteraction: [TEST_3RD_PARTY_DOMAIN],
+ redirects: [
+ TEST_TRACKING_REDIRECT_PAGE,
+ TEST_REDIRECT_PAGE,
+ TEST_ANOTHER_TRACKING_REDIRECT_PAGE,
+ TEST_PAGE,
+ ],
+ expectedPermissionNumber: 1,
+ expects: [
+ [
+ TEST_DOMAIN,
+ TEST_3RD_PARTY_DOMAIN,
+ Ci.nsIPermissionManager.ALLOW_ACTION,
+ ],
+ ],
+ },
+ // Tracker1(Interacted) -> Non-Tracker -> Tracker2(Interacted) -> Non-Tracker
+ // Note that the result is not quite correct in this case. We are supposed to
+ // grant access to the least tracker instead of the first one. But, this is
+ // the behavior how we act so far. We would fix this in another bug.
+ {
+ trackersHasUserInteraction: [
+ TEST_3RD_PARTY_DOMAIN,
+ TEST_ANOTHER_3RD_PARTY_DOMAIN_HTTPS,
+ ],
+ redirects: [
+ TEST_TRACKING_REDIRECT_PAGE,
+ TEST_REDIRECT_PAGE,
+ TEST_ANOTHER_TRACKING_REDIRECT_PAGE,
+ TEST_PAGE,
+ ],
+ expectedPermissionNumber: 1,
+ expects: [
+ [
+ TEST_DOMAIN,
+ TEST_3RD_PARTY_DOMAIN,
+ Ci.nsIPermissionManager.ALLOW_ACTION,
+ ],
+ ],
+ },
+ // Tracker(Interacted) -> Non-Tracker (heuristic disabled)
+ {
+ trackersHasUserInteraction: [TEST_3RD_PARTY_DOMAIN],
+ redirects: [TEST_TRACKING_REDIRECT_PAGE, TEST_PAGE],
+ expectedPermissionNumber: 0,
+ expects: [
+ [
+ TEST_DOMAIN,
+ TEST_3RD_PARTY_DOMAIN,
+ Ci.nsIPermissionManager.UNKNOWN_ACTION,
+ ],
+ ],
+ extraPrefs: [["privacy.antitracking.enableWebcompat", false]],
+ },
+];
+
+async function interactWithSpecificTracker(aTracker) {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ await BrowserTestUtils.withNewTab(
+ { gBrowser: win.gBrowser, url: aTracker },
+ async function (browser) {
+ info("Let's interact with the tracker");
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ SpecialPowers.wrap(content.document).userInteractionForTesting();
+ });
+ }
+ );
+ await BrowserTestUtils.closeWindow(win);
+}
+
+function getNumberOfStorageAccessPermissions() {
+ let num = 0;
+ for (let perm of Services.perms.all) {
+ if (perm.type.startsWith("3rdPartyStorage^")) {
+ num++;
+ }
+ }
+ return num;
+}
+
+async function verifyStorageAccessPermission(aExpects) {
+ for (let expect of aExpects) {
+ let uri = Services.io.newURI(expect[0]);
+ let principal = Services.scriptSecurityManager.createContentPrincipal(
+ uri,
+ {}
+ );
+ let access = Services.perms.testPermissionFromPrincipal(
+ principal,
+ `3rdPartyStorage^${expect[1].slice(0, -1)}`
+ );
+
+ is(access, expect[2], "The storage access is set correctly");
+ }
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["network.cookie.cookieBehavior", BEHAVIOR_REJECT_TRACKER],
+ ["privacy.trackingprotection.annotate_channels", true],
+ [
+ "privacy.restrict3rdpartystorage.userInteractionRequiredForHosts",
+ "tracking.example.com,tracking.example.org",
+ ],
+ ["privacy.restrict3rdpartystorage.heuristic.redirect", true],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ registerCleanupFunction(_ => {
+ Services.perms.removeAll();
+ });
+});
+
+add_task(async function testETPRedirectHeuristic() {
+ info("Starting testing ETP redirect heuristic ...");
+
+ for (const test of TEST_CASES) {
+ let { extraPrefs } = test;
+ if (extraPrefs) {
+ await SpecialPowers.pushPrefEnv({
+ set: extraPrefs,
+ });
+ }
+
+ // First, clear all permissions.
+ Services.perms.removeAll();
+
+ for (const tracker of test.trackersHasUserInteraction) {
+ info(`Interact with ${tracker} in top-level.`);
+ await interactWithSpecificTracker(tracker);
+ }
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Loading the tracking page and trigger the top-level redirect.");
+ SpecialPowers.spawn(browser, [test.redirects], async redirects => {
+ let link = content.document.createElement("a");
+ link.appendChild(content.document.createTextNode("click me!"));
+ link.href = redirects.shift() + "?" + redirects.join("|");
+ content.document.body.appendChild(link);
+ link.click();
+ });
+
+ let finalRedirectDest = test.redirects[test.redirects.length - 1];
+
+ await BrowserTestUtils.browserLoaded(browser, false, finalRedirectDest);
+
+ is(
+ getNumberOfStorageAccessPermissions(),
+ test.expectedPermissionNumber,
+ "The number of storage permissions is correct."
+ );
+
+ await verifyStorageAccessPermission(test.expects);
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ if (extraPrefs) {
+ await SpecialPowers.popPrefEnv();
+ }
+ }
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_PBMCookieBehavior.js b/toolkit/components/antitracking/test/browser/browser_PBMCookieBehavior.js
new file mode 100644
index 0000000000..ebed00f23c
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_PBMCookieBehavior.js
@@ -0,0 +1,108 @@
+"use strict";
+
+// This test will run all combinations of CookieBehavior. So, request a longer
+// timeout here
+requestLongerTimeout(3);
+
+const COOKIE_BEHAVIORS = [
+ Ci.nsICookieService.BEHAVIOR_ACCEPT,
+ Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN,
+ Ci.nsICookieService.BEHAVIOR_REJECT,
+ Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN,
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+];
+
+async function verifyCookieBehavior(browser, expected) {
+ await SpecialPowers.spawn(
+ browser,
+ [{ expected, page: TEST_3RD_PARTY_PAGE }],
+ async obj => {
+ is(
+ content.document.cookieJarSettings.cookieBehavior,
+ obj.expected,
+ "The tab in the window has the expected CookieBehavior."
+ );
+
+ // Create an 3rd party iframe and check the cookieBehavior.
+ let ifr = content.document.createElement("iframe");
+ let loading = new content.Promise(resolve => {
+ ifr.onload = resolve;
+ });
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ await loading;
+
+ await SpecialPowers.spawn(
+ ifr.browsingContext,
+ [obj.expected],
+ async expected => {
+ is(
+ content.document.cookieJarSettings.cookieBehavior,
+ expected,
+ "The iframe in the window has the expected CookieBehavior."
+ );
+ }
+ );
+ }
+ );
+}
+
+add_task(async function () {
+ for (let regularCookieBehavior of COOKIE_BEHAVIORS) {
+ for (let PBMCookieBehavior of COOKIE_BEHAVIORS) {
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["network.cookie.cookieBehavior", regularCookieBehavior],
+ ["network.cookie.cookieBehavior.pbmode", PBMCookieBehavior],
+ ["dom.security.https_first_pbm", false],
+ ],
+ });
+
+ info(
+ ` Start testing with regular cookieBehavior(${regularCookieBehavior}) and PBM cookieBehavior(${PBMCookieBehavior})`
+ );
+
+ info(" Open a tab in regular window.");
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_TOP_PAGE
+ );
+
+ info(
+ " Verify if the tab in regular window has the expected cookieBehavior."
+ );
+ await verifyCookieBehavior(tab.linkedBrowser, regularCookieBehavior);
+ BrowserTestUtils.removeTab(tab);
+
+ info(" Open a tab in private window.");
+ let pb_win = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+
+ tab = await BrowserTestUtils.openNewForegroundTab(
+ pb_win.gBrowser,
+ TEST_TOP_PAGE
+ );
+
+ let expectPBMCookieBehavior = PBMCookieBehavior;
+
+ // The private cookieBehavior will mirror the regular pref if the regular
+ // pref has a user value and the private pref doesn't have a user pref.
+ if (
+ Services.prefs.prefHasUserValue("network.cookie.cookieBehavior") &&
+ !Services.prefs.prefHasUserValue("network.cookie.cookieBehavior.pbmode")
+ ) {
+ expectPBMCookieBehavior = regularCookieBehavior;
+ }
+
+ info(
+ " Verify if the tab in private window has the expected cookieBehavior."
+ );
+ await verifyCookieBehavior(tab.linkedBrowser, expectPBMCookieBehavior);
+ BrowserTestUtils.removeTab(tab);
+ await BrowserTestUtils.closeWindow(pb_win);
+ }
+ }
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_aboutblank.js b/toolkit/components/antitracking/test/browser/browser_aboutblank.js
new file mode 100644
index 0000000000..f80a948771
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_aboutblank.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_aboutblankInIframe() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "network.cookie.cookieBehavior",
+ BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ ],
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_TOP_PAGE
+ );
+ let browser = tab.linkedBrowser;
+
+ await SpecialPowers.spawn(browser, [], async function (obj) {
+ let ifr = content.document.createElement("iframe");
+ let loading = new content.Promise(resolve => {
+ ifr.onload = resolve;
+ });
+ ifr.src = "about:blank";
+ content.document.body.appendChild(ifr);
+ await loading;
+
+ await SpecialPowers.spawn(ifr, [], async function (obj) {
+ ok(
+ content.navigator.cookieEnabled,
+ "Cookie should be enabled in about blank"
+ );
+ });
+ });
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_addonHostPermissionIgnoredInTP.js b/toolkit/components/antitracking/test/browser/browser_addonHostPermissionIgnoredInTP.js
new file mode 100644
index 0000000000..44664e239a
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_addonHostPermissionIgnoredInTP.js
@@ -0,0 +1,46 @@
+add_task(async function () {
+ info("Starting test");
+
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.trackingprotection.enabled", true]],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: { permissions: ["https://tracking.example.com/"] },
+ files: {
+ "page.html":
+ '<html><head></head><body><script src="script.js"></script><iframe src="https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/container2.html"></iframe></body></html>',
+ "script.js":
+ 'window.count=0;window.p=new Promise(resolve=>{onmessage=e=>{count=e.data.data;resolve();};});p.then(()=>{document.documentElement.setAttribute("count",count);});',
+ },
+ async background() {
+ browser.test.sendMessage("ready", browser.runtime.getURL("page.html"));
+ },
+ });
+ await extension.startup();
+ let url = await extension.awaitMessage("ready");
+
+ info("Creating a new tab");
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+ let browser = tab.linkedBrowser;
+
+ info("Verify the number of script nodes found");
+ await ContentTask.spawn(browser, [], async function (obj) {
+ // Need to wait a bit for cross-process postMessage...
+ await ContentTaskUtils.waitForCondition(
+ () => content.document.documentElement.getAttribute("count") !== null,
+ "waiting for 'count' attribute"
+ );
+ let count = content.document.documentElement.getAttribute("count");
+ is(count, 3, "Expected script nodes found");
+ });
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ await extension.unload();
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_allowListNotifications.js b/toolkit/components/antitracking/test/browser/browser_allowListNotifications.js
new file mode 100644
index 0000000000..999c6f93a0
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_allowListNotifications.js
@@ -0,0 +1,141 @@
+add_task(async function () {
+ info("Starting subResources test");
+
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ [
+ "privacy.partition.always_partition_third_party_non_cookie_storage",
+ false,
+ ],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ gProtectionsHandler.disableForCurrentPage();
+
+ // The previous function reloads the browser, so wait for it to load again!
+ await BrowserTestUtils.browserLoaded(browser);
+
+ // Now load subresources from a few third-party origins.
+ // We should expect to see none of these origins in the content blocking log at the end.
+ await fetch(
+ "https://test1.example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?result&what=image"
+ )
+ .then(r => r.text())
+ .then(text => {
+ is(text, "0", "Cookies received for images");
+ });
+
+ await fetch(
+ "https://test2.example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?result&what=image"
+ )
+ .then(r => r.text())
+ .then(text => {
+ is(text, "0", "Cookies received for images");
+ });
+
+ info("Creating a 3rd party content");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: TEST_3RD_PARTY_PAGE,
+ blockingCallback: (async _ => {}).toString(),
+ nonBlockingCallback: (async _ => {}).toString(),
+ },
+ ],
+ async function (obj) {
+ await new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.onload = function () {
+ info("Sending code to the 3rd party content");
+ ifr.contentWindow.postMessage(obj.blockingCallback, "*");
+ };
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ });
+ }
+ );
+
+ let expectTrackerFound = item => {
+ is(
+ item[0],
+ Ci.nsIWebProgressListener.STATE_LOADED_LEVEL_1_TRACKING_CONTENT,
+ "Correct blocking type reported"
+ );
+ is(item[1], true, "Correct blocking status reported");
+ ok(item[2] >= 1, "Correct repeat count reported");
+ };
+
+ let log = JSON.parse(await browser.getContentBlockingLog());
+ for (let trackerOrigin in log) {
+ is(
+ trackerOrigin + "/",
+ TEST_3RD_PARTY_DOMAIN,
+ "Correct tracker origin must be reported"
+ );
+ let originLog = log[trackerOrigin];
+ is(originLog.length, 1, "We should have 1 entry in the compressed log");
+ expectTrackerFound(originLog[0]);
+ }
+
+ gProtectionsHandler.enableForCurrentPage();
+
+ // The previous function reloads the browser, so wait for it to load again!
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+});
+
+add_task(async function () {
+ info("Cleaning up.");
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_allowListNotifications_alwaysPartition.js b/toolkit/components/antitracking/test/browser/browser_allowListNotifications_alwaysPartition.js
new file mode 100644
index 0000000000..0fd677e27f
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_allowListNotifications_alwaysPartition.js
@@ -0,0 +1,142 @@
+add_task(async function () {
+ info("Starting subResources test");
+
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ [
+ "privacy.partition.always_partition_third_party_non_cookie_storage",
+ true,
+ ],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ gProtectionsHandler.disableForCurrentPage();
+
+ // The previous function reloads the browser, so wait for it to load again!
+ await BrowserTestUtils.browserLoaded(browser);
+
+ // Now load subresources from a few third-party origins.
+ // We should expect to see none of these origins in the content blocking log at the end.
+ await fetch(
+ "https://test1.example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?result&what=image"
+ )
+ .then(r => r.text())
+ .then(text => {
+ is(text, "0", "Cookies received for images");
+ });
+
+ await fetch(
+ "https://test2.example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?result&what=image"
+ )
+ .then(r => r.text())
+ .then(text => {
+ is(text, "0", "Cookies received for images");
+ });
+
+ info("Creating a 3rd party content");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: TEST_3RD_PARTY_PAGE,
+ blockingCallback: (async _ => {}).toString(),
+ nonBlockingCallback: (async _ => {}).toString(),
+ },
+ ],
+ async function (obj) {
+ await new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.onload = function () {
+ info("Sending code to the 3rd party content");
+ ifr.contentWindow.postMessage(obj.blockingCallback, "*");
+ };
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ });
+ }
+ );
+
+ const nsIWPL = Ci.nsIWebProgressListener;
+ let expectTrackerFound = (item, expect) => {
+ is(item[0], expect, "Correct blocking type reported");
+ is(item[1], true, "Correct blocking status reported");
+ ok(item[2] >= 1, "Correct repeat count reported");
+ };
+
+ let log = JSON.parse(await browser.getContentBlockingLog());
+ for (let trackerOrigin in log) {
+ is(
+ trackerOrigin + "/",
+ TEST_3RD_PARTY_DOMAIN,
+ "Correct tracker origin must be reported"
+ );
+ let originLog = log[trackerOrigin];
+
+ is(originLog.length, 1, "We should have one entry in the compressed log");
+ expectTrackerFound(
+ originLog[0],
+ nsIWPL.STATE_LOADED_LEVEL_1_TRACKING_CONTENT
+ );
+ }
+
+ gProtectionsHandler.enableForCurrentPage();
+
+ // The previous function reloads the browser, so wait for it to load again!
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+});
+
+add_task(async function () {
+ info("Cleaning up.");
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_allowListSeparationInPrivateAndNormalWindows.js b/toolkit/components/antitracking/test/browser/browser_allowListSeparationInPrivateAndNormalWindows.js
new file mode 100644
index 0000000000..38eb9fc090
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_allowListSeparationInPrivateAndNormalWindows.js
@@ -0,0 +1,52 @@
+// This test works by setting up an exception for the private window allow list
+// manually, and it then expects to see some blocking notifications (note the
+// document.cookie setter in the blocking callback.)
+// If the exception lists aren't handled separately, we'd get confused and put
+// the pages loaded under this test in the allow list, which would result in
+// the test not passing because no blocking notifications would be observed.
+
+// Testing the reverse case would also be interesting, but unfortunately there
+// isn't a super easy way to do that with our antitracking test framework since
+// private windows wouldn't send any blocking notifications as they don't have
+// storage access in the first place.
+
+"use strict";
+add_task(async _ => {
+ let uri = Services.io.newURI("https://example.net");
+ PermissionTestUtils.add(
+ uri,
+ "trackingprotection-pb",
+ Services.perms.ALLOW_ACTION
+ );
+
+ registerCleanupFunction(_ => {
+ Services.perms.removeAll();
+ });
+});
+
+AntiTracking.runTest(
+ "Test that we don't honour a private allow list exception in a normal window",
+ // Blocking callback
+ async _ => {
+ document.cookie = "name=value";
+ },
+
+ // Non blocking callback
+ async _ => {
+ // Nothing to do here.
+ },
+
+ // Cleanup callback
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ null, // no extra prefs
+ false, // run the window.open() test
+ false, // run the user interaction test
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER, // expect blocking notifications
+ false
+); // run in a normal window
diff --git a/toolkit/components/antitracking/test/browser/browser_allowPermissionForTracker.js b/toolkit/components/antitracking/test/browser/browser_allowPermissionForTracker.js
new file mode 100644
index 0000000000..e628199ead
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_allowPermissionForTracker.js
@@ -0,0 +1,64 @@
+// This test works by setting up an exception for the tracker domain, which
+// disables all the anti-tracking tests.
+
+add_task(async _ => {
+ PermissionTestUtils.add(
+ "https://tracking.example.org",
+ "cookie",
+ Services.perms.ALLOW_ACTION
+ );
+ PermissionTestUtils.add(
+ "https://tracking.example.com",
+ "cookie",
+ Services.perms.ALLOW_ACTION
+ );
+ // Grant interaction permission so we can directly call
+ // requestStorageAccess from the tracker.
+ PermissionTestUtils.add(
+ "https://tracking.example.org",
+ "storageAccessAPI",
+ Services.perms.ALLOW_ACTION
+ );
+
+ registerCleanupFunction(_ => {
+ Services.perms.removeAll();
+ });
+});
+
+AntiTracking._createTask({
+ name: "Test that we do honour a cookie permission for nested windows",
+ cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+ blockingByContentBlockingRTUI: false,
+ allowList: false,
+ callback: async _ => {
+ document.cookie = "name=value";
+ ok(document.cookie != "", "Nothing is blocked");
+
+ // requestStorageAccess should resolve
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ await document
+ .requestStorageAccess()
+ .then(() => {
+ ok(true, "Should grant storage access");
+ })
+ .catch(() => {
+ ok(false, "Should grant storage access");
+ });
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ },
+ // Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default"
+ extraPrefs: [["network.cookie.sameSite.laxByDefault", false]],
+ expectedBlockingNotifications: 0,
+ runInPrivateWindow: false,
+ iframeSandbox: null,
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+});
+
+add_task(async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_backgroundImageAssertion.js b/toolkit/components/antitracking/test/browser/browser_backgroundImageAssertion.js
new file mode 100644
index 0000000000..16eec0da9e
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_backgroundImageAssertion.js
@@ -0,0 +1,69 @@
+add_task(async function () {
+ info("Starting subResources test");
+
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ [
+ "privacy.restrict3rdpartystorage.userInteractionRequiredForHosts",
+ "tracking.example.com,tracking.example.org",
+ ],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ await SpecialPowers.spawn(
+ browser,
+ [{ page: TEST_3RD_PARTY_PAGE_WITH_SVG }],
+ async function (obj) {
+ await new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ });
+ }
+ );
+
+ ok(true, "No crash, hopefully!");
+ BrowserTestUtils.removeTab(tab);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+});
+
+add_task(async function () {
+ info("Cleaning up.");
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingCookies.js b/toolkit/components/antitracking/test/browser/browser_blockingCookies.js
new file mode 100644
index 0000000000..a8e69b5e15
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_blockingCookies.js
@@ -0,0 +1,183 @@
+requestLongerTimeout(4);
+
+// Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default"
+Services.prefs.setBoolPref("network.cookie.sameSite.laxByDefault", false);
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("network.cookie.sameSite.laxByDefault");
+});
+
+AntiTracking.runTestInNormalAndPrivateMode(
+ "Set/Get Cookies",
+ // Blocking callback
+ async _ => {
+ is(document.cookie, "", "No cookies for me");
+ document.cookie = "name=value";
+ is(document.cookie, "", "No cookies for me");
+
+ for (let arg of ["?checkonly", "?redirect-checkonly"]) {
+ info(`checking with arg=${arg}`);
+ await fetch("server.sjs" + arg)
+ .then(r => r.text())
+ .then(text => {
+ is(text, "cookie-not-present", "We should not have cookies");
+ });
+ // Let's do it twice.
+ await fetch("server.sjs" + arg)
+ .then(r => r.text())
+ .then(text => {
+ is(text, "cookie-not-present", "We should not have cookies");
+ });
+ }
+
+ is(document.cookie, "", "Still no cookies for me");
+ },
+
+ // Non blocking callback
+ async _ => {
+ is(document.cookie, "", "No cookies for me");
+
+ // Note: The ?redirect test is _not_ using checkonly, so it will actually
+ // set our foopy=1 cookie.
+ for (let arg of ["?checkonly", "?redirect"]) {
+ info(`checking with arg=${arg}`);
+ await fetch("server.sjs" + arg)
+ .then(r => r.text())
+ .then(text => {
+ is(text, "cookie-not-present", "We should not have cookies");
+ });
+ }
+
+ document.cookie = "name=value";
+ ok(document.cookie.includes("name=value"), "Some cookies for me");
+ ok(document.cookie.includes("foopy=1"), "Some cookies for me");
+
+ for (let arg of ["", "?redirect"]) {
+ info(`checking with arg=${arg}`);
+ await fetch("server.sjs" + arg)
+ .then(r => r.text())
+ .then(text => {
+ is(text, "cookie-present", "We should have cookies");
+ });
+ }
+
+ ok(document.cookie.length, "Some Cookies for me");
+ },
+
+ // Cleanup callback
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ }
+);
+
+AntiTracking.runTestInNormalAndPrivateMode(
+ "Cookies and Storage Access API",
+ // Blocking callback
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+
+ is(document.cookie, "", "No cookies for me");
+ document.cookie = "name=value";
+ is(document.cookie, "", "No cookies for me");
+
+ for (let arg of ["", "?redirect"]) {
+ info(`checking with arg=${arg}`);
+ await fetch("server.sjs" + arg)
+ .then(r => r.text())
+ .then(text => {
+ is(text, "cookie-not-present", "We should not have cookies");
+ });
+ // Let's do it twice.
+ await fetch("server.sjs" + arg)
+ .then(r => r.text())
+ .then(text => {
+ is(text, "cookie-not-present", "We should not have cookies");
+ });
+ }
+
+ is(document.cookie, "", "Still no cookies for me");
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+
+ is(document.cookie, "", "No cookies for me");
+ document.cookie = "name=value";
+
+ let effectiveCookieBehavior = SpecialPowers.isContentWindowPrivate(window)
+ ? SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior.pbmode"
+ )
+ : SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior"
+ );
+
+ if (
+ [
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT,
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN,
+ ].includes(effectiveCookieBehavior)
+ ) {
+ is(document.cookie, "", "No cookies for me");
+ } else {
+ is(document.cookie, "name=value", "I have the cookies!");
+ }
+ },
+
+ // Non blocking callback
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await hasStorageAccessInitially();
+
+ is(document.cookie, "", "No cookies for me");
+
+ // Note: The ?redirect test is _not_ using checkonly, so it will actually
+ // set our foopy=1 cookie.
+ for (let arg of ["?checkonly", "?redirect"]) {
+ info(`checking with arg=${arg}`);
+ await fetch("server.sjs" + arg)
+ .then(r => r.text())
+ .then(text => {
+ is(text, "cookie-not-present", "We should not have cookies");
+ });
+ }
+
+ document.cookie = "name=value";
+ ok(document.cookie.includes("name=value"), "Some cookies for me");
+ ok(document.cookie.includes("foopy=1"), "Some cookies for me");
+
+ for (let arg of ["", "?redirect"]) {
+ info(`checking with arg=${arg}`);
+ await fetch("server.sjs" + arg)
+ .then(r => r.text())
+ .then(text => {
+ is(text, "cookie-present", "We should have cookies");
+ });
+ }
+
+ ok(document.cookie.length, "Some Cookies for me");
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+
+ // For non-tracking windows, calling the API is a no-op
+ ok(document.cookie.length, "Still some Cookies for me");
+ ok(document.cookie.includes("name=value"), "Some cookies for me");
+ ok(document.cookie.includes("foopy=1"), "Some cookies for me");
+ },
+
+ // Cleanup callback
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ null,
+ false,
+ false
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingDOMCache.js b/toolkit/components/antitracking/test/browser/browser_blockingDOMCache.js
new file mode 100644
index 0000000000..a8353cc8f1
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_blockingDOMCache.js
@@ -0,0 +1,39 @@
+requestLongerTimeout(2);
+
+AntiTracking.runTest(
+ "DOM Cache",
+ async _ => {
+ await caches.open("wow").then(
+ _ => {
+ ok(false, "DOM Cache cannot be used!");
+ },
+ _ => {
+ ok(true, "DOM Cache cannot be used!");
+ }
+ );
+ },
+ async _ => {
+ await caches.open("wow").then(
+ _ => {
+ ok(true, "DOM Cache can be used!");
+ },
+ _ => {
+ ok(false, "DOM Cache can be used!");
+ }
+ );
+ },
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [
+ ["dom.caches.testing.enabled", true],
+ [
+ "privacy.partition.always_partition_third_party_non_cookie_storage",
+ false,
+ ],
+ ]
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingDOMCacheAlwaysPartition.js b/toolkit/components/antitracking/test/browser/browser_blockingDOMCacheAlwaysPartition.js
new file mode 100644
index 0000000000..4cb90af8ad
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_blockingDOMCacheAlwaysPartition.js
@@ -0,0 +1,54 @@
+requestLongerTimeout(2);
+
+AntiTracking.runTest(
+ "DOM Cache Always Partition Storage",
+ async _ => {
+ let effectiveCookieBehavior = SpecialPowers.isContentWindowPrivate(window)
+ ? SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior.pbmode"
+ )
+ : SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior"
+ );
+
+ // caches.open still fails for cookieBehavior 2 (reject) and
+ // 1 (reject for third parties), so we need to account for that.
+ let shouldFail =
+ effectiveCookieBehavior ==
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT ||
+ (effectiveCookieBehavior ==
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN &&
+ !document.location.href.includes("3rdPartyWO") &&
+ !document.location.href.includes("3rdPartyUI"));
+
+ await caches.open("wow").then(
+ _ => {
+ ok(!shouldFail, "DOM Cache can be used!");
+ },
+ _ => {
+ ok(shouldFail, "DOM Cache can be used!");
+ }
+ );
+ },
+ async _ => {
+ await caches.open("wow").then(
+ _ => {
+ ok(true, "DOM Cache can be used!");
+ },
+ _ => {
+ ok(false, "DOM Cache can be used!");
+ }
+ );
+ },
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [
+ ["dom.caches.testing.enabled", true],
+ ["privacy.partition.always_partition_third_party_non_cookie_storage", true],
+ ]
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingDOMCacheAlwaysPartitionSAA.js b/toolkit/components/antitracking/test/browser/browser_blockingDOMCacheAlwaysPartitionSAA.js
new file mode 100644
index 0000000000..bef53f2936
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_blockingDOMCacheAlwaysPartitionSAA.js
@@ -0,0 +1,80 @@
+requestLongerTimeout(2);
+
+AntiTracking.runTest(
+ "DOM Cache Always Partition Storage and Storage Access API",
+ async _ => {
+ await noStorageAccessInitially();
+
+ let effectiveCookieBehavior = SpecialPowers.isContentWindowPrivate(window)
+ ? SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior.pbmode"
+ )
+ : SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior"
+ );
+
+ let shouldThrow = [
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT,
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN,
+ ].includes(effectiveCookieBehavior);
+
+ await caches.open("wow").then(
+ _ => {
+ ok(!shouldThrow, "DOM Cache can be used!");
+ },
+ _ => {
+ ok(shouldThrow, "DOM Cache can be used!");
+ }
+ );
+
+ await callRequestStorageAccess();
+
+ await caches.open("wow").then(
+ _ => {
+ ok(!shouldThrow, "DOM Cache can be used!");
+ },
+ _ => {
+ ok(shouldThrow, "DOM Cache can be used!");
+ }
+ );
+ },
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await hasStorageAccessInitially();
+
+ await caches.open("wow").then(
+ _ => {
+ ok(true, "DOM Cache can be used!");
+ },
+ _ => {
+ ok(false, "DOM Cache can be used!");
+ }
+ );
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+
+ // For non-tracking windows, calling the API is a no-op
+ await caches.open("wow").then(
+ _ => {
+ ok(true, "DOM Cache can be used!");
+ },
+ _ => {
+ ok(false, "DOM Cache can be used!");
+ }
+ );
+ },
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [
+ ["dom.caches.testing.enabled", true],
+ ["privacy.partition.always_partition_third_party_non_cookie_storage", true],
+ ],
+ false,
+ false
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingDOMCacheSAA.js b/toolkit/components/antitracking/test/browser/browser_blockingDOMCacheSAA.js
new file mode 100644
index 0000000000..06526972dd
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_blockingDOMCacheSAA.js
@@ -0,0 +1,85 @@
+requestLongerTimeout(2);
+
+AntiTracking.runTest(
+ "DOM Cache and Storage Access API",
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+
+ await caches.open("wow").then(
+ _ => {
+ ok(false, "DOM Cache cannot be used!");
+ },
+ _ => {
+ ok(true, "DOM Cache cannot be used!");
+ }
+ );
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+
+ let effectiveCookieBehavior = SpecialPowers.isContentWindowPrivate(window)
+ ? SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior.pbmode"
+ )
+ : SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior"
+ );
+
+ let shouldThrow = [
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT,
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN,
+ ].includes(effectiveCookieBehavior);
+
+ await caches.open("wow").then(
+ _ => {
+ ok(!shouldThrow, "DOM Cache can be used!");
+ },
+ _ => {
+ ok(shouldThrow, "DOM Cache can be used!");
+ }
+ );
+ },
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await hasStorageAccessInitially();
+
+ await caches.open("wow").then(
+ _ => {
+ ok(true, "DOM Cache can be used!");
+ },
+ _ => {
+ ok(false, "DOM Cache can be used!");
+ }
+ );
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+
+ // For non-tracking windows, calling the API is a no-op
+ await caches.open("wow").then(
+ _ => {
+ ok(true, "DOM Cache can be used!");
+ },
+ _ => {
+ ok(false, "DOM Cache can be used!");
+ }
+ );
+ },
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [
+ ["dom.caches.testing.enabled", true],
+ [
+ "privacy.partition.always_partition_third_party_non_cookie_storage",
+ false,
+ ],
+ ],
+ false,
+ false
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingIndexedDb.js b/toolkit/components/antitracking/test/browser/browser_blockingIndexedDb.js
new file mode 100644
index 0000000000..7fde2365bb
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_blockingIndexedDb.js
@@ -0,0 +1,102 @@
+requestLongerTimeout(4);
+
+AntiTracking.runTestInNormalAndPrivateMode(
+ "IndexedDB",
+ // blocking callback
+ async _ => {
+ try {
+ indexedDB.open("test", "1");
+ ok(false, "IDB should be blocked");
+ } catch (e) {
+ ok(true, "IDB should be blocked");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+ },
+ // non-blocking callback
+ async _ => {
+ indexedDB.open("test", "1");
+ ok(true, "IDB should be allowed");
+ },
+ // Cleanup callback
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [["dom.indexedDB.hide_in_pbmode.enabled", false]]
+);
+
+AntiTracking.runTestInNormalAndPrivateMode(
+ "IndexedDB and Storage Access API",
+ // blocking callback
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+
+ try {
+ indexedDB.open("test", "1");
+ ok(false, "IDB should be blocked");
+ } catch (e) {
+ ok(true, "IDB should be blocked");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+
+ let effectiveCookieBehavior = SpecialPowers.isContentWindowPrivate(window)
+ ? SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior.pbmode"
+ )
+ : SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior"
+ );
+
+ let shouldThrow = [
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT,
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN,
+ ].includes(effectiveCookieBehavior);
+
+ let hasThrown;
+ try {
+ indexedDB.open("test", "1");
+ hasThrown = false;
+ } catch (e) {
+ hasThrown = true;
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+
+ is(
+ hasThrown,
+ shouldThrow,
+ "IDB should be allowed if not in cookieBehavior pref value BEHAVIOR_REJECT/BEHAVIOR_REJECT_FOREIGN"
+ );
+ },
+ // non-blocking callback
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await hasStorageAccessInitially();
+
+ indexedDB.open("test", "1");
+ ok(true, "IDB should be allowed");
+
+ await callRequestStorageAccess();
+
+ // For non-tracking windows, calling the API is a no-op
+ indexedDB.open("test", "1");
+ ok(true, "IDB should be allowed");
+ },
+ // Cleanup callback
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [["dom.indexedDB.hide_in_pbmode.enabled", false]],
+ false,
+ false
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingIndexedDbInWorkers.js b/toolkit/components/antitracking/test/browser/browser_blockingIndexedDbInWorkers.js
new file mode 100644
index 0000000000..55db3cc05b
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_blockingIndexedDbInWorkers.js
@@ -0,0 +1,73 @@
+AntiTracking.runTestInNormalAndPrivateMode(
+ "IndexedDB in workers",
+ async _ => {
+ function blockCode() {
+ try {
+ indexedDB.open("test", "1");
+ postMessage(false);
+ } catch (e) {
+ postMessage(e.name == "SecurityError");
+ }
+ }
+
+ let blob = new Blob([blockCode.toString() + "; blockCode();"]);
+ ok(blob, "Blob has been created");
+
+ let blobURL = URL.createObjectURL(blob);
+ ok(blobURL, "Blob URL has been created");
+
+ let worker = new Worker(blobURL);
+ ok(worker, "Worker has been created");
+
+ await new Promise((resolve, reject) => {
+ worker.onmessage = function (e) {
+ if (e.data) {
+ resolve();
+ } else {
+ reject();
+ }
+ };
+
+ worker.onerror = function (e) {
+ reject();
+ };
+ });
+ },
+ async _ => {
+ function nonBlockCode() {
+ indexedDB.open("test", "1");
+ postMessage(true);
+ }
+
+ let blob = new Blob([nonBlockCode.toString() + "; nonBlockCode();"]);
+ ok(blob, "Blob has been created");
+
+ let blobURL = URL.createObjectURL(blob);
+ ok(blobURL, "Blob URL has been created");
+
+ let worker = new Worker(blobURL);
+ ok(worker, "Worker has been created");
+
+ await new Promise((resolve, reject) => {
+ worker.onmessage = function (e) {
+ if (e.data) {
+ resolve();
+ } else {
+ reject();
+ }
+ };
+
+ worker.onerror = function (e) {
+ reject();
+ };
+ });
+ },
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [["dom.indexedDB.hide_in_pbmode.enabled", false]]
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingIndexedDbInWorkers2.js b/toolkit/components/antitracking/test/browser/browser_blockingIndexedDbInWorkers2.js
new file mode 100644
index 0000000000..02ce8dc588
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_blockingIndexedDbInWorkers2.js
@@ -0,0 +1,152 @@
+requestLongerTimeout(6);
+
+AntiTracking.runTestInNormalAndPrivateMode(
+ "IndexedDB in workers and Storage Access API",
+ async _ => {
+ function blockCode() {
+ try {
+ indexedDB.open("test", "1");
+ postMessage(false);
+ } catch (e) {
+ postMessage(e.name == "SecurityError");
+ }
+ }
+ function nonBlockCode() {
+ indexedDB.open("test", "1");
+ postMessage(true);
+ }
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+
+ let blob = new Blob([blockCode.toString() + "; blockCode();"]);
+ ok(blob, "Blob has been created");
+
+ let blobURL = URL.createObjectURL(blob);
+ ok(blobURL, "Blob URL has been created");
+
+ let worker = new Worker(blobURL);
+ ok(worker, "Worker has been created");
+
+ await new Promise((resolve, reject) => {
+ worker.onmessage = function (e) {
+ if (e.data) {
+ resolve();
+ } else {
+ reject();
+ }
+ };
+
+ worker.onerror = function (e) {
+ reject();
+ };
+ });
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+
+ let effectiveCookieBehavior = SpecialPowers.isContentWindowPrivate(window)
+ ? SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior.pbmode"
+ )
+ : SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior"
+ );
+
+ if (
+ [
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT,
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN,
+ ].includes(effectiveCookieBehavior)
+ ) {
+ blob = new Blob([blockCode.toString() + "; blockCode();"]);
+ } else {
+ blob = new Blob([nonBlockCode.toString() + "; nonBlockCode();"]);
+ }
+ ok(blob, "Blob has been created");
+
+ blobURL = URL.createObjectURL(blob);
+ ok(blobURL, "Blob URL has been created");
+
+ worker = new Worker(blobURL);
+ ok(worker, "Worker has been created");
+
+ await new Promise((resolve, reject) => {
+ worker.onmessage = function (e) {
+ if (e.data) {
+ resolve();
+ } else {
+ reject();
+ }
+ };
+
+ worker.onerror = function (e) {
+ reject();
+ };
+ });
+ },
+ async _ => {
+ function nonBlockCode() {
+ indexedDB.open("test", "1");
+ postMessage(true);
+ }
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await hasStorageAccessInitially();
+
+ let blob = new Blob([nonBlockCode.toString() + "; nonBlockCode();"]);
+ ok(blob, "Blob has been created");
+
+ let blobURL = URL.createObjectURL(blob);
+ ok(blobURL, "Blob URL has been created");
+
+ let worker = new Worker(blobURL);
+ ok(worker, "Worker has been created");
+
+ await new Promise((resolve, reject) => {
+ worker.onmessage = function (e) {
+ if (e.data) {
+ resolve();
+ } else {
+ reject();
+ }
+ };
+
+ worker.onerror = function (e) {
+ reject();
+ };
+ });
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+
+ // For non-tracking windows, calling the API is a no-op
+
+ worker = new Worker(blobURL);
+ ok(worker, "Worker has been created");
+
+ await new Promise((resolve, reject) => {
+ worker.onmessage = function (e) {
+ if (e.data) {
+ resolve();
+ } else {
+ reject();
+ }
+ };
+
+ worker.onerror = function (e) {
+ reject();
+ };
+ });
+ },
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [["dom.indexedDB.hide_in_pbmode.enabled", false]],
+ false,
+ false
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingLocalStorage.js b/toolkit/components/antitracking/test/browser/browser_blockingLocalStorage.js
new file mode 100644
index 0000000000..1362fc7519
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_blockingLocalStorage.js
@@ -0,0 +1,94 @@
+requestLongerTimeout(4);
+
+AntiTracking.runTestInNormalAndPrivateMode(
+ "localStorage",
+ async _ => {
+ try {
+ localStorage.foo = 42;
+ ok(false, "LocalStorage cannot be used!");
+ } catch (e) {
+ ok(true, "LocalStorage cannot be used!");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+ },
+ async _ => {
+ localStorage.foo = 42;
+ ok(true, "LocalStorage is allowed");
+ },
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ }
+);
+
+AntiTracking.runTestInNormalAndPrivateMode(
+ "localStorage and Storage Access API",
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+
+ try {
+ localStorage.foo = 42;
+ ok(false, "LocalStorage cannot be used!");
+ } catch (e) {
+ ok(true, "LocalStorage cannot be used!");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+
+ let effectiveCookieBehavior = SpecialPowers.isContentWindowPrivate(window)
+ ? SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior.pbmode"
+ )
+ : SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior"
+ );
+
+ if (
+ [
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT,
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN,
+ ].includes(effectiveCookieBehavior)
+ ) {
+ try {
+ localStorage.foo = 42;
+ ok(false, "LocalStorage cannot be used!");
+ } catch (e) {
+ ok(true, "LocalStorage cannot be used!");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+ } else {
+ localStorage.foo = 42;
+ ok(true, "LocalStorage is allowed");
+ }
+ },
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await hasStorageAccessInitially();
+
+ localStorage.foo = 42;
+ ok(true, "LocalStorage is allowed");
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+
+ // For non-tracking windows, calling the API is a no-op
+ localStorage.foo = 42;
+ ok(true, "LocalStorage is allowed");
+ },
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ null,
+ false,
+ false
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingMessaging.js b/toolkit/components/antitracking/test/browser/browser_blockingMessaging.js
new file mode 100644
index 0000000000..34af2b76ce
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_blockingMessaging.js
@@ -0,0 +1,322 @@
+if (AppConstants.MOZ_CODE_COVERAGE) {
+ requestLongerTimeout(12);
+} else {
+ requestLongerTimeout(12);
+}
+
+AntiTracking.runTestInNormalAndPrivateMode(
+ "BroadcastChannel",
+ async _ => {
+ try {
+ new BroadcastChannel("hello");
+ ok(false, "BroadcastChannel cannot be used!");
+ } catch (e) {
+ ok(true, "BroadcastChannel cannot be used!");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+ },
+ async _ => {
+ new BroadcastChannel("hello");
+ ok(true, "BroadcastChannel be used");
+ },
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ }
+);
+
+AntiTracking.runTestInNormalAndPrivateMode(
+ "BroadcastChannel in workers",
+ async _ => {
+ function blockingCode() {
+ try {
+ new BroadcastChannel("hello");
+ postMessage(false);
+ } catch (e) {
+ postMessage(e.name == "SecurityError");
+ }
+ }
+
+ let blob = new Blob([blockingCode.toString() + "; blockingCode();"]);
+ ok(blob, "Blob has been created");
+
+ let blobURL = URL.createObjectURL(blob);
+ ok(blobURL, "Blob URL has been created");
+
+ let worker = new Worker(blobURL);
+ ok(worker, "Worker has been created");
+
+ await new Promise((resolve, reject) => {
+ worker.onmessage = function (e) {
+ if (e.data) {
+ resolve();
+ } else {
+ reject();
+ }
+ };
+
+ worker.onerror = function (e) {
+ reject();
+ };
+ });
+ },
+ async _ => {
+ function nonBlockingCode() {
+ new BroadcastChannel("hello");
+ postMessage(true);
+ }
+
+ let blob = new Blob([nonBlockingCode.toString() + "; nonBlockingCode();"]);
+ ok(blob, "Blob has been created");
+
+ let blobURL = URL.createObjectURL(blob);
+ ok(blobURL, "Blob URL has been created");
+
+ let worker = new Worker(blobURL);
+ ok(worker, "Worker has been created");
+
+ await new Promise((resolve, reject) => {
+ worker.onmessage = function (e) {
+ if (e.data) {
+ resolve();
+ } else {
+ reject();
+ }
+ };
+
+ worker.onerror = function (e) {
+ reject();
+ };
+ });
+ },
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ }
+);
+
+AntiTracking.runTestInNormalAndPrivateMode(
+ "BroadcastChannel and Storage Access API",
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+
+ try {
+ new BroadcastChannel("hello");
+ ok(false, "BroadcastChannel cannot be used!");
+ } catch (e) {
+ ok(true, "BroadcastChannel cannot be used!");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+
+ let effectiveCookieBehavior = SpecialPowers.isContentWindowPrivate(window)
+ ? SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior.pbmode"
+ )
+ : SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior"
+ );
+
+ if (
+ [
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT,
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN,
+ ].includes(effectiveCookieBehavior)
+ ) {
+ try {
+ new BroadcastChannel("hello");
+ ok(false, "BroadcastChannel cannot be used!");
+ } catch (e) {
+ ok(true, "BroadcastChannel cannot be used!");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+ } else {
+ new BroadcastChannel("hello");
+ ok(true, "BroadcastChannel can be used");
+ }
+ },
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await hasStorageAccessInitially();
+
+ new BroadcastChannel("hello");
+ ok(true, "BroadcastChanneli can be used");
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+
+ new BroadcastChannel("hello");
+ ok(true, "BroadcastChannel can be used");
+ },
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ null,
+ false,
+ false
+);
+
+AntiTracking.runTestInNormalAndPrivateMode(
+ "BroadcastChannel in workers and Storage Access API",
+ async _ => {
+ function blockingCode() {
+ try {
+ new BroadcastChannel("hello");
+ postMessage(false);
+ } catch (e) {
+ postMessage(e.name == "SecurityError");
+ }
+ }
+ function nonBlockingCode() {
+ new BroadcastChannel("hello");
+ postMessage(true);
+ }
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+
+ let blob = new Blob([blockingCode.toString() + "; blockingCode();"]);
+ ok(blob, "Blob has been created");
+
+ let blobURL = URL.createObjectURL(blob);
+ ok(blobURL, "Blob URL has been created");
+
+ let worker = new Worker(blobURL);
+ ok(worker, "Worker has been created");
+
+ await new Promise((resolve, reject) => {
+ worker.onmessage = function (e) {
+ if (e.data) {
+ resolve();
+ } else {
+ reject();
+ }
+ };
+
+ worker.onerror = function (e) {
+ reject();
+ };
+ });
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+
+ let effectiveCookieBehavior = SpecialPowers.isContentWindowPrivate(window)
+ ? SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior.pbmode"
+ )
+ : SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior"
+ );
+
+ if (
+ [
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT,
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN,
+ ].includes(effectiveCookieBehavior)
+ ) {
+ blob = new Blob([blockingCode.toString() + "; blockingCode();"]);
+ } else {
+ blob = new Blob([nonBlockingCode.toString() + "; nonBlockingCode();"]);
+ }
+
+ ok(blob, "Blob has been created");
+
+ blobURL = URL.createObjectURL(blob);
+ ok(blobURL, "Blob URL has been created");
+
+ worker = new Worker(blobURL);
+ ok(worker, "Worker has been created");
+
+ await new Promise((resolve, reject) => {
+ worker.onmessage = function (e) {
+ if (e.data) {
+ resolve();
+ } else {
+ reject();
+ }
+ };
+
+ worker.onerror = function (e) {
+ reject();
+ };
+ });
+ },
+ async _ => {
+ function nonBlockingCode() {
+ new BroadcastChannel("hello");
+ postMessage(true);
+ }
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await hasStorageAccessInitially();
+
+ let blob = new Blob([nonBlockingCode.toString() + "; nonBlockingCode();"]);
+ ok(blob, "Blob has been created");
+
+ let blobURL = URL.createObjectURL(blob);
+ ok(blobURL, "Blob URL has been created");
+
+ let worker = new Worker(blobURL);
+ ok(worker, "Worker has been created");
+
+ await new Promise((resolve, reject) => {
+ worker.onmessage = function (e) {
+ if (e.data) {
+ resolve();
+ } else {
+ reject();
+ }
+ };
+
+ worker.onerror = function (e) {
+ reject();
+ };
+ });
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+
+ // For non-tracking windows, calling the API is a no-op
+
+ worker = new Worker(blobURL);
+ ok(worker, "Worker has been created");
+
+ await new Promise((resolve, reject) => {
+ worker.onmessage = function (e) {
+ if (e.data) {
+ resolve();
+ } else {
+ reject();
+ }
+ };
+
+ worker.onerror = function (e) {
+ reject();
+ };
+ });
+ },
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ null,
+ false,
+ false
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingNoOpener.js b/toolkit/components/antitracking/test/browser/browser_blockingNoOpener.js
new file mode 100644
index 0000000000..ba75454e18
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_blockingNoOpener.js
@@ -0,0 +1,41 @@
+gFeatures = "noopener";
+
+AntiTracking.runTestInNormalAndPrivateMode(
+ "Blocking in the case of noopener windows",
+ async _ => {
+ try {
+ localStorage.foo = 42;
+ ok(false, "LocalStorage cannot be used!");
+ } catch (e) {
+ ok(true, "LocalStorage cannot be used!");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+ },
+ async phase => {
+ switch (phase) {
+ case 1:
+ localStorage.foo = 42;
+ ok(true, "LocalStorage is allowed");
+ break;
+ case 2:
+ try {
+ localStorage.foo = 42;
+ ok(false, "LocalStorage cannot be used!");
+ } catch (e) {
+ ok(true, "LocalStorage cannot be used!");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+ break;
+ }
+ },
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ null,
+ true,
+ false
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingServiceWorkers.js b/toolkit/components/antitracking/test/browser/browser_blockingServiceWorkers.js
new file mode 100644
index 0000000000..c61f85d69f
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_blockingServiceWorkers.js
@@ -0,0 +1,30 @@
+AntiTracking.runTest(
+ "ServiceWorkers",
+ async _ => {
+ await navigator.serviceWorker
+ .register("empty.js")
+ .then(
+ _ => {
+ ok(false, "ServiceWorker cannot be used!");
+ },
+ _ => {
+ ok(true, "ServiceWorker cannot be used!");
+ }
+ )
+ .catch(e => ok(false, "Promise rejected: " + e));
+ },
+ null,
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.ipc.processCount", 1],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingServiceWorkersStorageAccessAPI.js b/toolkit/components/antitracking/test/browser/browser_blockingServiceWorkersStorageAccessAPI.js
new file mode 100644
index 0000000000..a7676bf939
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_blockingServiceWorkersStorageAccessAPI.js
@@ -0,0 +1,133 @@
+/* import-globals-from antitracking_head.js */
+
+requestLongerTimeout(2);
+
+AntiTracking.runTest(
+ "ServiceWorkers and Storage Access API",
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+
+ await navigator.serviceWorker
+ .register("empty.js")
+ .then(
+ _ => {
+ ok(false, "ServiceWorker cannot be used!");
+ },
+ _ => {
+ ok(true, "ServiceWorker cannot be used!");
+ }
+ )
+ .catch(e => ok(false, "Promise rejected: " + e));
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+
+ let effectiveCookieBehavior = SpecialPowers.isContentWindowPrivate(window)
+ ? SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior.pbmode"
+ )
+ : SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior"
+ );
+
+ if (
+ [
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT,
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN,
+ ].includes(effectiveCookieBehavior)
+ ) {
+ await navigator.serviceWorker
+ .register("empty.js")
+ .then(
+ _ => {
+ ok(false, "ServiceWorker cannot be used!");
+ },
+ _ => {
+ ok(true, "ServiceWorker cannot be used!");
+ }
+ )
+ .catch(e => ok(false, "Promise rejected: " + e));
+ } else {
+ await navigator.serviceWorker
+ .register("empty.js")
+ .then(
+ reg => {
+ ok(true, "ServiceWorker can be used!");
+ return reg;
+ },
+ _ => {
+ ok(false, "ServiceWorker cannot be used! " + _);
+ }
+ )
+ .then(
+ reg => reg.unregister(),
+ _ => {
+ ok(false, "unregister failed");
+ }
+ )
+ .catch(e => ok(false, "Promise rejected: " + e));
+ }
+ },
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await hasStorageAccessInitially();
+
+ await navigator.serviceWorker
+ .register("empty.js")
+ .then(
+ reg => {
+ ok(true, "ServiceWorker can be used!");
+ return reg;
+ },
+ _ => {
+ ok(false, "ServiceWorker cannot be used!");
+ }
+ )
+ .then(
+ reg => reg.unregister(),
+ _ => {
+ ok(false, "unregister failed");
+ }
+ )
+ .catch(e => ok(false, "Promise rejected: " + e));
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+
+ // For non-tracking windows, calling the API is a no-op
+ await navigator.serviceWorker
+ .register("empty.js")
+ .then(
+ reg => {
+ ok(true, "ServiceWorker can be used!");
+ return reg;
+ },
+ _ => {
+ ok(false, "ServiceWorker cannot be used!");
+ }
+ )
+ .then(
+ reg => reg.unregister(),
+ _ => {
+ ok(false, "unregister failed");
+ }
+ )
+ .catch(e => ok(false, "Promise rejected: " + e));
+ },
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.ipc.processCount", 1],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ],
+ false,
+ false
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingSessionStorage.js b/toolkit/components/antitracking/test/browser/browser_blockingSessionStorage.js
new file mode 100644
index 0000000000..25a926ed3f
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_blockingSessionStorage.js
@@ -0,0 +1,126 @@
+requestLongerTimeout(6);
+
+AntiTracking.runTestInNormalAndPrivateMode(
+ "sessionStorage",
+ async _ => {
+ let effectiveCookieBehavior = SpecialPowers.isContentWindowPrivate(window)
+ ? SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior.pbmode"
+ )
+ : SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior"
+ );
+
+ let shouldThrow = [
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT,
+ ].includes(effectiveCookieBehavior);
+
+ let hasThrown;
+ try {
+ sessionStorage.foo = 42;
+ hasThrown = false;
+ } catch (e) {
+ hasThrown = true;
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+
+ is(
+ hasThrown,
+ shouldThrow,
+ "SessionStorage show thrown only if cookieBehavior is REJECT"
+ );
+ },
+ async _ => {
+ sessionStorage.foo = 42;
+ ok(true, "SessionStorage is always allowed");
+ },
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [],
+ true,
+ true
+);
+
+AntiTracking.runTestInNormalAndPrivateMode(
+ "sessionStorage and Storage Access API",
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+
+ let effectiveCookieBehavior = SpecialPowers.isContentWindowPrivate(window)
+ ? SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior.pbmode"
+ )
+ : SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior"
+ );
+
+ let shouldThrow = [
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT,
+ ].includes(effectiveCookieBehavior);
+
+ let hasThrown;
+ try {
+ sessionStorage.foo = 42;
+ hasThrown = false;
+ } catch (e) {
+ hasThrown = true;
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+
+ is(
+ hasThrown,
+ shouldThrow,
+ "SessionStorage show thrown only if cookieBehavior is REJECT"
+ );
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+
+ try {
+ sessionStorage.foo = 42;
+ hasThrown = false;
+ } catch (e) {
+ hasThrown = true;
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+
+ is(
+ hasThrown,
+ shouldThrow,
+ "SessionStorage show thrown only if cookieBehavior is REJECT"
+ );
+ },
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await hasStorageAccessInitially();
+
+ sessionStorage.foo = 42;
+ ok(true, "SessionStorage is always allowed");
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+
+ // For non-tracking windows, calling the API is a no-op
+ sessionStorage.foo = 42;
+ ok(
+ true,
+ "SessionStorage is allowed after calling the storage access API too"
+ );
+ },
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ null,
+ false,
+ false
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingSharedWorkers.js b/toolkit/components/antitracking/test/browser/browser_blockingSharedWorkers.js
new file mode 100644
index 0000000000..0daffc4565
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_blockingSharedWorkers.js
@@ -0,0 +1,94 @@
+requestLongerTimeout(4);
+
+AntiTracking.runTestInNormalAndPrivateMode(
+ "SharedWorkers",
+ async _ => {
+ try {
+ new SharedWorker("a.js", "foo");
+ ok(false, "SharedWorker cannot be used!");
+ } catch (e) {
+ ok(true, "SharedWorker cannot be used!");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+ },
+ async _ => {
+ new SharedWorker("a.js", "foo");
+ ok(true, "SharedWorker is allowed");
+ },
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ }
+);
+
+AntiTracking.runTestInNormalAndPrivateMode(
+ "SharedWorkers and Storage Access API",
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+
+ try {
+ new SharedWorker("a.js", "foo");
+ ok(false, "SharedWorker cannot be used!");
+ } catch (e) {
+ ok(true, "SharedWorker cannot be used!");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+
+ let effectiveCookieBehavior = SpecialPowers.isContentWindowPrivate(window)
+ ? SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior.pbmode"
+ )
+ : SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior"
+ );
+
+ if (
+ [
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT,
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN,
+ ].includes(effectiveCookieBehavior)
+ ) {
+ try {
+ new SharedWorker("a.js", "foo");
+ ok(false, "SharedWorker cannot be used!");
+ } catch (e) {
+ ok(true, "SharedWorker cannot be used!");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+ } else {
+ new SharedWorker("a.js", "foo");
+ ok(true, "SharedWorker is allowed");
+ }
+ },
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await hasStorageAccessInitially();
+
+ new SharedWorker("a.js", "foo");
+ ok(true, "SharedWorker is allowed");
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+
+ // For non-tracking windows, calling the API is a no-op
+ new SharedWorker("a.js", "bar");
+ ok(true, "SharedWorker is allowed");
+ },
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ null,
+ false,
+ false
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_contentBlockingAllowListPrincipal.js b/toolkit/components/antitracking/test/browser/browser_contentBlockingAllowListPrincipal.js
new file mode 100644
index 0000000000..a9e2ac6fce
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_contentBlockingAllowListPrincipal.js
@@ -0,0 +1,237 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_SANDBOX_URL =
+ "https://example.com/browser/toolkit/components/antitracking/test/browser/sandboxed.html";
+
+/**
+ * Tests the contentBlockingAllowListPrincipal.
+ * @param {Browser} browser - Browser to test.
+ * @param {("content"|"system")} type - Expected principal type.
+ * @param {String} [origin] - Expected origin of principal. Defaults to the
+ * origin of the browsers content principal.
+ */
+function checkAllowListPrincipal(
+ browser,
+ type,
+ origin = browser.contentPrincipal.origin
+) {
+ let principal =
+ browser.browsingContext.currentWindowGlobal
+ .contentBlockingAllowListPrincipal;
+ ok(principal, "Principal is set");
+
+ if (type == "content") {
+ ok(principal.isContentPrincipal, "Is content principal");
+
+ ok(
+ principal.schemeIs("https"),
+ "allowlist content principal must have https scheme"
+ );
+ } else if (type == "system") {
+ ok(principal.isSystemPrincipal, "Is system principal");
+ } else {
+ throw new Error("Unexpected principal type");
+ }
+
+ is(principal.origin, origin, "Correct origin");
+}
+
+/**
+ * Runs a given test in a normal window and in a private browsing window.
+ * @param {String} initialUrl - URL to load in the initial test tab.
+ * @param {Function} testCallback - Test function to run in both windows.
+ */
+async function runTestInNormalAndPrivateMode(initialUrl, testCallback) {
+ for (let i = 0; i < 2; i++) {
+ let isPrivateBrowsing = !!i;
+ info("Running test. Private browsing: " + !!i);
+ let win = await BrowserTestUtils.openNewBrowserWindow({
+ private: isPrivateBrowsing,
+ });
+ let tab = BrowserTestUtils.addTab(win.gBrowser, initialUrl);
+ let browser = tab.linkedBrowser;
+
+ await BrowserTestUtils.browserLoaded(browser);
+
+ await testCallback(browser, isPrivateBrowsing);
+
+ await BrowserTestUtils.closeWindow(win);
+ }
+}
+
+/**
+ * Creates an iframe in the passed browser and waits for it to load.
+ * @param {Browser} browser - Browser to create the frame in.
+ * @param {String} src - Frame source url.
+ * @param {String} id - Frame id.
+ * @param {String} [sandboxAttr] - Optional list of sandbox attributes to set
+ * for the iframe. Defaults to no sandbox.
+ * @returns {Promise} - Resolves once the frame has loaded.
+ */
+function createFrame(browser, src, id, sandboxAttr) {
+ return SpecialPowers.spawn(
+ browser,
+ [{ page: src, frameId: id, sandboxAttr }],
+ async function (obj) {
+ await new content.Promise(resolve => {
+ let frame = content.document.createElement("iframe");
+ frame.src = obj.page;
+ frame.id = obj.frameId;
+ if (obj.sandboxAttr) {
+ frame.setAttribute("sandbox", obj.sandboxAttr);
+ }
+ frame.addEventListener("load", resolve, { once: true });
+ content.document.body.appendChild(frame);
+ });
+ }
+ );
+}
+
+add_task(async setup => {
+ // Disable heuristics. We don't need them and if enabled the resulting
+ // telemetry can race with the telemetry in the next test.
+ // See Bug 1686836, Bug 1686894.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.restrict3rdpartystorage.heuristic.redirect", false],
+ ["privacy.restrict3rdpartystorage.heuristic.recently_visited", false],
+ ["privacy.restrict3rdpartystorage.heuristic.window_open", false],
+ ["dom.security.https_first_pbm", false],
+ ],
+ });
+});
+
+/**
+ * Test that we get the correct allow list principal which matches the content
+ * principal for an https site.
+ */
+add_task(async test_contentPrincipalHTTPS => {
+ await runTestInNormalAndPrivateMode("https://example.com", browser => {
+ checkAllowListPrincipal(browser, "content");
+ });
+});
+
+/**
+ * Tests that the scheme of the allowlist principal is HTTPS, even though the
+ * site is loaded via HTTP.
+ */
+add_task(async test_contentPrincipalHTTP => {
+ await runTestInNormalAndPrivateMode(
+ "http://example.net",
+ (browser, isPrivateBrowsing) => {
+ checkAllowListPrincipal(
+ browser,
+ "content",
+ "https://example.net" +
+ (isPrivateBrowsing ? "^privateBrowsingId=1" : "")
+ );
+ }
+ );
+});
+
+/**
+ * Tests that the allow list principal is a system principal for the preferences
+ * about site.
+ */
+add_task(async test_systemPrincipal => {
+ await runTestInNormalAndPrivateMode("about:preferences", browser => {
+ checkAllowListPrincipal(browser, "system");
+ });
+});
+
+/**
+ * Tests that we get a valid content principal for top level sandboxed pages,
+ * and not the document principal which is a null principal.
+ */
+add_task(async test_TopLevelSandbox => {
+ await runTestInNormalAndPrivateMode(
+ TEST_SANDBOX_URL,
+ (browser, isPrivateBrowsing) => {
+ ok(
+ browser.contentPrincipal.isNullPrincipal,
+ "Top level sandboxed page should have null principal"
+ );
+ checkAllowListPrincipal(
+ browser,
+ "content",
+ "https://example.com" +
+ (isPrivateBrowsing ? "^privateBrowsingId=1" : "")
+ );
+ }
+ );
+});
+
+/**
+ * Tests that we get a valid content principal for a new tab opened via
+ * window.open.
+ */
+add_task(async test_windowOpen => {
+ await runTestInNormalAndPrivateMode("https://example.com", async browser => {
+ checkAllowListPrincipal(browser, "content");
+
+ let promiseTabOpened = BrowserTestUtils.waitForNewTab(
+ browser.ownerGlobal.gBrowser,
+ "https://example.org/",
+ true
+ );
+
+ // Call window.open from iframe.
+ await SpecialPowers.spawn(browser, [], async function () {
+ content.open("https://example.org/");
+ });
+
+ let tab = await promiseTabOpened;
+
+ checkAllowListPrincipal(tab.linkedBrowser, "content");
+
+ BrowserTestUtils.removeTab(tab);
+ });
+});
+
+/**
+ * Tests that we get a valid content principal for a new tab opened via
+ * window.open from a sandboxed iframe.
+ */
+add_task(async test_windowOpenFromSandboxedFrame => {
+ await runTestInNormalAndPrivateMode(
+ "https://example.com",
+ async (browser, isPrivateBrowsing) => {
+ checkAllowListPrincipal(browser, "content");
+
+ // Create sandboxed iframe, allow popups.
+ await createFrame(
+ browser,
+ "https://example.com",
+ "sandboxedIframe",
+ "allow-popups"
+ );
+ // Iframe BC is the only child of the test browser.
+ let [frameBrowsingContext] = browser.browsingContext.children;
+
+ let promiseTabOpened = BrowserTestUtils.waitForNewTab(
+ browser.ownerGlobal.gBrowser,
+ "https://example.org/",
+ true
+ );
+
+ // Call window.open from iframe.
+ await SpecialPowers.spawn(frameBrowsingContext, [], async function () {
+ content.open("https://example.org/");
+ });
+
+ let tab = await promiseTabOpened;
+
+ checkAllowListPrincipal(
+ tab.linkedBrowser,
+ "content",
+ "https://example.org" +
+ (isPrivateBrowsing ? "^privateBrowsingId=1" : "")
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ }
+ );
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_contentBlockingTelemetry.js b/toolkit/components/antitracking/test/browser/browser_contentBlockingTelemetry.js
new file mode 100644
index 0000000000..dc7bf0c80e
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_contentBlockingTelemetry.js
@@ -0,0 +1,404 @@
+/**
+ * Bug 1668199 - Testing the content blocking telemetry.
+ */
+
+"use strict";
+
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+const LABEL_STORAGE_GRANTED = 0;
+const LABEL_STORAGE_ACCESS_API = 1;
+const LABEL_OPENER_AFTER_UI = 2;
+const LABEL_OPENER = 3;
+const LABEL_REDIRECT = 4;
+
+function clearTelemetry() {
+ Services.telemetry.getSnapshotForHistograms("main", true /* clear */);
+ Services.telemetry.getHistogramById("STORAGE_ACCESS_REMAINING_DAYS").clear();
+}
+
+const expectedExpiredDays = getExpectedExpiredDaysFromPref(
+ "privacy.restrict3rdpartystorage.expiration"
+);
+const expectedExpiredDaysRedirect = getExpectedExpiredDaysFromPref(
+ "privacy.restrict3rdpartystorage.expiration_redirect"
+);
+
+function getExpectedExpiredDaysFromPref(pref) {
+ let expiredSecond = Services.prefs.getIntPref(pref);
+
+ // This is unlikely to happen, but just in case.
+ if (expiredSecond <= 0) {
+ return 0;
+ }
+
+ // We need to subtract one second from the expect expired second because there
+ // will be a short delay between the time we add the permission and the time
+ // we record the telemetry. Subtracting one can help us to get the correct
+ // expected expired days.
+ //
+ // Note that 86400 is seconds in one day.
+ return Math.trunc((expiredSecond - 1) / 86400);
+}
+
+async function testTelemetry(
+ aProbeInParent,
+ aExpectedCnt,
+ aLabel,
+ aExpectedIdx
+) {
+ info("Trigger the 'idle-daily' to trigger the telemetry probe.");
+ // Synthesis a fake 'idle-daily' notification to the content blocking
+ // telemetry service.
+ let cbts = Cc["@mozilla.org/content-blocking-telemetry-service;1"].getService(
+ Ci.nsIObserver
+ );
+ cbts.observe(null, "idle-daily", null);
+
+ let storageAccessGrantedHistogram;
+
+ // Wait until the telemetry probe appears.
+ await BrowserTestUtils.waitForCondition(() => {
+ let histograms;
+ if (aProbeInParent) {
+ histograms = Services.telemetry.getSnapshotForHistograms(
+ "main",
+ false /* clear */
+ ).parent;
+ } else {
+ histograms = Services.telemetry.getSnapshotForHistograms(
+ "main",
+ false /* clear */
+ ).content;
+ }
+ storageAccessGrantedHistogram = histograms.STORAGE_ACCESS_GRANTED_COUNT;
+
+ return (
+ !!storageAccessGrantedHistogram &&
+ storageAccessGrantedHistogram.values[LABEL_STORAGE_GRANTED] ==
+ aExpectedCnt
+ );
+ });
+
+ is(
+ storageAccessGrantedHistogram.values[LABEL_STORAGE_GRANTED],
+ aExpectedCnt,
+ "There should be expected storage access granted count in telemetry."
+ );
+ is(
+ storageAccessGrantedHistogram.values[aLabel],
+ 1,
+ "There should be one reason count in telemetry."
+ );
+
+ let storageAccessRemainingDaysHistogram = Services.telemetry.getHistogramById(
+ "STORAGE_ACCESS_REMAINING_DAYS"
+ );
+
+ TelemetryTestUtils.assertHistogram(
+ storageAccessRemainingDaysHistogram,
+ aExpectedIdx,
+ 1
+ );
+
+ // Clear telemetry probes
+ clearTelemetry();
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["network.cookie.cookieBehavior", BEHAVIOR_REJECT_TRACKER],
+ ["network.cookie.cookieBehavior.pbmode", BEHAVIOR_REJECT_TRACKER],
+ ["privacy.trackingprotection.annotate_channels", true],
+ [
+ "privacy.restrict3rdpartystorage.userInteractionRequiredForHosts",
+ "tracking.example.com,tracking.example.org",
+ ],
+ ["privacy.restrict3rdpartystorage.heuristic.redirect", true],
+ ["toolkit.telemetry.ipcBatchTimeout", 0],
+ ],
+ });
+
+ // Clear Telemetry probes before testing.
+ // There can be telemetry race conditions if the previous test generates
+ // content blocking telemetry.
+ // See Bug 1686836, Bug 1686894.
+ clearTelemetry();
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ registerCleanupFunction(_ => {
+ Services.perms.removeAll();
+ });
+});
+
+add_task(async function testTelemetryForStorageAccessAPI() {
+ info("Starting testing if storage access API send telemetry probe ...");
+
+ // First, clear all permissions.
+ Services.perms.removeAll();
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Loading the tracking iframe and call the RequestStorageAccess.");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: TEST_3RD_PARTY_PAGE_UI,
+ },
+ ],
+ async obj => {
+ let msg = {};
+ msg.callback = (async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+ }).toString();
+
+ await new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.onload = function () {
+ info("Sending code to the 3rd party content");
+ ifr.contentWindow.postMessage(msg, "*");
+ };
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ });
+ }
+ );
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ // The storage access permission will be expired in 29 days, so the expected
+ // index in the telemetry probe would be 29.
+ await testTelemetry(false, 1, LABEL_STORAGE_ACCESS_API, expectedExpiredDays);
+});
+
+add_task(async function testTelemetryForWindowOpenHeuristic() {
+ info("Starting testing if window open heuristic send telemetry probe ...");
+
+ // First, clear all permissions.
+ Services.perms.removeAll();
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Loading the tracking iframe and trigger the heuristic");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: TEST_3RD_PARTY_PAGE_WO,
+ },
+ ],
+ async obj => {
+ let msg = {};
+ msg.blockingCallback = (async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+ }).toString();
+
+ msg.nonBlockingCallback = (async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await hasStorageAccessInitially();
+ }).toString();
+
+ info("Checking if storage access is denied");
+ await new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.onload = function () {
+ info("Sending code to the 3rd party content");
+ ifr.contentWindow.postMessage(msg, "*");
+ };
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ });
+ }
+ );
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ // The storage access permission will be expired in 29 days, so the expected
+ // index in the telemetry probe would be 29.
+ await testTelemetry(false, 1, LABEL_OPENER, expectedExpiredDays);
+});
+
+add_task(async function testTelemetryForUserInteractionHeuristic() {
+ info(
+ "Starting testing if UserInteraction heuristic send telemetry probe ..."
+ );
+
+ // First, clear all permissions.
+ Services.perms.removeAll();
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Interact with the tracker in top-level.");
+ await AntiTracking.interactWithTracker();
+
+ info("Loading the tracking iframe and trigger the heuristic");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: TEST_3RD_PARTY_PAGE_UI,
+ popup: TEST_POPUP_PAGE,
+ },
+ ],
+ async obj => {
+ let msg = {};
+ msg.blockingCallback = (async _ => {
+ await noStorageAccessInitially();
+ }).toString();
+
+ msg.nonBlockingCallback = (async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await hasStorageAccessInitially();
+ }).toString();
+
+ let ifr = content.document.createElement("iframe");
+ let loading = new content.Promise(resolve => {
+ ifr.onload = resolve;
+ });
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ await loading;
+
+ info("Opening a window from the iframe.");
+ await SpecialPowers.spawn(ifr, [obj.popup], async popup => {
+ let windowClosed = new content.Promise(resolve => {
+ Services.ww.registerNotification(function notification(
+ aSubject,
+ aTopic,
+ aData
+ ) {
+ // We need to check the document URI here as well for the same
+ // reason above.
+ if (
+ aTopic == "domwindowclosed" &&
+ aSubject.document.documentURI ==
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/3rdPartyOpenUI.html"
+ ) {
+ Services.ww.unregisterNotification(notification);
+ resolve();
+ }
+ });
+ });
+
+ content.open(popup);
+
+ info("Let's wait for the window to be closed");
+ await windowClosed;
+ });
+ }
+ );
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ // The storage access permission will be expired in 29 days, so the expected
+ // index in the telemetry probe would be 29.
+ //
+ // Note that the expected count here is 2. It's because the opener heuristic
+ // will also be triggered when triggered UserInteraction Heuristic.
+ await testTelemetry(false, 2, LABEL_OPENER_AFTER_UI, expectedExpiredDays);
+});
+
+add_task(async function testTelemetryForRedirectHeuristic() {
+ info("Starting testing if redirect heuristic send telemetry probe ...");
+
+ const TEST_TRACKING_PAGE = TEST_3RD_PARTY_DOMAIN + TEST_PATH + "page.html";
+ const TEST_REDIRECT_PAGE =
+ TEST_3RD_PARTY_DOMAIN + TEST_PATH + "redirect.sjs?" + TEST_TOP_PAGE;
+
+ // First, clear all permissions.
+ Services.perms.removeAll();
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_TRACKING_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Loading the tracking page and trigger the redirect.");
+ SpecialPowers.spawn(browser, [TEST_REDIRECT_PAGE], async url => {
+ content.document.userInteractionForTesting();
+
+ let link = content.document.createElement("a");
+ link.appendChild(content.document.createTextNode("click me!"));
+ link.href = url;
+ content.document.body.appendChild(link);
+ link.click();
+ });
+
+ await BrowserTestUtils.browserLoaded(browser, false, TEST_TOP_PAGE);
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ // We would only grant the storage permission for 29 days for the redirect
+ // heuristic, so the expected index in the telemetry probe would be 29.
+ await testTelemetry(true, 1, LABEL_REDIRECT, expectedExpiredDaysRedirect);
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_cookieBetweenTabs.js b/toolkit/components/antitracking/test/browser/browser_cookieBetweenTabs.js
new file mode 100644
index 0000000000..635f145d46
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_cookieBetweenTabs.js
@@ -0,0 +1,59 @@
+add_task(async function () {
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["network.cookie.cookieBehavior", BEHAVIOR_REJECT],
+ ["network.cookie.cookieBehavior.pbmode", BEHAVIOR_REJECT],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ ["dom.ipc.processCount", 4],
+ ],
+ });
+
+ info("First tab opened");
+ let tab = BrowserTestUtils.addTab(
+ gBrowser,
+ TEST_DOMAIN + TEST_PATH + "empty.html"
+ );
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Disabling content blocking for this page");
+ gProtectionsHandler.disableForCurrentPage();
+
+ await BrowserTestUtils.browserLoaded(browser);
+
+ await SpecialPowers.spawn(browser, [], async _ => {
+ is(content.document.cookie, "", "No cookie set");
+ content.document.cookie = "a=b";
+ is(content.document.cookie, "a=b", "Cookie set");
+ });
+
+ info("Second tab opened");
+ let tab2 = BrowserTestUtils.addTab(
+ gBrowser,
+ TEST_DOMAIN + TEST_PATH + "empty.html"
+ );
+ gBrowser.selectedTab = tab2;
+
+ let browser2 = gBrowser.getBrowserForTab(tab2);
+ await BrowserTestUtils.browserLoaded(browser2);
+
+ await SpecialPowers.spawn(browser2, [], async _ => {
+ is(content.document.cookie, "a=b", "Cookie set");
+ });
+
+ info("Removing tabs");
+ BrowserTestUtils.removeTab(tab);
+ BrowserTestUtils.removeTab(tab2);
+
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_denyPermissionForTracker.js b/toolkit/components/antitracking/test/browser/browser_denyPermissionForTracker.js
new file mode 100644
index 0000000000..cd19fa4466
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_denyPermissionForTracker.js
@@ -0,0 +1,64 @@
+// This test works by setting up an exception for the tracker domain, which
+// disables all the anti-tracking tests.
+
+add_task(async _ => {
+ PermissionTestUtils.add(
+ "https://tracking.example.org",
+ "cookie",
+ Services.perms.DENY_ACTION
+ );
+ PermissionTestUtils.add(
+ "https://tracking.example.com",
+ "cookie",
+ Services.perms.DENY_ACTION
+ );
+ // Grant interaction permission so we can directly call
+ // requestStorageAccess from the tracker.
+ PermissionTestUtils.add(
+ "https://tracking.example.org",
+ "storageAccessAPI",
+ Services.perms.ALLOW_ACTION
+ );
+
+ registerCleanupFunction(_ => {
+ Services.perms.removeAll();
+ });
+});
+
+AntiTracking._createTask({
+ name: "Test that we do honour a cookie permission for nested windows",
+ cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+ blockingByContentBlockingRTUI: true,
+ allowList: false,
+ callback: async _ => {
+ document.cookie = "name=value";
+ ok(document.cookie == "", "All is blocked");
+
+ // requestStorageAccess should reject
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ await document
+ .requestStorageAccess()
+ .then(() => {
+ ok(false, "Should not grant storage access");
+ })
+ .catch(() => {
+ ok(true, "Should not grant storage access");
+ });
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ },
+ extraPrefs: null,
+ expectedBlockingNotifications:
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_BY_PERMISSION,
+ runInPrivateWindow: false,
+ iframeSandbox: null,
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+});
+
+add_task(async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_doublyNestedTracker.js b/toolkit/components/antitracking/test/browser/browser_doublyNestedTracker.js
new file mode 100644
index 0000000000..c15e3abd5f
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_doublyNestedTracker.js
@@ -0,0 +1,130 @@
+add_task(async function () {
+ info("Starting doubly nested tracker test");
+
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ [
+ "privacy.restrict3rdpartystorage.userInteractionRequiredForHosts",
+ "tracking.example.com,tracking.example.org",
+ ],
+ // Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default"
+ ["network.cookie.sameSite.laxByDefault", false],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_3RD_PARTY_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ async function loadSubpage() {
+ async function runChecks() {
+ is(document.cookie, "", "No cookies for me");
+ document.cookie = "name=value";
+ is(document.cookie, "name=value", "I have the cookies!");
+ }
+
+ await new Promise(resolve => {
+ let ifr = document.createElement("iframe");
+ ifr.onload = _ => {
+ info("Sending code to the 3rd party content");
+ ifr.contentWindow.postMessage(runChecks.toString(), "*");
+ };
+
+ addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ document.body.appendChild(ifr);
+ ifr.src =
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/3rdParty.html";
+ });
+ }
+
+ // We need to use the same scheme in Fission test.
+ let testAnotherThirdPartyPage = SpecialPowers.useRemoteSubframes
+ ? TEST_ANOTHER_3RD_PARTY_PAGE_HTTPS
+ : TEST_ANOTHER_3RD_PARTY_PAGE;
+
+ await SpecialPowers.spawn(
+ browser,
+ [{ page: testAnotherThirdPartyPage, callback: loadSubpage.toString() }],
+ async function (obj) {
+ await new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.onload = _ => {
+ info("Sending code to the 3rd party content");
+ ifr.contentWindow.postMessage(obj.callback, "*");
+ };
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ });
+ }
+ );
+
+ BrowserTestUtils.removeTab(tab);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+});
+
+add_task(async function () {
+ info("Cleaning up.");
+ SpecialPowers.clearUserPref("network.cookie.sameSite.laxByDefault");
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_emailtracking.js b/toolkit/components/antitracking/test/browser/browser_emailtracking.js
new file mode 100644
index 0000000000..90616aba6d
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_emailtracking.js
@@ -0,0 +1,182 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+add_setup(async function () {
+ // Disable other tracking protection feature to avoid interfering with the
+ // current test. This also setup prefs for testing email tracking.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", false],
+ ["privacy.trackingprotection.cryptomining.enabled", false],
+ ["privacy.trackingprotection.emailtracking.enabled", true],
+ ["privacy.trackingprotection.fingerprinting.enabled", false],
+ ["privacy.trackingprotection.socialtracking.enabled", false],
+ [
+ "urlclassifier.features.emailtracking.blocklistTables",
+ "mochitest5-track-simple",
+ ],
+ ["urlclassifier.features.emailtracking.allowlistTables", ""],
+ [
+ "urlclassifier.features.emailtracking.datacollection.blocklistTables",
+ "mochitest5-track-simple",
+ ],
+ [
+ "urlclassifier.features.emailtracking.datacollection.allowlistTables",
+ "",
+ ],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ registerCleanupFunction(_ => {
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ });
+});
+
+function runTest(obj) {
+ add_task(async _ => {
+ info("Test: " + obj.testName);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "privacy.trackingprotection.emailtracking.enabled",
+ obj.protectionEnabled,
+ ],
+ [
+ "privacy.trackingprotection.emailtracking.pbmode.enabled",
+ obj.protectionPrivateEnabled,
+ ],
+ ],
+ });
+
+ let win;
+
+ if (obj.testPrivate) {
+ win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+ } else {
+ win = window;
+ }
+
+ info("Creating a non-tracker top-level context");
+ let tab = BrowserTestUtils.addTab(win.gBrowser, TEST_TOP_PAGE);
+ let browser = tab.linkedBrowser;
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("The non-tracker page opens an email tracker iframe");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ image: TEST_EMAIL_TRACKER_DOMAIN + TEST_PATH + "raptor.jpg",
+ script: TEST_EMAIL_TRACKER_DOMAIN + TEST_PATH + "empty.js",
+ loading: obj.loading,
+ },
+ ],
+ async obj => {
+ info("Image loading ...");
+ let loading = await new content.Promise(resolve => {
+ let image = new content.Image();
+ image.src = obj.image + "?" + Math.random();
+ image.onload = _ => resolve(true);
+ image.onerror = _ => resolve(false);
+ });
+
+ is(loading, obj.loading, "Image loading expected");
+
+ let script = content.document.createElement("script");
+ script.setAttribute("src", obj.script);
+
+ info("Script loading ...");
+ loading = await new content.Promise(resolve => {
+ script.onload = _ => resolve(true);
+ script.onerror = _ => resolve(false);
+ content.document.body.appendChild(script);
+ });
+
+ is(loading, obj.loading, "Script loading expected");
+ }
+ );
+
+ info("Checking content blocking log.");
+ let contentBlockingLog = JSON.parse(await browser.getContentBlockingLog());
+ let origins = Object.keys(contentBlockingLog);
+ is(origins.length, 1, "There should be one origin entry in the log.");
+ for (let origin of origins) {
+ is(
+ origin + "/",
+ TEST_EMAIL_TRACKER_DOMAIN,
+ "Correct tracker origin must be reported"
+ );
+ Assert.deepEqual(
+ contentBlockingLog[origin],
+ obj.expectedLogItems,
+ "Content blocking log should be as expected"
+ );
+ }
+
+ BrowserTestUtils.removeTab(tab);
+ if (obj.testPrivate) {
+ await BrowserTestUtils.closeWindow(win);
+ }
+ await SpecialPowers.popPrefEnv();
+ });
+}
+
+runTest({
+ testName:
+ "EmailTracking-dataCollection feature enabled but not considered for tracking detection.",
+ protectionEnabled: false,
+ protectionPrivateEnabled: false,
+ loading: true,
+ expectedLogItems: [
+ [
+ Ci.nsIWebProgressListener.STATE_LOADED_EMAILTRACKING_LEVEL_1_CONTENT,
+ true,
+ 2,
+ ],
+ ],
+});
+
+runTest({
+ testName: "Emailtracking-protection feature enabled.",
+ protectionEnabled: true,
+ protectionPrivateEnabled: true,
+ loading: false,
+ expectedLogItems: [
+ [Ci.nsIWebProgressListener.STATE_BLOCKED_EMAILTRACKING_CONTENT, true, 2],
+ ],
+});
+
+runTest({
+ testName:
+ "Emailtracking-protection feature enabled for private windows and doesn't block in normal windows",
+ protectionEnabled: false,
+ protectionPrivateEnabled: true,
+ loading: true,
+ expectedLogItems: [
+ [
+ Ci.nsIWebProgressListener.STATE_LOADED_EMAILTRACKING_LEVEL_1_CONTENT,
+ true,
+ 2,
+ ],
+ ],
+});
+
+runTest({
+ testName:
+ "Emailtracking-protection feature enabled for private windows and block in private windows",
+ testPrivate: true,
+ protectionEnabled: true,
+ protectionPrivateEnabled: true,
+ loading: false,
+ expectedLogItems: [
+ [Ci.nsIWebProgressListener.STATE_BLOCKED_EMAILTRACKING_CONTENT, true, 1],
+ ],
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_existingCookiesForSubresources.js b/toolkit/components/antitracking/test/browser/browser_existingCookiesForSubresources.js
new file mode 100644
index 0000000000..35e9dfb169
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_existingCookiesForSubresources.js
@@ -0,0 +1,235 @@
+add_task(async function () {
+ info("Starting subResources test");
+
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ [
+ "privacy.restrict3rdpartystorage.userInteractionRequiredForHosts",
+ "tracking.example.com,tracking.example.org",
+ ],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_3RD_PARTY_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info(
+ "Loading tracking scripts and tracking images before restricting 3rd party cookies"
+ );
+ await SpecialPowers.spawn(browser, [], async function () {
+ // Let's load the script twice here.
+ {
+ let src = content.document.createElement("script");
+ let p = new content.Promise(resolve => {
+ src.onload = resolve;
+ });
+ content.document.body.appendChild(src);
+ src.src =
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=script";
+ await p;
+ }
+ {
+ let src = content.document.createElement("script");
+ let p = new content.Promise(resolve => {
+ src.onload = resolve;
+ });
+ content.document.body.appendChild(src);
+ src.src =
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=script";
+ await p;
+ }
+
+ // Let's load an image twice here.
+ {
+ let img = content.document.createElement("img");
+ let p = new content.Promise(resolve => {
+ img.onload = resolve;
+ });
+ content.document.body.appendChild(img);
+ img.src =
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=image";
+ await p;
+ }
+ {
+ let img = content.document.createElement("img");
+ let p = new content.Promise(resolve => {
+ img.onload = resolve;
+ });
+ content.document.body.appendChild(img);
+ img.src =
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=image";
+ await p;
+ }
+ });
+
+ await fetch(
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?result&what=image"
+ )
+ .then(r => r.text())
+ .then(text => {
+ is(text, "1", "Cookies received for images");
+ });
+
+ await fetch(
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?result&what=script"
+ )
+ .then(r => r.text())
+ .then(text => {
+ is(text, "1", "Cookies received for scripts");
+ });
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ Services.perms.removeAll();
+
+ // Now set up our prefs
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ [
+ "privacy.restrict3rdpartystorage.userInteractionRequiredForHosts",
+ "tracking.example.com,tracking.example.org",
+ ],
+ ],
+ });
+
+ info("Creating a new tab");
+ tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Creating a 3rd party content");
+ await SpecialPowers.spawn(
+ browser,
+ [{ page: TEST_3RD_PARTY_PAGE, callback: (async _ => {}).toString() }],
+ async function (obj) {
+ await new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.onload = function () {
+ info("Sending code to the 3rd party content");
+ ifr.contentWindow.postMessage(obj.callback, "*");
+ };
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ });
+ }
+ );
+
+ info("Loading tracking scripts and tracking images again");
+ await SpecialPowers.spawn(browser, [], async function () {
+ // Let's load the script twice here.
+ {
+ let src = content.document.createElement("script");
+ let p = new content.Promise(resolve => {
+ src.onload = resolve;
+ });
+ content.document.body.appendChild(src);
+ src.src =
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=script";
+ await p;
+ }
+ {
+ let src = content.document.createElement("script");
+ let p = new content.Promise(resolve => {
+ src.onload = resolve;
+ });
+ content.document.body.appendChild(src);
+ src.src =
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=script";
+ await p;
+ }
+
+ // Let's load an image twice here.
+ {
+ let img = content.document.createElement("img");
+ let p = new content.Promise(resolve => {
+ img.onload = resolve;
+ });
+ content.document.body.appendChild(img);
+ img.src =
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=image";
+ await p;
+ }
+ {
+ let img = content.document.createElement("img");
+ let p = new content.Promise(resolve => {
+ img.onload = resolve;
+ });
+ content.document.body.appendChild(img);
+ img.src =
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=image";
+ await p;
+ }
+ });
+
+ await fetch(
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?result&what=image"
+ )
+ .then(r => r.text())
+ .then(text => {
+ is(text, "0", "No cookie received for images.");
+ });
+
+ await fetch(
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?result&what=script"
+ )
+ .then(r => r.text())
+ .then(text => {
+ is(text, "0", "No cookie received received for scripts.");
+ });
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+});
+
+add_task(async function () {
+ info("Cleaning up.");
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_fileUrl.js b/toolkit/components/antitracking/test/browser/browser_fileUrl.js
new file mode 100644
index 0000000000..509c143a9b
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_fileUrl.js
@@ -0,0 +1,41 @@
+/**
+ * Bug 1663192 - Testing for ensuring the top-level window in a fire url is
+ * treated as first-party.
+ */
+
+"use strict";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["network.cookie.cookieBehavior", 1],
+ ["network.cookie.cookieBehavior.pbmode", 1],
+ ],
+ });
+});
+
+add_task(async function () {
+ let dir = getChromeDir(getResolvedURI(gTestPath));
+ dir.append("file_localStorage.html");
+ const uriString = Services.io.newFileURI(dir).spec;
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, uriString);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ let result = content.document.getElementById("result");
+
+ is(
+ result.textContent,
+ "PASS",
+ "The localStorage is accessible in top-level window"
+ );
+
+ let loadInfo = content.docShell.currentDocumentChannel.loadInfo;
+
+ ok(
+ !loadInfo.isThirdPartyContextToTopWindow,
+ "The top-level window shouldn't be third-party"
+ );
+ });
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_firstPartyCookieRejectionHonoursAllowList.js b/toolkit/components/antitracking/test/browser/browser_firstPartyCookieRejectionHonoursAllowList.js
new file mode 100644
index 0000000000..d3d06d2950
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_firstPartyCookieRejectionHonoursAllowList.js
@@ -0,0 +1,77 @@
+add_task(async function () {
+ info("Starting subResources test");
+
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_REJECT],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT,
+ ],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ ],
+ });
+
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Disabling content blocking for this page");
+ gProtectionsHandler.disableForCurrentPage();
+
+ // The previous function reloads the browser, so wait for it to load again!
+ await BrowserTestUtils.browserLoaded(browser);
+
+ await SpecialPowers.spawn(browser, [], async function (obj) {
+ await new content.Promise(async resolve => {
+ let document = content.document;
+ let window = document.defaultView;
+
+ is(document.cookie, "", "No cookies for me");
+
+ await window
+ .fetch("server.sjs")
+ .then(r => r.text())
+ .then(text => {
+ is(text, "cookie-not-present", "We should not have cookies");
+ });
+
+ document.cookie = "name=value";
+ ok(document.cookie.includes("name=value"), "Some cookies for me");
+ ok(document.cookie.includes("foopy=1"), "Some cookies for me");
+
+ await window
+ .fetch("server.sjs")
+ .then(r => r.text())
+ .then(text => {
+ is(text, "cookie-present", "We should have cookies");
+ });
+
+ ok(document.cookie.length, "Some Cookies for me");
+
+ resolve();
+ });
+ });
+
+ info("Enabling content blocking for this page");
+ gProtectionsHandler.enableForCurrentPage();
+
+ // The previous function reloads the browser, so wait for it to load again!
+ await BrowserTestUtils.browserLoaded(browser);
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function () {
+ info("Cleaning up.");
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_fpiServiceWorkers_fingerprinting.js b/toolkit/components/antitracking/test/browser/browser_fpiServiceWorkers_fingerprinting.js
new file mode 100644
index 0000000000..fcdc83cde6
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_fpiServiceWorkers_fingerprinting.js
@@ -0,0 +1,90 @@
+/* import-globals-from storageAccessAPIHelpers.js */
+
+// This test ensures that a service worker for an exempted domain is exempted when it is
+// in the first party context, and not exempted when it is in a third party context.
+
+PartitionedStorageHelper.runTest(
+ "ServiceWorkers - Check that RFP correctly is exempted and not exempted when FPI is enabled",
+ async (win3rdParty, win1stParty, allowed) => {
+ // Quickly unset and reset these prefs so we can get the real navigator.hardwareConcurrency
+ // It can't be set enternally and then captured because this function gets stringified and then evaled in a new scope.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.firstParty.isolate", false],
+ ["privacy.resistFingerprinting", false],
+ ],
+ });
+
+ var SPOOFED_HW_CONCURRENCY = 2;
+ var DEFAULT_HARDWARE_CONCURRENCY = navigator.hardwareConcurrency;
+
+ await SpecialPowers.popPrefEnv();
+
+ // Register service worker for the first-party window.
+ if (!win1stParty.sw) {
+ win1stParty.sw = await registerServiceWorker(
+ win1stParty,
+ "serviceWorker.js"
+ );
+ }
+
+ // Register service worker for the third-party window.
+ if (!win3rdParty.sw) {
+ win3rdParty.sw = await registerServiceWorker(
+ win3rdParty,
+ "serviceWorker.js"
+ );
+ }
+
+ // Check DOM cache from the first-party service worker.
+ let res = await sendAndWaitWorkerMessage(
+ win1stParty.sw,
+ win1stParty.navigator.serviceWorker,
+ { type: "GetHWConcurrency" }
+ );
+ console.info(
+ "First Party, got: " +
+ res.value +
+ " Expected: " +
+ DEFAULT_HARDWARE_CONCURRENCY
+ );
+ is(
+ res.value,
+ DEFAULT_HARDWARE_CONCURRENCY,
+ "As a first party, HW Concurrency should not be spoofed"
+ );
+
+ // Check DOM cache from the third-party service worker.
+ res = await sendAndWaitWorkerMessage(
+ win3rdParty.sw,
+ win3rdParty.navigator.serviceWorker,
+ { type: "GetHWConcurrency" }
+ );
+ console.info(
+ "Third Party, got: " + res.value + " Expected: " + SPOOFED_HW_CONCURRENCY
+ );
+ is(
+ res.value,
+ SPOOFED_HW_CONCURRENCY,
+ "As a third party, HW Concurrency should be spoofed"
+ );
+ },
+
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+
+ [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.ipc.processCount", 1],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["privacy.firstParty.isolate", true],
+ ["privacy.resistFingerprinting", true],
+ ["privacy.resistFingerprinting.exemptedDomains", "*.example.com"],
+ ]
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_hasStorageAccess.js b/toolkit/components/antitracking/test/browser/browser_hasStorageAccess.js
new file mode 100644
index 0000000000..c51959fe78
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_hasStorageAccess.js
@@ -0,0 +1,196 @@
+// This test ensures HasStorageAccess API returns the right value under different
+// scenarios.
+
+var settings = [
+ // same-origin no-tracker
+ {
+ name: "Test whether same-origin non-tracker frame has storage access",
+ topPage: TEST_TOP_PAGE,
+ thirdPartyPage: TEST_DOMAIN + TEST_PATH + "3rdParty.html",
+ },
+ // 3rd-party no-tracker
+ {
+ name: "Test whether 3rd-party non-tracker frame has storage access",
+ topPage: TEST_TOP_PAGE,
+ thirdPartyPage: TEST_4TH_PARTY_PAGE,
+ },
+ // 3rd-party no-tracker with permission
+ {
+ name: "Test whether 3rd-party non-tracker frame has storage access when storage permission is granted before",
+ topPage: TEST_TOP_PAGE,
+ thirdPartyPage: TEST_4TH_PARTY_PAGE,
+ setup: () => {
+ let type = "3rdPartyStorage^http://not-tracking.example.com";
+ let permission = Services.perms.ALLOW_ACTION;
+ let expireType = Services.perms.EXPIRE_SESSION;
+ PermissionTestUtils.add(TEST_DOMAIN, type, permission, expireType, 0);
+
+ registerCleanupFunction(_ => {
+ Services.perms.removeAll();
+ });
+ },
+ },
+ // 3rd-party tracker
+ {
+ name: "Test whether 3rd-party tracker frame has storage access",
+ topPage: TEST_TOP_PAGE,
+ thirdPartyPage: TEST_3RD_PARTY_PAGE,
+ },
+ // 3rd-party tracker with permission
+ {
+ name: "Test whether 3rd-party tracker frame has storage access when storage access permission is granted before",
+ topPage: TEST_TOP_PAGE,
+ thirdPartyPage: TEST_3RD_PARTY_PAGE,
+ setup: () => {
+ let type = "3rdPartyStorage^https://tracking.example.org";
+ let permission = Services.perms.ALLOW_ACTION;
+ let expireType = Services.perms.EXPIRE_SESSION;
+ PermissionTestUtils.add(TEST_DOMAIN, type, permission, expireType, 0);
+
+ registerCleanupFunction(_ => {
+ Services.perms.removeAll();
+ });
+ },
+ },
+ // same-site 3rd-party tracker
+ {
+ name: "Test whether same-site 3rd-party tracker frame has storage access",
+ topPage: TEST_TOP_PAGE,
+ thirdPartyPage: TEST_ANOTHER_3RD_PARTY_PAGE,
+ },
+ // same-origin 3rd-party tracker
+ {
+ name: "Test whether same-origin 3rd-party tracker frame has storage access",
+ topPage: TEST_ANOTHER_3RD_PARTY_DOMAIN + TEST_PATH + "page.html",
+ thirdPartyPage: TEST_ANOTHER_3RD_PARTY_PAGE,
+ },
+];
+
+var testCases = [
+ {
+ behavior: BEHAVIOR_ACCEPT, // 0
+ hasStorageAccess: [
+ true /* same-origin non-tracker */,
+ true /* 3rd-party non-tracker */,
+ true /* 3rd-party non-tracker with permission */,
+ true /* 3rd-party tracker */,
+ true /* 3rd-party tracker with permission */,
+ true /* same-site tracker */,
+ true /* same-origin tracker */,
+ ],
+ },
+ {
+ behavior: BEHAVIOR_REJECT_FOREIGN, // 1
+ hasStorageAccess: [
+ true /* same-origin non-tracker */,
+ false /* 3rd-party non-tracker */,
+ SpecialPowers.Services.prefs.getBoolPref(
+ "network.cookie.rejectForeignWithExceptions.enabled"
+ ) /* 3rd-party tracker with permission */,
+ false /* 3rd-party tracker */,
+ SpecialPowers.Services.prefs.getBoolPref(
+ "network.cookie.rejectForeignWithExceptions.enabled"
+ ) /* 3rd-party non-tracker with permission */,
+ true /* same-site tracker */,
+ true /* same-origin tracker */,
+ ],
+ },
+ {
+ behavior: BEHAVIOR_REJECT, // 2
+ hasStorageAccess: [
+ false /* same-origin non-tracker */,
+ false /* 3rd-party non-tracker */,
+ false /* 3rd-party non-tracker with permission */,
+ false /* 3rd-party tracker */,
+ false /* 3rd-party tracker with permission */,
+ false /* same-site tracker */,
+ false /* same-origin tracker */,
+ ],
+ },
+ {
+ behavior: BEHAVIOR_LIMIT_FOREIGN, // 3
+ hasStorageAccess: [
+ true /* same-origin non-tracker */,
+ false /* 3rd-party non-tracker */,
+ false /* 3rd-party non-tracker with permission */,
+ false /* 3rd-party tracker */,
+ false /* 3rd-party tracker with permission */,
+ true /* same-site tracker */,
+ true /* same-origin tracker */,
+ ],
+ },
+ {
+ behavior: BEHAVIOR_REJECT_TRACKER, // 4
+ hasStorageAccess: [
+ true /* same-origin non-tracker */,
+ true /* 3rd-party non-tracker */,
+ true /* 3rd-party non-tracker with permission */,
+ false /* 3rd-party tracker */,
+ true /* 3rd-party tracker with permission */,
+ true /* same-site tracker */,
+ true /* same-origin tracker */,
+ ],
+ },
+ {
+ behavior: BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN, // 5
+ hasStorageAccess: [
+ true /* same-origin non-tracker */,
+ false /* 3rd-party non-tracker */,
+ true /* 3rd-party non-tracker with permission */,
+ false /* 3rd-party tracker */,
+ true /* 3rd-party tracker with permission */,
+ true /* same-site tracker */,
+ true /* same-origin tracker */,
+ ],
+ },
+];
+
+(function () {
+ settings.forEach(setting => {
+ if (setting.setup) {
+ add_task(async _ => {
+ setting.setup();
+ });
+ }
+
+ testCases.forEach(test => {
+ let callback = test.hasStorageAccess[settings.indexOf(setting)]
+ ? async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await hasStorageAccessInitially();
+ }
+ : async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+ };
+
+ AntiTracking._createTask({
+ name: setting.name,
+ cookieBehavior: test.behavior,
+ allowList: false,
+ callback,
+ extraPrefs: [
+ [
+ "privacy.partition.always_partition_third_party_non_cookie_storage",
+ false,
+ ],
+ ],
+ expectedBlockingNotifications: 0,
+ runInPrivateWindow: false,
+ iframeSandbox: null,
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+ topPage: setting.topPage,
+ thirdPartyPage: setting.thirdPartyPage,
+ });
+ });
+
+ add_task(async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ });
+ });
+})();
diff --git a/toolkit/components/antitracking/test/browser/browser_hasStorageAccess_alwaysPartition.js b/toolkit/components/antitracking/test/browser/browser_hasStorageAccess_alwaysPartition.js
new file mode 100644
index 0000000000..d89b0b1103
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_hasStorageAccess_alwaysPartition.js
@@ -0,0 +1,209 @@
+// This test ensures HasStorageAccess API returns the right value under different
+// scenarios.
+
+var settings = [
+ // same-origin no-tracker
+ {
+ name: "Test whether same-origin non-tracker frame has storage access",
+ topPage: TEST_TOP_PAGE,
+ thirdPartyPage: TEST_DOMAIN + TEST_PATH + "3rdParty.html",
+ },
+ // 3rd-party no-tracker
+ {
+ name: "Test whether 3rd-party non-tracker frame has storage access",
+ topPage: TEST_TOP_PAGE,
+ thirdPartyPage: TEST_4TH_PARTY_PAGE,
+ },
+ // 3rd-party no-tracker with permission
+ {
+ name: "Test whether 3rd-party non-tracker frame has storage access when storage permission is granted before",
+ topPage: TEST_TOP_PAGE,
+ thirdPartyPage: TEST_4TH_PARTY_PAGE,
+ setup: () => {
+ let type = "3rdPartyStorage^http://not-tracking.example.com";
+ let permission = Services.perms.ALLOW_ACTION;
+ let expireType = Services.perms.EXPIRE_SESSION;
+ PermissionTestUtils.add(TEST_DOMAIN, type, permission, expireType, 0);
+
+ registerCleanupFunction(_ => {
+ Services.perms.removeAll();
+ });
+ },
+ },
+ // 3rd-party tracker
+ {
+ name: "Test whether 3rd-party tracker frame has storage access",
+ topPage: TEST_TOP_PAGE,
+ thirdPartyPage: TEST_3RD_PARTY_PAGE,
+ },
+ // 3rd-party tracker with permission
+ {
+ name: "Test whether 3rd-party tracker frame has storage access when storage access permission is granted before",
+ topPage: TEST_TOP_PAGE,
+ thirdPartyPage: TEST_3RD_PARTY_PAGE,
+ setup: () => {
+ let type = "3rdPartyStorage^https://tracking.example.org";
+ let permission = Services.perms.ALLOW_ACTION;
+ let expireType = Services.perms.EXPIRE_SESSION;
+ PermissionTestUtils.add(TEST_DOMAIN, type, permission, expireType, 0);
+
+ registerCleanupFunction(_ => {
+ Services.perms.removeAll();
+ });
+ },
+ },
+ // same-site 3rd-party tracker
+ {
+ name: "Test whether same-site 3rd-party tracker frame has storage access",
+ topPage: TEST_TOP_PAGE,
+ thirdPartyPage: TEST_ANOTHER_3RD_PARTY_PAGE,
+ },
+ // same-origin 3rd-party tracker
+ {
+ name: "Test whether same-origin 3rd-party tracker frame has storage access",
+ topPage: TEST_ANOTHER_3RD_PARTY_DOMAIN + TEST_PATH + "page.html",
+ thirdPartyPage: TEST_ANOTHER_3RD_PARTY_PAGE,
+ },
+];
+
+const allBlocked = Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_ALL;
+const foreignBlocked = Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_FOREIGN;
+const trackerBlocked = Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER;
+
+var testCases = [
+ {
+ behavior: BEHAVIOR_ACCEPT, // 0
+ cases: [
+ [true] /* same-origin non-tracker */,
+ [true] /* 3rd-party non-tracker */,
+ [true] /* 3rd-party non-tracker with permission */,
+ [true] /* 3rd-party tracker */,
+ [true] /* 3rd-party tracker with permission */,
+ [true] /* same-site tracker */,
+ [true] /* same-origin tracker */,
+ ],
+ },
+ {
+ behavior: BEHAVIOR_REJECT_FOREIGN, // 1
+ cases: [
+ [true] /* same-origin non-tracker */,
+ [false, foreignBlocked] /* 3rd-party non-tracker */,
+ [
+ SpecialPowers.Services.prefs.getBoolPref(
+ "network.cookie.rejectForeignWithExceptions.enabled"
+ ),
+ foreignBlocked,
+ ] /* 3rd-party tracker with permission */,
+ [false, foreignBlocked] /* 3rd-party tracker */,
+ [
+ SpecialPowers.Services.prefs.getBoolPref(
+ "network.cookie.rejectForeignWithExceptions.enabled"
+ ),
+ foreignBlocked,
+ ] /* 3rd-party non-tracker with permission */,
+ [true] /* same-site tracker */,
+ [true] /* same-origin tracker */,
+ ],
+ },
+ {
+ behavior: BEHAVIOR_REJECT, // 2
+ cases: [
+ [false, allBlocked] /* same-origin non-tracker */,
+ [false, allBlocked] /* 3rd-party non-tracker */,
+ [false, allBlocked] /* 3rd-party non-tracker with permission */,
+ [false, allBlocked] /* 3rd-party tracker */,
+ [false, allBlocked] /* 3rd-party tracker with permission */,
+ [false, allBlocked] /* same-site tracker */,
+ [false, allBlocked] /* same-origin tracker */,
+ ],
+ },
+ {
+ behavior: BEHAVIOR_LIMIT_FOREIGN, // 3
+ cases: [
+ [true] /* same-origin non-tracker */,
+ [false, foreignBlocked] /* 3rd-party non-tracker */,
+ [false, foreignBlocked] /* 3rd-party non-tracker with permission */,
+ [false, foreignBlocked] /* 3rd-party tracker */,
+ [false, foreignBlocked] /* 3rd-party tracker with permission */,
+ [true] /* same-site tracker */,
+ [true] /* same-origin tracker */,
+ ],
+ },
+ {
+ behavior: BEHAVIOR_REJECT_TRACKER, // 4
+ cases: [
+ [true] /* same-origin non-tracker */,
+ [true] /* 3rd-party non-tracker */,
+ [true] /* 3rd-party non-tracker with permission */,
+ [false, trackerBlocked] /* 3rd-party tracker */,
+ [true] /* 3rd-party tracker with permission */,
+ [true] /* same-site tracker */,
+ [true] /* same-origin tracker */,
+ ],
+ },
+ {
+ behavior: BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN, // 5
+ cases: [
+ [true] /* same-origin non-tracker */,
+ [false] /* 3rd-party non-tracker */,
+ [true] /* 3rd-party non-tracker with permission */,
+ [false, trackerBlocked] /* 3rd-party tracker */,
+ [true] /* 3rd-party tracker with permission */,
+ [true] /* same-site tracker */,
+ [true] /* same-origin tracker */,
+ ],
+ },
+];
+
+(function () {
+ settings.forEach(setting => {
+ ok(true, JSON.stringify(setting));
+ if (setting.setup) {
+ add_task(async _ => {
+ setting.setup();
+ });
+ }
+
+ testCases.forEach(test => {
+ let [hasStorageAccess, expectedBlockingNotifications] =
+ test.cases[settings.indexOf(setting)];
+ let callback = hasStorageAccess
+ ? async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await hasStorageAccessInitially();
+ }
+ : async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+ };
+
+ AntiTracking._createTask({
+ name: setting.name,
+ cookieBehavior: test.behavior,
+ allowList: false,
+ callback,
+ extraPrefs: [
+ [
+ "privacy.partition.always_partition_third_party_non_cookie_storage",
+ true,
+ ],
+ ],
+ expectedBlockingNotifications,
+ runInPrivateWindow: false,
+ iframeSandbox: null,
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+ topPage: setting.topPage,
+ thirdPartyPage: setting.thirdPartyPage,
+ });
+ });
+
+ add_task(async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ });
+ });
+})();
diff --git a/toolkit/components/antitracking/test/browser/browser_iframe_document_open.js b/toolkit/components/antitracking/test/browser/browser_iframe_document_open.js
new file mode 100644
index 0000000000..73876ee1a5
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_iframe_document_open.js
@@ -0,0 +1,86 @@
+//
+// Bug 1725996 - Test if the cookie set in a document which is created by
+// document.open() in an about:blank iframe has a correct
+// partitionKey
+//
+
+const TEST_PAGE = TEST_DOMAIN + TEST_PATH + "file_iframe_document_open.html";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "network.cookie.cookieBehavior",
+ BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ ["privacy.dynamic_firstparty.use_site", true],
+ ],
+ });
+});
+
+add_task(async function test_firstParty_iframe() {
+ // Clear all cookies before test.
+ Services.cookies.removeAll();
+
+ // Open the test page which creates an iframe and then calls document.write()
+ // to write a cookie in the iframe.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE);
+
+ // Wait until the cookie appears.
+ await TestUtils.waitForCondition(_ => Services.cookies.cookies.length);
+
+ // Check the partitionKey in the cookie.
+ let cookie = Services.cookies.cookies[0];
+ is(
+ cookie.originAttributes.partitionKey,
+ "",
+ "The partitionKey should remain empty for first-party iframe."
+ );
+
+ // Clean up.
+ Services.cookies.removeAll();
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_thirdParty_iframe() {
+ // Clear all cookies before test.
+ Services.cookies.removeAll();
+
+ // Open a tab with a different domain with the test page.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_TOP_PAGE_2
+ );
+
+ // Open the test page within a third-party context.
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [TEST_PAGE],
+ async function (page) {
+ let ifr = content.document.createElement("iframe");
+ let loading = ContentTaskUtils.waitForEvent(ifr, "load");
+ ifr.src = page;
+ content.document.body.appendChild(ifr);
+ await loading;
+ }
+ );
+
+ // Wait until the cookie appears.
+ await TestUtils.waitForCondition(_ => Services.cookies.cookies.length);
+
+ // Check the partitionKey in the cookie.
+ let cookie = Services.cookies.cookies[0];
+ is(
+ cookie.originAttributes.partitionKey,
+ "(http,xn--exmple-cua.test)",
+ "The partitionKey should exist for third-party iframe."
+ );
+
+ // Clean up.
+ Services.cookies.removeAll();
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_imageCache4.js b/toolkit/components/antitracking/test/browser/browser_imageCache4.js
new file mode 100644
index 0000000000..8fcc298cf0
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_imageCache4.js
@@ -0,0 +1,13 @@
+let cookieBehavior = BEHAVIOR_REJECT_TRACKER;
+let blockingByAllowList = false;
+let expectedBlockingNotifications =
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER;
+
+let rootDir = getRootDirectory(gTestPath);
+let jar = getJar(rootDir);
+if (jar) {
+ let tmpdir = extractJarToTmp(jar);
+ rootDir = "file://" + tmpdir.path + "/";
+}
+/* import-globals-from imageCacheWorker.js */
+Services.scriptloader.loadSubScript(rootDir + "imageCacheWorker.js", this);
diff --git a/toolkit/components/antitracking/test/browser/browser_imageCache8.js b/toolkit/components/antitracking/test/browser/browser_imageCache8.js
new file mode 100644
index 0000000000..b57bf1dca5
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_imageCache8.js
@@ -0,0 +1,13 @@
+let cookieBehavior = BEHAVIOR_REJECT_TRACKER;
+let blockingByAllowList = true;
+let expectedBlockingNotifications =
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER;
+
+let rootDir = getRootDirectory(gTestPath);
+let jar = getJar(rootDir);
+if (jar) {
+ let tmpdir = extractJarToTmp(jar);
+ rootDir = "file://" + tmpdir.path + "/";
+}
+/* import-globals-from imageCacheWorker.js */
+Services.scriptloader.loadSubScript(rootDir + "imageCacheWorker.js", this);
diff --git a/toolkit/components/antitracking/test/browser/browser_localStorageEvents.js b/toolkit/components/antitracking/test/browser/browser_localStorageEvents.js
new file mode 100644
index 0000000000..b46cc60b91
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_localStorageEvents.js
@@ -0,0 +1,186 @@
+add_task(async function () {
+ info("Starting subResources test");
+
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+});
+
+add_task(async function testLocalStorageEventPropagation() {
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Loading tracking scripts");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: TEST_3RD_PARTY_DOMAIN + TEST_PATH + "localStorage.html",
+ },
+ ],
+ async obj => {
+ info("Creating tracker iframe");
+
+ let ifr = content.document.createElement("iframe");
+ ifr.src = obj.page;
+
+ await new content.Promise(resolve => {
+ ifr.onload = function () {
+ resolve();
+ };
+ content.document.body.appendChild(ifr);
+ });
+
+ info("LocalStorage should be blocked.");
+ await new content.Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ if (e.data.type == "test") {
+ is(e.data.status, false, "LocalStorage blocked");
+ } else {
+ ok(false, "Unknown message");
+ }
+ resolve();
+ },
+ { once: true }
+ );
+ ifr.contentWindow.postMessage("test", "*");
+ });
+
+ info("Let's open the popup");
+ await new content.Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ if (e.data.type == "test") {
+ is(e.data.status, true, "LocalStorage unblocked");
+ } else {
+ ok(false, "Unknown message");
+ }
+ resolve();
+ },
+ { once: true }
+ );
+ ifr.contentWindow.postMessage("open", "*");
+ });
+ }
+ );
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ info("Cleaning up.");
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
+
+add_task(async function testBlockedLocalStorageEventPropagation() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "privacy.restrict3rdpartystorage.userInteractionRequiredForHosts",
+ "tracking.example.com,tracking.example.org",
+ ],
+ ],
+ });
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Loading tracking scripts");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: TEST_3RD_PARTY_DOMAIN + TEST_PATH + "localStorage.html",
+ },
+ ],
+ async obj => {
+ info("Creating tracker iframe");
+
+ let ifr = content.document.createElement("iframe");
+ ifr.src = obj.page;
+
+ await new content.Promise(resolve => {
+ ifr.onload = function () {
+ resolve();
+ };
+ content.document.body.appendChild(ifr);
+ });
+
+ info("LocalStorage should be blocked.");
+ await new content.Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ if (e.data.type == "test") {
+ is(e.data.status, false, "LocalStorage blocked");
+ } else {
+ ok(false, "Unknown message");
+ }
+ resolve();
+ },
+ { once: true }
+ );
+ ifr.contentWindow.postMessage("test", "*");
+ });
+
+ info("Let's open the popup");
+ await new content.Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ if (e.data.type == "test") {
+ is(e.data.status, false, "LocalStorage still blocked");
+ } else {
+ ok(false, "Unknown message");
+ }
+ resolve();
+ },
+ { once: true }
+ );
+ ifr.contentWindow.postMessage("open and test", "*");
+ });
+ }
+ );
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+
+ info("Cleaning up.");
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_onBeforeRequestNotificationForTrackingResources.js b/toolkit/components/antitracking/test/browser/browser_onBeforeRequestNotificationForTrackingResources.js
new file mode 100644
index 0000000000..847bfd7dc9
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_onBeforeRequestNotificationForTrackingResources.js
@@ -0,0 +1,96 @@
+/**
+ * This test ensures that onBeforeRequest is dispatched for webRequest loads that
+ * are blocked by tracking protection. It sets up a page with a third-party script
+ * resource on it that is blocked by TP, and sets up an onBeforeRequest listener
+ * which waits to be notified about that resource. The test would time out if the
+ * onBeforeRequest listener isn't called dispatched before the load is canceled.
+ */
+
+let extension;
+add_task(async function () {
+ extension = ExtensionTestUtils.loadExtension({
+ manifest: { permissions: ["webRequest", "webRequestBlocking", "*://*/*"] },
+ async background() {
+ let gExpectedResourcesSeen = 0;
+ function onBeforeRequest(details) {
+ let spec = details.url;
+ browser.test.log("Observed channel for " + spec);
+ // We would use TEST_3RD_PARTY_DOMAIN_TP here, but the variable is inaccessible
+ // since it is defined in head.js!
+ if (!spec.startsWith("https://tracking.example.com/")) {
+ return undefined;
+ }
+ if (spec.endsWith("empty.js")) {
+ browser.test.succeed("Correct resource observed");
+ ++gExpectedResourcesSeen;
+ } else if (spec.endsWith("empty.js?redirect")) {
+ return { redirectUrl: spec.replace("empty.js?redirect", "head.js") };
+ } else if (spec.endsWith("head.js")) {
+ ++gExpectedResourcesSeen;
+ }
+ if (gExpectedResourcesSeen == 2) {
+ browser.webRequest.onBeforeRequest.removeListener(onBeforeRequest);
+ browser.test.sendMessage("finish");
+ }
+ return undefined;
+ }
+
+ browser.webRequest.onBeforeRequest.addListener(
+ onBeforeRequest,
+ { urls: ["*://*/*"] },
+ ["blocking"]
+ );
+ browser.test.sendMessage("ready");
+ },
+ });
+ await extension.startup();
+ await extension.awaitMessage("ready");
+});
+
+add_task(async function () {
+ info("Starting subResources test");
+
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.trackingprotection.enabled", true],
+ // the test doesn't open a private window, so we don't care about this pref's value
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ // tracking annotations aren't needed in this test, only TP is needed
+ ["privacy.trackingprotection.annotate_channels", false],
+ [
+ "privacy.restrict3rdpartystorage.userInteractionRequiredForHosts",
+ "tracking.example.com,tracking.example.org",
+ ],
+ ["privacy.trackingprotection.testing.report_blocked_node", true],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ let promise = extension.awaitMessage("finish");
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_EMBEDDER_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ await promise;
+
+ info("Verify the number of tracking nodes found");
+ await SpecialPowers.spawn(browser, [{ expected: 3 }], async function (obj) {
+ is(
+ content.document.blockedNodeByClassifierCount,
+ obj.expected,
+ "Expected tracking nodes found"
+ );
+ });
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ await extension.unload();
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_onModifyRequestNotificationForTrackingResources.js b/toolkit/components/antitracking/test/browser/browser_onModifyRequestNotificationForTrackingResources.js
new file mode 100644
index 0000000000..0674b87136
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_onModifyRequestNotificationForTrackingResources.js
@@ -0,0 +1,96 @@
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+/**
+ * This test ensures that http-on-modify-request is dispatched for channels that
+ * are blocked by tracking protection. It sets up a page with a third-party script
+ * resource on it that is blocked by TP, and sets up an http-on-modify-request
+ * observer which waits to be notified about that resource. The test would time out
+ * if the http-on-modify-request notification isn't dispatched before the channel is
+ * canceled.
+ */
+
+let gExpectedResourcesSeen = 0;
+async function onModifyRequest() {
+ return new Promise((resolve, reject) => {
+ Services.obs.addObserver(function observer(subject, topic, data) {
+ let httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
+ let spec = httpChannel.URI.spec;
+ info("Observed channel for " + spec);
+ if (httpChannel.URI.prePath + "/" != TEST_3RD_PARTY_DOMAIN_TP) {
+ return;
+ }
+ if (spec.endsWith("empty.js")) {
+ ok(true, "Correct resource observed");
+ ++gExpectedResourcesSeen;
+ } else if (spec.endsWith("empty.js?redirect")) {
+ httpChannel.redirectTo(
+ Services.io.newURI(spec.replace("empty.js?redirect", "head.js"))
+ );
+ } else if (spec.endsWith("empty.js?redirect2")) {
+ httpChannel.suspend();
+ setTimeout(() => {
+ httpChannel.redirectTo(
+ Services.io.newURI(spec.replace("empty.js?redirect2", "head.js"))
+ );
+ httpChannel.resume();
+ }, 100);
+ } else if (spec.endsWith("head.js")) {
+ ++gExpectedResourcesSeen;
+ }
+ if (gExpectedResourcesSeen == 3) {
+ Services.obs.removeObserver(observer, "http-on-modify-request");
+ resolve();
+ }
+ }, "http-on-modify-request");
+ });
+}
+
+add_task(async function () {
+ info("Starting subResources test");
+
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.trackingprotection.enabled", true],
+ // the test doesn't open a private window, so we don't care about this pref's value
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ // tracking annotations aren't needed in this test, only TP is needed
+ ["privacy.trackingprotection.annotate_channels", false],
+ [
+ "privacy.restrict3rdpartystorage.userInteractionRequiredForHosts",
+ "tracking.example.com,tracking.example.org",
+ ],
+ ["privacy.trackingprotection.testing.report_blocked_node", true],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ let promise = onModifyRequest();
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_EMBEDDER_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ await promise;
+
+ info("Verify the number of tracking nodes found");
+ await SpecialPowers.spawn(
+ browser,
+ [{ expected: gExpectedResourcesSeen }],
+ async function (obj) {
+ is(
+ content.document.blockedNodeByClassifierCount,
+ obj.expected,
+ "Expected tracking nodes found"
+ );
+ }
+ );
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_partitionedClearSiteDataHeader.js b/toolkit/components/antitracking/test/browser/browser_partitionedClearSiteDataHeader.js
new file mode 100644
index 0000000000..7c79ecbe32
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedClearSiteDataHeader.js
@@ -0,0 +1,593 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+/**
+ * Tests that when receiving the "clear-site-data" header - with dFPI enabled -
+ * we clear storage under the correct partition.
+ */
+
+const { SiteDataTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SiteDataTestUtils.sys.mjs"
+);
+
+const HOST_A = "example.com";
+const HOST_B = "example.org";
+const ORIGIN_A = `https://${HOST_A}`;
+const ORIGIN_B = `https://${HOST_B}`;
+const CLEAR_SITE_DATA_PATH = `/${TEST_PATH}clearSiteData.sjs`;
+const CLEAR_SITE_DATA_URL_ORIGIN_B = ORIGIN_B + CLEAR_SITE_DATA_PATH;
+const CLEAR_SITE_DATA_URL_ORIGIN_A = ORIGIN_A + CLEAR_SITE_DATA_PATH;
+const THIRD_PARTY_FRAME_ID_ORIGIN_B = "thirdPartyFrame";
+const THIRD_PARTY_FRAME_ID_ORIGIN_A = "thirdPartyFrame2";
+const STORAGE_KEY = "testKey";
+
+// Skip localStorage tests when using legacy localStorage. The legacy
+// localStorage implementation does not support clearing data by principal. See
+// Bug 1688221, Bug 1688665.
+const skipLocalStorageTests = Services.prefs.getBoolPref(
+ "dom.storage.enable_unsupported_legacy_implementation"
+);
+
+/**
+ * Creates an iframe in the passed browser and waits for it to load.
+ * @param {Browser} browser - Browser to create the frame in.
+ * @param {String} src - Frame source url.
+ * @param {String} id - Frame id.
+ * @param {boolean} sandbox - Whether the frame should be sandboxed.
+ * @returns {Promise} - Resolves once the frame has loaded.
+ */
+function createFrame(browser, src, id, sandbox) {
+ return SpecialPowers.spawn(
+ browser,
+ [{ page: src, frameId: id, sandbox }],
+ async function (obj) {
+ await new content.Promise(resolve => {
+ let frame = content.document.createElement("iframe");
+ if (obj.sandbox) {
+ frame.setAttribute("sandbox", "allow-scripts");
+ }
+ frame.src = obj.page;
+ frame.id = obj.frameId;
+ frame.addEventListener("load", resolve, { once: true });
+ content.document.body.appendChild(frame);
+ });
+ }
+ );
+}
+
+/**
+ * Creates a new tab, loads a url and creates an iframe.
+ * Callers need to clean up the tab before the test ends.
+ * @param {String} firstPartyUrl - Url to load in tab.
+ * @param {String} thirdPartyUrl - Url to load in frame.
+ * @param {String} frameId - Id of iframe element.
+ * @param {boolean} sandbox - Whether the frame should be sandboxed.
+ * @returns {Promise} - Resolves with the tab and the frame BrowsingContext once
+ * the tab and the frame have loaded.
+ */
+async function createTabWithFrame(
+ firstPartyUrl,
+ thirdPartyUrl,
+ frameId,
+ sandbox
+) {
+ // Create tab and wait for it to be loaded.
+ let tab = BrowserTestUtils.addTab(gBrowser, firstPartyUrl);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ // Create cross origin iframe.
+ await createFrame(tab.linkedBrowser, thirdPartyUrl, frameId, sandbox);
+
+ // Return BrowsingContext of created iframe.
+ return { tab, frameBC: tab.linkedBrowser.browsingContext.children[0] };
+}
+
+/**
+ * Test wrapper for the ClearSiteData tests.
+ * Loads ORIGIN_A and ORIGIN_B in two tabs and inserts a cross origin pointing
+ * to the other iframe each.
+ * Both frames ORIGIN_B under ORIGIN_A and ORIGIN_A under ORIGIN_B will be
+ * storage partitioned.
+ * Depending on the clearDataContext variable we then either navigate ORIGIN_A
+ * (as top level) or ORIGIN_B (as third party frame) to the clear-site-data
+ * endpoint.
+ * @param {function} cbPreClear - Called after initial setup, once top levels
+ * and frames have been loaded.
+ * @param {function} cbPostClear - Called after data has been cleared via the
+ * "Clear-Site-Data" header.
+ * @param {("firstParty"|"thirdPartyPartitioned")} clearDataContext - Whether to
+ * navigate to the path that sets the "Clear-Site-Data" header with the first or
+ * third party.
+ * @param {boolean} [sandboxFrame] - Whether the frames should be sandboxed. No
+ * sandbox by default.
+ */
+async function runClearSiteDataTest(
+ cbPreClear,
+ cbPostClear,
+ clearDataContext,
+ sandboxFrame = false
+) {
+ // Create a tabs for origin A and B with cross origins frames B and A
+ let [
+ { frameBC: frameContextB, tab: tabA },
+ { frameBC: frameContextA, tab: tabB },
+ ] = await Promise.all([
+ createTabWithFrame(
+ ORIGIN_A,
+ ORIGIN_B,
+ THIRD_PARTY_FRAME_ID_ORIGIN_B,
+ sandboxFrame
+ ),
+ createTabWithFrame(
+ ORIGIN_B,
+ ORIGIN_A,
+ THIRD_PARTY_FRAME_ID_ORIGIN_A,
+ sandboxFrame
+ ),
+ ]);
+
+ let browserA = tabA.linkedBrowser;
+ let contextA = browserA.browsingContext;
+ let browserB = tabB.linkedBrowser;
+ let contextB = browserB.browsingContext;
+
+ // Run test callback before clear-site-data
+ if (cbPreClear) {
+ await cbPreClear(contextA, contextB, frameContextB, frameContextA);
+ }
+
+ // Navigate to path with clear-site-data header
+ // Depending on the clearDataContext variable we either do this with the
+ // top browser or the third party storage partitioned frame (B embedded in A).
+ info(`Opening path with clear-site-data-header for ${clearDataContext}`);
+ if (clearDataContext == "firstParty") {
+ // Open in new tab so we keep our current test tab intact. The
+ // post-clear-callback might need it.
+ await BrowserTestUtils.withNewTab(CLEAR_SITE_DATA_URL_ORIGIN_A, () => {});
+ } else if (clearDataContext == "thirdPartyPartitioned") {
+ // Navigate frame to path with clear-site-data header
+ await SpecialPowers.spawn(
+ browserA,
+ [
+ {
+ page: CLEAR_SITE_DATA_URL_ORIGIN_B,
+ frameId: THIRD_PARTY_FRAME_ID_ORIGIN_B,
+ },
+ ],
+ async function (obj) {
+ await new content.Promise(resolve => {
+ let frame = content.document.getElementById(obj.frameId);
+ frame.addEventListener("load", resolve, { once: true });
+ frame.src = obj.page;
+ });
+ }
+ );
+ } else {
+ ok(false, "Invalid context requested for clear-site-data");
+ }
+
+ if (cbPostClear) {
+ await cbPostClear(contextA, contextB, frameContextB, frameContextA);
+ }
+
+ info("Cleaning up.");
+ BrowserTestUtils.removeTab(tabA);
+ BrowserTestUtils.removeTab(tabB);
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve);
+ });
+}
+
+/**
+ * Create an origin with partitionKey.
+ * @param {String} originNoSuffix - Origin without origin attributes.
+ * @param {String} [firstParty] - First party to create partitionKey.
+ * @returns {String} Origin with suffix. If not passed this will return the
+ * umodified origin.
+ */
+function getOrigin(originNoSuffix, firstParty) {
+ let origin = originNoSuffix;
+ if (firstParty) {
+ let [scheme, host] = firstParty.split("://");
+ origin += `^partitionKey=(${scheme},${host})`;
+ }
+ return origin;
+}
+
+/**
+ * Sets a storage item for an origin.
+ * @param {("cookie"|"localStorage")} storageType - Which storage type to use.
+ * @param {String} originNoSuffix - Context to set storage item in.
+ * @param {String} [firstParty] - Optional first party domain to partition
+ * under.
+ * @param {String} key - Key of the entry.
+ * @param {String} value - Value of the entry.
+ */
+function setStorageEntry(storageType, originNoSuffix, firstParty, key, value) {
+ if (storageType != "cookie" && storageType != "localStorage") {
+ ok(false, "Invalid storageType passed");
+ return;
+ }
+
+ let origin = getOrigin(originNoSuffix, firstParty);
+
+ if (storageType == "cookie") {
+ SiteDataTestUtils.addToCookies({ origin, name: key, value });
+ return;
+ }
+ // localStorage
+ SiteDataTestUtils.addToLocalStorage(origin, key, value);
+}
+
+/**
+ * Tests whether a host sets a cookie.
+ * For the purpose of this test we assume that there is either one or no cookie
+ * set.
+ * This performs cookie lookups directly via the cookie service.
+ * @param {boolean} hasCookie - Whether we expect to see a cookie.
+ * @param {String} originNoSuffix - Origin the cookie is stored for.
+ * @param {String|null} firstParty - Whether to test for a partitioned cookie.
+ * If set this will be used to construct the partitionKey.
+ * @param {String} [key] - Expected key / name of the cookie.
+ * @param {String} [value] - Expected value of the cookie.
+ */
+function testHasCookie(hasCookie, originNoSuffix, firstParty, key, value) {
+ let origin = getOrigin(originNoSuffix, firstParty);
+
+ let label = `${originNoSuffix}${
+ firstParty ? ` (partitioned under ${firstParty})` : ""
+ }`;
+
+ if (!hasCookie) {
+ ok(
+ !SiteDataTestUtils.hasCookies(origin),
+ `Cookie for ${label} is not set for key ${key}`
+ );
+ return;
+ }
+
+ ok(
+ SiteDataTestUtils.hasCookies(origin, [{ key, value }]),
+ `Cookie for ${label} is set ${key}=${value}`
+ );
+}
+
+/**
+ * Tests whether a context has a localStorage entry.
+ * @param {boolean} hasEntry - Whether we expect to see an entry.
+ * @param {String} originNoSuffix - Origin to test localStorage for.
+ * @param {String} [firstParty] - First party context to test under.
+ * @param {String} key - key of the localStorage item.
+ * @param {String} [expectedValue] - Expected value of the item.
+ */
+function testHasLocalStorageEntry(
+ hasEntry,
+ originNoSuffix,
+ firstParty,
+ key,
+ expectedValue
+) {
+ if (key == null) {
+ ok(false, "localStorage key is mandatory");
+ return;
+ }
+ let label = `${originNoSuffix}${
+ firstParty ? ` (partitioned under ${firstParty})` : ""
+ }`;
+ let origin = getOrigin(originNoSuffix, firstParty);
+ if (hasEntry) {
+ let hasEntry = SiteDataTestUtils.hasLocalStorage(origin, [
+ { key, value: expectedValue },
+ ]);
+ ok(
+ hasEntry,
+ `localStorage for ${label} has expected value ${key}=${expectedValue}`
+ );
+ } else {
+ let hasEntry = SiteDataTestUtils.hasLocalStorage(origin);
+ ok(!hasEntry, `localStorage for ${label} is not set for key ${key}`);
+ }
+}
+
+/**
+ * Sets the initial storage entries used by the storage tests in this file.
+ * 1. first party ( A )
+ * 2. first party ( B )
+ * 3. third party partitioned ( B under A)
+ * 4. third party partitioned ( A under B)
+ * The entry values reflect which context they are set for.
+ * @param {("cookie"|"localStorage")} storageType - Storage type to initialize.
+ */
+async function setupInitialStorageState(storageType) {
+ if (storageType != "cookie" && storageType != "localStorage") {
+ ok(false, "Invalid storageType passed");
+ return;
+ }
+
+ // Set a first party entry
+ setStorageEntry(storageType, ORIGIN_A, null, STORAGE_KEY, "firstParty");
+
+ // Set a storage entry in the storage partitioned third party frame
+ setStorageEntry(
+ storageType,
+ ORIGIN_B,
+ ORIGIN_A,
+ STORAGE_KEY,
+ "thirdPartyPartitioned"
+ );
+
+ // Set a storage entry in the non storage partitioned third party page
+ setStorageEntry(storageType, ORIGIN_B, null, STORAGE_KEY, "thirdParty");
+
+ // Set a storage entry in the second storage partitioned third party frame.
+ setStorageEntry(
+ storageType,
+ ORIGIN_A,
+ ORIGIN_B,
+ STORAGE_KEY,
+ "thirdPartyPartitioned2"
+ );
+
+ info("Test that storage entries are set for all contexts");
+
+ if (storageType == "cookie") {
+ testHasCookie(true, ORIGIN_A, null, STORAGE_KEY, "firstParty");
+ testHasCookie(true, ORIGIN_B, null, STORAGE_KEY, "thirdParty");
+ testHasCookie(
+ true,
+ ORIGIN_B,
+ ORIGIN_A,
+ STORAGE_KEY,
+ "thirdPartyPartitioned"
+ );
+ testHasCookie(
+ true,
+ ORIGIN_A,
+ ORIGIN_B,
+ STORAGE_KEY,
+ "thirdPartyPartitioned2"
+ );
+ return;
+ }
+
+ testHasLocalStorageEntry(true, ORIGIN_A, null, STORAGE_KEY, "firstParty");
+ testHasLocalStorageEntry(true, ORIGIN_B, null, STORAGE_KEY, "thirdParty");
+ testHasLocalStorageEntry(
+ true,
+ ORIGIN_B,
+ ORIGIN_A,
+ STORAGE_KEY,
+ "thirdPartyPartitioned"
+ );
+ testHasLocalStorageEntry(
+ true,
+ ORIGIN_A,
+ ORIGIN_B,
+ STORAGE_KEY,
+ "thirdPartyPartitioned2"
+ );
+}
+
+add_setup(async function () {
+ info("Starting ClearSiteData test");
+
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ // Needed for SiteDataTestUtils#hasLocalStorage
+ ["dom.storage.client_validation", false],
+ ],
+ });
+});
+
+/**
+ * Test clearing partitioned cookies via clear-site-data header
+ * (Cleared via the cookie service).
+ */
+
+/**
+ * Tests that when a storage partitioned third party frame loads a site with
+ * "Clear-Site-Data", the cookies are cleared for only that partitioned frame.
+ */
+add_task(async function cookieClearThirdParty() {
+ await runClearSiteDataTest(
+ // Pre Clear-Site-Data
+ () => setupInitialStorageState("cookie"),
+ // Post Clear-Site-Data
+ () => {
+ info("Testing: First party cookie has not changed");
+ testHasCookie(true, ORIGIN_A, null, STORAGE_KEY, "firstParty");
+ info("Testing: Unpartitioned cookie has not changed");
+ testHasCookie(true, ORIGIN_B, null, STORAGE_KEY, "thirdParty");
+ info("Testing: Partitioned cookie for HOST_B (HOST_A) has been cleared");
+ testHasCookie(false, ORIGIN_B, ORIGIN_A);
+ info("Testing: Partitioned cookie for HOST_A (HOST_B) has not changed");
+ testHasCookie(
+ true,
+ ORIGIN_A,
+ ORIGIN_B,
+ STORAGE_KEY,
+ "thirdPartyPartitioned2"
+ );
+ },
+ // Send clear-site-data header in partitioned third party context.
+ "thirdPartyPartitioned"
+ );
+});
+
+/**
+ * Tests that when a sandboxed storage partitioned third party frame loads a
+ * site with "Clear-Site-Data", no cookies are cleared and we don't crash.
+ * Crash details in Bug 1686938.
+ */
+add_task(async function cookieClearThirdPartySandbox() {
+ await runClearSiteDataTest(
+ // Pre Clear-Site-Data
+ () => setupInitialStorageState("cookie"),
+ // Post Clear-Site-Data
+ () => {
+ info("Testing: First party cookie has not changed");
+ testHasCookie(true, ORIGIN_A, null, STORAGE_KEY, "firstParty");
+ info("Testing: Unpartitioned cookie has not changed");
+ testHasCookie(true, ORIGIN_B, null, STORAGE_KEY, "thirdParty");
+ info("Testing: Partitioned cookie for HOST_B (HOST_A) has not changed");
+ testHasCookie(
+ true,
+ ORIGIN_B,
+ ORIGIN_A,
+ STORAGE_KEY,
+ "thirdPartyPartitioned"
+ );
+ info("Testing: Partitioned cookie for HOST_A (HOST_B) has not changed");
+ testHasCookie(
+ true,
+ ORIGIN_A,
+ ORIGIN_B,
+ STORAGE_KEY,
+ "thirdPartyPartitioned2"
+ );
+ },
+ // Send clear-site-data header in partitioned third party context.
+ "thirdPartyPartitioned",
+ true
+ );
+});
+
+/**
+ * Tests that when the we load a path with "Clear-Site-Data" at top level, the
+ * cookies are cleared only in the first party context.
+ */
+add_task(async function cookieClearFirstParty() {
+ await runClearSiteDataTest(
+ // Pre Clear-Site-Data
+ () => setupInitialStorageState("cookie"),
+ // Post Clear-Site-Data
+ () => {
+ info("Testing: First party cookie has been cleared");
+ testHasCookie(false, ORIGIN_A, null);
+ info("Testing: Unpartitioned cookie has not changed");
+ testHasCookie(true, ORIGIN_B, null, STORAGE_KEY, "thirdParty");
+ info("Testing: Partitioned cookie for HOST_B (HOST_A) has not changed");
+ testHasCookie(
+ true,
+ ORIGIN_B,
+ ORIGIN_A,
+ STORAGE_KEY,
+ "thirdPartyPartitioned"
+ );
+ info("Testing: Partitioned cookie for HOST_A (HOST_B) has not changed");
+ testHasCookie(
+ true,
+ ORIGIN_A,
+ ORIGIN_B,
+ STORAGE_KEY,
+ "thirdPartyPartitioned2"
+ );
+ },
+ // Send clear-site-data header in first party context.
+ "firstParty"
+ );
+});
+
+/**
+ * Test clearing partitioned localStorage via clear-site-data header
+ * (Cleared via the quota manager).
+ */
+
+/**
+ * Tests that when a storage partitioned third party frame loads a site with
+ * "Clear-Site-Data", localStorage is cleared for only that partitioned frame.
+ */
+add_task(async function localStorageClearThirdParty() {
+ // Bug 1688221, Bug 1688665.
+ if (skipLocalStorageTests) {
+ info("Skipping test");
+ return;
+ }
+ await runClearSiteDataTest(
+ // Pre Clear-Site-Data
+ () => setupInitialStorageState("localStorage"),
+ // Post Clear-Site-Data
+ async () => {
+ info("Testing: First party localStorage has not changed");
+ testHasLocalStorageEntry(true, ORIGIN_A, null, STORAGE_KEY, "firstParty");
+ info("Testing: Unpartitioned localStorage has not changed");
+ testHasLocalStorageEntry(true, ORIGIN_B, null, STORAGE_KEY, "thirdParty");
+ info(
+ "Testing: Partitioned localStorage for HOST_B (HOST_A) has been cleared"
+ );
+ testHasLocalStorageEntry(false, ORIGIN_B, ORIGIN_A, STORAGE_KEY);
+ info(
+ "Testing: Partitioned localStorage for HOST_A (HOST_B) has not changed"
+ );
+ testHasLocalStorageEntry(
+ true,
+ ORIGIN_A,
+ ORIGIN_B,
+ STORAGE_KEY,
+ "thirdPartyPartitioned2"
+ );
+ },
+ // Send clear-site-data header in partitioned third party context.
+ "thirdPartyPartitioned"
+ );
+});
+
+/**
+ * Tests that when the we load a path with "Clear-Site-Data" at top level,
+ * localStorage is cleared only in the first party context.
+ */
+add_task(async function localStorageClearFirstParty() {
+ // Bug 1688221, Bug 1688665.
+ if (skipLocalStorageTests) {
+ info("Skipping test");
+ return;
+ }
+ await runClearSiteDataTest(
+ // Pre Clear-Site-Data
+ () => setupInitialStorageState("localStorage"),
+ // Post Clear-Site-Data
+ () => {
+ info("Testing: First party localStorage has been cleared");
+ testHasLocalStorageEntry(false, ORIGIN_A, null, STORAGE_KEY);
+ info("Testing: Unpartitioned thirdParty localStorage has not changed");
+ testHasLocalStorageEntry(true, ORIGIN_B, null, STORAGE_KEY, "thirdParty");
+ info(
+ "Testing: Partitioned localStorage for HOST_B (HOST_A) has not changed"
+ );
+ testHasLocalStorageEntry(
+ true,
+ ORIGIN_B,
+ ORIGIN_A,
+ STORAGE_KEY,
+ "thirdPartyPartitioned"
+ );
+ info(
+ "Testing: Partitioned localStorage for HOST_A (HOST_B) has not changed"
+ );
+ testHasLocalStorageEntry(
+ true,
+ ORIGIN_A,
+ ORIGIN_B,
+ STORAGE_KEY,
+ "thirdPartyPartitioned2"
+ );
+ },
+ // Send clear-site-data header in first party context.
+ "firstParty"
+ );
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_partitionedConsoleMessage.js b/toolkit/components/antitracking/test/browser/browser_partitionedConsoleMessage.js
new file mode 100644
index 0000000000..ce74076825
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedConsoleMessage.js
@@ -0,0 +1,80 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Bug 1759496 - A test to verify if the console message of partitioned storage
+ * was sent correctly.
+ */
+
+"use strict";
+
+add_setup(async function () {
+ await setCookieBehaviorPref(
+ BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ false
+ );
+});
+
+add_task(async function runTest() {
+ info("Creating the tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = tab.linkedBrowser;
+ await BrowserTestUtils.browserLoaded(browser);
+
+ let consolePromise = new Promise(resolve => {
+ let consoleListener = {
+ observe(msg) {
+ if (
+ msg
+ .QueryInterface(Ci.nsIScriptError)
+ .category.startsWith("cookiePartitioned")
+ ) {
+ Services.console.unregisterListener(consoleListener);
+ resolve(msg.QueryInterface(Ci.nsIScriptError).errorMessage);
+ }
+ },
+ };
+
+ Services.console.registerListener(consoleListener);
+ });
+
+ info("Creating the third-party iframe");
+ let ifrBC = await SpecialPowers.spawn(
+ browser,
+ [TEST_TOP_PAGE_7],
+ async page => {
+ let ifr = content.document.createElement("iframe");
+
+ let loading = ContentTaskUtils.waitForEvent(ifr, "load");
+ content.document.body.appendChild(ifr);
+ ifr.src = page;
+ await loading;
+
+ return ifr.browsingContext;
+ }
+ );
+
+ info("Write cookie to the third-party iframe to ensure the console message");
+ await SpecialPowers.spawn(ifrBC, [], async _ => {
+ content.document.cookie = "foo";
+ });
+
+ let msg = await consolePromise;
+
+ ok(
+ msg.startsWith("Partitioned cookie or storage access was provided to"),
+ "The partitioned console message was sent correctly"
+ );
+
+ info("Clean up");
+ BrowserTestUtils.removeTab(tab);
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_partitionedCookies.js b/toolkit/components/antitracking/test/browser/browser_partitionedCookies.js
new file mode 100644
index 0000000000..d2d1e87dd4
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedCookies.js
@@ -0,0 +1,137 @@
+PartitionedStorageHelper.runTestInNormalAndPrivateMode(
+ "HTTP Cookies",
+ async (win3rdParty, win1stParty, allowed) => {
+ await win3rdParty.fetch("cookies.sjs?3rd").then(r => r.text());
+ await win3rdParty
+ .fetch("cookies.sjs")
+ .then(r => r.text())
+ .then(text => {
+ is(text, "cookie:foopy=3rd", "3rd party cookie set");
+ });
+
+ await win1stParty.fetch("cookies.sjs?first").then(r => r.text());
+ await win1stParty
+ .fetch("cookies.sjs")
+ .then(r => r.text())
+ .then(text => {
+ is(text, "cookie:foopy=first", "First party cookie set");
+ });
+
+ await win3rdParty
+ .fetch("cookies.sjs")
+ .then(r => r.text())
+ .then(text => {
+ if (allowed) {
+ is(
+ text,
+ "cookie:foopy=first",
+ "3rd party has the first party cookie set"
+ );
+ } else {
+ is(
+ text,
+ "cookie:foopy=3rd",
+ "3rd party has not the first party cookie set"
+ );
+ }
+ });
+ },
+
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ }
+);
+
+PartitionedStorageHelper.runTestInNormalAndPrivateMode(
+ "DOM Cookies",
+ async (win3rdParty, win1stParty, allowed) => {
+ win3rdParty.document.cookie = "foo=3rd";
+ is(win3rdParty.document.cookie, "foo=3rd", "3rd party cookie set");
+
+ win1stParty.document.cookie = "foo=first";
+ is(win1stParty.document.cookie, "foo=first", "First party cookie set");
+
+ if (allowed) {
+ is(
+ win3rdParty.document.cookie,
+ "foo=first",
+ "3rd party has the first party cookie set"
+ );
+ } else {
+ is(
+ win3rdParty.document.cookie,
+ "foo=3rd",
+ "3rd party has not the first party cookie set"
+ );
+ }
+ },
+
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ }
+);
+
+PartitionedStorageHelper.runPartitioningTestInNormalAndPrivateMode(
+ "Partitioned tabs - DOM Cookies",
+ "cookies",
+
+ // getDataCallback
+ async win => {
+ return win.document.cookie;
+ },
+
+ // addDataCallback
+ async (win, value) => {
+ win.document.cookie = value;
+ return true;
+ },
+
+ // cleanup
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ true
+);
+
+PartitionedStorageHelper.runPartitioningTestInNormalAndPrivateMode(
+ "Partitioned tabs - Network Cookies",
+ "cookies",
+
+ // getDataCallback
+ async win => {
+ return win
+ .fetch("cookies.sjs")
+ .then(r => r.text())
+ .then(text => {
+ return text.substring("cookie:foopy=".length);
+ });
+ },
+
+ // addDataCallback
+ async (win, value) => {
+ await win.fetch("cookies.sjs?" + value).then(r => r.text());
+ return true;
+ },
+
+ // cleanup
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ true
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_partitionedDOMCache.js b/toolkit/components/antitracking/test/browser/browser_partitionedDOMCache.js
new file mode 100644
index 0000000000..93fc71b11e
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedDOMCache.js
@@ -0,0 +1,110 @@
+const APS_PREF =
+ "privacy.partition.always_partition_third_party_non_cookie_storage";
+
+PartitionedStorageHelper.runTest(
+ "DOMCache",
+ async (win3rdParty, win1stParty, allowed) => {
+ await win3rdParty.caches.open("wow").then(
+ _ => {
+ ok(allowed, "DOM Cache cannot be used!");
+ },
+ _ => {
+ ok(!allowed, "DOM Cache cannot be used!");
+ }
+ );
+
+ await win1stParty.caches.open("wow").then(
+ _ => {
+ ok(true, "DOM Cache should be available");
+ },
+ _ => {
+ ok(false, "DOM Cache should be available");
+ }
+ );
+ },
+
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+
+ [[APS_PREF, false]],
+
+ { runInSecureContext: true }
+);
+
+PartitionedStorageHelper.runTest(
+ "DOMCache",
+ async (win3rdParty, win1stParty, allowed) => {
+ await win1stParty.caches.open("wow").then(
+ async cache => {
+ ok(true, "DOM Cache should be available");
+ await cache.add("/");
+ },
+ _ => {
+ ok(false, "DOM Cache should be available");
+ }
+ );
+
+ await win3rdParty.caches.open("wow").then(
+ async cache => {
+ ok(true, "DOM Cache can be used!");
+ is(undefined, await cache.match("/"), "DOM Cache is partitioned");
+ },
+ _ => {
+ ok(false, "DOM Cache cannot be used!");
+ }
+ );
+ },
+
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+
+ [[APS_PREF, true]],
+
+ { runInSecureContext: true }
+);
+
+// Test that DOM cache is not available in PBM.
+PartitionedStorageHelper.runTest(
+ "DOMCache",
+ async (win3rdParty, win1stParty, allowed) => {
+ await win1stParty.caches.open("wow").then(
+ async cache => {
+ ok(false, "DOM Cache should not be available in PBM");
+ },
+ _ => {
+ ok(true, "DOM Cache should not be available in PBM");
+ }
+ );
+
+ await win3rdParty.caches.open("wow").then(
+ async cache => {
+ ok(false, "DOM Cache should not be available in PBM");
+ },
+ _ => {
+ ok(true, "DOM Cache should not be available in PBM");
+ }
+ );
+ },
+
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+
+ [],
+
+ { runInSecureContext: true, runInPrivateWindow: true }
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_partitionedIndexedDB.js b/toolkit/components/antitracking/test/browser/browser_partitionedIndexedDB.js
new file mode 100644
index 0000000000..70dd0b03db
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedIndexedDB.js
@@ -0,0 +1,95 @@
+PartitionedStorageHelper.runTest(
+ "IndexedDB",
+ async (win3rdParty, win1stParty, allowed) => {
+ await new Promise(resolve => {
+ let a = win1stParty.indexedDB.open("test", 1);
+ ok(!!a, "IDB should not be blocked in 1st party contexts");
+
+ a.onsuccess = e => {
+ let db = e.target.result;
+ is(db.objectStoreNames.length, 1, "We have 1 objectStore");
+ is(db.objectStoreNames[0], "foobar", "We have 'foobar' objectStore");
+ resolve();
+ };
+
+ a.onupgradeneeded = e => {
+ let db = e.target.result;
+ is(db.objectStoreNames.length, 0, "We have 0 objectStores");
+ db.createObjectStore("foobar", { keyPath: "test" });
+ };
+ });
+
+ await new Promise(resolve => {
+ let a = win3rdParty.indexedDB.open("test", 1);
+ ok(!!a, "IDB should not be blocked in 3rd party contexts");
+
+ a.onsuccess = e => {
+ let db = e.target.result;
+
+ is(db.objectStoreNames.length, 0, "We have 0 objectStore");
+ resolve();
+ };
+ });
+ },
+
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ }
+);
+
+PartitionedStorageHelper.runPartitioningTest(
+ "Partitioned tabs - IndexedDB",
+ "indexeddb",
+
+ // getDataCallback
+ async win => {
+ return new Promise(resolve => {
+ let a = win.indexedDB.open("test", 1);
+
+ a.onupgradeneeded = e => {
+ let db = e.target.result;
+ db.createObjectStore("foobar", { keyPath: "id" });
+ };
+
+ a.onsuccess = e => {
+ let db = e.target.result;
+ db.transaction("foobar").objectStore("foobar").get(1).onsuccess =
+ ee => {
+ resolve(
+ ee.target.result === undefined ? "" : ee.target.result.value
+ );
+ };
+ };
+ });
+ },
+
+ // addDataCallback
+ async (win, value) => {
+ return new Promise(resolve => {
+ let a = win.indexedDB.open("test", 1);
+
+ a.onsuccess = e => {
+ let db = e.target.result;
+ db
+ .transaction("foobar", "readwrite")
+ .objectStore("foobar")
+ .put({ id: 1, value }).onsuccess = _ => {
+ resolve(true);
+ };
+ };
+ });
+ },
+
+ // cleanup
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ }
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_partitionedLocalStorage.js b/toolkit/components/antitracking/test/browser/browser_partitionedLocalStorage.js
new file mode 100644
index 0000000000..fe45970132
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedLocalStorage.js
@@ -0,0 +1,115 @@
+AntiTracking.runTestInNormalAndPrivateMode(
+ "localStorage and Storage Access API",
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+
+ let effectiveCookieBehavior = SpecialPowers.isContentWindowPrivate(window)
+ ? SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior.pbmode"
+ )
+ : SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior"
+ );
+
+ let shouldThrow = [
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT,
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN,
+ ].includes(effectiveCookieBehavior);
+
+ let hasThrown;
+ try {
+ localStorage.foo = 42;
+ ok(true, "LocalStorage is allowed");
+ is(localStorage.foo, "42", "The value matches");
+ hasThrown = false;
+ } catch (e) {
+ is(e.name, "SecurityError", "We want a security error message.");
+ hasThrown = true;
+ }
+
+ is(hasThrown, shouldThrow, "LocalStorage has been exposed correctly");
+
+ let prevLocalStorage;
+ if (!shouldThrow) {
+ prevLocalStorage = localStorage;
+ }
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+
+ if (shouldThrow) {
+ try {
+ is(localStorage.foo, undefined, "Undefined value after.");
+ ok(false, "localStorage should not be available");
+ } catch (e) {
+ ok(true, "localStorage should not be available");
+ }
+ } else {
+ ok(localStorage != prevLocalStorage, "We have a new localStorage");
+ is(localStorage.foo, undefined, "Undefined value after.");
+
+ localStorage.foo = 42;
+ ok(true, "LocalStorage is still allowed");
+ is(localStorage.foo, "42", "The value matches");
+ }
+ },
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await hasStorageAccessInitially();
+
+ localStorage.foo = 42;
+ ok(true, "LocalStorage is allowed");
+ is(localStorage.foo, "42", "The value matches");
+
+ var prevLocalStorage = localStorage;
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+
+ // For non-tracking windows, calling the API is a no-op
+ ok(localStorage == prevLocalStorage, "We have a new localStorage");
+ is(localStorage.foo, "42", "The value matches");
+ ok(true, "LocalStorage is allowed");
+ },
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [
+ [
+ "privacy.restrict3rdpartystorage.partitionedHosts",
+ "tracking.example.org,tracking.example.com",
+ ],
+ ],
+ false,
+ false
+);
+
+PartitionedStorageHelper.runPartitioningTestInNormalAndPrivateMode(
+ "Partitioned tabs - localStorage",
+ "localstorage",
+
+ // getDataCallback
+ async win => {
+ return "foo" in win.localStorage ? win.localStorage.foo : "";
+ },
+
+ // addDataCallback
+ async (win, value) => {
+ win.localStorage.foo = value;
+ return true;
+ },
+
+ // cleanup
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ }
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_partitionedLocalStorage_events.js b/toolkit/components/antitracking/test/browser/browser_partitionedLocalStorage_events.js
new file mode 100644
index 0000000000..14ca62d062
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedLocalStorage_events.js
@@ -0,0 +1,1014 @@
+function log(test) {
+ if ("iteration" in test) {
+ info(
+ `Running test with prefValue: ${test.prefValue} (Test #${
+ test.iteration + 1
+ })`
+ );
+ test.iteration++;
+ } else {
+ test.iteration = 0;
+ log(test);
+ }
+}
+
+function runAllTests(prefValue) {
+ const storagePrincipalTest =
+ prefValue == Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER;
+ const dynamicFPITest =
+ prefValue ==
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN;
+
+ const test = { dynamicFPITest, prefValue };
+
+ let thirdPartyDomain;
+ if (storagePrincipalTest) {
+ thirdPartyDomain = TEST_3RD_PARTY_DOMAIN;
+ }
+ if (dynamicFPITest) {
+ thirdPartyDomain = TEST_4TH_PARTY_DOMAIN;
+ }
+ ok(thirdPartyDomain, "Sanity check");
+
+ // A same origin (and same-process via setting "dom.ipc.processCount" to 1)
+ // top-level window with access to real localStorage does not share storage
+ // with an ePartitionOrDeny iframe that should have PartitionedLocalStorage and
+ // no storage events are received in either direction. (Same-process in order
+ // to avoid having to worry about any e10s propagation issues.)
+ add_task(async _ => {
+ log(test);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.ipc.processCount", 1],
+ ["network.cookie.cookieBehavior", prefValue],
+ ["network.cookie.cookieBehavior.pbmode", prefValue],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ [
+ "privacy.restrict3rdpartystorage.partitionedHosts",
+ "tracking.example.org,not-tracking.example.com",
+ ],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ info("Creating a non-tracker top-level context");
+ let normalTab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ let normalBrowser = gBrowser.getBrowserForTab(normalTab);
+ await BrowserTestUtils.browserLoaded(normalBrowser);
+
+ info("Creating a tracker top-level context");
+ let trackerTab = BrowserTestUtils.addTab(
+ gBrowser,
+ thirdPartyDomain + TEST_PATH + "page.html"
+ );
+ let trackerBrowser = gBrowser.getBrowserForTab(trackerTab);
+ await BrowserTestUtils.browserLoaded(trackerBrowser);
+
+ info("The non-tracker page opens a tracker iframe");
+ await SpecialPowers.spawn(
+ normalBrowser,
+ [
+ {
+ page: thirdPartyDomain + TEST_PATH + "localStorageEvents.html",
+ },
+ ],
+ async obj => {
+ let ifr = content.document.createElement("iframe");
+ ifr.setAttribute("id", "ifr");
+ ifr.setAttribute("src", obj.page);
+
+ info("Iframe loading...");
+ await new content.Promise(resolve => {
+ ifr.onload = resolve;
+ content.document.body.appendChild(ifr);
+ });
+
+ info("Setting localStorage value...");
+ ifr.contentWindow.postMessage("setValue", "*");
+
+ info("Getting the value...");
+ let value = await new Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ resolve(e.data);
+ },
+ { once: true }
+ );
+ ifr.contentWindow.postMessage("getValue", "*");
+ });
+
+ ok(
+ value.startsWith("tracker-"),
+ "The value is correctly set by the tracker"
+ );
+ }
+ );
+
+ info("The tracker page should not have received events");
+ await SpecialPowers.spawn(trackerBrowser, [], async _ => {
+ is(content.localStorage.foo, undefined, "Undefined value!");
+ content.localStorage.foo = "normal-" + Math.random();
+ });
+
+ info("Let's see if non-tracker page has received events");
+ await SpecialPowers.spawn(normalBrowser, [], async _ => {
+ let ifr = content.document.getElementById("ifr");
+
+ info("Getting the value...");
+ let value = await new Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ resolve(e.data);
+ },
+ { once: true }
+ );
+ ifr.contentWindow.postMessage("getValue", "*");
+ });
+
+ ok(
+ value.startsWith("tracker-"),
+ "The value is correctly set by the tracker"
+ );
+
+ info("Getting the events...");
+ let events = await new Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ resolve(e.data);
+ },
+ { once: true }
+ );
+ ifr.contentWindow.postMessage("getEvents", "*");
+ });
+
+ is(events, 0, "No events");
+ });
+
+ BrowserTestUtils.removeTab(trackerTab);
+ BrowserTestUtils.removeTab(normalTab);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ });
+
+ // Two ePartitionOrDeny iframes in the same tab in the same origin see the
+ // same localStorage values but no storage events are received from each other
+ // if dFPI is disabled.
+ add_task(async _ => {
+ log(test);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.ipc.processCount", 1],
+ ["network.cookie.cookieBehavior", prefValue],
+ ["network.cookie.cookieBehavior.pbmode", prefValue],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ [
+ "privacy.restrict3rdpartystorage.partitionedHosts",
+ "tracking.example.org,not-tracking.example.com",
+ ],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ info("Creating a non-tracker top-level context");
+ let normalTab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ let normalBrowser = gBrowser.getBrowserForTab(normalTab);
+ await BrowserTestUtils.browserLoaded(normalBrowser);
+
+ info("The non-tracker page opens a tracker iframe");
+ await SpecialPowers.spawn(
+ normalBrowser,
+ [
+ {
+ page: thirdPartyDomain + TEST_PATH + "localStorageEvents.html",
+ dynamicFPITest: test.dynamicFPITest,
+ },
+ ],
+ async obj => {
+ let ifr1 = content.document.createElement("iframe");
+ ifr1.setAttribute("id", "ifr1");
+ ifr1.setAttribute("src", obj.page);
+
+ info("Iframe 1 loading...");
+ await new content.Promise(resolve => {
+ ifr1.onload = resolve;
+ content.document.body.appendChild(ifr1);
+ });
+
+ let ifr2 = content.document.createElement("iframe");
+ ifr2.setAttribute("id", "ifr2");
+ ifr2.setAttribute("src", obj.page);
+
+ info("Iframe 2 loading...");
+ await new content.Promise(resolve => {
+ ifr2.onload = resolve;
+ content.document.body.appendChild(ifr2);
+ });
+
+ info("Setting localStorage value in ifr1...");
+ ifr1.contentWindow.postMessage("setValue", "*");
+
+ info("Getting the value from ifr1...");
+ let value = await new Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ resolve(e.data);
+ },
+ { once: true }
+ );
+ ifr1.contentWindow.postMessage("getValue", "*");
+ });
+
+ ok(value.startsWith("tracker-"), "The value is correctly set in ifr1");
+
+ info("Getting the value from ifr2...");
+ value = await new Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ resolve(e.data);
+ },
+ { once: true }
+ );
+ ifr2.contentWindow.postMessage("getValue", "*");
+ });
+
+ if (obj.dynamicFPITest) {
+ ok(
+ value.startsWith("tracker-"),
+ "The value is correctly set in ifr2"
+ );
+ } else {
+ is(value, null, "The value is not set in ifr2");
+ }
+
+ info("Getting the events received by ifr2...");
+ let events = await new Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ resolve(e.data);
+ },
+ { once: true }
+ );
+ ifr2.contentWindow.postMessage("getEvents", "*");
+ });
+
+ if (obj.dynamicFPITest) {
+ is(events, 1, "one event");
+ } else {
+ is(events, 0, "No events");
+ }
+ }
+ );
+
+ BrowserTestUtils.removeTab(normalTab);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ });
+
+ // Same as the previous test but with a cookie behavior of BEHAVIOR_ACCEPT
+ // instead of BEHAVIOR_REJECT_TRACKER so the iframes get real, persistent
+ // localStorage instead of partitioned localStorage.
+ add_task(async _ => {
+ log(test);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.ipc.processCount", 1],
+ ["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_ACCEPT],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_ACCEPT,
+ ],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ [
+ "privacy.restrict3rdpartystorage.partitionedHosts",
+ "tracking.example.org,not-tracking.example.com",
+ ],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ info("Creating a non-tracker top-level context");
+ let normalTab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ let normalBrowser = gBrowser.getBrowserForTab(normalTab);
+ await BrowserTestUtils.browserLoaded(normalBrowser);
+
+ info("The non-tracker page opens a tracker iframe");
+ await SpecialPowers.spawn(
+ normalBrowser,
+ [
+ {
+ page: thirdPartyDomain + TEST_PATH + "localStorageEvents.html",
+ },
+ ],
+ async obj => {
+ let ifr1 = content.document.createElement("iframe");
+ ifr1.setAttribute("id", "ifr1");
+ ifr1.setAttribute("src", obj.page);
+
+ info("Iframe 1 loading...");
+ await new content.Promise(resolve => {
+ ifr1.onload = resolve;
+ content.document.body.appendChild(ifr1);
+ });
+
+ let ifr2 = content.document.createElement("iframe");
+ ifr2.setAttribute("id", "ifr2");
+ ifr2.setAttribute("src", obj.page);
+
+ info("Iframe 2 loading...");
+ await new content.Promise(resolve => {
+ ifr2.onload = resolve;
+ content.document.body.appendChild(ifr2);
+ });
+
+ info("Setting localStorage value in ifr1...");
+ ifr1.contentWindow.postMessage("setValue", "*");
+
+ info("Getting the value from ifr1...");
+ let value1 = await new Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ resolve(e.data);
+ },
+ { once: true }
+ );
+ ifr1.contentWindow.postMessage("getValue", "*");
+ });
+
+ ok(value1.startsWith("tracker-"), "The value is correctly set in ifr1");
+
+ info("Getting the value from ifr2...");
+ let value2 = await new Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ resolve(e.data);
+ },
+ { once: true }
+ );
+ ifr2.contentWindow.postMessage("getValue", "*");
+ });
+
+ is(value2, value1, "The values match");
+
+ info("Getting the events received by ifr2...");
+ let events = await new Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ resolve(e.data);
+ },
+ { once: true }
+ );
+ ifr2.contentWindow.postMessage("getEvents", "*");
+ });
+
+ is(events, 1, "One event");
+ }
+ );
+
+ BrowserTestUtils.removeTab(normalTab);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ });
+
+ // An ePartitionOrDeny iframe navigated between two distinct pages on the same
+ // origin does not see the values stored by the previous iframe.
+ add_task(async _ => {
+ log(test);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.ipc.processCount", 1],
+ ["network.cookie.cookieBehavior", prefValue],
+ ["network.cookie.cookieBehavior.pbmode", prefValue],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ [
+ "privacy.restrict3rdpartystorage.partitionedHosts",
+ "tracking.example.org,not-tracking.example.com",
+ ],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ info("Creating a non-tracker top-level context");
+ let normalTab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ let normalBrowser = gBrowser.getBrowserForTab(normalTab);
+ await BrowserTestUtils.browserLoaded(normalBrowser);
+
+ info("The non-tracker page opens a tracker iframe");
+ await SpecialPowers.spawn(
+ normalBrowser,
+ [
+ {
+ page: thirdPartyDomain + TEST_PATH + "localStorageEvents.html",
+ dynamicFPITest: test.dynamicFPITest,
+ },
+ ],
+ async obj => {
+ let ifr = content.document.createElement("iframe");
+ ifr.setAttribute("id", "ifr");
+ ifr.setAttribute("src", obj.page);
+
+ info("Iframe loading...");
+ await new content.Promise(resolve => {
+ ifr.onload = resolve;
+ content.document.body.appendChild(ifr);
+ });
+
+ info("Setting localStorage value in ifr...");
+ ifr.contentWindow.postMessage("setValue", "*");
+
+ info("Getting the value from ifr...");
+ let value = await new Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ resolve(e.data);
+ },
+ { once: true }
+ );
+ ifr.contentWindow.postMessage("getValue", "*");
+ });
+
+ ok(value.startsWith("tracker-"), "The value is correctly set in ifr");
+
+ info("Navigate...");
+ await new content.Promise(resolve => {
+ ifr.onload = resolve;
+ ifr.setAttribute("src", obj.page + "?" + Math.random());
+ });
+
+ info("Getting the value from ifr...");
+ let value2 = await new Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ resolve(e.data);
+ },
+ { once: true }
+ );
+ ifr.contentWindow.postMessage("getValue", "*");
+ });
+
+ if (obj.dynamicFPITest) {
+ is(value, value2, "The value is received");
+ } else {
+ is(value2, null, "The value is undefined");
+ }
+ }
+ );
+
+ BrowserTestUtils.removeTab(normalTab);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ });
+
+ // Like the previous test, but accepting trackers
+ add_task(async _ => {
+ log(test);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.ipc.processCount", 1],
+ ["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_ACCEPT],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_ACCEPT,
+ ],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ [
+ "privacy.restrict3rdpartystorage.partitionedHosts",
+ "tracking.example.org,not-tracking.example.com",
+ ],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ info("Creating a non-tracker top-level context");
+ let normalTab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ let normalBrowser = gBrowser.getBrowserForTab(normalTab);
+ await BrowserTestUtils.browserLoaded(normalBrowser);
+
+ info("The non-tracker page opens a tracker iframe");
+ await SpecialPowers.spawn(
+ normalBrowser,
+ [
+ {
+ page: thirdPartyDomain + TEST_PATH + "localStorageEvents.html",
+ },
+ ],
+ async obj => {
+ let ifr = content.document.createElement("iframe");
+ ifr.setAttribute("id", "ifr");
+ ifr.setAttribute("src", obj.page);
+
+ info("Iframe loading...");
+ await new content.Promise(resolve => {
+ ifr.onload = resolve;
+ content.document.body.appendChild(ifr);
+ });
+
+ info("Setting localStorage value in ifr...");
+ ifr.contentWindow.postMessage("setValue", "*");
+
+ info("Getting the value from ifr...");
+ let value = await new Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ resolve(e.data);
+ },
+ { once: true }
+ );
+ ifr.contentWindow.postMessage("getValue", "*");
+ });
+
+ ok(value.startsWith("tracker-"), "The value is correctly set in ifr");
+
+ info("Navigate...");
+ await new content.Promise(resolve => {
+ ifr.onload = resolve;
+ ifr.setAttribute("src", obj.page + "?" + Math.random());
+ });
+
+ info("Getting the value from ifr...");
+ let value2 = await new Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ resolve(e.data);
+ },
+ { once: true }
+ );
+ ifr.contentWindow.postMessage("getValue", "*");
+ });
+
+ is(value, value2, "The value is received");
+ }
+ );
+
+ BrowserTestUtils.removeTab(normalTab);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ });
+
+ // An ePartitionOrDeny iframe on the same origin that is navigated to itself
+ // via window.location.reload() or equivalent does not see the values stored
+ // by its previous self.
+ add_task(async _ => {
+ log(test);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.ipc.processCount", 1],
+ ["network.cookie.cookieBehavior", prefValue],
+ ["network.cookie.cookieBehavior.pbmode", prefValue],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ [
+ "privacy.restrict3rdpartystorage.partitionedHosts",
+ "tracking.example.org,not-tracking.example.com",
+ ],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ info("Creating a non-tracker top-level context");
+ let normalTab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ let normalBrowser = gBrowser.getBrowserForTab(normalTab);
+ await BrowserTestUtils.browserLoaded(normalBrowser);
+
+ info("The non-tracker page opens a tracker iframe");
+ await SpecialPowers.spawn(
+ normalBrowser,
+ [
+ {
+ page: thirdPartyDomain + TEST_PATH + "localStorageEvents.html",
+ dynamicFPITest: test.dynamicFPITest,
+ },
+ ],
+ async obj => {
+ let ifr = content.document.createElement("iframe");
+ ifr.setAttribute("id", "ifr");
+ ifr.setAttribute("src", obj.page);
+
+ info("Iframe loading...");
+ await new content.Promise(resolve => {
+ ifr.onload = resolve;
+ content.document.body.appendChild(ifr);
+ });
+
+ info("Setting localStorage value in ifr...");
+ ifr.contentWindow.postMessage("setValue", "*");
+
+ info("Getting the value from ifr...");
+ let value = await new Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ resolve(e.data);
+ },
+ { once: true }
+ );
+ ifr.contentWindow.postMessage("getValue", "*");
+ });
+
+ ok(value.startsWith("tracker-"), "The value is correctly set in ifr");
+
+ info("Reload...");
+ await new content.Promise(resolve => {
+ ifr.onload = resolve;
+ ifr.contentWindow.postMessage("reload", "*");
+ });
+
+ info("Getting the value from ifr...");
+ let value2 = await new Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ resolve(e.data);
+ },
+ { once: true }
+ );
+ ifr.contentWindow.postMessage("getValue", "*");
+ });
+
+ if (obj.dynamicFPITest) {
+ is(value, value2, "The value is equal");
+ } else {
+ is(value2, null, "The value is undefined");
+ }
+ }
+ );
+
+ BrowserTestUtils.removeTab(normalTab);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ });
+
+ // Like the previous test, but accepting trackers
+ add_task(async _ => {
+ log(test);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.ipc.processCount", 1],
+ ["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_ACCEPT],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_ACCEPT,
+ ],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ [
+ "privacy.restrict3rdpartystorage.partitionedHosts",
+ "tracking.example.org,not-tracking.example.com",
+ ],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ info("Creating a non-tracker top-level context");
+ let normalTab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ let normalBrowser = gBrowser.getBrowserForTab(normalTab);
+ await BrowserTestUtils.browserLoaded(normalBrowser);
+
+ info("The non-tracker page opens a tracker iframe");
+ await SpecialPowers.spawn(
+ normalBrowser,
+ [
+ {
+ page: thirdPartyDomain + TEST_PATH + "localStorageEvents.html",
+ },
+ ],
+ async obj => {
+ let ifr = content.document.createElement("iframe");
+ ifr.setAttribute("id", "ifr");
+ ifr.setAttribute("src", obj.page);
+
+ info("Iframe loading...");
+ await new content.Promise(resolve => {
+ ifr.onload = resolve;
+ content.document.body.appendChild(ifr);
+ });
+
+ info("Setting localStorage value in ifr...");
+ ifr.contentWindow.postMessage("setValue", "*");
+
+ info("Getting the value from ifr...");
+ let value = await new Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ resolve(e.data);
+ },
+ { once: true }
+ );
+ ifr.contentWindow.postMessage("getValue", "*");
+ });
+
+ ok(value.startsWith("tracker-"), "The value is correctly set in ifr");
+
+ info("Reload...");
+ await new content.Promise(resolve => {
+ ifr.onload = resolve;
+ ifr.contentWindow.postMessage("reload", "*");
+ });
+
+ info("Getting the value from ifr...");
+ let value2 = await new Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ resolve(e.data);
+ },
+ { once: true }
+ );
+ ifr.contentWindow.postMessage("getValue", "*");
+ });
+
+ is(value, value2, "The value is equal");
+ }
+ );
+
+ BrowserTestUtils.removeTab(normalTab);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ });
+
+ // An ePartitionOrDeny iframe on different top-level domain tabs
+ add_task(async _ => {
+ log(test);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.ipc.processCount", 1],
+ ["network.cookie.cookieBehavior", prefValue],
+ ["network.cookie.cookieBehavior.pbmode", prefValue],
+ ["privacy.firstparty.isolate", false],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ [
+ "privacy.restrict3rdpartystorage.partitionedHosts",
+ "tracking.example.org,not-tracking.example.com",
+ ],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ info("Creating a non-tracker top-level context");
+ let normalTab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ let normalBrowser = gBrowser.getBrowserForTab(normalTab);
+ await BrowserTestUtils.browserLoaded(normalBrowser);
+
+ info("The non-tracker page opens a tracker iframe");
+ let result1 = await SpecialPowers.spawn(
+ normalBrowser,
+ [
+ {
+ page: thirdPartyDomain + TEST_PATH + "localStorageEvents.html",
+ },
+ ],
+ async obj => {
+ let ifr = content.document.createElement("iframe");
+ ifr.setAttribute("id", "ifr");
+ ifr.setAttribute("src", obj.page);
+
+ info("Iframe loading...");
+ await new content.Promise(resolve => {
+ ifr.onload = resolve;
+ content.document.body.appendChild(ifr);
+ });
+
+ info("Setting localStorage value in ifr...");
+ ifr.contentWindow.postMessage("setValue", "*");
+
+ info("Getting the value from ifr...");
+ let value = await new Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ resolve(e.data);
+ },
+ { once: true }
+ );
+ ifr.contentWindow.postMessage("getValue", "*");
+ });
+
+ ok(value.startsWith("tracker-"), "The value is correctly set in ifr");
+ return value;
+ }
+ );
+ ok(result1.startsWith("tracker-"), "The value is correctly set in tab1");
+
+ info("Creating a non-tracker top-level context");
+ let normalTab2 = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE_2);
+ let normalBrowser2 = gBrowser.getBrowserForTab(normalTab2);
+ await BrowserTestUtils.browserLoaded(normalBrowser2);
+
+ info("The non-tracker page opens a tracker iframe");
+ let result2 = await SpecialPowers.spawn(
+ normalBrowser2,
+ [
+ {
+ page: thirdPartyDomain + TEST_PATH + "localStorageEvents.html",
+ },
+ ],
+ async obj => {
+ let ifr = content.document.createElement("iframe");
+ ifr.setAttribute("id", "ifr");
+ ifr.setAttribute("src", obj.page);
+
+ info("Iframe loading...");
+ await new content.Promise(resolve => {
+ ifr.onload = resolve;
+ content.document.body.appendChild(ifr);
+ });
+
+ info("Getting the value from ifr...");
+ let value = await new Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ resolve(e.data);
+ },
+ { once: true }
+ );
+ ifr.contentWindow.postMessage("getValue", "*");
+ });
+ return value;
+ }
+ );
+
+ ok(!result2, "The value is null");
+
+ BrowserTestUtils.removeTab(normalTab);
+ BrowserTestUtils.removeTab(normalTab2);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ });
+
+ // Like the previous test, but accepting trackers
+ add_task(async _ => {
+ log(test);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.ipc.processCount", 1],
+ ["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_ACCEPT],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_ACCEPT,
+ ],
+ ["privacy.firstparty.isolate", false],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ [
+ "privacy.restrict3rdpartystorage.partitionedHosts",
+ "tracking.example.org,not-tracking.example.com",
+ ],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ info("Creating a non-tracker top-level context");
+ let normalTab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ let normalBrowser = gBrowser.getBrowserForTab(normalTab);
+ await BrowserTestUtils.browserLoaded(normalBrowser);
+
+ info("The non-tracker page opens a tracker iframe");
+ let result1 = await SpecialPowers.spawn(
+ normalBrowser,
+ [
+ {
+ page: thirdPartyDomain + TEST_PATH + "localStorageEvents.html",
+ },
+ ],
+ async obj => {
+ let ifr = content.document.createElement("iframe");
+ ifr.setAttribute("id", "ifr");
+ ifr.setAttribute("src", obj.page);
+
+ info("Iframe loading...");
+ await new content.Promise(resolve => {
+ ifr.onload = resolve;
+ content.document.body.appendChild(ifr);
+ });
+
+ info("Setting localStorage value in ifr...");
+ ifr.contentWindow.postMessage("setValue", "*");
+
+ info("Getting the value from ifr...");
+ let value = await new Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ resolve(e.data);
+ },
+ { once: true }
+ );
+ ifr.contentWindow.postMessage("getValue", "*");
+ });
+
+ ok(value.startsWith("tracker-"), "The value is correctly set in ifr");
+ return value;
+ }
+ );
+ ok(result1.startsWith("tracker-"), "The value is correctly set in tab1");
+
+ info("Creating a non-tracker top-level context");
+ let normalTab2 = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE_2);
+ let normalBrowser2 = gBrowser.getBrowserForTab(normalTab2);
+ await BrowserTestUtils.browserLoaded(normalBrowser2);
+
+ info("The non-tracker page opens a tracker iframe");
+ let result2 = await SpecialPowers.spawn(
+ normalBrowser2,
+ [
+ {
+ page: thirdPartyDomain + TEST_PATH + "localStorageEvents.html",
+ },
+ ],
+ async obj => {
+ let ifr = content.document.createElement("iframe");
+ ifr.setAttribute("id", "ifr");
+ ifr.setAttribute("src", obj.page);
+
+ info("Iframe loading...");
+ await new content.Promise(resolve => {
+ ifr.onload = resolve;
+ content.document.body.appendChild(ifr);
+ });
+
+ info("Getting the value from ifr...");
+ let value = await new Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ resolve(e.data);
+ },
+ { once: true }
+ );
+ ifr.contentWindow.postMessage("getValue", "*");
+ });
+ return value;
+ }
+ );
+
+ is(result1, result2, "The value is undefined");
+
+ BrowserTestUtils.removeTab(normalTab);
+ BrowserTestUtils.removeTab(normalTab2);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ });
+
+ // Cleanup data.
+ add_task(async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ });
+}
+
+for (let pref of [
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+]) {
+ runAllTests(pref);
+}
diff --git a/toolkit/components/antitracking/test/browser/browser_partitionedMessaging.js b/toolkit/components/antitracking/test/browser/browser_partitionedMessaging.js
new file mode 100644
index 0000000000..683b1cc874
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedMessaging.js
@@ -0,0 +1,20 @@
+PartitionedStorageHelper.runTestInNormalAndPrivateMode(
+ "BroadcastChannel",
+ async (win3rdParty, win1stParty, allowed) => {
+ let a = new win3rdParty.BroadcastChannel("hello");
+ ok(!!a, "BroadcastChannel should be created by 3rd party iframe");
+
+ let b = new win1stParty.BroadcastChannel("hello");
+ ok(!!b, "BroadcastChannel should be created by 1st party iframe");
+
+ // BroadcastChannel uses the incument global, this means that its CTOR will
+ // always use the 3rd party iframe's window as global.
+ },
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ }
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_partitionedServiceWorkers.js b/toolkit/components/antitracking/test/browser/browser_partitionedServiceWorkers.js
new file mode 100644
index 0000000000..f45caba43a
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedServiceWorkers.js
@@ -0,0 +1,625 @@
+/* import-globals-from storageAccessAPIHelpers.js */
+
+PartitionedStorageHelper.runTest(
+ "ServiceWorkers - disable partitioning",
+ async (win3rdParty, win1stParty, allowed) => {
+ // Partitioned serviceWorkers are disabled in third-party context.
+ await win3rdParty.navigator.serviceWorker.register("empty.js").then(
+ _ => {
+ ok(allowed, "Success: ServiceWorker cannot be used!");
+ },
+ _ => {
+ ok(!allowed, "Failed: ServiceWorker cannot be used!");
+ }
+ );
+
+ await win1stParty.navigator.serviceWorker.register("empty.js").then(
+ _ => {
+ ok(true, "Success: ServiceWorker should be available!");
+ },
+ _ => {
+ ok(false, "Failed: ServiceWorker should be available!");
+ }
+ );
+ },
+
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+
+ [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.ipc.processCount", 1],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["privacy.partition.serviceWorkers", false],
+ ]
+);
+
+PartitionedStorageHelper.runTest(
+ "ServiceWorkers - enable partitioning",
+ async (win3rdParty, win1stParty, allowed) => {
+ // Partitioned serviceWorkers are enabled in third-party context.
+ await win3rdParty.navigator.serviceWorker.register("empty.js").then(
+ _ => {
+ ok(
+ true,
+ "Success: ServiceWorker should be available in third parties."
+ );
+ },
+ _ => {
+ ok(
+ false,
+ "Failed: ServiceWorker should be available in third parties."
+ );
+ }
+ );
+
+ await win1stParty.navigator.serviceWorker.register("empty.js").then(
+ _ => {
+ ok(true, "Success: ServiceWorker should be available!");
+ },
+ _ => {
+ ok(false, "Failed: ServiceWorker should be available!");
+ }
+ );
+ },
+
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+
+ [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.ipc.processCount", 1],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["privacy.partition.serviceWorkers", true],
+ ]
+);
+
+PartitionedStorageHelper.runTest(
+ "ServiceWorkers - MatchAll",
+ async (win3rdParty, win1stParty, allowed) => {
+ if (!win1stParty.sw) {
+ win1stParty.sw = await registerServiceWorker(win1stParty, "matchAll.js");
+ }
+
+ let msgPromise = new Promise(resolve => {
+ win1stParty.navigator.serviceWorker.addEventListener("message", msg => {
+ resolve(msg.data);
+ });
+ });
+
+ win1stParty.sw.postMessage(win3rdParty.location.href);
+ let msg = await msgPromise;
+
+ // The service worker will always be partitioned. So, the first party window
+ // won't have control on the third-party window.
+ is(
+ false,
+ msg,
+ "We won't have the 3rd party window controlled regardless of StorageAccess."
+ );
+ },
+
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+
+ [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.ipc.processCount", 1],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["privacy.partition.serviceWorkers", true],
+ ]
+);
+
+PartitionedStorageHelper.runTest(
+ "ServiceWorkers - Partition ScriptContext",
+ async (win3rdParty, win1stParty, allowed) => {
+ // Register service worker for the first-party window.
+ if (!win1stParty.sw) {
+ win1stParty.sw = await registerServiceWorker(
+ win1stParty,
+ "serviceWorker.js"
+ );
+ }
+
+ // Register service worker for the third-party window.
+ if (!win3rdParty.sw) {
+ win3rdParty.sw = await registerServiceWorker(
+ win3rdParty,
+ "serviceWorker.js"
+ );
+ }
+
+ // Set a script value to first-party service worker.
+ let res = await sendAndWaitWorkerMessage(
+ win1stParty.sw,
+ win1stParty.navigator.serviceWorker,
+ {
+ type: "SetScriptValue",
+ value: "1stParty",
+ }
+ );
+ ok(res.result, "OK", "Set script value to first-party service worker.");
+
+ // Set a script value to third-party service worker.
+ res = await sendAndWaitWorkerMessage(
+ win3rdParty.sw,
+ win3rdParty.navigator.serviceWorker,
+ {
+ type: "SetScriptValue",
+ value: "3rdParty",
+ }
+ );
+ ok(res.result, "OK", "Set script value to third-party service worker.");
+
+ // Get and check script value from the first-party service worker.
+ res = await sendAndWaitWorkerMessage(
+ win1stParty.sw,
+ win1stParty.navigator.serviceWorker,
+ { type: "GetScriptValue" }
+ );
+ is(
+ res.value,
+ "1stParty",
+ "The script value in first party window is correct"
+ );
+
+ // Get and check script value from the third-party service worker.
+ res = await sendAndWaitWorkerMessage(
+ win3rdParty.sw,
+ win3rdParty.navigator.serviceWorker,
+ { type: "GetScriptValue" }
+ );
+
+ is(
+ res.value,
+ "3rdParty",
+ "The script value in third party window is correct"
+ );
+ },
+
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+
+ [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.ipc.processCount", 1],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["privacy.partition.serviceWorkers", true],
+ ]
+);
+
+PartitionedStorageHelper.runTest(
+ "ServiceWorkers - Partition DOM Cache",
+ async (win3rdParty, win1stParty, allowed) => {
+ // Register service worker for the first-party window.
+ if (!win1stParty.sw) {
+ win1stParty.sw = await registerServiceWorker(
+ win1stParty,
+ "serviceWorker.js"
+ );
+ }
+
+ // Register service worker for the third-party window.
+ if (!win3rdParty.sw) {
+ win3rdParty.sw = await registerServiceWorker(
+ win3rdParty,
+ "serviceWorker.js"
+ );
+ }
+
+ // Set DOM cache to first-party service worker.
+ let res = await sendAndWaitWorkerMessage(
+ win1stParty.sw,
+ win1stParty.navigator.serviceWorker,
+ {
+ type: "SetCache",
+ value: "1stParty",
+ }
+ );
+ ok(res.result, "OK", "Set cache to first-party service worker.");
+
+ // Set DOM cache to third-party service worker.
+ res = await sendAndWaitWorkerMessage(
+ win3rdParty.sw,
+ win3rdParty.navigator.serviceWorker,
+ {
+ type: "SetCache",
+ value: "3rdParty",
+ }
+ );
+ ok(res.result, "OK", "Set cache to third-party service worker.");
+
+ // Check DOM cache from the first-party service worker.
+ res = await sendAndWaitWorkerMessage(
+ win1stParty.sw,
+ win1stParty.navigator.serviceWorker,
+ { type: "HasCache", value: "1stParty" }
+ );
+ ok(
+ res.value,
+ "The '1stParty' cache storage should exist for the first-party window."
+ );
+ res = await sendAndWaitWorkerMessage(
+ win1stParty.sw,
+ win1stParty.navigator.serviceWorker,
+ { type: "HasCache", value: "3rdParty" }
+ );
+ ok(
+ !res.value,
+ "The '3rdParty' cache storage should not exist for the first-party window."
+ );
+
+ // Check DOM cache from the third-party service worker.
+ res = await sendAndWaitWorkerMessage(
+ win3rdParty.sw,
+ win3rdParty.navigator.serviceWorker,
+ { type: "HasCache", value: "1stParty" }
+ );
+
+ ok(
+ !res.value,
+ "The '1stParty' cache storage should not exist for the third-party window."
+ );
+
+ res = await sendAndWaitWorkerMessage(
+ win3rdParty.sw,
+ win3rdParty.navigator.serviceWorker,
+ { type: "HasCache", value: "3rdParty" }
+ );
+
+ ok(
+ res.value,
+ "The '3rdParty' cache storage should exist for the third-party window."
+ );
+ },
+
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+
+ [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.ipc.processCount", 1],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["privacy.partition.serviceWorkers", true],
+ ]
+);
+
+PartitionedStorageHelper.runTest(
+ "ServiceWorkers - Partition IndexedDB",
+ async (win3rdParty, win1stParty, allowed) => {
+ // Register service worker for the first-party window.
+ if (!win1stParty.sw) {
+ win1stParty.sw = await registerServiceWorker(
+ win1stParty,
+ "serviceWorker.js"
+ );
+ }
+
+ // Register service worker for the third-party window.
+ if (!win3rdParty.sw) {
+ win3rdParty.sw = await registerServiceWorker(
+ win3rdParty,
+ "serviceWorker.js"
+ );
+ }
+
+ // Set indexedDB value to first-party service worker.
+ let res = await sendAndWaitWorkerMessage(
+ win1stParty.sw,
+ win1stParty.navigator.serviceWorker,
+ {
+ type: "SetIndexedDB",
+ value: "1stParty",
+ }
+ );
+ ok(res.result, "OK", "Set cache to first-party service worker.");
+
+ // Set indexedDB value to third-party service worker.
+ res = await sendAndWaitWorkerMessage(
+ win3rdParty.sw,
+ win3rdParty.navigator.serviceWorker,
+ {
+ type: "SetIndexedDB",
+ value: "3rdParty",
+ }
+ );
+ ok(res.result, "OK", "Set cache to third-party service worker.");
+
+ // Get and check indexedDB value from the first-party service worker.
+ res = await sendAndWaitWorkerMessage(
+ win1stParty.sw,
+ win1stParty.navigator.serviceWorker,
+ { type: "GetIndexedDB" }
+ );
+ is(
+ res.value,
+ "1stParty",
+ "The indexedDB value in first party window is correct"
+ );
+
+ // Get and check indexedDB from the third-party service worker.
+ res = await sendAndWaitWorkerMessage(
+ win3rdParty.sw,
+ win3rdParty.navigator.serviceWorker,
+ { type: "GetIndexedDB" }
+ );
+
+ is(
+ res.value,
+ "3rdParty",
+ "The indexedDB value in third party window is correct"
+ );
+ },
+
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+
+ [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.ipc.processCount", 1],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["privacy.partition.serviceWorkers", true],
+ ]
+);
+
+PartitionedStorageHelper.runTest(
+ "ServiceWorkers - Partition Intercept",
+ async (win3rdParty, win1stParty, allowed) => {
+ // Register service worker for the first-party window.
+ if (!win1stParty.sw) {
+ win1stParty.sw = await registerServiceWorker(
+ win1stParty,
+ "serviceWorker.js"
+ );
+ }
+
+ // Register service worker for the third-party window.
+ if (!win3rdParty.sw) {
+ win3rdParty.sw = await registerServiceWorker(
+ win3rdParty,
+ "serviceWorker.js"
+ );
+ }
+
+ // Fetch a resource in the first-party window.
+ await win1stParty.fetch("empty.js");
+
+ // Check that only the first-party service worker gets fetch event.
+ let res = await sendAndWaitWorkerMessage(
+ win1stParty.sw,
+ win1stParty.navigator.serviceWorker,
+ { type: "GetFetchURL" }
+ );
+ is(
+ res.value,
+ "http://not-tracking.example.com/browser/toolkit/components/antitracking/test/browser/empty.js",
+ "The first-party service worker received fetch event."
+ );
+ res = await sendAndWaitWorkerMessage(
+ win3rdParty.sw,
+ win3rdParty.navigator.serviceWorker,
+ { type: "GetFetchURL" }
+ );
+ is(
+ res.value,
+ "",
+ "The third-party service worker received no fetch event."
+ );
+
+ // Fetch a resource in the third-party window.
+ await win3rdParty.fetch("empty.js");
+
+ // Check if the fetch event only happens in third-party service worker.
+ res = await sendAndWaitWorkerMessage(
+ win1stParty.sw,
+ win1stParty.navigator.serviceWorker,
+ { type: "GetFetchURL" }
+ );
+ is(
+ res.value,
+ "",
+ "The first-party service worker received no fetch event."
+ );
+ res = await sendAndWaitWorkerMessage(
+ win3rdParty.sw,
+ win3rdParty.navigator.serviceWorker,
+ { type: "GetFetchURL" }
+ );
+ is(
+ res.value,
+ "http://not-tracking.example.com/browser/toolkit/components/antitracking/test/browser/empty.js",
+ "The third-party service worker received fetch event."
+ );
+ },
+
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+
+ [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.ipc.processCount", 1],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["privacy.partition.serviceWorkers", true],
+ ]
+);
+
+// Bug1743236 - Verify the content process won't crash if we create a dedicated
+// worker in a service worker controlled third-party page with Storage Access.
+PartitionedStorageHelper.runTest(
+ "ServiceWorkers - Create Dedicated Worker",
+ async (win3rdParty, win1stParty, allowed) => {
+ // We only do this test when the storage access is granted.
+ if (!allowed) {
+ return;
+ }
+
+ // Register service worker for the first-party window.
+ if (!win1stParty.sw) {
+ win1stParty.sw = await registerServiceWorker(
+ win1stParty,
+ "serviceWorker.js"
+ );
+ }
+
+ // Register service worker for the third-party window.
+ if (!win3rdParty.sw) {
+ win3rdParty.sw = await registerServiceWorker(
+ win3rdParty,
+ "serviceWorker.js"
+ );
+ }
+
+ // Create a dedicated worker in first-party window.
+ let firstPartyWorker = new win1stParty.Worker("dedicatedWorker.js");
+
+ // Post a message to the dedicated worker and wait until the message circles
+ // back.
+ await new Promise(resolve => {
+ firstPartyWorker.addEventListener("message", msg => {
+ if (msg.data == "1stParty") {
+ resolve();
+ }
+ });
+
+ firstPartyWorker.postMessage("1stParty");
+ });
+
+ // Create a dedicated worker in third-party window.
+ let thirdPartyWorker = new win3rdParty.Worker("dedicatedWorker.js");
+
+ // Post a message to the dedicated worker and wait until the message circles
+ // back.
+ await new Promise(resolve => {
+ thirdPartyWorker.addEventListener("message", msg => {
+ if (msg.data == "3rdParty") {
+ resolve();
+ }
+ });
+
+ thirdPartyWorker.postMessage("3rdParty");
+ });
+
+ firstPartyWorker.terminate();
+ thirdPartyWorker.terminate();
+ },
+
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+
+ [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.ipc.processCount", 1],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["privacy.partition.serviceWorkers", true],
+ ]
+);
+
+// Bug1768193 - Verify the parent process won't crash if we create a shared
+// worker in a service worker controlled third-party page with Storage Access.
+PartitionedStorageHelper.runTest(
+ "ServiceWorkers - Create Shared Worker",
+ async (win3rdParty, win1stParty, allowed) => {
+ // We only do this test when the storage access is granted.
+ if (!allowed) {
+ return;
+ }
+
+ // Register service worker for the first-party window.
+ if (!win1stParty.sw) {
+ win1stParty.sw = await registerServiceWorker(
+ win1stParty,
+ "serviceWorker.js"
+ );
+ }
+
+ // Register service worker for the third-party window.
+ if (!win3rdParty.sw) {
+ win3rdParty.sw = await registerServiceWorker(
+ win3rdParty,
+ "serviceWorker.js"
+ );
+ }
+
+ // Create a shared worker in third-party window.
+ let thirdPartyWorker = new win3rdParty.SharedWorker("sharedWorker.js");
+
+ // Post a message to the dedicated worker and wait until the message circles
+ // back.
+ await new Promise(resolve => {
+ thirdPartyWorker.port.onmessage = msg => {
+ resolve();
+ };
+ thirdPartyWorker.onerror = _ => {
+ ok(false, "We should not be here");
+ resolve();
+ };
+ thirdPartyWorker.port.postMessage("count");
+ });
+
+ thirdPartyWorker.port.postMessage("close");
+ },
+
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+
+ [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.ipc.processCount", 1],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["privacy.partition.serviceWorkers", true],
+ ]
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_partitionedSharedWorkers.js b/toolkit/components/antitracking/test/browser/browser_partitionedSharedWorkers.js
new file mode 100644
index 0000000000..337d36b6e4
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedSharedWorkers.js
@@ -0,0 +1,74 @@
+PartitionedStorageHelper.runTestInNormalAndPrivateMode(
+ "SharedWorkers",
+ async (win3rdParty, win1stParty, allowed) => {
+ // This test fails if run with an HTTPS 3rd-party URL because the shared worker
+ // which would start from the window opened from 3rdPartyStorage.html will become
+ // secure context and per step 11.4.3 of
+ // https://html.spec.whatwg.org/multipage/workers.html#dom-sharedworker attempting
+ // to run the SharedWorker constructor would emit an error event.
+ is(
+ win3rdParty.location.protocol,
+ "http:",
+ "Our 3rd party URL shouldn't be HTTPS"
+ );
+
+ let sh1 = new win1stParty.SharedWorker("sharedWorker.js");
+ await new Promise(resolve => {
+ sh1.port.onmessage = e => {
+ is(e.data, 1, "We expected 1 connection");
+ resolve();
+ };
+ sh1.port.postMessage("count");
+ });
+
+ let sh3 = new win3rdParty.SharedWorker("sharedWorker.js");
+ await new Promise(resolve => {
+ sh3.port.onmessage = e => {
+ is(e.data, 1, `We expected 1 connection for 3rd party SharedWorker`);
+ resolve();
+ };
+ sh3.onerror = _ => {
+ ok(false, "We should not be here");
+ resolve();
+ };
+ sh3.port.postMessage("count");
+ });
+
+ sh1.port.postMessage("close");
+ sh3.port.postMessage("close");
+ },
+
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ }
+);
+
+PartitionedStorageHelper.runPartitioningTestInNormalAndPrivateMode(
+ "Partitioned tabs - SharedWorker",
+ "sharedworker",
+
+ // getDataCallback
+ async win => {
+ win.sh = new win.SharedWorker("partitionedSharedWorker.js");
+ return new Promise(resolve => {
+ win.sh.port.onmessage = e => {
+ resolve(e.data);
+ };
+ win.sh.port.postMessage({ what: "get" });
+ });
+ },
+
+ // addDataCallback
+ async (win, value) => {
+ win.sh = new win.SharedWorker("partitionedSharedWorker.js");
+ win.sh.port.postMessage({ what: "put", value });
+ return true;
+ },
+
+ // cleanup
+ async _ => {}
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_permissionInNormalWindows.js b/toolkit/components/antitracking/test/browser/browser_permissionInNormalWindows.js
new file mode 100644
index 0000000000..a414e7363b
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_permissionInNormalWindows.js
@@ -0,0 +1,106 @@
+AntiTracking.runTest(
+ "Test whether we receive any persistent permissions in normal windows",
+ // Blocking callback
+ async _ => {
+ // Nothing to do here!
+ },
+
+ // Non blocking callback
+ async _ => {
+ try {
+ // We load the test script in the parent process to check permissions.
+ let chromeScript = SpecialPowers.loadChromeScript(_ => {
+ /* eslint-env mozilla/chrome-script */
+ addMessageListener("go", _ => {
+ function ok(what, msg) {
+ sendAsyncMessage("ok", { what: !!what, msg });
+ }
+
+ function is(a, b, msg) {
+ ok(a === b, msg);
+ }
+
+ // We should use the principal of the TEST_DOMAIN since the storage
+ // permission is saved under it.
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ "http://example.net/"
+ );
+
+ for (let perm of Services.perms.getAllForPrincipal(principal)) {
+ // Ignore permissions other than storage access
+ if (!perm.type.startsWith("3rdPartyStorage^")) {
+ continue;
+ }
+ is(
+ perm.expireType,
+ Services.perms.EXPIRE_TIME,
+ "Permission must expire at a specific time"
+ );
+ ok(perm.expireTime > 0, "Permission must have a expiry time");
+ }
+
+ sendAsyncMessage("done");
+ });
+ });
+
+ chromeScript.addMessageListener("ok", obj => {
+ ok(obj.what, obj.msg);
+ });
+
+ await new Promise(resolve => {
+ chromeScript.addMessageListener("done", _ => {
+ chromeScript.destroy();
+ resolve();
+ });
+
+ chromeScript.sendAsyncMessage("go");
+ });
+
+ // We check the permission in tracking processes for non-Fission mode. In
+ // Fission mode, the permission won't be synced to the tracking process,
+ // so we don't check it.
+ if (!SpecialPowers.useRemoteSubframes) {
+ let Services = SpecialPowers.Services;
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ "http://example.net/"
+ );
+
+ for (let perm of Services.perms.getAllForPrincipal(principal)) {
+ // Ignore permissions other than storage access
+ if (!perm.type.startsWith("3rdPartyStorage^")) {
+ continue;
+ }
+ is(
+ perm.expireType,
+ Services.perms.EXPIRE_TIME,
+ "Permission must expire at a specific time"
+ );
+ ok(perm.expireTime > 0, "Permission must have a expiry time");
+ }
+ }
+ } catch (e) {
+ alert(e);
+ }
+ },
+
+ // Cleanup callback
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [
+ [
+ "privacy.partition.always_partition_third_party_non_cookie_storage",
+ false,
+ ],
+ ], // extra prefs
+ true, // run the window.open() test
+ true, // run the user interaction test
+ 0, // don't expect blocking notifications
+ false
+); // run in normal windows
diff --git a/toolkit/components/antitracking/test/browser/browser_permissionInNormalWindows_alwaysPartition.js b/toolkit/components/antitracking/test/browser/browser_permissionInNormalWindows_alwaysPartition.js
new file mode 100644
index 0000000000..8a53782ba2
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_permissionInNormalWindows_alwaysPartition.js
@@ -0,0 +1,109 @@
+AntiTracking.runTest(
+ "Test whether we receive any persistent permissions in normal windows",
+ // Blocking callback
+ async _ => {
+ // Nothing to do here!
+ },
+
+ // Non blocking callback
+ async _ => {
+ try {
+ // We load the test script in the parent process to check permissions.
+ let chromeScript = SpecialPowers.loadChromeScript(_ => {
+ /* eslint-env mozilla/chrome-script */
+ addMessageListener("go", _ => {
+ const { Services } = ChromeUtils.import(
+ "resource://gre/modules/Services.jsm"
+ );
+
+ function ok(what, msg) {
+ sendAsyncMessage("ok", { what: !!what, msg });
+ }
+
+ function is(a, b, msg) {
+ ok(a === b, msg);
+ }
+
+ // We should use the principal of the TEST_DOMAIN since the storage
+ // permission is saved under it.
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ "http://example.net/"
+ );
+
+ for (let perm of Services.perms.getAllForPrincipal(principal)) {
+ // Ignore permissions other than storage access
+ if (!perm.type.startsWith("3rdPartyStorage^")) {
+ continue;
+ }
+ is(
+ perm.expireType,
+ Services.perms.EXPIRE_TIME,
+ "Permission must expire at a specific time"
+ );
+ ok(perm.expireTime > 0, "Permission must have a expiry time");
+ }
+
+ sendAsyncMessage("done");
+ });
+ });
+
+ chromeScript.addMessageListener("ok", obj => {
+ ok(obj.what, obj.msg);
+ });
+
+ await new Promise(resolve => {
+ chromeScript.addMessageListener("done", _ => {
+ chromeScript.destroy();
+ resolve();
+ });
+
+ chromeScript.sendAsyncMessage("go");
+ });
+
+ // We check the permission in tracking processes for non-Fission mode. In
+ // Fission mode, the permission won't be synced to the tracking process,
+ // so we don't check it.
+ if (!SpecialPowers.useRemoteSubframes) {
+ let Services = SpecialPowers.Services;
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ "http://example.net/"
+ );
+
+ for (let perm of Services.perms.getAllForPrincipal(principal)) {
+ // Ignore permissions other than storage access
+ if (!perm.type.startsWith("3rdPartyStorage^")) {
+ continue;
+ }
+ is(
+ perm.expireType,
+ Services.perms.EXPIRE_TIME,
+ "Permission must expire at a specific time"
+ );
+ ok(perm.expireTime > 0, "Permission must have a expiry time");
+ }
+ }
+ } catch (e) {
+ alert(e);
+ }
+ },
+
+ // Cleanup callback
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [["privacy.partition.always_partition_third_party_non_cookie_storage", true]], // extra prefs
+ true, // run the window.open() test
+ true, // run the user interaction test
+ [
+ // expected blocking notifications
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER,
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_ALL,
+ ],
+ false
+); // run in normal windows
diff --git a/toolkit/components/antitracking/test/browser/browser_permissionInPrivateWindows.js b/toolkit/components/antitracking/test/browser/browser_permissionInPrivateWindows.js
new file mode 100644
index 0000000000..8c8944fe2f
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_permissionInPrivateWindows.js
@@ -0,0 +1,50 @@
+AntiTracking.runTest(
+ "Test whether we receive any persistent permissions in private windows",
+ // Blocking callback
+ async _ => {
+ // Nothing to do here!
+ },
+
+ // Non blocking callback
+ async _ => {
+ try {
+ let Services = SpecialPowers.Services;
+ // We would use TEST_3RD_PARTY_DOMAIN here, except that the variable isn't
+ // accessible in the context of the web page...
+ let principal = SpecialPowers.wrap(document).nodePrincipal;
+ for (let perm of Services.perms.getAllForPrincipal(principal)) {
+ // Ignore permissions other than storage access
+ if (!perm.type.startsWith("3rdPartyStorage^")) {
+ continue;
+ }
+ is(
+ perm.expireType,
+ Services.perms.EXPIRE_SESSION,
+ "Permission must expire at the end of session"
+ );
+ is(perm.expireTime, 0, "Permission must have no expiry time");
+ }
+ } catch (e) {
+ alert(e);
+ }
+ },
+
+ // Cleanup callback
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [
+ [
+ "privacy.partition.always_partition_third_party_non_cookie_storage",
+ false,
+ ],
+ ], // extra prefs
+ true, // run the window.open() test
+ true, // run the user interaction test
+ 0, // don't expect blocking notifications
+ true
+); // run in private windows
diff --git a/toolkit/components/antitracking/test/browser/browser_permissionInPrivateWindows_alwaysPartition.js b/toolkit/components/antitracking/test/browser/browser_permissionInPrivateWindows_alwaysPartition.js
new file mode 100644
index 0000000000..defb96004b
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_permissionInPrivateWindows_alwaysPartition.js
@@ -0,0 +1,49 @@
+AntiTracking.runTest(
+ "Test whether we receive any persistent permissions in private windows",
+ // Blocking callback
+ async _ => {
+ // Nothing to do here!
+ },
+
+ // Non blocking callback
+ async _ => {
+ try {
+ let Services = SpecialPowers.Services;
+ // We would use TEST_3RD_PARTY_DOMAIN here, except that the variable isn't
+ // accessible in the context of the web page...
+ let principal = SpecialPowers.wrap(document).nodePrincipal;
+ for (let perm of Services.perms.getAllForPrincipal(principal)) {
+ // Ignore permissions other than storage access
+ if (!perm.type.startsWith("3rdPartyStorage^")) {
+ continue;
+ }
+ is(
+ perm.expireType,
+ Services.perms.EXPIRE_SESSION,
+ "Permission must expire at the end of session"
+ );
+ is(perm.expireTime, 0, "Permission must have no expiry time");
+ }
+ } catch (e) {
+ alert(e);
+ }
+ },
+
+ // Cleanup callback
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [["privacy.partition.always_partition_third_party_non_cookie_storage", true]], // extra prefs
+ true, // run the window.open() test
+ true, // run the user interaction test
+ [
+ // expected blocking notifications
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER,
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_ALL,
+ ],
+ true
+); // run in private windows
diff --git a/toolkit/components/antitracking/test/browser/browser_permissionPropagation.js b/toolkit/components/antitracking/test/browser/browser_permissionPropagation.js
new file mode 100644
index 0000000000..35fd83bd31
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_permissionPropagation.js
@@ -0,0 +1,425 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+
+/**
+ * This test makes sure the when we grant the storage permission, the
+ * permission is also propagated to iframes within the same agent cluster,
+ * but not to iframes in the other tabs.
+ */
+
+async function createTab(topUrl, iframeCount, opener, params) {
+ let newTab;
+ let browser;
+ if (opener) {
+ let promise = BrowserTestUtils.waitForNewTab(gBrowser, topUrl);
+ await SpecialPowers.spawn(opener, [topUrl], function (url) {
+ content.window.open(url, "_blank");
+ });
+ newTab = await promise;
+ browser = gBrowser.getBrowserForTab(newTab);
+ } else {
+ newTab = BrowserTestUtils.addTab(gBrowser, topUrl);
+
+ browser = gBrowser.getBrowserForTab(newTab);
+ await BrowserTestUtils.browserLoaded(browser);
+ }
+
+ await SpecialPowers.spawn(
+ browser,
+ [params, iframeCount, createTrackerFrame.toString()],
+ async function (params, count, fn) {
+ // eslint-disable-next-line no-eval
+ let fnCreateTrackerFrame = eval(`(() => (${fn}))()`);
+ await fnCreateTrackerFrame(params, count, ifr => {
+ ifr.contentWindow.postMessage(
+ { callback: params.msg.blockingCallback },
+ "*"
+ );
+ });
+ }
+ );
+
+ return Promise.resolve(newTab);
+}
+
+async function createTrackerFrame(params, count, callback) {
+ let iframes = [];
+ for (var i = 0; i < count; i++) {
+ iframes[i] = content.document.createElement("iframe");
+ await new content.Promise(resolve => {
+ iframes[i].id = "ifr" + i;
+ iframes[i].src = params.page;
+ iframes[i].onload = resolve;
+ content.document.body.appendChild(iframes[i]);
+ });
+
+ await new content.Promise(resolve => {
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ callback(iframes[i]);
+ });
+ }
+}
+
+async function testPermission(browser, block, params) {
+ await SpecialPowers.spawn(
+ browser,
+ [block, params],
+ async function (block, params) {
+ for (let i = 0; ; i++) {
+ let ifr = content.document.getElementById("ifr" + i);
+ if (!ifr) {
+ break;
+ }
+
+ await new content.Promise(resolve => {
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ if (block) {
+ ifr.contentWindow.postMessage(
+ { callback: params.msg.blockingCallback },
+ "*"
+ );
+ } else {
+ ifr.contentWindow.postMessage(
+ { callback: params.msg.nonBlockingCallback },
+ "*"
+ );
+ }
+ });
+ }
+ }
+ );
+}
+
+add_task(async function testPermissionGrantedOn3rdParty() {
+ info("Starting permission propagation test");
+
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ [
+ "privacy.restrict3rdpartystorage.userInteractionRequiredForHosts",
+ "tracking.example.com,tracking.example.org",
+ ],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ let msg = {};
+ msg.blockingCallback = (async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+
+ await new Promise(resolve => {
+ // eslint-disable-next-line no-undef
+ let w = worker;
+ w.addEventListener(
+ "message",
+ e => {
+ ok(!e.data, "IDB is disabled");
+ resolve();
+ },
+ { once: true }
+ );
+ w.postMessage("go");
+ });
+ }).toString();
+
+ msg.nonBlockingCallback = (async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ console.log("test hasStorageAccessInitially\n");
+ await hasStorageAccessInitially();
+
+ await new Promise(resolve => {
+ // eslint-disable-next-line no-undef
+ let w = worker;
+ w.addEventListener(
+ "message",
+ e => {
+ ok(e.data, "IDB is enabled");
+ resolve();
+ },
+ { once: true }
+ );
+ w.postMessage("go");
+ });
+ }).toString();
+
+ let top = TEST_TOP_PAGE;
+ let page = TEST_3RD_PARTY_PAGE_WORKER;
+ let pageOther =
+ TEST_ANOTHER_3RD_PARTY_DOMAIN + TEST_PATH + "3rdPartyWorker.html";
+ let params = { page, msg, pageOther };
+ // Create 4 tabs:
+ // 1. The first tab has two tracker iframes, said A & B.
+ // 2. The second tab is opened by the first tab, and it has one tracker iframe, said C.
+ // 3. The third tab has one tracker iframe, said D.
+ // 4. The fourth tab is opened by the first tab but with a different top-level url).
+ // The tab has one tracker iframe, said E.
+ //
+ // This test grants permission on iframe A, which then should propagate storage
+ // permission to iframe B & C, but not D, E
+
+ info("Creating the first tab");
+ let tab1 = await createTab(top, 2, null, params);
+ let browser1 = gBrowser.getBrowserForTab(tab1);
+
+ info("Creating the second tab");
+ let tab2 = await createTab(top, 1, browser1 /* opener */, params);
+ let browser2 = gBrowser.getBrowserForTab(tab2);
+
+ info("Creating the third tab");
+ let tab3 = await createTab(top, 1, null, params);
+ let browser3 = gBrowser.getBrowserForTab(tab3);
+
+ info("Creating the fourth tab");
+ let tab4 = await createTab(TEST_TOP_PAGE_2, 1, browser1, params);
+ let browser4 = gBrowser.getBrowserForTab(tab4);
+
+ info("Grant storage permission to the first iframe in the first tab");
+ await SpecialPowers.spawn(browser1, [page, msg], async function (page, msg) {
+ await new content.Promise(resolve => {
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ let ifr = content.document.getElementById("ifr0");
+ ifr.contentWindow.postMessage(
+ {
+ callback: (async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+ }).toString(),
+ },
+ "*"
+ );
+ });
+ });
+
+ info("Both iframs of the first tab should have stroage permission");
+ await testPermission(browser1, false /* block */, params);
+
+ info("The iframe of the second tab should have storage permission");
+ await testPermission(browser2, false /* block */, params);
+
+ info("The iframe of the third tab should not have storage permission");
+ await testPermission(browser3, true /* block */, params);
+
+ info("The iframe of the fourth tab should not have storage permission");
+ await testPermission(browser4, true /* block */, params);
+
+ info("Removing the tabs");
+ BrowserTestUtils.removeTab(tab1);
+ BrowserTestUtils.removeTab(tab2);
+ BrowserTestUtils.removeTab(tab3);
+ BrowserTestUtils.removeTab(tab4);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+});
+
+add_task(async function () {
+ info("Cleaning up.");
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
+
+add_task(async function testPermissionGrantedOnFirstParty() {
+ info("Starting permission propagation test");
+
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ let msg = {};
+ msg.blockingCallback = (async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+
+ await new Promise(resolve => {
+ // eslint-disable-next-line no-undef
+ let w = worker;
+ w.addEventListener(
+ "message",
+ e => {
+ ok(!e.data, "IDB is disabled");
+ resolve();
+ },
+ { once: true }
+ );
+ w.postMessage("go");
+ });
+ }).toString();
+
+ msg.nonBlockingCallback = (async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ console.log("test hasStorageAccessInitially\n");
+ await hasStorageAccessInitially();
+
+ await new Promise(resolve => {
+ // eslint-disable-next-line no-undef
+ let w = worker;
+ w.addEventListener(
+ "message",
+ e => {
+ ok(e.data, "IDB is enabled");
+ resolve();
+ },
+ { once: true }
+ );
+ w.postMessage("go");
+ });
+ }).toString();
+
+ let top = TEST_TOP_PAGE;
+ let page = TEST_3RD_PARTY_PAGE_WORKER;
+ let params = { page, msg };
+ // Create 4 tabs:
+ // 1. The first tab has two tracker iframes, said A & B.
+ // 2. The second tab is opened by the first tab, and it has one tracker iframe, said C.
+ // 3. The third tab has one tracker iframe, said D.
+ // 4. The fourth tab is opened by the first tab but with a different top-level url).
+ // The tab has one tracker iframe, said E.
+ //
+ // This test grants permission on iframe A, which then should propagate storage
+ // permission to iframe B & C, but not D, E
+
+ info("Creating the first tab");
+ let tab1 = await createTab(top, 2, null, params);
+ let browser1 = gBrowser.getBrowserForTab(tab1);
+
+ info("Creating the second tab");
+ let tab2 = await createTab(top, 1, browser1 /* opener */, params);
+ let browser2 = gBrowser.getBrowserForTab(tab2);
+
+ info("Creating the third tab");
+ let tab3 = await createTab(top, 1, null, params);
+ let browser3 = gBrowser.getBrowserForTab(tab3);
+
+ info("Creating the fourth tab");
+ let tab4 = await createTab(TEST_TOP_PAGE_2, 1, browser1, params);
+ let browser4 = gBrowser.getBrowserForTab(tab4);
+
+ info("Grant storage permission to the first iframe in the first tab");
+ let promise = BrowserTestUtils.waitForNewTab(gBrowser, page);
+ await SpecialPowers.spawn(browser1, [page], async function (page) {
+ content.window.open(page, "_blank");
+ });
+ let tab = await promise;
+ BrowserTestUtils.removeTab(tab);
+
+ info("Both iframs of the first tab should have stroage permission");
+ await testPermission(browser1, false /* block */, params);
+
+ info("The iframe of the second tab should have storage permission");
+ await testPermission(browser2, false /* block */, params);
+
+ info("The iframe of the third tab should not have storage permission");
+ await testPermission(browser3, true /* block */, params);
+
+ info("The iframe of the fourth tab should not have storage permission");
+ await testPermission(browser4, true /* block */, params);
+
+ info("Removing the tabs");
+ BrowserTestUtils.removeTab(tab1);
+ BrowserTestUtils.removeTab(tab2);
+ BrowserTestUtils.removeTab(tab3);
+ BrowserTestUtils.removeTab(tab4);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+});
+
+add_task(async function () {
+ info("Cleaning up.");
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_referrerDefaultPolicy.js b/toolkit/components/antitracking/test/browser/browser_referrerDefaultPolicy.js
new file mode 100644
index 0000000000..e9030b98e4
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_referrerDefaultPolicy.js
@@ -0,0 +1,634 @@
+"use strict";
+
+requestLongerTimeout(8);
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/browser/base/content/test/general/head.js",
+ this
+);
+
+async function openAWindow(usePrivate) {
+ info("Creating a new " + (usePrivate ? "private" : "normal") + " window");
+ let win = OpenBrowserWindow({ private: usePrivate });
+ await TestUtils.topicObserved(
+ "browser-delayed-startup-finished",
+ subject => subject == win
+ ).then(() => win);
+ await BrowserTestUtils.firstBrowserLoaded(win);
+ return win;
+}
+
+async function testOnWindowBody(win, expectedReferrer, rp) {
+ let browser = win.gBrowser;
+ let tab = browser.selectedTab;
+ let b = browser.getBrowserForTab(tab);
+ await promiseTabLoadEvent(tab, TEST_TOP_PAGE);
+
+ info("Loading tracking scripts and tracking images");
+ let referrer = await SpecialPowers.spawn(
+ b,
+ [{ rp }],
+ async function ({ rp }) {
+ {
+ let src = content.document.createElement("script");
+ let p = new content.Promise(resolve => {
+ src.onload = resolve;
+ });
+ content.document.body.appendChild(src);
+ if (rp) {
+ src.referrerPolicy = rp;
+ }
+ src.src =
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/referrer.sjs?what=script";
+ await p;
+ }
+
+ {
+ let img = content.document.createElement("img");
+ let p = new content.Promise(resolve => {
+ img.onload = resolve;
+ });
+ content.document.body.appendChild(img);
+ if (rp) {
+ img.referrerPolicy = rp;
+ }
+ img.src =
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/referrer.sjs?what=image";
+ await p;
+ }
+
+ {
+ let iframe = content.document.createElement("iframe");
+ let p = new content.Promise(resolve => {
+ iframe.onload = resolve;
+ });
+ content.document.body.appendChild(iframe);
+ if (rp) {
+ iframe.referrerPolicy = rp;
+ }
+ iframe.src =
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/referrer.sjs?what=iframe";
+ await p;
+
+ p = new content.Promise(resolve => {
+ content.onmessage = event => {
+ resolve(event.data);
+ };
+ });
+ iframe.contentWindow.postMessage("ping", "*");
+ return p;
+ }
+ }
+ );
+
+ is(referrer, expectedReferrer, "The correct referrer must be read from DOM");
+
+ await fetch(
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/referrer.sjs?result&what=script"
+ )
+ .then(r => r.text())
+ .then(text => {
+ is(text, expectedReferrer, "We sent the correct Referer header");
+ });
+
+ await fetch(
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/referrer.sjs?result&what=image"
+ )
+ .then(r => r.text())
+ .then(text => {
+ is(text, expectedReferrer, "We sent the correct Referer header");
+ });
+
+ await fetch(
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/referrer.sjs?result&what=iframe"
+ )
+ .then(r => r.text())
+ .then(text => {
+ is(text, expectedReferrer, "We sent the correct Referer header");
+ });
+}
+
+async function closeAWindow(win) {
+ await BrowserTestUtils.closeWindow(win);
+}
+
+let gRecording = true;
+let gScenarios = [];
+let gRPs = [];
+let gTests = { private: [], nonPrivate: [] };
+const kPBPref = "network.http.referer.defaultPolicy.trackers.pbmode";
+const kNonPBPref = "network.http.referer.defaultPolicy.trackers";
+
+function recordScenario(isPrivate, expectedReferrer, rp) {
+ if (!gRPs.includes(rp)) {
+ gRPs.push(rp);
+ }
+ gScenarios.push({
+ private: isPrivate,
+ expectedReferrer,
+ rp,
+ pbPref: Services.prefs.getIntPref(kPBPref),
+ nonPBPref: Services.prefs.getIntPref(kNonPBPref),
+ });
+}
+
+async function testOnWindow(isPrivate, expectedReferrer, rp) {
+ if (gRecording) {
+ recordScenario(isPrivate, expectedReferrer, rp);
+ }
+}
+
+function compileScenarios() {
+ let keys = { false: [], true: [] };
+ for (let s of gScenarios) {
+ let key = {
+ rp: s.rp,
+ pbPref: s.pbPref,
+ nonPBPref: s.nonPBPref,
+ };
+ let skip = false;
+ for (let k of keys[s.private]) {
+ if (
+ key.rp == k.rp &&
+ key.pbPref == k.pbPref &&
+ key.nonPBPref == k.nonPBPref
+ ) {
+ skip = true;
+ break;
+ }
+ }
+ if (!skip) {
+ keys[s.private].push(key);
+ gTests[s.private ? "private" : "nonPrivate"].push({
+ rp: s.rp,
+ pbPref: s.pbPref,
+ nonPBPref: s.nonPBPref,
+ expectedReferrer: s.expectedReferrer,
+ });
+ }
+ }
+
+ // Verify that all scenarios are checked
+ let counter = 1;
+ for (let s of gScenarios) {
+ let checked = false;
+ for (let tt in gTests) {
+ let isPrivate = tt == "private";
+ for (let t of gTests[tt]) {
+ if (
+ isPrivate == s.private &&
+ t.rp == s.rp &&
+ t.pbPref == s.pbPref &&
+ t.nonPBPref == s.nonPBPref &&
+ t.expectedReferrer == s.expectedReferrer
+ ) {
+ checked = true;
+ break;
+ }
+ }
+ }
+ ok(checked, `Scenario number ${counter++} checked`);
+ }
+}
+
+async function executeTests() {
+ compileScenarios();
+
+ gRecording = false;
+ for (let mode in gTests) {
+ info(`Open a ${mode} window`);
+ while (gTests[mode].length) {
+ let test = gTests[mode].shift();
+ info(`Running test ${test.toSource()}`);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["network.http.referer.defaultPolicy.trackers", test.nonPBPref],
+ ["network.http.referer.defaultPolicy.trackers.pbmode", test.pbPref],
+ ["dom.security.https_first_pbm", false],
+ ["network.http.referer.disallowCrossSiteRelaxingDefault", false],
+ ],
+ });
+
+ let win = await openAWindow(mode == "private");
+
+ await testOnWindowBody(win, test.expectedReferrer, test.rp);
+
+ await closeAWindow(win);
+ }
+ }
+
+ Services.prefs.clearUserPref(kPBPref);
+ Services.prefs.clearUserPref(kNonPBPref);
+}
+
+function pn(name, isPrivate) {
+ return isPrivate ? name + ".pbmode" : name;
+}
+
+async function testOnNoReferrer(isPrivate) {
+ // no-referrer pref when no-referrer is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 3]],
+ });
+ await testOnWindow(isPrivate, "", "no-referrer");
+
+ // strict-origin-when-cross-origin pref when no-referrer is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 2]],
+ });
+ await testOnWindow(isPrivate, "", "no-referrer");
+
+ // same-origin pref when no-referrer is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 1]],
+ });
+ await testOnWindow(isPrivate, "", "no-referrer");
+
+ // no-referrer pref when no-referrer is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 0]],
+ });
+ await testOnWindow(isPrivate, "", "no-referrer");
+}
+
+async function testOnSameOrigin(isPrivate) {
+ // same-origin pref when same-origin is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 3]],
+ });
+ await testOnWindow(isPrivate, "", "same-origin");
+
+ // strict-origin-when-cross-origin pref when same-origin is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 2]],
+ });
+ await testOnWindow(isPrivate, "", "same-origin");
+
+ // same-origin pref when same-origin is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 1]],
+ });
+ await testOnWindow(isPrivate, "", "same-origin");
+
+ // same-origin pref when same-origin is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 0]],
+ });
+ await testOnWindow(isPrivate, "", "same-origin");
+}
+
+async function testOnNoReferrerWhenDowngrade(isPrivate) {
+ // The setting referrer policy will be ignored if it is
+ // no-referrer-when-downgrade in private mode. It will fallback to the default
+ // value.
+ //
+ // The pref 'network.http.referer.disallowCrossSiteRelaxingDefault.pbmode'
+ // controls this behavior in private mode.
+
+ // no-referrer-when-downgrade pref when no-referrer-when-downgrade is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 3]],
+ });
+ await testOnWindow(isPrivate, TEST_TOP_PAGE, "no-referrer-when-downgrade");
+
+ // strict-origin-when-cross-origin pref when no-referrer-when-downgrade is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 2]],
+ });
+ if (isPrivate) {
+ await testOnWindow(isPrivate, TEST_DOMAIN, "no-referrer-when-downgrade");
+ } else {
+ await testOnWindow(isPrivate, TEST_TOP_PAGE, "no-referrer-when-downgrade");
+ }
+
+ // same-origin pref when no-referrer-when-downgrade is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 1]],
+ });
+ if (isPrivate) {
+ await testOnWindow(isPrivate, "", "no-referrer-when-downgrade");
+ } else {
+ await testOnWindow(isPrivate, TEST_TOP_PAGE, "no-referrer-when-downgrade");
+ }
+
+ // no-referrer pref when no-referrer-when-downgrade is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 0]],
+ });
+ if (isPrivate) {
+ await testOnWindow(isPrivate, "", "no-referrer-when-downgrade");
+ } else {
+ await testOnWindow(isPrivate, TEST_TOP_PAGE, "no-referrer-when-downgrade");
+ }
+}
+
+async function testOnOrigin(isPrivate) {
+ // origin pref when origin is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 3]],
+ });
+ await testOnWindow(isPrivate, TEST_DOMAIN, "origin");
+
+ // strict-origin pref when origin is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 2]],
+ });
+ await testOnWindow(isPrivate, TEST_DOMAIN, "origin");
+
+ // same-origin pref when origin is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 1]],
+ });
+ await testOnWindow(isPrivate, TEST_DOMAIN, "origin");
+
+ // no-referrer pref when origin is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 0]],
+ });
+ await testOnWindow(isPrivate, TEST_DOMAIN, "origin");
+}
+
+async function testOnStrictOrigin(isPrivate) {
+ // strict-origin pref when strict-origin is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 3]],
+ });
+ await testOnWindow(isPrivate, TEST_DOMAIN, "strict-origin");
+
+ // strict-origin pref when strict-origin is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 2]],
+ });
+ await testOnWindow(isPrivate, TEST_DOMAIN, "strict-origin");
+
+ // same-origin pref when strict-origin is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 1]],
+ });
+ await testOnWindow(isPrivate, TEST_DOMAIN, "strict-origin");
+
+ // no-referrer pref when strict-origin is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 0]],
+ });
+ await testOnWindow(isPrivate, TEST_DOMAIN, "strict-origin");
+}
+
+async function testOnOriginWhenCrossOrigin(isPrivate) {
+ // The setting referrer policy will be ignored if it is
+ // origin-when-cross-origin in private mode. It will fallback to the default
+ // value. The pref controls this behavior mentioned above.
+
+ // no-referrer-when-downgrade pref when origin-when-cross-origin is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 3]],
+ });
+ if (isPrivate) {
+ await testOnWindow(isPrivate, TEST_TOP_PAGE, "origin-when-cross-origin");
+ } else {
+ await testOnWindow(isPrivate, TEST_DOMAIN, "origin-when-cross-origin");
+ }
+
+ // strict-origin-when-cross-origin pref when origin-when-cross-origin is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 2]],
+ });
+ await testOnWindow(isPrivate, TEST_DOMAIN, "origin-when-cross-origin");
+
+ // same-origin pref when origin-when-cross-origin is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 1]],
+ });
+ if (isPrivate) {
+ await testOnWindow(isPrivate, "", "origin-when-cross-origin");
+ } else {
+ await testOnWindow(isPrivate, TEST_DOMAIN, "origin-when-cross-origin");
+ }
+
+ // no-referrer pref when origin-when-cross-origin is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 0]],
+ });
+ if (isPrivate) {
+ await testOnWindow(isPrivate, "", "origin-when-cross-origin");
+ } else {
+ await testOnWindow(isPrivate, TEST_DOMAIN, "origin-when-cross-origin");
+ }
+}
+
+async function testOnStrictOriginWhenCrossOrigin(isPrivate) {
+ // origin-when-cross-origin pref when strict-origin-when-cross-origin is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 3]],
+ });
+ await testOnWindow(isPrivate, TEST_DOMAIN, "strict-origin-when-cross-origin");
+
+ // strict-origin-when-cross-origin pref when strict-origin-when-cross-origin is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 2]],
+ });
+ await testOnWindow(isPrivate, TEST_DOMAIN, "strict-origin-when-cross-origin");
+
+ // same-origin pref when strict-origin-when-cross-origin is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 1]],
+ });
+ await testOnWindow(isPrivate, TEST_DOMAIN, "strict-origin-when-cross-origin");
+
+ // no-referrer pref when strict-origin-when-cross-origin is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 0]],
+ });
+ await testOnWindow(isPrivate, TEST_DOMAIN, "strict-origin-when-cross-origin");
+}
+
+async function testOnUnsafeUrl(isPrivate) {
+ // The setting referrer policy will be ignored if it is unsafe in private
+ // mode. It will fallback to the default value. The pref controls this
+ // behavior mentioned above.
+
+ // no-referrer-when-downgrade pref when unsafe-url is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 3]],
+ });
+ await testOnWindow(isPrivate, TEST_TOP_PAGE, "unsafe-url");
+
+ // strict-origin-when-cross-origin pref when unsafe-url is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 2]],
+ });
+ if (isPrivate) {
+ await testOnWindow(isPrivate, TEST_DOMAIN, "unsafe-url");
+ } else {
+ await testOnWindow(isPrivate, TEST_TOP_PAGE, "unsafe-url");
+ }
+
+ // same-origin pref when unsafe-url is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 1]],
+ });
+ if (isPrivate) {
+ await testOnWindow(isPrivate, "", "unsafe-url");
+ } else {
+ await testOnWindow(isPrivate, TEST_TOP_PAGE, "unsafe-url");
+ }
+
+ // no-referrer pref when unsafe-url is forced
+ await SpecialPowers.pushPrefEnv({
+ set: [[pn("network.http.referer.defaultPolicy.trackers", isPrivate), 0]],
+ });
+ if (isPrivate) {
+ await testOnWindow(isPrivate, "", "unsafe-url");
+ } else {
+ await testOnWindow(isPrivate, TEST_TOP_PAGE, "unsafe-url");
+ }
+}
+
+add_task(async function () {
+ info("Starting referrer default policy test");
+
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ ["network.http.referer.defaultPolicy", 3],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ ],
+ });
+
+ // no-referrer-when-downgrade
+ await SpecialPowers.pushPrefEnv({
+ set: [["network.http.referer.defaultPolicy.trackers", 3]],
+ });
+ await testOnWindow(false, TEST_TOP_PAGE, null);
+
+ // strict-origin-when-cross-origin
+ await SpecialPowers.pushPrefEnv({
+ set: [["network.http.referer.defaultPolicy.trackers", 2]],
+ });
+ await testOnWindow(false, TEST_DOMAIN, null);
+
+ // same-origin
+ await SpecialPowers.pushPrefEnv({
+ set: [["network.http.referer.defaultPolicy.trackers", 1]],
+ });
+ await testOnWindow(false, "", null);
+
+ // no-referrer
+ await SpecialPowers.pushPrefEnv({
+ set: [["network.http.referer.defaultPolicy.trackers", 0]],
+ });
+ await testOnWindow(false, "", null);
+
+ // override with no-referrer
+ await testOnNoReferrer(false);
+
+ // override with same-origin
+ await testOnSameOrigin(false);
+
+ // override with no-referrer-when-downgrade
+ await testOnNoReferrerWhenDowngrade(false);
+
+ // override with origin
+ await testOnOrigin(false);
+
+ // override with strict-origin
+ await testOnStrictOrigin(false);
+
+ // override with origin-when-cross-origin
+ await testOnOriginWhenCrossOrigin(false);
+
+ // override with strict-origin-when-cross-origin
+ await testOnStrictOriginWhenCrossOrigin(false);
+
+ // override with unsafe-url
+ await testOnUnsafeUrl(false);
+
+ // Reset the pref.
+ Services.prefs.clearUserPref("network.http.referer.defaultPolicy.trackers");
+
+ // no-referrer-when-downgrade
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Set both prefs, because if we only set the trackers pref, then the PB
+ // mode default policy pref (2) would apply!
+ ["network.http.referer.defaultPolicy.pbmode", 3],
+ ["network.http.referer.defaultPolicy.trackers.pbmode", 3],
+ ],
+ });
+ await testOnWindow(true, TEST_TOP_PAGE, null);
+
+ // strict-origin-when-cross-origin
+ await SpecialPowers.pushPrefEnv({
+ set: [["network.http.referer.defaultPolicy.trackers.pbmode", 2]],
+ });
+ await testOnWindow(true, TEST_DOMAIN, null);
+
+ // same-origin
+ await SpecialPowers.pushPrefEnv({
+ set: [["network.http.referer.defaultPolicy.trackers.pbmode", 1]],
+ });
+ await testOnWindow(true, "", null);
+
+ // no-referrer
+ await SpecialPowers.pushPrefEnv({
+ set: [["network.http.referer.defaultPolicy.trackers.pbmode", 0]],
+ });
+ await testOnWindow(true, "", null);
+
+ // override with no-referrer
+ await testOnNoReferrer(true);
+
+ // override with same-origin
+ await testOnSameOrigin(true);
+
+ // override with no-referrer-when-downgrade
+ await testOnNoReferrerWhenDowngrade(true);
+
+ // override with origin
+ await testOnOrigin(true);
+
+ // override with strict-origin
+ await testOnStrictOrigin(true);
+
+ // override with origin-when-cross-origin
+ await testOnOriginWhenCrossOrigin(true);
+
+ // override with strict-origin-when-cross-origin
+ await testOnStrictOriginWhenCrossOrigin(true);
+
+ // override with unsafe-url
+ await testOnUnsafeUrl(true);
+
+ // Reset the pref.
+ Services.prefs.clearUserPref(
+ "network.http.referer.defaultPolicy.trackers.pbmode"
+ );
+});
+
+add_task(async function () {
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ await executeTests();
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+});
+
+add_task(async function () {
+ info("Cleaning up.");
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_script.js b/toolkit/components/antitracking/test/browser/browser_script.js
new file mode 100644
index 0000000000..ef6c7f67c6
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_script.js
@@ -0,0 +1,224 @@
+add_task(async function () {
+ info("Starting subResources test");
+
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ [
+ "privacy.restrict3rdpartystorage.userInteractionRequiredForHosts",
+ "tracking.example.com,tracking.example.org",
+ ],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Loading tracking scripts");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ scriptURL: TEST_DOMAIN + TEST_PATH + "tracker.js",
+ page: TEST_3RD_PARTY_PAGE,
+ },
+ ],
+ async obj => {
+ info("Checking if permission is denied");
+ let callbackBlocked = async _ => {
+ try {
+ localStorage.foo = 42;
+ ok(false, "LocalStorage cannot be used!");
+ } catch (e) {
+ ok(true, "LocalStorage cannot be used!");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+ };
+
+ let assertBlocked = () =>
+ new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.onload = function () {
+ info("Sending code to the 3rd party content");
+ ifr.contentWindow.postMessage(callbackBlocked.toString(), "*");
+ };
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ });
+
+ await assertBlocked();
+
+ info("Triggering a 3rd party script...");
+ let p = new content.Promise(resolve => {
+ let bc = new content.BroadcastChannel("a");
+ bc.onmessage = resolve;
+ });
+
+ let src = content.document.createElement("script");
+ content.document.body.appendChild(src);
+ src.src = obj.scriptURL;
+
+ await p;
+
+ info("Checking if permission is denied before interacting with tracker");
+ await assertBlocked();
+ }
+ );
+
+ await AntiTracking.interactWithTracker();
+
+ info("Loading tracking scripts");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ scriptURL: TEST_DOMAIN + TEST_PATH + "tracker.js",
+ page: TEST_3RD_PARTY_PAGE,
+ },
+ ],
+ async obj => {
+ info("Checking if permission is denied");
+ let callbackBlocked = async _ => {
+ try {
+ localStorage.foo = 42;
+ ok(false, "LocalStorage cannot be used!");
+ } catch (e) {
+ ok(true, "LocalStorage cannot be used!");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+ };
+
+ await new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.onload = function () {
+ info("Sending code to the 3rd party content");
+ ifr.contentWindow.postMessage(callbackBlocked.toString(), "*");
+ };
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ });
+
+ info("Triggering a 3rd party script...");
+ let p = new content.Promise(resolve => {
+ let bc = new content.BroadcastChannel("a");
+ bc.onmessage = resolve;
+ });
+
+ let src = content.document.createElement("script");
+ content.document.body.appendChild(src);
+ src.src = obj.scriptURL;
+
+ await p;
+
+ info("Checking if permission is granted");
+ let callbackAllowed = async _ => {
+ localStorage.foo = 42;
+ ok(true, "LocalStorage can be used!");
+ };
+
+ await new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.onload = function () {
+ info("Sending code to the 3rd party content");
+ ifr.contentWindow.postMessage(callbackAllowed.toString(), "*");
+ };
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ });
+ }
+ );
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+});
+
+add_task(async function () {
+ info("Cleaning up.");
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_serviceWorkersWithStorageAccessGranted.js b/toolkit/components/antitracking/test/browser/browser_serviceWorkersWithStorageAccessGranted.js
new file mode 100644
index 0000000000..4ae4ba74dd
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_serviceWorkersWithStorageAccessGranted.js
@@ -0,0 +1,148 @@
+/** This tests that the service worker can be used if we have storage access
+ * permission. We manually write the storage access permission into the
+ * permission manager to simulate the storage access has been granted. We would
+ * test the service worker three times. The fist time is to check the service
+ * work is allowed. The second time is to load again and check it won't hit
+ * assertion, this assertion would only be hit if we have registered a service
+ * worker, see Bug 1631234.
+ *
+ * The third time is to load again but in a sandbox iframe to check it won't
+ * hit the assertion. See Bug 1637226 for details.
+ *
+ * The fourth time is to load again in a nested iframe to check it won't hit
+ * the assertion. See Bug 1641153 for details.
+ * */
+
+add_task(async _ => {
+ // Manually add the storage permission.
+ PermissionTestUtils.add(
+ TEST_DOMAIN,
+ "3rdPartyStorage^https://tracking.example.org",
+ Services.perms.ALLOW_ACTION
+ );
+
+ registerCleanupFunction(_ => {
+ Services.perms.removeAll();
+ });
+
+ AntiTracking._createTask({
+ name: "Test that we can use service worker if we have the storage access permission",
+ cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+ allowList: false,
+ callback: async _ => {
+ await navigator.serviceWorker
+ .register("empty.js")
+ .then(
+ _ => {
+ ok(true, "ServiceWorker can be used!");
+ },
+ _ => {
+ ok(false, "ServiceWorker can be used!");
+ }
+ )
+ .catch(e => ok(false, "Promise rejected: " + e));
+ },
+ extraPrefs: [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ],
+ expectedBlockingNotifications: 0,
+ runInPrivateWindow: false,
+ iframeSandbox: null,
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+ });
+
+ AntiTracking._createTask({
+ name: "Test again to check if we can still use service worker without hit the assertion.",
+ cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+ allowList: false,
+ callback: async _ => {
+ await navigator.serviceWorker
+ .register("empty.js")
+ .then(
+ _ => {
+ ok(true, "ServiceWorker can be used!");
+ },
+ _ => {
+ ok(false, "ServiceWorker can be used!");
+ }
+ )
+ .catch(e => ok(false, "Promise rejected: " + e));
+ },
+ extraPrefs: [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ],
+ expectedBlockingNotifications: 0,
+ runInPrivateWindow: false,
+ iframeSandbox: null,
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+ });
+
+ AntiTracking._createTask({
+ name: "Test again to check if we cannot use service worker in a sandbox iframe without hit the assertion.",
+ cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+ allowList: false,
+ callback: async _ => {
+ await navigator.serviceWorker
+ .register("empty.js")
+ .then(
+ _ => {
+ ok(false, "ServiceWorker cannot be used in sandbox iframe!");
+ },
+ _ => {
+ ok(true, "ServiceWorker cannot be used in sandbox iframe!");
+ }
+ )
+ .catch(e => ok(false, "Promise rejected: " + e));
+ },
+ extraPrefs: [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ],
+ expectedBlockingNotifications:
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER, // expect blocking notifications,
+ runInPrivateWindow: false,
+ iframeSandbox: "allow-scripts allow-same-origin",
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+ });
+
+ const NESTED_THIRD_PARTY_PAGE =
+ TEST_DOMAIN + TEST_PATH + "3rdPartyRelay.html?" + TEST_3RD_PARTY_PAGE;
+
+ AntiTracking._createTask({
+ name: "Test again to check if we can use service worker in a nested iframe without hit the assertion.",
+ cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+ allowList: false,
+ callback: async _ => {
+ await navigator.serviceWorker
+ .register("empty.js")
+ .then(
+ _ => {
+ ok(true, "ServiceWorker can be used in nested iframe!");
+ },
+ _ => {
+ ok(false, "ServiceWorker can be used in nested iframe!");
+ }
+ )
+ .catch(e => ok(false, "Promise rejected: " + e));
+ },
+ extraPrefs: [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ],
+ expectedBlockingNotifications: 0,
+ runInPrivateWindow: false,
+ iframeSandbox: null,
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+ thirdPartyPage: NESTED_THIRD_PARTY_PAGE,
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_siteSpecificWorkArounds.js b/toolkit/components/antitracking/test/browser/browser_siteSpecificWorkArounds.js
new file mode 100644
index 0000000000..b35fbea8c7
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_siteSpecificWorkArounds.js
@@ -0,0 +1,153 @@
+AntiTracking.runTest(
+ "localStorage with a tracker that is entitylisted via a pref",
+ async _ => {
+ let effectiveCookieBehavior = SpecialPowers.isContentWindowPrivate(window)
+ ? SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior.pbmode"
+ )
+ : SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior"
+ );
+
+ let shouldThrow = [
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT,
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN,
+ ].includes(effectiveCookieBehavior);
+
+ let hasThrown;
+ try {
+ localStorage.foo = 42;
+ hasThrown = false;
+ } catch (e) {
+ hasThrown = true;
+ }
+
+ is(hasThrown, shouldThrow, "LocalStorage is allowed");
+ },
+ async _ => {
+ localStorage.foo = 42;
+ ok(true, "LocalStorage is allowed");
+ },
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [["urlclassifier.trackingAnnotationSkipURLs", "TRACKING.EXAMPLE.ORG"]],
+ false, // run the window.open() test
+ false, // run the user interaction test
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER, // expect blocking notifications
+ false
+); // run in a normal window
+
+AntiTracking.runTest(
+ "localStorage with a tracker that is entitylisted via a fancy pref",
+ async _ => {
+ let effectiveCookieBehavior = SpecialPowers.isContentWindowPrivate(window)
+ ? SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior.pbmode"
+ )
+ : SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior"
+ );
+
+ let shouldThrow = [
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT,
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN,
+ ].includes(effectiveCookieBehavior);
+
+ let hasThrown;
+ try {
+ localStorage.foo = 42;
+ hasThrown = false;
+ } catch (e) {
+ hasThrown = true;
+ }
+
+ is(hasThrown, shouldThrow, "LocalStorage is allowed");
+ },
+ async _ => {
+ localStorage.foo = 42;
+ ok(true, "LocalStorage is allowed");
+ },
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [
+ [
+ "urlclassifier.trackingAnnotationSkipURLs",
+ "foobar.example,*.example.org,baz.example",
+ ],
+ ],
+ false, // run the window.open() test
+ false, // run the user interaction test
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER, // expect blocking notifications
+ false
+); // run in a normal window
+
+AntiTracking.runTest(
+ "localStorage with a tracker that is entitylisted via a misconfigured pref",
+ async _ => {
+ try {
+ localStorage.foo = 42;
+ ok(false, "LocalStorage cannot be used!");
+ } catch (e) {
+ ok(true, "LocalStorage cannot be used!");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+ },
+ async _ => {
+ localStorage.foo = 42;
+ ok(true, "LocalStorage is allowed");
+ },
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [["urlclassifier.trackingAnnotationSkipURLs", "*.tracking.example.org"]],
+ false, // run the window.open() test
+ false, // run the user interaction test
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER, // expect blocking notifications
+ false
+); // run in a normal window
+
+AntiTracking.runTest(
+ "localStorage with a tracker that is entitylisted via a pref, but skip lists are disabled.",
+ async _ => {
+ try {
+ localStorage.foo = 42;
+ ok(false, "LocalStorage cannot be used!");
+ } catch (e) {
+ ok(true, "LocalStorage cannot be used!");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+ },
+ async _ => {
+ localStorage.foo = 42;
+ ok(true, "LocalStorage is allowed");
+ },
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [
+ ["urlclassifier.trackingAnnotationSkipURLs", "TRACKING.EXAMPLE.ORG"],
+ ["privacy.antitracking.enableWebcompat", false],
+ ],
+ false, // run the window.open() test
+ false, // run the user interaction test
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER, // expect blocking notifications
+ false
+); // run in a normal window
diff --git a/toolkit/components/antitracking/test/browser/browser_socialtracking.js b/toolkit/components/antitracking/test/browser/browser_socialtracking.js
new file mode 100644
index 0000000000..1cbf5d9278
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_socialtracking.js
@@ -0,0 +1,147 @@
+function runTest(obj) {
+ add_task(async _ => {
+ info("Test: " + obj.testName);
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.ipc.processCount", 1],
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ [
+ "privacy.trackingprotection.socialtracking.enabled",
+ obj.protectionEnabled,
+ ],
+ ["privacy.socialtracking.block_cookies.enabled", obj.cookieBlocking],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ info("Creating a non-tracker top-level context");
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("The non-tracker page opens a tracker iframe");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: TEST_3RD_PARTY_DOMAIN_STP + TEST_PATH + "localStorage.html",
+ image: TEST_3RD_PARTY_DOMAIN_STP + TEST_PATH + "raptor.jpg",
+ loading: obj.loading,
+ result: obj.result,
+ },
+ ],
+ async obj => {
+ let loading = await new content.Promise(resolve => {
+ let image = new content.Image();
+ image.src = obj.image + "?" + Math.random();
+ image.onload = _ => resolve(true);
+ image.onerror = _ => resolve(false);
+ });
+
+ is(loading, obj.loading, "Loading expected");
+
+ if (obj.loading) {
+ let ifr = content.document.createElement("iframe");
+ ifr.setAttribute("id", "ifr");
+ ifr.setAttribute("src", obj.page);
+
+ info("Iframe loading...");
+ await new content.Promise(resolve => {
+ ifr.onload = resolve;
+ content.document.body.appendChild(ifr);
+ });
+
+ let p = new Promise(resolve => {
+ content.addEventListener(
+ "message",
+ e => {
+ resolve(e.data);
+ },
+ { once: true }
+ );
+ });
+
+ info("Setting localStorage value...");
+ ifr.contentWindow.postMessage("test", "*");
+
+ info("Getting the value...");
+ let value = await p;
+ is(value.status, obj.result, "We expect to succeed");
+ }
+ }
+ );
+
+ info("Checking content blocking log.");
+ let contentBlockingLog = JSON.parse(await browser.getContentBlockingLog());
+ let origins = Object.keys(contentBlockingLog);
+ is(origins.length, 1, "There should be one origin entry in the log.");
+ for (let origin of origins) {
+ is(
+ origin + "/",
+ TEST_3RD_PARTY_DOMAIN_STP,
+ "Correct tracker origin must be reported"
+ );
+ Assert.deepEqual(
+ contentBlockingLog[origin],
+ obj.expectedLogItems,
+ "Content blocking log should be as expected"
+ );
+ }
+
+ BrowserTestUtils.removeTab(tab);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ });
+}
+
+runTest({
+ testName:
+ "Socialtracking-annotation feature enabled but not considered for tracking detection.",
+ protectionEnabled: false,
+ loading: true,
+ cookieBlocking: false,
+ result: true,
+ expectedLogItems: [
+ [Ci.nsIWebProgressListener.STATE_COOKIES_LOADED, true, 1],
+ [Ci.nsIWebProgressListener.STATE_COOKIES_LOADED_SOCIALTRACKER, true, 1],
+ ],
+});
+
+runTest({
+ testName:
+ "Socialtracking-annotation feature enabled and considered for tracking detection.",
+ protectionEnabled: false,
+ loading: true,
+ cookieBlocking: true,
+ result: false,
+ expectedLogItems: [
+ [Ci.nsIWebProgressListener.STATE_COOKIES_LOADED, true, 1],
+ [Ci.nsIWebProgressListener.STATE_COOKIES_LOADED_SOCIALTRACKER, true, 1],
+ [Ci.nsIWebProgressListener.STATE_LOADED_SOCIALTRACKING_CONTENT, true, 2],
+ // We cache the storage allowed decision, so we will only get one block
+ // event per window and origin.
+ [Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_SOCIALTRACKER, true, 1],
+ ],
+});
+
+runTest({
+ testName: "Socialtracking-protection feature enabled.",
+ protectionEnabled: true,
+ loading: false,
+ cookieBlocking: true,
+ result: false,
+ expectedLogItems: [
+ [Ci.nsIWebProgressListener.STATE_BLOCKED_SOCIALTRACKING_CONTENT, true, 1],
+ ],
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_socialtracking_save_image.js b/toolkit/components/antitracking/test/browser/browser_socialtracking_save_image.js
new file mode 100644
index 0000000000..e7389b1456
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_socialtracking_save_image.js
@@ -0,0 +1,114 @@
+/**
+ * Bug 1663992 - Testing the 'Save Image As' works in an image document if the
+ * image is blocked by social tracker.
+ */
+"use strict";
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
+ this
+);
+
+const TEST_IMAGE_URL =
+ "http://social-tracking.example.org/browser/toolkit/components/antitracking/test/browser/raptor.jpg";
+
+let MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+
+const tempDir = createTemporarySaveDirectory();
+MockFilePicker.displayDirectory = tempDir;
+
+function createTemporarySaveDirectory() {
+ let saveDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ saveDir.append("testsavedir");
+ saveDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ return saveDir;
+}
+
+function createPromiseForTransferComplete() {
+ return new Promise(resolve => {
+ MockFilePicker.showCallback = fp => {
+ info("MockFilePicker showCallback");
+
+ let fileName = fp.defaultString;
+ let destFile = tempDir.clone();
+ destFile.append(fileName);
+
+ MockFilePicker.setFiles([destFile]);
+ MockFilePicker.filterIndex = 0; // kSaveAsType_Complete
+
+ MockFilePicker.showCallback = null;
+ mockTransferCallback = function (downloadSuccess) {
+ ok(downloadSuccess, "Image should have been downloaded successfully");
+ mockTransferCallback = () => {};
+ resolve();
+ };
+ };
+ });
+}
+
+add_setup(async function () {
+ info("Setting up the prefs.");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.trackingprotection.socialtracking.enabled", true],
+ [
+ "urlclassifier.features.socialtracking.blacklistHosts",
+ "social-tracking.example.org",
+ ],
+ ],
+ });
+
+ info("Setting MockFilePicker.");
+ mockTransferRegisterer.register();
+
+ registerCleanupFunction(function () {
+ mockTransferRegisterer.unregister();
+ MockFilePicker.cleanup();
+ tempDir.remove(true);
+ });
+});
+
+add_task(async function () {
+ info("Open a new tab for testing");
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_IMAGE_URL
+ );
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown");
+
+ let browser = gBrowser.selectedBrowser;
+
+ info("Open the context menu.");
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "img",
+ {
+ type: "contextmenu",
+ button: 2,
+ },
+ browser
+ );
+
+ await popupShownPromise;
+
+ let transferCompletePromise = createPromiseForTransferComplete();
+ let saveElement = document.getElementById(`context-saveimage`);
+ info("Triggering the save process.");
+ saveElement.doCommand();
+
+ info("Wait until the save is finished.");
+ await transferCompletePromise;
+
+ info("Close the context menu.");
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popuphidden"
+ );
+ contextMenu.hidePopup();
+ await popupHiddenPromise;
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_staticPartition_CORS_preflight.js b/toolkit/components/antitracking/test/browser/browser_staticPartition_CORS_preflight.js
new file mode 100644
index 0000000000..5c232fabc2
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_staticPartition_CORS_preflight.js
@@ -0,0 +1,149 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const TEST_PAGE =
+ "http://example.org/browser/toolkit/components/antitracking/test/browser/empty.html";
+const TEST_ANOTHER_PAGE =
+ "http://example.com/browser/toolkit/components/antitracking/test/browser/empty.html";
+const TEST_PREFLIGHT_IFRAME_PAGE =
+ "http://mochi.test:8888/browser/toolkit/components/antitracking/test/browser/empty.html";
+const TEST_PREFLIGHT_PAGE =
+ "http://example.net/browser/toolkit/components/antitracking/test/browser/browser_staticPartition_CORS_preflight.sjs";
+
+add_task(async function () {
+ let uuidGenerator = Services.uuid;
+
+ for (let networkIsolation of [true, false]) {
+ for (let partitionPerSite of [true, false]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.partition.network_state", networkIsolation],
+ ["privacy.dynamic_firstparty.use_site", partitionPerSite],
+ ],
+ });
+
+ // First, create one tab under one first party.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_PAGE
+ );
+ let token = uuidGenerator.generateUUID().toString();
+
+ // Use fetch to verify that preflight cache is working. The preflight
+ // cache is keyed by the loading principal and the url. So, we load an
+ // iframe with one origin and use fetch in there to ensure we will have
+ // the same loading principal.
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [TEST_PREFLIGHT_PAGE, TEST_PREFLIGHT_IFRAME_PAGE, token],
+ async (url, iframe_url, token) => {
+ let iframe = content.document.createElement("iframe");
+
+ await new Promise(resolve => {
+ iframe.onload = () => {
+ resolve();
+ };
+ content.document.body.appendChild(iframe);
+ iframe.src = iframe_url;
+ });
+
+ await SpecialPowers.spawn(
+ iframe,
+ [url, token],
+ async (url, token) => {
+ const test_url = `${url}?token=${token}`;
+ let response = await content.fetch(
+ new content.Request(test_url, {
+ mode: "cors",
+ method: "GET",
+ headers: [["x-test-header", "check"]],
+ })
+ );
+
+ is(
+ await response.text(),
+ "1",
+ "The preflight should be sent at first time"
+ );
+
+ response = await content.fetch(
+ new content.Request(test_url, {
+ mode: "cors",
+ method: "GET",
+ headers: [["x-test-header", "check"]],
+ })
+ );
+
+ is(
+ await response.text(),
+ "0",
+ "The preflight shouldn't be sent due to the preflight cache"
+ );
+ }
+ );
+ }
+ );
+
+ // Load the tab with a different first party. And use fetch to check if
+ // the preflight cache is partitioned. The fetch will also be performed in
+ // the iframe with the same origin as above to ensure we use the same
+ // loading principal.
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, TEST_ANOTHER_PAGE);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [
+ TEST_PREFLIGHT_PAGE,
+ TEST_PREFLIGHT_IFRAME_PAGE,
+ token,
+ networkIsolation,
+ ],
+ async (url, iframe_url, token, partitioned) => {
+ let iframe = content.document.createElement("iframe");
+
+ await new Promise(resolve => {
+ iframe.onload = () => {
+ resolve();
+ };
+ content.document.body.appendChild(iframe);
+ iframe.src = iframe_url;
+ });
+
+ await SpecialPowers.spawn(
+ iframe,
+ [url, token, partitioned],
+ async (url, token, partitioned) => {
+ const test_url = `${url}?token=${token}`;
+
+ let response = await content.fetch(
+ new content.Request(test_url, {
+ mode: "cors",
+ method: "GET",
+ headers: [["x-test-header", "check"]],
+ })
+ );
+
+ if (partitioned) {
+ is(
+ await response.text(),
+ "1",
+ "The preflight cache should be partitioned"
+ );
+ } else {
+ is(
+ await response.text(),
+ "0",
+ "The preflight cache shouldn't be partitioned"
+ );
+ }
+ }
+ );
+ }
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ }
+ }
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_staticPartition_CORS_preflight.sjs b/toolkit/components/antitracking/test/browser/browser_staticPartition_CORS_preflight.sjs
new file mode 100644
index 0000000000..0f7041aaa7
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_staticPartition_CORS_preflight.sjs
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Cu.importGlobalProperties(["URLSearchParams"]);
+
+function handleRequest(request, response) {
+ let query = new URLSearchParams(request.queryString);
+ let token = query.get("token");
+
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ response.setHeader("Access-Control-Allow-Headers", "x-test-header", false);
+
+ if (request.method == "OPTIONS") {
+ response.setHeader(
+ "Access-Control-Allow-Methods",
+ request.getHeader("Access-Control-Request-Method"),
+ false
+ );
+ response.setHeader("Access-Control-Max-Age", "20", false);
+
+ setState(token, token);
+ } else {
+ let test_op = request.getHeader("x-test-header");
+
+ if (test_op == "check") {
+ let value = getState(token);
+
+ if (value) {
+ response.write("1");
+ setState(token, "");
+ } else {
+ response.write("0");
+ }
+ }
+ }
+}
diff --git a/toolkit/components/antitracking/test/browser/browser_staticPartition_HSTS.js b/toolkit/components/antitracking/test/browser/browser_staticPartition_HSTS.js
new file mode 100644
index 0000000000..6bacac0af9
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_staticPartition_HSTS.js
@@ -0,0 +1,251 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var unsecureEmptyURL =
+ "http://example.org/browser/toolkit/components/antitracking/test/browser/empty.html";
+var secureEmptyURL =
+ "https://example.org/browser/toolkit/components/antitracking/test/browser/empty.html";
+var secureAnotherEmptyURL =
+ "https://example.com/browser/toolkit/components/antitracking/test/browser/empty.html";
+var secureURL =
+ "https://example.com/browser/toolkit/components/antitracking/test/browser/browser_staticPartition_HSTS.sjs";
+var unsecureURL =
+ "http://example.com/browser/toolkit/components/antitracking/test/browser/browser_staticPartition_HSTS.sjs";
+var secureImgURL =
+ "https://example.com/browser/toolkit/components/antitracking/test/browser/browser_staticPartition_HSTS.sjs?image";
+var unsecureImgURL =
+ "http://example.com/browser/toolkit/components/antitracking/test/browser/browser_staticPartition_HSTS.sjs?image";
+var secureIncludeSubURL =
+ "https://example.com/browser/toolkit/components/antitracking/test/browser/browser_staticPartition_HSTS.sjs?includeSub";
+var unsecureSubEmptyURL =
+ "http://test1.example.com/browser/toolkit/components/antitracking/test/browser/empty.html";
+var secureSubEmptyURL =
+ "https://test1.example.com/browser/toolkit/components/antitracking/test/browser/empty.html";
+var unsecureNoCertSubEmptyURL =
+ "http://nocert.example.com/browser/toolkit/components/antitracking/test/browser/empty.html";
+
+function cleanupHSTS(aPartitionEnabled, aUseSite) {
+ // Ensure to remove example.com from the HSTS list.
+ let sss = Cc["@mozilla.org/ssservice;1"].getService(
+ Ci.nsISiteSecurityService
+ );
+
+ for (let origin of ["example.com", "example.org"]) {
+ let originAttributes = {};
+
+ if (aPartitionEnabled) {
+ if (aUseSite) {
+ originAttributes = { partitionKey: `(http,${origin})` };
+ } else {
+ originAttributes = { partitionKey: origin };
+ }
+ }
+
+ sss.resetState(NetUtil.newURI("http://example.com/"), originAttributes);
+ }
+}
+
+function promiseTabLoadEvent(aTab, aURL, aFinalURL) {
+ info("Wait for load tab event");
+ BrowserTestUtils.loadURIString(aTab.linkedBrowser, aURL);
+ return BrowserTestUtils.browserLoaded(aTab.linkedBrowser, false, aFinalURL);
+}
+
+function waitFor(host, type) {
+ return new Promise(resolve => {
+ const observer = channel => {
+ if (
+ channel instanceof Ci.nsIHttpChannel &&
+ channel.URI.host === host &&
+ channel.loadInfo.internalContentPolicyType === type
+ ) {
+ Services.obs.removeObserver(observer, "http-on-stop-request");
+ resolve(channel.URI.spec);
+ }
+ };
+ Services.obs.addObserver(observer, "http-on-stop-request");
+ });
+}
+
+add_task(async function () {
+ for (let networkIsolation of [true, false]) {
+ for (let partitionPerSite of [true, false]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.partition.network_state", networkIsolation],
+ ["privacy.dynamic_firstparty.use_site", partitionPerSite],
+ ["security.mixed_content.upgrade_display_content", false],
+ ],
+ });
+
+ let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser));
+
+ // Let's load the secureURL as first-party in order to activate HSTS.
+ await promiseTabLoadEvent(tab, secureURL, secureURL);
+
+ // Let's test HSTS: unsecure -> secure.
+ await promiseTabLoadEvent(tab, unsecureURL, secureURL);
+ ok(true, "unsecure -> secure, first-party works!");
+
+ // Let's load a first-party.
+ await promiseTabLoadEvent(tab, unsecureEmptyURL, unsecureEmptyURL);
+
+ let finalURL = waitFor(
+ "example.com",
+ Ci.nsIContentPolicy.TYPE_INTERNAL_IFRAME
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [unsecureURL], async url => {
+ let ifr = content.document.createElement("iframe");
+ content.document.body.appendChild(ifr);
+ ifr.src = url;
+ });
+
+ if (networkIsolation) {
+ is(await finalURL, unsecureURL, "HSTS doesn't work for 3rd parties");
+ } else {
+ is(await finalURL, secureURL, "HSTS works for 3rd parties");
+ }
+
+ gBrowser.removeCurrentTab();
+ cleanupHSTS(networkIsolation, partitionPerSite);
+ }
+ }
+});
+
+add_task(async function test_subresource() {
+ for (let networkIsolation of [true, false]) {
+ for (let partitionPerSite of [true, false]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.partition.network_state", networkIsolation],
+ ["privacy.dynamic_firstparty.use_site", partitionPerSite],
+ ["security.mixed_content.upgrade_display_content", false],
+ ],
+ });
+
+ let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser));
+
+ // Load a secure page as first party.
+ await promiseTabLoadEvent(tab, secureEmptyURL, secureEmptyURL);
+
+ let loadPromise = waitFor(
+ "example.com",
+ Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE
+ );
+
+ // Load a secure subresource. HSTS won't be activated, since third
+ // parties can't set HSTS.
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [secureImgURL],
+ async url => {
+ let ifr = content.document.createElement("img");
+ content.document.body.appendChild(ifr);
+ ifr.src = url;
+ }
+ );
+
+ // Ensure the subresource is loaded.
+ await loadPromise;
+
+ // Reload the secure page as first party.
+ await promiseTabLoadEvent(tab, secureEmptyURL, secureEmptyURL);
+
+ let finalURL = waitFor(
+ "example.com",
+ Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE
+ );
+
+ // Load an unsecure subresource. It should not be upgraded to https.
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [unsecureImgURL],
+ async url => {
+ let ifr = content.document.createElement("img");
+ content.document.body.appendChild(ifr);
+ ifr.src = url;
+ }
+ );
+
+ is(await finalURL, unsecureImgURL, "HSTS isn't set for 3rd parties");
+
+ // Load the secure page with a different origin as first party.
+ await promiseTabLoadEvent(
+ tab,
+ secureAnotherEmptyURL,
+ secureAnotherEmptyURL
+ );
+
+ finalURL = waitFor(
+ "example.com",
+ Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE
+ );
+
+ // Load a unsecure subresource
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [unsecureImgURL],
+ async url => {
+ let ifr = content.document.createElement("img");
+ content.document.body.appendChild(ifr);
+ ifr.src = url;
+ }
+ );
+
+ is(await finalURL, unsecureImgURL, "HSTS isn't set for 3rd parties");
+
+ gBrowser.removeCurrentTab();
+ cleanupHSTS(networkIsolation, partitionPerSite);
+ }
+ }
+});
+
+add_task(async function test_includeSubDomains() {
+ for (let networkIsolation of [true, false]) {
+ for (let partitionPerSite of [true, false]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.partition.network_state", networkIsolation],
+ ["privacy.dynamic_firstparty.use_site", partitionPerSite],
+ ["security.mixed_content.upgrade_display_content", false],
+ ],
+ });
+
+ let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser));
+
+ // Load a secure page as first party to activate HSTS.
+ await promiseTabLoadEvent(tab, secureIncludeSubURL, secureIncludeSubURL);
+
+ // Load a unsecure sub-domain page as first party to see if it's upgraded.
+ await promiseTabLoadEvent(tab, unsecureSubEmptyURL, secureSubEmptyURL);
+
+ // Load a sub domain page which will trigger the cert error page.
+ let certErrorLoaded = BrowserTestUtils.waitForErrorPage(
+ tab.linkedBrowser
+ );
+ BrowserTestUtils.loadURIString(
+ tab.linkedBrowser,
+ unsecureNoCertSubEmptyURL
+ );
+ await certErrorLoaded;
+
+ // Verify the error page has the 'badStsCert' in its query string
+ await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ let searchParams = new content.URLSearchParams(
+ content.document.documentURI
+ );
+
+ is(
+ searchParams.get("s"),
+ "badStsCert",
+ "The cert error page has 'badStsCert' set"
+ );
+ });
+
+ gBrowser.removeCurrentTab();
+ cleanupHSTS(networkIsolation, partitionPerSite);
+ }
+ }
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_staticPartition_HSTS.sjs b/toolkit/components/antitracking/test/browser/browser_staticPartition_HSTS.sjs
new file mode 100644
index 0000000000..185c7c7e05
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_staticPartition_HSTS.sjs
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const IMG_BYTES = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAA" +
+ "DUlEQVQImWNgY2P7DwABOgESJhRQtgAAAABJRU5ErkJggg=="
+);
+
+const PAGE = "<!DOCTYPE html><html><body><p>HSTS page</p></body></html>";
+
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, "200", "OK");
+ if (request.queryString == "includeSub") {
+ response.setHeader(
+ "Strict-Transport-Security",
+ "max-age=60; includeSubDomains"
+ );
+ } else {
+ response.setHeader("Strict-Transport-Security", "max-age=60");
+ }
+
+ if (request.queryString == "image") {
+ response.setHeader("Content-Type", "image/png", false);
+ response.setHeader("Content-Length", IMG_BYTES.length + "", false);
+ response.write(IMG_BYTES);
+ return;
+ }
+
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Content-Length", PAGE.length + "", false);
+ response.write(PAGE);
+}
diff --git a/toolkit/components/antitracking/test/browser/browser_staticPartition_cache.js b/toolkit/components/antitracking/test/browser/browser_staticPartition_cache.js
new file mode 100644
index 0000000000..f6bc072b75
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_staticPartition_cache.js
@@ -0,0 +1,194 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const cacheURL =
+ "http://example.org/browser/browser/components/originattributes/test/browser/file_cache.html";
+
+function countMatchingCacheEntries(cacheEntries, domain, fileSuffix) {
+ return cacheEntries
+ .map(entry => entry.uri.asciiSpec)
+ .filter(spec => spec.includes(domain))
+ .filter(spec => spec.includes("file_thirdPartyChild." + fileSuffix)).length;
+}
+
+async function checkCache(suffixes, originAttributes) {
+ const loadContextInfo = Services.loadContextInfo.custom(
+ false,
+ originAttributes
+ );
+
+ const data = await new Promise(resolve => {
+ let cacheEntries = [];
+ let cacheVisitor = {
+ onCacheStorageInfo(num, consumption) {},
+ onCacheEntryInfo(uri, idEnhance) {
+ cacheEntries.push({ uri, idEnhance });
+ },
+ onCacheEntryVisitCompleted() {
+ resolve(cacheEntries);
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsICacheStorageVisitor"]),
+ };
+ // Visiting the disk cache also visits memory storage so we do not
+ // need to use Services.cache2.memoryCacheStorage() here.
+ let storage = Services.cache2.diskCacheStorage(loadContextInfo);
+ storage.asyncVisitStorage(cacheVisitor, true);
+ });
+
+ for (let suffix of suffixes) {
+ let foundEntryCount = countMatchingCacheEntries(
+ data,
+ "example.net",
+ suffix
+ );
+ ok(
+ foundEntryCount > 0,
+ `Cache entries expected for ${suffix} and OA=${JSON.stringify(
+ originAttributes
+ )}`
+ );
+ }
+}
+
+add_task(async function () {
+ info("Disable predictor and accept all");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["network.predictor.enabled", false],
+ ["network.predictor.enable-prefetch", false],
+ ["network.cookie.cookieBehavior", 0],
+ ],
+ });
+
+ const tests = [
+ {
+ prefValue: true,
+ originAttributes: { partitionKey: "(http,example.org)" },
+ },
+ {
+ prefValue: false,
+ originAttributes: {},
+ },
+ ];
+
+ for (let test of tests) {
+ info("Clear image and network caches");
+ let tools = SpecialPowers.Cc["@mozilla.org/image/tools;1"].getService(
+ SpecialPowers.Ci.imgITools
+ );
+ let imageCache = tools.getImgCacheForDocument(window.document);
+ imageCache.clearCache(true); // true=chrome
+ imageCache.clearCache(false); // false=content
+ Services.cache2.clear();
+
+ info("Enabling network state partitioning");
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.partition.network_state", test.prefValue]],
+ });
+
+ info("Let's load a page to populate some entries");
+ let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser));
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, cacheURL);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, cacheURL);
+
+ let argObj = {
+ randomSuffix: Math.random(),
+ urlPrefix:
+ "http://example.net/browser/browser/components/originattributes/test/browser/",
+ };
+
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [argObj],
+ async function (arg) {
+ // The CSS cache needs to be cleared in-process.
+ content.windowUtils.clearSharedStyleSheetCache();
+
+ let videoURL = arg.urlPrefix + "file_thirdPartyChild.video.ogv";
+ let audioURL = arg.urlPrefix + "file_thirdPartyChild.audio.ogg";
+ let URLSuffix = "?r=" + arg.randomSuffix;
+
+ // Create the audio and video elements.
+ let audio = content.document.createElement("audio");
+ let video = content.document.createElement("video");
+ let audioSource = content.document.createElement("source");
+
+ // Append the audio element into the body, and wait until they're finished.
+ await new content.Promise(resolve => {
+ let audioLoaded = false;
+
+ let audioListener = () => {
+ Assert.ok(true, `Audio suspended: ${audioURL + URLSuffix}`);
+ audio.removeEventListener("suspend", audioListener);
+
+ audioLoaded = true;
+ if (audioLoaded) {
+ resolve();
+ }
+ };
+
+ Assert.ok(true, `Loading audio: ${audioURL + URLSuffix}`);
+
+ // Add the event listeners before everything in case we lose events.
+ audio.addEventListener("suspend", audioListener);
+
+ // Assign attributes for the audio element.
+ audioSource.setAttribute("src", audioURL + URLSuffix);
+ audioSource.setAttribute("type", "audio/ogg");
+
+ audio.appendChild(audioSource);
+ audio.autoplay = true;
+
+ content.document.body.appendChild(audio);
+ });
+
+ // Append the video element into the body, and wait until it's finished.
+ await new content.Promise(resolve => {
+ let listener = () => {
+ Assert.ok(true, `Video suspended: ${videoURL + URLSuffix}`);
+ video.removeEventListener("suspend", listener);
+ resolve();
+ };
+
+ Assert.ok(true, `Loading video: ${videoURL + URLSuffix}`);
+
+ // Add the event listener before everything in case we lose the event.
+ video.addEventListener("suspend", listener);
+
+ // Assign attributes for the video element.
+ video.setAttribute("src", videoURL + URLSuffix);
+ video.setAttribute("type", "video/ogg");
+
+ content.document.body.appendChild(video);
+ });
+ }
+ );
+
+ let maybePartitionedSuffixes = [
+ "iframe.html",
+ "link.css",
+ "script.js",
+ "img.png",
+ "favicon.png",
+ "object.png",
+ "embed.png",
+ "xhr.html",
+ "worker.xhr.html",
+ "audio.ogg",
+ "video.ogv",
+ "fetch.html",
+ "worker.fetch.html",
+ "request.html",
+ "worker.request.html",
+ "import.js",
+ "worker.js",
+ "sharedworker.js",
+ ];
+
+ info("Query the cache (maybe) partitioned cache");
+ await checkCache(maybePartitionedSuffixes, test.originAttributes);
+
+ gBrowser.removeCurrentTab();
+ }
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_staticPartition_network.js b/toolkit/components/antitracking/test/browser/browser_staticPartition_network.js
new file mode 100644
index 0000000000..a28f7d5adc
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_staticPartition_network.js
@@ -0,0 +1,116 @@
+function altSvcCacheKeyIsolated(parsed) {
+ return parsed.length > 5 && parsed[5] == "I";
+}
+
+function altSvcPartitionKey(key) {
+ let parts = key.split(":");
+ return parts[parts.length - 2];
+}
+
+const gHttpHandler = Cc["@mozilla.org/network/protocol;1?name=http"].getService(
+ Ci.nsIHttpProtocolHandler
+);
+
+add_task(async function () {
+ info("Starting tlsSessionTickets test");
+
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.cache.disk.enable", false],
+ ["browser.cache.memory.enable", false],
+ ["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_ACCEPT],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_ACCEPT,
+ ],
+ ["network.http.altsvc.proxy_checks", false],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", false],
+ ["privacy.partition.network_state", true],
+ ["privacy.partition.network_state.connection_with_proxy", true],
+ ],
+ });
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ const thirdPartyURL =
+ "https://tlsresumptiontest.example.org/browser/toolkit/components/antitracking/test/browser/empty-altsvc.js";
+ const partitionKey1 = "^partitionKey=%28http%2Cexample.net%29";
+ const partitionKey2 = "^partitionKey=%28http%2Cmochi.test%29";
+
+ function checkAltSvcCache(keys) {
+ let arr = gHttpHandler.altSvcCacheKeys;
+ is(
+ arr.length,
+ keys.length,
+ "Found the expected number of items in the cache"
+ );
+ for (let i = 0; i < arr.length; ++i) {
+ is(
+ altSvcPartitionKey(arr[i]),
+ keys[i],
+ "Expected top window origin found in the Alt-Svc cache key"
+ );
+ }
+ }
+
+ checkAltSvcCache([]);
+
+ info("Loading something in the tab");
+ await SpecialPowers.spawn(browser, [{ thirdPartyURL }], async function (obj) {
+ dump("AAA: " + content.window.location.href + "\n");
+ let src = content.document.createElement("script");
+ let p = new content.Promise(resolve => {
+ src.onload = resolve;
+ });
+ content.document.body.appendChild(src);
+ src.src = obj.thirdPartyURL;
+ await p;
+ });
+
+ checkAltSvcCache([partitionKey1]);
+
+ info("Creating a second tab");
+ let tab2 = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE_6);
+ gBrowser.selectedTab = tab2;
+
+ let browser2 = gBrowser.getBrowserForTab(tab2);
+ await BrowserTestUtils.browserLoaded(browser2);
+
+ info("Loading something in the second tab");
+ await SpecialPowers.spawn(
+ browser2,
+ [{ thirdPartyURL }],
+ async function (obj) {
+ let src = content.document.createElement("script");
+ let p = new content.Promise(resolve => {
+ src.onload = resolve;
+ });
+ content.document.body.appendChild(src);
+ src.src = obj.thirdPartyURL;
+ await p;
+ }
+ );
+
+ checkAltSvcCache([partitionKey1, partitionKey2]);
+
+ info("Removing the tabs");
+ BrowserTestUtils.removeTab(tab);
+ BrowserTestUtils.removeTab(tab2);
+});
+
+add_task(async function () {
+ info("Cleaning up.");
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_staticPartition_saveAs.js b/toolkit/components/antitracking/test/browser/browser_staticPartition_saveAs.js
new file mode 100644
index 0000000000..22db484e16
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_staticPartition_saveAs.js
@@ -0,0 +1,532 @@
+/**
+ * Bug 1641270 - A test case for ensuring the save channel will use the correct
+ * cookieJarSettings when doing the saving and the cache would
+ * work as expected.
+ */
+
+"use strict";
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
+ this
+);
+
+const TEST_IMAGE_URL = TEST_DOMAIN + TEST_PATH + "file_saveAsImage.sjs";
+const TEST_VIDEO_URL = TEST_DOMAIN + TEST_PATH + "file_saveAsVideo.sjs";
+const TEST_PAGEINFO_URL = TEST_DOMAIN + TEST_PATH + "file_saveAsPageInfo.html";
+
+let MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+
+const tempDir = createTemporarySaveDirectory();
+MockFilePicker.displayDirectory = tempDir;
+
+function createTemporarySaveDirectory() {
+ let saveDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ saveDir.append("testsavedir");
+ saveDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ return saveDir;
+}
+
+function createPromiseForTransferComplete(aDesirableFileName) {
+ return new Promise(resolve => {
+ MockFilePicker.showCallback = fp => {
+ info("MockFilePicker showCallback");
+
+ let fileName = fp.defaultString;
+ let destFile = tempDir.clone();
+ destFile.append(fileName);
+
+ if (aDesirableFileName) {
+ is(fileName, aDesirableFileName, "The default file name is correct.");
+ }
+
+ MockFilePicker.setFiles([destFile]);
+ MockFilePicker.filterIndex = 0; // kSaveAsType_Complete
+
+ MockFilePicker.showCallback = null;
+ mockTransferCallback = function (downloadSuccess) {
+ ok(downloadSuccess, "File should have been downloaded successfully");
+ mockTransferCallback = () => {};
+ resolve();
+ };
+ };
+ });
+}
+
+function createPromiseForObservingChannel(aURL, aPartitionKey) {
+ return new Promise(resolve => {
+ let observer = (aSubject, aTopic) => {
+ if (aTopic === "http-on-modify-request") {
+ let httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+ let reqLoadInfo = httpChannel.loadInfo;
+
+ // Make sure this is the request which we want to check.
+ if (!httpChannel.URI.spec.endsWith(aURL)) {
+ return;
+ }
+
+ info(`Checking loadInfo for URI: ${httpChannel.URI.spec}\n`);
+ is(
+ reqLoadInfo.cookieJarSettings.partitionKey,
+ aPartitionKey,
+ "The loadInfo has the correct partition key"
+ );
+
+ Services.obs.removeObserver(observer, "http-on-modify-request");
+ resolve();
+ }
+ };
+
+ Services.obs.addObserver(observer, "http-on-modify-request");
+ });
+}
+
+add_setup(async function () {
+ info("Setting MockFilePicker.");
+ mockTransferRegisterer.register();
+
+ registerCleanupFunction(function () {
+ mockTransferRegisterer.unregister();
+ MockFilePicker.cleanup();
+ tempDir.remove(true);
+ });
+});
+
+add_task(async function testContextMenuSaveImage() {
+ let uuidGenerator = Services.uuid;
+
+ for (let networkIsolation of [true, false]) {
+ for (let partitionPerSite of [true, false]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.partition.network_state", networkIsolation],
+ ["privacy.dynamic_firstparty.use_site", partitionPerSite],
+ ],
+ });
+
+ // We use token to separate the caches.
+ let token = uuidGenerator.generateUUID().toString();
+ const testImageURL = `${TEST_IMAGE_URL}?token=${token}`;
+
+ info(`Open a new tab for testing "Save image as" in context menu.`);
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_TOP_PAGE
+ );
+
+ info(`Insert the testing image into the tab.`);
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [testImageURL],
+ async url => {
+ let img = content.document.createElement("img");
+ let loaded = new content.Promise(resolve => {
+ img.onload = resolve;
+ });
+ content.document.body.appendChild(img);
+ img.setAttribute("id", "image1");
+ img.src = url;
+ await loaded;
+ }
+ );
+
+ info("Open the context menu.");
+ let popupShownPromise = BrowserTestUtils.waitForEvent(
+ document,
+ "popupshown"
+ );
+
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#image1",
+ {
+ type: "contextmenu",
+ button: 2,
+ },
+ tab.linkedBrowser
+ );
+
+ await popupShownPromise;
+
+ let partitionKey = partitionPerSite
+ ? "(http,example.net)"
+ : "example.net";
+
+ let transferCompletePromise = createPromiseForTransferComplete();
+ let observerPromise = createPromiseForObservingChannel(
+ testImageURL,
+ partitionKey
+ );
+
+ let saveElement = document.getElementById("context-saveimage");
+ info("Triggering the save process.");
+ saveElement.doCommand();
+
+ info("Waiting for the channel.");
+ await observerPromise;
+
+ info("Wait until the save is finished.");
+ await transferCompletePromise;
+
+ info("Close the context menu.");
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popuphidden"
+ );
+ contextMenu.hidePopup();
+ await popupHiddenPromise;
+
+ // Check if there will be only one network request. The another one should
+ // be from cache.
+ let res = await fetch(`${TEST_IMAGE_URL}?token=${token}&result`);
+ let res_text = await res.text();
+ is(res_text, "1", "The image should be loaded only once.");
+
+ BrowserTestUtils.removeTab(tab);
+ }
+ }
+});
+
+add_task(async function testContextMenuSaveVideo() {
+ let uuidGenerator = Services.uuid;
+
+ for (let networkIsolation of [true, false]) {
+ for (let partitionPerSite of [true, false]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.partition.network_state", networkIsolation],
+ ["privacy.dynamic_firstparty.use_site", partitionPerSite],
+ ],
+ });
+
+ // We use token to separate the caches.
+ let token = uuidGenerator.generateUUID().toString();
+ const testVideoURL = `${TEST_VIDEO_URL}?token=${token}`;
+
+ info(`Open a new tab for testing "Save Video as" in context menu.`);
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_TOP_PAGE
+ );
+
+ info(`Insert the testing video into the tab.`);
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [testVideoURL],
+ async url => {
+ let video = content.document.createElement("video");
+ let loaded = new content.Promise(resolve => {
+ video.onloadeddata = resolve;
+ });
+ content.document.body.appendChild(video);
+ video.setAttribute("id", "video1");
+ video.src = url;
+ await loaded;
+ }
+ );
+
+ info("Open the context menu.");
+ let popupShownPromise = BrowserTestUtils.waitForEvent(
+ document,
+ "popupshown"
+ );
+
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#video1",
+ {
+ type: "contextmenu",
+ button: 2,
+ },
+ tab.linkedBrowser
+ );
+
+ await popupShownPromise;
+
+ let partitionKey = partitionPerSite
+ ? "(http,example.net)"
+ : "example.net";
+
+ // We also check the default file name, see Bug 1679325.
+ let transferCompletePromise = createPromiseForTransferComplete(
+ "file_saveAsVideo.webm"
+ );
+ let observerPromise = createPromiseForObservingChannel(
+ testVideoURL,
+ partitionKey
+ );
+
+ let saveElement = document.getElementById("context-savevideo");
+ info("Triggering the save process.");
+ saveElement.doCommand();
+
+ info("Waiting for the channel.");
+ await observerPromise;
+
+ info("Wait until the save is finished.");
+ await transferCompletePromise;
+
+ info("Close the context menu.");
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popuphidden"
+ );
+ contextMenu.hidePopup();
+ await popupHiddenPromise;
+
+ // Check if there will be only one network request. The another one should
+ // be from cache.
+ let res = await fetch(`${TEST_VIDEO_URL}?token=${token}&result`);
+ let res_text = await res.text();
+ is(res_text, "1", "The video should be loaded only once.");
+
+ BrowserTestUtils.removeTab(tab);
+ }
+ }
+});
+
+add_task(async function testSavePageInOfflineMode() {
+ for (let networkIsolation of [true, false]) {
+ for (let partitionPerSite of [true, false]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.partition.network_state", networkIsolation],
+ ["privacy.dynamic_firstparty.use_site", partitionPerSite],
+ ],
+ });
+
+ let partitionKey = partitionPerSite
+ ? "(http,example.net)"
+ : "example.net";
+
+ info(`Open a new tab which loads an image`);
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_IMAGE_URL
+ );
+
+ info("Toggle on the offline mode");
+ BrowserOffline.toggleOfflineStatus();
+
+ info("Open file menu and trigger 'Save Page As'");
+ let menubar = document.getElementById("main-menubar");
+ let filePopup = document.getElementById("menu_FilePopup");
+
+ // We only use the shortcut keys to open the file menu in Windows and Linux.
+ // Mac doesn't have a shortcut to only open the file menu. Instead, we directly
+ // trigger the save in MAC without any UI interactions.
+ if (Services.appinfo.OS !== "Darwin") {
+ let menubarActive = BrowserTestUtils.waitForEvent(
+ menubar,
+ "DOMMenuBarActive"
+ );
+ EventUtils.synthesizeKey("KEY_F10");
+ await menubarActive;
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(
+ filePopup,
+ "popupshown"
+ );
+ // In window, it still needs one extra down key to open the file menu.
+ if (Services.appinfo.OS === "WINNT") {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ }
+ await popupShownPromise;
+ }
+
+ let transferCompletePromise = createPromiseForTransferComplete();
+ let observerPromise = createPromiseForObservingChannel(
+ TEST_IMAGE_URL,
+ partitionKey
+ );
+
+ info("Triggering the save process.");
+ let fileSavePageAsElement = document.getElementById("menu_savePage");
+ fileSavePageAsElement.doCommand();
+
+ info("Waiting for the channel.");
+ await observerPromise;
+
+ info("Wait until the save is finished.");
+ await transferCompletePromise;
+
+ // Close the file menu.
+ if (Services.appinfo.OS !== "Darwin") {
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(
+ filePopup,
+ "popuphidden"
+ );
+ filePopup.hidePopup();
+ await popupHiddenPromise;
+ }
+
+ info("Toggle off the offline mode");
+ BrowserOffline.toggleOfflineStatus();
+
+ // Clean up
+ BrowserTestUtils.removeTab(tab);
+
+ // Clean up the cache count on the server side.
+ await fetch(`${TEST_IMAGE_URL}?result`);
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ }
+ }
+});
+
+add_task(async function testPageInfoMediaSaveAs() {
+ for (let networkIsolation of [true, false]) {
+ for (let partitionPerSite of [true, false]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.partition.network_state", networkIsolation],
+ ["privacy.dynamic_firstparty.use_site", partitionPerSite],
+ ],
+ });
+
+ let partitionKey = partitionPerSite
+ ? "(http,example.net)"
+ : "example.net";
+
+ info(
+ `Open a new tab for testing "Save AS" in the media panel of the page info.`
+ );
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_PAGEINFO_URL
+ );
+
+ info("Open the media panel of the pageinfo.");
+ let pageInfo = BrowserPageInfo(
+ gBrowser.selectedBrowser.currentURI.spec,
+ "mediaTab"
+ );
+
+ await BrowserTestUtils.waitForEvent(pageInfo, "page-info-init");
+
+ let imageTree = pageInfo.document.getElementById("imagetree");
+ let imageRowsNum = imageTree.view.rowCount;
+
+ is(imageRowsNum, 2, "There should be two media items here.");
+
+ for (let i = 0; i < imageRowsNum; i++) {
+ imageTree.view.selection.select(i);
+ imageTree.ensureRowIsVisible(i);
+ imageTree.focus();
+
+ // Wait until the preview is loaded.
+ let preview = pageInfo.document.getElementById("thepreviewimage");
+ let mediaType = pageInfo.gImageView.data[i][1]; // COL_IMAGE_TYPE
+ if (mediaType == "Image") {
+ await BrowserTestUtils.waitForEvent(preview, "load");
+ } else if (mediaType == "Video") {
+ await BrowserTestUtils.waitForEvent(preview, "canplaythrough");
+ }
+
+ let url = pageInfo.gImageView.data[i][0]; // COL_IMAGE_ADDRESS
+ info(`Start to save the media item with URL: ${url}`);
+
+ let transferCompletePromise = createPromiseForTransferComplete();
+
+ // Observe the channel and check if it has the correct partitionKey.
+ let observerPromise = createPromiseForObservingChannel(
+ url,
+ partitionKey
+ );
+
+ info("Triggering the save process.");
+ let saveElement = pageInfo.document.getElementById("imagesaveasbutton");
+ saveElement.doCommand();
+
+ info("Waiting for the channel.");
+ await observerPromise;
+
+ info("Wait until the save is finished.");
+ await transferCompletePromise;
+ }
+
+ pageInfo.close();
+ BrowserTestUtils.removeTab(tab);
+ }
+ }
+});
+
+add_task(async function testPageInfoMediaMultipleSelectedSaveAs() {
+ for (let networkIsolation of [true, false]) {
+ for (let partitionPerSite of [true, false]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.partition.network_state", networkIsolation],
+ ["privacy.dynamic_firstparty.use_site", partitionPerSite],
+ ],
+ });
+
+ let partitionKey = partitionPerSite
+ ? "(http,example.net)"
+ : "example.net";
+
+ info(
+ `Open a new tab for testing "Save AS" in the media panel of the page info.`
+ );
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_PAGEINFO_URL
+ );
+
+ info("Open the media panel of the pageinfo.");
+ let pageInfo = BrowserPageInfo(
+ gBrowser.selectedBrowser.currentURI.spec,
+ "mediaTab"
+ );
+
+ await BrowserTestUtils.waitForEvent(pageInfo, "page-info-init");
+
+ // Make sure the preview image is loaded in order to avoid interfering
+ // following tests.
+ let preview = pageInfo.document.getElementById("thepreviewimage");
+ await BrowserTestUtils.waitForCondition(() => {
+ return preview.complete;
+ });
+
+ let imageTree = pageInfo.document.getElementById("imagetree");
+ let imageRowsNum = imageTree.view.rowCount;
+
+ is(imageRowsNum, 2, "There should be two media items here.");
+
+ imageTree.view.selection.selectAll();
+ imageTree.focus();
+
+ let url = pageInfo.gImageView.data[0][0]; // COL_IMAGE_ADDRESS
+ info(`Start to save the media item with URL: ${url}`);
+
+ let transferCompletePromise = createPromiseForTransferComplete();
+ let observerPromises = [];
+
+ // Observe all channels and check if they have the correct partitionKey.
+ for (let i = 0; i < imageRowsNum; ++i) {
+ let observerPromise = createPromiseForObservingChannel(
+ url,
+ partitionKey
+ );
+ observerPromises.push(observerPromise);
+ }
+
+ info("Triggering the save process.");
+ let saveElement = pageInfo.document.getElementById("imagesaveasbutton");
+ saveElement.doCommand();
+
+ info("Waiting for the all channels.");
+ await Promise.all(observerPromises);
+
+ info("Wait until the save is finished.");
+ await transferCompletePromise;
+
+ pageInfo.close();
+ BrowserTestUtils.removeTab(tab);
+ }
+ }
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_staticPartition_tls_session.js b/toolkit/components/antitracking/test/browser/browser_staticPartition_tls_session.js
new file mode 100644
index 0000000000..e131f71169
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_staticPartition_tls_session.js
@@ -0,0 +1,115 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+/**
+ * Tests that we correctly partition TLS sessions by inspecting the
+ * socket's peerId. The peerId contains the OriginAttributes suffix which
+ * includes the partitionKey.
+ */
+
+const TEST_ORIGIN_A = "https://example.com";
+const TEST_ORIGIN_B = "https://example.org";
+const TEST_ORIGIN_C = "https://w3c-test.org";
+
+const TEST_ENDPOINT =
+ "/browser/toolkit/components/antitracking/test/browser/empty.js";
+
+const TEST_URL_C = TEST_ORIGIN_C + TEST_ENDPOINT;
+
+/**
+ * Waits for a load with the given URL to happen and returns the peerId.
+ * @param {string} url - The URL expected to load.
+ * @returns {Promise<string>} a promise which resolves on load with the
+ * associated socket peerId.
+ */
+async function waitForLoad(url) {
+ return new Promise(resolve => {
+ const TOPIC = "http-on-examine-response";
+
+ function observer(subject, topic, data) {
+ if (topic != TOPIC) {
+ return;
+ }
+ subject.QueryInterface(Ci.nsIChannel);
+ if (subject.URI.spec != url) {
+ return;
+ }
+
+ Services.obs.removeObserver(observer, TOPIC);
+
+ resolve(subject.securityInfo.peerId);
+ }
+ Services.obs.addObserver(observer, TOPIC);
+ });
+}
+
+/**
+ * Loads a url in the given browser and returns the tls socket's peer id
+ * associated with the load.
+ * Note: Loads are identified by URI. If multiple loads with the same URI happen
+ * concurrently, this method may not map them correctly.
+ * @param {MozBrowser} browser
+ * @param {string} url
+ * @returns {Promise<string>} Resolves on load with the peer id associated with
+ * the load.
+ */
+function loadURLInFrame(browser, url) {
+ let loadPromise = waitForLoad(url);
+ ContentTask.spawn(browser, [url], async testURL => {
+ let frame = content.document.createElement("iframe");
+ frame.src = testURL;
+ content.document.body.appendChild(frame);
+ });
+ return loadPromise;
+}
+
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.cache.disk.enable", false],
+ ["browser.cache.memory.enable", false],
+ ["privacy.partition.network_state", true],
+ // The test harness acts as a proxy, so we need to make sure to also
+ // partition for proxies.
+ ["privacy.partition.network_state.connection_with_proxy", true],
+ ],
+ });
+
+ // C (first party)
+ let loadPromiseC = waitForLoad(TEST_URL_C);
+ await BrowserTestUtils.withNewTab(TEST_URL_C, async () => {});
+ let peerIdC = await loadPromiseC;
+
+ // C embedded in C (same origin)
+ let peerIdCC;
+ await BrowserTestUtils.withNewTab(TEST_ORIGIN_C, async browser => {
+ peerIdCC = await loadURLInFrame(browser, TEST_URL_C);
+ });
+
+ // C embedded in A (third party)
+ let peerIdAC;
+ await BrowserTestUtils.withNewTab(TEST_ORIGIN_A, async browser => {
+ peerIdAC = await loadURLInFrame(browser, TEST_URL_C);
+ });
+
+ // C embedded in B (third party)
+ let peerIdBC;
+ await BrowserTestUtils.withNewTab(TEST_ORIGIN_B, async browser => {
+ peerIdBC = await loadURLInFrame(browser, TEST_URL_C);
+ });
+
+ info("Test that top level load and same origin frame have the same peerId.");
+ is(peerIdC, peerIdCC, "Should have the same peerId");
+
+ info("Test that all partitioned peer ids are distinct.");
+ isnot(peerIdCC, peerIdAC, "Should have different peerId partitioned under A");
+ isnot(peerIdCC, peerIdBC, "Should have different peerId partitioned under B");
+ isnot(
+ peerIdAC,
+ peerIdBC,
+ "Should have a different peerId under different first parties."
+ );
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_staticPartition_websocket.js b/toolkit/components/antitracking/test/browser/browser_staticPartition_websocket.js
new file mode 100644
index 0000000000..11f53d69ed
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_staticPartition_websocket.js
@@ -0,0 +1,198 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const FIRST_PARTY_A = "http://example.com";
+const FIRST_PARTY_B = "http://example.org";
+const THIRD_PARTY = "http://example.net";
+const WS_ENDPOINT_HOST = "mochi.test:8888";
+
+function getWSTestUrlForHost(host) {
+ return (
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ `ws://${host}`
+ ) + `file_ws_handshake_delay`
+ );
+}
+
+function connect(browsingContext, host, protocol) {
+ let url = getWSTestUrlForHost(host);
+ info("Creating websocket with endpoint " + url);
+
+ // Create websocket connection in third party iframe.
+ let createPromise = SpecialPowers.spawn(
+ browsingContext.children[0],
+ [url, protocol],
+ (url, protocol) => {
+ let ws = new content.WebSocket(url, [protocol]);
+ ws.addEventListener("error", () => {
+ ws._testError = true;
+ });
+ if (!content.ws) {
+ content.ws = {};
+ }
+ content.ws[protocol] = ws;
+ }
+ );
+
+ let openPromise = createPromise.then(() =>
+ SpecialPowers.spawn(
+ browsingContext.children[0],
+ [protocol],
+ async protocol => {
+ let ws = content.ws[protocol];
+ if (ws.readyState != 0) {
+ return !ws._testError;
+ }
+ // Still connecting.
+ let result = await Promise.race([
+ ContentTaskUtils.waitForEvent(ws, "open"),
+ ContentTaskUtils.waitForEvent(ws, "error"),
+ ]);
+ return result.type != "error";
+ }
+ )
+ );
+
+ let result = { createPromise, openPromise };
+ return result;
+}
+
+// Open 3 websockets which target the same ip/port combination, but have
+// different principals. We send a protocol identifier to the server to signal
+// how long the request should be delayed.
+//
+// When partitioning is disabled A blocks B and B blocks C. The timeline will
+// look like this:
+// A________
+// B____
+// C_
+//
+// When partitioning is enabled, connection handshakes for A and B will run
+// (semi-) parallel since they have different origin attributes. B and C share
+// origin attributes and therefore still run serially.
+// A________
+// B____
+// C_
+//
+// By observing the order of the handshakes we can ensure that the queue
+// partitioning is working correctly.
+async function runTest(partitioned) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.partition.network_state", partitioned]],
+ });
+
+ let tabA = BrowserTestUtils.addTab(gBrowser, FIRST_PARTY_A);
+ await BrowserTestUtils.browserLoaded(tabA.linkedBrowser);
+ let tabB = BrowserTestUtils.addTab(gBrowser, FIRST_PARTY_B);
+ await BrowserTestUtils.browserLoaded(tabB.linkedBrowser);
+
+ for (let tab of [tabA, tabB]) {
+ await SpecialPowers.spawn(tab.linkedBrowser, [THIRD_PARTY], async src => {
+ let frame = content.document.createElement("iframe");
+ frame.src = src;
+ let loadPromise = ContentTaskUtils.waitForEvent(frame, "load");
+ content.document.body.appendChild(frame);
+ await loadPromise;
+ });
+ }
+
+ // First ensure that we can open websocket connections to the test endpoint.
+ let { openPromise, createPromise } = await connect(
+ tabA.linkedBrowser.browsingContext,
+ WS_ENDPOINT_HOST,
+ false
+ );
+ await createPromise;
+ let openPromiseResult = await openPromise;
+ ok(openPromiseResult, "Websocket endpoint accepts connections.");
+
+ let openedA;
+ let openedB;
+ let openedC;
+
+ let { createPromise: createPromiseA, openPromise: openPromiseA } = connect(
+ tabA.linkedBrowser.browsingContext,
+ WS_ENDPOINT_HOST,
+ "test-6"
+ );
+
+ openPromiseA = openPromiseA.then(opened => {
+ openedA = opened;
+ info("Completed WS connection A");
+ if (partitioned) {
+ ok(openedA, "Should have opened A");
+ ok(openedB, "Should have opened B");
+ } else {
+ ok(openedA, "Should have opened A");
+ ok(openedB == null, "B should be pending");
+ }
+ });
+ await createPromiseA;
+
+ // The frame of connection B is embedded in a different first party as A.
+ let { createPromise: createPromiseB, openPromise: openPromiseB } = connect(
+ tabB.linkedBrowser.browsingContext,
+ WS_ENDPOINT_HOST,
+ "test-3"
+ );
+ openPromiseB = openPromiseB.then(opened => {
+ openedB = opened;
+ info("Completed WS connection B");
+ if (partitioned) {
+ ok(openedA == null, "A should be pending");
+ ok(openedB, "Should have opened B");
+ ok(openedC == null, "C should be pending");
+ } else {
+ ok(openedA, "Should have opened A");
+ ok(openedB, "Should have opened B");
+ ok(openedC == null, "C should be pending");
+ }
+ });
+ await createPromiseB;
+
+ // The frame of connection C is embedded in the same first party as B.
+ let { createPromise: createPromiseC, openPromise: openPromiseC } = connect(
+ tabB.linkedBrowser.browsingContext,
+ WS_ENDPOINT_HOST,
+ "test-0"
+ );
+ openPromiseC = openPromiseC.then(opened => {
+ openedC = opened;
+ info("Completed WS connection C");
+ if (partitioned) {
+ ok(openedB, "Should have opened B");
+ ok(openedC, "Should have opened C");
+ } else {
+ ok(opened, "Should have opened B");
+ ok(opened, "Should have opened C");
+ }
+ });
+ await createPromiseC;
+
+ // Wait for all connections to complete before closing the tabs.
+ await Promise.all([openPromiseA, openPromiseB, openPromiseC]);
+
+ BrowserTestUtils.removeTab(tabA);
+ BrowserTestUtils.removeTab(tabB);
+
+ await SpecialPowers.popPrefEnv();
+}
+
+add_setup(async function () {
+ // This test relies on a WS connection timeout > 6 seconds.
+ await SpecialPowers.pushPrefEnv({
+ set: [["network.websocket.timeout.open", 20]],
+ });
+});
+
+add_task(async function test_non_partitioned() {
+ await runTest(false);
+});
+
+add_task(async function test_partitioned() {
+ await runTest(true);
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessAutograntRequiresUserInteraction.js b/toolkit/components/antitracking/test/browser/browser_storageAccessAutograntRequiresUserInteraction.js
new file mode 100644
index 0000000000..c2d6898313
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessAutograntRequiresUserInteraction.js
@@ -0,0 +1,57 @@
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/browser/modules/test/browser/head.js",
+ this
+);
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/toolkit/components/antitracking/test/browser/storage_access_head.js",
+ this
+);
+
+async function setAutograntPreferences() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.auto_grants", true],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+}
+
+add_task(async function testPopupWithUserInteraction() {
+ await setPreferences();
+ await setAutograntPreferences();
+
+ // Test that requesting storage access initially does not autogrant.
+ // If the autogrant doesn't occur, we click reject on the door hanger
+ // and expect the promise returned by requestStorageAccess to reject.
+ await openPageAndRunCode(
+ TEST_TOP_PAGE,
+ getExpectPopupAndClick("reject"),
+ TEST_3RD_PARTY_PAGE,
+ requestStorageAccessAndExpectFailure
+ );
+ // Grant the storageAccessAPI permission to the third-party.
+ // This signifies that it has been interacted with and should allow autogrants
+ // among other behaviors.
+ const uri = Services.io.newURI(TEST_3RD_PARTY_DOMAIN);
+ const principal = Services.scriptSecurityManager.createContentPrincipal(
+ uri,
+ {}
+ );
+ Services.perms.addFromPrincipal(
+ principal,
+ "storageAccessAPI",
+ Services.perms.ALLOW_ACTION
+ );
+
+ // Test that requesting storage access autogrants here. If a popup occurs,
+ // expectNoPopup will cause an error in this test.
+ await openPageAndRunCode(
+ TEST_TOP_PAGE,
+ expectNoPopup,
+ TEST_3RD_PARTY_PAGE,
+ requestStorageAccessAndExpectSuccess
+ );
+
+ await cleanUpData();
+ await SpecialPowers.flushPrefEnv();
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessDeniedGivesNoUserInteraction.js b/toolkit/components/antitracking/test/browser/browser_storageAccessDeniedGivesNoUserInteraction.js
new file mode 100644
index 0000000000..cf47c43a14
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessDeniedGivesNoUserInteraction.js
@@ -0,0 +1,30 @@
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/browser/modules/test/browser/head.js",
+ this
+);
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/toolkit/components/antitracking/test/browser/storage_access_head.js",
+ this
+);
+
+add_task(async function testGrantGivesPermission() {
+ await setPreferences();
+
+ await openPageAndRunCode(
+ TEST_TOP_PAGE,
+ getExpectPopupAndClick("reject"),
+ TEST_3RD_PARTY_PAGE,
+ requestStorageAccessAndExpectFailure
+ );
+
+ await SpecialPowers.testPermission(
+ "storageAccessAPI",
+ SpecialPowers.Services.perms.UNKNOWN_ACTION,
+ {
+ url: "https://tracking.example.org/",
+ }
+ );
+
+ await cleanUpData();
+ await SpecialPowers.flushPrefEnv();
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessDoorHanger.js b/toolkit/components/antitracking/test/browser/browser_storageAccessDoorHanger.js
new file mode 100644
index 0000000000..733eb34240
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessDoorHanger.js
@@ -0,0 +1,372 @@
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/browser/modules/test/browser/head.js",
+ this
+);
+
+const BLOCK = 0;
+const ALLOW = 1;
+
+async function testDoorHanger(
+ choice,
+ showPrompt,
+ useEscape,
+ topPage,
+ maxConcurrent,
+ disableWebcompat = false
+) {
+ info(
+ `Running doorhanger test with choice #${choice}, showPrompt: ${showPrompt} and ` +
+ `useEscape: ${useEscape}, topPage: ${topPage}, maxConcurrent: ${maxConcurrent}`
+ );
+
+ if (!showPrompt) {
+ is(choice, ALLOW, "When not showing a prompt, we can only auto-grant");
+ ok(
+ !useEscape,
+ "When not showing a prompt, we should not be trying to use the Esc key"
+ );
+ }
+
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.antitracking.enableWebcompat", !disableWebcompat],
+ ["dom.storage_access.auto_grants", true],
+ ["dom.storage_access.auto_grants.delayed", false],
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.max_concurrent_auto_grants", maxConcurrent],
+ ["dom.storage_access.prompt.testing", false],
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ [
+ "privacy.restrict3rdpartystorage.userInteractionRequiredForHosts",
+ "tracking.example.com,tracking.example.org",
+ ],
+ // Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default"
+ ["network.cookie.sameSite.laxByDefault", false],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ let tab = BrowserTestUtils.addTab(gBrowser, topPage);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ async function runChecks() {
+ // We need to repeat this constant here since runChecks is stringified
+ // and sent to the content process.
+ const BLOCK = 0;
+
+ await new Promise(resolve => {
+ addEventListener(
+ "message",
+ function onMessage(e) {
+ if (e.data.startsWith("choice:")) {
+ window.choice = e.data.split(":")[1];
+ window.useEscape = e.data.split(":")[3];
+ removeEventListener("message", onMessage);
+ resolve();
+ }
+ },
+ false
+ );
+ parent.postMessage("getchoice", "*");
+ });
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+
+ is(document.cookie, "", "No cookies for me");
+ document.cookie = "name=value";
+ is(document.cookie, "", "No cookies for me");
+
+ await fetch("server.sjs")
+ .then(r => r.text())
+ .then(text => {
+ is(text, "cookie-not-present", "We should not have cookies");
+ });
+ // Let's do it twice.
+ await fetch("server.sjs")
+ .then(r => r.text())
+ .then(text => {
+ is(text, "cookie-not-present", "We should not have cookies");
+ });
+
+ is(document.cookie, "", "Still no cookies for me");
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+
+ if (choice == BLOCK) {
+ // We've said no, so cookies are still blocked
+ is(document.cookie, "", "Still no cookies for me");
+ document.cookie = "name=value";
+ is(document.cookie, "", "No cookies for me");
+ } else {
+ // We've said yes, so cookies are allowed now
+ is(document.cookie, "", "No cookies for me");
+ document.cookie = "name=value";
+ is(document.cookie, "name=value", "I have the cookies!");
+ }
+ }
+
+ let permChanged;
+ // Only create the promise when we're going to click one of the allow buttons.
+ if (choice != BLOCK) {
+ permChanged = TestUtils.topicObserved("perm-changed", (subject, data) => {
+ let result;
+ if (choice == ALLOW) {
+ result =
+ subject &&
+ subject
+ .QueryInterface(Ci.nsIPermission)
+ .type.startsWith("3rdPartyStorage^") &&
+ subject.principal.origin == new URL(topPage).origin &&
+ data == "added";
+ }
+ return result;
+ });
+ }
+ let shownPromise = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popupshown"
+ );
+ shownPromise.then(async _ => {
+ if (topPage != gBrowser.currentURI.spec) {
+ return;
+ }
+ ok(showPrompt, "We shouldn't show the prompt when we don't intend to");
+ let notification = await new Promise(function poll(resolve) {
+ let notification = PopupNotifications.getNotification(
+ "storage-access",
+ browser
+ );
+ if (notification) {
+ resolve(notification);
+ return;
+ }
+ setTimeout(poll, 10);
+ });
+ Assert.ok(notification, "Should have gotten the notification");
+
+ if (choice == BLOCK) {
+ if (useEscape) {
+ EventUtils.synthesizeKey("KEY_Escape", {}, window);
+ } else {
+ await clickSecondaryAction();
+ }
+ } else if (choice == ALLOW) {
+ await clickMainAction();
+ }
+ if (choice != BLOCK) {
+ await permChanged;
+ }
+ });
+
+ let url = TEST_3RD_PARTY_PAGE + "?disableWaitUntilPermission";
+ let ct = SpecialPowers.spawn(
+ browser,
+ [{ page: url, callback: runChecks.toString(), choice, useEscape }],
+ async function (obj) {
+ await new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.onload = function () {
+ info("Sending code to the 3rd party content");
+ ifr.contentWindow.postMessage(obj.callback, "*");
+ };
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ if (event.data == "getchoice") {
+ ifr.contentWindow.postMessage(
+ "choice:" + obj.choice + ":useEscape:" + obj.useEscape,
+ "*"
+ );
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ });
+ }
+ );
+ if (showPrompt) {
+ await Promise.all([ct, shownPromise]);
+ } else {
+ await Promise.all([ct, permChanged]);
+ }
+
+ let permissionPopupPromise = BrowserTestUtils.waitForEvent(
+ window,
+ "popupshown",
+ true,
+ event => event.target == gPermissionPanel._permissionPopup
+ );
+ gPermissionPanel._identityPermissionBox.click();
+ await permissionPopupPromise;
+ let permissionItem = document.querySelector(
+ ".permission-popup-permission-item-3rdPartyStorage"
+ );
+ ok(permissionItem, "Permission item exists");
+ ok(
+ BrowserTestUtils.is_visible(permissionItem),
+ "Permission item visible in the identity panel"
+ );
+ let permissionLearnMoreLink = document.getElementById(
+ "permission-popup-storage-access-permission-learn-more"
+ );
+ ok(permissionLearnMoreLink, "Permission learn more link exists");
+ ok(
+ BrowserTestUtils.is_visible(permissionLearnMoreLink),
+ "Permission learn more link is visible in the identity panel"
+ );
+ permissionPopupPromise = BrowserTestUtils.waitForEvent(
+ gPermissionPanel._permissionPopup,
+ "popuphidden"
+ );
+ gPermissionPanel._permissionPopup.hidePopup();
+ await permissionPopupPromise;
+
+ BrowserTestUtils.removeTab(tab);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+}
+
+async function preparePermissionsFromOtherSites(topPage) {
+ info("Faking permissions from other sites");
+ let type = "3rdPartyStorage^https://tracking.example.org";
+ let permission = Services.perms.ALLOW_ACTION;
+ let expireType = Services.perms.EXPIRE_SESSION;
+ if (topPage == TEST_TOP_PAGE) {
+ // For the first page, don't do anything
+ } else if (topPage == TEST_TOP_PAGE_2) {
+ // For the second page, only add the permission from the first page
+ PermissionTestUtils.add(TEST_DOMAIN, type, permission, expireType, 0);
+ } else if (topPage == TEST_TOP_PAGE_3) {
+ // For the third page, add the permissions from the first two pages
+ PermissionTestUtils.add(TEST_DOMAIN, type, permission, expireType, 0);
+ PermissionTestUtils.add(TEST_DOMAIN_2, type, permission, expireType, 0);
+ } else if (topPage == TEST_TOP_PAGE_4) {
+ // For the fourth page, add the permissions from the first three pages
+ PermissionTestUtils.add(TEST_DOMAIN, type, permission, expireType, 0);
+ PermissionTestUtils.add(TEST_DOMAIN_2, type, permission, expireType, 0);
+ PermissionTestUtils.add(TEST_DOMAIN_3, type, permission, expireType, 0);
+ } else if (topPage == TEST_TOP_PAGE_5) {
+ // For the fifth page, add the permissions from the first four pages
+ PermissionTestUtils.add(TEST_DOMAIN, type, permission, expireType, 0);
+ PermissionTestUtils.add(TEST_DOMAIN_2, type, permission, expireType, 0);
+ PermissionTestUtils.add(TEST_DOMAIN_3, type, permission, expireType, 0);
+ PermissionTestUtils.add(TEST_DOMAIN_4, type, permission, expireType, 0);
+ } else if (topPage == TEST_TOP_PAGE_6) {
+ // For the sixth page, add the permissions from the first five pages
+ PermissionTestUtils.add(TEST_DOMAIN, type, permission, expireType, 0);
+ PermissionTestUtils.add(TEST_DOMAIN_2, type, permission, expireType, 0);
+ PermissionTestUtils.add(TEST_DOMAIN_3, type, permission, expireType, 0);
+ PermissionTestUtils.add(TEST_DOMAIN_4, type, permission, expireType, 0);
+ PermissionTestUtils.add(TEST_DOMAIN_5, type, permission, expireType, 0);
+ } else {
+ ok(false, "Unexpected top page: " + topPage);
+ }
+}
+
+async function cleanUp() {
+ info("Cleaning up.");
+ SpecialPowers.clearUserPref("network.cookie.sameSite.laxByDefault");
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+}
+
+async function runRound(topPage, showPrompt, maxConcurrent, disableWebcompat) {
+ if (showPrompt) {
+ await preparePermissionsFromOtherSites(topPage);
+ await testDoorHanger(
+ BLOCK,
+ showPrompt,
+ true,
+ topPage,
+ maxConcurrent,
+ disableWebcompat
+ );
+ await cleanUp();
+ await preparePermissionsFromOtherSites(topPage);
+ await testDoorHanger(
+ BLOCK,
+ showPrompt,
+ false,
+ topPage,
+ maxConcurrent,
+ disableWebcompat
+ );
+ await cleanUp();
+ await preparePermissionsFromOtherSites(topPage);
+ await testDoorHanger(
+ ALLOW,
+ showPrompt,
+ false,
+ topPage,
+ maxConcurrent,
+ disableWebcompat
+ );
+ await cleanUp();
+ } else {
+ await preparePermissionsFromOtherSites(topPage);
+ await testDoorHanger(
+ ALLOW,
+ showPrompt,
+ false,
+ topPage,
+ maxConcurrent,
+ disableWebcompat
+ );
+ }
+ await cleanUp();
+}
+
+add_task(async function test_combinations() {
+ await runRound(TEST_TOP_PAGE, false, 1);
+ await runRound(TEST_TOP_PAGE_2, true, 1);
+ await runRound(TEST_TOP_PAGE, false, 5);
+ await runRound(TEST_TOP_PAGE_2, false, 5);
+ await runRound(TEST_TOP_PAGE_3, false, 5);
+ await runRound(TEST_TOP_PAGE_4, false, 5);
+ await runRound(TEST_TOP_PAGE_5, false, 5);
+ await runRound(TEST_TOP_PAGE_6, true, 5);
+});
+
+add_task(async function test_disableWebcompat() {
+ await runRound(TEST_TOP_PAGE, true, 5, true);
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessFrameInteractionGrantsUserInteraction.js b/toolkit/components/antitracking/test/browser/browser_storageAccessFrameInteractionGrantsUserInteraction.js
new file mode 100644
index 0000000000..13b882c0c7
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessFrameInteractionGrantsUserInteraction.js
@@ -0,0 +1,70 @@
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/browser/modules/test/browser/head.js",
+ this
+);
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/toolkit/components/antitracking/test/browser/storage_access_head.js",
+ this
+);
+/* import-globals-from storageAccessAPIHelpers.js */
+
+async function testEmbeddedPageBehavior() {
+ // Get the storage access permission
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ let p = document.requestStorageAccess();
+ try {
+ await p;
+ ok(true, "gain storage access.");
+ } catch {
+ ok(false, "denied storage access.");
+ }
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+
+ // Wait until we have the permission before we remove it.
+ waitUntilPermission(
+ "https://tracking.example.org/",
+ "storageAccessAPI",
+ SpecialPowers.Services.perms.ALLOW_ACTION
+ );
+
+ // Remove the storageAccessAPI permission
+ SpecialPowers.removePermission(
+ "storageAccessAPI",
+ "https://tracking.example.org/"
+ );
+
+ // Wait until the permission is removed
+ waitUntilPermission(
+ "https://tracking.example.org/",
+ "storageAccessAPI",
+ SpecialPowers.Services.perms.UNKNOWN_ACTION
+ );
+
+ // Interact with the third-party iframe and wait for the permission to appear
+ SpecialPowers.wrap(document).userInteractionForTesting();
+ waitUntilPermission(
+ "https://tracking.example.org/",
+ "storageAccessAPI",
+ SpecialPowers.Services.perms.ALLOW_ACTION
+ );
+}
+
+// This test verifies that interacting with a third-party iframe with
+// storage access gives the storageAccessAPI permission, as if it were a first
+// party. This is done by loading a page, then within an iframe in that page
+// requesting storage access, ensuring there is no storageAccessAPI permission,
+// then interacting with the page and waiting for that storageAccessAPI
+// permission to reappear.
+add_task(async function testInteractionGivesPermission() {
+ await setPreferences();
+
+ await openPageAndRunCode(
+ TEST_TOP_PAGE,
+ getExpectPopupAndClick("accept"),
+ TEST_3RD_PARTY_PAGE,
+ testEmbeddedPageBehavior
+ );
+
+ await cleanUpData();
+ await SpecialPowers.flushPrefEnv();
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessGrantedGivesUserInteraction.js b/toolkit/components/antitracking/test/browser/browser_storageAccessGrantedGivesUserInteraction.js
new file mode 100644
index 0000000000..cd70d8c059
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessGrantedGivesUserInteraction.js
@@ -0,0 +1,40 @@
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/browser/modules/test/browser/head.js",
+ this
+);
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/toolkit/components/antitracking/test/browser/storage_access_head.js",
+ this
+);
+/* import-globals-from storageAccessAPIHelpers.js */
+
+async function testEmbeddedPageBehavior() {
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ var p = document.requestStorageAccess();
+ try {
+ await p;
+ ok(true, "gain storage access.");
+ } catch {
+ ok(false, "denied storage access.");
+ }
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ waitUntilPermission(
+ "https://tracking.example.org/",
+ "storageAccessAPI",
+ SpecialPowers.Services.perms.ALLOW_ACTION
+ );
+}
+
+add_task(async function testGrantGivesPermission() {
+ await setPreferences();
+
+ await openPageAndRunCode(
+ TEST_TOP_PAGE,
+ getExpectPopupAndClick("accept"),
+ TEST_3RD_PARTY_PAGE,
+ testEmbeddedPageBehavior
+ );
+
+ await cleanUpData();
+ await SpecialPowers.flushPrefEnv();
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessPrivilegeAPI.js b/toolkit/components/antitracking/test/browser/browser_storageAccessPrivilegeAPI.js
new file mode 100644
index 0000000000..ba109768be
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessPrivilegeAPI.js
@@ -0,0 +1,617 @@
+//
+// Bug 1724376 - Tests for the privilege requestStorageAccessForOrigin API.
+//
+
+/* import-globals-from storageAccessAPIHelpers.js */
+
+"use strict";
+
+const TEST_ANOTHER_TRACKER_DOMAIN = "https://itisatracker.org/";
+const TEST_ANOTHER_TRACKER_PAGE =
+ TEST_ANOTHER_TRACKER_DOMAIN + TEST_PATH + "3rdParty.html";
+const TEST_ANOTHER_4TH_PARTY_DOMAIN = "https://test1.example.org/";
+const TEST_ANOTHER_4TH_PARTY_PAGE =
+ TEST_ANOTHER_4TH_PARTY_DOMAIN + TEST_PATH + "3rdParty.html";
+
+// Insert an iframe with the given id into the content.
+async function insertSubFrame(browser, url, id) {
+ return SpecialPowers.spawn(browser, [url, id], async (url, id) => {
+ let ifr = content.document.createElement("iframe");
+ ifr.setAttribute("id", id);
+
+ let loaded = ContentTaskUtils.waitForEvent(ifr, "load", false);
+ content.document.body.appendChild(ifr);
+ ifr.src = url;
+ await loaded;
+ });
+}
+
+// Run the given script in the iframe with the given id.
+function runScriptInSubFrame(browser, id, script) {
+ return SpecialPowers.spawn(
+ browser,
+ [{ callback: script.toString(), id }],
+ async obj => {
+ await new content.Promise(resolve => {
+ let ifr = content.document.getElementById(obj.id);
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ ifr.contentWindow.postMessage(obj.callback, "*");
+ });
+ }
+ );
+}
+
+function waitStoragePermission(trackingOrigin) {
+ return TestUtils.topicObserved("perm-changed", (aSubject, aData) => {
+ let permission = aSubject.QueryInterface(Ci.nsIPermission);
+ let uri = Services.io.newURI(TEST_DOMAIN);
+ return (
+ permission.type == `3rdPartyStorage^${trackingOrigin}` &&
+ permission.principal.equalsURI(uri)
+ );
+ });
+}
+
+function clearStoragePermission(trackingOrigin) {
+ return SpecialPowers.removePermission(
+ `3rdPartyStorage^${trackingOrigin}`,
+ TEST_TOP_PAGE
+ );
+}
+
+function triggerCommand(button) {
+ let notifications = PopupNotifications.panel.children;
+ let notification = notifications[0];
+ EventUtils.synthesizeMouseAtCenter(notification[button], {});
+}
+
+function triggerMainCommand() {
+ triggerCommand("button");
+}
+
+function triggerSecondaryCommand() {
+ triggerCommand("secondaryButton");
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.auto_grants", true],
+ ["dom.storage_access.auto_grants.delayed", false],
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.prompt.testing", false],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ // Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default"
+ ["network.cookie.sameSite.laxByDefault", false],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ registerCleanupFunction(() => {
+ SpecialPowers.clearUserPref("network.cookie.sameSite.laxByDefault");
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ });
+});
+
+add_task(async function test_api_only_available_in_privilege_scope() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_TOP_PAGE
+ );
+ let browser = tab.linkedBrowser;
+
+ await SpecialPowers.spawn(browser, [], async _ => {
+ ok(
+ content.document.requestStorageAccessForOrigin,
+ "The privilege API is available in system privilege code."
+ );
+ });
+
+ // Open an iframe and check if the privilege is not available in content code.
+ await insertSubFrame(browser, TEST_3RD_PARTY_PAGE, "test");
+ await runScriptInSubFrame(browser, "test", async function check() {
+ ok(
+ !document.requestStorageAccessForOrigin,
+ "The privilege API is not available in content code."
+ );
+ });
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_privilege_api_with_reject_tracker() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ ],
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_TOP_PAGE
+ );
+ let browser = tab.linkedBrowser;
+
+ await insertSubFrame(browser, TEST_3RD_PARTY_PAGE, "test");
+
+ // Verify that the third party tracker doesn't have storage access at
+ // beginning.
+ await runScriptInSubFrame(browser, "test", async _ => {
+ await noStorageAccessInitially();
+
+ is(document.cookie, "", "No cookies for me");
+ document.cookie = "name=value";
+ is(document.cookie, "", "Setting cookie is blocked");
+ });
+
+ let storagePermissionPromise = waitStoragePermission(
+ "https://tracking.example.org"
+ );
+
+ // Verify if the prompt has been shown.
+ let shownPromise = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popupshown"
+ );
+
+ // Call the privilege API.
+ let callAPIPromise = SpecialPowers.spawn(browser, [], async _ => {
+ // The privilege API requires user activation. So, we set the user
+ // activation flag before we call the API.
+ content.document.notifyUserGestureActivation();
+
+ try {
+ await content.document.requestStorageAccessForOrigin(
+ "https://tracking.example.org/"
+ );
+ } catch (e) {
+ ok(false, "The API shouldn't throw.");
+ }
+
+ content.document.clearUserGestureActivation();
+ });
+
+ await shownPromise;
+
+ // Accept the prompt
+ triggerMainCommand();
+
+ await callAPIPromise;
+
+ // Verify if the storage access permission is set correctly.
+ await storagePermissionPromise;
+
+ // Verify if the existing third-party tracker iframe gains the storage
+ // access.
+ await runScriptInSubFrame(browser, "test", async _ => {
+ await hasStorageAccessInitially();
+
+ is(document.cookie, "", "Still no cookies for me");
+ document.cookie = "name=value";
+ is(document.cookie, "name=value", "Successfully set cookies.");
+ });
+
+ // Insert another third-party tracker iframe and check if it has storage access.
+ await insertSubFrame(browser, TEST_3RD_PARTY_PAGE, "test2");
+ await runScriptInSubFrame(browser, "test2", async _ => {
+ await hasStorageAccessInitially();
+
+ is(document.cookie, "name=value", "Some cookies for me");
+ });
+
+ // Insert another iframe with different third-party tracker and check it has
+ // no storage access.
+ await insertSubFrame(browser, TEST_ANOTHER_TRACKER_PAGE, "test3");
+ await runScriptInSubFrame(browser, "test3", async _ => {
+ await noStorageAccessInitially();
+
+ is(document.cookie, "", "No cookies for me");
+ document.cookie = "name=value";
+ is(document.cookie, "", "Setting cookie is blocked for another tracker.");
+ });
+
+ await clearStoragePermission("https://tracking.example.org");
+ Services.cookies.removeAll();
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_privilege_api_with_dFPI() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ ],
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_TOP_PAGE
+ );
+ let browser = tab.linkedBrowser;
+
+ await insertSubFrame(browser, TEST_4TH_PARTY_PAGE, "test");
+
+ // Verify that the third-party context doesn't have storage access at
+ // beginning.
+ await runScriptInSubFrame(browser, "test", async _ => {
+ await noStorageAccessInitially();
+
+ is(document.cookie, "", "No cookies for me");
+ document.cookie = "name=partitioned";
+ is(
+ document.cookie,
+ "name=partitioned",
+ "Setting cookie in partitioned context."
+ );
+ });
+
+ let storagePermissionPromise = waitStoragePermission(
+ "http://not-tracking.example.com"
+ );
+
+ // Verify if the prompt has been shown.
+ let shownPromise = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popupshown"
+ );
+
+ // Call the privilege API.
+ let callAPIPromise = SpecialPowers.spawn(browser, [], async _ => {
+ // The privilege API requires a user gesture. So, we set the user handling
+ // flag before we call the API.
+ content.document.notifyUserGestureActivation();
+
+ try {
+ await content.document.requestStorageAccessForOrigin(
+ "http://not-tracking.example.com/"
+ );
+ } catch (e) {
+ ok(false, "The API shouldn't throw.");
+ }
+
+ content.document.clearUserGestureActivation();
+ });
+
+ await shownPromise;
+
+ // Accept the prompt
+ triggerMainCommand();
+
+ await callAPIPromise;
+
+ // Verify if the storage access permission is set correctly.
+ await storagePermissionPromise;
+
+ // Verify if the existing third-party iframe gains the storage access.
+ await runScriptInSubFrame(browser, "test", async _ => {
+ await hasStorageAccessInitially();
+
+ is(document.cookie, "", "No unpartitioned cookies");
+ document.cookie = "name=unpartitioned";
+ is(document.cookie, "name=unpartitioned", "Successfully set cookies.");
+ });
+
+ // Insert another third-party content iframe and check if it has storage access.
+ await insertSubFrame(browser, TEST_4TH_PARTY_PAGE, "test2");
+ await runScriptInSubFrame(browser, "test2", async _ => {
+ await hasStorageAccessInitially();
+
+ is(
+ document.cookie,
+ "name=unpartitioned",
+ "Some cookies for unpartitioned context"
+ );
+ });
+
+ // Insert another iframe with different third-party content and check it has
+ // no storage access.
+ await insertSubFrame(browser, TEST_ANOTHER_4TH_PARTY_PAGE, "test3");
+ await runScriptInSubFrame(browser, "test3", async _ => {
+ await noStorageAccessInitially();
+
+ is(document.cookie, "", "No cookies for me");
+ document.cookie = "name=value";
+ is(document.cookie, "name=value", "Setting cookie to partitioned context.");
+ });
+
+ await clearStoragePermission("http://not-tracking.example.com");
+ Services.cookies.removeAll();
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_prompt() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.auto_grants", false],
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ ],
+ });
+
+ for (const allow of [false, true]) {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_TOP_PAGE
+ );
+ let browser = tab.linkedBrowser;
+
+ // Verify if the prompt has been shown.
+ let shownPromise = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popupshown"
+ );
+
+ let hiddenPromise = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popuphidden"
+ );
+
+ // Call the privilege API.
+ let callAPIPromise = SpecialPowers.spawn(browser, [allow], async allow => {
+ // The privilege API requires a user gesture. So, we set the user handling
+ // flag before we call the API.
+ content.document.notifyUserGestureActivation();
+ let isThrown = false;
+
+ try {
+ await content.document.requestStorageAccessForOrigin(
+ "https://tracking.example.org"
+ );
+ } catch (e) {
+ isThrown = true;
+ }
+
+ is(isThrown, !allow, `The API ${allow ? "shouldn't" : "should"} throw.`);
+
+ content.document.clearUserGestureActivation();
+ });
+
+ await shownPromise;
+
+ let notification = await TestUtils.waitForCondition(_ =>
+ PopupNotifications.getNotification("storage-access", browser)
+ );
+ ok(notification, "Should have gotten the notification");
+
+ // Click the popup button.
+ if (allow) {
+ triggerMainCommand();
+ } else {
+ triggerSecondaryCommand();
+ }
+
+ // Wait until the popup disappears.
+ await hiddenPromise;
+
+ // Wait until the API finishes.
+ await callAPIPromise;
+
+ await insertSubFrame(browser, TEST_3RD_PARTY_PAGE, "test");
+
+ if (allow) {
+ await runScriptInSubFrame(browser, "test", async _ => {
+ await hasStorageAccessInitially();
+
+ is(document.cookie, "", "Still no cookies for me");
+ document.cookie = "name=value";
+ is(document.cookie, "name=value", "Successfully set cookies.");
+ });
+ } else {
+ await runScriptInSubFrame(browser, "test", async _ => {
+ await noStorageAccessInitially();
+
+ is(document.cookie, "", "Still no cookies for me");
+ document.cookie = "name=value";
+ is(document.cookie, "", "No cookie after setting.");
+ });
+ }
+
+ BrowserTestUtils.removeTab(tab);
+ }
+
+ await clearStoragePermission("https://tracking.example.org");
+ Services.cookies.removeAll();
+});
+
+// Tests that the priviledged rSA method should show a prompt when auto grants
+// are enabled, but we don't have user activation. When requiring user
+// activation, rSA should still reject.
+add_task(async function test_prompt_no_user_activation() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.auto_grants", true],
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ ],
+ });
+
+ for (let requireUserActivation of [false, true]) {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_TOP_PAGE
+ );
+ let browser = tab.linkedBrowser;
+
+ let shownPromise, hiddenPromise;
+
+ // Verify if the prompt has been shown.
+ if (!requireUserActivation) {
+ shownPromise = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popupshown"
+ );
+
+ hiddenPromise = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popuphidden"
+ );
+ }
+
+ // Call the privilege API.
+ let callAPIPromise = SpecialPowers.spawn(
+ browser,
+ [requireUserActivation],
+ async requireUserActivation => {
+ let isThrown = false;
+
+ try {
+ await content.document.requestStorageAccessForOrigin(
+ "https://tracking.example.org",
+ requireUserActivation
+ );
+ } catch (e) {
+ isThrown = true;
+ }
+
+ is(
+ isThrown,
+ requireUserActivation,
+ `The API ${requireUserActivation ? "shouldn't" : "should"} throw.`
+ );
+ }
+ );
+
+ if (!requireUserActivation) {
+ await shownPromise;
+
+ let notification = await TestUtils.waitForCondition(_ =>
+ PopupNotifications.getNotification("storage-access", browser)
+ );
+ ok(notification, "Should have gotten the notification");
+
+ // Click the popup button.
+ triggerMainCommand();
+
+ // Wait until the popup disappears.
+ await hiddenPromise;
+ }
+
+ // Wait until the API finishes.
+ await callAPIPromise;
+
+ await insertSubFrame(browser, TEST_3RD_PARTY_PAGE, "test");
+
+ if (!requireUserActivation) {
+ await runScriptInSubFrame(browser, "test", async _ => {
+ await hasStorageAccessInitially();
+
+ is(document.cookie, "", "Still no cookies for me");
+ document.cookie = "name=value";
+ is(document.cookie, "name=value", "Successfully set cookies.");
+ });
+ } else {
+ await runScriptInSubFrame(browser, "test", async _ => {
+ await noStorageAccessInitially();
+
+ is(document.cookie, "", "Still no cookies for me");
+ document.cookie = "name=value";
+ is(document.cookie, "", "No cookie after setting.");
+ });
+ }
+
+ BrowserTestUtils.removeTab(tab);
+ await clearStoragePermission("https://tracking.example.org");
+ Services.cookies.removeAll();
+ }
+});
+
+add_task(async function test_invalid_input() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_TOP_PAGE
+ );
+ let browser = tab.linkedBrowser;
+
+ await SpecialPowers.spawn(browser, [], async _ => {
+ let isThrown = false;
+ try {
+ await content.document.requestStorageAccessForOrigin(
+ "https://tracking.example.org"
+ );
+ } catch (e) {
+ isThrown = true;
+ }
+ ok(isThrown, "The API should throw without user gesture.");
+
+ content.document.notifyUserGestureActivation();
+ isThrown = false;
+ try {
+ await content.document.requestStorageAccessForOrigin();
+ } catch (e) {
+ isThrown = true;
+ }
+ ok(isThrown, "The API should throw with no input.");
+
+ content.document.notifyUserGestureActivation();
+ isThrown = false;
+ try {
+ await content.document.requestStorageAccessForOrigin("");
+ } catch (e) {
+ isThrown = true;
+ is(e.name, "NS_ERROR_MALFORMED_URI", "The input is not a valid url");
+ }
+ ok(isThrown, "The API should throw with empty string.");
+
+ content.document.notifyUserGestureActivation();
+ isThrown = false;
+ try {
+ await content.document.requestStorageAccessForOrigin("invalid url");
+ } catch (e) {
+ isThrown = true;
+ is(e.name, "NS_ERROR_MALFORMED_URI", "The input is not a valid url");
+ }
+ ok(isThrown, "The API should throw with invalid url.");
+
+ content.document.clearUserGestureActivation();
+ });
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseRejectHandlerUserInteraction.js b/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseRejectHandlerUserInteraction.js
new file mode 100644
index 0000000000..835c02e262
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseRejectHandlerUserInteraction.js
@@ -0,0 +1,36 @@
+AntiTracking.runTest(
+ "Storage Access API returns promises that do not maintain user activation for calling its reject handler",
+ // blocking callback
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ let [threw, rejected] = await callRequestStorageAccess(() => {
+ ok(
+ !SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ "Promise reject handler must not have user activation"
+ );
+ }, true);
+ ok(!threw, "requestStorageAccess should not throw");
+ ok(rejected, "requestStorageAccess should not be available");
+ },
+
+ null, // non-blocking callback
+ // cleanup function
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [
+ [
+ "privacy.partition.always_partition_third_party_non_cookie_storage",
+ false,
+ ],
+ ], // extra prefs
+ false, // no window open test
+ false, // no user-interaction test
+ 0, // expected blocking notifications
+ false, // private window
+ "allow-scripts allow-same-origin allow-popups" // iframe sandbox
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseRejectHandlerUserInteraction_alwaysPartition.js b/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseRejectHandlerUserInteraction_alwaysPartition.js
new file mode 100644
index 0000000000..21bf8b7639
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseRejectHandlerUserInteraction_alwaysPartition.js
@@ -0,0 +1,31 @@
+AntiTracking.runTest(
+ "Storage Access API returns promises that do not maintain user activation for calling its reject handler",
+ // blocking callback
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ let [threw, rejected] = await callRequestStorageAccess(() => {
+ ok(
+ !SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ "Promise reject handler must not have user activation"
+ );
+ }, true);
+ ok(!threw, "requestStorageAccess should not throw");
+ ok(rejected, "requestStorageAccess should not be available");
+ },
+
+ null, // non-blocking callback
+ // cleanup function
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [["privacy.partition.always_partition_third_party_non_cookie_storage", true]], // extra prefs
+ false, // no window open test
+ false, // no user-interaction test
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER, // expected blocking notifications
+ false, // private window
+ "allow-scripts allow-same-origin allow-popups" // iframe sandbox
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseResolveHandlerUserInteraction.js b/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseResolveHandlerUserInteraction.js
new file mode 100644
index 0000000000..6db7c4c241
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseResolveHandlerUserInteraction.js
@@ -0,0 +1,37 @@
+AntiTracking.runTest(
+ "Storage Access API returns promises that maintain user activation",
+ // blocking callback
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ let [threw, rejected] = await callRequestStorageAccess(() => {
+ ok(
+ SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ "Promise handler must run as if we're handling user input"
+ );
+ });
+ ok(!threw, "requestStorageAccess should not throw");
+ ok(!rejected, "requestStorageAccess should be available");
+
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+
+ await document.hasStorageAccess();
+
+ ok(
+ SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ "Promise handler must run as if we're handling user input"
+ );
+ },
+
+ null, // non-blocking callback
+ // cleanup function
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ null, // extra prefs
+ false, // no window open test
+ false // no user-interaction test
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateSubframe.js b/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateSubframe.js
new file mode 100644
index 0000000000..f658dde790
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateSubframe.js
@@ -0,0 +1,46 @@
+AntiTracking.runTest(
+ "Storage Access is removed when subframe navigates",
+ // blocking callback
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+ },
+
+ // non-blocking callback
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await hasStorageAccessInitially();
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ let [threw, rejected] = await callRequestStorageAccess();
+ ok(!threw, "requestStorageAccess should not throw");
+ ok(!rejected, "requestStorageAccess should be available");
+ },
+ // cleanup function
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [
+ [
+ "privacy.partition.always_partition_third_party_non_cookie_storage",
+ false,
+ ],
+ ], // extra prefs
+ false, // no window open test
+ false, // no user-interaction test
+ 0, // no blocking notifications
+ false, // run in normal window
+ null, // no iframe sandbox
+ "navigate-subframe", // access removal type
+ // after-removal callback
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ // TODO: this is just a temporarily fixed, we should update the testcase
+ // in Bug 1649399
+ await hasStorageAccessInitially();
+ }
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateSubframe_alwaysPartition.js b/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateSubframe_alwaysPartition.js
new file mode 100644
index 0000000000..b4355f2a24
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateSubframe_alwaysPartition.js
@@ -0,0 +1,41 @@
+AntiTracking.runTest(
+ "Storage Access is removed when subframe navigates",
+ // blocking callback
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+ },
+
+ // non-blocking callback
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await hasStorageAccessInitially();
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ let [threw, rejected] = await callRequestStorageAccess();
+ ok(!threw, "requestStorageAccess should not throw");
+ ok(!rejected, "requestStorageAccess should be available");
+ },
+ // cleanup function
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [["privacy.partition.always_partition_third_party_non_cookie_storage", true]], // extra prefs
+ false, // no window open test
+ false, // no user-interaction test
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER, // expected blocking notifications
+ false, // run in normal window
+ null, // no iframe sandbox
+ "navigate-subframe", // access removal type
+ // after-removal callback
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ // TODO: this is just a temporarily fixed, we should update the testcase
+ // in Bug 1649399
+ await hasStorageAccessInitially();
+ }
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateTopframe.js b/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateTopframe.js
new file mode 100644
index 0000000000..fe79a8d935
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateTopframe.js
@@ -0,0 +1,44 @@
+AntiTracking.runTest(
+ "Storage Access is removed when topframe navigates",
+ // blocking callback
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+ },
+
+ // non-blocking callback
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await hasStorageAccessInitially();
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ let [threw, rejected] = await callRequestStorageAccess();
+ ok(!threw, "requestStorageAccess should not throw");
+ ok(!rejected, "requestStorageAccess should be available");
+ },
+ // cleanup function
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [
+ [
+ "privacy.partition.always_partition_third_party_non_cookie_storage",
+ false,
+ ],
+ ], // extra prefs
+ false, // no window open test
+ false, // no user-interaction test
+ 0, // no blocking notifications
+ false, // run in normal window
+ null, // no iframe sandbox
+ "navigate-topframe", // access removal type
+ // after-removal callback
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+ }
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateTopframe_alwaysPartition.js b/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateTopframe_alwaysPartition.js
new file mode 100644
index 0000000000..63a0480e83
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateTopframe_alwaysPartition.js
@@ -0,0 +1,39 @@
+AntiTracking.runTest(
+ "Storage Access is removed when topframe navigates",
+ // blocking callback
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+ },
+
+ // non-blocking callback
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await hasStorageAccessInitially();
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ let [threw, rejected] = await callRequestStorageAccess();
+ ok(!threw, "requestStorageAccess should not throw");
+ ok(!rejected, "requestStorageAccess should be available");
+ },
+ // cleanup function
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [["privacy.partition.always_partition_third_party_non_cookie_storage", true]], // extra prefs
+ false, // no window open test
+ false, // no user-interaction test
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER, // expected blocking notifications
+ false, // run in normal window
+ null, // no iframe sandbox
+ "navigate-topframe", // access removal type
+ // after-removal callback
+ async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+ }
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessSandboxed.js b/toolkit/components/antitracking/test/browser/browser_storageAccessSandboxed.js
new file mode 100644
index 0000000000..d83bcf4b7b
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessSandboxed.js
@@ -0,0 +1,214 @@
+/* import-globals-from storageAccessAPIHelpers.js */
+
+const APS_PREF =
+ "privacy.partition.always_partition_third_party_non_cookie_storage";
+
+AntiTracking.runTest(
+ "Storage Access API called in a sandboxed iframe",
+ // blocking callback
+ async _ => {
+ let [threw, rejected] = await callRequestStorageAccess();
+ ok(!threw, "requestStorageAccess should not throw");
+ ok(rejected, "requestStorageAccess shouldn't be available");
+ },
+
+ null, // non-blocking callback
+ // cleanup function
+ async _ => {
+ // Only clear the user-interaction permissions for the tracker here so that
+ // the next test has a clean slate.
+ await new Promise(resolve => {
+ Services.clearData.deleteDataFromHost(
+ Services.io.newURI(TEST_3RD_PARTY_DOMAIN).host,
+ true,
+ Ci.nsIClearDataService.CLEAR_PERMISSIONS,
+ value => resolve()
+ );
+ });
+ },
+ [
+ ["dom.storage_access.enabled", true],
+ [APS_PREF, false],
+ ], // extra prefs
+ false, // no window open test
+ false, // no user-interaction test
+ 0, // no blocking notifications
+ false, // run in normal window
+ "allow-scripts allow-same-origin allow-popups"
+);
+
+AntiTracking.runTest(
+ "Exception List can work in a sandboxed iframe",
+ // blocking callback
+ async _ => {
+ await hasStorageAccessInitially();
+
+ try {
+ await navigator.serviceWorker.register("empty.js");
+
+ ok(
+ true,
+ "ServiceWorker can be registered in allowlisted sandboxed iframe!"
+ );
+ } catch (e) {
+ info("Promise rejected: " + e);
+ ok(
+ false,
+ "ServiceWorker should be able to be registered in allowlisted sandboxed iframe"
+ );
+ }
+ },
+
+ null, // non-blocking callback
+ null, // cleanup function
+ [
+ ["dom.storage_access.enabled", true],
+ [
+ "privacy.restrict3rdpartystorage.skip_list",
+ "http://example.net,https://tracking.example.org",
+ ],
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ [APS_PREF, false],
+ ], // extra prefs
+ false, // no window open test
+ false, // no user-interaction test
+ 0, // no blocking notifications
+ false, // run in normal window
+ "allow-scripts allow-same-origin allow-popups"
+);
+
+AntiTracking.runTest(
+ "Storage Access API called in a sandboxed iframe with" +
+ " allow-storage-access-by-user-activation",
+ // blocking callback
+ async _ => {
+ await noStorageAccessInitially();
+
+ let [threw, rejected] = await callRequestStorageAccess();
+ ok(!threw, "requestStorageAccess should not throw");
+ ok(!rejected, "requestStorageAccess should be available");
+ },
+
+ null, // non-blocking callback
+ null, // cleanup function
+ [
+ ["dom.storage_access.enabled", true],
+ [APS_PREF, false],
+ ], // extra prefs
+ false, // no window open test
+ false, // no user-interaction test
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER, // expect blocking notifications
+ false, // run in normal window
+ "allow-scripts allow-same-origin allow-popups allow-storage-access-by-user-activation"
+);
+
+AntiTracking.runTest(
+ "Verify that sandboxed contexts don't get the saved permission",
+ // blocking callback
+ async _ => {
+ await noStorageAccessInitially();
+
+ try {
+ localStorage.foo = 42;
+ ok(false, "LocalStorage cannot be used!");
+ } catch (e) {
+ ok(true, "LocalStorage cannot be used!");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+ },
+
+ null, // non-blocking callback
+ null, // cleanup function
+ [
+ ["dom.storage_access.enabled", true],
+ [APS_PREF, false],
+ ], // extra prefs
+ false, // no window open test
+ false, // no user-interaction test
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER, // expect blocking notifications
+ false, // run in normal window
+ "allow-scripts allow-same-origin allow-popups"
+);
+
+AntiTracking.runTest(
+ "Verify that sandboxed contexts with" +
+ " allow-storage-access-by-user-activation get the" +
+ " saved permission",
+ // blocking callback
+ async _ => {
+ await hasStorageAccessInitially();
+
+ localStorage.foo = 42;
+ ok(true, "LocalStorage can be used!");
+ },
+
+ null, // non-blocking callback
+ null, // cleanup function
+ [
+ ["dom.storage_access.enabled", true],
+ [APS_PREF, false],
+ ], // extra prefs
+ false, // no window open test
+ false, // no user-interaction test
+ 0, // no blocking notifications
+ false, // run in normal window
+ "allow-scripts allow-same-origin allow-popups allow-storage-access-by-user-activation"
+);
+
+AntiTracking.runTest(
+ "Verify that private browsing contexts don't get the saved permission",
+ // blocking callback
+ async _ => {
+ await noStorageAccessInitially();
+
+ try {
+ localStorage.foo = 42;
+ ok(false, "LocalStorage cannot be used!");
+ } catch (e) {
+ ok(true, "LocalStorage cannot be used!");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+ },
+
+ null, // non-blocking callback
+ null, // cleanup function
+ [
+ ["dom.storage_access.enabled", true],
+ [APS_PREF, false],
+ ], // extra prefs
+ false, // no window open test
+ false, // no user-interaction test
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER, // expect blocking notifications
+ true, // run in private window
+ null // iframe sandbox
+);
+
+AntiTracking.runTest(
+ "Verify that non-sandboxed contexts get the saved permission",
+ // blocking callback
+ async _ => {
+ await hasStorageAccessInitially();
+
+ localStorage.foo = 42;
+ ok(true, "LocalStorage can be used!");
+ },
+
+ null, // non-blocking callback
+ // cleanup function
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [
+ ["dom.storage_access.enabled", true],
+ [APS_PREF, false],
+ ], // extra prefs
+ false, // no window open test
+ false, // no user-interaction test
+ 0 // no blocking notifications
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessSandboxed_alwaysPartition.js b/toolkit/components/antitracking/test/browser/browser_storageAccessSandboxed_alwaysPartition.js
new file mode 100644
index 0000000000..402ba74ec8
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessSandboxed_alwaysPartition.js
@@ -0,0 +1,214 @@
+/* import-globals-from storageAccessAPIHelpers.js */
+
+const APS_PREF =
+ "privacy.partition.always_partition_third_party_non_cookie_storage";
+
+AntiTracking.runTest(
+ "Storage Access API called in a sandboxed iframe",
+ // blocking callback
+ async _ => {
+ let [threw, rejected] = await callRequestStorageAccess();
+ ok(!threw, "requestStorageAccess should not throw");
+ ok(rejected, "requestStorageAccess shouldn't be available");
+ },
+
+ null, // non-blocking callback
+ // cleanup function
+ async _ => {
+ // Only clear the user-interaction permissions for the tracker here so that
+ // the next test has a clean slate.
+ await new Promise(resolve => {
+ Services.clearData.deleteDataFromHost(
+ Services.io.newURI(TEST_3RD_PARTY_DOMAIN).host,
+ true,
+ Ci.nsIClearDataService.CLEAR_PERMISSIONS,
+ value => resolve()
+ );
+ });
+ },
+ [
+ ["dom.storage_access.enabled", true],
+ [APS_PREF, true],
+ ], // extra prefs
+ false, // no window open test
+ false, // no user-interaction test
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER, // expected blocking notifications
+ false, // run in normal window
+ "allow-scripts allow-same-origin allow-popups"
+);
+
+AntiTracking.runTest(
+ "Exception List can work in a sandboxed iframe",
+ // blocking callback
+ async _ => {
+ await hasStorageAccessInitially();
+
+ try {
+ await navigator.serviceWorker.register("empty.js");
+
+ ok(
+ true,
+ "ServiceWorker can be registered in allowlisted sandboxed iframe!"
+ );
+ } catch (e) {
+ info("Promise rejected: " + e);
+ ok(
+ false,
+ "ServiceWorker should be able to be registered in allowlisted sandboxed iframe"
+ );
+ }
+ },
+
+ null, // non-blocking callback
+ null, // cleanup function
+ [
+ ["dom.storage_access.enabled", true],
+ [
+ "privacy.restrict3rdpartystorage.skip_list",
+ "http://example.net,https://tracking.example.org",
+ ],
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ [APS_PREF, true],
+ ], // extra prefs
+ false, // no window open test
+ false, // no user-interaction test
+ 0, // no blocking notifications
+ false, // run in normal window
+ "allow-scripts allow-same-origin allow-popups"
+);
+
+AntiTracking.runTest(
+ "Storage Access API called in a sandboxed iframe with" +
+ " allow-storage-access-by-user-activation",
+ // blocking callback
+ async _ => {
+ await noStorageAccessInitially();
+
+ let [threw, rejected] = await callRequestStorageAccess();
+ ok(!threw, "requestStorageAccess should not throw");
+ ok(!rejected, "requestStorageAccess should be available");
+ },
+
+ null, // non-blocking callback
+ null, // cleanup function
+ [
+ ["dom.storage_access.enabled", true],
+ [APS_PREF, true],
+ ], // extra prefs
+ false, // no window open test
+ false, // no user-interaction test
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER, // expect blocking notifications
+ false, // run in normal window
+ "allow-scripts allow-same-origin allow-popups allow-storage-access-by-user-activation"
+);
+
+AntiTracking.runTest(
+ "Verify that sandboxed contexts don't get the saved permission",
+ // blocking callback
+ async _ => {
+ await noStorageAccessInitially();
+
+ try {
+ localStorage.foo = 42;
+ ok(false, "LocalStorage cannot be used!");
+ } catch (e) {
+ ok(true, "LocalStorage cannot be used!");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+ },
+
+ null, // non-blocking callback
+ null, // cleanup function
+ [
+ ["dom.storage_access.enabled", true],
+ [APS_PREF, true],
+ ], // extra prefs
+ false, // no window open test
+ false, // no user-interaction test
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER, // expect blocking notifications
+ false, // run in normal window
+ "allow-scripts allow-same-origin allow-popups"
+);
+
+AntiTracking.runTest(
+ "Verify that sandboxed contexts with" +
+ " allow-storage-access-by-user-activation get the" +
+ " saved permission",
+ // blocking callback
+ async _ => {
+ await hasStorageAccessInitially();
+
+ localStorage.foo = 42;
+ ok(true, "LocalStorage can be used!");
+ },
+
+ null, // non-blocking callback
+ null, // cleanup function
+ [
+ ["dom.storage_access.enabled", true],
+ [APS_PREF, true],
+ ], // extra prefs
+ false, // no window open test
+ false, // no user-interaction test
+ 0, // no blocking notifications
+ false, // run in normal window
+ "allow-scripts allow-same-origin allow-popups allow-storage-access-by-user-activation"
+);
+
+AntiTracking.runTest(
+ "Verify that private browsing contexts don't get the saved permission",
+ // blocking callback
+ async _ => {
+ await noStorageAccessInitially();
+
+ try {
+ localStorage.foo = 42;
+ ok(false, "LocalStorage cannot be used!");
+ } catch (e) {
+ ok(true, "LocalStorage cannot be used!");
+ is(e.name, "SecurityError", "We want a security error message.");
+ }
+ },
+
+ null, // non-blocking callback
+ null, // cleanup function
+ [
+ ["dom.storage_access.enabled", true],
+ [APS_PREF, true],
+ ], // extra prefs
+ false, // no window open test
+ false, // no user-interaction test
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER, // expect blocking notifications
+ true, // run in private window
+ null // iframe sandbox
+);
+
+AntiTracking.runTest(
+ "Verify that non-sandboxed contexts get the saved permission",
+ // blocking callback
+ async _ => {
+ await hasStorageAccessInitially();
+
+ localStorage.foo = 42;
+ ok(true, "LocalStorage can be used!");
+ },
+
+ null, // non-blocking callback
+ // cleanup function
+ async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ },
+ [
+ ["dom.storage_access.enabled", true],
+ [APS_PREF, true],
+ ], // extra prefs
+ false, // no window open test
+ false, // no user-interaction test
+ 0 // no blocking notifications
+);
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessScopeDifferentSite.js b/toolkit/components/antitracking/test/browser/browser_storageAccessScopeDifferentSite.js
new file mode 100644
index 0000000000..5b3edec49e
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessScopeDifferentSite.js
@@ -0,0 +1,64 @@
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/browser/modules/test/browser/head.js",
+ this
+);
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/toolkit/components/antitracking/test/browser/storage_access_head.js",
+ this
+);
+
+add_task(async function testInitialBlock() {
+ await setPreferences();
+
+ await openPageAndRunCode(
+ TEST_TOP_PAGE_7,
+ getExpectPopupAndClick("reject"),
+ TEST_3RD_PARTY_PAGE,
+ requestStorageAccessAndExpectFailure
+ );
+
+ await cleanUpData();
+ await SpecialPowers.flushPrefEnv();
+});
+
+add_task(async function testDifferentSitePermission() {
+ await setPreferences(/*alwaysPartitionStorage*/ false);
+
+ await openPageAndRunCode(
+ TEST_TOP_PAGE_7,
+ getExpectPopupAndClick("accept"),
+ TEST_3RD_PARTY_PAGE,
+ requestStorageAccessAndExpectSuccess
+ );
+
+ await openPageAndRunCode(
+ TEST_TOP_PAGE,
+ getExpectPopupAndClick("reject"),
+ TEST_3RD_PARTY_PAGE,
+ requestStorageAccessAndExpectFailure
+ );
+
+ await cleanUpData();
+ await SpecialPowers.flushPrefEnv();
+});
+
+add_task(async function testDifferentSitePermissionAPS() {
+ await setPreferences(/*alwaysPartitionStorage*/ true);
+
+ await openPageAndRunCode(
+ TEST_TOP_PAGE_7,
+ getExpectPopupAndClick("accept"),
+ TEST_3RD_PARTY_PAGE,
+ requestStorageAccessAndExpectSuccess
+ );
+
+ await openPageAndRunCode(
+ TEST_TOP_PAGE,
+ getExpectPopupAndClick("reject"),
+ TEST_3RD_PARTY_PAGE,
+ requestStorageAccessAndExpectFailure
+ );
+
+ await cleanUpData();
+ await SpecialPowers.flushPrefEnv();
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessScopeSameOrigin.js b/toolkit/components/antitracking/test/browser/browser_storageAccessScopeSameOrigin.js
new file mode 100644
index 0000000000..f0b1aca0b6
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessScopeSameOrigin.js
@@ -0,0 +1,43 @@
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/browser/modules/test/browser/head.js",
+ this
+);
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/toolkit/components/antitracking/test/browser/storage_access_head.js",
+ this
+);
+
+add_task(async function testInitialBlock() {
+ await setPreferences();
+
+ await openPageAndRunCode(
+ TEST_TOP_PAGE_7,
+ getExpectPopupAndClick("reject"),
+ TEST_3RD_PARTY_PAGE,
+ requestStorageAccessAndExpectFailure
+ );
+
+ await cleanUpData();
+ await SpecialPowers.flushPrefEnv();
+});
+
+add_task(async function testSameOriginPermission() {
+ await setPreferences();
+
+ await openPageAndRunCode(
+ TEST_TOP_PAGE_7,
+ getExpectPopupAndClick("accept"),
+ TEST_3RD_PARTY_PAGE,
+ requestStorageAccessAndExpectSuccess
+ );
+
+ await openPageAndRunCode(
+ TEST_TOP_PAGE_7,
+ expectNoPopup,
+ TEST_3RD_PARTY_PAGE,
+ requestStorageAccessAndExpectSuccess
+ );
+
+ await cleanUpData();
+ await SpecialPowers.flushPrefEnv();
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessScopeSameSiteRead.js b/toolkit/components/antitracking/test/browser/browser_storageAccessScopeSameSiteRead.js
new file mode 100644
index 0000000000..0dd893eda6
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessScopeSameSiteRead.js
@@ -0,0 +1,43 @@
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/browser/modules/test/browser/head.js",
+ this
+);
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/toolkit/components/antitracking/test/browser/storage_access_head.js",
+ this
+);
+
+add_task(async function testInitialBlock() {
+ await setPreferences();
+
+ await openPageAndRunCode(
+ TEST_TOP_PAGE_7,
+ getExpectPopupAndClick("reject"),
+ TEST_3RD_PARTY_PAGE,
+ requestStorageAccessAndExpectFailure
+ );
+
+ await cleanUpData();
+ await SpecialPowers.flushPrefEnv();
+});
+
+add_task(async function testSameSitePermission() {
+ await setPreferences();
+
+ await openPageAndRunCode(
+ TEST_TOP_PAGE_7,
+ getExpectPopupAndClick("accept"),
+ TEST_3RD_PARTY_PAGE,
+ requestStorageAccessAndExpectSuccess
+ );
+
+ await openPageAndRunCode(
+ TEST_TOP_PAGE_8,
+ expectNoPopup,
+ TEST_3RD_PARTY_PAGE,
+ requestStorageAccessAndExpectSuccess
+ );
+
+ await cleanUpData();
+ await SpecialPowers.flushPrefEnv();
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessScopeSameSiteWrite.js b/toolkit/components/antitracking/test/browser/browser_storageAccessScopeSameSiteWrite.js
new file mode 100644
index 0000000000..4b99cb266e
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessScopeSameSiteWrite.js
@@ -0,0 +1,43 @@
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/browser/modules/test/browser/head.js",
+ this
+);
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/toolkit/components/antitracking/test/browser/storage_access_head.js",
+ this
+);
+
+add_task(async function testInitialBlock() {
+ await setPreferences();
+
+ await openPageAndRunCode(
+ TEST_TOP_PAGE_7,
+ getExpectPopupAndClick("reject"),
+ TEST_3RD_PARTY_PAGE,
+ requestStorageAccessAndExpectFailure
+ );
+
+ await cleanUpData();
+ await SpecialPowers.flushPrefEnv();
+});
+
+add_task(async function testSameSitePermissionReversed() {
+ await setPreferences();
+
+ await openPageAndRunCode(
+ TEST_TOP_PAGE_8,
+ getExpectPopupAndClick("accept"),
+ TEST_3RD_PARTY_PAGE,
+ requestStorageAccessAndExpectSuccess
+ );
+
+ await openPageAndRunCode(
+ TEST_TOP_PAGE_7,
+ expectNoPopup,
+ TEST_3RD_PARTY_PAGE,
+ requestStorageAccessAndExpectSuccess
+ );
+
+ await cleanUpData();
+ await SpecialPowers.flushPrefEnv();
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessThirdPartyChecks.js b/toolkit/components/antitracking/test/browser/browser_storageAccessThirdPartyChecks.js
new file mode 100644
index 0000000000..5a8c7970c5
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessThirdPartyChecks.js
@@ -0,0 +1,157 @@
+const APS_PREF =
+ "privacy.partition.always_partition_third_party_non_cookie_storage";
+
+AntiTracking._createTask({
+ name: "Test that after a storage access grant we have full first-party access",
+ cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+ blockingByContentBlockingRTUI: true,
+ allowList: false,
+ callback: async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+
+ await callRequestStorageAccess();
+
+ const TRACKING_PAGE =
+ "http://another-tracking.example.net/browser/browser/base/content/test/protectionsUI/trackingPage.html";
+ async function runChecks(name) {
+ let iframe = document.createElement("iframe");
+ iframe.src = TRACKING_PAGE;
+ document.body.appendChild(iframe);
+ await new Promise(resolve => {
+ iframe.onload = resolve;
+ });
+
+ await SpecialPowers.spawn(iframe, [name], name => {
+ content.postMessage(name, "*");
+ });
+
+ await new Promise(resolve => {
+ onmessage = e => {
+ if (e.data == "done") {
+ resolve();
+ }
+ };
+ });
+ }
+
+ await runChecks("image");
+ },
+ extraPrefs: [[APS_PREF, false]],
+ expectedBlockingNotifications:
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER,
+ runInPrivateWindow: false,
+ iframeSandbox: null,
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+ thirdPartyPage: TEST_3RD_PARTY_PAGE_HTTP,
+ errorMessageDomains: [
+ "http://tracking.example.org",
+ "http://tracking.example.org",
+ "http://tracking.example.org",
+ ],
+});
+
+add_task(async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
+
+AntiTracking._createTask({
+ name: "Test that we never grant access to cookieBehavior=1, when network.cookie.rejectForeignWithExceptions.enabled is set to false",
+ cookieBehavior: BEHAVIOR_REJECT_FOREIGN,
+ allowList: false,
+ callback: async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+
+ await callRequestStorageAccess(null, true);
+ },
+ extraPrefs: [
+ ["network.cookie.rejectForeignWithExceptions.enabled", false],
+ [APS_PREF, false],
+ ],
+ expectedBlockingNotifications: 0,
+ runInPrivateWindow: false,
+ iframeSandbox: null,
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+ thirdPartyPage: TEST_3RD_PARTY_PAGE_HTTP,
+ errorMessageDomains: [
+ "http://tracking.example.org",
+ "http://tracking.example.org",
+ ],
+});
+
+add_task(async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
+
+AntiTracking._createTask({
+ name: "Test that we never grant access to cookieBehavior=2",
+ cookieBehavior: BEHAVIOR_REJECT,
+ allowList: false,
+ callback: async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+
+ await callRequestStorageAccess(null, true);
+ },
+ extraPrefs: [[APS_PREF, false]],
+ expectedBlockingNotifications: 0,
+ runInPrivateWindow: false,
+ iframeSandbox: null,
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+ thirdPartyPage: TEST_3RD_PARTY_PAGE_HTTP,
+ errorMessageDomains: [
+ "http://tracking.example.org",
+ "http://tracking.example.org",
+ ],
+});
+
+add_task(async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
+
+AntiTracking._createTask({
+ name: "Test that we never grant access to cookieBehavior=3",
+ cookieBehavior: BEHAVIOR_LIMIT_FOREIGN,
+ allowList: false,
+ callback: async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+
+ await callRequestStorageAccess(null, true);
+ },
+ extraPrefs: [[APS_PREF, false]],
+ expectedBlockingNotifications: 0,
+ runInPrivateWindow: false,
+ iframeSandbox: null,
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+ thirdPartyPage: TEST_3RD_PARTY_PAGE_HTTP,
+ errorMessageDomains: [
+ "http://tracking.example.org",
+ "http://tracking.example.org",
+ ],
+});
+
+add_task(async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessThirdPartyChecks_alwaysPartition.js b/toolkit/components/antitracking/test/browser/browser_storageAccessThirdPartyChecks_alwaysPartition.js
new file mode 100644
index 0000000000..1ba0b89230
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessThirdPartyChecks_alwaysPartition.js
@@ -0,0 +1,153 @@
+const allBlocked = Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_ALL;
+const foreignBlocked = Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_FOREIGN;
+
+const APS_PREF =
+ "privacy.partition.always_partition_third_party_non_cookie_storage";
+
+AntiTracking._createTask({
+ name: "Test that after a storage access grant we have full first-party access",
+ cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+ blockingByContentBlockingRTUI: true,
+ allowList: false,
+ callback: async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+
+ await callRequestStorageAccess();
+
+ const TRACKING_PAGE =
+ "http://another-tracking.example.net/browser/browser/base/content/test/protectionsUI/trackingPage.html";
+ async function runChecks(name) {
+ let iframe = document.createElement("iframe");
+ iframe.src = TRACKING_PAGE;
+ document.body.appendChild(iframe);
+ await new Promise(resolve => {
+ iframe.onload = resolve;
+ });
+
+ await SpecialPowers.spawn(iframe, [name], name => {
+ content.postMessage(name, "*");
+ });
+
+ await new Promise(resolve => {
+ onmessage = e => {
+ if (e.data == "done") {
+ resolve();
+ }
+ };
+ });
+ }
+
+ await runChecks("image");
+ },
+ extraPrefs: [[APS_PREF, true]],
+ expectedBlockingNotifications:
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER,
+ runInPrivateWindow: false,
+ iframeSandbox: null,
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+ thirdPartyPage: TEST_3RD_PARTY_PAGE_HTTP,
+ errorMessageDomains: [
+ "http://tracking.example.org",
+ "http://tracking.example.org",
+ "http://tracking.example.org",
+ "http://tracking.example.org",
+ "http://trackertest.org",
+ ],
+});
+
+add_task(async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
+
+AntiTracking._createTask({
+ name: "Test that we never grant access to cookieBehavior=1, when network.cookie.rejectForeignWithExceptions.enabled is set to false",
+ cookieBehavior: BEHAVIOR_REJECT_FOREIGN,
+ allowList: false,
+ callback: async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+
+ await callRequestStorageAccess(null, true);
+ },
+ extraPrefs: [
+ ["network.cookie.rejectForeignWithExceptions.enabled", false],
+ [APS_PREF, true],
+ ],
+ expectedBlockingNotifications: foreignBlocked,
+ runInPrivateWindow: false,
+ iframeSandbox: null,
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+ thirdPartyPage: TEST_3RD_PARTY_PAGE_HTTP,
+ errorMessageDomains: ["http://tracking.example.org"],
+});
+
+add_task(async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
+
+AntiTracking._createTask({
+ name: "Test that we never grant access to cookieBehavior=2",
+ cookieBehavior: BEHAVIOR_REJECT,
+ allowList: false,
+ callback: async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+
+ await callRequestStorageAccess(null, true);
+ },
+ extraPrefs: [[APS_PREF, true]],
+ expectedBlockingNotifications: allBlocked,
+ runInPrivateWindow: false,
+ iframeSandbox: null,
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+ thirdPartyPage: TEST_3RD_PARTY_PAGE_HTTP,
+ errorMessageDomains: ["http://example.net", "http://tracking.example.org"],
+});
+
+add_task(async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
+
+AntiTracking._createTask({
+ name: "Test that we never grant access to cookieBehavior=3",
+ cookieBehavior: BEHAVIOR_LIMIT_FOREIGN,
+ allowList: false,
+ callback: async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+
+ await callRequestStorageAccess(null, true);
+ },
+ extraPrefs: [[APS_PREF, true]],
+ expectedBlockingNotifications: foreignBlocked,
+ runInPrivateWindow: false,
+ iframeSandbox: null,
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+ thirdPartyPage: TEST_3RD_PARTY_PAGE_HTTP,
+ errorMessageDomains: ["http://tracking.example.org"],
+});
+
+add_task(async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessWithDynamicFpi.js b/toolkit/components/antitracking/test/browser/browser_storageAccessWithDynamicFpi.js
new file mode 100644
index 0000000000..f50e515c84
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessWithDynamicFpi.js
@@ -0,0 +1,612 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { RemoteSettings } = ChromeUtils.importESModule(
+ "resource://services-settings/remote-settings.sys.mjs"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "peuService",
+ "@mozilla.org/partitioning/exception-list-service;1",
+ "nsIPartitioningExceptionListService"
+);
+
+const TEST_REDIRECT_TOP_PAGE =
+ TEST_3RD_PARTY_DOMAIN + TEST_PATH + "redirect.sjs?" + TEST_TOP_PAGE;
+const TEST_REDIRECT_3RD_PARTY_PAGE =
+ TEST_DOMAIN + TEST_PATH + "redirect.sjs?" + TEST_3RD_PARTY_PARTITIONED_PAGE;
+
+const COLLECTION_NAME = "partitioning-exempt-urls";
+const EXCEPTION_LIST_PREF_NAME = "privacy.restrict3rdpartystorage.skip_list";
+
+async function cleanup() {
+ Services.prefs.clearUserPref(EXCEPTION_LIST_PREF_NAME);
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+}
+
+add_setup(async function () {
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ ["privacy.restrict3rdpartystorage.heuristic.redirect", false],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ // Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default"
+ ["network.cookie.sameSite.laxByDefault", false],
+ ],
+ });
+ registerCleanupFunction(cleanup);
+});
+
+function executeContentScript(browser, callback, options = {}) {
+ return SpecialPowers.spawn(
+ browser,
+ [
+ {
+ callback: callback.toString(),
+ ...options,
+ },
+ ],
+ obj => {
+ return new content.Promise(async resolve => {
+ if (obj.page) {
+ // third-party
+ let ifr = content.document.createElement("iframe");
+ ifr.onload = async () => {
+ info("Sending code to the 3rd party content");
+ ifr.contentWindow.postMessage(
+ { cb: obj.callback, value: obj.value },
+ "*"
+ );
+ };
+
+ content.addEventListener("message", event => resolve(event.data), {
+ once: true,
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ } else {
+ // first-party
+ let runnableStr = `(() => {return (${obj.callback});})();`;
+ let runnable = eval(runnableStr); // eslint-disable-line no-eval
+ resolve(await runnable.call(content, content, obj.value));
+ }
+ });
+ }
+ );
+}
+
+function readNetworkCookie(win) {
+ return win
+ .fetch("cookies.sjs")
+ .then(r => r.text())
+ .then(text => {
+ return text.substring("cookie:foopy=".length);
+ });
+}
+
+async function writeNetworkCookie(win, value) {
+ await win.fetch("cookies.sjs?" + value).then(r => r.text());
+ return true;
+}
+
+function createDataInFirstParty(browser, value) {
+ return executeContentScript(browser, writeNetworkCookie, { value });
+}
+function getDataFromFirstParty(browser) {
+ return executeContentScript(browser, readNetworkCookie, {});
+}
+function createDataInThirdParty(browser, value) {
+ return executeContentScript(browser, writeNetworkCookie, {
+ page: TEST_3RD_PARTY_PARTITIONED_PAGE,
+ value,
+ });
+}
+function getDataFromThirdParty(browser) {
+ return executeContentScript(browser, readNetworkCookie, {
+ page: TEST_3RD_PARTY_PARTITIONED_PAGE,
+ });
+}
+
+async function redirectWithUserInteraction(browser, url, wait = null) {
+ await executeContentScript(
+ browser,
+ (content, value) => {
+ content.document.userInteractionForTesting();
+
+ let link = content.document.createElement("a");
+ link.appendChild(content.document.createTextNode("click me!"));
+ link.href = value;
+ content.document.body.appendChild(link);
+ link.click();
+ },
+ {
+ value: url,
+ }
+ );
+ await BrowserTestUtils.browserLoaded(browser, false, wait || url);
+}
+
+async function checkData(browser, options) {
+ if ("firstParty" in options) {
+ is(
+ await getDataFromFirstParty(browser),
+ options.firstParty,
+ "correct first-party data"
+ );
+ }
+ if ("thirdParty" in options) {
+ is(
+ await getDataFromThirdParty(browser),
+ options.thirdParty,
+ "correct third-party data"
+ );
+ }
+}
+
+async function runTestRedirectHeuristic(disableHeuristics) {
+ info("Starting Dynamic FPI Redirect Heuristic test...");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.restrict3rdpartystorage.heuristic.recently_visited", true],
+ ["privacy.antitracking.enableWebcompat", !disableHeuristics],
+ ],
+ });
+
+ // mark third-party as tracker
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("initializing...");
+ await checkData(browser, { firstParty: "", thirdParty: "" });
+
+ await Promise.all([
+ createDataInFirstParty(browser, "firstParty"),
+ createDataInThirdParty(browser, "thirdParty"),
+ ]);
+
+ await checkData(browser, {
+ firstParty: "firstParty",
+ thirdParty: "",
+ });
+
+ info("load third-party content as first-party");
+ await redirectWithUserInteraction(
+ browser,
+ TEST_REDIRECT_3RD_PARTY_PAGE,
+ TEST_3RD_PARTY_PARTITIONED_PAGE
+ );
+
+ await checkData(browser, { firstParty: "" });
+ await createDataInFirstParty(browser, "heuristicFirstParty");
+ await checkData(browser, { firstParty: "heuristicFirstParty" });
+
+ info("redirect back to first-party page");
+ await redirectWithUserInteraction(
+ browser,
+ TEST_REDIRECT_TOP_PAGE,
+ TEST_TOP_PAGE
+ );
+
+ info("third-party tracker should NOT able to access first-party data");
+ await checkData(browser, {
+ firstParty: "firstParty",
+ thirdParty: "",
+ });
+
+ // remove third-party from tracker
+ await UrlClassifierTestUtils.cleanupTestTrackers();
+
+ info("load third-party content as first-party");
+ await redirectWithUserInteraction(
+ browser,
+ TEST_REDIRECT_3RD_PARTY_PAGE,
+ TEST_3RD_PARTY_PARTITIONED_PAGE
+ );
+
+ await checkData(browser, {
+ firstParty: "heuristicFirstParty",
+ });
+
+ info("redirect back to first-party page");
+ await redirectWithUserInteraction(
+ browser,
+ TEST_REDIRECT_TOP_PAGE,
+ TEST_TOP_PAGE
+ );
+
+ info(
+ `third-party page should ${
+ disableHeuristics ? "not " : ""
+ }be able to access first-party data`
+ );
+ await checkData(browser, {
+ firstParty: "firstParty",
+ thirdParty: disableHeuristics ? "" : "heuristicFirstParty",
+ });
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ await SpecialPowers.popPrefEnv();
+
+ await cleanup();
+}
+
+add_task(async function testRedirectHeuristic() {
+ await runTestRedirectHeuristic(false);
+});
+
+add_task(async function testRedirectHeuristicDisabled() {
+ await runTestRedirectHeuristic(true);
+});
+
+class UpdateEvent extends EventTarget {}
+function waitForEvent(element, eventName) {
+ return new Promise(function (resolve) {
+ element.addEventListener(eventName, e => resolve(e.detail), { once: true });
+ });
+}
+
+// The test URLs have a trailing / which means they're not valid origins.
+const TEST_ORIGIN = TEST_DOMAIN.substring(0, TEST_DOMAIN.length - 1);
+const TEST_3RD_PARTY_ORIGIN = TEST_3RD_PARTY_DOMAIN.substring(
+ 0,
+ TEST_3RD_PARTY_DOMAIN.length - 1
+);
+
+async function runTestExceptionListPref(disableHeuristics) {
+ info("Starting Dynamic FPI exception list test pref");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.restrict3rdpartystorage.heuristic.recently_visited", false],
+ ["privacy.antitracking.enableWebcompat", !disableHeuristics],
+ ],
+ });
+
+ info("Creating new tabs");
+ let tabThirdParty = BrowserTestUtils.addTab(
+ gBrowser,
+ TEST_3RD_PARTY_PARTITIONED_PAGE
+ );
+ gBrowser.selectedTab = tabThirdParty;
+
+ let browserThirdParty = gBrowser.getBrowserForTab(tabThirdParty);
+ await BrowserTestUtils.browserLoaded(browserThirdParty);
+
+ let tabFirstParty = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tabFirstParty;
+
+ let browserFirstParty = gBrowser.getBrowserForTab(tabFirstParty);
+ await BrowserTestUtils.browserLoaded(browserFirstParty);
+
+ info("initializing...");
+ await Promise.all([
+ checkData(browserFirstParty, { firstParty: "", thirdParty: "" }),
+ checkData(browserThirdParty, { firstParty: "" }),
+ ]);
+
+ info("fill default data");
+ await Promise.all([
+ createDataInFirstParty(browserFirstParty, "firstParty"),
+ createDataInThirdParty(browserFirstParty, "thirdParty"),
+ createDataInFirstParty(browserThirdParty, "ExceptionListFirstParty"),
+ ]);
+
+ info("check data");
+ await Promise.all([
+ checkData(browserFirstParty, {
+ firstParty: "firstParty",
+ thirdParty: "thirdParty",
+ }),
+ checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }),
+ ]);
+
+ info("set exception list pref");
+ Services.prefs.setStringPref(
+ EXCEPTION_LIST_PREF_NAME,
+ `${TEST_ORIGIN},${TEST_3RD_PARTY_ORIGIN}`
+ );
+
+ info("check data");
+ await Promise.all([
+ checkData(browserFirstParty, {
+ firstParty: "firstParty",
+ thirdParty: disableHeuristics ? "thirdParty" : "ExceptionListFirstParty",
+ }),
+ checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }),
+ ]);
+
+ info("set incomplete exception list pref");
+ Services.prefs.setStringPref(EXCEPTION_LIST_PREF_NAME, `${TEST_ORIGIN}`);
+
+ info("check data");
+ await Promise.all([
+ checkData(browserFirstParty, {
+ firstParty: "firstParty",
+ thirdParty: "thirdParty",
+ }),
+ checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }),
+ ]);
+
+ info("set exception list pref, with extra semicolons");
+ Services.prefs.setStringPref(
+ EXCEPTION_LIST_PREF_NAME,
+ `;${TEST_ORIGIN},${TEST_3RD_PARTY_ORIGIN};;`
+ );
+
+ info("check data");
+ await Promise.all([
+ checkData(browserFirstParty, {
+ firstParty: "firstParty",
+ thirdParty: disableHeuristics ? "thirdParty" : "ExceptionListFirstParty",
+ }),
+ checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }),
+ ]);
+
+ info("set exception list pref, with subdomain wildcard");
+ Services.prefs.setStringPref(
+ EXCEPTION_LIST_PREF_NAME,
+ `${TEST_ORIGIN},${TEST_3RD_PARTY_ORIGIN.replace("tracking", "*")}`
+ );
+
+ info("check data");
+ await Promise.all([
+ checkData(browserFirstParty, {
+ firstParty: "firstParty",
+ thirdParty: disableHeuristics ? "thirdParty" : "ExceptionListFirstParty",
+ }),
+ checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }),
+ ]);
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tabFirstParty);
+ BrowserTestUtils.removeTab(tabThirdParty);
+
+ await SpecialPowers.popPrefEnv();
+
+ await cleanup();
+}
+
+add_task(async function testExceptionListPref() {
+ await runTestExceptionListPref(false);
+});
+
+add_task(async function testExceptionListPrefDisabled() {
+ await runTestExceptionListPref(true);
+});
+
+add_task(async function testExceptionListRemoteSettings() {
+ info("Starting Dynamic FPI exception list test (remote settings)");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.restrict3rdpartystorage.heuristic.recently_visited", false],
+ ],
+ });
+
+ // Make sure we have a pref initially, since the exception list service
+ // requires it.
+ Services.prefs.setStringPref(EXCEPTION_LIST_PREF_NAME, "");
+
+ // Add some initial data
+ let db = RemoteSettings(COLLECTION_NAME).db;
+ await db.importChanges({}, Date.now(), []);
+
+ // make peuSerivce start working by calling
+ // registerAndRunExceptionListObserver
+ let updateEvent = new UpdateEvent();
+ let obs = data => {
+ let event = new CustomEvent("update", { detail: data });
+ updateEvent.dispatchEvent(event);
+ };
+ let promise = waitForEvent(updateEvent, "update");
+ peuService.registerAndRunExceptionListObserver(obs);
+ await promise;
+
+ info("Creating new tabs");
+ let tabThirdParty = BrowserTestUtils.addTab(
+ gBrowser,
+ TEST_3RD_PARTY_PARTITIONED_PAGE
+ );
+ gBrowser.selectedTab = tabThirdParty;
+
+ let browserThirdParty = gBrowser.getBrowserForTab(tabThirdParty);
+ await BrowserTestUtils.browserLoaded(browserThirdParty);
+
+ let tabFirstParty = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tabFirstParty;
+
+ let browserFirstParty = gBrowser.getBrowserForTab(tabFirstParty);
+ await BrowserTestUtils.browserLoaded(browserFirstParty);
+
+ info("initializing...");
+ await Promise.all([
+ checkData(browserFirstParty, { firstParty: "", thirdParty: "" }),
+ checkData(browserThirdParty, { firstParty: "" }),
+ ]);
+
+ info("fill default data");
+ await Promise.all([
+ createDataInFirstParty(browserFirstParty, "firstParty"),
+ createDataInThirdParty(browserFirstParty, "thirdParty"),
+ createDataInFirstParty(browserThirdParty, "ExceptionListFirstParty"),
+ ]);
+
+ info("check data");
+ await Promise.all([
+ checkData(browserFirstParty, {
+ firstParty: "firstParty",
+ thirdParty: "thirdParty",
+ }),
+ checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }),
+ ]);
+
+ info("set exception list remote settings");
+
+ // set records
+ promise = waitForEvent(updateEvent, "update");
+ await RemoteSettings(COLLECTION_NAME).emit("sync", {
+ data: {
+ current: [
+ {
+ id: "1",
+ last_modified: 1000000000000001,
+ firstPartyOrigin: TEST_ORIGIN,
+ thirdPartyOrigin: TEST_3RD_PARTY_ORIGIN,
+ },
+ ],
+ },
+ });
+
+ let list = await promise;
+ is(
+ list,
+ `${TEST_ORIGIN},${TEST_3RD_PARTY_ORIGIN}`,
+ "exception list is correctly set"
+ );
+
+ info("check data");
+ await Promise.all([
+ checkData(browserFirstParty, {
+ firstParty: "firstParty",
+ thirdParty: "ExceptionListFirstParty",
+ }),
+ checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }),
+ ]);
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tabFirstParty);
+ BrowserTestUtils.removeTab(tabThirdParty);
+
+ promise = waitForEvent(updateEvent, "update");
+ await RemoteSettings(COLLECTION_NAME).emit("sync", {
+ data: {
+ current: [],
+ },
+ });
+ is(await promise, "", "Exception list is cleared");
+
+ peuService.unregisterExceptionListObserver(obs);
+ await cleanup();
+});
+
+add_task(async function testWildcardExceptionListPref() {
+ info("Starting Dynamic FPI wirdcard exception list test pref");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.restrict3rdpartystorage.heuristic.recently_visited", false],
+ ],
+ });
+
+ info("Creating new tabs");
+ let tabThirdParty = BrowserTestUtils.addTab(
+ gBrowser,
+ TEST_3RD_PARTY_PARTITIONED_PAGE
+ );
+ gBrowser.selectedTab = tabThirdParty;
+
+ let browserThirdParty = gBrowser.getBrowserForTab(tabThirdParty);
+ await BrowserTestUtils.browserLoaded(browserThirdParty);
+
+ let tabFirstParty = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tabFirstParty;
+
+ let browserFirstParty = gBrowser.getBrowserForTab(tabFirstParty);
+ await BrowserTestUtils.browserLoaded(browserFirstParty);
+
+ info("initializing...");
+ await Promise.all([
+ checkData(browserFirstParty, { firstParty: "", thirdParty: "" }),
+ checkData(browserThirdParty, { firstParty: "" }),
+ ]);
+
+ info("fill default data");
+ await Promise.all([
+ createDataInFirstParty(browserFirstParty, "firstParty"),
+ createDataInThirdParty(browserFirstParty, "thirdParty"),
+ createDataInFirstParty(browserThirdParty, "ExceptionListFirstParty"),
+ ]);
+
+ info("check initial data");
+ await Promise.all([
+ checkData(browserFirstParty, {
+ firstParty: "firstParty",
+ thirdParty: "thirdParty",
+ }),
+ checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }),
+ ]);
+
+ info("set wildcard (1st-party) pref");
+ Services.prefs.setStringPref(
+ EXCEPTION_LIST_PREF_NAME,
+ `*,${TEST_3RD_PARTY_ORIGIN}`
+ );
+
+ info("check wildcard (1st-party) data");
+ await Promise.all([
+ checkData(browserFirstParty, {
+ firstParty: "firstParty",
+ thirdParty: "ExceptionListFirstParty",
+ }),
+ checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }),
+ ]);
+
+ info("set invalid exception list pref");
+ Services.prefs.setStringPref(EXCEPTION_LIST_PREF_NAME, "*,*");
+
+ info("check initial data");
+ await Promise.all([
+ checkData(browserFirstParty, {
+ firstParty: "firstParty",
+ thirdParty: "thirdParty",
+ }),
+ checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }),
+ ]);
+
+ info("set wildcard (3rd-party) pref");
+ Services.prefs.setStringPref(EXCEPTION_LIST_PREF_NAME, `${TEST_ORIGIN},*`);
+
+ info("check wildcard (3rd-party) data");
+ await Promise.all([
+ checkData(browserFirstParty, {
+ firstParty: "firstParty",
+ thirdParty: "ExceptionListFirstParty",
+ }),
+ checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }),
+ ]);
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tabFirstParty);
+ BrowserTestUtils.removeTab(tabThirdParty);
+
+ await cleanup();
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessWithHeuristics.js b/toolkit/components/antitracking/test/browser/browser_storageAccessWithHeuristics.js
new file mode 100644
index 0000000000..5b68975d03
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessWithHeuristics.js
@@ -0,0 +1,912 @@
+function waitStoragePermission() {
+ return new Promise(resolve => {
+ let id = setInterval(async _ => {
+ if (
+ await SpecialPowers.testPermission(
+ `3rdPartyStorage^${TEST_3RD_PARTY_DOMAIN.slice(0, -1)}`,
+ SpecialPowers.Services.perms.ALLOW_ACTION,
+ TEST_DOMAIN
+ )
+ ) {
+ clearInterval(id);
+ resolve();
+ }
+ }, 0);
+ });
+}
+
+add_setup(async function () {
+ info("Starting subResources test");
+
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ [
+ "privacy.restrict3rdpartystorage.userInteractionRequiredForHosts",
+ "tracking.example.com,tracking.example.org",
+ ],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+});
+
+async function runTestWindowOpenHeuristic(disableHeuristics) {
+ info(
+ `Starting window.open() heuristic test with heuristic ${
+ disableHeuristics ? "disabled" : "enabled"
+ }.`
+ );
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.antitracking.enableWebcompat", !disableHeuristics]],
+ });
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Loading tracking scripts");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: TEST_3RD_PARTY_PAGE_WO,
+ disableHeuristics,
+ },
+ ],
+ async obj => {
+ let msg = {};
+ msg.blockingCallback = (async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+ }).toString();
+
+ // If the heuristic is disabled, we won't get storage access.
+ if (obj.disableHeuristics) {
+ msg.nonBlockingCallback = (async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await stillNoStorageAccess();
+ }).toString();
+ } else {
+ msg.nonBlockingCallback = (async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await hasStorageAccessInitially();
+ }).toString();
+ }
+
+ info("Checking if storage access is denied");
+ await new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.onload = function () {
+ info("Sending code to the 3rd party content");
+ ifr.contentWindow.postMessage(msg, "*");
+ };
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ });
+ }
+ );
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ await SpecialPowers.popPrefEnv();
+
+ info("Cleaning up.");
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+}
+
+add_task(async function testWindowOpenHeuristic() {
+ await runTestWindowOpenHeuristic(false);
+});
+
+add_task(async function testWindowOpenHeuristicDisabled() {
+ await runTestWindowOpenHeuristic(true);
+});
+
+add_task(async function testDoublyNestedWindowOpenHeuristic() {
+ info("Starting doubly nested window.open() heuristic test...");
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Loading tracking scripts");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: TEST_3RD_PARTY_PAGE_RELAY + "?" + TEST_3RD_PARTY_PAGE_WO,
+ },
+ ],
+ async obj => {
+ let msg = {};
+ msg.blockingCallback = (async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+ }).toString();
+
+ msg.nonBlockingCallback = (async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await hasStorageAccessInitially();
+ }).toString();
+
+ info("Checking if storage access is denied");
+ await new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.onload = function () {
+ info("Sending code to the 3rd party content");
+ ifr.contentWindow.postMessage(msg, "*");
+ };
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ });
+ }
+ );
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function () {
+ info("Cleaning up.");
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
+
+async function runTestUserInteractionHeuristic(disableHeuristics) {
+ info(
+ `Starting user interaction heuristic test with heuristic ${
+ disableHeuristics ? "disabled" : "enabled"
+ }.`
+ );
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.antitracking.enableWebcompat", !disableHeuristics]],
+ });
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Loading tracking scripts");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: TEST_3RD_PARTY_PAGE_UI,
+ popup: TEST_POPUP_PAGE,
+ },
+ ],
+ async obj => {
+ let msg = {};
+ msg.blockingCallback = (async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+ }).toString();
+
+ info("Checking if storage access is denied");
+
+ let ifr = content.document.createElement("iframe");
+ let loading = new content.Promise(resolve => {
+ ifr.onload = resolve;
+ });
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ await loading;
+
+ info(
+ "The 3rd party content should not have access to first party storage."
+ );
+ await new content.Promise(resolve => {
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+ ifr.contentWindow.postMessage({ callback: msg.blockingCallback }, "*");
+ });
+
+ info("Opening a window from the iframe.");
+ await SpecialPowers.spawn(ifr, [obj.popup], async popup => {
+ let windowClosed = new content.Promise(resolve => {
+ Services.ww.registerNotification(function notification(
+ aSubject,
+ aTopic,
+ aData
+ ) {
+ // We need to check the document URI for Fission. It's because the
+ // 'domwindowclosed' would be triggered twice, one for the
+ // 'about:blank' page and another for the tracker page.
+ if (
+ aTopic == "domwindowclosed" &&
+ aSubject.document.documentURI ==
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/3rdPartyOpenUI.html"
+ ) {
+ Services.ww.unregisterNotification(notification);
+ resolve();
+ }
+ });
+ });
+
+ content.open(popup);
+
+ info("Let's wait for the window to be closed");
+ await windowClosed;
+ });
+
+ info("The 3rd party content should have access to first party storage.");
+ await new content.Promise(resolve => {
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+ ifr.contentWindow.postMessage({ callback: msg.blockingCallback }, "*");
+ });
+ }
+ );
+
+ await AntiTracking.interactWithTracker();
+
+ info("Loading tracking scripts");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: TEST_3RD_PARTY_PAGE_UI,
+ popup: TEST_POPUP_PAGE,
+ disableHeuristics,
+ },
+ ],
+ async obj => {
+ let msg = {};
+
+ msg.blockingCallback = (async _ => {
+ await noStorageAccessInitially();
+ }).toString();
+
+ // If the heuristic is disabled, we won't get storage access.
+ if (obj.disableHeuristics) {
+ msg.nonBlockingCallback = (async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await stillNoStorageAccess();
+ }).toString();
+ } else {
+ msg.nonBlockingCallback = (async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await hasStorageAccessInitially();
+ }).toString();
+ }
+
+ info("Checking if storage access is denied");
+
+ let ifr = content.document.createElement("iframe");
+ let loading = new content.Promise(resolve => {
+ ifr.onload = resolve;
+ });
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ await loading;
+
+ info(
+ "The 3rd party content should not have access to first party storage."
+ );
+ await new content.Promise(resolve => {
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+ ifr.contentWindow.postMessage({ callback: msg.blockingCallback }, "*");
+ });
+
+ info("Opening a window from the iframe.");
+ await SpecialPowers.spawn(ifr, [obj.popup], async popup => {
+ let windowClosed = new content.Promise(resolve => {
+ Services.ww.registerNotification(function notification(
+ aSubject,
+ aTopic,
+ aData
+ ) {
+ // We need to check the document URI here as well for the same
+ // reason above.
+ if (
+ aTopic == "domwindowclosed" &&
+ aSubject.document.documentURI ==
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/3rdPartyOpenUI.html"
+ ) {
+ Services.ww.unregisterNotification(notification);
+ resolve();
+ }
+ });
+ });
+
+ content.open(popup);
+
+ info("Let's wait for the window to be closed");
+ await windowClosed;
+ });
+
+ info("The 3rd party content should have access to first party storage.");
+ await new content.Promise(resolve => {
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+ ifr.contentWindow.postMessage(
+ { callback: msg.nonBlockingCallback },
+ "*"
+ );
+ });
+ }
+ );
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ if (!disableHeuristics) {
+ info("Wait until the storage permission is ready before cleaning up.");
+ await waitStoragePermission();
+ }
+
+ info("Cleaning up.");
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+
+ await SpecialPowers.popPrefEnv();
+}
+
+add_task(async function testUserInteractionHeuristic() {
+ await runTestUserInteractionHeuristic(false);
+});
+
+add_task(async function testUserInteractionHeuristicDisabled() {
+ await runTestUserInteractionHeuristic(true);
+});
+
+add_task(async function testDoublyNestedUserInteractionHeuristic() {
+ info("Starting doubly nested user interaction heuristic test...");
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Loading tracking scripts");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: TEST_3RD_PARTY_PAGE_RELAY + "?" + TEST_3RD_PARTY_PAGE_UI,
+ popup: TEST_POPUP_PAGE,
+ },
+ ],
+ async obj => {
+ let msg = {};
+ msg.blockingCallback = (async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+ }).toString();
+
+ msg.openWindowCallback = (async url => {
+ open(url);
+ }).toString();
+
+ info("Checking if storage access is denied");
+
+ let ifr = content.document.createElement("iframe");
+ let loading = new content.Promise(resolve => {
+ ifr.onload = resolve;
+ });
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ await loading;
+
+ info(
+ "The 3rd party content should not have access to first party storage."
+ );
+ await new content.Promise(resolve => {
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+ ifr.contentWindow.postMessage({ callback: msg.blockingCallback }, "*");
+ });
+
+ let windowClosed = new content.Promise(resolve => {
+ Services.ww.registerNotification(function notification(
+ aSubject,
+ aTopic,
+ aData
+ ) {
+ if (aTopic == "domwindowclosed") {
+ Services.ww.unregisterNotification(notification);
+ resolve();
+ }
+ });
+ });
+
+ info("Opening a window from the iframe.");
+ ifr.contentWindow.postMessage(
+ { callback: msg.openWindowCallback, arg: obj.popup },
+ "*"
+ );
+
+ info("Let's wait for the window to be closed");
+ await windowClosed;
+
+ info("The 3rd party content should have access to first party storage.");
+ await new content.Promise(resolve => {
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+ ifr.contentWindow.postMessage({ callback: msg.blockingCallback }, "*");
+ });
+ }
+ );
+
+ await AntiTracking.interactWithTracker();
+
+ info("Loading tracking scripts");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: TEST_3RD_PARTY_PAGE_RELAY + "?" + TEST_3RD_PARTY_PAGE_UI,
+ popup: TEST_POPUP_PAGE,
+ },
+ ],
+ async obj => {
+ let msg = {};
+ msg.blockingCallback = (async _ => {
+ await noStorageAccessInitially();
+ }).toString();
+
+ msg.nonBlockingCallback = (async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await hasStorageAccessInitially();
+ }).toString();
+
+ msg.openWindowCallback = (async url => {
+ open(url);
+ }).toString();
+
+ info("Checking if storage access is denied");
+
+ let ifr = content.document.createElement("iframe");
+ let loading = new content.Promise(resolve => {
+ ifr.onload = resolve;
+ });
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ await loading;
+
+ info(
+ "The 3rd party content should not have access to first party storage."
+ );
+ await new content.Promise(resolve => {
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+ ifr.contentWindow.postMessage({ callback: msg.blockingCallback }, "*");
+ });
+
+ let windowClosed = new content.Promise(resolve => {
+ Services.ww.registerNotification(function notification(
+ aSubject,
+ aTopic,
+ aData
+ ) {
+ if (aTopic == "domwindowclosed") {
+ Services.ww.unregisterNotification(notification);
+ resolve();
+ }
+ });
+ });
+
+ info("Opening a window from the iframe.");
+ ifr.contentWindow.postMessage(
+ { callback: msg.openWindowCallback, arg: obj.popup },
+ "*"
+ );
+
+ info("Let's wait for the window to be closed");
+ await windowClosed;
+
+ info("The 3rd party content should have access to first party storage.");
+ await new content.Promise(resolve => {
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+ ifr.contentWindow.postMessage(
+ { callback: msg.nonBlockingCallback },
+ "*"
+ );
+ });
+ }
+ );
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function () {
+ info("Wait until the storage permission is ready before cleaning up.");
+ await waitStoragePermission();
+
+ info("Cleaning up.");
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
+
+async function runTestFirstPartyWindowOpenHeuristic(disableHeuristics) {
+ info(
+ `Starting first-party window.open() heuristic test with heuristic ${
+ disableHeuristics ? "disabled" : "enabled"
+ }.`
+ );
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.antitracking.enableWebcompat", !disableHeuristics]],
+ });
+
+ // Interact with the tracker first before testing window.open heuristic
+ await AntiTracking.interactWithTracker();
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Loading tracking scripts");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: TEST_3RD_PARTY_PAGE,
+ },
+ ],
+ async obj => {
+ info("Tracker shouldn't have storage access initially");
+ let msg = {};
+ msg.blockingCallback = (async _ => {
+ await noStorageAccessInitially();
+ }).toString();
+
+ await new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.onload = function () {
+ info("Sending code to the 3rd party content");
+ ifr.contentWindow.postMessage(msg.blockingCallback, "*");
+ };
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.id = "ifr";
+ ifr.src = obj.page;
+ });
+ }
+ );
+
+ info("Calling window.open in a first-party iframe");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: TEST_IFRAME_PAGE,
+ popup: TEST_3RD_PARTY_DOMAIN + TEST_PATH + "3rdPartyOpen.html",
+ },
+ ],
+ async obj => {
+ let ifr = content.document.createElement("iframe");
+ let loading = new content.Promise(resolve => {
+ ifr.onload = resolve;
+ });
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ await loading;
+
+ info("Opening a window from the iframe.");
+ await SpecialPowers.spawn(ifr, [obj.popup], async popup => {
+ await new content.Promise(resolve => {
+ content.open(popup);
+ content.addEventListener("message", function msg(event) {
+ if (event.data == "hello!") {
+ resolve();
+ }
+ });
+ });
+ });
+ }
+ );
+
+ await SpecialPowers.spawn(browser, [{ disableHeuristics }], async obj => {
+ info(
+ "If the heuristic is enabled, the tracker should have storage access now."
+ );
+ let msg = {};
+
+ // If the heuristic is disabled, we won't get storage access.
+ if (obj.disableHeuristics) {
+ msg.nonBlockingCallback = (async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await stillNoStorageAccess();
+ }).toString();
+ } else {
+ msg.nonBlockingCallback = (async _ => {
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await hasStorageAccessInitially();
+ }).toString();
+ }
+
+ await new content.Promise(resolve => {
+ let ifr = content.document.getElementById("ifr");
+ info("Sending code to the 3rd party content");
+ ifr.contentWindow.postMessage(msg.nonBlockingCallback, "*");
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+ });
+ });
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ await SpecialPowers.popPrefEnv();
+
+ info("Cleaning up.");
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+}
+
+add_task(async function testFirstPartyWindowOpenHeuristic() {
+ await runTestFirstPartyWindowOpenHeuristic(false);
+});
+
+add_task(async function testFirstPartyWindowOpenHeuristicDisabled() {
+ await runTestFirstPartyWindowOpenHeuristic(true);
+});
+
+add_task(async function () {
+ UrlClassifierTestUtils.cleanupTestTrackers();
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Arguments.js b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Arguments.js
new file mode 100644
index 0000000000..a88fc8bcb3
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Arguments.js
@@ -0,0 +1,117 @@
+add_task(async function testArgumentInRequestStorageAccessUnderSite() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ [
+ "network.cookie.cookieBehavior",
+ BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_4TH_PARTY_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [], async _ => {
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.requestStorageAccessUnderSite("blob://test");
+ try {
+ await p;
+ ok(false, "Blob URLs must be rejected.");
+ } catch {
+ ok(true, "Must reject.");
+ }
+
+ p = content.document.requestStorageAccessUnderSite("about:config");
+ try {
+ await p;
+ ok(false, "about URLs must be rejected.");
+ } catch {
+ ok(true, "Must reject.");
+ }
+
+ p = content.document.requestStorageAccessUnderSite("qwertyuiop");
+ try {
+ await p;
+ ok(false, "Non URLs must be rejected.");
+ } catch {
+ ok(true, "Must reject.");
+ }
+
+ p = content.document.requestStorageAccessUnderSite("");
+ try {
+ await p;
+ ok(false, "Nullstring must be rejected.");
+ } catch {
+ ok(true, "Must reject.");
+ }
+ });
+
+ await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function testArgumentInCompleteStorageAccessRequest() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ ["network.cookie.cookieBehavior", BEHAVIOR_ACCEPT],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_TOP_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [], async _ => {
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p =
+ content.document.completeStorageAccessRequestFromSite("blob://test");
+ try {
+ await p;
+ ok(false, "Blob URLs must be rejected.");
+ } catch {
+ ok(true, "Must reject.");
+ }
+
+ p = content.document.completeStorageAccessRequestFromSite("about:config");
+ try {
+ await p;
+ ok(false, "about URLs must be rejected.");
+ } catch {
+ ok(true, "Must reject.");
+ }
+
+ p = content.document.completeStorageAccessRequestFromSite("qwertyuiop");
+ try {
+ await p;
+ ok(false, "Non URLs must be rejected.");
+ } catch {
+ ok(true, "Must reject.");
+ }
+
+ p = content.document.completeStorageAccessRequestFromSite("");
+ try {
+ await p;
+ ok(false, "Nullstring must be rejected.");
+ } catch {
+ ok(true, "Must reject.");
+ }
+ });
+ await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async () => {
+ Services.perms.removeAll();
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CookieBehavior.js b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CookieBehavior.js
new file mode 100644
index 0000000000..ea44a34f24
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CookieBehavior.js
@@ -0,0 +1,412 @@
+add_task(async function testBehaviorAcceptRequestStorageAccessUnderSite() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ ["network.cookie.cookieBehavior", BEHAVIOR_ACCEPT],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_3RD_PARTY_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [TEST_DOMAIN], async tp => {
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.requestStorageAccessUnderSite(tp);
+ try {
+ await p;
+ ok(true, "Must resolve.");
+ } catch {
+ ok(false, "Must not reject.");
+ }
+ });
+
+ await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function testBehaviorAcceptCompleteStorageAccessRequest() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ ["network.cookie.cookieBehavior", BEHAVIOR_ACCEPT],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_TOP_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [TEST_3RD_PARTY_DOMAIN], async tp => {
+ await SpecialPowers.pushPermissions([
+ {
+ type: "AllowStorageAccessRequest^http://example.org",
+ allow: 1,
+ context: content.document,
+ },
+ ]);
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.completeStorageAccessRequestFromSite(tp);
+ try {
+ await p;
+ ok(true, "Must resolve.");
+ } catch {
+ ok(false, "Must not reject.");
+ }
+ });
+ await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function testBehaviorRejectRequestStorageAccessUnderSite() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ ["network.cookie.cookieBehavior", BEHAVIOR_REJECT],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_3RD_PARTY_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [TEST_DOMAIN], async tp => {
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.requestStorageAccessUnderSite(tp);
+ try {
+ await p;
+ ok(false, "Must not resolve.");
+ } catch {
+ ok(true, "Must reject.");
+ }
+ });
+
+ await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function testBehaviorRejectCompleteStorageAccessRequest() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ ["network.cookie.cookieBehavior", BEHAVIOR_REJECT],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_TOP_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [TEST_3RD_PARTY_DOMAIN], async tp => {
+ await SpecialPowers.pushPermissions([
+ {
+ type: "AllowStorageAccessRequest^http://example.org",
+ allow: 1,
+ context: content.document,
+ },
+ ]);
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.completeStorageAccessRequestFromSite(tp);
+ try {
+ await p;
+ ok(false, "Must not resolve.");
+ } catch {
+ ok(true, "Must reject.");
+ }
+ });
+ await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(
+ async function testBehaviorLimitForeignRequestStorageAccessUnderSite() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ ["network.cookie.cookieBehavior", BEHAVIOR_LIMIT_FOREIGN],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_3RD_PARTY_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [TEST_DOMAIN], async tp => {
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.requestStorageAccessUnderSite(tp);
+ try {
+ await p;
+ ok(false, "Must not resolve.");
+ } catch {
+ ok(true, "Must reject.");
+ }
+ });
+
+ await BrowserTestUtils.removeTab(tab);
+ }
+);
+
+add_task(async function testBehaviorLimitForeignCompleteStorageAccessRequest() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ ["network.cookie.cookieBehavior", BEHAVIOR_LIMIT_FOREIGN],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_TOP_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [TEST_3RD_PARTY_DOMAIN], async tp => {
+ await SpecialPowers.pushPermissions([
+ {
+ type: "AllowStorageAccessRequest^http://example.org",
+ allow: 1,
+ context: content.document,
+ },
+ ]);
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.completeStorageAccessRequestFromSite(tp);
+ try {
+ await p;
+ ok(false, "Must not resolve.");
+ } catch {
+ ok(true, "Must reject.");
+ }
+ });
+ await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(
+ async function testBehaviorRejectForeignRequestStorageAccessUnderSite() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ ["network.cookie.cookieBehavior", BEHAVIOR_REJECT_FOREIGN],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_3RD_PARTY_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [TEST_DOMAIN], async tp => {
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.requestStorageAccessUnderSite(tp);
+ try {
+ await p;
+ ok(false, "Must not resolve.");
+ } catch {
+ ok(true, "Must reject.");
+ }
+ });
+
+ await BrowserTestUtils.removeTab(tab);
+ }
+);
+
+add_task(
+ async function testBehaviorRejectForeignCompleteStorageAccessRequest() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ ["network.cookie.cookieBehavior", BEHAVIOR_REJECT_FOREIGN],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_TOP_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [TEST_3RD_PARTY_DOMAIN], async tp => {
+ await SpecialPowers.pushPermissions([
+ {
+ type: "AllowStorageAccessRequest^http://example.org",
+ allow: 1,
+ context: content.document,
+ },
+ ]);
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.completeStorageAccessRequestFromSite(tp);
+ try {
+ await p;
+ ok(false, "Must not resolve.");
+ } catch {
+ ok(true, "Must reject.");
+ }
+ });
+ await BrowserTestUtils.removeTab(tab);
+ }
+);
+
+add_task(
+ async function testBehaviorRejectTrackerRequestStorageAccessUnderSite() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ ["network.cookie.cookieBehavior", BEHAVIOR_REJECT_TRACKER],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_3RD_PARTY_DOMAIN,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [TEST_DOMAIN], async tp => {
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.requestStorageAccessUnderSite(tp);
+ try {
+ await p;
+ ok(true, "Must resolve.");
+ } catch {
+ ok(false, "Must not reject.");
+ }
+ });
+
+ await BrowserTestUtils.removeTab(tab);
+ }
+);
+
+add_task(
+ async function testBehaviorRejectTrackerCompleteStorageAccessRequest() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ ["network.cookie.cookieBehavior", BEHAVIOR_REJECT_TRACKER],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_TOP_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [TEST_3RD_PARTY_DOMAIN], async tp => {
+ await SpecialPowers.pushPermissions([
+ {
+ type: "AllowStorageAccessRequest^http://example.com",
+ allow: 1,
+ context: content.document,
+ },
+ ]);
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.completeStorageAccessRequestFromSite(tp);
+ try {
+ await p;
+ ok(true, "Must resolve.");
+ } catch {
+ ok(false, "Must not reject.");
+ }
+ });
+ await BrowserTestUtils.removeTab(tab);
+ }
+);
+
+add_task(
+ async function testBehaviorRejectTrackerAndPartitionForeignRequestStorageAccessUnderSite() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ [
+ "network.cookie.cookieBehavior",
+ BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_3RD_PARTY_DOMAIN,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [TEST_DOMAIN], async tp => {
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.requestStorageAccessUnderSite(tp);
+ try {
+ await p;
+ ok(true, "Must resolve.");
+ } catch {
+ ok(false, "Must not reject.");
+ }
+ });
+
+ await BrowserTestUtils.removeTab(tab);
+ }
+);
+
+add_task(
+ async function testBehaviorRejectTrackerAndPartitionForeignCompleteStorageAccessRequest() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ [
+ "network.cookie.cookieBehavior",
+ BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_TOP_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [TEST_3RD_PARTY_DOMAIN], async tp => {
+ await SpecialPowers.pushPermissions([
+ {
+ type: "AllowStorageAccessRequest^http://example.com",
+ allow: 1,
+ context: content.document,
+ },
+ ]);
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.completeStorageAccessRequestFromSite(tp);
+ try {
+ await p;
+ ok(true, "Must resolve.");
+ } catch {
+ ok(false, "Must not reject.");
+ }
+ });
+ await BrowserTestUtils.removeTab(tab);
+ }
+);
+
+add_task(async () => {
+ Services.perms.removeAll();
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CookiePermission.js b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CookiePermission.js
new file mode 100644
index 0000000000..64ead20020
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CookiePermission.js
@@ -0,0 +1,174 @@
+add_task(async _ => {
+ PermissionTestUtils.add(
+ TEST_4TH_PARTY_PAGE,
+ "cookie",
+ Services.perms.ALLOW_ACTION
+ );
+});
+
+add_task(async function testCookiePermissionRequestStorageAccessUnderSite() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ [
+ "network.cookie.cookieBehavior",
+ BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_4TH_PARTY_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [TEST_DOMAIN], async tp => {
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.requestStorageAccessUnderSite(tp);
+ try {
+ await p;
+ ok(true, "Must resolve.");
+ } catch {
+ ok(false, "Must not reject.");
+ }
+ });
+ await BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPermissions();
+});
+
+add_task(async function testCookiePermissionCompleteStorageAccessRequest() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ [
+ "network.cookie.cookieBehavior",
+ BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_TOP_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [TEST_4TH_PARTY_DOMAIN], async tp => {
+ await SpecialPowers.pushPermissions([
+ {
+ type: "AllowStorageAccessRequest^http://example.com",
+ allow: Services.perms.ALLOW_ACTION,
+ context: content.document,
+ },
+ ]);
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.completeStorageAccessRequestFromSite(tp);
+ try {
+ await p;
+ ok(true, "Must resolve.");
+ } catch {
+ ok(false, "Must not reject.");
+ }
+ });
+ await BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPermissions();
+});
+
+add_task(async _ => {
+ Services.perms.removeAll();
+});
+
+add_task(async _ => {
+ PermissionTestUtils.add(
+ TEST_4TH_PARTY_PAGE,
+ "cookie",
+ Services.perms.DENY_ACTION
+ );
+});
+
+add_task(
+ async function testCookiePermissionRejectRequestStorageAccessUnderSite() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ [
+ "network.cookie.cookieBehavior",
+ BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_4TH_PARTY_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [TEST_DOMAIN], async tp => {
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.requestStorageAccessUnderSite(tp);
+ try {
+ await p;
+ ok(false, "Must not resolve.");
+ } catch {
+ ok(true, "Must reject.");
+ }
+ });
+ await BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPermissions();
+ }
+);
+
+add_task(
+ async function testCookiePermissionRejectCompleteStorageAccessRequest() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ [
+ "network.cookie.cookieBehavior",
+ BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_TOP_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [TEST_4TH_PARTY_DOMAIN], async tp => {
+ await SpecialPowers.pushPermissions([
+ {
+ type: "AllowStorageAccessRequest^http://example.com",
+ allow: Services.perms.ALLOW_ACTION,
+ context: content.document,
+ },
+ ]);
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.completeStorageAccessRequestFromSite(tp);
+ try {
+ await p;
+ ok(false, "Must not resolve.");
+ } catch {
+ ok(true, "Must reject.");
+ }
+ });
+ await BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPermissions();
+ }
+);
+
+add_task(async () => {
+ Services.perms.removeAll();
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CrossOriginSameSite.js b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CrossOriginSameSite.js
new file mode 100644
index 0000000000..ca3e47d8e7
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CrossOriginSameSite.js
@@ -0,0 +1,162 @@
+add_task(async function testIntermediatePreferenceReadSameSite() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ [
+ "network.cookie.cookieBehavior",
+ BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_DOMAIN_7,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [TEST_3RD_PARTY_DOMAIN], async tp => {
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.completeStorageAccessRequestFromSite(tp);
+ try {
+ await p;
+ ok(false, "Must not resolve.");
+ } catch {
+ ok(true, "Must reject because we don't have the initial request.");
+ }
+ });
+
+ await SpecialPowers.pushPermissions([
+ {
+ type: "AllowStorageAccessRequest^https://example.com",
+ allow: 1,
+ context: TEST_DOMAIN_7,
+ },
+ ]);
+
+ await SpecialPowers.spawn(browser, [TEST_3RD_PARTY_DOMAIN], async tp => {
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.completeStorageAccessRequestFromSite(tp);
+ try {
+ await p;
+ ok(false, "Must not resolve.");
+ } catch {
+ ok(true, "Must reject because the permission is cross site.");
+ }
+ });
+
+ await SpecialPowers.pushPermissions([
+ {
+ type: "AllowStorageAccessRequest^https://example.org",
+ allow: 1,
+ context: TEST_DOMAIN_7,
+ },
+ ]);
+
+ await SpecialPowers.spawn(browser, [TEST_3RD_PARTY_DOMAIN], async tp => {
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.completeStorageAccessRequestFromSite(tp);
+ try {
+ await p;
+ ok(
+ true,
+ "Must resolve now that we have the permission from the embedee."
+ );
+ } catch {
+ ok(false, "Must not reject.");
+ }
+ });
+
+ await SpecialPowers.pushPermissions([
+ {
+ type: "AllowStorageAccessRequest^https://example.org",
+ allow: 1,
+ context: TEST_DOMAIN_8,
+ },
+ ]);
+
+ await SpecialPowers.spawn(browser, [TEST_3RD_PARTY_DOMAIN], async tp => {
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.completeStorageAccessRequestFromSite(tp);
+ try {
+ await p;
+ ok(
+ true,
+ "Must resolve now that we have the permission from the embedee."
+ );
+ } catch {
+ ok(false, "Must not reject.");
+ }
+ });
+
+ await BrowserTestUtils.removeTab(tab);
+});
+
+// Note: TEST_DOMAIN_7 and TEST_DOMAIN_8 are Same-Site
+add_task(async function testIntermediatePreferenceWriteCrossOrigin() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ [
+ "network.cookie.cookieBehavior",
+ BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_3RD_PARTY_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [TEST_DOMAIN_8], async tp => {
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.requestStorageAccessUnderSite(tp);
+ try {
+ await p;
+ ok(
+ true,
+ "Must resolve- no funny business here, we just want to set the intermediate pref"
+ );
+ } catch {
+ ok(false, "Must not reject.");
+ }
+ });
+
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ TEST_DOMAIN_8
+ );
+ // Important to note that this is the site but not origin of TEST_3RD_PARTY_PAGE
+ var permission = Services.perms.testPermissionFromPrincipal(
+ principal,
+ "AllowStorageAccessRequest^https://example.org"
+ );
+ ok(permission == Services.perms.ALLOW_ACTION);
+
+ // Test that checking the permission across site works
+ principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ TEST_DOMAIN_7
+ );
+ // Important to note that this is the site but not origin of TEST_3RD_PARTY_PAGE
+ permission = Services.perms.testPermissionFromPrincipal(
+ principal,
+ "AllowStorageAccessRequest^https://example.org"
+ );
+ ok(permission == Services.perms.ALLOW_ACTION);
+
+ await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async () => {
+ Services.perms.removeAll();
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Doorhanger.js b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Doorhanger.js
new file mode 100644
index 0000000000..e8d6ce9bb1
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Doorhanger.js
@@ -0,0 +1,122 @@
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/browser/modules/test/browser/head.js",
+ this
+);
+
+async function cleanUp() {
+ Services.perms.removeAll();
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+}
+
+add_task(async function testDoorhangerRequestStorageAccessUnderSite() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ ["dom.storage_access.prompt.testing", false],
+ [
+ "network.cookie.cookieBehavior",
+ BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_4TH_PARTY_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ let permChanged = TestUtils.topicObserved("perm-changed", (subject, data) => {
+ let result =
+ subject
+ .QueryInterface(Ci.nsIPermission)
+ .type.startsWith("AllowStorageAccessRequest^") &&
+ subject.principal.origin == new URL(TEST_TOP_PAGE).origin &&
+ data == "added";
+ return result;
+ }).then(() => {
+ ok(true, "Permission changed to add intermediate permission");
+ });
+ let shownPromise = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popupshown"
+ ).then(_ => {
+ ok(true, "Must display doorhanger from RequestStorageAccessUnderSite");
+ return clickMainAction();
+ });
+ let sp = SpecialPowers.spawn(browser, [TEST_DOMAIN], async tp => {
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ let p = content.document.requestStorageAccessUnderSite(tp);
+ try {
+ await p;
+ ok(true, "Must resolve.");
+ } catch {
+ ok(false, "Must not reject.");
+ }
+ });
+
+ await Promise.all([sp, shownPromise, permChanged]);
+ await cleanUp();
+ await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function testNoDoorhangerCompleteStorageAccessRequestFromSite() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ [
+ "network.cookie.cookieBehavior",
+ BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ ["dom.storage_access.prompt.testing", false],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_TOP_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ let popupShown = false;
+ // This promise is used to the absence of a doorhanger showing up.
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown")
+ .then(_ => {
+ // This will be called if a doorhanger is shown.
+ ok(
+ false,
+ "Must not display doorhanger from CompleteStorageAccessRequestFromSite"
+ );
+ popupShown = true;
+ })
+ .catch(_ => {
+ // This will be called when the test ends if a doorhanger is not shown
+ ok(true, "It is expected for this popup to not show up.");
+ });
+ await SpecialPowers.spawn(browser, [TEST_4TH_PARTY_DOMAIN], async tp => {
+ await SpecialPowers.pushPermissions([
+ {
+ type: "AllowStorageAccessRequest^http://example.com",
+ allow: 1,
+ context: content.document,
+ },
+ ]);
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ let p = content.document.completeStorageAccessRequestFromSite(tp);
+ try {
+ await p;
+ ok(true, "Must resolve.");
+ } catch {
+ ok(false, "Must not reject.");
+ }
+ });
+ ok(!popupShown, "Must not have shown a popup during this test.");
+ await cleanUp();
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Embed.js b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Embed.js
new file mode 100644
index 0000000000..86f584763c
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Embed.js
@@ -0,0 +1,155 @@
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/toolkit/components/antitracking/test/browser/storage_access_head.js",
+ this
+);
+
+async function requestStorageAccessUnderSiteAndExpectSuccess() {
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ var p = document.requestStorageAccessUnderSite("http://example.org");
+ try {
+ await p;
+ ok(true, "Must resolve.");
+ } catch {
+ ok(false, "Must not reject.");
+ }
+}
+
+async function requestStorageAccessUnderSiteAndExpectFailure() {
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ var p = document.requestStorageAccessUnderSite("http://example.org");
+ try {
+ await p;
+ ok(false, "Must not resolve.");
+ } catch {
+ ok(true, "Must reject.");
+ }
+}
+
+async function completeStorageAccessRequestFromSiteAndExpectSuccess() {
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ var p = document.completeStorageAccessRequestFromSite("http://example.org");
+ try {
+ await p;
+ ok(true, "Must resolve.");
+ } catch {
+ ok(false, "Must not reject.");
+ }
+}
+
+async function completeStorageAccessRequestFromSiteAndExpectFailure() {
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ var p = document.completeStorageAccessRequestFromSite("http://example.org");
+ try {
+ await p;
+ ok(false, "Must not resolve.");
+ } catch {
+ ok(true, "Must reject.");
+ }
+}
+
+async function setIntermediatePreference() {
+ await SpecialPowers.pushPermissions([
+ {
+ type: "AllowStorageAccessRequest^http://example.org",
+ allow: 1,
+ context: "http://example.com/",
+ },
+ ]);
+}
+
+async function configurePrefs() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ [
+ "network.cookie.cookieBehavior",
+ BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+}
+
+add_task(async function rSAUS_sameOriginIframe() {
+ await configurePrefs();
+ await openPageAndRunCode(
+ TEST_TOP_PAGE_7,
+ () => {},
+ TEST_DOMAIN_7 + TEST_PATH + "3rdParty.html",
+ requestStorageAccessUnderSiteAndExpectSuccess
+ );
+ await cleanUpData();
+ await SpecialPowers.flushPrefEnv();
+});
+
+add_task(async function rSAUS_sameSiteIframe() {
+ await configurePrefs();
+ await openPageAndRunCode(
+ TEST_TOP_PAGE_7,
+ () => {},
+ TEST_DOMAIN_8 + TEST_PATH + "3rdParty.html",
+ requestStorageAccessUnderSiteAndExpectSuccess
+ );
+ await cleanUpData();
+ await SpecialPowers.flushPrefEnv();
+});
+
+add_task(async function rSAUS_crossSiteIframe() {
+ await configurePrefs();
+ await openPageAndRunCode(
+ TEST_TOP_PAGE_7,
+ () => {},
+ TEST_DOMAIN + TEST_PATH + "3rdParty.html",
+ requestStorageAccessUnderSiteAndExpectFailure
+ );
+ await cleanUpData();
+ await SpecialPowers.flushPrefEnv();
+});
+
+add_task(async function cSAR_sameOriginIframe() {
+ await configurePrefs();
+ await openPageAndRunCode(
+ TEST_TOP_PAGE_7,
+ setIntermediatePreference,
+ TEST_DOMAIN_7 + TEST_PATH + "3rdParty.html",
+ completeStorageAccessRequestFromSiteAndExpectSuccess
+ );
+ await cleanUpData();
+ await SpecialPowers.flushPrefEnv();
+});
+
+add_task(async function cSAR_sameSiteIframe() {
+ await configurePrefs();
+ await setIntermediatePreference();
+ await openPageAndRunCode(
+ TEST_TOP_PAGE_7,
+ () => {},
+ TEST_DOMAIN_8 + TEST_PATH + "3rdParty.html",
+ completeStorageAccessRequestFromSiteAndExpectSuccess
+ );
+ await cleanUpData();
+ await SpecialPowers.flushPrefEnv();
+});
+
+add_task(async function cSAR_crossSiteIframe() {
+ await configurePrefs();
+ await openPageAndRunCode(
+ TEST_TOP_PAGE_7,
+ setIntermediatePreference,
+ TEST_DOMAIN + TEST_PATH + "3rdParty.html",
+ completeStorageAccessRequestFromSiteAndExpectFailure
+ );
+ await cleanUpData();
+ await SpecialPowers.flushPrefEnv();
+});
+
+add_task(async () => {
+ Services.perms.removeAll();
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Enable.js b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Enable.js
new file mode 100644
index 0000000000..6ee61cc378
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Enable.js
@@ -0,0 +1,86 @@
+add_task(async function testDefaultDisabled() {
+ let value = Services.prefs.getBoolPref(
+ "dom.storage_access.forward_declared.enabled"
+ );
+ ok(!value, "dom.storage_access.forward_declared.enabled should be false");
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_TOP_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [], async _ => {
+ ok(
+ content.window.requestStorageAccessUnderSite == undefined,
+ "API should not be on the window"
+ );
+ ok(
+ content.window.completeStorageAccessRequestFromSite == undefined,
+ "API should not be on the window"
+ );
+ });
+ await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function testExplicitlyDisabled() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.storage_access.forward_declared.enabled", false]],
+ });
+ let value = Services.prefs.getBoolPref(
+ "dom.storage_access.forward_declared.enabled"
+ );
+ ok(!value, "dom.storage_access.forward_declared.enabled should be false");
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_TOP_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [], async _ => {
+ ok(
+ content.window.requestStorageAccessUnderSite == undefined,
+ "API should not be on the window"
+ );
+ ok(
+ content.window.completeStorageAccessRequestFromSite == undefined,
+ "API should not be on the window"
+ );
+ });
+ await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function testExplicitlyEnabled() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ ],
+ });
+ let value = Services.prefs.getBoolPref(
+ "dom.storage_access.forward_declared.enabled"
+ );
+ ok(value, "dom.storage_access.forward_declared.enabled should be true");
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_TOP_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [], async _ => {
+ ok(
+ content.document.requestStorageAccessUnderSite != undefined,
+ "API should be on the window"
+ );
+ ok(
+ content.document.completeStorageAccessRequestFromSite != undefined,
+ "API should be on the window"
+ );
+ });
+ await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async () => {
+ Services.perms.removeAll();
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_RequireIntermediatePermission.js b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_RequireIntermediatePermission.js
new file mode 100644
index 0000000000..a5cc6f10b5
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_RequireIntermediatePermission.js
@@ -0,0 +1,61 @@
+add_task(async function testIntermediatePermissionRequired() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ [
+ "network.cookie.cookieBehavior",
+ BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_TOP_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [TEST_3RD_PARTY_DOMAIN], async tp => {
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.completeStorageAccessRequestFromSite(tp);
+ try {
+ await p;
+ ok(false, "Must not resolve.");
+ } catch {
+ ok(true, "Must reject because we don't have the initial request.");
+ }
+ });
+
+ await SpecialPowers.pushPermissions([
+ {
+ type: "AllowStorageAccessRequest^https://example.org",
+ allow: 1,
+ context: TEST_TOP_PAGE,
+ },
+ ]);
+
+ await SpecialPowers.spawn(browser, [TEST_3RD_PARTY_DOMAIN], async tp => {
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.completeStorageAccessRequestFromSite(tp);
+ try {
+ await p;
+ ok(
+ true,
+ "Must resolve now that we have the permission from the embedee."
+ );
+ } catch {
+ ok(false, "Must not reject.");
+ }
+ });
+ await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async () => {
+ Services.perms.removeAll();
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_StorageAccessPermission.js b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_StorageAccessPermission.js
new file mode 100644
index 0000000000..0b4f3e7273
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_StorageAccessPermission.js
@@ -0,0 +1,94 @@
+add_task(
+ async function testStorageAccessPermissionRequestStorageAccessUnderSite() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ [
+ "network.cookie.cookieBehavior",
+ BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_4TH_PARTY_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [TEST_DOMAIN], async tp => {
+ await SpecialPowers.pushPermissions([
+ {
+ type: "3rdPartyStorage^http://not-tracking.example.com",
+ allow: 1,
+ context: tp,
+ },
+ ]);
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.requestStorageAccessUnderSite(tp);
+ try {
+ await p;
+ ok(true, "Must resolve.");
+ } catch {
+ ok(false, "Must not reject.");
+ }
+ });
+
+ await BrowserTestUtils.removeTab(tab);
+ }
+);
+
+add_task(
+ async function testStorageAccessPermissionCompleteStorageAccessRequestFromSite() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ [
+ "network.cookie.cookieBehavior",
+ BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_TOP_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [TEST_4TH_PARTY_DOMAIN], async tp => {
+ await SpecialPowers.pushPermissions([
+ {
+ type: "AllowStorageAccessRequest^http://example.com",
+ allow: 1,
+ context: content.document,
+ },
+ {
+ type: "3rdPartyStorage^http://not-tracking.example.com",
+ allow: 1,
+ context: content.document,
+ },
+ ]);
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ var p = content.document.completeStorageAccessRequestFromSite(tp);
+ try {
+ await p;
+ ok(true, "Must resolve.");
+ } catch {
+ ok(false, "Must not reject.");
+ }
+ });
+ await BrowserTestUtils.removeTab(tab);
+ }
+);
+
+add_task(async () => {
+ Services.perms.removeAll();
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_UserActivation.js b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_UserActivation.js
new file mode 100644
index 0000000000..49dd73fbf7
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_UserActivation.js
@@ -0,0 +1,63 @@
+add_task(async function testUserActivations() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.forward_declared.enabled", true],
+ ["network.cookie.cookieBehavior", BEHAVIOR_ACCEPT],
+ ["dom.storage_access.auto_grants", false],
+ ["dom.storage_access.max_concurrent_auto_grants", 1],
+ ],
+ });
+ // Part 1: open the embedded site as a top level
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_3RD_PARTY_PAGE,
+ });
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [TEST_DOMAIN], async tp => {
+ // Part 2: requestStorageAccessUnderSite without activation
+ var p = content.document.requestStorageAccessUnderSite(tp);
+ try {
+ await p;
+ ok(false, "Must not resolve without user activation.");
+ } catch {
+ ok(true, "Must reject without user activation.");
+ }
+ // Part 3: requestStorageAccessUnderSite with activation
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ p = content.document.requestStorageAccessUnderSite(tp);
+ try {
+ await p;
+ ok(true, "Must resolve with user activation and autogrant.");
+ } catch {
+ ok(false, "Must not reject with user activation.");
+ }
+ });
+ // Part 4: open the embedding site as a top level
+ let tab2 = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: TEST_TOP_PAGE,
+ });
+ let browser2 = tab2.linkedBrowser;
+ await SpecialPowers.spawn(browser2, [TEST_3RD_PARTY_DOMAIN], async tp => {
+ // Part 5: completeStorageAccessRequestFromSite without activation
+ var p = content.document.completeStorageAccessRequestFromSite(tp);
+ try {
+ await p;
+ ok(true, "Must resolve without user activation.");
+ } catch {
+ ok(false, "Must not reject without user activation in this context.");
+ }
+ });
+ await BrowserTestUtils.removeTab(tab);
+ await BrowserTestUtils.removeTab(tab2);
+});
+
+add_task(async () => {
+ Services.perms.removeAll();
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_subResources.js b/toolkit/components/antitracking/test/browser/browser_subResources.js
new file mode 100644
index 0000000000..4841527d19
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_subResources.js
@@ -0,0 +1,277 @@
+add_task(async function () {
+ info("Starting subResources test");
+
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ ["privacy.partition.network_state", false],
+ [
+ "privacy.restrict3rdpartystorage.userInteractionRequiredForHosts",
+ "tracking.example.com,tracking.example.org",
+ ],
+ // Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default"
+ ["network.cookie.sameSite.laxByDefault", false],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Loading tracking scripts and tracking images");
+ await SpecialPowers.spawn(browser, [], async function () {
+ // Let's load the script twice here.
+ {
+ let src = content.document.createElement("script");
+ let p = new content.Promise(resolve => {
+ src.onload = resolve;
+ });
+ content.document.body.appendChild(src);
+ src.src =
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=script";
+ await p;
+ }
+ {
+ let src = content.document.createElement("script");
+ let p = new content.Promise(resolve => {
+ src.onload = resolve;
+ });
+ content.document.body.appendChild(src);
+ src.src =
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=script";
+ await p;
+ }
+
+ // Let's load an image twice here.
+ {
+ let img = content.document.createElement("img");
+ let p = new content.Promise(resolve => {
+ img.onload = resolve;
+ });
+ content.document.body.appendChild(img);
+ img.src =
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=image";
+ await p;
+ }
+ {
+ let img = content.document.createElement("img");
+ let p = new content.Promise(resolve => {
+ img.onload = resolve;
+ });
+ content.document.body.appendChild(img);
+ img.src =
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=image";
+ await p;
+ }
+ });
+
+ await fetch(
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?result&what=image"
+ )
+ .then(r => r.text())
+ .then(text => {
+ is(text, "0", "Cookies received for images");
+ });
+
+ await fetch(
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?result&what=script"
+ )
+ .then(r => r.text())
+ .then(text => {
+ is(text, "0", "Cookies received for scripts");
+ });
+
+ info("Creating a 3rd party content");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: TEST_3RD_PARTY_PAGE_WO,
+ blockingCallback: (async _ => {}).toString(),
+ nonBlockingCallback: (async _ => {}).toString(),
+ },
+ ],
+ async function (obj) {
+ await new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.onload = function () {
+ info("Sending code to the 3rd party content");
+ ifr.contentWindow.postMessage(obj, "*");
+ };
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ });
+ }
+ );
+
+ info("Loading tracking scripts and tracking images again");
+ await SpecialPowers.spawn(browser, [], async function () {
+ // Let's load the script twice here.
+ {
+ let src = content.document.createElement("script");
+ let p = new content.Promise(resolve => {
+ src.onload = resolve;
+ });
+ content.document.body.appendChild(src);
+ src.src =
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=script";
+ await p;
+ }
+ {
+ let src = content.document.createElement("script");
+ let p = new content.Promise(resolve => {
+ src.onload = resolve;
+ });
+ content.document.body.appendChild(src);
+ src.src =
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=script";
+ await p;
+ }
+
+ // Let's load an image twice here.
+ {
+ let img = content.document.createElement("img");
+ let p = new content.Promise(resolve => {
+ img.onload = resolve;
+ });
+ content.document.body.appendChild(img);
+ img.src =
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=image";
+ await p;
+ }
+ {
+ let img = content.document.createElement("img");
+ let p = new content.Promise(resolve => {
+ img.onload = resolve;
+ });
+ content.document.body.appendChild(img);
+ img.src =
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=image";
+ await p;
+ }
+ });
+
+ await fetch(
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?result&what=image"
+ )
+ .then(r => r.text())
+ .then(text => {
+ is(text, "1", "One cookie received for images.");
+ });
+
+ await fetch(
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?result&what=script"
+ )
+ .then(r => r.text())
+ .then(text => {
+ is(text, "1", "One cookie received received for scripts.");
+ });
+
+ let expectTrackerBlocked = (item, blocked) => {
+ is(
+ item[0],
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER,
+ "Correct blocking type reported"
+ );
+ is(item[1], blocked, "Correct blocking status reported");
+ ok(item[2] >= 1, "Correct repeat count reported");
+ };
+
+ let expectTrackerFound = item => {
+ is(
+ item[0],
+ Ci.nsIWebProgressListener.STATE_LOADED_LEVEL_1_TRACKING_CONTENT,
+ "Correct blocking type reported"
+ );
+ is(item[1], true, "Correct blocking status reported");
+ ok(item[2] >= 1, "Correct repeat count reported");
+ };
+
+ let expectCookiesLoaded = item => {
+ is(
+ item[0],
+ Ci.nsIWebProgressListener.STATE_COOKIES_LOADED,
+ "Correct blocking type reported"
+ );
+ is(item[1], true, "Correct blocking status reported");
+ ok(item[2] >= 1, "Correct repeat count reported");
+ };
+
+ let expectTrackerCookiesLoaded = item => {
+ is(
+ item[0],
+ Ci.nsIWebProgressListener.STATE_COOKIES_LOADED_TRACKER,
+ "Correct blocking type reported"
+ );
+ is(item[1], true, "Correct blocking status reported");
+ ok(item[2] >= 1, "Correct repeat count reported");
+ };
+
+ let log = JSON.parse(await browser.getContentBlockingLog());
+ for (let trackerOrigin in log) {
+ is(
+ trackerOrigin + "/",
+ TEST_3RD_PARTY_DOMAIN,
+ "Correct tracker origin must be reported"
+ );
+ let originLog = log[trackerOrigin];
+ is(originLog.length, 5, "We should have 4 entries in the compressed log");
+ expectTrackerFound(originLog[0]);
+ expectCookiesLoaded(originLog[1]);
+ expectTrackerCookiesLoaded(originLog[2]);
+ expectTrackerBlocked(originLog[3], true);
+ expectTrackerBlocked(originLog[4], false);
+ }
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+});
+
+add_task(async function () {
+ info("Cleaning up.");
+ SpecialPowers.clearUserPref("network.cookie.sameSite.laxByDefault");
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_subResourcesPartitioned.js b/toolkit/components/antitracking/test/browser/browser_subResourcesPartitioned.js
new file mode 100644
index 0000000000..b2de150075
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_subResourcesPartitioned.js
@@ -0,0 +1,308 @@
+async function runTests(topPage, limitForeignContexts) {
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, topPage);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Loading scripts and images");
+ await SpecialPowers.spawn(browser, [], async function () {
+ // Let's load the script twice here.
+ {
+ let src = content.document.createElement("script");
+ let p = new content.Promise(resolve => {
+ src.onload = resolve;
+ });
+ content.document.body.appendChild(src);
+ src.src =
+ "https://example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=script";
+ await p;
+ }
+ {
+ let src = content.document.createElement("script");
+ let p = new content.Promise(resolve => {
+ src.onload = resolve;
+ });
+ content.document.body.appendChild(src);
+ src.src =
+ "https://example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=script";
+ await p;
+ }
+
+ // Let's load an image twice here.
+ {
+ let img = content.document.createElement("img");
+ let p = new content.Promise(resolve => {
+ img.onload = resolve;
+ });
+ content.document.body.appendChild(img);
+ img.src =
+ "https://example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=image";
+ await p;
+ }
+ {
+ let img = content.document.createElement("img");
+ let p = new content.Promise(resolve => {
+ img.onload = resolve;
+ });
+ content.document.body.appendChild(img);
+ img.src =
+ "https://example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=image";
+ await p;
+ }
+ });
+
+ await fetch(
+ "https://example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?result&what=image"
+ )
+ .then(r => r.text())
+ .then(text => {
+ if (limitForeignContexts) {
+ is(text, "0", "No cookie received for images.");
+ } else {
+ is(text, "1", "One cookie received for images.");
+ }
+ });
+
+ await fetch(
+ "https://example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?result&what=script"
+ )
+ .then(r => r.text())
+ .then(text => {
+ if (limitForeignContexts) {
+ is(text, "0", "No cookie received received for scripts.");
+ } else {
+ is(text, "1", "One cookie received received for scripts.");
+ }
+ });
+
+ info("Creating a 3rd party content");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: TEST_3RD_PARTY_PAGE_WO,
+ blockingCallback: (async _ => {}).toString(),
+ nonBlockingCallback: (async _ => {}).toString(),
+ },
+ ],
+ async function (obj) {
+ await new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.onload = function () {
+ info("Sending code to the 3rd party content");
+ ifr.contentWindow.postMessage(obj, "*");
+ };
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ });
+ }
+ );
+
+ info("Loading scripts and images again");
+ await SpecialPowers.spawn(browser, [], async function () {
+ // Let's load the script twice here.
+ {
+ let src = content.document.createElement("script");
+ let p = new content.Promise(resolve => {
+ src.onload = resolve;
+ });
+ content.document.body.appendChild(src);
+ src.src =
+ "https://example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=script";
+ await p;
+ }
+ {
+ let src = content.document.createElement("script");
+ let p = new content.Promise(resolve => {
+ src.onload = resolve;
+ });
+ content.document.body.appendChild(src);
+ src.src =
+ "https://example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=script";
+ await p;
+ }
+
+ // Let's load an image twice here.
+ {
+ let img = content.document.createElement("img");
+ let p = new content.Promise(resolve => {
+ img.onload = resolve;
+ });
+ content.document.body.appendChild(img);
+ img.src =
+ "https://example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=image";
+ await p;
+ }
+ {
+ let img = content.document.createElement("img");
+ let p = new content.Promise(resolve => {
+ img.onload = resolve;
+ });
+ content.document.body.appendChild(img);
+ img.src =
+ "https://example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=image";
+ await p;
+ }
+ });
+
+ await fetch(
+ "https://example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?result&what=image"
+ )
+ .then(r => r.text())
+ .then(text => {
+ if (limitForeignContexts) {
+ is(text, "0", "No cookie received for images.");
+ } else {
+ is(text, "1", "One cookie received for images.");
+ }
+ });
+
+ await fetch(
+ "https://example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?result&what=script"
+ )
+ .then(r => r.text())
+ .then(text => {
+ if (limitForeignContexts) {
+ is(text, "0", "No cookie received received for scripts.");
+ } else {
+ is(text, "1", "One cookie received received for scripts.");
+ }
+ });
+
+ let expectTrackerBlocked = (item, blocked) => {
+ is(
+ item[0],
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER,
+ "Correct blocking type reported"
+ );
+ is(item[1], blocked, "Correct blocking status reported");
+ ok(item[2] >= 1, "Correct repeat count reported");
+ };
+
+ let expectCookiesLoaded = item => {
+ is(
+ item[0],
+ Ci.nsIWebProgressListener.STATE_COOKIES_LOADED,
+ "Correct blocking type reported"
+ );
+ is(item[1], true, "Correct blocking status reported");
+ ok(item[2] >= 1, "Correct repeat count reported");
+ };
+
+ let expectCookiesBlockedForeign = item => {
+ is(
+ item[0],
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_FOREIGN,
+ "Correct blocking type reported"
+ );
+ is(item[1], true, "Correct blocking status reported");
+ ok(item[2] >= 1, "Correct repeat count reported");
+ };
+
+ let log = JSON.parse(await browser.getContentBlockingLog());
+ for (let trackerOrigin in log) {
+ let originLog = log[trackerOrigin];
+ info(trackerOrigin);
+ switch (trackerOrigin) {
+ case "https://example.org":
+ case "https://example.com":
+ let numEntries = 1;
+ if (limitForeignContexts) {
+ ++numEntries;
+ }
+ is(
+ originLog.length,
+ numEntries,
+ `We should have ${numEntries} entries in the compressed log`
+ );
+ expectCookiesLoaded(originLog[0]);
+ if (limitForeignContexts) {
+ expectCookiesBlockedForeign(originLog[1]);
+ }
+ break;
+ case "https://tracking.example.org":
+ is(
+ originLog.length,
+ 1,
+ "We should have 1 entries in the compressed log"
+ );
+ expectTrackerBlocked(originLog[0], false);
+ break;
+ }
+ }
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+}
+
+add_task(async function () {
+ info("Starting subResources test");
+
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ // Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default"
+ ["network.cookie.sameSite.laxByDefault", false],
+ [
+ "privacy.partition.always_partition_third_party_non_cookie_storage",
+ false,
+ ],
+ ],
+ });
+
+ for (let limitForeignContexts of [false, true]) {
+ SpecialPowers.setBoolPref(
+ "privacy.dynamic_firstparty.limitForeign",
+ limitForeignContexts
+ );
+ for (let page of [TEST_TOP_PAGE, TEST_TOP_PAGE_2, TEST_TOP_PAGE_3]) {
+ await runTests(page, limitForeignContexts);
+ }
+ }
+
+ SpecialPowers.clearUserPref("privacy.dynamic_firstparty.limitForeign");
+ SpecialPowers.clearUserPref("network.cookie.sameSite.laxByDefault");
+});
+
+add_task(async function () {
+ info("Cleaning up.");
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_subResourcesPartitioned_alwaysPartition.js b/toolkit/components/antitracking/test/browser/browser_subResourcesPartitioned_alwaysPartition.js
new file mode 100644
index 0000000000..53a90854b3
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_subResourcesPartitioned_alwaysPartition.js
@@ -0,0 +1,313 @@
+async function runTests(topPage, limitForeignContexts) {
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, topPage);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Loading scripts and images");
+ await SpecialPowers.spawn(browser, [], async function () {
+ // Let's load the script twice here.
+ {
+ let src = content.document.createElement("script");
+ let p = new content.Promise(resolve => {
+ src.onload = resolve;
+ });
+ content.document.body.appendChild(src);
+ src.src =
+ "https://example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=script";
+ await p;
+ }
+ {
+ let src = content.document.createElement("script");
+ let p = new content.Promise(resolve => {
+ src.onload = resolve;
+ });
+ content.document.body.appendChild(src);
+ src.src =
+ "https://example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=script";
+ await p;
+ }
+
+ // Let's load an image twice here.
+ {
+ let img = content.document.createElement("img");
+ let p = new content.Promise(resolve => {
+ img.onload = resolve;
+ });
+ content.document.body.appendChild(img);
+ img.src =
+ "https://example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=image";
+ await p;
+ }
+ {
+ let img = content.document.createElement("img");
+ let p = new content.Promise(resolve => {
+ img.onload = resolve;
+ });
+ content.document.body.appendChild(img);
+ img.src =
+ "https://example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=image";
+ await p;
+ }
+ });
+
+ await fetch(
+ "https://example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?result&what=image"
+ )
+ .then(r => r.text())
+ .then(text => {
+ if (limitForeignContexts) {
+ is(text, "0", "No cookie received for images.");
+ } else {
+ is(text, "1", "One cookie received for images.");
+ }
+ });
+
+ await fetch(
+ "https://example.org/browser/toolkit/components/antitracking/test/browser/subResources.sjs?result&what=script"
+ )
+ .then(r => r.text())
+ .then(text => {
+ if (limitForeignContexts) {
+ is(text, "0", "No cookie received received for scripts.");
+ } else {
+ is(text, "1", "One cookie received received for scripts.");
+ }
+ });
+
+ info("Creating a 3rd party content");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: TEST_3RD_PARTY_PAGE_WO,
+ blockingCallback: (async _ => {}).toString(),
+ nonBlockingCallback: (async _ => {}).toString(),
+ },
+ ],
+ async function (obj) {
+ await new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.onload = function () {
+ info("Sending code to the 3rd party content");
+ ifr.contentWindow.postMessage(obj, "*");
+ };
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ });
+ }
+ );
+
+ info("Loading scripts and images again");
+ await SpecialPowers.spawn(browser, [], async function () {
+ // Let's load the script twice here.
+ {
+ let src = content.document.createElement("script");
+ let p = new content.Promise(resolve => {
+ src.onload = resolve;
+ });
+ content.document.body.appendChild(src);
+ src.src =
+ "https://example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=script";
+ await p;
+ }
+ {
+ let src = content.document.createElement("script");
+ let p = new content.Promise(resolve => {
+ src.onload = resolve;
+ });
+ content.document.body.appendChild(src);
+ src.src =
+ "https://example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=script";
+ await p;
+ }
+
+ // Let's load an image twice here.
+ {
+ let img = content.document.createElement("img");
+ let p = new content.Promise(resolve => {
+ img.onload = resolve;
+ });
+ content.document.body.appendChild(img);
+ img.src =
+ "https://example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=image";
+ await p;
+ }
+ {
+ let img = content.document.createElement("img");
+ let p = new content.Promise(resolve => {
+ img.onload = resolve;
+ });
+ content.document.body.appendChild(img);
+ img.src =
+ "https://example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=image";
+ await p;
+ }
+ });
+
+ await fetch(
+ "https://example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?result&what=image"
+ )
+ .then(r => r.text())
+ .then(text => {
+ if (limitForeignContexts) {
+ is(text, "0", "No cookie received for images.");
+ } else {
+ is(text, "1", "One cookie received for images.");
+ }
+ });
+
+ await fetch(
+ "https://example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?result&what=script"
+ )
+ .then(r => r.text())
+ .then(text => {
+ if (limitForeignContexts) {
+ is(text, "0", "No cookie received received for scripts.");
+ } else {
+ is(text, "1", "One cookie received received for scripts.");
+ }
+ });
+
+ let expectTrackerBlocked = (item, blocked, type) => {
+ is(item[0], type, "Correct blocking type reported");
+ is(item[1], blocked, "Correct blocking status reported");
+ ok(item[2] >= 1, "Correct repeat count reported");
+ };
+
+ let expectCookiesLoaded = item => {
+ is(
+ item[0],
+ Ci.nsIWebProgressListener.STATE_COOKIES_LOADED,
+ "Correct blocking type reported"
+ );
+ is(item[1], true, "Correct blocking status reported");
+ ok(item[2] >= 1, "Correct repeat count reported");
+ };
+
+ let expectCookiesBlockedForeign = item => {
+ is(
+ item[0],
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_FOREIGN,
+ "Correct blocking type reported"
+ );
+ is(item[1], true, "Correct blocking status reported");
+ ok(item[2] >= 1, "Correct repeat count reported");
+ };
+
+ let log = JSON.parse(await browser.getContentBlockingLog());
+ for (let trackerOrigin in log) {
+ let originLog = log[trackerOrigin];
+ info(trackerOrigin);
+ switch (trackerOrigin) {
+ case "https://example.org":
+ case "https://example.com":
+ let numEntries = 1;
+ if (limitForeignContexts) {
+ ++numEntries;
+ }
+ is(
+ originLog.length,
+ numEntries,
+ `We should have ${numEntries} entries in the compressed log`
+ );
+ expectCookiesLoaded(originLog[0]);
+ if (limitForeignContexts) {
+ expectCookiesBlockedForeign(originLog[1]);
+ }
+ break;
+ case "https://tracking.example.org":
+ is(
+ originLog.length,
+ 2,
+ "We should have 2 entries in the compressed log"
+ );
+ expectTrackerBlocked(
+ originLog[0],
+ true,
+ Ci.nsIWebProgressListener.STATE_COOKIES_LOADED
+ );
+ expectTrackerBlocked(
+ originLog[1],
+ false,
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER
+ );
+ break;
+ }
+ }
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+}
+
+add_task(async function () {
+ info("Starting subResources test");
+
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ // Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default"
+ ["network.cookie.sameSite.laxByDefault", false],
+ [
+ "privacy.partition.always_partition_third_party_non_cookie_storage",
+ true,
+ ],
+ ],
+ });
+
+ for (let limitForeignContexts of [false, true]) {
+ SpecialPowers.setBoolPref(
+ "privacy.dynamic_firstparty.limitForeign",
+ limitForeignContexts
+ );
+ for (let page of [TEST_TOP_PAGE, TEST_TOP_PAGE_2, TEST_TOP_PAGE_3]) {
+ await runTests(page, limitForeignContexts);
+ }
+ }
+
+ SpecialPowers.clearUserPref("privacy.dynamic_firstparty.limitForeign");
+ SpecialPowers.clearUserPref("network.cookie.sameSite.laxByDefault");
+});
+
+add_task(async function () {
+ info("Cleaning up.");
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_thirdPartyStorageRejectionForCORS.js b/toolkit/components/antitracking/test/browser/browser_thirdPartyStorageRejectionForCORS.js
new file mode 100644
index 0000000000..b415594662
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_thirdPartyStorageRejectionForCORS.js
@@ -0,0 +1,96 @@
+// This test works by setting up an exception for the tracker domain, which
+// disables all the anti-tracking tests.
+
+add_task(async _ => {
+ PermissionTestUtils.add(
+ "http://example.net",
+ "cookie",
+ Services.perms.ALLOW_ACTION
+ );
+
+ registerCleanupFunction(_ => {
+ Services.perms.removeAll();
+ });
+});
+
+AntiTracking._createTask({
+ name: "Test that we don't store 3P cookies from non-anonymous CORS XHR",
+ cookieBehavior: BEHAVIOR_REJECT_FOREIGN,
+ blockingByContentBlockingRTUI: false,
+ allowList: false,
+ thirdPartyPage: TEST_DOMAIN + TEST_PATH + "3rdParty.html",
+ callback: async _ => {
+ await new Promise(resolve => {
+ const xhr = new XMLHttpRequest();
+ xhr.open(
+ "GET",
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/cookiesCORS.sjs?some;max-age=999999",
+ true
+ );
+ xhr.withCredentials = true;
+ xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+ xhr.onreadystatechange = _ => {
+ if (4 === xhr.readyState && 200 === xhr.status) {
+ resolve();
+ }
+ };
+ xhr.send();
+ });
+ },
+ extraPrefs: [["network.cookie.rejectForeignWithExceptions.enabled", true]],
+ expectedBlockingNotifications:
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_FOREIGN,
+ runInPrivateWindow: false,
+ iframeSandbox: null,
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+});
+
+add_task(async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
+
+AntiTracking._createTask({
+ name: "Test that we don't store 3P cookies from non-anonymous CORS XHR",
+ cookieBehavior: BEHAVIOR_REJECT_FOREIGN,
+ blockingByContentBlockingRTUI: false,
+ allowList: false,
+ thirdPartyPage: TEST_DOMAIN + TEST_PATH + "3rdParty.html",
+ callback: async _ => {
+ await new Promise(resolve => {
+ const xhr = new XMLHttpRequest();
+ xhr.open(
+ "GET",
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/cookiesCORS.sjs?some;max-age=999999",
+ true
+ );
+ xhr.withCredentials = true;
+ xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+ xhr.onreadystatechange = _ => {
+ if (4 === xhr.readyState && 200 === xhr.status) {
+ resolve();
+ }
+ };
+ xhr.send();
+ });
+ },
+ extraPrefs: [["network.cookie.rejectForeignWithExceptions.enabled", false]],
+ expectedBlockingNotifications:
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_FOREIGN,
+ runInPrivateWindow: false,
+ iframeSandbox: null,
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+});
+
+add_task(async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_urlDecorationStripping.js b/toolkit/components/antitracking/test/browser/browser_urlDecorationStripping.js
new file mode 100644
index 0000000000..642b5d2cbd
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_urlDecorationStripping.js
@@ -0,0 +1,253 @@
+// This test ensures that the URL decoration annotations service works as
+// expected, and also we successfully downgrade document.referrer to the
+// eTLD+1 URL when tracking identifiers controlled by this service are
+// present in the referrer URI.
+
+"use strict";
+
+const { RemoteSettings } = ChromeUtils.importESModule(
+ "resource://services-settings/remote-settings.sys.mjs"
+);
+const { Preferences } = ChromeUtils.importESModule(
+ "resource://gre/modules/Preferences.sys.mjs"
+);
+
+const APS_PREF =
+ "privacy.partition.always_partition_third_party_non_cookie_storage";
+
+const COLLECTION_NAME = "anti-tracking-url-decoration";
+const PREF_NAME = "privacy.restrict3rdpartystorage.url_decorations";
+const TOKEN_1 = "fooBar";
+const TOKEN_2 = "foobaz";
+const TOKEN_3 = "fooqux";
+const TOKEN_4 = "bazqux";
+
+const token_1 = TOKEN_1.toLowerCase();
+
+const DOMAIN = TEST_DOMAIN_3;
+const SUB_DOMAIN = "https://sub1.xn--hxajbheg2az3al.xn--jxalpdlp/";
+const TOP_PAGE_WITHOUT_TRACKING_IDENTIFIER =
+ SUB_DOMAIN + TEST_PATH + "page.html";
+const TOP_PAGE_WITH_TRACKING_IDENTIFIER =
+ TOP_PAGE_WITHOUT_TRACKING_IDENTIFIER + "?" + TOKEN_1 + "=123";
+
+add_task(async _ => {
+ let uds = Cc["@mozilla.org/tracking-url-decoration-service;1"].getService(
+ Ci.nsIURLDecorationAnnotationsService
+ );
+
+ let records = [
+ {
+ id: "1",
+ last_modified: 1000000000000001,
+ schema: Date.now(),
+ token: TOKEN_1,
+ },
+ ];
+
+ // Add some initial data
+ async function emitSync() {
+ await RemoteSettings(COLLECTION_NAME).emit("sync", {
+ data: { current: records },
+ });
+ }
+ let db = RemoteSettings(COLLECTION_NAME).db;
+ await db.importChanges({}, Date.now(), [records[0]]);
+ await emitSync();
+
+ await uds.ensureUpdated();
+
+ let list = Preferences.get(PREF_NAME).split(" ");
+ ok(list.includes(TOKEN_1), "Token must now be available in " + PREF_NAME);
+ ok(Preferences.locked(PREF_NAME), PREF_NAME + " must be locked");
+
+ async function verifyList(array, not_array) {
+ await emitSync();
+
+ await uds.ensureUpdated();
+
+ list = Preferences.get(PREF_NAME).split(" ");
+ for (let token of array) {
+ ok(
+ list.includes(token),
+ token + " must now be available in " + PREF_NAME
+ );
+ }
+ for (let token of not_array) {
+ ok(
+ !list.includes(token),
+ token + " must not be available in " + PREF_NAME
+ );
+ }
+ ok(Preferences.locked(PREF_NAME), PREF_NAME + " must be locked");
+ }
+
+ records.push(
+ {
+ id: "2",
+ last_modified: 1000000000000002,
+ schema: Date.now(),
+ token: TOKEN_2,
+ },
+ {
+ id: "3",
+ last_modified: 1000000000000003,
+ schema: Date.now(),
+ token: TOKEN_3,
+ },
+ {
+ id: "4",
+ last_modified: 1000000000000005,
+ schema: Date.now(),
+ token: TOKEN_4,
+ }
+ );
+
+ await verifyList([TOKEN_1, TOKEN_2, TOKEN_3, TOKEN_4], []);
+
+ records.pop();
+
+ await verifyList([TOKEN_1, TOKEN_2, TOKEN_3], [TOKEN_4]);
+
+ is(
+ Services.eTLD.getBaseDomain(Services.io.newURI(DOMAIN)),
+ Services.eTLD.getBaseDomain(Services.io.newURI(SUB_DOMAIN)),
+ "Sanity check"
+ );
+
+ registerCleanupFunction(async _ => {
+ records = [];
+ await db.clear();
+ await emitSync();
+ });
+});
+
+AntiTracking._createTask({
+ name: "Test that we do not downgrade document.referrer when it does not contain a tracking identifier",
+ cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+ blockingByContentBlockingRTUI: true,
+ allowList: false,
+ callback: async _ => {
+ let ref = new URL(document.referrer);
+ is(
+ ref.hostname,
+ "sub1.xn--hxajbheg2az3al.xn--jxalpdlp",
+ "Hostname shouldn't be stripped"
+ );
+ ok(ref.pathname.length > 1, "Path must not be trimmed");
+ // eslint-disable-next-line no-unused-vars
+ for (let entry of ref.searchParams.entries()) {
+ ok(false, "No query parameters should be found");
+ }
+ },
+ extraPrefs: [
+ ["network.http.referer.defaultPolicy", 3], // Ensure we don't downgrade because of the default policy.
+ ["network.http.referer.defaultPolicy.trackers", 3],
+ [APS_PREF, false],
+ ],
+ expectedBlockingNotifications: 0,
+ runInPrivateWindow: false,
+ iframeSandbox: null,
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+ topPage: TOP_PAGE_WITHOUT_TRACKING_IDENTIFIER,
+});
+
+AntiTracking._createTask({
+ name: "Test that we do not downgrade document.referrer when it does not contain a tracking identifier even though it gets downgraded to origin only due to the default referrer policy",
+ cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+ blockingByContentBlockingRTUI: true,
+ allowList: false,
+ callback: async _ => {
+ let ref = new URL(document.referrer);
+ is(
+ ref.hostname,
+ "sub1.xn--hxajbheg2az3al.xn--jxalpdlp",
+ "Hostname shouldn't be stripped"
+ );
+ is(ref.pathname.length, 1, "Path must be trimmed");
+ // eslint-disable-next-line no-unused-vars
+ for (let entry of ref.searchParams.entries()) {
+ ok(false, "No query parameters should be found");
+ }
+ },
+ extraPrefs: [
+ ["network.http.referer.defaultPolicy.trackers", 2],
+ [APS_PREF, false],
+ ],
+ expectedBlockingNotifications: 0,
+ runInPrivateWindow: false,
+ iframeSandbox: null,
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+ topPage: TOP_PAGE_WITHOUT_TRACKING_IDENTIFIER,
+});
+
+AntiTracking._createTask({
+ name: "Test that we downgrade document.referrer when it contains a tracking identifier",
+ cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+ blockingByContentBlockingRTUI: true,
+ allowList: false,
+ callback: async _ => {
+ let ref = new URL(document.referrer);
+ is(
+ ref.hostname,
+ "xn--hxajbheg2az3al.xn--jxalpdlp",
+ "Hostname should be stripped"
+ );
+ is(ref.pathname.length, 1, "Path must be trimmed");
+ // eslint-disable-next-line no-unused-vars
+ for (let entry of ref.searchParams.entries()) {
+ ok(false, "No query parameters should be found");
+ }
+ },
+ extraPrefs: [
+ ["network.http.referer.defaultPolicy", 3], // Ensure we don't downgrade because of the default policy.
+ ["network.http.referer.defaultPolicy.trackers", 3],
+ [APS_PREF, false],
+ ],
+ expectedBlockingNotifications: 0,
+ runInPrivateWindow: false,
+ iframeSandbox: null,
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+ topPage: TOP_PAGE_WITH_TRACKING_IDENTIFIER,
+});
+
+AntiTracking._createTask({
+ name: "Test that we don't downgrade document.referrer when it contains a tracking identifier if it gets downgraded to origin only due to the default referrer policy because the tracking identifier wouldn't be present in the referrer any more",
+ cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+ blockingByContentBlockingRTUI: true,
+ allowList: false,
+ callback: async _ => {
+ let ref = new URL(document.referrer);
+ is(
+ ref.hostname,
+ "sub1.xn--hxajbheg2az3al.xn--jxalpdlp",
+ "Hostname shouldn't be stripped"
+ );
+ is(ref.pathname.length, 1, "Path must be trimmed");
+ // eslint-disable-next-line no-unused-vars
+ for (let entry of ref.searchParams.entries()) {
+ ok(false, "No query parameters should be found");
+ }
+ },
+ extraPrefs: [
+ ["network.http.referer.defaultPolicy.trackers", 2],
+ [APS_PREF, false],
+ ],
+ expectedBlockingNotifications: 0,
+ runInPrivateWindow: false,
+ iframeSandbox: null,
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+ topPage: TOP_PAGE_WITH_TRACKING_IDENTIFIER,
+});
+
+add_task(async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_urlDecorationStripping_alwaysPartition.js b/toolkit/components/antitracking/test/browser/browser_urlDecorationStripping_alwaysPartition.js
new file mode 100644
index 0000000000..2fbac9811b
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_urlDecorationStripping_alwaysPartition.js
@@ -0,0 +1,255 @@
+// This test ensures that the URL decoration annotations service works as
+// expected, and also we successfully downgrade document.referrer to the
+// eTLD+1 URL when tracking identifiers controlled by this service are
+// present in the referrer URI.
+
+"use strict";
+
+const trackerBlocked = Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER;
+
+const { RemoteSettings } = ChromeUtils.importESModule(
+ "resource://services-settings/remote-settings.sys.mjs"
+);
+const { Preferences } = ChromeUtils.importESModule(
+ "resource://gre/modules/Preferences.sys.mjs"
+);
+
+const COLLECTION_NAME = "anti-tracking-url-decoration";
+const PREF_NAME = "privacy.restrict3rdpartystorage.url_decorations";
+const TOKEN_1 = "fooBar";
+const TOKEN_2 = "foobaz";
+const TOKEN_3 = "fooqux";
+const TOKEN_4 = "bazqux";
+
+const APS_PREF =
+ "privacy.partition.always_partition_third_party_non_cookie_storage";
+
+const token_1 = TOKEN_1.toLowerCase();
+
+const DOMAIN = TEST_DOMAIN_3;
+const SUB_DOMAIN = "https://sub1.xn--hxajbheg2az3al.xn--jxalpdlp/";
+const TOP_PAGE_WITHOUT_TRACKING_IDENTIFIER =
+ SUB_DOMAIN + TEST_PATH + "page.html";
+const TOP_PAGE_WITH_TRACKING_IDENTIFIER =
+ TOP_PAGE_WITHOUT_TRACKING_IDENTIFIER + "?" + TOKEN_1 + "=123";
+
+add_task(async _ => {
+ let uds = Cc["@mozilla.org/tracking-url-decoration-service;1"].getService(
+ Ci.nsIURLDecorationAnnotationsService
+ );
+
+ let records = [
+ {
+ id: "1",
+ last_modified: 1000000000000001,
+ schema: Date.now(),
+ token: TOKEN_1,
+ },
+ ];
+
+ // Add some initial data
+ async function emitSync() {
+ await RemoteSettings(COLLECTION_NAME).emit("sync", {
+ data: { current: records },
+ });
+ }
+ let db = RemoteSettings(COLLECTION_NAME).db;
+ await db.importChanges({}, Date.now(), [records[0]]);
+ await emitSync();
+
+ await uds.ensureUpdated();
+
+ let list = Preferences.get(PREF_NAME).split(" ");
+ ok(list.includes(TOKEN_1), "Token must now be available in " + PREF_NAME);
+ ok(Preferences.locked(PREF_NAME), PREF_NAME + " must be locked");
+
+ async function verifyList(array, not_array) {
+ await emitSync();
+
+ await uds.ensureUpdated();
+
+ list = Preferences.get(PREF_NAME).split(" ");
+ for (let token of array) {
+ ok(
+ list.includes(token),
+ token + " must now be available in " + PREF_NAME
+ );
+ }
+ for (let token of not_array) {
+ ok(
+ !list.includes(token),
+ token + " must not be available in " + PREF_NAME
+ );
+ }
+ ok(Preferences.locked(PREF_NAME), PREF_NAME + " must be locked");
+ }
+
+ records.push(
+ {
+ id: "2",
+ last_modified: 1000000000000002,
+ schema: Date.now(),
+ token: TOKEN_2,
+ },
+ {
+ id: "3",
+ last_modified: 1000000000000003,
+ schema: Date.now(),
+ token: TOKEN_3,
+ },
+ {
+ id: "4",
+ last_modified: 1000000000000005,
+ schema: Date.now(),
+ token: TOKEN_4,
+ }
+ );
+
+ await verifyList([TOKEN_1, TOKEN_2, TOKEN_3, TOKEN_4], []);
+
+ records.pop();
+
+ await verifyList([TOKEN_1, TOKEN_2, TOKEN_3], [TOKEN_4]);
+
+ is(
+ Services.eTLD.getBaseDomain(Services.io.newURI(DOMAIN)),
+ Services.eTLD.getBaseDomain(Services.io.newURI(SUB_DOMAIN)),
+ "Sanity check"
+ );
+
+ registerCleanupFunction(async _ => {
+ records = [];
+ await db.clear();
+ await emitSync();
+ });
+});
+
+AntiTracking._createTask({
+ name: "Test that we do not downgrade document.referrer when it does not contain a tracking identifier",
+ cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+ blockingByContentBlockingRTUI: true,
+ allowList: false,
+ callback: async _ => {
+ let ref = new URL(document.referrer);
+ is(
+ ref.hostname,
+ "sub1.xn--hxajbheg2az3al.xn--jxalpdlp",
+ "Hostname shouldn't be stripped"
+ );
+ ok(ref.pathname.length > 1, "Path must not be trimmed");
+ // eslint-disable-next-line no-unused-vars
+ for (let entry of ref.searchParams.entries()) {
+ ok(false, "No query parameters should be found");
+ }
+ },
+ extraPrefs: [
+ ["network.http.referer.defaultPolicy", 3], // Ensure we don't downgrade because of the default policy.
+ ["network.http.referer.defaultPolicy.trackers", 3],
+ [APS_PREF, true],
+ ],
+ expectedBlockingNotifications: trackerBlocked,
+ runInPrivateWindow: false,
+ iframeSandbox: null,
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+ topPage: TOP_PAGE_WITHOUT_TRACKING_IDENTIFIER,
+});
+
+AntiTracking._createTask({
+ name: "Test that we do not downgrade document.referrer when it does not contain a tracking identifier even though it gets downgraded to origin only due to the default referrer policy",
+ cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+ blockingByContentBlockingRTUI: true,
+ allowList: false,
+ callback: async _ => {
+ let ref = new URL(document.referrer);
+ is(
+ ref.hostname,
+ "sub1.xn--hxajbheg2az3al.xn--jxalpdlp",
+ "Hostname shouldn't be stripped"
+ );
+ is(ref.pathname.length, 1, "Path must be trimmed");
+ // eslint-disable-next-line no-unused-vars
+ for (let entry of ref.searchParams.entries()) {
+ ok(false, "No query parameters should be found");
+ }
+ },
+ extraPrefs: [
+ ["network.http.referer.defaultPolicy.trackers", 2],
+ [APS_PREF, true],
+ ],
+ expectedBlockingNotifications: trackerBlocked,
+ runInPrivateWindow: false,
+ iframeSandbox: null,
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+ topPage: TOP_PAGE_WITHOUT_TRACKING_IDENTIFIER,
+});
+
+AntiTracking._createTask({
+ name: "Test that we downgrade document.referrer when it contains a tracking identifier",
+ cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+ blockingByContentBlockingRTUI: true,
+ allowList: false,
+ callback: async _ => {
+ let ref = new URL(document.referrer);
+ is(
+ ref.hostname,
+ "xn--hxajbheg2az3al.xn--jxalpdlp",
+ "Hostname should be stripped"
+ );
+ is(ref.pathname.length, 1, "Path must be trimmed");
+ // eslint-disable-next-line no-unused-vars
+ for (let entry of ref.searchParams.entries()) {
+ ok(false, "No query parameters should be found");
+ }
+ },
+ extraPrefs: [
+ ["network.http.referer.defaultPolicy", 3], // Ensure we don't downgrade because of the default policy.
+ ["network.http.referer.defaultPolicy.trackers", 3],
+ [APS_PREF, true],
+ ],
+ expectedBlockingNotifications: trackerBlocked,
+ runInPrivateWindow: false,
+ iframeSandbox: null,
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+ topPage: TOP_PAGE_WITH_TRACKING_IDENTIFIER,
+});
+
+AntiTracking._createTask({
+ name: "Test that we don't downgrade document.referrer when it contains a tracking identifier if it gets downgraded to origin only due to the default referrer policy because the tracking identifier wouldn't be present in the referrer any more",
+ cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+ blockingByContentBlockingRTUI: true,
+ allowList: false,
+ callback: async _ => {
+ let ref = new URL(document.referrer);
+ is(
+ ref.hostname,
+ "sub1.xn--hxajbheg2az3al.xn--jxalpdlp",
+ "Hostname shouldn't be stripped"
+ );
+ is(ref.pathname.length, 1, "Path must be trimmed");
+ // eslint-disable-next-line no-unused-vars
+ for (let entry of ref.searchParams.entries()) {
+ ok(false, "No query parameters should be found");
+ }
+ },
+ extraPrefs: [
+ ["network.http.referer.defaultPolicy.trackers", 2],
+ [APS_PREF, true],
+ ],
+ expectedBlockingNotifications: trackerBlocked,
+ runInPrivateWindow: false,
+ iframeSandbox: null,
+ accessRemoval: null,
+ callbackAfterRemoval: null,
+ topPage: TOP_PAGE_WITH_TRACKING_IDENTIFIER,
+});
+
+add_task(async _ => {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping.js b/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping.js
new file mode 100644
index 0000000000..6395110f41
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping.js
@@ -0,0 +1,855 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+requestLongerTimeout(6);
+
+const TEST_THIRD_PARTY_DOMAIN = TEST_DOMAIN_2;
+const TEST_THIRD_PARTY_SUB_DOMAIN = "http://sub1.xn--exmple-cua.test/";
+
+const TEST_URI = TEST_DOMAIN + TEST_PATH + "file_stripping.html";
+const TEST_THIRD_PARTY_URI =
+ TEST_THIRD_PARTY_DOMAIN + TEST_PATH + "file_stripping.html";
+const TEST_REDIRECT_URI = TEST_DOMAIN + TEST_PATH + "redirect.sjs";
+
+const TEST_CASES = [
+ { testQueryString: "paramToStrip1=123", strippedQueryString: "" },
+ {
+ testQueryString: "PARAMTOSTRIP1=123&paramToStrip2=456",
+ strippedQueryString: "",
+ },
+ {
+ testQueryString: "paramToStrip1=123&paramToKeep=456",
+ strippedQueryString: "paramToKeep=456",
+ },
+ {
+ testQueryString: "paramToStrip1=123&paramToKeep=456&paramToStrip2=abc",
+ strippedQueryString: "paramToKeep=456",
+ },
+ {
+ testQueryString: "paramToKeep=123",
+ strippedQueryString: "paramToKeep=123",
+ },
+ // Test to make sure we don't encode the unstripped parameters.
+ {
+ testQueryString: "paramToStrip1=123&paramToKeep=?$!%",
+ strippedQueryString: "paramToKeep=?$!%",
+ },
+];
+
+let listService;
+
+function observeChannel(uri, expected) {
+ return TestUtils.topicObserved("http-on-modify-request", (subject, data) => {
+ let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+ let channelURI = channel.URI;
+
+ if (channelURI.spec.startsWith(uri)) {
+ is(
+ channelURI.query,
+ expected,
+ "The loading channel has the expected query string."
+ );
+ return true;
+ }
+
+ return false;
+ });
+}
+
+async function verifyQueryString(browser, expected) {
+ await SpecialPowers.spawn(browser, [expected], expected => {
+ // Strip the first question mark.
+ let search = content.location.search.slice(1);
+
+ is(search, expected, "The query string is correct.");
+ });
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.query_stripping.strip_list", "paramToStrip1 paramToStrip2"],
+ ["privacy.query_stripping.redirect", true],
+ ["privacy.query_stripping.listService.logLevel", "Debug"],
+ ["privacy.query_stripping.strip_on_share.enabled", false],
+ ],
+ });
+
+ // Get the list service so we can wait for it to be fully initialized before running tests.
+ listService = Cc["@mozilla.org/query-stripping-list-service;1"].getService(
+ Ci.nsIURLQueryStrippingListService
+ );
+ // Here we don't care about the actual enabled state, we just want any init to be done so we get reliable starting conditions.
+ await listService.testWaitForInit();
+});
+
+async function waitForListServiceInit(strippingEnabled) {
+ info("Waiting for nsIURLQueryStrippingListService to be initialized.");
+ let isInitialized = await listService.testWaitForInit();
+ is(
+ isInitialized,
+ strippingEnabled,
+ "nsIURLQueryStrippingListService should be initialized when the feature is enabled."
+ );
+}
+
+add_task(async function doTestsForTabOpen() {
+ info("Start testing query stripping for tab open.");
+ for (const strippingEnabled of [false, true]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.query_stripping.enabled", strippingEnabled]],
+ });
+ await waitForListServiceInit(strippingEnabled);
+
+ for (const test of TEST_CASES) {
+ let testURI = TEST_URI + "?" + test.testQueryString;
+
+ let expected = strippingEnabled
+ ? test.strippedQueryString
+ : test.testQueryString;
+
+ // Observe the channel and check if the query string is expected.
+ let networkPromise = observeChannel(TEST_URI, expected);
+
+ // Open a new tab.
+ await BrowserTestUtils.withNewTab(testURI, async browser => {
+ // Verify if the query string is expected in the new tab.
+ await verifyQueryString(browser, expected);
+ });
+
+ await networkPromise;
+ }
+
+ await SpecialPowers.popPrefEnv();
+ }
+});
+
+add_task(async function doTestsForWindowOpen() {
+ info("Start testing query stripping for window.open().");
+ for (const strippingEnabled of [false, true]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.query_stripping.enabled", strippingEnabled]],
+ });
+ await waitForListServiceInit(strippingEnabled);
+
+ for (const test of TEST_CASES) {
+ let testFirstPartyURI = TEST_URI + "?" + test.testQueryString;
+ let testThirdPartyURI = TEST_THIRD_PARTY_URI + "?" + test.testQueryString;
+
+ let originalQueryString = test.testQueryString;
+ let expectedQueryString = strippingEnabled
+ ? test.strippedQueryString
+ : test.testQueryString;
+
+ await BrowserTestUtils.withNewTab(TEST_URI, async browser => {
+ // Observe the channel and check if the query string is intact when open
+ // a same-origin URI.
+ let networkPromise = observeChannel(TEST_URI, originalQueryString);
+
+ // Create the promise to wait for the opened tab.
+ let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, url => {
+ return url.startsWith(TEST_URI);
+ });
+
+ // Call window.open() to open the same-origin URI where the query string
+ // won't be stripped.
+ await SpecialPowers.spawn(browser, [testFirstPartyURI], async url => {
+ content.postMessage({ type: "window-open", url }, "*");
+ });
+
+ await networkPromise;
+ let newTab = await newTabPromise;
+
+ // Verify if the query string is expected in the new opened tab.
+ await verifyQueryString(newTab.linkedBrowser, originalQueryString);
+
+ BrowserTestUtils.removeTab(newTab);
+
+ // Observe the channel and check if the query string is expected for
+ // cross-origin URI.
+ networkPromise = observeChannel(
+ TEST_THIRD_PARTY_URI,
+ expectedQueryString
+ );
+
+ newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, url => {
+ return url.startsWith(TEST_THIRD_PARTY_URI);
+ });
+
+ // Call window.open() to open the cross-site URI where the query string
+ // could be stripped.
+ await SpecialPowers.spawn(browser, [testThirdPartyURI], async url => {
+ content.postMessage({ type: "window-open", url }, "*");
+ });
+
+ await networkPromise;
+ newTab = await newTabPromise;
+
+ // Verify if the query string is expected in the new opened tab.
+ await verifyQueryString(newTab.linkedBrowser, expectedQueryString);
+
+ BrowserTestUtils.removeTab(newTab);
+ });
+ }
+
+ await SpecialPowers.popPrefEnv();
+ }
+});
+
+add_task(async function doTestsForLinkClick() {
+ info("Start testing query stripping for link navigation.");
+ for (const strippingEnabled of [false, true]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.query_stripping.enabled", strippingEnabled]],
+ });
+ await waitForListServiceInit(strippingEnabled);
+
+ for (const test of TEST_CASES) {
+ let testFirstPartyURI = TEST_URI + "?" + test.testQueryString;
+ let testThirdPartyURI = TEST_THIRD_PARTY_URI + "?" + test.testQueryString;
+
+ let originalQueryString = test.testQueryString;
+ let expectedQueryString = strippingEnabled
+ ? test.strippedQueryString
+ : test.testQueryString;
+
+ await BrowserTestUtils.withNewTab(TEST_URI, async browser => {
+ // Observe the channel and check if the query string is intact when
+ // click a same-origin link.
+ let networkPromise = observeChannel(TEST_URI, originalQueryString);
+
+ // Create the promise to wait for the location change.
+ let locationChangePromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ testFirstPartyURI
+ );
+
+ // Create a link and click it to navigate.
+ await SpecialPowers.spawn(browser, [testFirstPartyURI], async uri => {
+ let link = content.document.createElement("a");
+ link.setAttribute("href", uri);
+ link.textContent = "Link";
+ content.document.body.appendChild(link);
+ link.click();
+ });
+
+ await networkPromise;
+ await locationChangePromise;
+
+ // Verify the query string in the content window.
+ await verifyQueryString(browser, originalQueryString);
+
+ // Second, create a link to a cross-origin site to see if the query
+ // string is stripped as expected.
+
+ // Observe the channel and check if the query string is expected when
+ // click a cross-origin link.
+ networkPromise = observeChannel(
+ TEST_THIRD_PARTY_URI,
+ expectedQueryString
+ );
+
+ let targetURI = expectedQueryString
+ ? `${TEST_THIRD_PARTY_URI}?${expectedQueryString}`
+ : TEST_THIRD_PARTY_URI;
+ // Create the promise to wait for the location change.
+ locationChangePromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ targetURI
+ );
+
+ // Create a cross-origin link and click it to navigate.
+ await SpecialPowers.spawn(browser, [testThirdPartyURI], async uri => {
+ let link = content.document.createElement("a");
+ link.setAttribute("href", uri);
+ link.textContent = "Link";
+ content.document.body.appendChild(link);
+ link.click();
+ });
+
+ await networkPromise;
+ await locationChangePromise;
+
+ // Verify the query string in the content window.
+ await verifyQueryString(browser, expectedQueryString);
+ });
+ }
+
+ await SpecialPowers.popPrefEnv();
+ }
+});
+
+add_task(async function doTestsForLinkClickInIframe() {
+ info("Start testing query stripping for link navigation in iframe.");
+ for (const strippingEnabled of [false, true]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.query_stripping.enabled", strippingEnabled]],
+ });
+ await waitForListServiceInit(strippingEnabled);
+
+ for (const test of TEST_CASES) {
+ let testFirstPartyURI = TEST_URI + "?" + test.testQueryString;
+ let testThirdPartyURI = TEST_THIRD_PARTY_URI + "?" + test.testQueryString;
+
+ let originalQueryString = test.testQueryString;
+ let expectedQueryString = strippingEnabled
+ ? test.strippedQueryString
+ : test.testQueryString;
+
+ await BrowserTestUtils.withNewTab(TEST_URI, async browser => {
+ // Create an iframe and wait until it has been loaded.
+ let iframeBC = await SpecialPowers.spawn(
+ browser,
+ [TEST_URI],
+ async url => {
+ let frame = content.document.createElement("iframe");
+ content.document.body.appendChild(frame);
+
+ await new Promise(done => {
+ frame.addEventListener(
+ "load",
+ function () {
+ done();
+ },
+ { capture: true, once: true }
+ );
+
+ frame.setAttribute("src", url);
+ });
+
+ return frame.browsingContext;
+ }
+ );
+
+ // Observe the channel and check if the query string is intact when
+ // click a same-origin link.
+ let networkPromise = observeChannel(TEST_URI, originalQueryString);
+
+ // Create the promise to wait for the new tab.
+ let newTabPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ testFirstPartyURI
+ );
+
+ // Create a same-site link which has '_blank' as target in the iframe
+ // and click it to navigate.
+ await SpecialPowers.spawn(iframeBC, [testFirstPartyURI], async uri => {
+ let link = content.document.createElement("a");
+ link.setAttribute("href", uri);
+ link.setAttribute("target", "_blank");
+ link.textContent = "Link";
+ content.document.body.appendChild(link);
+ link.click();
+ });
+
+ await networkPromise;
+ let newOpenedTab = await newTabPromise;
+
+ // Verify the query string in the content window.
+ await verifyQueryString(
+ newOpenedTab.linkedBrowser,
+ originalQueryString
+ );
+ BrowserTestUtils.removeTab(newOpenedTab);
+
+ // Second, create a link to a cross-origin site in the iframe to see if
+ // the query string is stripped as expected.
+
+ // Observe the channel and check if the query string is expected when
+ // click a cross-origin link.
+ networkPromise = observeChannel(
+ TEST_THIRD_PARTY_URI,
+ expectedQueryString
+ );
+
+ let targetURI = expectedQueryString
+ ? `${TEST_THIRD_PARTY_URI}?${expectedQueryString}`
+ : TEST_THIRD_PARTY_URI;
+ // Create the promise to wait for the new tab.
+ newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, targetURI);
+
+ // Create a cross-origin link which has '_blank' as target in the iframe
+ // and click it to navigate.
+ await SpecialPowers.spawn(iframeBC, [testThirdPartyURI], async uri => {
+ let link = content.document.createElement("a");
+ link.setAttribute("href", uri);
+ link.setAttribute("target", "_blank");
+ link.textContent = "Link";
+ content.document.body.appendChild(link);
+ link.click();
+ });
+
+ await networkPromise;
+ newOpenedTab = await newTabPromise;
+
+ // Verify the query string in the content window.
+ await verifyQueryString(
+ newOpenedTab.linkedBrowser,
+ expectedQueryString
+ );
+ BrowserTestUtils.removeTab(newOpenedTab);
+ });
+ }
+
+ await SpecialPowers.popPrefEnv();
+ }
+});
+
+add_task(async function doTestsForScriptNavigation() {
+ info("Start testing query stripping for script navigation.");
+ for (const strippingEnabled of [false, true]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.query_stripping.enabled", strippingEnabled]],
+ });
+ await waitForListServiceInit(strippingEnabled);
+
+ for (const test of TEST_CASES) {
+ let testFirstPartyURI = TEST_URI + "?" + test.testQueryString;
+ let testThirdPartyURI = TEST_THIRD_PARTY_URI + "?" + test.testQueryString;
+
+ let originalQueryString = test.testQueryString;
+ let expectedQueryString = strippingEnabled
+ ? test.strippedQueryString
+ : test.testQueryString;
+
+ await BrowserTestUtils.withNewTab(TEST_URI, async browser => {
+ // Observe the channel and check if the query string is intact when
+ // navigating to a same-origin URI via script.
+ let networkPromise = observeChannel(TEST_URI, originalQueryString);
+
+ // Create the promise to wait for the location change.
+ let locationChangePromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ testFirstPartyURI
+ );
+
+ // Trigger the navigation by script.
+ await SpecialPowers.spawn(browser, [testFirstPartyURI], async url => {
+ content.postMessage({ type: "script", url }, "*");
+ });
+
+ await networkPromise;
+ await locationChangePromise;
+
+ // Verify the query string in the content window.
+ await verifyQueryString(browser, originalQueryString);
+
+ // Second, trigger a cross-origin navigation through script to see if
+ // the query string is stripped as expected.
+
+ let targetURI = expectedQueryString
+ ? `${TEST_THIRD_PARTY_URI}?${expectedQueryString}`
+ : TEST_THIRD_PARTY_URI;
+
+ // Observe the channel and check if the query string is expected.
+ networkPromise = observeChannel(
+ TEST_THIRD_PARTY_URI,
+ expectedQueryString
+ );
+
+ locationChangePromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ targetURI
+ );
+
+ // Trigger the cross-origin navigation by script.
+ await SpecialPowers.spawn(browser, [testThirdPartyURI], async url => {
+ content.postMessage({ type: "script", url }, "*");
+ });
+
+ await networkPromise;
+ await locationChangePromise;
+
+ // Verify the query string in the content window.
+ await verifyQueryString(browser, expectedQueryString);
+ });
+ }
+
+ await SpecialPowers.popPrefEnv();
+ }
+});
+
+add_task(async function doTestsForNoStrippingForIframeNavigation() {
+ info("Start testing no query stripping for iframe navigation.");
+
+ for (const strippingEnabled of [false, true]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.query_stripping.enabled", strippingEnabled]],
+ });
+ await waitForListServiceInit(strippingEnabled);
+
+ for (const test of TEST_CASES) {
+ let testFirstPartyURI = TEST_URI + "?" + test.testQueryString;
+ let testThirdPartyURI = TEST_THIRD_PARTY_URI + "?" + test.testQueryString;
+
+ // There should be no query stripping for the iframe navigation.
+ let originalQueryString = test.testQueryString;
+ let expectedQueryString = test.testQueryString;
+
+ await BrowserTestUtils.withNewTab(TEST_URI, async browser => {
+ // Create an iframe and wait until it has been loaded.
+ let iframeBC = await SpecialPowers.spawn(
+ browser,
+ [TEST_URI],
+ async url => {
+ let frame = content.document.createElement("iframe");
+ content.document.body.appendChild(frame);
+
+ await new Promise(done => {
+ frame.addEventListener(
+ "load",
+ function () {
+ done();
+ },
+ { capture: true, once: true }
+ );
+
+ frame.setAttribute("src", url);
+ });
+
+ return frame.browsingContext;
+ }
+ );
+
+ // Observe the channel and check if the query string is intact when
+ // navigating an iframe.
+ let networkPromise = observeChannel(TEST_URI, originalQueryString);
+
+ // Create the promise to wait for the location change.
+ let locationChangePromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ testFirstPartyURI
+ );
+
+ // Trigger the iframe navigation by script.
+ await SpecialPowers.spawn(iframeBC, [testFirstPartyURI], async url => {
+ content.postMessage({ type: "script", url }, "*");
+ });
+
+ await networkPromise;
+ await locationChangePromise;
+
+ // Verify the query string in the iframe.
+ await verifyQueryString(iframeBC, originalQueryString);
+
+ // Second, trigger a cross-origin navigation through script to see if
+ // the query string is still the same.
+
+ let targetURI = expectedQueryString
+ ? `${TEST_THIRD_PARTY_URI}?${expectedQueryString}`
+ : TEST_THIRD_PARTY_URI;
+
+ // Observe the channel and check if the query string is not stripped.
+ networkPromise = observeChannel(
+ TEST_THIRD_PARTY_URI,
+ expectedQueryString
+ );
+
+ locationChangePromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ targetURI
+ );
+
+ // Trigger the cross-origin iframe navigation by script.
+ await SpecialPowers.spawn(iframeBC, [testThirdPartyURI], async url => {
+ content.postMessage({ type: "script", url }, "*");
+ });
+
+ await networkPromise;
+ await locationChangePromise;
+
+ // Verify the query string in the content window.
+ await verifyQueryString(iframeBC, expectedQueryString);
+ });
+ }
+
+ await SpecialPowers.popPrefEnv();
+ }
+});
+
+add_task(async function doTestsForRedirect() {
+ info("Start testing query stripping for redirects.");
+
+ for (const strippingEnabled of [false, true]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.query_stripping.enabled", strippingEnabled]],
+ });
+ await waitForListServiceInit(strippingEnabled);
+
+ for (const test of TEST_CASES) {
+ let testFirstPartyURI =
+ TEST_REDIRECT_URI + "?" + TEST_URI + "?" + test.testQueryString;
+ let testThirdPartyURI = `${TEST_REDIRECT_URI}?${TEST_THIRD_PARTY_URI}?${test.testQueryString}`;
+
+ let originalQueryString = test.testQueryString;
+ let expectedQueryString = strippingEnabled
+ ? test.strippedQueryString
+ : test.testQueryString;
+
+ await BrowserTestUtils.withNewTab(TEST_URI, async browser => {
+ // Observe the channel and check if the query string is intact when
+ // redirecting to a same-origin URI .
+ let networkPromise = observeChannel(TEST_URI, originalQueryString);
+
+ let targetURI = `${TEST_URI}?${originalQueryString}`;
+
+ // Create the promise to wait for the location change.
+ let locationChangePromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ targetURI
+ );
+
+ // Trigger the redirect.
+ await SpecialPowers.spawn(browser, [testFirstPartyURI], async url => {
+ content.postMessage({ type: "script", url }, "*");
+ });
+
+ await networkPromise;
+ await locationChangePromise;
+
+ // Verify the query string in the content window.
+ await verifyQueryString(browser, originalQueryString);
+
+ // Second, trigger a redirect to a cross-origin site where the query
+ // string should be stripped.
+
+ targetURI = expectedQueryString
+ ? `${TEST_THIRD_PARTY_URI}?${expectedQueryString}`
+ : TEST_THIRD_PARTY_URI;
+
+ // Observe the channel and check if the query string is expected.
+ networkPromise = observeChannel(
+ TEST_THIRD_PARTY_URI,
+ expectedQueryString
+ );
+
+ locationChangePromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ targetURI
+ );
+
+ // Trigger the cross-origin redirect.
+ await SpecialPowers.spawn(browser, [testThirdPartyURI], async url => {
+ content.postMessage({ type: "script", url }, "*");
+ });
+
+ await networkPromise;
+ await locationChangePromise;
+
+ // Verify the query string in the content window.
+ await verifyQueryString(browser, expectedQueryString);
+ });
+ }
+
+ await SpecialPowers.popPrefEnv();
+ }
+});
+
+add_task(async function doTestForAllowList() {
+ info("Start testing query stripping allow list.");
+
+ // Enable the query stripping and set the allow list.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.query_stripping.enabled", true],
+ ["privacy.query_stripping.allow_list", "xn--exmple-cua.test"],
+ ],
+ });
+ await waitForListServiceInit(true);
+
+ const expected = "paramToStrip1=123";
+
+ // Make sure the allow list works for sites, so we will test both the domain
+ // and the sub domain.
+ for (const domain of [TEST_THIRD_PARTY_DOMAIN, TEST_THIRD_PARTY_SUB_DOMAIN]) {
+ let testURI = `${domain}${TEST_PATH}file_stripping.html`;
+ let testURIWithQueryString = `${testURI}?${expected}`;
+
+ // 1. Test the allow list for tab open.
+ info("Run tab open test.");
+
+ // Observe the channel and check if the query string is not stripped.
+ let networkPromise = observeChannel(testURI, expected);
+
+ await BrowserTestUtils.withNewTab(testURIWithQueryString, async browser => {
+ // Verify if the query string is not stripped in the new tab.
+ await verifyQueryString(browser, expected);
+ });
+
+ await networkPromise;
+
+ // 2. Test the allow list for window open
+ info("Run window open test.");
+ await BrowserTestUtils.withNewTab(TEST_URI, async browser => {
+ // Observe the channel and check if the query string is not stripped.
+ let networkPromise = observeChannel(testURI, expected);
+
+ let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, url => {
+ return url.startsWith(testURI);
+ });
+
+ await SpecialPowers.spawn(
+ browser,
+ [testURIWithQueryString],
+ async url => {
+ content.postMessage({ type: "window-open", url }, "*");
+ }
+ );
+
+ await networkPromise;
+ let newTab = await newTabPromise;
+
+ // Verify if the query string is not stripped in the new opened tab.
+ await verifyQueryString(newTab.linkedBrowser, expected);
+
+ BrowserTestUtils.removeTab(newTab);
+ });
+
+ // 3. Test the allow list for link click
+ info("Run link click test.");
+ await BrowserTestUtils.withNewTab(TEST_URI, async browser => {
+ // Observe the channel and check if the query string is not stripped.
+ let networkPromise = observeChannel(testURI, expected);
+
+ // Create the promise to wait for the location change.
+ let locationChangePromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ testURIWithQueryString
+ );
+
+ await SpecialPowers.spawn(
+ browser,
+ [testURIWithQueryString],
+ async url => {
+ let link = content.document.createElement("a");
+ link.setAttribute("href", url);
+ link.textContent = "Link";
+ content.document.body.appendChild(link);
+ link.click();
+ }
+ );
+
+ await networkPromise;
+ await locationChangePromise;
+
+ // Verify the query string is not stripped in the content window.
+ await verifyQueryString(browser, expected);
+ });
+
+ // 4. Test the allow list for clicking link in an iframe.
+ info("Run link click in iframe test.");
+ await BrowserTestUtils.withNewTab(TEST_URI, async browser => {
+ // Create an iframe and wait until it has been loaded.
+ let iframeBC = await SpecialPowers.spawn(
+ browser,
+ [TEST_URI],
+ async url => {
+ let frame = content.document.createElement("iframe");
+ content.document.body.appendChild(frame);
+
+ await new Promise(done => {
+ frame.addEventListener(
+ "load",
+ function () {
+ done();
+ },
+ { capture: true, once: true }
+ );
+
+ frame.setAttribute("src", url);
+ });
+
+ return frame.browsingContext;
+ }
+ );
+
+ // Observe the channel and check if the query string is not stripped.
+ let networkPromise = observeChannel(testURI, expected);
+
+ // Create the promise to wait for the new tab.
+ let newTabPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ testURIWithQueryString
+ );
+
+ // Create a same-site link which has '_blank' as target in the iframe
+ // and click it to navigate.
+ await SpecialPowers.spawn(
+ iframeBC,
+ [testURIWithQueryString],
+ async uri => {
+ let link = content.document.createElement("a");
+ link.setAttribute("href", uri);
+ link.setAttribute("target", "_blank");
+ link.textContent = "Link";
+ content.document.body.appendChild(link);
+ link.click();
+ }
+ );
+
+ await networkPromise;
+ let newOpenedTab = await newTabPromise;
+
+ // Verify the query string is not stripped in the content window.
+ await verifyQueryString(newOpenedTab.linkedBrowser, expected);
+ BrowserTestUtils.removeTab(newOpenedTab);
+
+ // 5. Test the allow list for script navigation.
+ info("Run script navigation test.");
+ await BrowserTestUtils.withNewTab(TEST_URI, async browser => {
+ // Observe the channel and check if the query string is not stripped.
+ let networkPromise = observeChannel(testURI, expected);
+
+ // Create the promise to wait for the location change.
+ let locationChangePromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ testURIWithQueryString
+ );
+
+ await SpecialPowers.spawn(
+ browser,
+ [testURIWithQueryString],
+ async url => {
+ content.postMessage({ type: "script", url }, "*");
+ }
+ );
+
+ await networkPromise;
+ await locationChangePromise;
+
+ // Verify the query string is not stripped in the content window.
+ await verifyQueryString(browser, expected);
+ });
+
+ // 6. Test the allow list for redirect.
+ info("Run redirect test.");
+ await BrowserTestUtils.withNewTab(TEST_URI, async browser => {
+ // Observe the channel and check if the query string is not stripped.
+ let networkPromise = observeChannel(testURI, expected);
+
+ // Create the promise to wait for the location change.
+ let locationChangePromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ testURIWithQueryString
+ );
+
+ let testRedirectURI = `${TEST_REDIRECT_URI}?${testURI}?${expected}`;
+
+ // Trigger the redirect.
+ await SpecialPowers.spawn(browser, [testRedirectURI], async url => {
+ content.postMessage({ type: "script", url }, "*");
+ });
+
+ await networkPromise;
+ await locationChangePromise;
+
+ // Verify the query string in the content window.
+ await verifyQueryString(browser, expected);
+ });
+ });
+ }
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping_allowList.js b/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping_allowList.js
new file mode 100644
index 0000000000..6dee6cede0
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping_allowList.js
@@ -0,0 +1,442 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const TEST_THIRD_PARTY_DOMAIN = TEST_DOMAIN_2;
+
+const TEST_URI = TEST_DOMAIN + TEST_PATH + "file_stripping.html";
+const TEST_THIRD_PARTY_URI =
+ TEST_THIRD_PARTY_DOMAIN + TEST_PATH + "file_stripping.html";
+const TEST_REDIRECT_URI = TEST_DOMAIN + TEST_PATH + "redirect.sjs";
+
+const TEST_QUERY_STRING = "paramToStrip=1";
+
+function observeChannel(uri, expected) {
+ return TestUtils.topicObserved("http-on-before-connect", (subject, data) => {
+ let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+ let channelURI = channel.URI;
+
+ if (channelURI.spec.startsWith(uri)) {
+ is(
+ channelURI.query,
+ expected,
+ "The loading channel has the expected query string."
+ );
+ return true;
+ }
+
+ return false;
+ });
+}
+
+async function verifyQueryString(browser, expected) {
+ await SpecialPowers.spawn(browser, [expected], expected => {
+ // Strip the first question mark.
+ let search = content.location.search.slice(1);
+
+ is(search, expected, "The query string is correct.");
+ });
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.query_stripping.strip_list", "paramToStrip"],
+ ["privacy.query_stripping.redirect", true],
+ ["privacy.query_stripping.enabled", true],
+ ],
+ });
+
+ let listService = Cc[
+ "@mozilla.org/query-stripping-list-service;1"
+ ].getService(Ci.nsIURLQueryStrippingListService);
+ await listService.testWaitForInit();
+});
+
+add_task(async function doTestsForTabOpen() {
+ let testURI = TEST_URI + "?" + TEST_QUERY_STRING;
+
+ // Observe the channel and check if the query string is stripped.
+ let networkPromise = observeChannel(TEST_URI, "");
+
+ // Open a new tab.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, testURI);
+
+ // Verify if the query string is stripped.
+ await verifyQueryString(tab.linkedBrowser, "");
+ await networkPromise;
+
+ // Toggle ETP off and verify if the query string is restored.
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ testURI
+ );
+ // Observe the channel and check if the query string is not stripped.
+ networkPromise = observeChannel(TEST_URI, TEST_QUERY_STRING);
+
+ gProtectionsHandler.disableForCurrentPage();
+ await browserLoadedPromise;
+ await networkPromise;
+
+ await verifyQueryString(tab.linkedBrowser, TEST_QUERY_STRING);
+
+ BrowserTestUtils.removeTab(tab);
+
+ // Open the tab again and check if the query string is not stripped.
+ networkPromise = observeChannel(TEST_URI, TEST_QUERY_STRING);
+ tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, testURI);
+ await networkPromise;
+
+ // Verify if the query string is not stripped because it's in the content
+ // blocking allow list.
+ await verifyQueryString(tab.linkedBrowser, TEST_QUERY_STRING);
+
+ // Toggle ETP on and verify if the query string is stripped again.
+ networkPromise = observeChannel(TEST_URI, "");
+ browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ TEST_URI
+ );
+ gProtectionsHandler.enableForCurrentPage();
+ await browserLoadedPromise;
+ await networkPromise;
+
+ await verifyQueryString(tab.linkedBrowser, "");
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function doTestsForWindowOpen() {
+ let testURI = TEST_THIRD_PARTY_URI + "?" + TEST_QUERY_STRING;
+
+ await BrowserTestUtils.withNewTab(TEST_URI, async browser => {
+ // Observe the channel and check if the query string is stripped.
+ let networkPromise = observeChannel(TEST_THIRD_PARTY_URI, "");
+
+ // Create the promise to wait for the opened tab.
+ let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, url => {
+ return url.startsWith(TEST_THIRD_PARTY_URI);
+ });
+
+ // Call window.open() to open the third-party URI.
+ await SpecialPowers.spawn(browser, [testURI], async url => {
+ content.postMessage({ type: "window-open", url }, "*");
+ });
+
+ await networkPromise;
+ let newTab = await newTabPromise;
+
+ // Verify if the query string is stripped in the new opened tab.
+ await verifyQueryString(newTab.linkedBrowser, "");
+
+ // Toggle ETP off and verify if the query string is restored.
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ newTab.linkedBrowser,
+ false,
+ testURI
+ );
+ // Observe the channel and check if the query string is not stripped.
+ networkPromise = observeChannel(TEST_THIRD_PARTY_URI, TEST_QUERY_STRING);
+
+ gProtectionsHandler.disableForCurrentPage();
+ await browserLoadedPromise;
+ await networkPromise;
+
+ await verifyQueryString(newTab.linkedBrowser, TEST_QUERY_STRING);
+
+ BrowserTestUtils.removeTab(newTab);
+
+ // Call window.open() again to check if the query string is not stripped if
+ // it's in the content blocking allow list.
+ networkPromise = observeChannel(TEST_THIRD_PARTY_URI, TEST_QUERY_STRING);
+ newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, url => {
+ return url.startsWith(TEST_THIRD_PARTY_URI);
+ });
+
+ await SpecialPowers.spawn(browser, [testURI], async url => {
+ content.postMessage({ type: "window-open", url }, "*");
+ });
+
+ await networkPromise;
+ newTab = await newTabPromise;
+
+ // Verify if the query string is not stripped in the new opened tab.
+ await verifyQueryString(newTab.linkedBrowser, TEST_QUERY_STRING);
+
+ // Toggle ETP on and verify if the query string is stripped again.
+ networkPromise = observeChannel(TEST_THIRD_PARTY_URI, "");
+ browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ newTab.linkedBrowser,
+ false,
+ TEST_THIRD_PARTY_URI
+ );
+ gProtectionsHandler.enableForCurrentPage();
+ await browserLoadedPromise;
+ await networkPromise;
+
+ await verifyQueryString(newTab.linkedBrowser, "");
+ BrowserTestUtils.removeTab(newTab);
+ });
+});
+
+add_task(async function doTestsForLinkClick() {
+ let testURI = TEST_THIRD_PARTY_URI + "?" + TEST_QUERY_STRING;
+
+ await BrowserTestUtils.withNewTab(TEST_URI, async browser => {
+ // Observe the channel and check if the query string is stripped.
+ let networkPromise = observeChannel(TEST_THIRD_PARTY_URI, "");
+
+ // Create the promise to wait for the location change.
+ let locationChangePromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ TEST_THIRD_PARTY_URI
+ );
+
+ // Create a link and click it to navigate.
+ await SpecialPowers.spawn(browser, [testURI], async uri => {
+ let link = content.document.createElement("a");
+ link.setAttribute("href", uri);
+ link.textContent = "Link";
+ content.document.body.appendChild(link);
+ link.click();
+ });
+
+ await networkPromise;
+ await locationChangePromise;
+
+ // Verify the query string in the content window.
+ await verifyQueryString(browser, "");
+
+ // Toggle ETP off and verify if the query string is restored.
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ browser,
+ false,
+ testURI
+ );
+ // Observe the channel and check if the query string is not stripped.
+ networkPromise = observeChannel(TEST_THIRD_PARTY_URI, TEST_QUERY_STRING);
+
+ gProtectionsHandler.disableForCurrentPage();
+ await browserLoadedPromise;
+ await networkPromise;
+
+ // Verify the query string in the content window.
+ await verifyQueryString(browser, TEST_QUERY_STRING);
+ });
+
+ // Repeat the test again to see if the query string is not stripped if it's in
+ // the content blocking allow list.
+ await BrowserTestUtils.withNewTab(TEST_URI, async browser => {
+ // Observe the channel and check if the query string is not stripped.
+ let networkPromise = observeChannel(
+ TEST_THIRD_PARTY_URI,
+ TEST_QUERY_STRING
+ );
+
+ // Create the promise to wait for the location change.
+ let locationChangePromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ testURI
+ );
+
+ // Create a link and click it to navigate.
+ await SpecialPowers.spawn(browser, [testURI], async uri => {
+ let link = content.document.createElement("a");
+ link.setAttribute("href", uri);
+ link.textContent = "Link";
+ content.document.body.appendChild(link);
+ link.click();
+ });
+
+ await networkPromise;
+ await locationChangePromise;
+
+ // Verify the query string in the content window.
+ await verifyQueryString(browser, TEST_QUERY_STRING);
+
+ // Toggle ETP on and verify if the query string is stripped again.
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ browser,
+ false,
+ TEST_THIRD_PARTY_URI
+ );
+ // Observe the channel and check if the query string is not stripped.
+ networkPromise = observeChannel(TEST_THIRD_PARTY_URI, "");
+
+ gProtectionsHandler.enableForCurrentPage();
+ await browserLoadedPromise;
+ await networkPromise;
+
+ // Verify the query string in the content window.
+ await verifyQueryString(browser, "");
+ });
+});
+
+add_task(async function doTestsForScriptNavigation() {
+ let testURI = TEST_THIRD_PARTY_URI + "?" + TEST_QUERY_STRING;
+
+ await BrowserTestUtils.withNewTab(TEST_URI, async browser => {
+ // Observe the channel and check if the query string is stripped.
+ let networkPromise = observeChannel(TEST_THIRD_PARTY_URI, "");
+
+ // Create the promise to wait for the location change.
+ let locationChangePromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ TEST_THIRD_PARTY_URI
+ );
+
+ // Trigger the navigation by script.
+ await SpecialPowers.spawn(browser, [testURI], async url => {
+ content.postMessage({ type: "script", url }, "*");
+ });
+
+ await networkPromise;
+ await locationChangePromise;
+
+ // Verify the query string in the content window.
+ await verifyQueryString(browser, "");
+
+ // Toggle ETP off and verify if the query string is restored.
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ browser,
+ false,
+ testURI
+ );
+ // Observe the channel and check if the query string is not stripped.
+ networkPromise = observeChannel(TEST_THIRD_PARTY_URI, TEST_QUERY_STRING);
+
+ gProtectionsHandler.disableForCurrentPage();
+ await browserLoadedPromise;
+ await networkPromise;
+
+ // Verify the query string in the content window.
+ await verifyQueryString(browser, TEST_QUERY_STRING);
+ });
+
+ // Repeat the test again to see if the query string is not stripped if it's in
+ // the content blocking allow list.
+ await BrowserTestUtils.withNewTab(TEST_URI, async browser => {
+ // Observe the channel and check if the query string is not stripped.
+ let networkPromise = observeChannel(
+ TEST_THIRD_PARTY_URI,
+ TEST_QUERY_STRING
+ );
+
+ // Create the promise to wait for the location change.
+ let locationChangePromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ testURI
+ );
+
+ // Trigger the navigation by script.
+ await SpecialPowers.spawn(browser, [testURI], async url => {
+ content.postMessage({ type: "script", url }, "*");
+ });
+
+ await networkPromise;
+ await locationChangePromise;
+
+ // Verify the query string in the content window.
+ await verifyQueryString(browser, TEST_QUERY_STRING);
+
+ // Toggle ETP on and verify if the query string is stripped again.
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ browser,
+ false,
+ TEST_THIRD_PARTY_URI
+ );
+ // Observe the channel and check if the query string is stripped.
+ networkPromise = observeChannel(TEST_THIRD_PARTY_URI, "");
+
+ gProtectionsHandler.enableForCurrentPage();
+ await browserLoadedPromise;
+ await networkPromise;
+
+ // Verify the query string in the content window.
+ await verifyQueryString(browser, "");
+ });
+});
+
+add_task(async function doTestsForRedirect() {
+ let testURI = `${TEST_REDIRECT_URI}?${TEST_THIRD_PARTY_URI}?${TEST_QUERY_STRING}`;
+ let resultURI = TEST_THIRD_PARTY_URI;
+ let resultURIWithQuery = `${TEST_THIRD_PARTY_URI}?${TEST_QUERY_STRING}`;
+
+ // Open a new tab.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URI);
+
+ // Observe the channel and check if the query string is stripped.
+ let networkPromise = observeChannel(TEST_THIRD_PARTY_URI, "");
+
+ // Create the promise to wait for the location change.
+ let locationChangePromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ resultURI
+ );
+
+ // Trigger the redirect.
+ await SpecialPowers.spawn(tab.linkedBrowser, [testURI], async url => {
+ content.postMessage({ type: "script", url }, "*");
+ });
+
+ await networkPromise;
+ await locationChangePromise;
+
+ // Verify the query string in the content window.
+ await verifyQueryString(tab.linkedBrowser, "");
+
+ // Toggle ETP off and verify if the query string is restored.
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ resultURIWithQuery
+ );
+ // Observe the channel and check if the query string is not stripped.
+ networkPromise = observeChannel(TEST_THIRD_PARTY_URI, TEST_QUERY_STRING);
+
+ gProtectionsHandler.disableForCurrentPage();
+ await browserLoadedPromise;
+ await networkPromise;
+
+ BrowserTestUtils.removeTab(tab);
+
+ // Open the tab again to check if the query string is not stripped.
+ networkPromise = observeChannel(TEST_THIRD_PARTY_URI, TEST_QUERY_STRING);
+ tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URI);
+
+ locationChangePromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ resultURIWithQuery
+ );
+
+ // Trigger the redirect.
+ await SpecialPowers.spawn(tab.linkedBrowser, [testURI], async url => {
+ content.postMessage({ type: "script", url }, "*");
+ });
+
+ await networkPromise;
+ await locationChangePromise;
+
+ // Verify the query string in the content window.
+ await verifyQueryString(tab.linkedBrowser, TEST_QUERY_STRING);
+
+ // Toggle ETP on and verify if the query string is stripped again.
+ networkPromise = observeChannel(TEST_THIRD_PARTY_URI, "");
+ browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ resultURI
+ );
+ gProtectionsHandler.enableForCurrentPage();
+ await browserLoadedPromise;
+ await networkPromise;
+
+ await verifyQueryString(tab.linkedBrowser, "");
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping_nimbus.js b/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping_nimbus.js
new file mode 100644
index 0000000000..e5f256b870
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping_nimbus.js
@@ -0,0 +1,145 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Simplified version of browser_urlQueryStripping.js to test that the Nimbus
+ * integration works correctly in both normal and private browsing.
+ */
+
+const { ExperimentFakes } = ChromeUtils.importESModule(
+ "resource://testing-common/NimbusTestUtils.sys.mjs"
+);
+
+const TEST_URI = TEST_DOMAIN + TEST_PATH + "file_stripping.html";
+const TEST_QUERY_STRING = "paramToStrip1=123&paramToKeep=456";
+const TEST_QUERY_STRING_STRIPPED = "paramToKeep=456";
+const TEST_URI_WITH_QUERY = TEST_URI + "?" + TEST_QUERY_STRING;
+
+let listService;
+
+async function waitForListServiceInit(strippingEnabled) {
+ info("Waiting for nsIURLQueryStrippingListService to be initialized.");
+ let isInitialized = await listService.testWaitForInit();
+ is(
+ isInitialized,
+ strippingEnabled,
+ "nsIURLQueryStrippingListService should be initialized when the feature is enabled."
+ );
+}
+
+/**
+ * Set a list of prefs on the default branch and restore the original values on test end.
+ * @param {*} prefs - Key value pairs in an array.
+ */
+function setDefaultPrefs(prefs) {
+ let originalValues = new Map();
+ let defaultPrefs = Services.prefs.getDefaultBranch("");
+
+ let prefValueToSetter = prefValue => {
+ let type = typeof prefValue;
+ if (type == "string") {
+ return defaultPrefs.setStringPref;
+ }
+ if (type == "boolean") {
+ return defaultPrefs.setBoolPref;
+ }
+ throw new Error("unexpected pref type");
+ };
+
+ prefs.forEach(([key, value]) => {
+ prefValueToSetter(value)(key, value);
+ originalValues.set(key, value);
+ });
+
+ registerCleanupFunction(function () {
+ prefs.forEach(([key, value]) => {
+ prefValueToSetter(value)(key, originalValues.get(key));
+ });
+ });
+}
+
+add_setup(async function () {
+ // Disable the feature via the default pref. This is required so we can set
+ // user values via Nimbus.
+ setDefaultPrefs([
+ ["privacy.query_stripping.enabled", false],
+ ["privacy.query_stripping.enabled.pbmode", false],
+ ["privacy.query_stripping.strip_list", ""],
+ ["privacy.query_stripping.strip_on_share.enabled", false],
+ ]);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.query_stripping.listService.logLevel", "Debug"]],
+ });
+
+ // Get the list service so we can wait for it to be fully initialized before running tests.
+ listService = Cc["@mozilla.org/query-stripping-list-service;1"].getService(
+ Ci.nsIURLQueryStrippingListService
+ );
+ // Here we don't care about the actual enabled state, we just want any init to be done so we get reliable starting conditions.
+ await listService.testWaitForInit();
+});
+
+add_task(async function test() {
+ let [normalWindow, pbWindow] = await Promise.all([
+ BrowserTestUtils.openNewBrowserWindow(),
+ BrowserTestUtils.openNewBrowserWindow({ private: true }),
+ ]);
+
+ for (let enableStripPBM of [false, true]) {
+ for (let enableStrip of [false, true]) {
+ let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({
+ featureId: "queryStripping",
+ value: {
+ enabledNormalBrowsing: enableStrip,
+ enabledPrivateBrowsing: enableStripPBM,
+ stripList: "paramToStrip1 paramToStrip2",
+ },
+ });
+
+ for (let testPBM of [false, true]) {
+ let shouldStrip =
+ (testPBM && enableStripPBM) || (!testPBM && enableStrip);
+ let expectedQueryString = shouldStrip
+ ? TEST_QUERY_STRING_STRIPPED
+ : TEST_QUERY_STRING;
+
+ info(
+ "Test stripping " +
+ JSON.stringify({
+ enableStripPBM,
+ enableStrip,
+ testPBM,
+ expectedQueryString,
+ })
+ );
+
+ await waitForListServiceInit(enableStripPBM || enableStrip);
+
+ let tabBrowser = testPBM ? pbWindow.gBrowser : normalWindow.gBrowser;
+ await BrowserTestUtils.withNewTab(
+ { gBrowser: tabBrowser, url: TEST_URI_WITH_QUERY },
+ async browser => {
+ is(
+ browser.currentURI.query,
+ expectedQueryString,
+ "Correct query string"
+ );
+ }
+ );
+ }
+
+ await doExperimentCleanup();
+ }
+ }
+
+ // Cleanup
+ await Promise.all([
+ BrowserTestUtils.closeWindow(normalWindow),
+ BrowserTestUtils.closeWindow(pbWindow),
+ ]);
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping_pbmode.js b/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping_pbmode.js
new file mode 100644
index 0000000000..fd37f94765
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping_pbmode.js
@@ -0,0 +1,105 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Simplified version of browser_urlQueryStripping.js to test that the feature
+ * prefs work correctly in both normal and private browsing.
+ */
+
+const TEST_URI = TEST_DOMAIN + TEST_PATH + "file_stripping.html";
+const TEST_QUERY_STRING = "paramToStrip1=123&paramToKeep=456";
+const TEST_QUERY_STRING_STRIPPED = "paramToKeep=456";
+const TEST_URI_WITH_QUERY = TEST_URI + "?" + TEST_QUERY_STRING;
+
+let listService;
+
+async function waitForListServiceInit(strippingEnabled) {
+ info("Waiting for nsIURLQueryStrippingListService to be initialized.");
+ let isInitialized = await listService.testWaitForInit();
+ is(
+ isInitialized,
+ strippingEnabled,
+ "nsIURLQueryStrippingListService should be initialized when the feature is enabled."
+ );
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.query_stripping.strip_list", "paramToStrip1 paramToStrip2"],
+ ["privacy.query_stripping.listService.logLevel", "Debug"],
+ ["privacy.query_stripping.strip_on_share.enabled", false],
+ ],
+ });
+
+ // Get the list service so we can wait for it to be fully initialized before running tests.
+ listService = Cc["@mozilla.org/query-stripping-list-service;1"].getService(
+ Ci.nsIURLQueryStrippingListService
+ );
+ // Here we don't care about the actual enabled state, we just want any init to be done so we get reliable starting conditions.
+ await listService.testWaitForInit();
+});
+
+add_task(async function test() {
+ let [normalWindow, pbWindow] = await Promise.all([
+ BrowserTestUtils.openNewBrowserWindow(),
+ BrowserTestUtils.openNewBrowserWindow({ private: true }),
+ ]);
+
+ for (let enableStripPBM of [false, true]) {
+ Services.prefs.setBoolPref(
+ "privacy.query_stripping.enabled.pbmode",
+ enableStripPBM
+ );
+ for (let enableStrip of [false, true]) {
+ Services.prefs.setBoolPref(
+ "privacy.query_stripping.enabled",
+ enableStrip
+ );
+ for (let testPBM of [false, true]) {
+ let shouldStrip =
+ (testPBM && enableStripPBM) || (!testPBM && enableStrip);
+ let expectedQueryString = shouldStrip
+ ? TEST_QUERY_STRING_STRIPPED
+ : TEST_QUERY_STRING;
+
+ info(
+ "Test stripping " +
+ JSON.stringify({
+ enableStripPBM,
+ enableStrip,
+ testPBM,
+ expectedQueryString,
+ })
+ );
+
+ await waitForListServiceInit(enableStripPBM || enableStrip);
+
+ let tabBrowser = testPBM ? pbWindow.gBrowser : normalWindow.gBrowser;
+ await BrowserTestUtils.withNewTab(
+ { gBrowser: tabBrowser, url: TEST_URI_WITH_QUERY },
+ async browser => {
+ is(
+ browser.currentURI.query,
+ expectedQueryString,
+ "Correct query string"
+ );
+ }
+ );
+ }
+ }
+ }
+
+ // Cleanup
+ await Promise.all([
+ BrowserTestUtils.closeWindow(normalWindow),
+ BrowserTestUtils.closeWindow(pbWindow),
+ ]);
+
+ Services.prefs.clearUserPref("privacy.query_stripping.enabled");
+ Services.prefs.clearUserPref("privacy.query_stripping.enabled.pbmode");
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping_telemetry.js b/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping_telemetry.js
new file mode 100644
index 0000000000..20c830113c
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping_telemetry.js
@@ -0,0 +1,355 @@
+/**
+ * Bug 1706616 - Testing the URL query string stripping telemetry.
+ */
+
+"use strict";
+
+const TEST_URI = TEST_DOMAIN + TEST_PATH + "file_stripping.html";
+const TEST_THIRD_PARTY_URI = TEST_DOMAIN_2 + TEST_PATH + "file_stripping.html";
+const TEST_REDIRECT_URI = TEST_DOMAIN + TEST_PATH + "redirect.sjs";
+
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+const LABEL_NAVIGATION = 0;
+const LABEL_REDIRECT = 1;
+const LABEL_STRIP_FOR_NAVIGATION = 2;
+const LABEL_STRIP_FOR_REDIRECT = 3;
+
+const QUERY_STRIPPING_COUNT = "QUERY_STRIPPING_COUNT";
+const QUERY_STRIPPING_PARAM_COUNT = "QUERY_STRIPPING_PARAM_COUNT";
+
+async function clearTelemetry() {
+ // There's an arbitrary interval of 2 seconds in which the content
+ // processes sync their data with the parent process, we wait
+ // this out to ensure that we clear everything.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 2000));
+
+ Services.telemetry.getSnapshotForHistograms("main", true /* clear */);
+ Services.telemetry.getHistogramById(QUERY_STRIPPING_COUNT).clear();
+ Services.telemetry.getHistogramById(QUERY_STRIPPING_PARAM_COUNT).clear();
+
+ let isCleared = () => {
+ let histograms = Services.telemetry.getSnapshotForHistograms(
+ "main",
+ false /* clear */
+ ).content;
+
+ return (
+ !histograms ||
+ (!histograms[QUERY_STRIPPING_COUNT] &&
+ !histograms[QUERY_STRIPPING_PARAM_COUNT])
+ );
+ };
+
+ // Check that the telemetry probes have been cleared properly. Do this check
+ // sync first to avoid any race conditions where telemetry arrives after
+ // clearing.
+ if (!isCleared()) {
+ await TestUtils.waitForCondition(
+ isCleared,
+ "waiting for query stripping probes to be cleared"
+ );
+ }
+
+ ok(true, "Telemetry has been cleared.");
+}
+
+async function verifyQueryString(browser, expected) {
+ await SpecialPowers.spawn(browser, [expected], expected => {
+ // Strip the first question mark.
+ let search = content.location.search.slice(1);
+
+ is(search, expected, "The query string is correct.");
+ });
+}
+
+async function getTelemetryProbe(key, label, checkCntFn) {
+ let histogram;
+
+ // Wait until the telemetry probe appears.
+ await TestUtils.waitForCondition(() => {
+ let histograms = Services.telemetry.getSnapshotForHistograms(
+ "main",
+ false /* clear */
+ ).parent;
+ histogram = histograms[key];
+
+ let checkRes = false;
+
+ if (histogram) {
+ checkRes = checkCntFn ? checkCntFn(histogram.values[label]) : true;
+ }
+
+ return checkRes;
+ }, `waiting for telemetry probe (key=${key}, label=${label}) to appear`);
+
+ return histogram.values[label];
+}
+
+async function checkTelemetryProbe(key, expectedCnt, label) {
+ let cnt = await getTelemetryProbe(key, label, cnt => cnt == expectedCnt);
+
+ is(cnt, expectedCnt, "There should be expected count in telemetry.");
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.query_stripping.enabled", true],
+ [
+ "privacy.query_stripping.strip_list",
+ "paramToStrip paramToStripB paramToStripC paramToStripD",
+ ],
+ ],
+ });
+
+ // Clear Telemetry probes before testing.
+ await clearTelemetry();
+});
+
+add_task(async function testQueryStrippingNavigationInParent() {
+ let testURI = TEST_URI + "?paramToStrip=value";
+
+ // Open a new tab and trigger the query stripping.
+ await BrowserTestUtils.withNewTab(testURI, async browser => {
+ // Verify if the query string was happened.
+ await verifyQueryString(browser, "");
+ });
+
+ // Verify the telemetry probe.
+ await checkTelemetryProbe(
+ QUERY_STRIPPING_COUNT,
+ 1,
+ LABEL_STRIP_FOR_NAVIGATION
+ );
+ await checkTelemetryProbe(QUERY_STRIPPING_PARAM_COUNT, 1, "1");
+
+ // Because there would be some loading happening during the test and they
+ // could interfere the count here. So, we only verify if the counter is
+ // increased, but not the exact count.
+ let newNavigationCnt = await getTelemetryProbe(
+ QUERY_STRIPPING_COUNT,
+ LABEL_NAVIGATION,
+ cnt => cnt > 0
+ );
+ ok(newNavigationCnt > 0, "There is navigation count added.");
+
+ await clearTelemetry();
+});
+
+add_task(async function testQueryStrippingNavigationInContent() {
+ let testThirdPartyURI = TEST_THIRD_PARTY_URI + "?paramToStrip=value";
+
+ await BrowserTestUtils.withNewTab(TEST_URI, async browser => {
+ // Create the promise to wait for the location change.
+ let locationChangePromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ TEST_THIRD_PARTY_URI
+ );
+
+ // Trigger the navigation by script.
+ await SpecialPowers.spawn(browser, [testThirdPartyURI], async url => {
+ content.postMessage({ type: "script", url }, "*");
+ });
+
+ await locationChangePromise;
+
+ // Verify if the query string was happened.
+ await verifyQueryString(browser, "");
+ });
+
+ // Verify the telemetry probe.
+ await checkTelemetryProbe(
+ QUERY_STRIPPING_COUNT,
+ 1,
+ LABEL_STRIP_FOR_NAVIGATION
+ );
+ await checkTelemetryProbe(QUERY_STRIPPING_PARAM_COUNT, 1, "1");
+
+ // Check if the navigation count is increased.
+ let newNavigationCnt = await getTelemetryProbe(
+ QUERY_STRIPPING_COUNT,
+ LABEL_NAVIGATION,
+ cnt => cnt > 0
+ );
+ ok(newNavigationCnt > 0, "There is navigation count added.");
+
+ await clearTelemetry();
+});
+
+add_task(async function testQueryStrippingNavigationInContentQueryCount() {
+ let testThirdPartyURI =
+ TEST_THIRD_PARTY_URI +
+ "?paramToStrip=value&paramToStripB=valueB&paramToStripC=valueC&paramToStripD=valueD";
+
+ await BrowserTestUtils.withNewTab(TEST_URI, async browser => {
+ // Create the promise to wait for the location change.
+ let locationChangePromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ TEST_THIRD_PARTY_URI
+ );
+
+ // Trigger the navigation by script.
+ await SpecialPowers.spawn(browser, [testThirdPartyURI], async url => {
+ content.postMessage({ type: "script", url }, "*");
+ });
+
+ await locationChangePromise;
+
+ // Verify if the query string was happened.
+ await verifyQueryString(browser, "");
+ });
+
+ // Verify the telemetry probe.
+ await checkTelemetryProbe(
+ QUERY_STRIPPING_COUNT,
+ 1,
+ LABEL_STRIP_FOR_NAVIGATION
+ );
+
+ await getTelemetryProbe(QUERY_STRIPPING_PARAM_COUNT, "0", cnt => !cnt);
+ await getTelemetryProbe(QUERY_STRIPPING_PARAM_COUNT, "1", cnt => !cnt);
+ await getTelemetryProbe(QUERY_STRIPPING_PARAM_COUNT, "2", cnt => !cnt);
+ await getTelemetryProbe(QUERY_STRIPPING_PARAM_COUNT, "3", cnt => !cnt);
+ await getTelemetryProbe(QUERY_STRIPPING_PARAM_COUNT, "4", cnt => cnt == 1);
+ await getTelemetryProbe(QUERY_STRIPPING_PARAM_COUNT, "5", cnt => !cnt);
+
+ // Check if the navigation count is increased.
+ let newNavigationCnt = await getTelemetryProbe(
+ QUERY_STRIPPING_COUNT,
+ LABEL_NAVIGATION,
+ cnt => cnt > 0
+ );
+ ok(newNavigationCnt > 0, "There is navigation count added.");
+
+ await clearTelemetry();
+});
+
+add_task(async function testQueryStrippingRedirect() {
+ let testThirdPartyURI = `${TEST_REDIRECT_URI}?${TEST_THIRD_PARTY_URI}?paramToStrip=value`;
+
+ await BrowserTestUtils.withNewTab(TEST_URI, async browser => {
+ // Create the promise to wait for the location change.
+ let locationChangePromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ TEST_THIRD_PARTY_URI
+ );
+
+ // Trigger the redirect.
+ await SpecialPowers.spawn(browser, [testThirdPartyURI], async url => {
+ content.postMessage({ type: "script", url }, "*");
+ });
+
+ await locationChangePromise;
+
+ // Verify if the query string was happened.
+ await verifyQueryString(browser, "");
+ });
+
+ // Verify the telemetry probe in parent process. Note that there is no
+ // non-test loading is using redirect. So, we can check the exact count here.
+ await checkTelemetryProbe(QUERY_STRIPPING_COUNT, 1, LABEL_STRIP_FOR_REDIRECT);
+ await checkTelemetryProbe(QUERY_STRIPPING_COUNT, 1, LABEL_REDIRECT);
+ await checkTelemetryProbe(QUERY_STRIPPING_PARAM_COUNT, 1, "1");
+
+ await clearTelemetry();
+});
+
+add_task(async function testQueryStrippingDisabled() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.query_stripping.enabled", false]],
+ });
+
+ // First, test the navigation in parent process.
+ let testURI = TEST_URI + "?paramToStrip=value";
+
+ // Open a new tab and trigger the query stripping.
+ await BrowserTestUtils.withNewTab(testURI, async browser => {
+ // Verify if the query string was not happened.
+ await verifyQueryString(browser, "paramToStrip=value");
+ });
+
+ // Verify the telemetry probe. There should be no stripped navigation count.
+ await checkTelemetryProbe(
+ QUERY_STRIPPING_COUNT,
+ undefined,
+ LABEL_STRIP_FOR_NAVIGATION
+ );
+ // Check if the navigation count is increased.
+ let newNavigationCnt = await getTelemetryProbe(
+ QUERY_STRIPPING_COUNT,
+ LABEL_NAVIGATION,
+ cnt => cnt > 0
+ );
+ ok(newNavigationCnt > 0, "There is navigation count added.");
+
+ // Second, test the navigation in content.
+ let testThirdPartyURI = TEST_THIRD_PARTY_URI + "?paramToStrip=value";
+
+ await BrowserTestUtils.withNewTab(TEST_URI, async browser => {
+ // Create the promise to wait for the location change.
+ let locationChangePromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ testThirdPartyURI
+ );
+
+ // Trigger the navigation by script.
+ await SpecialPowers.spawn(browser, [testThirdPartyURI], async url => {
+ content.postMessage({ type: "script", url }, "*");
+ });
+
+ await locationChangePromise;
+
+ // Verify if the query string was happened.
+ await verifyQueryString(browser, "paramToStrip=value");
+ });
+
+ // Verify the telemetry probe in content process. There should be no stripped
+ // navigation count.
+ await checkTelemetryProbe(
+ QUERY_STRIPPING_COUNT,
+ undefined,
+ LABEL_STRIP_FOR_NAVIGATION
+ );
+ // Check if the navigation count is increased.
+ newNavigationCnt = await getTelemetryProbe(
+ QUERY_STRIPPING_COUNT,
+ LABEL_NAVIGATION,
+ cnt => cnt > 0
+ );
+ ok(newNavigationCnt > 0, "There is navigation count added.");
+
+ // Third, test the redirect.
+ testThirdPartyURI = `${TEST_REDIRECT_URI}?${TEST_THIRD_PARTY_URI}?paramToStrip=value`;
+
+ await BrowserTestUtils.withNewTab(TEST_URI, async browser => {
+ // Create the promise to wait for the location change.
+ let locationChangePromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ `${TEST_THIRD_PARTY_URI}?paramToStrip=value`
+ );
+
+ // Trigger the redirect.
+ await SpecialPowers.spawn(browser, [testThirdPartyURI], async url => {
+ content.postMessage({ type: "script", url }, "*");
+ });
+
+ await locationChangePromise;
+
+ // Verify if the query string was happened.
+ await verifyQueryString(browser, "paramToStrip=value");
+ });
+
+ // Verify the telemetry probe. The stripped redirect count should not exist.
+ await checkTelemetryProbe(
+ QUERY_STRIPPING_COUNT,
+ undefined,
+ LABEL_STRIP_FOR_REDIRECT
+ );
+ await checkTelemetryProbe(QUERY_STRIPPING_COUNT, 1, LABEL_REDIRECT);
+
+ await clearTelemetry();
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping_telemetry_2.js b/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping_telemetry_2.js
new file mode 100644
index 0000000000..6874e4bc62
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping_telemetry_2.js
@@ -0,0 +1,159 @@
+"use strict";
+
+const TEST_URI = TEST_DOMAIN + TEST_PATH + "file_stripping.html";
+
+const QUERY_STRIPPING_COUNT = "QUERY_STRIPPING_COUNT";
+const QUERY_STRIPPING_PARAM_COUNT = "QUERY_STRIPPING_PARAM_COUNT";
+const QUERY_STRIPPING_COUNT_BY_PARAM = "QUERY_STRIPPING_COUNT_BY_PARAM";
+
+const histogramLabels =
+ Services.telemetry.getCategoricalLabels().QUERY_STRIPPING_COUNT_BY_PARAM;
+
+async function clearTelemetry() {
+ // There's an arbitrary interval of 2 seconds in which the content
+ // processes sync their data with the parent process, we wait
+ // this out to ensure that we clear everything.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 2000));
+
+ Services.telemetry.getSnapshotForHistograms("main", true /* clear */);
+ Services.telemetry.getHistogramById(QUERY_STRIPPING_COUNT).clear();
+ Services.telemetry.getHistogramById(QUERY_STRIPPING_PARAM_COUNT).clear();
+ Services.telemetry.getHistogramById(QUERY_STRIPPING_COUNT_BY_PARAM).clear();
+
+ let isCleared = () => {
+ let histograms = Services.telemetry.getSnapshotForHistograms(
+ "main",
+ false /* clear */
+ ).content;
+
+ return (
+ !histograms ||
+ (!histograms[QUERY_STRIPPING_COUNT] &&
+ !histograms[QUERY_STRIPPING_PARAM_COUNT] &&
+ !histograms.QUERY_STRIPPING_COUNT_BY_PARAM)
+ );
+ };
+
+ // Check that the telemetry probes have been cleared properly. Do this check
+ // sync first to avoid any race conditions where telemetry arrives after
+ // clearing.
+ if (!isCleared()) {
+ await TestUtils.waitForCondition(isCleared);
+ }
+
+ ok(true, "Telemetry has been cleared.");
+}
+
+async function verifyQueryString(browser, expected) {
+ await SpecialPowers.spawn(browser, [expected], expected => {
+ // Strip the first question mark.
+ let search = content.location.search.slice(1);
+
+ is(search, expected, "The query string is correct.");
+ });
+}
+
+function testTelemetry(queryParamToCount) {
+ const histogram = Services.telemetry.getHistogramById(
+ QUERY_STRIPPING_COUNT_BY_PARAM
+ );
+
+ let snapshot = histogram.snapshot();
+
+ let indexToCount = {};
+ Object.entries(queryParamToCount).forEach(([key, value]) => {
+ let index = histogramLabels.indexOf(`param_${key}`);
+
+ // In debug builds we perform additional stripping for testing, which
+ // results in telemetry being recorded twice. This does not impact
+ // production builds.
+ if (SpecialPowers.isDebugBuild) {
+ indexToCount[index] = value * 2;
+ } else {
+ indexToCount[index] = value;
+ }
+ });
+
+ for (let [i, val] of Object.entries(snapshot.values)) {
+ let expectedCount = indexToCount[i] || 0;
+
+ is(
+ val,
+ expectedCount,
+ `Histogram ${QUERY_STRIPPING_COUNT_BY_PARAM} should have expected value for label ${histogramLabels[i]}.`
+ );
+ }
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.query_stripping.enabled", true],
+ [
+ "privacy.query_stripping.strip_list",
+ "foo mc_eid oly_anon_id oly_enc_id __s vero_id _hsenc mkt_tok fbclid",
+ ],
+ ],
+ });
+
+ // Clear Telemetry probes before testing.
+ await clearTelemetry();
+});
+
+/**
+ * Tests the QUERY_STRIPPING_COUNT_BY_PARAM histogram telemetry which counts how
+ * often query params from a predefined lists are stripped.
+ */
+add_task(async function test_queryParamCountTelemetry() {
+ info("Test with a query params to be stripped and recoded in telemetry.");
+ let url = new URL(TEST_URI);
+ url.searchParams.set("mc_eid", "myValue");
+
+ // Open a new tab and trigger the query stripping.
+ await BrowserTestUtils.withNewTab(url.href, async browser => {
+ // Verify that the tracking query param has been stripped.
+ await verifyQueryString(browser, "");
+ });
+
+ testTelemetry({ mc_eid: 1 });
+
+ // Repeat this with the same query parameter, the respective histogram bucket
+ // should be incremented.
+ await BrowserTestUtils.withNewTab(url.href, async browser => {
+ await verifyQueryString(browser, "");
+ });
+
+ testTelemetry({ mc_eid: 2 });
+
+ url = new URL(TEST_URI);
+ url.searchParams.set("fbclid", "myValue2");
+ url.searchParams.set("mkt_tok", "myValue3");
+ url.searchParams.set("bar", "foo");
+
+ info("Test with multiple query params to be stripped.");
+ await BrowserTestUtils.withNewTab(url.href, async browser => {
+ await verifyQueryString(browser, "bar=foo");
+ });
+ testTelemetry({ mc_eid: 2, fbclid: 1, mkt_tok: 1 });
+
+ info(
+ "Test with query param on the strip-list, which should not be recoded in telemetry."
+ );
+ url = new URL(TEST_URI);
+ url.searchParams.set("foo", "bar");
+ url.searchParams.set("__s", "myValue4");
+ await BrowserTestUtils.withNewTab(url.href, async browser => {
+ await verifyQueryString(browser, "");
+ });
+ testTelemetry({ mc_eid: 2, fbclid: 1, mkt_tok: 1, __s: 1 });
+
+ url = new URL(TEST_URI);
+ url.searchParams.set("foo", "bar");
+ await BrowserTestUtils.withNewTab(url.href, async browser => {
+ await verifyQueryString(browser, "");
+ });
+ testTelemetry({ mc_eid: 2, fbclid: 1, mkt_tok: 1, __s: 1 });
+
+ await clearTelemetry();
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_urlQueryStrippingListService.js b/toolkit/components/antitracking/test/browser/browser_urlQueryStrippingListService.js
new file mode 100644
index 0000000000..1d46986ff1
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_urlQueryStrippingListService.js
@@ -0,0 +1,249 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { RemoteSettings } = ChromeUtils.importESModule(
+ "resource://services-settings/remote-settings.sys.mjs"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "urlQueryStrippingListService",
+ "@mozilla.org/query-stripping-list-service;1",
+ "nsIURLQueryStrippingListService"
+);
+
+const COLLECTION_NAME = "query-stripping";
+
+const TEST_URI = TEST_DOMAIN + TEST_PATH + "empty.html";
+const TEST_THIRD_PARTY_URI = TEST_DOMAIN_2 + TEST_PATH + "empty.html";
+
+// The Update Event here is used to listen the observer from the
+// URLQueryStrippingListService. We need to use the event here so that the same
+// observer can be called multiple times.
+class UpdateEvent extends EventTarget {}
+function waitForEvent(element, eventName) {
+ return BrowserTestUtils.waitForEvent(element, eventName).then(e => e.detail);
+}
+
+async function verifyQueryString(browser, expected) {
+ await SpecialPowers.spawn(browser, [expected], expected => {
+ // Strip the first question mark.
+ let search = content.location.search.slice(1);
+
+ is(search, expected, "The query string is correct.");
+ });
+}
+
+async function check(query, expected) {
+ // Open a tab with the query string.
+ let testURI = TEST_URI + "?" + query;
+
+ // Test for stripping in parent process.
+ await BrowserTestUtils.withNewTab(testURI, async browser => {
+ // Verify if the query string is expected in the new tab.
+ await verifyQueryString(browser, expected);
+ });
+
+ testURI = TEST_URI + "?" + query;
+ let expectedURI;
+ if (expected != "") {
+ expectedURI = TEST_URI + "?" + expected;
+ } else {
+ expectedURI = TEST_URI;
+ }
+
+ // Test for stripping in content processes. This will first open a third-party
+ // page and create a link to the test uri. And then, click the link to
+ // navigate the page, which will trigger the stripping in content processes.
+ await BrowserTestUtils.withNewTab(TEST_THIRD_PARTY_URI, async browser => {
+ // Create the promise to wait for the location change.
+ let locationChangePromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ expectedURI
+ );
+
+ // Create a link and click it to navigate.
+ await SpecialPowers.spawn(browser, [testURI], async uri => {
+ let link = content.document.createElement("a");
+ link.setAttribute("href", uri);
+ link.textContent = "Link";
+ content.document.body.appendChild(link);
+ link.click();
+ });
+
+ await locationChangePromise;
+
+ // Verify the query string in the content window.
+ await verifyQueryString(browser, expected);
+ });
+}
+
+registerCleanupFunction(() => {
+ Cc["@mozilla.org/query-stripping-list-service;1"]
+ .getService(Ci.nsIURLQueryStrippingListService)
+ .clearLists();
+});
+
+add_task(async function testPrefSettings() {
+ // Enable query stripping and clear the prefs at the beginning.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.query_stripping.enabled", true],
+ ["privacy.query_stripping.strip_list", ""],
+ ["privacy.query_stripping.allow_list", ""],
+ ["privacy.query_stripping.testing", true],
+ ],
+ });
+
+ // Test if the observer been called when adding to the service.
+ let updateEvent = new UpdateEvent();
+ let obs = (stripList, allowList) => {
+ let event = new CustomEvent("update", { detail: { stripList, allowList } });
+ updateEvent.dispatchEvent(event);
+ };
+ let promise = waitForEvent(updateEvent, "update");
+ urlQueryStrippingListService.registerAndRunObserver(obs);
+ let lists = await promise;
+ is(lists.stripList, "", "No strip list at the beginning.");
+ is(lists.allowList, "", "No allow list at the beginning.");
+
+ // Verify that no query stripping happens.
+ await check("pref_query1=123", "pref_query1=123");
+ await check("pref_query2=456", "pref_query2=456");
+
+ // Set pref for strip list
+ promise = waitForEvent(updateEvent, "update");
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.query_stripping.strip_list", "pref_query1 pref_query2"]],
+ });
+ lists = await promise;
+
+ is(
+ lists.stripList,
+ "pref_query1 pref_query2",
+ "There should be strip list entries."
+ );
+ is(lists.allowList, "", "There should be no allow list entries.");
+
+ // The query string should be stripped.
+ await check("pref_query1=123", "");
+ await check("pref_query2=456", "");
+
+ // Set the pref for allow list.
+ promise = waitForEvent(updateEvent, "update");
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.query_stripping.allow_list", "example.net"]],
+ });
+ lists = await promise;
+
+ is(
+ lists.stripList,
+ "pref_query1 pref_query2",
+ "There should be strip list entires."
+ );
+ is(lists.allowList, "example.net", "There should be one allow list entry.");
+
+ // The query string shouldn't be stripped because this host is in allow list.
+ await check("pref_query1=123", "pref_query1=123");
+ await check("pref_query2=123", "pref_query2=123");
+
+ urlQueryStrippingListService.unregisterObserver(obs);
+
+ // Clear prefs.
+ SpecialPowers.flushPrefEnv();
+});
+
+add_task(async function testRemoteSettings() {
+ // Enable query stripping and clear the prefs at the beginning.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.query_stripping.enabled", true],
+ ["privacy.query_stripping.strip_list", ""],
+ ["privacy.query_stripping.allow_list", ""],
+ ["privacy.query_stripping.testing", true],
+ ],
+ });
+
+ // Add initial empty record.
+ let db = RemoteSettings(COLLECTION_NAME).db;
+ await db.importChanges({}, Date.now(), []);
+
+ // Test if the observer been called when adding to the service.
+ let updateEvent = new UpdateEvent();
+ let obs = (stripList, allowList) => {
+ let event = new CustomEvent("update", { detail: { stripList, allowList } });
+ updateEvent.dispatchEvent(event);
+ };
+ let promise = waitForEvent(updateEvent, "update");
+ urlQueryStrippingListService.registerAndRunObserver(obs);
+ let lists = await promise;
+ is(lists.stripList, "", "No strip list at the beginning.");
+ is(lists.allowList, "", "No allow list at the beginning.");
+
+ // Verify that no query stripping happens.
+ await check("remote_query1=123", "remote_query1=123");
+ await check("remote_query2=456", "remote_query2=456");
+
+ // Set record for strip list.
+ promise = waitForEvent(updateEvent, "update");
+ await RemoteSettings(COLLECTION_NAME).emit("sync", {
+ data: {
+ current: [
+ {
+ id: "1",
+ last_modified: 1000000000000001,
+ stripList: ["remote_query1", "remote_query2"],
+ allowList: [],
+ },
+ ],
+ },
+ });
+ lists = await promise;
+
+ is(
+ lists.stripList,
+ "remote_query1 remote_query2",
+ "There should be strip list entries."
+ );
+ is(lists.allowList, "", "There should be no allow list entries.");
+
+ // The query string should be stripped.
+ await check("remote_query1=123", "");
+ await check("remote_query2=456", "");
+
+ // Set record for strip list and allow list.
+ promise = waitForEvent(updateEvent, "update");
+ await RemoteSettings(COLLECTION_NAME).emit("sync", {
+ data: {
+ current: [
+ {
+ id: "2",
+ last_modified: 1000000000000002,
+ stripList: ["remote_query1", "remote_query2"],
+ allowList: ["example.net"],
+ },
+ ],
+ },
+ });
+ lists = await promise;
+
+ is(
+ lists.stripList,
+ "remote_query1 remote_query2",
+ "There should be strip list entries."
+ );
+ is(lists.allowList, "example.net", "There should be one allow list entry.");
+
+ // The query string shouldn't be stripped because this host is in allow list.
+ await check("remote_query1=123", "remote_query1=123");
+ await check("remote_query2=123", "remote_query2=123");
+
+ urlQueryStrippingListService.unregisterObserver(obs);
+
+ // Clear the remote settings.
+ await db.clear();
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_userInteraction.js b/toolkit/components/antitracking/test/browser/browser_userInteraction.js
new file mode 100644
index 0000000000..d343a56731
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_userInteraction.js
@@ -0,0 +1,124 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+
+add_task(async function () {
+ info("Starting subResources test");
+
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.userInteraction.document.interval", 1],
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ let uri = Services.io.newURI(TEST_DOMAIN);
+ is(
+ PermissionTestUtils.testPermission(uri, "storageAccessAPI"),
+ Services.perms.UNKNOWN_ACTION,
+ "Before user-interaction we don't have a permission"
+ );
+
+ let promise = TestUtils.topicObserved("perm-changed", (aSubject, aData) => {
+ let permission = aSubject.QueryInterface(Ci.nsIPermission);
+ return (
+ permission.type == "storageAccessAPI" &&
+ permission.principal.equalsURI(uri)
+ );
+ });
+
+ info("Simulating user-interaction.");
+ await SpecialPowers.spawn(browser, [], async function () {
+ content.document.userInteractionForTesting();
+ });
+
+ info("Waiting to have a permissions set.");
+ await promise;
+
+ // Let's see if the document is able to update the permission correctly.
+ for (var i = 0; i < 3; ++i) {
+ // Another perm-changed event should be triggered by the timer.
+ promise = TestUtils.topicObserved("perm-changed", (aSubject, aData) => {
+ let permission = aSubject.QueryInterface(Ci.nsIPermission);
+ return (
+ permission.type == "storageAccessAPI" &&
+ permission.principal.equalsURI(uri)
+ );
+ });
+
+ info("Simulating another user-interaction.");
+ await SpecialPowers.spawn(browser, [], async function () {
+ content.document.userInteractionForTesting();
+ });
+
+ info("Waiting to have a permissions set.");
+ await promise;
+ }
+
+ // Let's disable the document.interval.
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.userInteraction.document.interval", 0]],
+ });
+
+ promise = new Promise(resolve => {
+ let id;
+
+ function observer(subject, topic, data) {
+ ok(false, "Notification received!");
+ Services.obs.removeObserver(observer, "perm-changed");
+ clearTimeout(id);
+ resolve();
+ }
+
+ Services.obs.addObserver(observer, "perm-changed");
+
+ id = setTimeout(() => {
+ ok(true, "No notification received!");
+ Services.obs.removeObserver(observer, "perm-changed");
+ resolve();
+ }, 2000);
+ });
+
+ info("Simulating another user-interaction.");
+ await SpecialPowers.spawn(browser, [], async function () {
+ content.document.userInteractionForTesting();
+ });
+
+ info("Waiting to have a permissions set.");
+ await promise;
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+});
+
+add_task(async function () {
+ info("Cleaning up.");
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/browser_workerPropagation.js b/toolkit/components/antitracking/test/browser/browser_workerPropagation.js
new file mode 100644
index 0000000000..54ec0c1bf6
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_workerPropagation.js
@@ -0,0 +1,87 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+
+add_task(async function () {
+ info("Starting subResources test");
+
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.auto_grants", true],
+ ["dom.storage_access.auto_grants.delayed", false],
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.prompt.testing", false],
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ ],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ [
+ "privacy.restrict3rdpartystorage.userInteractionRequiredForHosts",
+ "tracking.example.com,tracking.example.org",
+ ],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ // Let's create an iframe and run the test there.
+ let page = TEST_3RD_PARTY_DOMAIN + TEST_PATH + "workerIframe.html";
+ await SpecialPowers.spawn(browser, [page], async function (page) {
+ await new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.id = "test";
+
+ content.addEventListener("message", e => {
+ if (e.data.type == "finish") {
+ resolve();
+ return;
+ }
+
+ if (e.data.type == "info") {
+ info(e.data.msg);
+ return;
+ }
+
+ if (e.data.type == "ok") {
+ ok(e.data.what, e.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = page;
+ });
+ });
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+});
+
+add_task(async function () {
+ info("Cleaning up.");
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/clearSiteData.sjs b/toolkit/components/antitracking/test/browser/clearSiteData.sjs
new file mode 100644
index 0000000000..374f03a474
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/clearSiteData.sjs
@@ -0,0 +1,6 @@
+function handleRequest(aRequest, aResponse) {
+ aResponse.setStatusLine(aRequest.httpVersion, 200);
+ aResponse.setHeader("Clear-Site-Data", '"*"');
+ aResponse.setHeader("Content-Type", "text/plain");
+ aResponse.write("Clear-Site-Data");
+}
diff --git a/toolkit/components/antitracking/test/browser/container.html b/toolkit/components/antitracking/test/browser/container.html
new file mode 100644
index 0000000000..24daa80113
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/container.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<iframe src="embedder.html"></iframe>
+</body>
+</html>
diff --git a/toolkit/components/antitracking/test/browser/container2.html b/toolkit/components/antitracking/test/browser/container2.html
new file mode 100644
index 0000000000..c2591ad7fc
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/container2.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script>
+ onmessage = function(e) {
+ parent.postMessage(e.data, "*");
+ };
+</script>
+<iframe src="embedder2.html"></iframe>
+</body>
+</html>
diff --git a/toolkit/components/antitracking/test/browser/cookies.sjs b/toolkit/components/antitracking/test/browser/cookies.sjs
new file mode 100644
index 0000000000..1267a69d8c
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/cookies.sjs
@@ -0,0 +1,12 @@
+function handleRequest(aRequest, aResponse) {
+ aResponse.setStatusLine(aRequest.httpVersion, 200);
+ let cookie = "";
+ if (aRequest.hasHeader("Cookie")) {
+ cookie = aRequest.getHeader("Cookie");
+ }
+ aResponse.write("cookie:" + cookie);
+
+ if (aRequest.queryString) {
+ aResponse.setHeader("Set-Cookie", "foopy=" + aRequest.queryString);
+ }
+}
diff --git a/toolkit/components/antitracking/test/browser/cookiesCORS.sjs b/toolkit/components/antitracking/test/browser/cookiesCORS.sjs
new file mode 100644
index 0000000000..2cfdca2700
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/cookiesCORS.sjs
@@ -0,0 +1,9 @@
+function handleRequest(aRequest, aResponse) {
+ aResponse.setStatusLine(aRequest.httpVersion, 200);
+ aResponse.setHeader("Access-Control-Allow-Origin", "http://example.net");
+ aResponse.setHeader("Access-Control-Allow-Credentials", "true");
+
+ if (aRequest.queryString) {
+ aResponse.setHeader("Set-Cookie", "foopy=" + aRequest.queryString);
+ }
+}
diff --git a/toolkit/components/antitracking/test/browser/dedicatedWorker.js b/toolkit/components/antitracking/test/browser/dedicatedWorker.js
new file mode 100644
index 0000000000..72fd4ad850
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/dedicatedWorker.js
@@ -0,0 +1,3 @@
+self.onmessage = msg => {
+ self.postMessage(msg.data);
+};
diff --git a/toolkit/components/antitracking/test/browser/dynamicfpi_head.js b/toolkit/components/antitracking/test/browser/dynamicfpi_head.js
new file mode 100644
index 0000000000..6eaa620508
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/dynamicfpi_head.js
@@ -0,0 +1,180 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from head.js */
+
+"use strict";
+
+this.DynamicFPIHelper = {
+ getTestPageConfig(runInSecureContext) {
+ if (runInSecureContext) {
+ return {
+ topPage: TEST_TOP_PAGE_HTTPS,
+ thirdPartyPage: TEST_4TH_PARTY_STORAGE_PAGE_HTTPS,
+ partitionKey: "(https,example.net)",
+ };
+ }
+ return {
+ topPage: TEST_TOP_PAGE,
+ thirdPartyPage: TEST_4TH_PARTY_STORAGE_PAGE,
+ partitionKey: "(http,example.net)",
+ };
+ },
+
+ runTest(
+ name,
+ callback,
+ cleanupFunction,
+ extraPrefs,
+ runInPrivateWindow,
+ { runInSecureContext = false } = {}
+ ) {
+ add_task(async _ => {
+ info(
+ "Starting test `" +
+ name +
+ "' with dynamic FPI running in a " +
+ (runInPrivateWindow ? "private" : "normal") +
+ " window..."
+ );
+
+ await SpecialPowers.flushPrefEnv();
+ await setCookieBehaviorPref(
+ BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ runInPrivateWindow
+ );
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ [
+ "privacy.partition.always_partition_third_party_non_cookie_storage",
+ true,
+ ],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ ["privacy.dynamic_firstparty.use_site", true],
+ ["dom.security.https_first_pbm", false],
+ [
+ "privacy.restrict3rdpartystorage.userInteractionRequiredForHosts",
+ "not-tracking.example.com",
+ ],
+ ],
+ });
+
+ if (extraPrefs && Array.isArray(extraPrefs) && extraPrefs.length) {
+ await SpecialPowers.pushPrefEnv({ set: extraPrefs });
+ }
+
+ let win = window;
+ if (runInPrivateWindow) {
+ win = OpenBrowserWindow({ private: true });
+ await TestUtils.topicObserved("browser-delayed-startup-finished");
+ }
+
+ const { topPage, thirdPartyPage, partitionKey } =
+ this.getTestPageConfig(runInSecureContext);
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(win.gBrowser, topPage);
+ win.gBrowser.selectedTab = tab;
+
+ let browser = win.gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Check the cookieJarSettings of the browser object");
+ ok(
+ browser.cookieJarSettings,
+ "The browser object has the cookieJarSettings."
+ );
+ is(
+ browser.cookieJarSettings.cookieBehavior,
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ "The cookieJarSettings has the correct cookieBehavior"
+ );
+ is(
+ browser.cookieJarSettings.partitionKey,
+ partitionKey,
+ "The cookieJarSettings has the correct partitionKey"
+ );
+
+ info("Creating a 3rd party content");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: thirdPartyPage,
+ callback: callback.toString(),
+ partitionKey,
+ },
+ ],
+ async obj => {
+ await new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.onload = async _ => {
+ await SpecialPowers.spawn(ifr, [obj], async obj => {
+ is(
+ content.document.nodePrincipal.originAttributes.partitionKey,
+ "",
+ "We don't have first-party set on nodePrincipal"
+ );
+ is(
+ content.document.effectiveStoragePrincipal.originAttributes
+ .partitionKey,
+ obj.partitionKey,
+ "We have first-party set on storagePrincipal"
+ );
+ });
+ info("Sending code to the 3rd party content");
+ ifr.contentWindow.postMessage(obj.callback, "*");
+ };
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ });
+ }
+ );
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ if (runInPrivateWindow) {
+ win.close();
+ }
+ });
+
+ add_task(async _ => {
+ info("Cleaning up.");
+ if (cleanupFunction) {
+ await cleanupFunction();
+ }
+
+ // While running these tests we typically do not have enough idle time to do
+ // GC reliably, so force it here.
+ /* import-globals-from antitracking_head.js */
+ forceGC();
+ });
+ },
+};
diff --git a/toolkit/components/antitracking/test/browser/embedder.html b/toolkit/components/antitracking/test/browser/embedder.html
new file mode 100644
index 0000000000..1a517079e0
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/embedder.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script src="https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/empty.js"></script>
+<script src="https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/empty.js?redirect"></script>
+<script src="https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/empty.js?redirect2"></script>
diff --git a/toolkit/components/antitracking/test/browser/embedder2.html b/toolkit/components/antitracking/test/browser/embedder2.html
new file mode 100644
index 0000000000..b1441894a4
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/embedder2.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/empty.js"></script>
+<script src="https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/empty.js?redirect"></script>
+<script src="https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/empty.js?redirect2"></script>
+</head>
+<body onload="parent.postMessage({data:document.querySelectorAll('script').length}, '*');"></body>
+</html>
diff --git a/toolkit/components/antitracking/test/browser/empty-altsvc.js b/toolkit/components/antitracking/test/browser/empty-altsvc.js
new file mode 100644
index 0000000000..3053583c76
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/empty-altsvc.js
@@ -0,0 +1 @@
+/* nothing here */
diff --git a/toolkit/components/antitracking/test/browser/empty-altsvc.js^headers^ b/toolkit/components/antitracking/test/browser/empty-altsvc.js^headers^
new file mode 100644
index 0000000000..70592d2f93
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/empty-altsvc.js^headers^
@@ -0,0 +1 @@
+Alt-Svc: h2=":12345"; ma=60
diff --git a/toolkit/components/antitracking/test/browser/empty.html b/toolkit/components/antitracking/test/browser/empty.html
new file mode 100644
index 0000000000..e20d67db57
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/empty.html
@@ -0,0 +1 @@
+<h1>Empty</h1>
diff --git a/toolkit/components/antitracking/test/browser/empty.js b/toolkit/components/antitracking/test/browser/empty.js
new file mode 100644
index 0000000000..3053583c76
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/empty.js
@@ -0,0 +1 @@
+/* nothing here */
diff --git a/toolkit/components/antitracking/test/browser/file_iframe_document_open.html b/toolkit/components/antitracking/test/browser/file_iframe_document_open.html
new file mode 100644
index 0000000000..fd2969f270
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/file_iframe_document_open.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<script>
+function run() {
+ let ifr = document.createElement("iframe");
+ document.body.appendChild(ifr);
+
+ let doc = ifr.contentWindow.document;
+ doc.open();
+ doc.write(`<script>document.cookie = "foo=bar"<\/script>`);
+ doc.close();
+}
+</script>
+</head>
+<body onLoad="run();">
+</body>
+</html>
diff --git a/toolkit/components/antitracking/test/browser/file_localStorage.html b/toolkit/components/antitracking/test/browser/file_localStorage.html
new file mode 100644
index 0000000000..54bad94bc9
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/file_localStorage.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Bug 1663192 - Accessing localStorage in a file urls</title>
+</head>
+<script>
+ window.addEventListener("DOMContentLoaded", () => {
+ let result = document.getElementById("result");
+
+ try {
+ window.localStorage.setItem("foo", "bar");
+ result.textContent = "PASS";
+ } catch (e) {
+ result.textContent = "FAIL";
+ }
+ }, { once: true });
+</script>
+<body>
+<a id="result"></a>
+</body>
+</html>
diff --git a/toolkit/components/antitracking/test/browser/file_saveAsImage.sjs b/toolkit/components/antitracking/test/browser/file_saveAsImage.sjs
new file mode 100644
index 0000000000..2afb7d435f
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/file_saveAsImage.sjs
@@ -0,0 +1,20 @@
+// small red image
+const IMAGE = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" +
+ "P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="
+);
+
+function handleRequest(aRequest, aResponse) {
+ aResponse.setStatusLine(aRequest.httpVersion, 200);
+
+ if (aRequest.queryString.includes("result")) {
+ aResponse.write(getState("hints") || 0);
+ setState("hints", "0");
+ } else {
+ let hints = parseInt(getState("hints") || 0) + 1;
+ setState("hints", hints.toString());
+
+ aResponse.setHeader("Content-Type", "image/png", false);
+ aResponse.write(IMAGE);
+ }
+}
diff --git a/toolkit/components/antitracking/test/browser/file_saveAsPageInfo.html b/toolkit/components/antitracking/test/browser/file_saveAsPageInfo.html
new file mode 100644
index 0000000000..aa3de2a555
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/file_saveAsPageInfo.html
@@ -0,0 +1,6 @@
+<html>
+<body>
+ <img src="http://example.net/browser/toolkit/components/antitracking/test/browser/raptor.jpg" id="image1">
+ <video src="http://example.net/browser/toolkit/components/antitracking/test/browser/file_video.ogv" id="video1"> </video>
+</body>
+</html>
diff --git a/toolkit/components/antitracking/test/browser/file_saveAsVideo.sjs b/toolkit/components/antitracking/test/browser/file_saveAsVideo.sjs
new file mode 100644
index 0000000000..10bf246f62
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/file_saveAsVideo.sjs
@@ -0,0 +1,38 @@
+const VIDEO = atob(
+ "GkXfo49CgoR3ZWJtQoeBAkKFgQIYU4BnI0nOEU2bdKpNu" +
+ "4tTq4QVSalmU6yBL027i1OrhBZUrmtTrIGmTbuLU6uEHF" +
+ "O7a1OsgdEVSalm8k2ApWxpYmVibWwyIHYwLjkuNyArIGx" +
+ "pYm1hdHJvc2thMiB2MC45LjhXQZ5ta2NsZWFuIDAuMi41" +
+ "IGZyb20gTGF2ZjUyLjU3LjFzpJC/CoyJjSbckmOytS9Se" +
+ "y3cRImIQK9AAAAAAABEYYgEHiZDUAHwABZUrmumrqTXgQ" +
+ "FzxYEBnIEAIrWcg3VuZIaFVl9WUDiDgQHgh7CCAUC6gfA" +
+ "cU7trzbuMs4EAt4f3gQHxggEju42zggMgt4f3gQHxgrZd" +
+ "u46zggZAt4j3gQHxgwFba7uOs4IJYLeI94EB8YMCAR+7j" +
+ "rOCDIC3iPeBAfGDAqggH0O2dSBibueBAKNbiYEAAIDQdw" +
+ "CdASpAAfAAAAcIhYWIhYSIAIIb347n/c/Z0BPBfjv7f+I" +
+ "/6df275Wbh/XPuZ+qv9k58KrftA9tvkP+efiN/ovmd/if" +
+ "9L/ef2z+Xv+H/rv+39xD/N/zL+nfid71363e9X+pf6/+q" +
+ "+x7+W/0P/H/233Zf6n/Wv696APuDf0b+YdcL+2HsUfxr+" +
+ "gfP/91P+F/yH9K+H79Z/7j/Xvhj/VjVsvwHXt/n/qz9U/" +
+ "w749+c/g="
+);
+
+function handleRequest(aRequest, aResponse) {
+ aResponse.setStatusLine(aRequest.httpVersion, 200);
+
+ if (aRequest.queryString.includes("result")) {
+ aResponse.write(getState("hints") || 0);
+ setState("hints", "0");
+ } else {
+ let hints = parseInt(getState("hints") || 0) + 1;
+ setState("hints", hints.toString());
+
+ aResponse.setHeader("Content-Type", "video/webm", false);
+ aResponse.setHeader(
+ "Cache-Control",
+ "public, max-age=604800, immutable",
+ false
+ );
+ aResponse.write(VIDEO);
+ }
+}
diff --git a/toolkit/components/antitracking/test/browser/file_stripping.html b/toolkit/components/antitracking/test/browser/file_stripping.html
new file mode 100644
index 0000000000..60d9840a0f
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/file_stripping.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf8">
+<script>
+ onmessage = event => {
+ switch (event.data.type) {
+ case "window-open":
+ window.open(event.data.url);
+ break;
+ case "script":
+ window.location.href = event.data.url;
+ break
+ }
+ };
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/toolkit/components/antitracking/test/browser/file_video.ogv b/toolkit/components/antitracking/test/browser/file_video.ogv
new file mode 100644
index 0000000000..68dee3cf2b
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/file_video.ogv
Binary files differ
diff --git a/toolkit/components/antitracking/test/browser/file_ws_handshake_delay_wsh.py b/toolkit/components/antitracking/test/browser/file_ws_handshake_delay_wsh.py
new file mode 100644
index 0000000000..412ac2e24c
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/file_ws_handshake_delay_wsh.py
@@ -0,0 +1,28 @@
+import time
+
+from mod_pywebsocket import msgutil
+
+
+def web_socket_do_extra_handshake(request):
+ # # must set request.ws_protocol to the selected version from ws_requested_protocols
+ for x in request.ws_requested_protocols:
+ if x != "test-does-not-exist":
+ request.ws_protocol = x
+ break
+
+ if request.ws_protocol == "test-3":
+ time.sleep(3)
+ elif request.ws_protocol == "test-6":
+ time.sleep(6)
+ else:
+ pass
+
+
+def web_socket_passive_closing_handshake(request):
+ if request.ws_close_code == 1005:
+ return None, None
+ return request.ws_close_code, request.ws_close_reason
+
+
+def web_socket_transfer_data(request):
+ msgutil.close_connection(request)
diff --git a/toolkit/components/antitracking/test/browser/head.js b/toolkit/components/antitracking/test/browser/head.js
new file mode 100644
index 0000000000..da99cb433b
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/head.js
@@ -0,0 +1,149 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const TEST_DOMAIN = "http://example.net/";
+const TEST_DOMAIN_HTTPS = "https://example.net/";
+const TEST_DOMAIN_2 = "http://xn--exmple-cua.test/";
+const TEST_DOMAIN_3 = "https://xn--hxajbheg2az3al.xn--jxalpdlp/";
+const TEST_DOMAIN_4 = "http://prefixexample.com/";
+const TEST_DOMAIN_5 = "http://test/";
+const TEST_DOMAIN_6 = "http://mochi.test:8888/";
+const TEST_DOMAIN_7 = "http://example.com/";
+const TEST_DOMAIN_8 = "http://www.example.com/";
+const TEST_3RD_PARTY_DOMAIN = "https://tracking.example.org/";
+const TEST_3RD_PARTY_DOMAIN_HTTP = "http://tracking.example.org/";
+const TEST_3RD_PARTY_DOMAIN_TP = "https://tracking.example.com/";
+const TEST_3RD_PARTY_DOMAIN_STP = "https://social-tracking.example.org/";
+const TEST_4TH_PARTY_DOMAIN = "http://not-tracking.example.com/";
+const TEST_4TH_PARTY_DOMAIN_HTTPS = "https://not-tracking.example.com/";
+const TEST_ANOTHER_3RD_PARTY_DOMAIN_HTTP =
+ "http://another-tracking.example.net/";
+const TEST_ANOTHER_3RD_PARTY_DOMAIN_HTTPS =
+ "https://another-tracking.example.net/";
+const TEST_ANOTHER_3RD_PARTY_DOMAIN = SpecialPowers.useRemoteSubframes
+ ? TEST_ANOTHER_3RD_PARTY_DOMAIN_HTTP
+ : TEST_ANOTHER_3RD_PARTY_DOMAIN_HTTPS;
+const TEST_EMAIL_TRACKER_DOMAIN = "http://email-tracking.example.org/";
+
+const TEST_PATH = "browser/toolkit/components/antitracking/test/browser/";
+
+const TEST_TOP_PAGE = TEST_DOMAIN + TEST_PATH + "page.html";
+const TEST_TOP_PAGE_HTTPS = TEST_DOMAIN_HTTPS + TEST_PATH + "page.html";
+const TEST_TOP_PAGE_2 = TEST_DOMAIN_2 + TEST_PATH + "page.html";
+const TEST_TOP_PAGE_3 = TEST_DOMAIN_3 + TEST_PATH + "page.html";
+const TEST_TOP_PAGE_4 = TEST_DOMAIN_4 + TEST_PATH + "page.html";
+const TEST_TOP_PAGE_5 = TEST_DOMAIN_5 + TEST_PATH + "page.html";
+const TEST_TOP_PAGE_6 = TEST_DOMAIN_6 + TEST_PATH + "page.html";
+const TEST_TOP_PAGE_7 = TEST_DOMAIN_7 + TEST_PATH + "page.html";
+const TEST_TOP_PAGE_8 = TEST_DOMAIN_8 + TEST_PATH + "page.html";
+const TEST_EMBEDDER_PAGE = TEST_DOMAIN + TEST_PATH + "embedder.html";
+const TEST_POPUP_PAGE = TEST_DOMAIN + TEST_PATH + "popup.html";
+const TEST_IFRAME_PAGE = TEST_DOMAIN + TEST_PATH + "iframe.html";
+const TEST_3RD_PARTY_PAGE = TEST_3RD_PARTY_DOMAIN + TEST_PATH + "3rdParty.html";
+const TEST_3RD_PARTY_PAGE_HTTP =
+ TEST_3RD_PARTY_DOMAIN_HTTP + TEST_PATH + "3rdParty.html";
+const TEST_3RD_PARTY_PAGE_WO =
+ TEST_3RD_PARTY_DOMAIN + TEST_PATH + "3rdPartyWO.html";
+const TEST_3RD_PARTY_PAGE_UI =
+ TEST_3RD_PARTY_DOMAIN + TEST_PATH + "3rdPartyUI.html";
+const TEST_3RD_PARTY_PAGE_WITH_SVG =
+ TEST_3RD_PARTY_DOMAIN + TEST_PATH + "3rdPartySVG.html";
+const TEST_3RD_PARTY_PAGE_RELAY =
+ TEST_4TH_PARTY_DOMAIN + TEST_PATH + "3rdPartyRelay.html";
+const TEST_4TH_PARTY_PAGE = TEST_4TH_PARTY_DOMAIN + TEST_PATH + "3rdParty.html";
+const TEST_ANOTHER_3RD_PARTY_PAGE =
+ TEST_ANOTHER_3RD_PARTY_DOMAIN + TEST_PATH + "3rdParty.html";
+const TEST_ANOTHER_3RD_PARTY_PAGE_HTTPS =
+ TEST_ANOTHER_3RD_PARTY_DOMAIN_HTTPS + TEST_PATH + "3rdParty.html";
+const TEST_3RD_PARTY_STORAGE_PAGE =
+ TEST_3RD_PARTY_DOMAIN_HTTP + TEST_PATH + "3rdPartyStorage.html";
+const TEST_3RD_PARTY_PAGE_WORKER =
+ TEST_3RD_PARTY_DOMAIN + TEST_PATH + "3rdPartyWorker.html";
+const TEST_3RD_PARTY_PARTITIONED_PAGE =
+ TEST_3RD_PARTY_DOMAIN + TEST_PATH + "3rdPartyPartitioned.html";
+const TEST_4TH_PARTY_STORAGE_PAGE =
+ TEST_4TH_PARTY_DOMAIN + TEST_PATH + "3rdPartyStorage.html";
+const TEST_4TH_PARTY_STORAGE_PAGE_HTTPS =
+ TEST_4TH_PARTY_DOMAIN_HTTPS + TEST_PATH + "3rdPartyStorage.html";
+const TEST_4TH_PARTY_PARTITIONED_PAGE =
+ TEST_4TH_PARTY_DOMAIN + TEST_PATH + "3rdPartyPartitioned.html";
+
+const BEHAVIOR_ACCEPT = Ci.nsICookieService.BEHAVIOR_ACCEPT;
+const BEHAVIOR_REJECT = Ci.nsICookieService.BEHAVIOR_REJECT;
+const BEHAVIOR_LIMIT_FOREIGN = Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN;
+const BEHAVIOR_REJECT_FOREIGN = Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN;
+const BEHAVIOR_REJECT_TRACKER = Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER;
+const BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN =
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN;
+
+let originalRequestLongerTimeout = requestLongerTimeout;
+// eslint-disable-next-line no-global-assign
+requestLongerTimeout = function AntiTrackingRequestLongerTimeout(factor) {
+ let ccovMultiplier = AppConstants.MOZ_CODE_COVERAGE ? 2 : 1;
+ let fissionMultiplier = SpecialPowers.useRemoteSubframes ? 2 : 1;
+ originalRequestLongerTimeout(ccovMultiplier * fissionMultiplier * factor);
+};
+
+requestLongerTimeout(3);
+
+const { UrlClassifierTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/UrlClassifierTestUtils.sys.mjs"
+);
+
+const { PermissionTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PermissionTestUtils.sys.mjs"
+);
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/toolkit/components/antitracking/test/browser/antitracking_head.js",
+ this
+);
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/toolkit/components/antitracking/test/browser/partitionedstorage_head.js",
+ this
+);
+
+function setCookieBehaviorPref(cookieBehavior, runInPrivateWindow) {
+ let cbRegular;
+ let cbPrivate;
+
+ // Set different cookieBehaviors to regular mode and private mode so that we
+ // can make sure these two prefs don't interfere with each other for all
+ // tests.
+ if (runInPrivateWindow) {
+ cbPrivate = cookieBehavior;
+
+ let defaultPrefBranch = Services.prefs.getDefaultBranch("");
+ // In order to test the default private cookieBehavior pref, we need to set
+ // the regular pref to the default value because we don't want the private
+ // pref to mirror the regular pref in this case.
+ //
+ // Note that the private pref will mirror the regular pref if the private
+ // pref is in default value and the regular pref is not in default value.
+ if (
+ cookieBehavior ==
+ defaultPrefBranch.getIntPref("network.cookie.cookieBehavior.pbmode")
+ ) {
+ cbRegular = defaultPrefBranch.getIntPref("network.cookie.cookieBehavior");
+ } else {
+ cbRegular =
+ cookieBehavior == BEHAVIOR_ACCEPT ? BEHAVIOR_REJECT : BEHAVIOR_ACCEPT;
+ }
+ } else {
+ cbRegular = cookieBehavior;
+ cbPrivate =
+ cookieBehavior == BEHAVIOR_ACCEPT ? BEHAVIOR_REJECT : BEHAVIOR_ACCEPT;
+ }
+
+ return SpecialPowers.pushPrefEnv({
+ set: [
+ ["network.cookie.cookieBehavior", cbRegular],
+ ["network.cookie.cookieBehavior.pbmode", cbPrivate],
+ ],
+ });
+}
diff --git a/toolkit/components/antitracking/test/browser/iframe.html b/toolkit/components/antitracking/test/browser/iframe.html
new file mode 100644
index 0000000000..85d37ed7fa
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/iframe.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+ <title>Just a first-level iframe</title>
+</head>
+<body>
+ <h1>This is the first-level iframe</h1>
+</body>
+</html>
diff --git a/toolkit/components/antitracking/test/browser/image.sjs b/toolkit/components/antitracking/test/browser/image.sjs
new file mode 100644
index 0000000000..145ff1f0a4
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/image.sjs
@@ -0,0 +1,22 @@
+// A 1x1 PNG image.
+// Source: https://commons.wikimedia.org/wiki/File:1x1.png (Public Domain)
+const IMAGE = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAA" +
+ "ACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII="
+);
+
+function handleRequest(aRequest, aResponse) {
+ aResponse.setStatusLine(aRequest.httpVersion, 200);
+
+ if (aRequest.queryString.includes("result")) {
+ aResponse.write(getState("hints") || 0);
+ setState("hints", "0");
+ } else {
+ let hints = parseInt(getState("hints") || 0) + 1;
+ setState("hints", hints.toString());
+
+ aResponse.setHeader("Set-Cookie", "foopy=1");
+ aResponse.setHeader("Content-Type", "image/png", false);
+ aResponse.write(IMAGE);
+ }
+}
diff --git a/toolkit/components/antitracking/test/browser/imageCacheWorker.js b/toolkit/components/antitracking/test/browser/imageCacheWorker.js
new file mode 100644
index 0000000000..d11221112c
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/imageCacheWorker.js
@@ -0,0 +1,78 @@
+/* import-globals-from head.js */
+/* import-globals-from antitracking_head.js */
+/* import-globals-from browser_imageCache4.js */
+
+AntiTracking.runTest(
+ "Image cache - should load the image three times.",
+ // blocking callback
+ async _ => {
+ // Let's load the image twice here.
+ let img = document.createElement("img");
+ document.body.appendChild(img);
+ img.src =
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/image.sjs";
+ await new Promise(resolve => {
+ img.onload = resolve;
+ });
+ ok(true, "Image 1 loaded");
+
+ img = document.createElement("img");
+ document.body.appendChild(img);
+ img.src =
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/image.sjs";
+ await new Promise(resolve => {
+ img.onload = resolve;
+ });
+ ok(true, "Image 2 loaded");
+ },
+
+ // non-blocking callback
+ {
+ runExtraTests: false,
+ cookieBehavior,
+ blockingByAllowList,
+ expectedBlockingNotifications,
+ callback: async _ => {
+ // Let's load the image twice here as well.
+ let img = document.createElement("img");
+ document.body.appendChild(img);
+ img.src =
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/image.sjs";
+ await new Promise(resolve => {
+ img.onload = resolve;
+ });
+ ok(true, "Image 3 loaded");
+
+ img = document.createElement("img");
+ document.body.appendChild(img);
+ img.src =
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/image.sjs";
+ await new Promise(resolve => {
+ img.onload = resolve;
+ });
+ ok(true, "Image 4 loaded");
+ },
+ },
+ null, // cleanup function
+ null, // no extra prefs
+ false, // no window open test
+ false, // no user-interaction test
+ expectedBlockingNotifications
+);
+
+// We still want to see just expected requests.
+add_task(async _ => {
+ await fetch(
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/image.sjs?result"
+ )
+ .then(r => r.text())
+ .then(text => {
+ is(text, "2", "The image should be loaded correctly.");
+ });
+
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+});
diff --git a/toolkit/components/antitracking/test/browser/localStorage.html b/toolkit/components/antitracking/test/browser/localStorage.html
new file mode 100644
index 0000000000..e08c25f2c4
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/localStorage.html
@@ -0,0 +1,68 @@
+<h1>Here a tracker!</h1>
+<script>
+
+if (window.opener) {
+ SpecialPowers.wrap(document).userInteractionForTesting();
+ localStorage.foo = "opener" + Math.random();
+ // Don't call window.close immediatelly. It can happen that adding the
+ // "storage" event listener below takes more time than usual (it may need to
+ // synchronously subscribe in the parent process to receive storage
+ // notifications). Spending more time in the initial script can prevent
+ // the "load" event from being fired for the window opened by "open and test".
+ setTimeout(() => {
+ window.close();
+ }, 0);
+}
+
+if (parent) {
+ window.onmessage = e => {
+ if (e.data == "test") {
+ let status;
+ try {
+ localStorage.foo = "value" + Math.random();
+ status = true;
+ } catch (e) {
+ status = false;
+ }
+
+ parent.postMessage({type: "test", status }, "*");
+ return;
+ }
+
+ if (e.data == "open") {
+ window.open("localStorage.html");
+ return;
+ }
+
+ if (e.data == "open and test") {
+ let w = window.open("localStorage.html");
+ w.addEventListener("load", _ => {
+ let status;
+ try {
+ localStorage.foo = "value" + Math.random();
+ status = true;
+ } catch (e) {
+ status = false;
+ }
+
+ parent.postMessage({type: "test", status }, "*");
+ }, {once: true});
+ }
+ };
+
+ window.addEventListener("storage", e => {
+ let fromOpener = localStorage.foo.startsWith("opener");
+
+ let status;
+ try {
+ localStorage.foo = "value" + Math.random();
+ status = true;
+ } catch (e) {
+ status = false;
+ }
+
+ parent.postMessage({type: "test", status: status && fromOpener }, "*");
+ });
+}
+
+</script>
diff --git a/toolkit/components/antitracking/test/browser/localStorageEvents.html b/toolkit/components/antitracking/test/browser/localStorageEvents.html
new file mode 100644
index 0000000000..737d1e0cab
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/localStorageEvents.html
@@ -0,0 +1,30 @@
+<script>
+
+let eventCounter = 0;
+
+onmessage = e => {
+ if (e.data == "getValue") {
+ parent.postMessage(localStorage.foo, "*");
+ return;
+ }
+
+ if (e.data == "setValue") {
+ localStorage.foo = "tracker-" + Math.random();
+ return;
+ }
+
+ if (e.data == "getEvents") {
+ parent.postMessage(eventCounter, "*");
+ return;
+ }
+
+ if (e.data == "reload") {
+ window.location.reload();
+ }
+};
+
+addEventListener("storage", _ => {
+ ++eventCounter;
+});
+
+</script>
diff --git a/toolkit/components/antitracking/test/browser/matchAll.js b/toolkit/components/antitracking/test/browser/matchAll.js
new file mode 100644
index 0000000000..8f112b0804
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/matchAll.js
@@ -0,0 +1,16 @@
+self.addEventListener("message", async e => {
+ let clients = await self.clients.matchAll({
+ type: "window",
+ includeUncontrolled: true,
+ });
+
+ let hasWindow = false;
+ for (let client of clients) {
+ if (e.data == client.url) {
+ hasWindow = true;
+ break;
+ }
+ }
+
+ e.source.postMessage(hasWindow);
+});
diff --git a/toolkit/components/antitracking/test/browser/page.html b/toolkit/components/antitracking/test/browser/page.html
new file mode 100644
index 0000000000..a99e8be179
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/page.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+ <title>Just a top-level page</title>
+</head>
+<body>
+ <h1>This is the top-level page</h1>
+</body>
+</html>
diff --git a/toolkit/components/antitracking/test/browser/partitionedSharedWorker.js b/toolkit/components/antitracking/test/browser/partitionedSharedWorker.js
new file mode 100644
index 0000000000..5ac4ec9f27
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/partitionedSharedWorker.js
@@ -0,0 +1,17 @@
+let value = "";
+self.onconnect = e => {
+ e.ports[0].onmessage = event => {
+ if (event.data.what === "get") {
+ e.ports[0].postMessage(value);
+ return;
+ }
+
+ if (event.data.what === "put") {
+ value = event.data.value;
+ return;
+ }
+
+ // Error.
+ e.ports[0].postMessage(-1);
+ };
+};
diff --git a/toolkit/components/antitracking/test/browser/partitionedstorage_head.js b/toolkit/components/antitracking/test/browser/partitionedstorage_head.js
new file mode 100644
index 0000000000..42574fdb93
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/partitionedstorage_head.js
@@ -0,0 +1,455 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from head.js */
+
+"use strict";
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/toolkit/components/antitracking/test/browser/dynamicfpi_head.js",
+ this
+);
+
+this.PartitionedStorageHelper = {
+ runTestInNormalAndPrivateMode(name, callback, cleanupFunction, extraPrefs) {
+ // Normal mode
+ this.runTest(name, callback, cleanupFunction, extraPrefs, {
+ runInPrivateWindow: false,
+ });
+
+ // Private mode
+ this.runTest(name, callback, cleanupFunction, extraPrefs, {
+ runInPrivateWindow: true,
+ });
+ },
+
+ runTest(
+ name,
+ callback,
+ cleanupFunction,
+ extraPrefs,
+ { runInPrivateWindow = false, runInSecureContext = false } = {}
+ ) {
+ DynamicFPIHelper.runTest(
+ name,
+ callback,
+ cleanupFunction,
+ extraPrefs,
+ runInPrivateWindow,
+ { runInSecureContext }
+ );
+ },
+
+ runPartitioningTestInNormalAndPrivateMode(
+ name,
+ testCategory,
+ getDataCallback,
+ addDataCallback,
+ cleanupFunction,
+ expectUnpartition = false
+ ) {
+ // Normal mode
+ this.runPartitioningTest(
+ name,
+ testCategory,
+ getDataCallback,
+ addDataCallback,
+ cleanupFunction,
+ expectUnpartition,
+ false
+ );
+
+ // Private mode
+ this.runPartitioningTest(
+ name,
+ testCategory,
+ getDataCallback,
+ addDataCallback,
+ cleanupFunction,
+ expectUnpartition,
+ true
+ );
+ },
+
+ runPartitioningTest(
+ name,
+ testCategory,
+ getDataCallback,
+ addDataCallback,
+ cleanupFunction,
+ expectUnpartition,
+ runInPrivateWindow = false
+ ) {
+ for (let variant of ["normal", "initial-aboutblank"]) {
+ for (let limitForeignContexts of [false, true]) {
+ this.runPartitioningTestInner(
+ name,
+ testCategory,
+ getDataCallback,
+ addDataCallback,
+ cleanupFunction,
+ variant,
+ runInPrivateWindow,
+ limitForeignContexts,
+ expectUnpartition
+ );
+ }
+ }
+ },
+
+ runPartitioningTestInner(
+ name,
+ testCategory,
+ getDataCallback,
+ addDataCallback,
+ cleanupFunction,
+ variant,
+ runInPrivateWindow,
+ limitForeignContexts,
+ expectUnpartition
+ ) {
+ add_task(async _ => {
+ info(
+ "Starting test `" +
+ name +
+ "' testCategory `" +
+ testCategory +
+ "' variant `" +
+ variant +
+ "' in a " +
+ (runInPrivateWindow ? "private" : "normal") +
+ " window " +
+ (limitForeignContexts ? "with" : "without") +
+ " limitForeignContexts to check that 2 tabs are correctly partititioned"
+ );
+
+ await SpecialPowers.flushPrefEnv();
+ await setCookieBehaviorPref(
+ BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ runInPrivateWindow
+ );
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ [
+ "privacy.partition.always_partition_third_party_non_cookie_storage",
+ true,
+ ],
+ ["privacy.dynamic_firstparty.limitForeign", limitForeignContexts],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ ["dom.security.https_first_pbm", false],
+ [
+ "privacy.restrict3rdpartystorage.userInteractionRequiredForHosts",
+ "not-tracking.example.com",
+ ],
+ ],
+ });
+
+ let win = window;
+ if (runInPrivateWindow) {
+ win = OpenBrowserWindow({ private: true });
+ await TestUtils.topicObserved("browser-delayed-startup-finished");
+ }
+
+ info("Creating the first tab");
+ let tab1 = BrowserTestUtils.addTab(win.gBrowser, TEST_TOP_PAGE);
+ win.gBrowser.selectedTab = tab1;
+
+ let browser1 = win.gBrowser.getBrowserForTab(tab1);
+ await BrowserTestUtils.browserLoaded(browser1);
+
+ info("Creating the second tab");
+ let tab2 = BrowserTestUtils.addTab(win.gBrowser, TEST_TOP_PAGE_6);
+ win.gBrowser.selectedTab = tab2;
+
+ let browser2 = win.gBrowser.getBrowserForTab(tab2);
+ await BrowserTestUtils.browserLoaded(browser2);
+
+ info("Creating the third tab");
+ let tab3 = BrowserTestUtils.addTab(
+ win.gBrowser,
+ TEST_4TH_PARTY_PARTITIONED_PAGE
+ );
+ win.gBrowser.selectedTab = tab3;
+
+ let browser3 = win.gBrowser.getBrowserForTab(tab3);
+ await BrowserTestUtils.browserLoaded(browser3);
+
+ // Use the same URL as first tab to check partitioned data
+ info("Creating the forth tab");
+ let tab4 = BrowserTestUtils.addTab(win.gBrowser, TEST_TOP_PAGE);
+ win.gBrowser.selectedTab = tab4;
+
+ let browser4 = win.gBrowser.getBrowserForTab(tab4);
+ await BrowserTestUtils.browserLoaded(browser4);
+
+ async function getDataFromThirdParty(browser, result) {
+ // Overwrite the special case here since third party cookies are not
+ // avilable when `limitForeignContexts` is enabled.
+ if (testCategory === "cookies" && limitForeignContexts) {
+ info("overwrite result to empty");
+ result = "";
+ }
+
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: TEST_4TH_PARTY_PARTITIONED_PAGE + "?variant=" + variant,
+ getDataCallback: getDataCallback.toString(),
+ result,
+ },
+ ],
+ async obj => {
+ await new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.onload = __ => {
+ info("Sending code to the 3rd party content");
+ ifr.contentWindow.postMessage({ cb: obj.getDataCallback }, "*");
+ };
+
+ content.addEventListener(
+ "message",
+ function msg(event) {
+ is(
+ event.data,
+ obj.result,
+ "Partitioned cookie jar has value: " + obj.result
+ );
+ resolve();
+ },
+ { once: true }
+ );
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ });
+ }
+ );
+ }
+
+ async function getDataFromFirstParty(browser, result) {
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ getDataCallback: getDataCallback.toString(),
+ result,
+ variant,
+ },
+ ],
+ async obj => {
+ let runnableStr = `(() => {return (${obj.getDataCallback});})();`;
+ let runnable = eval(runnableStr); // eslint-disable-line no-eval
+ let win = content;
+ if (obj.variant == "initial-aboutblank") {
+ let i = win.document.createElement("iframe");
+ i.src = "about:blank";
+ win.document.body.appendChild(i);
+ // override win to make it point to the initial about:blank window
+ win = i.contentWindow;
+ }
+
+ let result = await runnable.call(content, win);
+ is(
+ result,
+ obj.result,
+ "Partitioned cookie jar is empty: " + obj.result
+ );
+ }
+ );
+ }
+
+ info("Checking 3rd party has an empty cookie jar in first tab");
+ await getDataFromThirdParty(browser1, "");
+
+ info("Checking 3rd party has an empty cookie jar in second tab");
+ await getDataFromThirdParty(browser2, "");
+
+ info("Checking first party has an empty cookie jar in third tab");
+ await getDataFromFirstParty(browser3, "");
+
+ info("Checking 3rd party has an empty cookie jar in forth tab");
+ await getDataFromThirdParty(browser4, "");
+
+ async function createDataInThirdParty(browser, value) {
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: TEST_4TH_PARTY_PARTITIONED_PAGE + "?variant=" + variant,
+ addDataCallback: addDataCallback.toString(),
+ value,
+ },
+ ],
+ async obj => {
+ await new content.Promise(resolve => {
+ let ifr = content.document.getElementsByTagName("iframe")[0];
+ content.addEventListener(
+ "message",
+ function msg(event) {
+ ok(event.data, "Data created");
+ resolve();
+ },
+ { once: true }
+ );
+
+ ifr.contentWindow.postMessage(
+ {
+ cb: obj.addDataCallback,
+ value: obj.value,
+ },
+ "*"
+ );
+ });
+ }
+ );
+ }
+
+ async function createDataInFirstParty(browser, value) {
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ addDataCallback: addDataCallback.toString(),
+ value,
+ variant,
+ },
+ ],
+ async obj => {
+ let runnableStr = `(() => {return (${obj.addDataCallback});})();`;
+ let runnable = eval(runnableStr); // eslint-disable-line no-eval
+ let win = content;
+ if (obj.variant == "initial-aboutblank") {
+ let i = win.document.createElement("iframe");
+ i.src = "about:blank";
+ win.document.body.appendChild(i);
+ // override win to make it point to the initial about:blank window
+ win = i.contentWindow;
+ }
+
+ let result = await runnable.call(content, win, obj.value);
+ ok(result, "Data created");
+ }
+ );
+ }
+
+ info("Creating data in the first tab");
+ await createDataInThirdParty(browser1, "A");
+
+ info("Creating data in the second tab");
+ await createDataInThirdParty(browser2, "B");
+
+ // Before writing browser4, check data written by browser1
+ info("First tab should still have just 'A'");
+ await getDataFromThirdParty(browser1, "A");
+ info("Forth tab should still have just 'A'");
+ await getDataFromThirdParty(browser4, "A");
+
+ // Ensure to create data in the forth tab before the third tab,
+ // otherwise cookie will be written successfully due to prior cookie
+ // of the base domain exists.
+ info("Creating data in the forth tab");
+ await createDataInThirdParty(browser4, "D");
+
+ info("Creating data in the third tab");
+ await createDataInFirstParty(browser3, "C");
+
+ // read all tabs
+ info("First tab should be changed to 'D'");
+ await getDataFromThirdParty(browser1, "D");
+
+ info("Second tab should still have just 'B'");
+ await getDataFromThirdParty(browser2, "B");
+
+ info("Third tab should still have just 'C'");
+ await getDataFromFirstParty(browser3, "C");
+
+ info("Forth tab should still have just 'D'");
+ await getDataFromThirdParty(browser4, "D");
+
+ async function setStorageAccessForThirdParty(browser) {
+ info(`Setting permission for ${browser.currentURI.spec}`);
+ let type = "3rdPartyStorage^http://not-tracking.example.com";
+ let permission = Services.perms.ALLOW_ACTION;
+ let expireType = Services.perms.EXPIRE_SESSION;
+ Services.perms.addFromPrincipal(
+ browser.contentPrincipal,
+ type,
+ permission,
+ expireType,
+ 0
+ );
+ // Wait for permission to be set successfully
+ let originAttributes = runInPrivateWindow
+ ? { privateBrowsingId: 1 }
+ : {};
+ await new Promise(resolve => {
+ let id = setInterval(async _ => {
+ if (
+ await SpecialPowers.testPermission(type, permission, {
+ url: browser.currentURI.spec,
+ originAttributes,
+ })
+ ) {
+ clearInterval(id);
+ resolve();
+ }
+ }, 0);
+ });
+ }
+
+ if (!expectUnpartition) {
+ info("Setting Storage access for third parties");
+
+ await setStorageAccessForThirdParty(browser1);
+ await setStorageAccessForThirdParty(browser2);
+ await setStorageAccessForThirdParty(browser3);
+ await setStorageAccessForThirdParty(browser4);
+
+ info("Done setting Storage access for third parties");
+
+ // read all tabs
+ info("First tab should still have just 'D'");
+ await getDataFromThirdParty(browser1, "D");
+
+ info("Second tab should still have just 'B'");
+ await getDataFromThirdParty(browser2, "B");
+
+ info("Third tab should still have just 'C'");
+ await getDataFromFirstParty(browser3, "C");
+
+ info("Forth tab should still have just 'D'");
+ await getDataFromThirdParty(browser4, "D");
+ }
+
+ info("Done checking departitioned state");
+
+ info("Removing the tabs");
+ BrowserTestUtils.removeTab(tab1);
+ BrowserTestUtils.removeTab(tab2);
+ BrowserTestUtils.removeTab(tab3);
+ BrowserTestUtils.removeTab(tab4);
+
+ if (runInPrivateWindow) {
+ win.close();
+ }
+ });
+
+ add_task(async _ => {
+ info("Cleaning up.");
+ if (cleanupFunction) {
+ await cleanupFunction();
+ }
+
+ // While running these tests we typically do not have enough idle time to do
+ // GC reliably, so force it here.
+ /* import-globals-from antitracking_head.js */
+ forceGC();
+ });
+ },
+};
diff --git a/toolkit/components/antitracking/test/browser/popup.html b/toolkit/components/antitracking/test/browser/popup.html
new file mode 100644
index 0000000000..f195add788
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/popup.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+ <title>Just a popup that does a redirect</title>
+</head>
+<body>
+ <h1>Just a popup that does a redirect</h1>
+ <script>
+ window.location = "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/3rdPartyOpenUI.html";
+ </script>
+</body>
+</html>
diff --git a/toolkit/components/antitracking/test/browser/raptor.jpg b/toolkit/components/antitracking/test/browser/raptor.jpg
new file mode 100644
index 0000000000..243ba9e2d4
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/raptor.jpg
Binary files differ
diff --git a/toolkit/components/antitracking/test/browser/redirect.sjs b/toolkit/components/antitracking/test/browser/redirect.sjs
new file mode 100644
index 0000000000..4645aedca5
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/redirect.sjs
@@ -0,0 +1,11 @@
+function handleRequest(aRequest, aResponse) {
+ aResponse.setStatusLine(aRequest.httpVersion, 302);
+
+ let query = aRequest.queryString;
+ let locations = query.split("|");
+ let nextLocation = locations.shift();
+ if (locations.length) {
+ nextLocation += "?" + locations.join("|");
+ }
+ aResponse.setHeader("Location", nextLocation);
+}
diff --git a/toolkit/components/antitracking/test/browser/referrer.sjs b/toolkit/components/antitracking/test/browser/referrer.sjs
new file mode 100644
index 0000000000..8e80eb7fa3
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/referrer.sjs
@@ -0,0 +1,49 @@
+// A 1x1 PNG image.
+// Source: https://commons.wikimedia.org/wiki/File:1x1.png (Public Domain)
+const IMAGE = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAA" +
+ "ACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII="
+);
+
+const IFRAME =
+ "<!DOCTYPE html>\n" +
+ "<script>\n" +
+ "onmessage = event => {\n" +
+ "parent.postMessage(document.referrer, '*');\n" +
+ "};\n" +
+ "</script>";
+
+function handleRequest(aRequest, aResponse) {
+ aResponse.setStatusLine(aRequest.httpVersion, 200);
+
+ let key;
+ if (aRequest.queryString.includes("what=script")) {
+ key = "script";
+ } else if (aRequest.queryString.includes("what=image")) {
+ key = "image";
+ } else {
+ key = "iframe";
+ }
+
+ if (aRequest.queryString.includes("result")) {
+ aResponse.write(getState(key));
+ setState(key, "");
+ return;
+ }
+
+ if (aRequest.hasHeader("Referer")) {
+ let referrer = aRequest.getHeader("Referer");
+ setState(key, referrer);
+ }
+
+ if (key == "script") {
+ aResponse.setHeader("Content-Type", "text/javascript", false);
+ aResponse.write("42;");
+ } else if (key == "image") {
+ aResponse.setHeader("Content-Type", "image/png", false);
+ aResponse.write(IMAGE);
+ } else {
+ aResponse.setHeader("Content-Type", "text/html", false);
+ aResponse.write(IFRAME);
+ }
+}
diff --git a/toolkit/components/antitracking/test/browser/sandboxed.html b/toolkit/components/antitracking/test/browser/sandboxed.html
new file mode 100644
index 0000000000..a359ae0aa3
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/sandboxed.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ </head>
+ <body>
+ <p>Hello, World!</p>
+ </body>
+</html>
diff --git a/toolkit/components/antitracking/test/browser/sandboxed.html^headers^ b/toolkit/components/antitracking/test/browser/sandboxed.html^headers^
new file mode 100644
index 0000000000..4705ce9ded
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/sandboxed.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: sandbox allow-scripts;
diff --git a/toolkit/components/antitracking/test/browser/server.sjs b/toolkit/components/antitracking/test/browser/server.sjs
new file mode 100644
index 0000000000..4d1b2ef01a
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/server.sjs
@@ -0,0 +1,20 @@
+function handleRequest(aRequest, aResponse) {
+ if (aRequest.queryString.includes("redirect")) {
+ aResponse.setStatusLine(aRequest.httpVersion, 302);
+ if (aRequest.queryString.includes("redirect-checkonly")) {
+ aResponse.setHeader("Location", "server.sjs?checkonly");
+ } else {
+ aResponse.setHeader("Location", "server.sjs");
+ }
+ return;
+ }
+ aResponse.setStatusLine(aRequest.httpVersion, 200);
+ if (aRequest.hasHeader("Cookie")) {
+ aResponse.write("cookie-present");
+ } else {
+ if (!aRequest.queryString.includes("checkonly")) {
+ aResponse.setHeader("Set-Cookie", "foopy=1");
+ }
+ aResponse.write("cookie-not-present");
+ }
+}
diff --git a/toolkit/components/antitracking/test/browser/serviceWorker.js b/toolkit/components/antitracking/test/browser/serviceWorker.js
new file mode 100644
index 0000000000..71c88c721a
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/serviceWorker.js
@@ -0,0 +1,103 @@
+let value = "";
+let fetch_url = "";
+
+self.onfetch = function (e) {
+ fetch_url = e.request.url;
+};
+
+// Call clients.claim() to enable fetch event.
+self.addEventListener("activate", e => {
+ e.waitUntil(self.clients.claim());
+});
+
+self.addEventListener("message", async e => {
+ let res = {};
+
+ switch (e.data.type) {
+ case "GetHWConcurrency":
+ res.result = "OK";
+ res.value = navigator.hardwareConcurrency;
+ break;
+
+ case "GetScriptValue":
+ res.result = "OK";
+ res.value = value;
+ break;
+
+ case "SetScriptValue":
+ res.result = "OK";
+ value = e.data.value;
+ break;
+
+ case "HasCache":
+ // Return if the cache storage exists or not.
+ try {
+ res.value = await caches.has(e.data.value);
+ res.result = "OK";
+ } catch (e) {
+ res.result = "ERROR";
+ }
+ break;
+
+ case "SetCache":
+ // Open a cache storage with the given name.
+ try {
+ let cache = await caches.open(e.data.value);
+ await cache.add("empty.js");
+ res.result = "OK";
+ } catch (e) {
+ res.result = "ERROR";
+ }
+ break;
+
+ case "SetIndexedDB":
+ await new Promise(resolve => {
+ let idxDB = indexedDB.open("test", 1);
+
+ idxDB.onupgradeneeded = evt => {
+ let db = evt.target.result;
+ db.createObjectStore("foobar", { keyPath: "id" });
+ };
+
+ idxDB.onsuccess = evt => {
+ let db = evt.target.result;
+ db
+ .transaction("foobar", "readwrite")
+ .objectStore("foobar")
+ .put({ id: 1, value: e.data.value }).onsuccess = _ => {
+ resolve();
+ };
+ };
+ });
+ res.result = "OK";
+ break;
+
+ case "GetIndexedDB":
+ res.value = await new Promise(resolve => {
+ let idxDB = indexedDB.open("test", 1);
+
+ idxDB.onsuccess = evt => {
+ let db = evt.target.result;
+ db.transaction("foobar").objectStore("foobar").get(1).onsuccess =
+ ee => {
+ resolve(
+ ee.target.result === undefined ? "" : ee.target.result.value
+ );
+ };
+ };
+ });
+ res.result = "OK";
+ break;
+
+ case "GetFetchURL":
+ res.value = fetch_url;
+ fetch_url = "";
+ res.result = "OK";
+ break;
+
+ default:
+ res.result = "ERROR";
+ }
+
+ e.source.postMessage(res);
+});
diff --git a/toolkit/components/antitracking/test/browser/sharedWorker.js b/toolkit/components/antitracking/test/browser/sharedWorker.js
new file mode 100644
index 0000000000..01188ed10a
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/sharedWorker.js
@@ -0,0 +1,18 @@
+let ports = 0;
+self.onconnect = e => {
+ ++ports;
+ e.ports[0].onmessage = event => {
+ if (event.data === "count") {
+ e.ports[0].postMessage(ports);
+ return;
+ }
+
+ if (event.data === "close") {
+ self.close();
+ return;
+ }
+
+ // Error.
+ e.ports[0].postMessage(-1);
+ };
+};
diff --git a/toolkit/components/antitracking/test/browser/storageAccessAPIHelpers.js b/toolkit/components/antitracking/test/browser/storageAccessAPIHelpers.js
new file mode 100644
index 0000000000..6a983ec250
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/storageAccessAPIHelpers.js
@@ -0,0 +1,236 @@
+/* global allowListed */
+
+async function hasStorageAccessInitially() {
+ let hasAccess = await document.hasStorageAccess();
+ ok(hasAccess, "Has storage access");
+}
+
+async function noStorageAccessInitially() {
+ let hasAccess = await document.hasStorageAccess();
+ ok(!hasAccess, "Doesn't yet have storage access");
+}
+
+async function stillNoStorageAccess() {
+ let hasAccess = await document.hasStorageAccess();
+ ok(!hasAccess, "Still doesn't have storage access");
+}
+
+async function callRequestStorageAccess(callback, expectFail) {
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+
+ let origin = new URL(location.href).origin;
+
+ let effectiveCookieBehavior = SpecialPowers.isContentWindowPrivate(window)
+ ? SpecialPowers.Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior.pbmode"
+ )
+ : SpecialPowers.Services.prefs.getIntPref("network.cookie.cookieBehavior");
+
+ let success = true;
+ // We only grant storage exceptions when the reject tracker behavior is enabled.
+ let rejectTrackers =
+ [
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ SpecialPowers.Ci.nsICookieService
+ .BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ].includes(effectiveCookieBehavior) && !isOnContentBlockingAllowList();
+ const TEST_ANOTHER_3RD_PARTY_ORIGIN = SpecialPowers.useRemoteSubframes
+ ? "http://another-tracking.example.net"
+ : "https://another-tracking.example.net";
+ // With another-tracking.example.net, we're same-eTLD+1, so the first try succeeds.
+ if (origin != TEST_ANOTHER_3RD_PARTY_ORIGIN) {
+ if (rejectTrackers) {
+ let p;
+ let threw = false;
+ try {
+ p = document.requestStorageAccess();
+ } catch (e) {
+ threw = true;
+ }
+ ok(!threw, "requestStorageAccess should not throw");
+ try {
+ if (callback) {
+ if (expectFail) {
+ await p.catch(_ => callback());
+ success = false;
+ } else {
+ await p.then(_ => callback());
+ }
+ } else {
+ await p;
+ }
+ } catch (e) {
+ success = false;
+ } finally {
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ }
+ ok(!success, "Should not have worked without user interaction");
+
+ await noStorageAccessInitially();
+
+ await interactWithTracker();
+
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ }
+ if (
+ effectiveCookieBehavior ==
+ SpecialPowers.Ci.nsICookieService.BEHAVIOR_ACCEPT &&
+ !isOnContentBlockingAllowList()
+ ) {
+ try {
+ if (callback) {
+ if (expectFail) {
+ await document.requestStorageAccess().catch(_ => callback());
+ success = false;
+ } else {
+ await document.requestStorageAccess().then(_ => callback());
+ }
+ } else {
+ await document.requestStorageAccess();
+ }
+ } catch (e) {
+ success = false;
+ } finally {
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ }
+ ok(success, "Should not have thrown");
+
+ await hasStorageAccessInitially();
+
+ await interactWithTracker();
+
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ }
+ }
+
+ let p;
+ let threw = false;
+ try {
+ p = document.requestStorageAccess();
+ } catch (e) {
+ threw = true;
+ }
+ let rejected = false;
+ try {
+ if (callback) {
+ if (expectFail) {
+ await p.catch(_ => callback());
+ rejected = true;
+ } else {
+ await p.then(_ => callback());
+ }
+ } else {
+ await p;
+ }
+ } catch (e) {
+ rejected = true;
+ } finally {
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ }
+
+ success = !threw && !rejected;
+ let hasAccess = await document.hasStorageAccess();
+ is(
+ hasAccess,
+ success,
+ "Should " + (success ? "" : "not ") + "have storage access now"
+ );
+ if (
+ success &&
+ rejectTrackers &&
+ window.location.search != "?disableWaitUntilPermission" &&
+ origin != TEST_ANOTHER_3RD_PARTY_ORIGIN
+ ) {
+ let protocol = isSecureContext ? "https" : "http";
+ // Wait until the permission is visible in parent process to avoid race
+ // conditions. We don't need to wait the permission to be visible in content
+ // processes since the content process doesn't rely on the permission to
+ // know the storage access is updated.
+ await waitUntilPermission(
+ `${protocol}://example.net/browser/toolkit/components/antitracking/test/browser/page.html`,
+ "3rdPartyStorage^" + window.origin
+ );
+ }
+
+ return [threw, rejected];
+}
+
+async function waitUntilPermission(
+ url,
+ name,
+ value = SpecialPowers.Services.perms.ALLOW_ACTION
+) {
+ let originAttributes = SpecialPowers.isContentWindowPrivate(window)
+ ? { privateBrowsingId: 1 }
+ : {};
+ await new Promise(resolve => {
+ let id = setInterval(async _ => {
+ if (
+ await SpecialPowers.testPermission(name, value, {
+ url,
+ originAttributes,
+ })
+ ) {
+ clearInterval(id);
+ resolve();
+ }
+ }, 0);
+ });
+}
+
+async function interactWithTracker() {
+ await new Promise(resolve => {
+ let orionmessage = onmessage;
+ onmessage = _ => {
+ onmessage = orionmessage;
+ resolve();
+ };
+
+ info("Let's interact with the tracker");
+ window.open(
+ "/browser/toolkit/components/antitracking/test/browser/3rdPartyOpenUI.html?messageme"
+ );
+ });
+
+ // Wait until the user interaction permission becomes visible in our process
+ await waitUntilPermission(window.origin, "storageAccessAPI");
+}
+
+function isOnContentBlockingAllowList() {
+ // We directly check the window.allowListed here instead of checking the
+ // permission. The allow list permission might not be available since it is
+ // not in the preload list.
+
+ return window.allowListed;
+}
+
+async function registerServiceWorker(win, url) {
+ let reg = await win.navigator.serviceWorker.register(url);
+ if (reg.installing.state !== "activated") {
+ await new Promise(resolve => {
+ let w = reg.installing;
+ w.addEventListener("statechange", function onStateChange() {
+ if (w.state === "activated") {
+ w.removeEventListener("statechange", onStateChange);
+ resolve();
+ }
+ });
+ });
+ }
+
+ return reg.active;
+}
+
+function sendAndWaitWorkerMessage(target, worker, message) {
+ return new Promise(resolve => {
+ worker.addEventListener(
+ "message",
+ msg => {
+ resolve(msg.data);
+ },
+ { once: true }
+ );
+
+ target.postMessage(message);
+ });
+}
diff --git a/toolkit/components/antitracking/test/browser/storage_access_head.js b/toolkit/components/antitracking/test/browser/storage_access_head.js
new file mode 100644
index 0000000000..274e128783
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/storage_access_head.js
@@ -0,0 +1,248 @@
+/* import-globals-from ../../../../../browser/modules/test/browser/head.js */
+/* import-globals-from antitracking_head.js */
+
+async function openPageAndRunCode(
+ topPage,
+ topPageCallback,
+ embeddedPage,
+ embeddedPageCallback
+) {
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: topPage,
+ waitForLoad: true,
+ });
+ let browser = gBrowser.getBrowserForTab(tab);
+
+ await topPageCallback();
+ await SpecialPowers.spawn(
+ browser,
+ [{ page: embeddedPage, callback: embeddedPageCallback.toString() }],
+ async function (obj) {
+ await new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.onload = function () {
+ ifr.contentWindow.postMessage(obj.callback, "*");
+ };
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ });
+ }
+ );
+
+ await BrowserTestUtils.removeTab(tab);
+}
+
+// This function returns a function that spawns an asynchronous task to handle
+// the popup and click on the appropriate values. If that task is never executed
+// the catch case is reached and we fail the test. If for some reason that catch
+// case isn't reached, having an extra event listener at the end of the test
+// will cause the test to fail anyway.
+// Note: this means that tests that use this callback should probably be in
+// their own test file.
+function getExpectPopupAndClick(accept) {
+ return function () {
+ let shownPromise = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popupshown"
+ );
+ shownPromise
+ .then(async _ => {
+ // This occurs when the promise resolves on the test finishing
+ let popupNotifications = PopupNotifications.panel.childNodes;
+ if (!popupNotifications.length) {
+ ok(false, "Prompt did not show up");
+ } else if (accept == "accept") {
+ ok(true, "Prompt shows up, clicking accept.");
+ await clickMainAction();
+ } else if (accept == "reject") {
+ ok(true, "Prompt shows up, clicking reject.");
+ await clickSecondaryAction();
+ } else {
+ ok(false, "Unknown accept value for test: " + accept);
+ info("Clicking accept so that the test can finish.");
+ await clickMainAction();
+ }
+ })
+ .catch(() => {
+ ok(false, "Prompt did not show up");
+ });
+ };
+}
+
+// This function spawns an asynchronous task that fails the test if a popup
+// appears. If that never happens, the catch case is executed on the test
+// cleanup.
+// Note: this means that tests that use this callback should probably be in
+// their own test file.
+function expectNoPopup() {
+ let shownPromise = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popupshown"
+ );
+ shownPromise
+ .then(async _ => {
+ // This occurs when the promise resolves on the test finishing
+ let popupNotifications = PopupNotifications.panel.childNodes;
+ if (!popupNotifications.length) {
+ ok(true, "Prompt did not show up");
+ } else {
+ ok(false, "Prompt shows up");
+ info(PopupNotifications.panel);
+ await clickSecondaryAction();
+ }
+ })
+ .catch(() => {
+ ok(true, "Prompt did not show up");
+ });
+}
+
+async function requestStorageAccessAndExpectSuccess() {
+ const aps = SpecialPowers.Services.prefs.getBoolPref(
+ "privacy.partition.always_partition_third_party_non_cookie_storage"
+ );
+
+ // When always partitioning storage, we do not clear non-cookie storage
+ // after a requestStorageAccess is accepted by the user. So here we test
+ // that indexedDB is cleared when the pref is off, but not when it is on.
+ await new Promise((resolve, reject) => {
+ const db = window.indexedDB.open("rSATest", 1);
+ db.onupgradeneeded = resolve;
+ db.success = resolve;
+ db.onerror = reject;
+ });
+
+ const hadAccessAlready = await document.hasStorageAccess();
+ const shouldClearIDB = !aps && !hadAccessAlready;
+
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ let p = document.requestStorageAccess();
+ try {
+ await p;
+ ok(true, "gain storage access.");
+ } catch {
+ ok(false, "denied storage access.");
+ }
+
+ await new Promise((resolve, reject) => {
+ const req = window.indexedDB.open("rSATest", 1);
+ req.onerror = reject;
+ req.onupgradeneeded = () => {
+ ok(shouldClearIDB, "iDB was cleared");
+ req.onsuccess = undefined;
+ resolve();
+ };
+ req.onsuccess = () => {
+ ok(!shouldClearIDB, "iDB was not cleared");
+ resolve();
+ };
+ });
+
+ await new Promise(resolve => {
+ const req = window.indexedDB.deleteDatabase("rSATest");
+ req.onsuccess = resolve;
+ req.onerror = resolve;
+ });
+
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+}
+
+async function requestStorageAccessAndExpectFailure() {
+ // When always partitioning storage, we do not clear non-cookie storage
+ // after a requestStorageAccess is accepted by the user. So here we test
+ // that indexedDB is cleared when the pref is off, but not when it is on.
+ await new Promise((resolve, reject) => {
+ const db = window.indexedDB.open("rSATest", 1);
+ db.onupgradeneeded = resolve;
+ db.success = resolve;
+ db.onerror = reject;
+ });
+
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ let p = document.requestStorageAccess();
+ try {
+ await p;
+ ok(false, "gain storage access.");
+ } catch {
+ ok(true, "denied storage access.");
+ }
+
+ await new Promise((resolve, reject) => {
+ const req = window.indexedDB.open("rSATest", 1);
+ req.onerror = reject;
+ req.onupgradeneeded = () => {
+ ok(false, "iDB was cleared");
+ req.onsuccess = undefined;
+ resolve();
+ };
+ req.onsuccess = () => {
+ ok(true, "iDB was not cleared");
+ resolve();
+ };
+ });
+
+ await new Promise(resolve => {
+ const req = window.indexedDB.deleteDatabase("rSATest");
+ req.onsuccess = resolve;
+ req.onerror = resolve;
+ });
+
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+}
+
+async function cleanUpData() {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ resolve()
+ );
+ });
+ ok(true, "Deleted all data.");
+}
+
+async function setPreferences(alwaysPartitionStorage = false) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.auto_grants", true],
+ ["dom.storage_access.auto_grants.delayed", false],
+ ["dom.storage_access.enabled", true],
+ ["dom.storage_access.max_concurrent_auto_grants", 0],
+ ["dom.storage_access.prompt.testing", false],
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ [
+ "network.cookie.cookieBehavior.pbmode",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ [
+ "privacy.partition.always_partition_third_party_non_cookie_storage",
+ alwaysPartitionStorage,
+ ],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ ],
+ });
+}
diff --git a/toolkit/components/antitracking/test/browser/subResources.sjs b/toolkit/components/antitracking/test/browser/subResources.sjs
new file mode 100644
index 0000000000..f40d5cac97
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/subResources.sjs
@@ -0,0 +1,33 @@
+// A 1x1 PNG image.
+// Source: https://commons.wikimedia.org/wiki/File:1x1.png (Public Domain)
+const IMAGE = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAA" +
+ "ACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII="
+);
+
+function handleRequest(aRequest, aResponse) {
+ aResponse.setStatusLine(aRequest.httpVersion, 200);
+
+ let key = aRequest.queryString.includes("what=script") ? "script" : "image";
+
+ if (aRequest.queryString.includes("result")) {
+ aResponse.write(getState(key) || 0);
+ setState(key, "0");
+ return;
+ }
+
+ if (aRequest.hasHeader("Cookie")) {
+ let hints = parseInt(getState(key) || 0) + 1;
+ setState(key, hints.toString());
+ }
+
+ aResponse.setHeader("Set-Cookie", "foopy=1");
+
+ if (key == "script") {
+ aResponse.setHeader("Content-Type", "text/javascript", false);
+ aResponse.write("42;");
+ } else {
+ aResponse.setHeader("Content-Type", "image/png", false);
+ aResponse.write(IMAGE);
+ }
+}
diff --git a/toolkit/components/antitracking/test/browser/tracker.js b/toolkit/components/antitracking/test/browser/tracker.js
new file mode 100644
index 0000000000..85e943f7c4
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/tracker.js
@@ -0,0 +1,7 @@
+window.addEventListener("message", e => {
+ let bc = new BroadcastChannel("a");
+ bc.postMessage("ready!");
+});
+window.open(
+ "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/3rdPartyOpen.html"
+);
diff --git a/toolkit/components/antitracking/test/browser/workerIframe.html b/toolkit/components/antitracking/test/browser/workerIframe.html
new file mode 100644
index 0000000000..37aa5d7c0d
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/workerIframe.html
@@ -0,0 +1,71 @@
+<html>
+<head>
+ <title>3rd party content!</title>
+ <script type="text/javascript" src="https://example.com/browser/toolkit/components/antitracking/test/browser/storageAccessAPIHelpers.js"></script>
+</head>
+<body>
+<h1>Here the 3rd party content!</h1>
+<script>
+
+function info(msg) {
+ parent.postMessage({ type: "info", msg }, "*");
+}
+
+function ok(what, msg) {
+ parent.postMessage({ type: "ok", what: !!what, msg }, "*");
+}
+
+function is(a, b, msg) {
+ ok(a === b, msg);
+}
+
+async function runTest() {
+ function workerCode() {
+ onmessage = e => {
+ try {
+ indexedDB.open("test", "1");
+ postMessage(true);
+ } catch (e) {
+ postMessage(false);
+ }
+ };
+ }
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await noStorageAccessInitially();
+ info("Initialized");
+
+ let blob = new Blob([workerCode.toString() + "; workerCode();"]);
+ let blobURL = URL.createObjectURL(blob);
+ info("Blob created");
+
+ let w = new Worker(blobURL);
+ info("Worker created");
+
+ await new Promise(resolve => {
+ w.addEventListener("message", e => {
+ ok(!e.data, "IDB is disabled");
+ resolve();
+ }, { once: true });
+ w.postMessage("go");
+ });
+
+ /* import-globals-from storageAccessAPIHelpers.js */
+ await callRequestStorageAccess();
+
+ await new Promise(resolve => {
+ w.addEventListener("message", e => {
+ ok(e.data, "IDB is enabled");
+ resolve();
+ }, { once: true });
+ w.postMessage("go");
+ });
+
+ parent.postMessage({ type: "finish" }, "*");
+}
+
+runTest();
+
+</script>
+</body>
+</html>
diff --git a/toolkit/components/antitracking/test/gtest/TestPartitioningExceptionList.cpp b/toolkit/components/antitracking/test/gtest/TestPartitioningExceptionList.cpp
new file mode 100644
index 0000000000..1aa0614622
--- /dev/null
+++ b/toolkit/components/antitracking/test/gtest/TestPartitioningExceptionList.cpp
@@ -0,0 +1,184 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/PartitioningExceptionList.h"
+
+using namespace mozilla;
+
+static const char kPrefPartitioningExceptionList[] =
+ "privacy.restrict3rdpartystorage.skip_list";
+
+static const char kPrefEnableWebcompat[] =
+ "privacy.antitracking.enableWebcompat";
+
+TEST(TestPartitioningExceptionList, TestPrefBasic)
+{
+ nsAutoCString oldPartitioningExceptionList;
+ Preferences::GetCString(kPrefEnableWebcompat, oldPartitioningExceptionList);
+ bool oldEnableWebcompat = Preferences::GetBool(kPrefEnableWebcompat);
+
+ for (uint32_t populateList = 0; populateList <= 1; populateList++) {
+ for (uint32_t enableWebcompat = 0; enableWebcompat <= 1;
+ enableWebcompat++) {
+ if (populateList) {
+ Preferences::SetCString(kPrefPartitioningExceptionList,
+ "https://example.com,https://example.net");
+ } else {
+ Preferences::SetCString(kPrefPartitioningExceptionList, "");
+ }
+
+ Preferences::SetBool(kPrefEnableWebcompat, enableWebcompat);
+
+ EXPECT_FALSE(
+ PartitioningExceptionList::Check(""_ns, "https://example.net"_ns));
+ EXPECT_FALSE(
+ PartitioningExceptionList::Check("https://example.com"_ns, ""_ns));
+ EXPECT_FALSE(PartitioningExceptionList::Check(""_ns, ""_ns));
+ EXPECT_FALSE(PartitioningExceptionList::Check("https://example.net"_ns,
+ "https://example.com"_ns));
+ EXPECT_FALSE(PartitioningExceptionList::Check("https://example.com"_ns,
+ "https://example.org"_ns));
+ EXPECT_FALSE(PartitioningExceptionList::Check("https://example.com"_ns,
+ "https://example.com"_ns));
+ EXPECT_FALSE(PartitioningExceptionList::Check("http://example.com"_ns,
+ "http://example.net"_ns));
+ EXPECT_FALSE(PartitioningExceptionList::Check("https://example.com"_ns,
+ "http://example.net"_ns));
+ EXPECT_FALSE(PartitioningExceptionList::Check("https://example.com."_ns,
+ "https://example.net"_ns));
+
+ bool result = PartitioningExceptionList::Check("https://example.com"_ns,
+ "https://example.net"_ns);
+ EXPECT_TRUE(result == (populateList && enableWebcompat));
+ }
+ }
+
+ Preferences::SetCString(kPrefPartitioningExceptionList,
+ oldPartitioningExceptionList);
+ Preferences::SetBool(kPrefEnableWebcompat, oldEnableWebcompat);
+}
+
+TEST(TestPartitioningExceptionList, TestPrefWildcard)
+{
+ nsAutoCString oldPartitioningExceptionList;
+ Preferences::GetCString(kPrefEnableWebcompat, oldPartitioningExceptionList);
+ bool oldEnableWebcompat = Preferences::GetBool(kPrefEnableWebcompat);
+
+ Preferences::SetCString(kPrefPartitioningExceptionList,
+ "https://example.com,https://example.net;"
+ "https://*.foo.com,https://bar.com;"
+ "https://*.foo.com,https://foobar.net;"
+ "https://test.net,https://*.example.com;"
+ "https://test.com,https://*.example.com;"
+ "https://*.test2.org,*;"
+ "*,http://notatracker.org");
+
+ Preferences::SetBool(kPrefEnableWebcompat, true);
+
+ EXPECT_TRUE(PartitioningExceptionList::Check("https://example.com"_ns,
+ "https://example.net"_ns));
+
+ EXPECT_TRUE(PartitioningExceptionList::Check("https://two.foo.com"_ns,
+ "https://bar.com"_ns));
+ EXPECT_TRUE(PartitioningExceptionList::Check("https://another.foo.com"_ns,
+ "https://bar.com"_ns));
+ EXPECT_TRUE(PartitioningExceptionList::Check("https://three.two.foo.com"_ns,
+ "https://bar.com"_ns));
+ EXPECT_FALSE(PartitioningExceptionList::Check("https://two.foo.com"_ns,
+ "https://example.com"_ns));
+ EXPECT_FALSE(PartitioningExceptionList::Check("https://foo.com"_ns,
+ "https://bar.com"_ns));
+ EXPECT_FALSE(PartitioningExceptionList::Check("https://two.foo.com"_ns,
+ "http://bar.com"_ns));
+ EXPECT_FALSE(PartitioningExceptionList::Check("http://two.foo.com"_ns,
+ "https://bar.com"_ns));
+
+ EXPECT_TRUE(PartitioningExceptionList::Check("https://a.foo.com"_ns,
+ "https://foobar.net"_ns));
+
+ EXPECT_TRUE(PartitioningExceptionList::Check("https://test.net"_ns,
+ "https://test.example.com"_ns));
+ EXPECT_TRUE(PartitioningExceptionList::Check(
+ "https://test.net"_ns, "https://foo.bar.example.com"_ns));
+ EXPECT_FALSE(PartitioningExceptionList::Check("https://test.com"_ns,
+ "https://foo.test.net"_ns));
+
+ EXPECT_TRUE(PartitioningExceptionList::Check("https://one.test2.org"_ns,
+ "https://example.net"_ns));
+ EXPECT_TRUE(PartitioningExceptionList::Check("https://two.test2.org"_ns,
+ "https://foo.example.net"_ns));
+ EXPECT_TRUE(PartitioningExceptionList::Check("https://three.test2.org"_ns,
+ "http://example.net"_ns));
+ EXPECT_TRUE(PartitioningExceptionList::Check("https://four.sub.test2.org"_ns,
+ "https://bar.com"_ns));
+ EXPECT_FALSE(PartitioningExceptionList::Check("https://four.sub.test2.com"_ns,
+ "https://bar.com"_ns));
+ EXPECT_FALSE(PartitioningExceptionList::Check("http://four.sub.test2.org"_ns,
+ "https://bar.com"_ns));
+ EXPECT_FALSE(PartitioningExceptionList::Check(
+ "https://four.sub.test2.org."_ns, "https://bar.com"_ns));
+
+ EXPECT_TRUE(PartitioningExceptionList::Check("https://example.com"_ns,
+ "http://notatracker.org"_ns));
+
+ Preferences::SetCString(kPrefPartitioningExceptionList,
+ oldPartitioningExceptionList);
+ Preferences::SetBool(kPrefEnableWebcompat, oldEnableWebcompat);
+}
+
+TEST(TestPartitioningExceptionList, TestInvalidEntries)
+{
+ nsAutoCString oldPartitioningExceptionList;
+ Preferences::GetCString(kPrefEnableWebcompat, oldPartitioningExceptionList);
+ bool oldEnableWebcompat = Preferences::GetBool(kPrefEnableWebcompat);
+
+ Preferences::SetBool(kPrefEnableWebcompat, true);
+
+ // Empty entries.
+ Preferences::SetCString(kPrefPartitioningExceptionList, ";;;,;");
+
+ EXPECT_FALSE(PartitioningExceptionList::Check("https://example.com"_ns,
+ "https://example.net"_ns));
+
+ // Schemeless entries.
+ Preferences::SetCString(kPrefPartitioningExceptionList,
+ "example.com,example.net");
+
+ EXPECT_FALSE(PartitioningExceptionList::Check("https://example.com"_ns,
+ "https://example.net"_ns));
+
+ // Invalid entry should be skipped and not break other entries.
+ Preferences::SetCString(kPrefPartitioningExceptionList,
+ "*,*;"
+ "https://example.com,https://example.net;"
+ "http://example.org,");
+
+ EXPECT_TRUE(PartitioningExceptionList::Check("https://example.com"_ns,
+ "https://example.net"_ns));
+ EXPECT_FALSE(PartitioningExceptionList::Check("https://foo.com"_ns,
+ "https://bar.net"_ns));
+
+ // Unsupported schemes should not be accepted.
+ Preferences::SetCString(kPrefPartitioningExceptionList,
+ "ftp://example.com,ftp://example.net;");
+
+ EXPECT_FALSE(PartitioningExceptionList::Check("https://example.com"_ns,
+ "https://example.net"_ns));
+ EXPECT_FALSE(PartitioningExceptionList::Check("ftp://example.com"_ns,
+ "ftp://example.net"_ns));
+
+ // Test invalid origins with trailing '/'.
+ Preferences::SetCString(kPrefPartitioningExceptionList,
+ "https://example.com/,https://example.net/");
+ EXPECT_FALSE(PartitioningExceptionList::Check("https://example.com"_ns,
+ "https://example.net"_ns));
+
+ Preferences::SetCString(kPrefPartitioningExceptionList,
+ oldPartitioningExceptionList);
+ Preferences::SetBool(kPrefEnableWebcompat, oldEnableWebcompat);
+}
diff --git a/toolkit/components/antitracking/test/gtest/TestStoragePrincipalHelper.cpp b/toolkit/components/antitracking/test/gtest/TestStoragePrincipalHelper.cpp
new file mode 100644
index 0000000000..44959e4dc8
--- /dev/null
+++ b/toolkit/components/antitracking/test/gtest/TestStoragePrincipalHelper.cpp
@@ -0,0 +1,215 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsIChannel.h"
+#include "nsIContentPolicy.h"
+#include "nsICookieJarSettings.h"
+#include "nsILoadInfo.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsStringFwd.h"
+
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/StoragePrincipalHelper.h"
+
+using mozilla::Preferences;
+using namespace mozilla;
+
+/**
+ * Creates a test channel with CookieJarSettings which have a partitionKey set.
+ */
+nsresult CreateMockChannel(nsIPrincipal* aPrincipal, bool isThirdParty,
+ const nsACString& aPartitionKey,
+ nsIChannel** aChannel,
+ nsICookieJarSettings** aCookieJarSettings) {
+ nsCOMPtr<nsIURI> mockUri;
+ nsresult rv = NS_NewURI(getter_AddRefs(mockUri), "http://example.com"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> mockChannel;
+ nsCOMPtr<nsIIOService> service = do_GetIOService(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = service->NewChannelFromURI(mockUri, nullptr, aPrincipal, aPrincipal, 0,
+ nsContentPolicyType::TYPE_OTHER,
+ getter_AddRefs(mockChannel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILoadInfo> mockLoadInfo = mockChannel->LoadInfo();
+ rv = mockLoadInfo->SetIsThirdPartyContextToTopWindow(isThirdParty);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsICookieJarSettings> cjs;
+ rv = mockLoadInfo->GetCookieJarSettings(getter_AddRefs(cjs));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> partitionKeyUri;
+ rv = NS_NewURI(getter_AddRefs(partitionKeyUri), aPartitionKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cjs->InitWithURI(partitionKeyUri, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ cjs.forget(aCookieJarSettings);
+ mockChannel.forget(aChannel);
+ return NS_OK;
+}
+
+TEST(TestStoragePrincipalHelper, TestCreateContentPrincipal)
+{
+ nsCOMPtr<nsIPrincipal> contentPrincipal =
+ BasePrincipal::CreateContentPrincipal("https://example.com"_ns);
+ EXPECT_TRUE(contentPrincipal);
+
+ nsCOMPtr<nsIChannel> mockChannel;
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ nsresult rv = CreateMockChannel(
+ contentPrincipal, false, "https://example.org"_ns,
+ getter_AddRefs(mockChannel), getter_AddRefs(cookieJarSettings));
+ ASSERT_EQ(rv, NS_OK) << "Could not create a mock channel";
+
+ nsCOMPtr<nsIPrincipal> storagePrincipal;
+ rv = StoragePrincipalHelper::Create(mockChannel, contentPrincipal, true,
+ getter_AddRefs(storagePrincipal));
+ ASSERT_EQ(rv, NS_OK) << "Should not fail for ContentPrincipal";
+ EXPECT_TRUE(storagePrincipal);
+
+ nsCOMPtr<nsIPrincipal> storagePrincipalSW;
+ rv = StoragePrincipalHelper::CreatePartitionedPrincipalForServiceWorker(
+ contentPrincipal, cookieJarSettings, getter_AddRefs(storagePrincipalSW));
+ ASSERT_EQ(rv, NS_OK) << "Should not fail for ContentPrincipal";
+ EXPECT_TRUE(storagePrincipalSW);
+}
+
+TEST(TestStoragePrincipalHelper, TestCreateNullPrincipal)
+{
+ RefPtr<NullPrincipal> nullPrincipal =
+ NullPrincipal::CreateWithoutOriginAttributes();
+ EXPECT_TRUE(nullPrincipal);
+
+ nsCOMPtr<nsIChannel> mockChannel;
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ nsresult rv = CreateMockChannel(
+ nullPrincipal, false, "https://example.org"_ns,
+ getter_AddRefs(mockChannel), getter_AddRefs(cookieJarSettings));
+ ASSERT_EQ(rv, NS_OK) << "Could not create a mock channel";
+
+ nsCOMPtr<nsIPrincipal> storagePrincipal;
+ rv = StoragePrincipalHelper::Create(mockChannel, nullPrincipal, true,
+ getter_AddRefs(storagePrincipal));
+ EXPECT_NS_FAILED(rv) << "Should fail for NullPrincipal";
+ EXPECT_FALSE(storagePrincipal);
+
+ nsCOMPtr<nsIPrincipal> storagePrincipalSW;
+ rv = StoragePrincipalHelper::CreatePartitionedPrincipalForServiceWorker(
+ nullPrincipal, cookieJarSettings, getter_AddRefs(storagePrincipalSW));
+ EXPECT_NS_FAILED(rv) << "Should fail for NullPrincipal";
+ EXPECT_FALSE(storagePrincipal);
+}
+
+TEST(TestStoragePrincipalHelper, TestGetPrincipalCookieBehavior4)
+{
+ Preferences::SetInt("network.cookie.cookieBehavior", 4);
+
+ nsCOMPtr<nsIPrincipal> contentPrincipal =
+ BasePrincipal::CreateContentPrincipal("https://example.com"_ns);
+ EXPECT_TRUE(contentPrincipal);
+
+ for (auto isThirdParty : {false, true}) {
+ nsCOMPtr<nsIChannel> mockChannel;
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ nsresult rv = CreateMockChannel(
+ contentPrincipal, isThirdParty, "https://example.org"_ns,
+ getter_AddRefs(mockChannel), getter_AddRefs(cookieJarSettings));
+ ASSERT_EQ(rv, NS_OK) << "Could not create a mock channel";
+
+ nsCOMPtr<nsIPrincipal> testPrincipal;
+ rv = StoragePrincipalHelper::GetPrincipal(
+ mockChannel, StoragePrincipalHelper::eRegularPrincipal,
+ getter_AddRefs(testPrincipal));
+ ASSERT_EQ(rv, NS_OK) << "Could not get regular principal";
+ EXPECT_TRUE(testPrincipal);
+ EXPECT_TRUE(testPrincipal->OriginAttributesRef().mPartitionKey.IsEmpty());
+
+ rv = StoragePrincipalHelper::GetPrincipal(
+ mockChannel, StoragePrincipalHelper::ePartitionedPrincipal,
+ getter_AddRefs(testPrincipal));
+ ASSERT_EQ(rv, NS_OK) << "Could not get partitioned principal";
+ EXPECT_TRUE(testPrincipal);
+ EXPECT_TRUE(
+ testPrincipal->OriginAttributesRef().mPartitionKey.EqualsLiteral(
+ "(https,example.org)"));
+
+ // We should always get regular principal if the dFPI is disabled.
+ rv = StoragePrincipalHelper::GetPrincipal(
+ mockChannel, StoragePrincipalHelper::eForeignPartitionedPrincipal,
+ getter_AddRefs(testPrincipal));
+ ASSERT_EQ(rv, NS_OK) << "Could not get foreign partitioned principal";
+ EXPECT_TRUE(testPrincipal);
+ EXPECT_TRUE(testPrincipal->OriginAttributesRef().mPartitionKey.IsEmpty());
+
+ // Note that we don't test eStorageAccessPrincipal here because it's hard to
+ // setup the right state for the storage access in gTest.
+ }
+}
+
+TEST(TestStoragePrincipalHelper, TestGetPrincipalCookieBehavior5)
+{
+ Preferences::SetInt("network.cookie.cookieBehavior", 5);
+
+ nsCOMPtr<nsIPrincipal> contentPrincipal =
+ BasePrincipal::CreateContentPrincipal("https://example.com"_ns);
+ EXPECT_TRUE(contentPrincipal);
+
+ for (auto isThirdParty : {false, true}) {
+ nsCOMPtr<nsIChannel> mockChannel;
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ nsresult rv = CreateMockChannel(
+ contentPrincipal, isThirdParty, "https://example.org"_ns,
+ getter_AddRefs(mockChannel), getter_AddRefs(cookieJarSettings));
+ ASSERT_EQ(rv, NS_OK) << "Could not create a mock channel";
+
+ nsCOMPtr<nsIPrincipal> testPrincipal;
+ rv = StoragePrincipalHelper::GetPrincipal(
+ mockChannel, StoragePrincipalHelper::eRegularPrincipal,
+ getter_AddRefs(testPrincipal));
+ ASSERT_EQ(rv, NS_OK) << "Could not get regular principal";
+ EXPECT_TRUE(testPrincipal);
+ EXPECT_TRUE(testPrincipal->OriginAttributesRef().mPartitionKey.IsEmpty());
+
+ rv = StoragePrincipalHelper::GetPrincipal(
+ mockChannel, StoragePrincipalHelper::ePartitionedPrincipal,
+ getter_AddRefs(testPrincipal));
+ ASSERT_EQ(rv, NS_OK) << "Could not get partitioned principal";
+ EXPECT_TRUE(testPrincipal);
+ EXPECT_TRUE(
+ testPrincipal->OriginAttributesRef().mPartitionKey.EqualsLiteral(
+ "(https,example.org)"));
+
+ // We should always get regular principal if the dFPI is disabled.
+ rv = StoragePrincipalHelper::GetPrincipal(
+ mockChannel, StoragePrincipalHelper::eForeignPartitionedPrincipal,
+ getter_AddRefs(testPrincipal));
+ ASSERT_EQ(rv, NS_OK) << "Could not get foreign partitioned principal";
+ EXPECT_TRUE(testPrincipal);
+ if (isThirdParty) {
+ EXPECT_TRUE(
+ testPrincipal->OriginAttributesRef().mPartitionKey.EqualsLiteral(
+ "(https,example.org)"));
+ } else {
+ EXPECT_TRUE(testPrincipal->OriginAttributesRef().mPartitionKey.IsEmpty());
+ }
+
+ // Note that we don't test eStorageAccessPrincipal here because it's hard to
+ // setup the right state for the storage access in gTest.
+ }
+}
diff --git a/toolkit/components/antitracking/test/gtest/TestURLQueryStringStripper.cpp b/toolkit/components/antitracking/test/gtest/TestURLQueryStringStripper.cpp
new file mode 100644
index 0000000000..3f77479f74
--- /dev/null
+++ b/toolkit/components/antitracking/test/gtest/TestURLQueryStringStripper.cpp
@@ -0,0 +1,176 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+
+#include "mozilla/Components.h"
+#include "nsIURLQueryStringStripper.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsStringFwd.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/URLQueryStringStripper.h"
+
+using namespace mozilla;
+
+static const char kPrefQueryStrippingEnabled[] =
+ "privacy.query_stripping.enabled";
+static const char kPrefQueryStrippingEnabledPBM[] =
+ "privacy.query_stripping.enabled.pbmode";
+static const char kPrefQueryStrippingList[] =
+ "privacy.query_stripping.strip_list";
+
+/**
+ * Waits for the strip list in the URLQueryStringStripper to match aExpected.
+ */
+void waitForStripListChange(const nsACString& aExpected) {
+ nsresult rv;
+ nsCOMPtr<nsIURLQueryStringStripper> queryStripper =
+ components::URLQueryStringStripper::Service(&rv);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ MOZ_ALWAYS_TRUE(mozilla::SpinEventLoopUntil(
+ "TestURLQueryStringStripper waitForStripListChange"_ns, [&]() -> bool {
+ nsAutoCString stripList;
+ rv = queryStripper->TestGetStripList(stripList);
+ return NS_SUCCEEDED(rv) && stripList.Equals(aExpected);
+ }));
+}
+
+void DoTest(const nsACString& aTestURL, const bool aIsPBM,
+ const nsACString& aExpectedURL, uint32_t aExpectedResult) {
+ nsCOMPtr<nsIURI> testURI;
+
+ NS_NewURI(getter_AddRefs(testURI), aTestURL);
+
+ nsresult rv;
+ nsCOMPtr<nsIURLQueryStringStripper> queryStripper =
+ components::URLQueryStringStripper::Service(&rv);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsIURI> strippedURI;
+ uint32_t numStripped;
+ rv = queryStripper->Strip(testURI, aIsPBM, getter_AddRefs(strippedURI),
+ &numStripped);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ EXPECT_TRUE(numStripped == aExpectedResult);
+
+ if (!numStripped) {
+ EXPECT_TRUE(!strippedURI);
+ } else {
+ EXPECT_TRUE(strippedURI->GetSpecOrDefault().Equals(aExpectedURL));
+ }
+}
+
+TEST(TestURLQueryStringStripper, TestPrefDisabled)
+{
+ // Disable the query string stripping by the pref and make sure the stripping
+ // is disabled.
+ // Note that we don't need to run a dummy test to create the
+ // URLQueryStringStripper here because the stripper will never be created if
+ // the query stripping is disabled.
+ Preferences::SetCString(kPrefQueryStrippingList, "fooBar foobaz");
+ Preferences::SetBool(kPrefQueryStrippingEnabled, false);
+ Preferences::SetBool(kPrefQueryStrippingEnabledPBM, false);
+
+ for (bool isPBM : {false, true}) {
+ DoTest("https://example.com/"_ns, isPBM, ""_ns, 0);
+ DoTest("https://example.com/?Barfoo=123"_ns, isPBM, ""_ns, 0);
+ DoTest("https://example.com/?fooBar=123&foobaz"_ns, isPBM, ""_ns, 0);
+ }
+}
+
+TEST(TestURLQueryStringStripper, TestEmptyStripList)
+{
+ // Make sure there is no error if the strip list is empty.
+ Preferences::SetBool(kPrefQueryStrippingEnabled, true);
+ Preferences::SetBool(kPrefQueryStrippingEnabledPBM, true);
+
+ // To create the URLQueryStringStripper, we need to run a dummy test after
+ // the query stripping is enabled. By doing this, the stripper will be
+ // initiated and we are good to test.
+ DoTest("https://example.com/"_ns, false, ""_ns, 0);
+
+ // Set the strip list to empty and wait until the pref setting is set to the
+ // stripper.
+ Preferences::SetCString(kPrefQueryStrippingList, "");
+
+ waitForStripListChange(""_ns);
+
+ for (bool isPBM : {false, true}) {
+ DoTest("https://example.com/"_ns, isPBM, ""_ns, 0);
+ DoTest("https://example.com/?Barfoo=123"_ns, isPBM, ""_ns, 0);
+ DoTest("https://example.com/?fooBar=123&foobaz"_ns, isPBM, ""_ns, 0);
+ }
+}
+
+TEST(TestURLQueryStringStripper, TestStripping)
+{
+ Preferences::SetBool(kPrefQueryStrippingEnabled, true);
+ Preferences::SetBool(kPrefQueryStrippingEnabledPBM, true);
+ DoTest("https://example.com/"_ns, false, ""_ns, 0);
+
+ Preferences::SetCString(kPrefQueryStrippingList, "fooBar foobaz");
+ waitForStripListChange("foobar foobaz"_ns);
+
+ // Test all pref combinations.
+ for (bool pref : {false, true}) {
+ for (bool prefPBM : {false, true}) {
+ Preferences::SetBool(kPrefQueryStrippingEnabled, pref);
+ Preferences::SetBool(kPrefQueryStrippingEnabledPBM, prefPBM);
+
+ // If the service is enabled with the given pref config we need for the
+ // list changes to propagate as they happen async.
+ if (pref || prefPBM) {
+ waitForStripListChange("foobar foobaz"_ns);
+ }
+
+ // Test with normal and private browsing mode.
+ for (bool isPBM : {false, true}) {
+ bool expectStrip = (prefPBM && isPBM) || (pref && !isPBM);
+
+ DoTest("https://example.com/"_ns, isPBM, ""_ns, 0);
+ DoTest("https://example.com/?Barfoo=123"_ns, isPBM, ""_ns, 0);
+
+ DoTest("https://example.com/?fooBar=123"_ns, isPBM,
+ "https://example.com/"_ns, expectStrip ? 1 : 0);
+ DoTest("https://example.com/?fooBar=123&foobaz"_ns, isPBM,
+ "https://example.com/"_ns, expectStrip ? 2 : 0);
+ DoTest("https://example.com/?fooBar=123&Barfoo=456&foobaz"_ns, isPBM,
+ "https://example.com/?Barfoo=456"_ns, expectStrip ? 2 : 0);
+
+ DoTest("https://example.com/?FOOBAR=123"_ns, isPBM,
+ "https://example.com/"_ns, expectStrip ? 1 : 0);
+ DoTest("https://example.com/?barfoo=foobar"_ns, isPBM,
+ "https://example.com/?barfoo=foobar"_ns, 0);
+ DoTest("https://example.com/?foobar=123&nostrip=456&FooBar=789"_ns,
+ isPBM, "https://example.com/?nostrip=456"_ns,
+ expectStrip ? 2 : 0);
+ DoTest("https://example.com/?AfoobazB=123"_ns, isPBM,
+ "https://example.com/?AfoobazB=123"_ns, 0);
+ }
+ }
+ }
+
+ // Change the strip list pref to see if it is updated properly.
+ // We test this in normal browsing, so set the prefs accordingly.
+ Preferences::SetBool(kPrefQueryStrippingEnabled, true);
+ Preferences::SetBool(kPrefQueryStrippingEnabledPBM, false);
+
+ Preferences::SetCString(kPrefQueryStrippingList, "Barfoo bazfoo");
+
+ waitForStripListChange("barfoo bazfoo"_ns);
+
+ DoTest("https://example.com/?fooBar=123"_ns, false, ""_ns, 0);
+ DoTest("https://example.com/?fooBar=123&foobaz"_ns, false, ""_ns, 0);
+
+ DoTest("https://example.com/?bazfoo=123"_ns, false, "https://example.com/"_ns,
+ 1);
+ DoTest("https://example.com/?fooBar=123&Barfoo=456&foobaz=abc"_ns, false,
+ "https://example.com/?fooBar=123&foobaz=abc"_ns, 1);
+}
diff --git a/toolkit/components/antitracking/test/gtest/moz.build b/toolkit/components/antitracking/test/gtest/moz.build
new file mode 100644
index 0000000000..a7e8595c5e
--- /dev/null
+++ b/toolkit/components/antitracking/test/gtest/moz.build
@@ -0,0 +1,19 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+UNIFIED_SOURCES += [
+ "TestPartitioningExceptionList.cpp",
+ "TestStoragePrincipalHelper.cpp",
+ "TestURLQueryStringStripper.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/xpcom/tests/gtest",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/toolkit/components/antitracking/test/xpcshell/data/font.woff b/toolkit/components/antitracking/test/xpcshell/data/font.woff
new file mode 100644
index 0000000000..acda4f3d9f
--- /dev/null
+++ b/toolkit/components/antitracking/test/xpcshell/data/font.woff
Binary files differ
diff --git a/toolkit/components/antitracking/test/xpcshell/head.js b/toolkit/components/antitracking/test/xpcshell/head.js
new file mode 100644
index 0000000000..f9bf797641
--- /dev/null
+++ b/toolkit/components/antitracking/test/xpcshell/head.js
@@ -0,0 +1,11 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* import-globals-from ../../../../components/url-classifier/tests/unit/head_urlclassifier.js */
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
diff --git a/toolkit/components/antitracking/test/xpcshell/test_ExceptionListService.js b/toolkit/components/antitracking/test/xpcshell/test_ExceptionListService.js
new file mode 100644
index 0000000000..4063d067f5
--- /dev/null
+++ b/toolkit/components/antitracking/test/xpcshell/test_ExceptionListService.js
@@ -0,0 +1,107 @@
+// This test ensures that the URL decoration annotations service works as
+// expected, and also we successfully downgrade document.referrer to the
+// eTLD+1 URL when tracking identifiers controlled by this service are
+// present in the referrer URI.
+
+"use strict";
+
+/* Unit tests for the nsIPartitioningExceptionListService implementation. */
+
+const { RemoteSettings } = ChromeUtils.importESModule(
+ "resource://services-settings/remote-settings.sys.mjs"
+);
+
+const COLLECTION_NAME = "partitioning-exempt-urls";
+const PREF_NAME = "privacy.restrict3rdpartystorage.skip_list";
+
+do_get_profile();
+
+class UpdateEvent extends EventTarget {}
+function waitForEvent(element, eventName) {
+ return new Promise(function (resolve) {
+ element.addEventListener(eventName, e => resolve(e.detail), { once: true });
+ });
+}
+
+add_task(async _ => {
+ let peuService = Cc[
+ "@mozilla.org/partitioning/exception-list-service;1"
+ ].getService(Ci.nsIPartitioningExceptionListService);
+
+ // Make sure we have a pref initially, since the exception list service
+ // requires it.
+ Services.prefs.setStringPref(PREF_NAME, "");
+
+ let updateEvent = new UpdateEvent();
+ let records = [
+ {
+ id: "1",
+ last_modified: 1000000000000001,
+ firstPartyOrigin: "https://example.org",
+ thirdPartyOrigin: "https://tracking.example.com",
+ },
+ ];
+
+ // Add some initial data
+ let db = RemoteSettings(COLLECTION_NAME).db;
+ await db.importChanges({}, Date.now(), records);
+
+ let promise = waitForEvent(updateEvent, "update");
+ let obs = data => {
+ let event = new CustomEvent("update", { detail: data });
+ updateEvent.dispatchEvent(event);
+ };
+ peuService.registerAndRunExceptionListObserver(obs);
+ let list = await promise;
+ Assert.equal(list, "", "No items in the list");
+
+ // Second event is from the RemoteSettings record.
+ list = await waitForEvent(updateEvent, "update");
+ Assert.equal(
+ list,
+ "https://example.org,https://tracking.example.com",
+ "Has one item in the list"
+ );
+
+ records.push({
+ id: "2",
+ last_modified: 1000000000000002,
+ firstPartyOrigin: "https://foo.org",
+ thirdPartyOrigin: "https://bar.com",
+ });
+
+ promise = waitForEvent(updateEvent, "update");
+ await RemoteSettings(COLLECTION_NAME).emit("sync", {
+ data: { current: records },
+ });
+ list = await promise;
+ Assert.equal(
+ list,
+ "https://example.org,https://tracking.example.com;https://foo.org,https://bar.com",
+ "Has several items in the list"
+ );
+
+ promise = waitForEvent(updateEvent, "update");
+ Services.prefs.setStringPref(PREF_NAME, "https://test.com,https://test3.com");
+ list = await promise;
+ Assert.equal(
+ list,
+ "https://test.com,https://test3.com;https://example.org,https://tracking.example.com;https://foo.org,https://bar.com",
+ "Has several items in the list"
+ );
+
+ promise = waitForEvent(updateEvent, "update");
+ Services.prefs.setStringPref(
+ PREF_NAME,
+ "https://test.com,https://test3.com;https://abc.com,https://def.com"
+ );
+ list = await promise;
+ Assert.equal(
+ list,
+ "https://test.com,https://test3.com;https://abc.com,https://def.com;https://example.org,https://tracking.example.com;https://foo.org,https://bar.com",
+ "Has several items in the list"
+ );
+
+ peuService.unregisterExceptionListObserver(obs);
+ await db.clear();
+});
diff --git a/toolkit/components/antitracking/test/xpcshell/test_cookie_behavior.js b/toolkit/components/antitracking/test/xpcshell/test_cookie_behavior.js
new file mode 100644
index 0000000000..3ce1f8bfb7
--- /dev/null
+++ b/toolkit/components/antitracking/test/xpcshell/test_cookie_behavior.js
@@ -0,0 +1,94 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Note: This test may cause intermittents if run at exactly midnight.
+
+"use strict";
+
+const PREF_FPI = "privacy.firstparty.isolate";
+const PREF_COOKIE_BEHAVIOR = "network.cookie.cookieBehavior";
+const PREF_COOKIE_BEHAVIOR_PBMODE = "network.cookie.cookieBehavior.pbmode";
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(PREF_FPI);
+ Services.prefs.clearUserPref(PREF_COOKIE_BEHAVIOR);
+ Services.prefs.clearUserPref(PREF_COOKIE_BEHAVIOR_PBMODE);
+});
+
+add_task(function test_FPI_off() {
+ Services.prefs.setBoolPref(PREF_FPI, false);
+
+ for (let i = 0; i <= Ci.nsICookieService.BEHAVIOR_LAST; ++i) {
+ Services.prefs.setIntPref(PREF_COOKIE_BEHAVIOR, i);
+ equal(Services.prefs.getIntPref(PREF_COOKIE_BEHAVIOR), i);
+ equal(Services.cookies.getCookieBehavior(false), i);
+ }
+
+ Services.prefs.clearUserPref(PREF_COOKIE_BEHAVIOR);
+
+ for (let i = 0; i <= Ci.nsICookieService.BEHAVIOR_LAST; ++i) {
+ Services.prefs.setIntPref(PREF_COOKIE_BEHAVIOR_PBMODE, i);
+ equal(Services.prefs.getIntPref(PREF_COOKIE_BEHAVIOR_PBMODE), i);
+ equal(Services.cookies.getCookieBehavior(true), i);
+ }
+});
+
+add_task(function test_FPI_on() {
+ Services.prefs.setBoolPref(PREF_FPI, true);
+
+ for (let i = 0; i <= Ci.nsICookieService.BEHAVIOR_LAST; ++i) {
+ Services.prefs.setIntPref(PREF_COOKIE_BEHAVIOR, i);
+ equal(Services.prefs.getIntPref(PREF_COOKIE_BEHAVIOR), i);
+ equal(
+ Services.cookies.getCookieBehavior(false),
+ i == Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN
+ ? Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER
+ : i
+ );
+ }
+
+ Services.prefs.clearUserPref(PREF_COOKIE_BEHAVIOR);
+
+ for (let i = 0; i <= Ci.nsICookieService.BEHAVIOR_LAST; ++i) {
+ Services.prefs.setIntPref(PREF_COOKIE_BEHAVIOR_PBMODE, i);
+ equal(Services.prefs.getIntPref(PREF_COOKIE_BEHAVIOR_PBMODE), i);
+ equal(
+ Services.cookies.getCookieBehavior(true),
+ i == Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN
+ ? Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER
+ : i
+ );
+ }
+
+ Services.prefs.clearUserPref(PREF_FPI);
+});
+
+add_task(function test_private_cookieBehavior_mirroring() {
+ // Test that the private cookieBehavior getter will return the regular pref if
+ // the regular pref has a user value and the private pref has a default value.
+ Services.prefs.clearUserPref(PREF_COOKIE_BEHAVIOR_PBMODE);
+ for (let i = 0; i <= Ci.nsICookieService.BEHAVIOR_LAST; ++i) {
+ Services.prefs.setIntPref(PREF_COOKIE_BEHAVIOR, i);
+ if (!Services.prefs.prefHasUserValue(PREF_COOKIE_BEHAVIOR)) {
+ continue;
+ }
+
+ equal(Services.cookies.getCookieBehavior(true), i);
+ }
+
+ // Test that the private cookieBehavior getter will always return the private
+ // pref if the private cookieBehavior has a user value.
+ for (let i = 0; i <= Ci.nsICookieService.BEHAVIOR_LAST; ++i) {
+ Services.prefs.setIntPref(PREF_COOKIE_BEHAVIOR_PBMODE, i);
+ if (!Services.prefs.prefHasUserValue(PREF_COOKIE_BEHAVIOR_PBMODE)) {
+ continue;
+ }
+
+ for (let j = 0; j <= Ci.nsICookieService.BEHAVIOR_LAST; ++j) {
+ Services.prefs.setIntPref(PREF_COOKIE_BEHAVIOR, j);
+
+ equal(Services.cookies.getCookieBehavior(true), i);
+ }
+ }
+});
diff --git a/toolkit/components/antitracking/test/xpcshell/test_getPartitionKeyFromURL.js b/toolkit/components/antitracking/test/xpcshell/test_getPartitionKeyFromURL.js
new file mode 100644
index 0000000000..cdf2cdec2c
--- /dev/null
+++ b/toolkit/components/antitracking/test/xpcshell/test_getPartitionKeyFromURL.js
@@ -0,0 +1,223 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { CookieXPCShellUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/CookieXPCShellUtils.sys.mjs"
+);
+
+CookieXPCShellUtils.init(this);
+
+const TEST_CASES = [
+ // Tests for different schemes.
+ {
+ url: "http://example.com/",
+ partitionKeySite: "(http,example.com)",
+ partitionKeyWithoutSite: "example.com",
+ },
+ {
+ url: "https://example.com/",
+ partitionKeySite: "(https,example.com)",
+ partitionKeyWithoutSite: "example.com",
+ },
+ // Tests for sub domains
+ {
+ url: "http://sub.example.com/",
+ partitionKeySite: "(http,example.com)",
+ partitionKeyWithoutSite: "example.com",
+ },
+ {
+ url: "http://sub.sub.example.com/",
+ partitionKeySite: "(http,example.com)",
+ partitionKeyWithoutSite: "example.com",
+ },
+ // Tests for path and query.
+ {
+ url: "http://www.example.com/path/to/somewhere/",
+ partitionKeySite: "(http,example.com)",
+ partitionKeyWithoutSite: "example.com",
+ },
+ {
+ url: "http://www.example.com/?query=string",
+ partitionKeySite: "(http,example.com)",
+ partitionKeyWithoutSite: "example.com",
+ },
+ // Tests for other ports.
+ {
+ url: "http://example.com:8080/",
+ partitionKeySite: "(http,example.com)",
+ partitionKeyWithoutSite: "example.com",
+ },
+ {
+ url: "https://example.com:8080/",
+ partitionKeySite: "(https,example.com)",
+ partitionKeyWithoutSite: "example.com",
+ },
+ // Tests for about urls
+ {
+ url: "about:about",
+ partitionKeySite:
+ "(about,about.ef2a7dd5-93bc-417f-a698-142c3116864f.mozilla)",
+ partitionKeyWithoutSite:
+ "about.ef2a7dd5-93bc-417f-a698-142c3116864f.mozilla",
+ },
+ {
+ url: "about:preferences",
+ partitionKeySite:
+ "(about,about.ef2a7dd5-93bc-417f-a698-142c3116864f.mozilla)",
+ partitionKeyWithoutSite:
+ "about.ef2a7dd5-93bc-417f-a698-142c3116864f.mozilla",
+ },
+ // Test for ip addresses
+ {
+ url: "http://127.0.0.1/",
+ partitionKeySite: "(http,127.0.0.1)",
+ partitionKeyWithoutSite: "127.0.0.1",
+ },
+ {
+ url: "http://127.0.0.1:8080/",
+ partitionKeySite: "(http,127.0.0.1,8080)",
+ partitionKeyWithoutSite: "127.0.0.1",
+ },
+ {
+ url: "http://[2001:db8::ff00:42:8329]",
+ partitionKeySite: "(http,[2001:db8::ff00:42:8329])",
+ partitionKeyWithoutSite: "[2001:db8::ff00:42:8329]",
+ },
+ {
+ url: "http://[2001:db8::ff00:42:8329]:8080",
+ partitionKeySite: "(http,[2001:db8::ff00:42:8329],8080)",
+ partitionKeyWithoutSite: "[2001:db8::ff00:42:8329]",
+ },
+ // Tests for moz-extension
+ {
+ url: "moz-extension://bafa4a3f-5c49-48d6-9788-03489419b70e",
+ partitionKeySite: "",
+ partitionKeyWithoutSite: "",
+ },
+ // Tests for non tld
+ {
+ url: "http://notld",
+ partitionKeySite: "(http,notld)",
+ partitionKeyWithoutSite: "notld",
+ },
+ {
+ url: "http://com",
+ partitionKeySite: "(http,com)",
+ partitionKeyWithoutSite: "com",
+ },
+ {
+ url: "http://com:8080",
+ partitionKeySite: "(http,com,8080)",
+ partitionKeyWithoutSite: "com",
+ },
+];
+
+const TEST_INVALID_URLS = [
+ "",
+ "/foo",
+ "An invalid URL",
+ "https://",
+ "http:///",
+ "http://foo:bar",
+];
+
+add_task(async function test_get_partition_key_from_url() {
+ for (const test of TEST_CASES) {
+ info(`Testing url: ${test.url}`);
+ let partitionKey = ChromeUtils.getPartitionKeyFromURL(test.url);
+
+ Assert.equal(
+ partitionKey,
+ test.partitionKeySite,
+ "The partitionKey is correct."
+ );
+ }
+});
+
+add_task(async function test_get_partition_key_from_url_without_site() {
+ Services.prefs.setBoolPref("privacy.dynamic_firstparty.use_site", false);
+
+ for (const test of TEST_CASES) {
+ info(`Testing url: ${test.url}`);
+ let partitionKey = ChromeUtils.getPartitionKeyFromURL(test.url);
+
+ Assert.equal(
+ partitionKey,
+ test.partitionKeyWithoutSite,
+ "The partitionKey is correct."
+ );
+ }
+
+ Services.prefs.clearUserPref("privacy.dynamic_firstparty.use_site");
+});
+
+add_task(async function test_blob_url() {
+ do_get_profile();
+
+ const server = CookieXPCShellUtils.createServer({
+ hosts: ["example.org", "foo.com"],
+ });
+
+ server.registerPathHandler("/empty", (metadata, response) => {
+ var body = "<h1>Hello!</h1>";
+ response.write(body);
+ });
+
+ server.registerPathHandler("/iframe", (metadata, response) => {
+ var body = `
+ <script>
+ var blobUrl = URL.createObjectURL(new Blob([]));
+ parent.postMessage(blobUrl, "http://example.org");
+ </script>
+ `;
+ response.write(body);
+ });
+
+ let contentPage = await CookieXPCShellUtils.loadContentPage(
+ "http://example.org/empty"
+ );
+
+ let blobUrl = await contentPage.spawn([], async () => {
+ // Create a third-party iframe and create a blob url in there.
+ let f = this.content.document.createElement("iframe");
+ f.src = "http://foo.com/iframe";
+
+ let blob_url = await new Promise(resolve => {
+ this.content.addEventListener("message", event => resolve(event.data), {
+ once: true,
+ });
+ this.content.document.body.append(f);
+ });
+
+ return blob_url;
+ });
+
+ let partitionKey = ChromeUtils.getPartitionKeyFromURL(blobUrl);
+
+ // The partitionKey of the blob url is empty because the principal of the
+ // blob url is the JS principal of the global, which doesn't have
+ // partitionKey. And ChromeUtils.getPartitionKeyFromURL() will get
+ // partitionKey from that principal. So, we will get an empty partitionKey
+ // here.
+ // XXX: The behavior here is debatable.
+ Assert.equal(partitionKey, "", "The partitionKey of blob url is correct.");
+
+ await contentPage.close();
+});
+
+add_task(async function test_throw_with_invalid_URL() {
+ // The API should throw if the url is invalid.
+ for (const invalidURL of TEST_INVALID_URLS) {
+ info(`Testing invalid url: ${invalidURL}`);
+
+ Assert.throws(
+ () => {
+ ChromeUtils.getPartitionKeyFromURL(invalidURL);
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "It should fail on invalid URLs."
+ );
+ }
+});
diff --git a/toolkit/components/antitracking/test/xpcshell/test_purge_trackers.js b/toolkit/components/antitracking/test/xpcshell/test_purge_trackers.js
new file mode 100644
index 0000000000..84694a35d5
--- /dev/null
+++ b/toolkit/components/antitracking/test/xpcshell/test_purge_trackers.js
@@ -0,0 +1,523 @@
+/* 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();
+ }
+});
diff --git a/toolkit/components/antitracking/test/xpcshell/test_purge_trackers_telemetry.js b/toolkit/components/antitracking/test/xpcshell/test_purge_trackers_telemetry.js
new file mode 100644
index 0000000000..a1502373dc
--- /dev/null
+++ b/toolkit/components/antitracking/test/xpcshell/test_purge_trackers_telemetry.js
@@ -0,0 +1,175 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TRACKING_PAGE = "https://tracking.example.org";
+const BENIGN_PAGE = "https://example.com";
+
+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 { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "PurgeTrackerService",
+ "@mozilla.org/purge-tracker-service;1",
+ "nsIPurgeTrackerService"
+);
+
+add_task(async function setup() {
+ Services.prefs.setIntPref(
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER
+ );
+ Services.prefs.setBoolPref("privacy.purge_trackers.enabled", true);
+ Services.prefs.setStringPref(
+ "urlclassifier.trackingAnnotationTable.testEntries",
+ "tracking.example.org"
+ );
+ Services.prefs.setBoolPref(
+ "toolkit.telemetry.testing.overrideProductsCheck",
+ true
+ );
+
+ // Enables us to test localStorage in xpcshell.
+ Services.prefs.setBoolPref("dom.storage.client_validation", false);
+});
+
+/**
+ * Test telemetry for cookie purging.
+ */
+add_task(async function () {
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ let FIVE_DAYS = 5 * 24 * 60 * 60 * 1000;
+
+ PermissionTestUtils.add(
+ TRACKING_PAGE,
+ "storageAccessAPI",
+ Services.perms.ALLOW_ACTION,
+ Services.perms.EXPIRE_TIME,
+ Date.now() + FIVE_DAYS
+ );
+
+ SiteDataTestUtils.addToLocalStorage(TRACKING_PAGE);
+ SiteDataTestUtils.addToCookies({ origin: BENIGN_PAGE });
+ SiteDataTestUtils.addToCookies({ origin: TRACKING_PAGE });
+ await SiteDataTestUtils.addToIndexedDB(TRACKING_PAGE);
+
+ let purgedHistogram = TelemetryTestUtils.getAndClearHistogram(
+ "COOKIE_PURGING_ORIGINS_PURGED"
+ );
+ let notPurgedHistogram = TelemetryTestUtils.getAndClearHistogram(
+ "COOKIE_PURGING_TRACKERS_WITH_USER_INTERACTION"
+ );
+ let remainingDaysHistogram = TelemetryTestUtils.getAndClearHistogram(
+ "COOKIE_PURGING_TRACKERS_USER_INTERACTION_REMAINING_DAYS"
+ );
+ let intervalHistogram = TelemetryTestUtils.getAndClearHistogram(
+ "COOKIE_PURGING_INTERVAL_HOURS"
+ );
+
+ // Purge while storage access permission exists.
+ await PurgeTrackerService.purgeTrackingCookieJars();
+
+ ok(
+ SiteDataTestUtils.hasCookies(TRACKING_PAGE),
+ "cookie remains while storage access permission exists."
+ );
+ ok(
+ SiteDataTestUtils.hasLocalStorage(TRACKING_PAGE),
+ "localStorage should not have been removed while storage access permission exists."
+ );
+ Assert.greater(
+ await SiteDataTestUtils.getQuotaUsage(TRACKING_PAGE),
+ 0,
+ `We have data for ${TRACKING_PAGE}`
+ );
+
+ TelemetryTestUtils.assertHistogram(purgedHistogram, 0, 1);
+ TelemetryTestUtils.assertHistogram(notPurgedHistogram, 1, 1);
+ TelemetryTestUtils.assertHistogram(remainingDaysHistogram, 4, 2);
+ TelemetryTestUtils.assertHistogram(intervalHistogram, 0, 1);
+
+ purgedHistogram = TelemetryTestUtils.getAndClearHistogram(
+ "COOKIE_PURGING_ORIGINS_PURGED"
+ );
+ notPurgedHistogram = TelemetryTestUtils.getAndClearHistogram(
+ "COOKIE_PURGING_TRACKERS_WITH_USER_INTERACTION"
+ );
+ intervalHistogram = TelemetryTestUtils.getAndClearHistogram(
+ "COOKIE_PURGING_INTERVAL_HOURS"
+ );
+
+ // 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"
+ );
+
+ // Cookie should have been removed.
+ ok(
+ !SiteDataTestUtils.hasCookies(TRACKING_PAGE),
+ "cookie is removed after purge with no storage access permission."
+ );
+ ok(
+ !SiteDataTestUtils.hasLocalStorage(TRACKING_PAGE),
+ "localStorage should not have been removed while storage access permission exists."
+ );
+ Assert.equal(
+ await SiteDataTestUtils.getQuotaUsage(TRACKING_PAGE),
+ 0,
+ "quota storage was deleted"
+ );
+
+ TelemetryTestUtils.assertHistogram(purgedHistogram, 1, 1);
+ Assert.equal(
+ notPurgedHistogram.snapshot().sum,
+ 0,
+ "no origins with user interaction"
+ );
+ TelemetryTestUtils.assertHistogram(intervalHistogram, 0, 1);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+});
+
+/**
+ * Test counting correctly across cookies batches
+ */
+add_task(async function () {
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ // Enforce deleting the same origin twice by adding two cookies and setting
+ // the max number of cookies per batch to 1.
+ SiteDataTestUtils.addToCookies({ origin: TRACKING_PAGE, name: "cookie1" });
+ SiteDataTestUtils.addToCookies({ origin: TRACKING_PAGE, name: "cookie2" });
+ Services.prefs.setIntPref("privacy.purge_trackers.max_purge_count", 1);
+
+ let purgedHistogram = TelemetryTestUtils.getAndClearHistogram(
+ "COOKIE_PURGING_ORIGINS_PURGED"
+ );
+
+ await PurgeTrackerService.purgeTrackingCookieJars();
+
+ // Cookie should have been removed.
+ await TestUtils.waitForCondition(
+ () => !SiteDataTestUtils.hasCookies(TRACKING_PAGE),
+ "cookie is removed after purge."
+ );
+
+ TelemetryTestUtils.assertHistogram(purgedHistogram, 1, 1);
+
+ Services.prefs.clearUserPref("privacy.purge_trackers.max_purge_count");
+ UrlClassifierTestUtils.cleanupTestTrackers();
+});
diff --git a/toolkit/components/antitracking/test/xpcshell/test_rejectForeignAllowList.js b/toolkit/components/antitracking/test/xpcshell/test_rejectForeignAllowList.js
new file mode 100644
index 0000000000..97e95a43f4
--- /dev/null
+++ b/toolkit/components/antitracking/test/xpcshell/test_rejectForeignAllowList.js
@@ -0,0 +1,116 @@
+"use strict";
+
+do_get_profile();
+
+// Let's use XPCShellContentUtils to open/close tabs.
+const { XPCShellContentUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/XPCShellContentUtils.sys.mjs"
+);
+
+XPCShellContentUtils.init(this);
+
+var createHttpServer = (...args) => {
+ return XPCShellContentUtils.createHttpServer(...args);
+};
+
+const server = createHttpServer({
+ hosts: ["3rdparty.org", "4thparty.org", "foobar.com"],
+});
+
+async function testThings(prefValue, expected) {
+ await new Promise(resolve =>
+ Services.clearData.deleteData(
+ Ci.nsIClearDataService.CLEAR_ALL_CACHES,
+ resolve
+ )
+ );
+
+ Services.prefs.setCharPref("privacy.rejectForeign.allowList", prefValue);
+
+ let cookiePromise = new Promise(resolve => {
+ server.registerPathHandler("/test3rdPartyChannel", (request, response) => {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html; charset=utf-8", false);
+ response.write(`<html><img src="http://3rdparty.org/img" /></html>`);
+ });
+
+ server.registerPathHandler("/img", (request, response) => {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ resolve(request.hasHeader("Cookie") ? request.getHeader("Cookie") : "");
+ response.setHeader("Content-Type", "image/png", false);
+ response.write("Not an image");
+ });
+ });
+
+ // Let's load 3rdparty.org as a 3rd-party.
+ let contentPage = await XPCShellContentUtils.loadContentPage(
+ "http://foobar.com/test3rdPartyChannel"
+ );
+ Assert.equal(await cookiePromise, expected, "Cookies received?");
+ await contentPage.close();
+
+ cookiePromise = new Promise(resolve => {
+ server.registerPathHandler("/test3rdPartyDocument", (request, response) => {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html; charset=utf-8", false);
+ response.write(
+ `<html><iframe src="http://3rdparty.org/iframe" /></html>`
+ );
+ });
+
+ server.registerPathHandler("/iframe", (request, response) => {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ resolve(request.hasHeader("Cookie") ? request.getHeader("Cookie") : "");
+ response.setHeader("Content-Type", "text/html; charset=utf-8", false);
+ response.write(`<html><img src="http://4thparty.org/img" /></html>`);
+ });
+
+ server.registerPathHandler("/img", (request, response) => {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ resolve(request.hasHeader("Cookie") ? request.getHeader("Cookie") : "");
+ response.setHeader("Content-Type", "image/png", false);
+ response.write("Not an image");
+ });
+ });
+
+ // Let's load 3rdparty.org loading a 4th-party.
+ contentPage = await XPCShellContentUtils.loadContentPage(
+ "http://foobar.com/test3rdPartyDocument"
+ );
+ Assert.equal(await cookiePromise, expected, "Cookies received?");
+ await contentPage.close();
+}
+
+add_task(async function test_rejectForeignAllowList() {
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 1);
+ Services.prefs.setBoolPref(
+ "network.cookie.rejectForeignWithExceptions.enabled",
+ true
+ );
+
+ // We don't want to have 'secure' cookies because our test http server doesn't run in https.
+ Services.prefs.setBoolPref(
+ "network.cookie.sameSite.noneRequiresSecure",
+ false
+ );
+
+ server.registerPathHandler("/setCookies", (request, response) => {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html; charset=utf-8", false);
+ response.setHeader("Set-Cookie", "cookie=wow; sameSite=none", true);
+ response.write("<html></html>");
+ });
+
+ // Let's set a cookie.
+ let contentPage = await XPCShellContentUtils.loadContentPage(
+ "http://3rdparty.org/setCookies"
+ );
+ await contentPage.close();
+ Assert.equal(Services.cookies.cookies.length, 1);
+
+ // Without exceptionlisting, no cookies should be shared.
+ await testThings("", "");
+
+ // Let's exceptionlist 3rdparty.org
+ await testThings("3rdparty.org", "cookie=wow");
+});
diff --git a/toolkit/components/antitracking/test/xpcshell/test_staticPartition_authhttp.js b/toolkit/components/antitracking/test/xpcshell/test_staticPartition_authhttp.js
new file mode 100644
index 0000000000..0492f5ff2a
--- /dev/null
+++ b/toolkit/components/antitracking/test/xpcshell/test_staticPartition_authhttp.js
@@ -0,0 +1,125 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const { CookieXPCShellUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/CookieXPCShellUtils.sys.mjs"
+);
+
+CookieXPCShellUtils.init(this);
+
+function Requestor() {}
+Requestor.prototype = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIInterfaceRequestor",
+ "nsIAuthPrompt2",
+ ]),
+
+ getInterface(iid) {
+ if (iid.equals(Ci.nsIAuthPrompt2)) {
+ return this;
+ }
+
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ promptAuth(channel, level, authInfo) {
+ Assert.equal("secret", authInfo.realm);
+ // No passwords in the URL -> nothing should be prefilled
+ Assert.equal(authInfo.username, "");
+ Assert.equal(authInfo.password, "");
+ Assert.equal(authInfo.domain, "");
+
+ authInfo.username = "guest";
+ authInfo.password = "guest";
+
+ return true;
+ },
+
+ asyncPromptAuth(chan, cb, ctx, lvl, info) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+};
+
+let observer = channel => {
+ if (
+ !(channel instanceof Ci.nsIHttpChannel && channel.URI.host === "localhost")
+ ) {
+ return;
+ }
+ channel.notificationCallbacks = new Requestor();
+};
+Services.obs.addObserver(observer, "http-on-modify-request");
+
+add_task(async () => {
+ do_get_profile();
+
+ Services.prefs.setBoolPref("network.predictor.enabled", false);
+ Services.prefs.setBoolPref("network.predictor.enable-prefetch", false);
+ Services.prefs.setBoolPref("network.http.rcwn.enabled", false);
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setIntPref("network.auth.subresource-http-auth-allow", 2);
+
+ for (let test of [true, false]) {
+ Cc["@mozilla.org/network/http-auth-manager;1"]
+ .getService(Ci.nsIHttpAuthManager)
+ .clearAll();
+
+ await new Promise(resolve =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve)
+ );
+
+ info("Enabling network state partitioning");
+ Services.prefs.setBoolPref("privacy.partition.network_state", test);
+
+ const httpserv = new HttpServer();
+ httpserv.registerPathHandler("/auth", (metadata, response) => {
+ // btoa("guest:guest"), but that function is not available here
+ const expectedHeader = "Basic Z3Vlc3Q6Z3Vlc3Q=";
+
+ let body;
+ if (
+ metadata.hasHeader("Authorization") &&
+ metadata.getHeader("Authorization") == expectedHeader
+ ) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+
+ body = "success";
+ } else {
+ // didn't know guest:guest, failure
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+
+ body = "failed";
+ }
+
+ response.bodyOutputStream.write(body, body.length);
+ });
+
+ httpserv.start(-1);
+ const URL = "http://localhost:" + httpserv.identity.primaryPort;
+
+ const httpHandler = Cc[
+ "@mozilla.org/network/protocol;1?name=http"
+ ].getService(Ci.nsIHttpProtocolHandler);
+
+ const contentPage = await CookieXPCShellUtils.loadContentPage(
+ URL + "/auth?r=" + Math.random()
+ );
+ await contentPage.close();
+
+ let key;
+ if (test) {
+ key = `^partitionKey=%28http%2Clocalhost%2C${httpserv.identity.primaryPort}%29:http://localhost:${httpserv.identity.primaryPort}`;
+ } else {
+ key = `:http://localhost:${httpserv.identity.primaryPort}`;
+ }
+
+ Assert.equal(httpHandler.authCacheKeys.includes(key), true, "Key found!");
+
+ await new Promise(resolve => httpserv.stop(resolve));
+ }
+});
diff --git a/toolkit/components/antitracking/test/xpcshell/test_staticPartition_clientAuthRemember.js b/toolkit/components/antitracking/test/xpcshell/test_staticPartition_clientAuthRemember.js
new file mode 100644
index 0000000000..ba2f6c6894
--- /dev/null
+++ b/toolkit/components/antitracking/test/xpcshell/test_staticPartition_clientAuthRemember.js
@@ -0,0 +1,129 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+let cars = Cc["@mozilla.org/security/clientAuthRememberService;1"].getService(
+ Ci.nsIClientAuthRememberService
+);
+let certDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+);
+
+function getOAWithPartitionKey(
+ { scheme = "https", topLevelBaseDomain, port = null } = {},
+ originAttributes = {}
+) {
+ if (!topLevelBaseDomain || !scheme) {
+ return originAttributes;
+ }
+
+ return {
+ ...originAttributes,
+ partitionKey: `(${scheme},${topLevelBaseDomain}${port ? `,${port}` : ""})`,
+ };
+}
+
+// These are not actual server and client certs. The ClientAuthRememberService
+// does not care which certs we store decisions for, as long as they're valid.
+let [clientCert] = certDB.getCerts();
+
+function addSecurityInfo({ host, topLevelBaseDomain, originAttributes = {} }) {
+ let attrs = getOAWithPartitionKey({ topLevelBaseDomain }, originAttributes);
+ cars.rememberDecisionScriptable(host, attrs, clientCert);
+}
+
+function testSecurityInfo({
+ host,
+ topLevelBaseDomain,
+ originAttributes = {},
+ expected = true,
+}) {
+ let attrs = getOAWithPartitionKey({ topLevelBaseDomain }, originAttributes);
+
+ let messageSuffix = `for ${host}`;
+ if (topLevelBaseDomain) {
+ messageSuffix += ` partitioned under ${topLevelBaseDomain}`;
+ }
+
+ let hasRemembered = cars.hasRememberedDecisionScriptable(host, attrs, {});
+
+ Assert.equal(
+ hasRemembered,
+ expected,
+ `CAR ${expected ? "is set" : "is not set"} ${messageSuffix}`
+ );
+}
+
+function addTestEntries() {
+ let entries = [
+ { host: "example.net" },
+ { host: "test.example.net" },
+ { host: "example.org" },
+ { host: "example.com", topLevelBaseDomain: "example.net" },
+ {
+ host: "test.example.net",
+ topLevelBaseDomain: "example.org",
+ },
+ {
+ host: "foo.example.com",
+ originAttributes: {
+ privateBrowsingId: 1,
+ },
+ },
+ ];
+
+ info("Add test state");
+ entries.forEach(addSecurityInfo);
+ info("Ensure we have the correct state initially");
+ entries.forEach(testSecurityInfo);
+}
+
+add_task(async () => {
+ addTestEntries();
+
+ info("Should not be set for unrelated host");
+ [undefined, "example.org", "example.net", "example.com"].forEach(
+ topLevelBaseDomain =>
+ testSecurityInfo({
+ host: "mochit.test",
+ topLevelBaseDomain,
+ expected: false,
+ })
+ );
+
+ info("Should not be set for unrelated subdomain");
+ testSecurityInfo({ host: "foo.example.net", expected: false });
+
+ info("Should not be set for unpartitioned first party");
+ testSecurityInfo({
+ host: "example.com",
+ expected: false,
+ });
+
+ info("Should not be set under different first party");
+ testSecurityInfo({
+ host: "example.com",
+ topLevelBaseDomain: "example.org",
+ expected: false,
+ });
+ testSecurityInfo({
+ host: "test.example.net",
+ topLevelBaseDomain: "example.com",
+ expected: false,
+ });
+
+ info("Should not be set in partitioned context");
+ ["example.com", "example.net", "example.org", "mochi.test"].forEach(
+ topLevelBaseDomain =>
+ testSecurityInfo({
+ host: "foo.example.com",
+ topLevelBaseDomain,
+ expected: false,
+ })
+ );
+
+ // Cleanup
+ cars.clearRememberedDecisions();
+});
diff --git a/toolkit/components/antitracking/test/xpcshell/test_staticPartition_font.js b/toolkit/components/antitracking/test/xpcshell/test_staticPartition_font.js
new file mode 100644
index 0000000000..46e230ec3e
--- /dev/null
+++ b/toolkit/components/antitracking/test/xpcshell/test_staticPartition_font.js
@@ -0,0 +1,112 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const { CookieXPCShellUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/CookieXPCShellUtils.sys.mjs"
+);
+
+CookieXPCShellUtils.init(this);
+
+let gHits = 0;
+
+add_task(async function () {
+ do_get_profile();
+
+ info("Disable predictor and accept all");
+ Services.prefs.setBoolPref("network.predictor.enabled", false);
+ Services.prefs.setBoolPref("network.predictor.enable-prefetch", false);
+ Services.prefs.setBoolPref("network.http.rcwn.enabled", false);
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+
+ const server = CookieXPCShellUtils.createServer({
+ hosts: ["example.org", "foo.com", "bar.com"],
+ });
+
+ server.registerFile(
+ "/font.woff",
+ do_get_file("data/font.woff"),
+ (_, response) => {
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ gHits++;
+ }
+ );
+
+ server.registerPathHandler("/font", (request, response) => {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html", false);
+ let body = `
+ <style type="text/css">
+ @font-face {
+ font-family: foo;
+ src: url("http://example.org/font.woff") format('woff');
+ }
+ body { font-family: foo }
+ </style>
+ <iframe src="http://example.org/font-iframe">
+ </iframe>`;
+ response.bodyOutputStream.write(body, body.length);
+ });
+
+ server.registerPathHandler("/font-iframe", (request, response) => {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html", false);
+ let body = `
+ <style type="text/css">
+ @font-face {
+ font-family: foo;
+ src: url("http://example.org/font.woff") format('woff');
+ }
+ body { font-family: foo }
+ </style>`;
+ response.bodyOutputStream.write(body, body.length);
+ });
+
+ const tests = [
+ {
+ prefValue: true,
+ hitsCount: 5,
+ },
+ {
+ prefValue: false,
+ // The font in page B/C is CORS, the channel will be flagged with
+ // nsIRequest::LOAD_ANONYMOUS.
+ // The flag makes the font in A and B/C use different cache key.
+ hitsCount: 2,
+ },
+ ];
+
+ for (let test of tests) {
+ info("Clear network caches");
+ Services.cache2.clear();
+
+ info("Reset the hits count");
+ gHits = 0;
+
+ info("Enabling network state partitioning");
+ Services.prefs.setBoolPref(
+ "privacy.partition.network_state",
+ test.prefValue
+ );
+
+ info("Let's load a page with origin A");
+ let contentPage = await CookieXPCShellUtils.loadContentPage(
+ "http://example.org/font"
+ );
+ await contentPage.close();
+
+ info("Let's load a page with origin B");
+ contentPage = await CookieXPCShellUtils.loadContentPage(
+ "http://foo.com/font"
+ );
+ await contentPage.close();
+
+ info("Let's load a page with origin C");
+ contentPage = await CookieXPCShellUtils.loadContentPage(
+ "http://bar.com/font"
+ );
+ await contentPage.close();
+
+ Assert.equal(gHits, test.hitsCount, "The number of hits match");
+ }
+});
diff --git a/toolkit/components/antitracking/test/xpcshell/test_staticPartition_image.js b/toolkit/components/antitracking/test/xpcshell/test_staticPartition_image.js
new file mode 100644
index 0000000000..7492d2267a
--- /dev/null
+++ b/toolkit/components/antitracking/test/xpcshell/test_staticPartition_image.js
@@ -0,0 +1,86 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const { CookieXPCShellUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/CookieXPCShellUtils.sys.mjs"
+);
+
+CookieXPCShellUtils.init(this);
+
+let gHits = 0;
+
+add_task(async function () {
+ do_get_profile();
+
+ info("Disable predictor and accept all");
+ Services.prefs.setBoolPref("network.predictor.enabled", false);
+ Services.prefs.setBoolPref("network.predictor.enable-prefetch", false);
+ Services.prefs.setBoolPref("network.http.rcwn.enabled", false);
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+
+ const server = CookieXPCShellUtils.createServer({
+ hosts: ["example.org", "foo.com"],
+ });
+ server.registerPathHandler("/image.png", (metadata, response) => {
+ gHits++;
+ response.setHeader("Cache-Control", "max-age=10000", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "image/png", false);
+ var body = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAEUlEQVQImWP4z8AAQTAamQkAhpcI+DeMzFcAAAAASUVORK5CYII="
+ );
+ response.bodyOutputStream.write(body, body.length);
+ });
+
+ server.registerPathHandler("/image", (metadata, response) => {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html", false);
+ var body = `<img src="http://example.org/image.png">`;
+ response.bodyOutputStream.write(body, body.length);
+ });
+
+ const tests = [
+ {
+ prefValue: true,
+ hitsCount: 2,
+ },
+ {
+ prefValue: false,
+ hitsCount: 1,
+ },
+ ];
+
+ for (let test of tests) {
+ info("Clear image and network caches");
+ let imageCache = Cc["@mozilla.org/image/tools;1"]
+ .getService(Ci.imgITools)
+ .getImgCacheForDocument(null);
+ imageCache.clearCache(true); // true=chrome
+ imageCache.clearCache(false); // false=content
+ Services.cache2.clear();
+
+ info("Reset the hits count");
+ gHits = 0;
+
+ info("Enabling network state partitioning");
+ Services.prefs.setBoolPref(
+ "privacy.partition.network_state",
+ test.prefValue
+ );
+
+ info("Let's load a page with origin A");
+ let contentPage = await CookieXPCShellUtils.loadContentPage(
+ "http://example.org/image"
+ );
+ await contentPage.close();
+
+ info("Let's load a page with origin B");
+ contentPage = await CookieXPCShellUtils.loadContentPage(
+ "http://foo.com/image"
+ );
+ await contentPage.close();
+
+ Assert.equal(gHits, test.hitsCount, "The number of hits match");
+ }
+});
diff --git a/toolkit/components/antitracking/test/xpcshell/test_staticPartition_prefetch.js b/toolkit/components/antitracking/test/xpcshell/test_staticPartition_prefetch.js
new file mode 100644
index 0000000000..1215ec2384
--- /dev/null
+++ b/toolkit/components/antitracking/test/xpcshell/test_staticPartition_prefetch.js
@@ -0,0 +1,177 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const { CookieXPCShellUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/CookieXPCShellUtils.sys.mjs"
+);
+
+// Small red image.
+const IMG_BYTES = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" +
+ "P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="
+);
+
+let gHints = 0;
+
+CookieXPCShellUtils.init(this);
+
+function countMatchingCacheEntries(cacheEntries, domain, path) {
+ return cacheEntries
+ .map(entry => entry.uri.asciiSpec)
+ .filter(spec => spec.includes(domain))
+ .filter(spec => spec.includes(path)).length;
+}
+
+async function checkCache(originAttributes) {
+ const loadContextInfo = Services.loadContextInfo.custom(
+ false,
+ originAttributes
+ );
+
+ const data = await new Promise(resolve => {
+ let cacheEntries = [];
+ let cacheVisitor = {
+ onCacheStorageInfo(num, consumption) {},
+ onCacheEntryInfo(uri, idEnhance) {
+ cacheEntries.push({ uri, idEnhance });
+ },
+ onCacheEntryVisitCompleted() {
+ resolve(cacheEntries);
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsICacheStorageVisitor"]),
+ };
+ // Visiting the disk cache also visits memory storage so we do not
+ // need to use Services.cache2.memoryCacheStorage() here.
+ let storage = Services.cache2.diskCacheStorage(loadContextInfo);
+ storage.asyncVisitStorage(cacheVisitor, true);
+ });
+
+ let foundEntryCount = countMatchingCacheEntries(
+ data,
+ "example.org",
+ "image.png"
+ );
+ ok(
+ foundEntryCount > 0,
+ `Cache entries expected for image.png and OA=${originAttributes}`
+ );
+}
+
+add_task(async () => {
+ do_get_profile();
+
+ Services.prefs.setBoolPref("network.prefetch-next", true);
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+
+ const server = CookieXPCShellUtils.createServer({
+ hosts: ["example.org", "foo.com"],
+ });
+
+ server.registerPathHandler("/image.png", (metadata, response) => {
+ gHints++;
+ response.setHeader("Cache-Control", "max-age=10000", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "image/png", false);
+ response.write(IMG_BYTES);
+ });
+
+ server.registerPathHandler("/prefetch", (metadata, response) => {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html", false);
+ var body = `<html><head></head><body><script>
+ const link = document.createElement("link")
+ link.setAttribute("rel", "prefetch");
+ link.setAttribute("href", "http://example.org/image.png");
+ document.head.appendChild(link);
+ link.onload = () => {
+ const img = document.createElement("IMG");
+ img.src = "http://example.org/image.png";
+ document.body.appendChild(img);
+ fetch("/done").then(() => {});
+ }
+ </script></body></html>`;
+ response.bodyOutputStream.write(body, body.length);
+ });
+
+ const tests = [
+ {
+ // 2 hints because we have 2 different top-level origins, loading the
+ // same resource. This will end up creating 2 separate cache entries.
+ hints: 2,
+ originAttributes: { partitionKey: "(http,example.org)" },
+ prefValue: true,
+ },
+ {
+ // 1 hint because, with network-state isolation, the cache entry will be
+ // reused for the second loading, even if the top-level origins are
+ // different.
+ hints: 1,
+ originAttributes: {},
+ prefValue: false,
+ },
+ ];
+
+ for (let test of tests) {
+ await new Promise(resolve =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve)
+ );
+
+ info("Reset the counter");
+ gHints = 0;
+
+ info("Enabling network state partitioning");
+ Services.prefs.setBoolPref(
+ "privacy.partition.network_state",
+ test.prefValue
+ );
+
+ let complete = new Promise(resolve => {
+ server.registerPathHandler("/done", (metadata, response) => {
+ response.setHeader("Cache-Control", "max-age=10000", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html", false);
+ var body = "OK";
+ response.bodyOutputStream.write(body, body.length);
+ resolve();
+ });
+ });
+
+ info("Let's load a page with origin A");
+ let contentPage = await CookieXPCShellUtils.loadContentPage(
+ "http://example.org/prefetch"
+ );
+
+ await complete;
+ await checkCache(test.originAttributes);
+ await contentPage.close();
+
+ complete = new Promise(resolve => {
+ server.registerPathHandler("/done", (metadata, response) => {
+ response.setHeader("Cache-Control", "max-age=10000", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html", false);
+ var body = "OK";
+ response.bodyOutputStream.write(body, body.length);
+ resolve();
+ });
+ });
+
+ info("Let's load a page with origin B");
+ contentPage = await CookieXPCShellUtils.loadContentPage(
+ "http://foo.com/prefetch"
+ );
+
+ await complete;
+ await checkCache(test.originAttributes);
+ await contentPage.close();
+
+ Assert.equal(
+ gHints,
+ test.hints,
+ "We have the current number of requests with pref " + test.prefValue
+ );
+ }
+});
diff --git a/toolkit/components/antitracking/test/xpcshell/test_staticPartition_preload.js b/toolkit/components/antitracking/test/xpcshell/test_staticPartition_preload.js
new file mode 100644
index 0000000000..be61a2e6d4
--- /dev/null
+++ b/toolkit/components/antitracking/test/xpcshell/test_staticPartition_preload.js
@@ -0,0 +1,187 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const { CookieXPCShellUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/CookieXPCShellUtils.sys.mjs"
+);
+
+let gHints = 0;
+
+CookieXPCShellUtils.init(this);
+
+function countMatchingCacheEntries(cacheEntries, domain, path) {
+ return cacheEntries
+ .map(entry => entry.uri.asciiSpec)
+ .filter(spec => spec.includes(domain))
+ .filter(spec => spec.includes(path)).length;
+}
+
+async function checkCache(originAttributes) {
+ const loadContextInfo = Services.loadContextInfo.custom(
+ false,
+ originAttributes
+ );
+
+ const data = await new Promise(resolve => {
+ let cacheEntries = [];
+ let cacheVisitor = {
+ onCacheStorageInfo(num, consumption) {},
+ onCacheEntryInfo(uri, idEnhance) {
+ cacheEntries.push({ uri, idEnhance });
+ },
+ onCacheEntryVisitCompleted() {
+ resolve(cacheEntries);
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsICacheStorageVisitor"]),
+ };
+ // Visiting the disk cache also visits memory storage so we do not
+ // need to use Services.cache2.memoryCacheStorage() here.
+ let storage = Services.cache2.diskCacheStorage(loadContextInfo);
+ storage.asyncVisitStorage(cacheVisitor, true);
+ });
+
+ let foundEntryCount = countMatchingCacheEntries(
+ data,
+ "example.org",
+ "style.css"
+ );
+ ok(
+ foundEntryCount > 0,
+ `Cache entries expected for style.css and OA=${originAttributes}`
+ );
+}
+
+add_task(async () => {
+ do_get_profile();
+
+ Services.prefs.setBoolPref("network.preload", true);
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+
+ const server = CookieXPCShellUtils.createServer({
+ hosts: ["example.org", "foo.com"],
+ });
+
+ server.registerPathHandler("/empty", (metadata, response) => {
+ var body = "<h1>Hello!</h1>";
+ response.bodyOutputStream.write(body, body.length);
+ });
+
+ server.registerPathHandler("/style.css", (metadata, response) => {
+ gHints++;
+ response.setHeader("Cache-Control", "max-age=10000", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ var body = "* { color: red }";
+ response.bodyOutputStream.write(body, body.length);
+ });
+
+ server.registerPathHandler("/preload", (metadata, response) => {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html", false);
+ var body = `<html><head></head><body><script>
+ const link = document.createElement("link")
+ link.setAttribute("rel", "preload");
+ link.setAttribute("as", "style");
+ link.setAttribute("href", "http://example.org/style.css");
+ document.head.appendChild(link);
+ link.onload = () => {
+ fetch("/done").then(() => {});
+ };
+ </script></body></html>`;
+ response.bodyOutputStream.write(body, body.length);
+ });
+
+ const tests = [
+ {
+ // 2 hints because we have 2 different top-level origins, loading the
+ // same resource. This will end up creating 2 separate cache entries.
+ hints: 2,
+ prefValue: true,
+ originAttributes: { partitionKey: "(http,example.org)" },
+ },
+ {
+ // 1 hint because, with network-state isolation, the cache entry will be
+ // reused for the second loading, even if the top-level origins are
+ // different.
+ hints: 1,
+ originAttributes: {},
+ prefValue: false,
+ },
+ ];
+
+ for (let test of tests) {
+ await new Promise(resolve =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve)
+ );
+
+ info("Reset the shared sheets");
+ let contentPage = await CookieXPCShellUtils.loadContentPage(
+ "http://example.org/empty"
+ );
+
+ await contentPage.spawn([], () =>
+ // eslint-disable-next-line no-undef
+ content.windowUtils.clearSharedStyleSheetCache()
+ );
+
+ await contentPage.close();
+
+ info("Reset the counter");
+ gHints = 0;
+
+ info("Enabling network state partitioning");
+ Services.prefs.setBoolPref(
+ "privacy.partition.network_state",
+ test.prefValue
+ );
+
+ let complete = new Promise(resolve => {
+ server.registerPathHandler("/done", (metadata, response) => {
+ response.setHeader("Cache-Control", "max-age=10000", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html", false);
+ var body = "OK";
+ response.bodyOutputStream.write(body, body.length);
+ resolve();
+ });
+ });
+
+ info("Let's load a page with origin A");
+ contentPage = await CookieXPCShellUtils.loadContentPage(
+ "http://example.org/preload"
+ );
+
+ await complete;
+ await checkCache(test.originAttributes);
+ await contentPage.close();
+
+ complete = new Promise(resolve => {
+ server.registerPathHandler("/done", (metadata, response) => {
+ response.setHeader("Cache-Control", "max-age=10000", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html", false);
+ var body = "OK";
+ response.bodyOutputStream.write(body, body.length);
+ resolve();
+ });
+ });
+
+ info("Let's load a page with origin B");
+ contentPage = await CookieXPCShellUtils.loadContentPage(
+ "http://foo.com/preload"
+ );
+
+ await complete;
+ await checkCache(test.originAttributes);
+ await contentPage.close();
+
+ Assert.equal(
+ gHints,
+ test.hints,
+ "We have the current number of requests with pref " + test.prefValue
+ );
+ }
+});
diff --git a/toolkit/components/antitracking/test/xpcshell/test_tracking_db_service.js b/toolkit/components/antitracking/test/xpcshell/test_tracking_db_service.js
new file mode 100644
index 0000000000..0f6ded1218
--- /dev/null
+++ b/toolkit/components/antitracking/test/xpcshell/test_tracking_db_service.js
@@ -0,0 +1,492 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Note: This test may cause intermittents if run at exactly midnight.
+
+"use strict";
+
+const { Sqlite } = ChromeUtils.importESModule(
+ "resource://gre/modules/Sqlite.sys.mjs"
+);
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "TrackingDBService",
+ "@mozilla.org/tracking-db-service;1",
+ "nsITrackingDBService"
+);
+
+XPCOMUtils.defineLazyGetter(this, "DB_PATH", function () {
+ return PathUtils.join(PathUtils.profileDir, "protections.sqlite");
+});
+
+const SQL = {
+ insertCustomTimeEvent:
+ "INSERT INTO events (type, count, timestamp)" +
+ "VALUES (:type, :count, date(:timestamp));",
+
+ selectAllEntriesOfType: "SELECT * FROM events WHERE type = :type;",
+
+ selectAll: "SELECT * FROM events",
+};
+
+// Emulate the content blocking log. We do not record the url key, nor
+// do we use the aggregated event number (the last element in the array).
+const LOG = {
+ "https://1.example.com": [
+ [Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT, true, 1],
+ ],
+ "https://2.example.com": [
+ [Ci.nsIWebProgressListener.STATE_BLOCKED_FINGERPRINTING_CONTENT, true, 1],
+ ],
+ "https://3.example.com": [
+ [Ci.nsIWebProgressListener.STATE_BLOCKED_CRYPTOMINING_CONTENT, true, 2],
+ ],
+ "https://4.example.com": [
+ [Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER, true, 3],
+ ],
+ "https://5.example.com": [
+ [Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER, true, 1],
+ ],
+ // Cookie blocked for other reason, then identified as a tracker
+ "https://6.example.com": [
+ [
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_ALL |
+ Ci.nsIWebProgressListener.STATE_LOADED_LEVEL_1_TRACKING_CONTENT,
+ true,
+ 4,
+ ],
+ ],
+ "https://7.example.com": [
+ [Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_SOCIALTRACKER, true, 1],
+ ],
+ "https://8.example.com": [
+ [Ci.nsIWebProgressListener.STATE_BLOCKED_SOCIALTRACKING_CONTENT, true, 1],
+ ],
+
+ // The contents below should not add to the database.
+ // Cookie loaded but not blocked.
+ "https://10.example.com": [
+ [Ci.nsIWebProgressListener.STATE_COOKIES_LOADED, true, 1],
+ ],
+ // Tracker cookie loaded but not blocked.
+ "https://11.unblocked.example.com": [
+ [Ci.nsIWebProgressListener.STATE_COOKIES_LOADED_TRACKER, true, 1],
+ ],
+ // Social tracker cookie loaded but not blocked.
+ "https://12.example.com": [
+ [Ci.nsIWebProgressListener.STATE_COOKIES_LOADED_SOCIALTRACKER, true, 1],
+ ],
+ // Cookie blocked for other reason (not a tracker)
+ "https://13.example.com": [
+ [Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_BY_PERMISSION, true, 2],
+ ],
+ // Fingerprinters set to block, but this one has an exception
+ "https://14.example.com": [
+ [Ci.nsIWebProgressListener.STATE_BLOCKED_FINGERPRINTING_CONTENT, false, 1],
+ ],
+ // Two fingerprinters replaced with a shims script, should be treated as blocked
+ // and increment the counter.
+ "https://15.example.com": [
+ [Ci.nsIWebProgressListener.STATE_REPLACED_FINGERPRINTING_CONTENT, true, 1],
+ ],
+ "https://16.example.com": [
+ [Ci.nsIWebProgressListener.STATE_REPLACED_FINGERPRINTING_CONTENT, true, 1],
+ ],
+};
+
+do_get_profile();
+
+Services.prefs.setBoolPref("browser.contentblocking.database.enabled", true);
+Services.prefs.setBoolPref(
+ "privacy.socialtracking.block_cookies.enabled",
+ true
+);
+Services.prefs.setBoolPref(
+ "privacy.trackingprotection.fingerprinting.enabled",
+ true
+);
+Services.prefs.setBoolPref(
+ "browser.contentblocking.cfr-milestone.enabled",
+ true
+);
+Services.prefs.setIntPref(
+ "browser.contentblocking.cfr-milestone.update-interval",
+ 0
+);
+Services.prefs.setStringPref(
+ "browser.contentblocking.cfr-milestone.milestones",
+ "[1000, 5000, 10000, 25000, 100000, 500000]"
+);
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("browser.contentblocking.database.enabled");
+ Services.prefs.clearUserPref("privacy.socialtracking.block_cookies.enabled");
+ Services.prefs.clearUserPref(
+ "privacy.trackingprotection.fingerprinting.enabled"
+ );
+ Services.prefs.clearUserPref("browser.contentblocking.cfr-milestone.enabled");
+ Services.prefs.clearUserPref(
+ "browser.contentblocking.cfr-milestone.update-interval"
+ );
+ Services.prefs.clearUserPref(
+ "browser.contentblocking.cfr-milestone.milestones"
+ );
+});
+
+// This tests that data is added successfully, different types of events should get
+// their own entries, when the type is the same they should be aggregated. Events
+// that are not blocking events should not be recorded. Cookie blocking events
+// should only be recorded if we can identify the cookie as a tracking cookie.
+add_task(async function test_save_and_delete() {
+ await TrackingDBService.saveEvents(JSON.stringify(LOG));
+
+ // Peek in the DB to make sure we have the right data.
+ let db = await Sqlite.openConnection({ path: DB_PATH });
+ // Make sure the items table was created.
+ ok(await db.tableExists("events"), "events table exists");
+
+ // make sure we have the correct contents in the database
+ let rows = await db.execute(SQL.selectAll);
+ equal(
+ rows.length,
+ 5,
+ "Events that should not be saved have not been, length is 4"
+ );
+ rows = await db.execute(SQL.selectAllEntriesOfType, {
+ type: TrackingDBService.TRACKERS_ID,
+ });
+ equal(rows.length, 1, "Only one day has had tracker entries, length is 1");
+ let count = rows[0].getResultByName("count");
+ equal(count, 1, "there is only one tracker entry");
+
+ rows = await db.execute(SQL.selectAllEntriesOfType, {
+ type: TrackingDBService.TRACKING_COOKIES_ID,
+ });
+ equal(rows.length, 1, "Only one day has had cookies entries, length is 1");
+ count = rows[0].getResultByName("count");
+ equal(count, 3, "Cookie entries were aggregated");
+
+ rows = await db.execute(SQL.selectAllEntriesOfType, {
+ type: TrackingDBService.CRYPTOMINERS_ID,
+ });
+ equal(
+ rows.length,
+ 1,
+ "Only one day has had cryptominer entries, length is 1"
+ );
+ count = rows[0].getResultByName("count");
+ equal(count, 1, "there is only one cryptominer entry");
+
+ rows = await db.execute(SQL.selectAllEntriesOfType, {
+ type: TrackingDBService.FINGERPRINTERS_ID,
+ });
+ equal(
+ rows.length,
+ 1,
+ "Only one day has had fingerprinters entries, length is 1"
+ );
+ count = rows[0].getResultByName("count");
+ equal(count, 3, "there are three fingerprinter entries");
+
+ rows = await db.execute(SQL.selectAllEntriesOfType, {
+ type: TrackingDBService.SOCIAL_ID,
+ });
+ equal(rows.length, 1, "Only one day has had social entries, length is 1");
+ count = rows[0].getResultByName("count");
+ equal(count, 2, "there are two social entries");
+
+ // Use the TrackingDBService API to delete the data.
+ await TrackingDBService.clearAll();
+ // Make sure the data was deleted.
+ rows = await db.execute(SQL.selectAll);
+ equal(rows.length, 0, "length is 0");
+ await db.close();
+});
+
+// This tests that content blocking events encountered on the same day get aggregated,
+// and those on different days get seperate entries
+add_task(async function test_timestamp_aggragation() {
+ // This creates the schema.
+ await TrackingDBService.saveEvents(JSON.stringify({}));
+ let db = await Sqlite.openConnection({ path: DB_PATH });
+
+ let yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
+ let today = new Date().toISOString();
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.TRACKERS_ID,
+ count: 4,
+ timestamp: yesterday,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.CRYPTOMINERS_ID,
+ count: 3,
+ timestamp: yesterday,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.FINGERPRINTERS_ID,
+ count: 2,
+ timestamp: yesterday,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.TRACKING_COOKIES_ID,
+ count: 1,
+ timestamp: yesterday,
+ });
+
+ // Add some events for today which must get aggregated
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.TRACKERS_ID,
+ count: 2,
+ timestamp: today,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.CRYPTOMINERS_ID,
+ count: 2,
+ timestamp: today,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.FINGERPRINTERS_ID,
+ count: 2,
+ timestamp: today,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.TRACKING_COOKIES_ID,
+ count: 2,
+ timestamp: today,
+ });
+
+ // Add new events, they will have today's timestamp.
+ await TrackingDBService.saveEvents(JSON.stringify(LOG));
+
+ // Ensure events that are inserted today are not aggregated with past events.
+ let rows = await db.execute(SQL.selectAllEntriesOfType, {
+ type: TrackingDBService.TRACKERS_ID,
+ });
+ equal(rows.length, 2, "Tracker entries for today and yesterday, length is 2");
+ for (let i = 0; i < rows.length; i++) {
+ let count = rows[i].getResultByName("count");
+ if (i == 0) {
+ equal(count, 4, "Yesterday's count is 4");
+ } else if (i == 1) {
+ equal(count, 3, "Today's count is 3, new entries were aggregated");
+ }
+ }
+
+ rows = await db.execute(SQL.selectAllEntriesOfType, {
+ type: TrackingDBService.CRYPTOMINERS_ID,
+ });
+ equal(
+ rows.length,
+ 2,
+ "Cryptominer entries for today and yesterday, length is 2"
+ );
+ for (let i = 0; i < rows.length; i++) {
+ let count = rows[i].getResultByName("count");
+ if (i == 0) {
+ equal(count, 3, "Yesterday's count is 3");
+ } else if (i == 1) {
+ equal(count, 3, "Today's count is 3, new entries were aggregated");
+ }
+ }
+
+ rows = await db.execute(SQL.selectAllEntriesOfType, {
+ type: TrackingDBService.FINGERPRINTERS_ID,
+ });
+ equal(
+ rows.length,
+ 2,
+ "Fingerprinter entries for today and yesterday, length is 2"
+ );
+ for (let i = 0; i < rows.length; i++) {
+ let count = rows[i].getResultByName("count");
+ if (i == 0) {
+ equal(count, 2, "Yesterday's count is 2");
+ } else if (i == 1) {
+ equal(count, 5, "Today's count is 5, new entries were aggregated");
+ }
+ }
+
+ rows = await db.execute(SQL.selectAllEntriesOfType, {
+ type: TrackingDBService.TRACKING_COOKIES_ID,
+ });
+ equal(
+ rows.length,
+ 2,
+ "Tracking Cookies entries for today and yesterday, length is 2"
+ );
+ for (let i = 0; i < rows.length; i++) {
+ let count = rows[i].getResultByName("count");
+ if (i == 0) {
+ equal(count, 1, "Yesterday's count is 1");
+ } else if (i == 1) {
+ equal(count, 5, "Today's count is 5, new entries were aggregated");
+ }
+ }
+
+ // Use the TrackingDBService API to delete the data.
+ await TrackingDBService.clearAll();
+ // Make sure the data was deleted.
+ rows = await db.execute(SQL.selectAll);
+ equal(rows.length, 0, "length is 0");
+ await db.close();
+});
+
+let addEventsToDB = async db => {
+ let d = new Date(1521009000000);
+ let date = d.toISOString();
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.CRYPTOMINERS_ID,
+ count: 3,
+ timestamp: date,
+ });
+
+ date = new Date(d - 2 * 24 * 60 * 60 * 1000).toISOString();
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.TRACKERS_ID,
+ count: 2,
+ timestamp: date,
+ });
+
+ date = new Date(d - 3 * 24 * 60 * 60 * 1000).toISOString();
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.TRACKING_COOKIES_ID,
+ count: 2,
+ timestamp: date,
+ });
+
+ date = new Date(d - 4 * 24 * 60 * 60 * 1000).toISOString();
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.TRACKING_COOKIES_ID,
+ count: 2,
+ timestamp: date,
+ });
+
+ date = new Date(d - 9 * 24 * 60 * 60 * 1000).toISOString();
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.FINGERPRINTERS_ID,
+ count: 2,
+ timestamp: date,
+ });
+};
+
+// This tests that TrackingDBService.getEventsByDateRange can accept two timestamps in unix epoch time
+// and return entries that occur within the timestamps, rounded to the nearest day and inclusive.
+add_task(async function test_getEventsByDateRange() {
+ // This creates the schema.
+ await TrackingDBService.saveEvents(JSON.stringify({}));
+ let db = await Sqlite.openConnection({ path: DB_PATH });
+ await addEventsToDB(db);
+
+ let d = new Date(1521009000000);
+ let daysBefore1 = new Date(d - 24 * 60 * 60 * 1000);
+ let daysBefore4 = new Date(d - 4 * 24 * 60 * 60 * 1000);
+ let daysBefore9 = new Date(d - 9 * 24 * 60 * 60 * 1000);
+
+ let events = await TrackingDBService.getEventsByDateRange(daysBefore1, d);
+ equal(
+ events.length,
+ 1,
+ "There is 1 event entry between the date and one day before, inclusive"
+ );
+
+ events = await TrackingDBService.getEventsByDateRange(daysBefore4, d);
+ equal(
+ events.length,
+ 4,
+ "There is 4 event entries between the date and four days before, inclusive"
+ );
+
+ events = await TrackingDBService.getEventsByDateRange(
+ daysBefore9,
+ daysBefore4
+ );
+ equal(
+ events.length,
+ 2,
+ "There is 2 event entries between nine and four days before, inclusive"
+ );
+
+ await TrackingDBService.clearAll();
+ await db.close();
+});
+
+// This tests that TrackingDBService.sumAllEvents returns the number of
+// tracking events in the database, and can handle 0 entries.
+add_task(async function test_sumAllEvents() {
+ // This creates the schema.
+ await TrackingDBService.saveEvents(JSON.stringify({}));
+ let db = await Sqlite.openConnection({ path: DB_PATH });
+
+ let sum = await TrackingDBService.sumAllEvents();
+ equal(sum, 0, "There have been 0 events recorded");
+
+ // populate the database
+ await addEventsToDB(db);
+
+ sum = await TrackingDBService.sumAllEvents();
+ equal(sum, 11, "There have been 11 events recorded");
+
+ await TrackingDBService.clearAll();
+ await db.close();
+});
+
+// This tests that TrackingDBService.getEarliestRecordedDate returns the
+// earliest date recorded and can handle 0 entries.
+add_task(async function test_getEarliestRecordedDate() {
+ // This creates the schema.
+ await TrackingDBService.saveEvents(JSON.stringify({}));
+ let db = await Sqlite.openConnection({ path: DB_PATH });
+
+ let timestamp = await TrackingDBService.getEarliestRecordedDate();
+ equal(timestamp, null, "There is no earliest recorded date");
+
+ // populate the database
+ await addEventsToDB(db);
+ let d = new Date(1521009000000);
+ let daysBefore9 = new Date(d - 9 * 24 * 60 * 60 * 1000)
+ .toISOString()
+ .split("T")[0];
+
+ timestamp = await TrackingDBService.getEarliestRecordedDate();
+ let date = new Date(timestamp).toISOString().split("T")[0];
+ equal(date, daysBefore9, "The earliest recorded event is nine days before.");
+
+ await TrackingDBService.clearAll();
+ await db.close();
+});
+
+// This tests that a message to CFR is sent when the amount of saved trackers meets a milestone
+add_task(async function test_sendMilestoneNotification() {
+ let milestones = JSON.parse(
+ Services.prefs.getStringPref(
+ "browser.contentblocking.cfr-milestone.milestones"
+ )
+ );
+ // This creates the schema.
+ await TrackingDBService.saveEvents(JSON.stringify({}));
+ let db = await Sqlite.openConnection({ path: DB_PATH });
+ // save number of trackers equal to the first milestone
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.CRYPTOMINERS_ID,
+ count: milestones[0],
+ timestamp: new Date().toISOString(),
+ });
+
+ let awaitNotification = TestUtils.topicObserved(
+ "SiteProtection:ContentBlockingMilestone"
+ );
+
+ // trigger a "save" event to compare the trackers with the milestone.
+ await TrackingDBService.saveEvents(
+ JSON.stringify({
+ "https://1.example.com": [
+ [Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT, true, 1],
+ ],
+ })
+ );
+ await awaitNotification;
+
+ await TrackingDBService.clearAll();
+ await db.close();
+});
diff --git a/toolkit/components/antitracking/test/xpcshell/test_view_source.js b/toolkit/components/antitracking/test/xpcshell/test_view_source.js
new file mode 100644
index 0000000000..ebd70cf476
--- /dev/null
+++ b/toolkit/components/antitracking/test/xpcshell/test_view_source.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const { CookieXPCShellUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/CookieXPCShellUtils.sys.mjs"
+);
+
+CookieXPCShellUtils.init(this);
+
+let gCookieHits = 0;
+let gLoadingHits = 0;
+
+add_task(async function () {
+ do_get_profile();
+
+ info("Disable predictor and accept all");
+ Services.prefs.setBoolPref("network.predictor.enabled", false);
+ Services.prefs.setBoolPref("network.predictor.enable-prefetch", false);
+ Services.prefs.setBoolPref("network.http.rcwn.enabled", false);
+ Services.prefs.setIntPref(
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN
+ );
+
+ const server = CookieXPCShellUtils.createServer({
+ hosts: ["example.org"],
+ });
+ server.registerPathHandler("/test", (request, response) => {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html", false);
+ if (
+ request.hasHeader("Cookie") &&
+ request.getHeader("Cookie") == "foo=bar"
+ ) {
+ gCookieHits++;
+ } else {
+ response.setHeader("Set-Cookie", "foo=bar");
+ }
+
+ gLoadingHits++;
+ var body = "<html></html>";
+ response.bodyOutputStream.write(body, body.length);
+ });
+
+ info("Reset the hits count");
+ gCookieHits = 0;
+ gLoadingHits = 0;
+
+ info("Let's load a page");
+ let contentPage = await CookieXPCShellUtils.loadContentPage(
+ "http://example.org/test?1"
+ );
+ await contentPage.close();
+
+ Assert.equal(gCookieHits, 0, "The number of cookie hits match");
+ Assert.equal(gLoadingHits, 1, "The number of loading hits match");
+
+ info("Let's load the source of the page again to see if it loads from cache");
+ contentPage = await CookieXPCShellUtils.loadContentPage(
+ "view-source:http://example.org/test?1"
+ );
+ await contentPage.close();
+
+ Assert.equal(gCookieHits, 0, "The number of cookie hits match");
+ Assert.equal(gLoadingHits, 1, "The number of loading hits match");
+
+ info(
+ "Let's load the source of the page without hitting the cache to see if the cookie is sent properly"
+ );
+ contentPage = await CookieXPCShellUtils.loadContentPage(
+ "view-source:http://example.org/test?2"
+ );
+ await contentPage.close();
+
+ Assert.equal(gCookieHits, 1, "The number of cookie hits match");
+ Assert.equal(gLoadingHits, 2, "The number of loading hits match");
+});
diff --git a/toolkit/components/antitracking/test/xpcshell/xpcshell.ini b/toolkit/components/antitracking/test/xpcshell/xpcshell.ini
new file mode 100644
index 0000000000..b895aa8e07
--- /dev/null
+++ b/toolkit/components/antitracking/test/xpcshell/xpcshell.ini
@@ -0,0 +1,51 @@
+[DEFAULT]
+head = head.js ../../../../components/url-classifier/tests/unit/head_urlclassifier.js
+prefs =
+ dom.security.https_first=false #Disable https-first because of explicit http/https testing
+
+[test_cookie_behavior.js]
+[test_getPartitionKeyFromURL.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[test_purge_trackers.js]
+skip-if =
+ win10_2004 # Bug 1718292
+ win10_2009 # Bug 1718292
+ win11_2009 # Bug 1797751
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+run-sequentially = very high failure rate in parallel
+
+[test_purge_trackers_telemetry.js]
+[test_tracking_db_service.js]
+skip-if = toolkit == "android" # Bug 1697936
+[test_rejectForeignAllowList.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[test_staticPartition_clientAuthRemember.js]
+[test_staticPartition_font.js]
+support-files =
+ data/font.woff
+skip-if =
+ os == "linux" && !debug # Bug 1760086
+ apple_silicon # bug 1729551
+ os == "mac" && bits == 64 && !debug # Bug 1652119
+ os == "win" && bits == 64 && !debug # Bug 1652119
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+ socketprocess_networking # Bug 1759035
+run-sequentially = very high failure rate in parallel
+[test_staticPartition_image.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[test_staticPartition_authhttp.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[test_staticPartition_prefetch.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[test_staticPartition_preload.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[test_ExceptionListService.js]
+[test_view_source.js]
+skip-if =
+ socketprocess_networking # Bug 1759035 (not as common on win, perma on linux/osx)