Bug 1494476 - Part 4: Add tests for requiring interaction with a tracker in the first-party context before granting a third-party storage exception for it r=baku
authorEhsan Akhgari <ehsan@mozilla.com>
Wed, 10 Oct 2018 15:46:57 +0000
changeset 498995 0c6a935d5c0af0036a69d4b69d7bf8f5a00bccb7
parent 498994 a9d96c509598da0fd8f3a9ae2ef6909ebf1f15ed
child 498996 b4523b784c7ce4d0f2ec700d6318fe5e77b06f75
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
bugs1494476
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 1494476 - Part 4: Add tests for requiring interaction with a tracker in the first-party context before granting a third-party storage exception for it r=baku Depends on D8157 Differential Revision: https://phabricator.services.mozilla.com/D8158
toolkit/components/antitracking/test/browser/3rdPartyOpenUI.html
toolkit/components/antitracking/test/browser/3rdPartyWO.html
toolkit/components/antitracking/test/browser/browser_blockingWorkers.js
toolkit/components/antitracking/test/browser/browser_script.js
toolkit/components/antitracking/test/browser/browser_storageAccessPrivateWindow.js
toolkit/components/antitracking/test/browser/browser_storageAccessSandboxed.js
toolkit/components/antitracking/test/browser/browser_storageAccessWithHeuristics.js
toolkit/components/antitracking/test/browser/head.js
toolkit/components/antitracking/test/browser/storageAccessAPIHelpers.js
--- a/toolkit/components/antitracking/test/browser/3rdPartyOpenUI.html
+++ b/toolkit/components/antitracking/test/browser/3rdPartyOpenUI.html
@@ -2,14 +2,17 @@
 <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>
 
--- a/toolkit/components/antitracking/test/browser/3rdPartyWO.html
+++ b/toolkit/components/antitracking/test/browser/3rdPartyWO.html
@@ -34,16 +34,47 @@ onmessage = function(e) {
       } 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 ontain 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" }, "*");
   });
 };
 
