Bug 1498591 - Add a test to ensure that navigating a subframe will revoke its storage access r=baku
☠☠ backed out by 87b544fc71d5 ☠ ☠
authorEhsan Akhgari <ehsan@mozilla.com>
Mon, 15 Oct 2018 10:35:39 +0000
changeset 499738 8efab497c2e6f710481d215e360650b220865269
parent 499737 e362c18e1830925ac30a83c45eccd16257cb58b9
child 499739 7b24c0c835bf54698cf49b5a21538bddd616642b
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1498591
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1498591 - Add a test to ensure that navigating a subframe will revoke its storage access r=baku Differential Revision: https://phabricator.services.mozilla.com/D8555
toolkit/components/antitracking/test/browser/browser.ini
toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateSubframe.js
toolkit/components/antitracking/test/browser/head.js
--- a/toolkit/components/antitracking/test/browser/browser.ini
+++ b/toolkit/components/antitracking/test/browser/browser.ini
@@ -55,11 +55,12 @@ skip-if = serviceworker_e10s
 [browser_subResources.js]
 support-files = subResources.sjs
 [browser_script.js]
 support-files = tracker.js
 [browser_userInteraction.js]
 [browser_storageAccessPrivateWindow.js]
 skip-if = serviceworker_e10s
 [browser_storageAccessPromiseResolveHandlerUserInteraction.js]
+[browser_storageAccessRemovalNavigateSubframe.js]
 [browser_storageAccessSandboxed.js]
 skip-if = serviceworker_e10s
 [browser_storageAccessWithHeuristics.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateSubframe.js
@@ -0,0 +1,38 @@
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+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 noStorageAccessInitially();
+
+    /* 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());
+    });
+  },
+  null, // extra prefs
+  false, // no window open test
+  false, // no user-interaction test
+  false, // 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 */
+    await noStorageAccessInitially();
+  }
+);
--- a/toolkit/components/antitracking/test/browser/head.js
+++ b/toolkit/components/antitracking/test/browser/head.js
@@ -1,62 +1,69 @@
 const TEST_DOMAIN = "http://example.net";
 const TEST_3RD_PARTY_DOMAIN = "https://tracking.example.org";
 const TEST_3RD_PARTY_DOMAIN_TP = "https://tracking.example.com";