--- a/toolkit/components/antitracking/test/browser/browser_blockingWorkers.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingWorkers.js
@@ -78,17 +78,17 @@ AntiTracking.runTest("SharedWorkers and 
 
     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", "foo");
+    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);
--- a/toolkit/components/antitracking/test/browser/browser_script.js
+++ b/toolkit/components/antitracking/test/browser/browser_script.js
@@ -35,16 +35,82 @@ add_task(async function() {
         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 ContentTask.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) {
--- a/toolkit/components/antitracking/test/browser/browser_storageAccessPrivateWindow.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessPrivateWindow.js
@@ -5,16 +5,21 @@ AntiTracking.runTest("Storage Access API
   async _ => {
     /* import-globals-from storageAccessAPIHelpers.js */
     let [threw, rejected] = await callRequestStorageAccess();
     ok(!threw, "requestStorageAccess should not throw");
     ok(rejected, "requestStorageAccess shouldn't be available");
   },
 
   null, // non-blocking callback
-  null, // cleanup function
+  // cleanup function
+  async _ => {
+    await new Promise(resolve => {
+      Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
+    });
+  },
   [["dom.storage_access.enabled", true]], // extra prefs
   false, // no window open test
   false, // no user-interaction test
   false, // no blocking notifications
   true, // run in private window
   null // iframe sandbox
 );
--- a/toolkit/components/antitracking/test/browser/browser_storageAccessSandboxed.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessSandboxed.js
@@ -7,23 +7,33 @@ AntiTracking.runTest("Storage Access API
   async _ => {
     /* import-globals-from storageAccessAPIHelpers.js */
     let [threw, rejected] = await callRequestStorageAccess();
     ok(!threw, "requestStorageAccess should not throw");
     ok(rejected, "requestStorageAccess shouldn't be available");
   },
 
   null, // non-blocking callback
-  null, // cleanup function
+  // 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]], // extra prefs
   false, // no window open test
   false, // no user-interaction test
   false, // no blocking notifications
   false, // run in normal window
-  "allow-scripts allow-same-origin"
+  "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 _ => {
     /* import-globals-from storageAccessAPIHelpers.js */
     let [threw, rejected] = await callRequestStorageAccess();
@@ -47,17 +57,17 @@ AntiTracking.runTest("Storage Access API
       Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
     });
   },
   [["dom.storage_access.enabled", true]], // extra prefs
   false, // no window open test
   false, // no user-interaction test
   true, // expect blocking notifications
   false, // run in normal window
-  "allow-scripts allow-same-origin allow-storage-access-by-user-activation"
+  "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 _ => {
     /* import-globals-from storageAccessAPIHelpers.js */
     await noStorageAccessInitially();
 
@@ -72,17 +82,17 @@ AntiTracking.runTest("Verify that sandbo
 
   null, // non-blocking callback
   null, // cleanup function
   [["dom.storage_access.enabled", true]], // extra prefs
   false, // no window open test
   false, // no user-interaction test
   false, // no blocking notifications
   false, // run in normal window
-  "allow-scripts allow-same-origin"
+  "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 _ => {
     /* import-globals-from storageAccessAPIHelpers.js */
@@ -94,17 +104,17 @@ AntiTracking.runTest("Verify that sandbo
 
   null, // non-blocking callback
   null, // cleanup function
   [["dom.storage_access.enabled", true]], // extra prefs
   false, // no window open test
   false, // no user-interaction test
   false, // no blocking notifications
   false, // run in normal window
-  "allow-scripts allow-same-origin allow-storage-access-by-user-activation"
+  "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 _ => {
     /* import-globals-from storageAccessAPIHelpers.js */
     await noStorageAccessInitially();
 
--- a/toolkit/components/antitracking/test/browser/browser_storageAccessWithHeuristics.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessWithHeuristics.js
@@ -105,16 +105,100 @@ add_task(async function testUserInteract
                                      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 }, "*");
+    });
+
+    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.open(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 ContentTask.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();
 
     info("Checking if storage access is denied");
 
     let ifr = content.document.createElement("iframe");
--- a/toolkit/components/antitracking/test/browser/head.js
+++ b/toolkit/components/antitracking/test/browser/head.js
@@ -350,16 +350,31 @@ this.AntiTracking = {
         this._createUserInteractionTask(name, callbackTracking, callbackNonTracking,
                                         runInPrivateWindow, iframeSandbox,
                                         expectedBlockingNotifications, extraPrefs);
         this._createCleanupTask(cleanupFunction);
       }
     }
   },
 
+  async interactWithTracker() {
+    let windowClosed = new Promise(resolve => {
+      Services.ww.registerNotification(function notification(aSubject, aTopic, aData) {
+        if (aTopic == "domwindowclosed") {
+          Services.ww.unregisterNotification(notification);
+          resolve();
+        }
+      });
+    });
+
+    info("Let's interact with the tracker");
+    window.open(TEST_3RD_PARTY_DOMAIN + TEST_PATH + "3rdPartyOpenUI.html");
+    await windowClosed;
+  },
+
   async _setupTest(win, cookieBehavior, blockingByContentBlocking,
                    blockingByContentBlockingRTUI,
                    extraPrefs) {
     await SpecialPowers.flushPrefEnv();
     await SpecialPowers.pushPrefEnv({"set": [
       ["dom.storage_access.enabled", true],
       ["browser.contentblocking.allowlist.annotations.enabled", blockingByContentBlockingRTUI],
       ["browser.contentblocking.allowlist.storage.enabled", blockingByContentBlockingRTUI],
@@ -406,30 +421,35 @@ this.AntiTracking = {
           if (state & Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER) {
             ++cookieBlocked;
           }
           let contentBlockingLog = {};
           try {
             contentBlockingLog = JSON.parse(contentBlockingLogJSON);
           } catch (e) {
           }
+
+          let trackerInteractionHelper = false;
+          if (request) {
+            request.QueryInterface(Ci.nsIChannel);
+            trackerInteractionHelper = request.URI.spec.endsWith("?messageme");
+          }
+
           // If this is the first cookie to be blocked, our state should have
           // just changed, otherwise it should have previously contained the
           // STATE_COOKIES_BLOCKED_TRACKER bit too.
           if (options.expectedBlockingNotifications && cookieBlocked &&
-              !options.allowList) {
+              !options.allowList && !trackerInteractionHelper) {
             if (cookieBlocked == 1) {
               is(oldState & Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER, 0,
                  "When blocking the first cookie, old state should not have had the " +
                  "STATE_COOKIES_BLOCKED_TRACKER bit");
             }
 
-            let trackerOriginCount = 0;
             for (let trackerOrigin in contentBlockingLog) {
-              ++trackerOriginCount;
               is(trackerOrigin, TEST_3RD_PARTY_DOMAIN, "Correct tracker origin must be reported");
               let originLog = contentBlockingLog[trackerOrigin];
               if (originLog.length == 1) {
                 is(originLog[0][0], Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER,
                    "Correct blocking type reported");
                 is(originLog[0][1], true,
                    "Correct blocking status reported");
                 ok(originLog[0][2] >= 1,
@@ -451,17 +471,19 @@ this.AntiTracking = {
                 is(originLog[1][0], Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER,
                    "Correct blocking type reported");
                 is(originLog[1][1], false,
                    "Correct blocking status reported");
                 is(originLog[1][2], 1,
                    "Correct repeat count reported");
               }
             }
-            is(trackerOriginCount, 1, "Should only report one tracker origin");
+            // Can't assert the number of tracker origins because we may get 0
+            // for web progress navigations coming from the window opening from
+            // storage access API tracker interaction attempts...
           }
           if (!options.expectedBlockingNotifications) {
             is(oldState & Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER, 0,
                "When not blocking, old state should not have had the " +
                "STATE_COOKIES_BLOCKED_TRACKER bit");
             is(Object.keys(contentBlockingLog).length, 0,
                "Content blocking log JSON must be empty");
           }
@@ -765,17 +787,16 @@ this.AntiTracking = {
       let browser = win.gBrowser.getBrowserForTab(tab);
       await BrowserTestUtils.browserLoaded(browser);
 
       info("Creating a 3rd party content");
       await ContentTask.spawn(browser,
                               { page: TEST_3RD_PARTY_PAGE_UI,
                                 popup: TEST_POPUP_PAGE,
                                 blockingCallback: blockingCallback.toString(),
-                                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);
         }
@@ -817,17 +838,76 @@ this.AntiTracking = {
         });
 
         info("Opening a window from the iframe.");
         ifr.contentWindow.open(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.");
+        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 }, "*");
+        });
+      });
+
+      await AntiTracking.interactWithTracker();
+
+      await ContentTask.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.");
+        ifr.contentWindow.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;
             }
 
--- a/toolkit/components/antitracking/test/browser/storageAccessAPIHelpers.js
+++ b/toolkit/components/antitracking/test/browser/storageAccessAPIHelpers.js
@@ -7,16 +7,73 @@ async function noStorageAccessInitially(
   let hasAccess = await document.hasStorageAccess();
   ok(!hasAccess, "Doesn't yet have storage access");
 }
 
 async function callRequestStorageAccess(callback) {
   let dwu = SpecialPowers.getDOMWindowUtils(window);
   let helper = dwu.setHandlingUserInput(true);
 
+  let success = true;
+  // We only grant storage exceptions when the reject tracker behavior is enabled.
+  let rejectTrackers = SpecialPowers.Services.prefs.getIntPref("network.cookie.cookieBehavior") ==
+                         SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER &&
+                       SpecialPowers.Services.prefs.getBoolPref("browser.contentblocking.enabled") &&
+                       !isOnContentBlockingAllowList();
+  if (rejectTrackers) {
+    let p;
+    let threw = false;
+    try {
+      p = document.requestStorageAccess();
+    } catch (e) {
+      threw = true;
+    } finally {
+      helper.destruct();
+    }
+    ok(!threw, "requestStorageAccess should not throw");
+    try {
+      if (callback) {
+        await p.then(_ => callback(dwu));
+      } else {
+        await p;
+      }
+    } catch (e) {
+      success = false;
+    }
+    ok(!success, "Should not have worked without user interaction");
+
+    await noStorageAccessInitially();
+
+    await interactWithTracker();
+
+    helper = dwu.setHandlingUserInput(true);
+  }
+  if (SpecialPowers.Services.prefs.getIntPref("network.cookie.cookieBehavior") ==
+        SpecialPowers.Ci.nsICookieService.BEHAVIOR_ACCEPT &&
+      !isOnContentBlockingAllowList()) {
+    try {
+      if (callback) {
+        await document.requestStorageAccess().then(_ => callback(dwu));
+      } else {
+        await document.requestStorageAccess();
+      }
+    } catch (e) {
+      success = false;
+    } finally {
+      helper.destruct();
+    }
+    ok(success, "Should not have thrown");
+
+    await noStorageAccessInitially();
+
+    await interactWithTracker();
+
+    helper = dwu.setHandlingUserInput(true);
+  }
+
   let p;
   let threw = false;
   try {
     p = document.requestStorageAccess();
   } catch (e) {
     threw = true;
   } finally {
     helper.destruct();
@@ -27,16 +84,69 @@ async function callRequestStorageAccess(
       await p.then(_ => callback(dwu));
     } else {
       await p;
     }
   } catch (e) {
     rejected = true;
   }
 
-  let success = !threw && !rejected;
+  success = rejectTrackers && !threw && !rejected;
   let hasAccess = await document.hasStorageAccess();
   is(hasAccess, success,
      "Should " + (success ? "" : "not ") + "have storage access now");
+  if (success) {
+    // Wait until the permission is visible in our process to avoid race
+    // conditions.
+    await waitUntilPermission("http://example.net/browser/toolkit/components/antitracking/test/browser/page.html",
+                              "3rdPartyStorage^https://tracking.example.org");
+  }
 
   return [threw, rejected];
 }
 
+async function waitUntilPermission(url, name) {
+  await new Promise(resolve => {
+    let id = setInterval(_ => {
+      let Services = SpecialPowers.Services;
+      let uri = Services.io.newURI(url);
+      if (Services.perms.testPermission(uri, name) ==
+            Services.perms.ALLOW_ACTION) {
+        clearInterval(id);
+        resolve();
+      }
+    }, 0);
+  });
+}
+
+async function interactWithTracker() {
+  await new Promise(resolve => {
+    onmessage = resolve;
+
+    info("Let's interact with the tracker");
+    window.open("https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/3rdPartyOpenUI.html?messageme");
+  });
+
+  // Wait until the user interaction permission becomes visible in our process
+  await waitUntilPermission("https://tracking.example.org",
+                            "storageAccessAPI");
+}
+
+function isOnContentBlockingAllowList() {
+  let prefs = ["browser.contentblocking.allowlist.storage.enabled",
+               "browser.contentblocking.allowlist.annotations.enabled"];
+  function allEnabled(prev, pref) {
+    return pref &&
+           SpecialPowers.Services.prefs.getBoolPref(pref);
+  }
+  if (!prefs.reduce(allEnabled)) {
+    return false;
+  }
+
+  let url = new URL(SpecialPowers.wrap(top).location.href);
+  let origin = SpecialPowers.Services.io.newURI("https://" + url.host);
+  let types = ["trackingprotection", "trackingprotection-pb"];
+  return types.some(type => {
+    return SpecialPowers.Services.perms.testPermission(origin, type) ==
+             SpecialPowers.Services.perms.ALLOW_ACTION;
+  });
+}
+