+const TEST_4TH_PARTY_DOMAIN = "http://not-tracking.example.com";
 
 const TEST_PATH = "/browser/toolkit/components/antitracking/test/browser/";
 
 const TEST_TOP_PAGE = TEST_DOMAIN + 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_3RD_PARTY_PAGE = TEST_3RD_PARTY_DOMAIN + 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_4TH_PARTY_PAGE = TEST_4TH_PARTY_DOMAIN + TEST_PATH + "3rdParty.html";
 
 const BEHAVIOR_ACCEPT         = Ci.nsICookieService.BEHAVIOR_ACCEPT;
 const BEHAVIOR_REJECT_FOREIGN = Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN;
 const BEHAVIOR_REJECT_TRACKER = Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER;
 
 var gFeatures = undefined;
 
 let {UrlClassifierTestUtils} = ChromeUtils.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
 
 requestLongerTimeout(5);
 
 this.AntiTracking = {
   runTest(name, callbackTracking, callbackNonTracking, cleanupFunction, extraPrefs,
           windowOpenTest = true, userInteractionTest = true, expectedBlockingNotifications = true,
-          runInPrivateWindow = false, iframeSandbox = null) {
+          runInPrivateWindow = false, iframeSandbox = null, accessRemoval = null,
+          callbackAfterRemoval = null) {
     // Here we want to test that a 3rd party context is simply blocked.
     this._createTask({
       name,
       cookieBehavior: BEHAVIOR_REJECT_TRACKER,
       blockingByContentBlocking: true,
       blockingByContentBlockingRTUI: true,
       allowList: false,
       callback: callbackTracking,
       extraPrefs,
       expectedBlockingNotifications,
       runInPrivateWindow,
       iframeSandbox,
+      accessRemoval,
+      callbackAfterRemoval,
     });
     this._createCleanupTask(cleanupFunction);
 
     this._createTask({
       name,
       cookieBehavior: BEHAVIOR_REJECT_TRACKER,
       blockingByContentBlocking: true,
       blockingByContentBlockingRTUI: false,
       allowList: true,
       callback: callbackTracking,
       extraPrefs,
       expectedBlockingNotifications,
       runInPrivateWindow,
       iframeSandbox,
+      accessRemoval,
+      callbackAfterRemoval,
     });
     this._createCleanupTask(cleanupFunction);
 
     if (callbackNonTracking) {
       let runExtraTests = true;
       let options = {};
       if (typeof callbackNonTracking == "object") {
         options.callback = callbackNonTracking.callback;
@@ -80,16 +87,18 @@ this.AntiTracking = {
         }
         if ("blockingByAllowList" in callbackNonTracking) {
           options.blockingByAllowList =
             callbackNonTracking.blockingByAllowList;
         } else {
           options.blockingByAllowList = false;
         }
         callbackNonTracking = options.callback;
+        options.accessRemoval = null;
+        options.callbackAfterRemoval = null;
       }
 
       // 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.
@@ -102,241 +111,275 @@ this.AntiTracking = {
           blockingByContentBlocking: true,
           blockingByContentBlockingRTUI: true,
           allowList: false,
           callback: callbackNonTracking,
           extraPrefs: [],
           expectedBlockingNotifications: false,
           runInPrivateWindow,
           iframeSandbox,
+          accessRemoval: null, // only passed with non-blocking callback
+          callbackAfterRemoval: null,
         });
         this._createCleanupTask(cleanupFunction);
 
         this._createTask({
           name,
           cookieBehavior: BEHAVIOR_ACCEPT,
           blockingByContentBlocking: true,
           blockingByContentBlockingRTUI: false,
           allowList: true,
           callback: callbackNonTracking,
           extraPrefs: [],
           expectedBlockingNotifications: false,
           runInPrivateWindow,
           iframeSandbox,
+          accessRemoval: null, // only passed with non-blocking callback
+          callbackAfterRemoval: null,
         });
         this._createCleanupTask(cleanupFunction);
 
         this._createTask({
           name,
           cookieBehavior: BEHAVIOR_ACCEPT,
           blockingByContentBlocking: true,
           blockingByContentBlockingRTUI: false,
           allowList: false,
           callback: callbackNonTracking,
           extraPrefs: [],
           expectedBlockingNotifications: false,
           runInPrivateWindow,
           iframeSandbox,
+          accessRemoval: null, // only passed with non-blocking callback
+          callbackAfterRemoval: null,
         });
         this._createCleanupTask(cleanupFunction);
 
         this._createTask({
           name,
           cookieBehavior: BEHAVIOR_ACCEPT,
           blockingByContentBlocking: false,
           blockingByContentBlockingRTUI: true,
           allowList: false,
           callback: callbackNonTracking,
           extraPrefs: [],
           expectedBlockingNotifications: false,
           runInPrivateWindow,
           iframeSandbox,
+          accessRemoval: null, // only passed with non-blocking callback
+          callbackAfterRemoval: null,
         });
         this._createCleanupTask(cleanupFunction);
 
         this._createTask({
           name,
           cookieBehavior: BEHAVIOR_ACCEPT,
           blockingByContentBlocking: false,
           blockingByContentBlockingRTUI: false,
           allowList: true,
           callback: callbackNonTracking,
           extraPrefs: [],
           expectedBlockingNotifications: false,
           runInPrivateWindow,
           iframeSandbox,
+          accessRemoval: null, // only passed with non-blocking callback
+          callbackAfterRemoval: null,
         });
         this._createCleanupTask(cleanupFunction);
 
         this._createTask({
           name,
           cookieBehavior: BEHAVIOR_ACCEPT,
           blockingByContentBlocking: false,
           blockingByContentBlockingRTUI: false,
           allowList: false,
           callback: callbackNonTracking,
           extraPrefs: [],
           expectedBlockingNotifications: false,
           runInPrivateWindow,
           iframeSandbox,
+          accessRemoval: null, // only passed with non-blocking callback
+          callbackAfterRemoval: null,
         });
         this._createCleanupTask(cleanupFunction);
 
         this._createTask({
           name,
           cookieBehavior: BEHAVIOR_REJECT_FOREIGN,
           blockingByContentBlocking: false,
           blockingByContentBlockingRTUI: true,
           allowList: false,
           callback: callbackNonTracking,
           extraPrefs: [],
           expectedBlockingNotifications: false,
           runInPrivateWindow,
           iframeSandbox,
+          accessRemoval: null, // only passed with non-blocking callback
+          callbackAfterRemoval: null,
         });
         this._createCleanupTask(cleanupFunction);
 
         this._createTask({
           name,
           cookieBehavior: BEHAVIOR_REJECT_FOREIGN,
           blockingByContentBlocking: false,
           blockingByContentBlockingRTUI: false,
           allowList: true,
           callback: callbackNonTracking,
           extraPrefs: [],
           expectedBlockingNotifications: false,
           runInPrivateWindow,
           iframeSandbox,
+          accessRemoval: null, // only passed with non-blocking callback
+          callbackAfterRemoval: null,
         });
         this._createCleanupTask(cleanupFunction);
 
         this._createTask({
           name,
           cookieBehavior: BEHAVIOR_REJECT_FOREIGN,
           blockingByContentBlocking: false,
           blockingByContentBlockingRTUI: false,
           allowList: false,
           callback: callbackNonTracking,
           extraPrefs: [],
           expectedBlockingNotifications: false,
           runInPrivateWindow,
           iframeSandbox,
+          accessRemoval: null, // only passed with non-blocking callback
+          callbackAfterRemoval: null,
         });
         this._createCleanupTask(cleanupFunction);
 
         this._createTask({
           name,
           cookieBehavior: BEHAVIOR_REJECT_FOREIGN,
           blockingByContentBlocking: false,
           blockingByContentBlockingRTUI: true,
           allowList: true,
           callback: callbackNonTracking,
           extraPrefs: [],
           expectedBlockingNotifications: false,
           runInPrivateWindow,
           iframeSandbox,
+          accessRemoval: null, // only passed with non-blocking callback
+          callbackAfterRemoval: null,
         });
         this._createCleanupTask(cleanupFunction);
 
         this._createTask({
           name,
           cookieBehavior: BEHAVIOR_REJECT_FOREIGN,
           blockingByContentBlocking: true,
           blockingByContentBlockingRTUI: true,
           allowList: true,
           callback: callbackNonTracking,
           extraPrefs: [],
           expectedBlockingNotifications: false,
           runInPrivateWindow,
           iframeSandbox,
+          accessRemoval: null, // only passed with non-blocking callback
+          callbackAfterRemoval: null,
         });
         this._createCleanupTask(cleanupFunction);
 
         this._createTask({
           name,
           cookieBehavior: BEHAVIOR_REJECT_TRACKER,
           blockingByContentBlocking: false,
           blockingByContentBlockingRTUI: true,
           allowList: false,
           callback: callbackNonTracking,
           extraPrefs: [],
           expectedBlockingNotifications: false,
           runInPrivateWindow,
           iframeSandbox,
+          accessRemoval,
+          callbackAfterRemoval,
         });
         this._createCleanupTask(cleanupFunction);
 
         this._createTask({
           name,
           cookieBehavior: BEHAVIOR_REJECT_TRACKER,
           blockingByContentBlocking: false,
           blockingByContentBlockingRTUI: false,
           allowList: true,
           callback: callbackNonTracking,
           extraPrefs: [],
           expectedBlockingNotifications: false,
           runInPrivateWindow,
           iframeSandbox,
+          accessRemoval,
+          callbackAfterRemoval,
         });
         this._createCleanupTask(cleanupFunction);
 
         this._createTask({
           name,
           cookieBehavior: BEHAVIOR_REJECT_TRACKER,
           blockingByContentBlocking: false,
           blockingByContentBlockingRTUI: false,
           allowList: false,
           callback: callbackNonTracking,
           extraPrefs: [],
           expectedBlockingNotifications: false,
           runInPrivateWindow,
           iframeSandbox,
+          accessRemoval,
+          callbackAfterRemoval,
         });
         this._createCleanupTask(cleanupFunction);
 
         this._createTask({
           name,
           cookieBehavior: BEHAVIOR_REJECT_TRACKER,
           blockingByContentBlocking: false,
           blockingByContentBlockingRTUI: true,
           allowList: true,
           callback: callbackNonTracking,
           extraPrefs: [],
           expectedBlockingNotifications: false,
           runInPrivateWindow,
           iframeSandbox,
+          accessRemoval,
+          callbackAfterRemoval,
         });
         this._createCleanupTask(cleanupFunction);
 
         this._createTask({
           name,
           cookieBehavior: BEHAVIOR_REJECT_TRACKER,
           blockingByContentBlocking: true,
           blockingByContentBlockingRTUI: true,
           allowList: true,
           callback: callbackNonTracking,
           extraPrefs: [],
           expectedBlockingNotifications: false,
           runInPrivateWindow,
           iframeSandbox,
+          accessRemoval,
+          callbackAfterRemoval,
         });
         this._createCleanupTask(cleanupFunction);
       } else if (!options.blockingByContentBlocking) {
         // Don't run this extra test if we are in blocking mode!
         this._createTask({
           name,
           cookieBehavior: options.cookieBehavior,
           blockingByContentBlocking: options.blockingByContentBlocking,
           blockingByContentBlockingRTUI: options.blockingByContentBlockingRTUI,
           allowList: options.blockingByAllowList,
           callback: callbackNonTracking,
           extraPrefs: [],
           expectedBlockingNotifications: false,
           runInPrivateWindow,
           iframeSandbox,
+          accessRemoval,
+          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, callbackTracking, callbackNonTracking,
@@ -396,17 +439,21 @@ this.AntiTracking = {
 
   _createTask(options) {
     add_task(async function() {
       info("Starting " + (options.cookieBehavior != BEHAVIOR_ACCEPT ? "blocking" : "non-blocking") + " cookieBehavior (" + options.cookieBehavior + ") and " +
                          (options.blockingByContentBlocking ? "blocking" : "non-blocking") + " contentBlocking and " +
                          (options.blockingByContentBlockingRTUI ? "" : "no") + " contentBlocking third-party cookies UI with" +
                          (options.allowList ? "" : "out") + " allow list test " + options.name +
                          " running in a " + (options.runInPrivateWindow ? "private" : "normal") + " window " +
-                         " with iframe sandbox set to " + options.iframeSandbox);
+                         " with iframe sandbox set to " + options.iframeSandbox +
+                         " and access removal set to " + options.accessRemoval);
+
+      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,
@@ -498,23 +545,35 @@ this.AntiTracking = {
         info("Disabling content blocking for this page");
         win.ContentBlocking.disableForCurrentPage();
 
         // 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.blockingByContentBlocking &&
+                                  options.blockingByContentBlockingRTUI &&
+                                  !options.allowList;
       await ContentTask.spawn(browser,
                               { page: TEST_3RD_PARTY_PAGE,
+                                nextPage: TEST_4TH_PARTY_PAGE,
                                 callback: options.callback.toString(),
-                                iframeSandbox: options.iframeSandbox },
+                                callbackAfterRemoval: options.callbackAfterRemoval ?
+                                  options.callbackAfterRemoval.toString() : null,
+                                accessRemoval: options.accessRemoval,
+                                iframeSandbox: options.iframeSandbox,
+                                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");
             ifr.contentWindow.postMessage(obj.callback, "*");
           };
           if (typeof obj.iframeSandbox == "string") {
             ifr.setAttribute("sandbox", obj.iframeSandbox);
           }
 
@@ -536,16 +595,60 @@ this.AntiTracking = {
             }
 
             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;
+          default:
+            ok(false, "Unexpected accessRemoval code passed: " + obj.accessRemoval);
+            break;
+          }
+        }
       });
 
       if (options.allowList) {
         info("Enabling content blocking for this page");
         win.ContentBlocking.enableForCurrentPage();
 
         // The previous function reloads the browser, so wait for it to load again!
         await BrowserTestUtils.browserLoaded(browser);