Shared Storage: Add test for SSW iframe navigation from service worker

We add some web tests to show that iframe navigations, with the
`sharedStorageWritable` attribute set to true, that are intercepted
by service workers but fallback to network can write to shared storage
via response headers.

Bug: 1434529,1218540
Change-Id: Ied0713969bdabc9d91a91e22e0597b39621c6639
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5105293
Commit-Queue: Cammie Smith Barnes <cammie@chromium.org>
Reviewed-by: Yao Xiao <yaoxia@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1236436}
diff --git a/shared-storage/resources/register-service-worker-iframe.https.html b/shared-storage/resources/register-service-worker-iframe.https.html
new file mode 100644
index 0000000..547ab1d
--- /dev/null
+++ b/shared-storage/resources/register-service-worker-iframe.https.html
@@ -0,0 +1,66 @@
+<!doctype html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/common/utils.js></script>
+  <script src=/fenced-frame/resources/utils.js></script>
+  <script src=/shared-storage/resources/util.js></script>
+  <script src=/shared-storage/resources/util.sub.js></script>
+  <script src=/service-workers/service-worker/resources/test-helpers.sub.js></script>
+  <script>
+    const INTERMEDIATE_FRAME_SUFFIX =
+      'able-fetch-request-fallback-to-network-iframe.https.html'
+    const ORIGIN = new URL("", location.href).origin;
+
+    window.addEventListener('message', async function handler(event) {
+      const data = event.data;
+      if (data.script && data.scope && data.port) {
+        var absoluteScope = (new URL(data.scope, window.location).href);
+        let oldReg =await navigator.serviceWorker.getRegistration(data.scope);
+        if (oldReg && oldReg.scope === absoluteScope) {
+          await oldReg.unregister();
+        }
+        let reg = await navigator.serviceWorker.register(data.script,
+                                                         { scope: data.scope });
+        let worker = reg.installing;
+        await new Promise(function(resolve) {
+          worker.addEventListener('statechange', function() {
+            if (worker.state == 'activated') {
+              resolve();
+            }
+          });
+        });
+        assert_not_equals(worker, null, 'worker is installing');
+
+        let result = await loadNestedSharedStorageFrameInNewFrame({
+          key: 'c', value: 'd',
+          hasSharedStorageWritableAttribute: true,
+          // Same-origin to this frame, cross-origin to top.
+          isSameOrigin: true,
+        });
+        const urls = [
+          {
+           "url": ORIGIN + data.scope + INTERMEDIATE_FRAME_SUFFIX,
+           "mode": "navigate",
+           "SSWHeader": "null"
+          },
+          {
+          "url": ORIGIN + "/resources/testharness.js",
+          "mode": "no-cors",
+          "SSWHeader": "null"
+          },
+          {
+            "url":  ORIGIN + result.nestedFrameUrl,
+            "mode": "navigate",
+            "SSWHeader": "null"
+          },
+        ];
+        await checkInterceptedUrls(worker, urls);
+        await verifyKeyValueForOrigin('c', 'd', ORIGIN);
+        await deleteKeyForOrigin('c', ORIGIN);
+        data.port.postMessage({msg: 'test completed'});
+        reg.unregister()
+        window.removeEventListener('message', handler);
+      }
+    });
+  </script>
+</body>
diff --git a/shared-storage/resources/shared-storage-writable-fetch-request-fallback-to-network-iframe.https.html b/shared-storage/resources/shared-storage-writable-fetch-request-fallback-to-network-iframe.https.html
index 8229ce8..2966723 100644
--- a/shared-storage/resources/shared-storage-writable-fetch-request-fallback-to-network-iframe.https.html
+++ b/shared-storage/resources/shared-storage-writable-fetch-request-fallback-to-network-iframe.https.html
@@ -18,5 +18,23 @@
         img.src = url;
       });
     }
+
+    function loadFrame(url, hasSharedStorageWritableAttribute) {
+      return new Promise(function(resolve, reject) {
+        var frame = document.createElement('iframe');
+        document.body.appendChild(frame);
+        frame.onload = function() {
+          window.parent.postMessage({msg: 'iframe loaded'}, "*");
+          resolve(frame);
+        };
+        frame.onerror = function() {
+          reject(new Error('Nested iframe load failed'));
+        };
+        if (hasSharedStorageWritableAttribute) {
+          frame.sharedStorageWritable = true;
+        }
+        frame.src = url;
+      });
+    }
   </script>
 </body>
diff --git a/shared-storage/resources/util.js b/shared-storage/resources/util.js
index f827658..4a7fcc4 100644
--- a/shared-storage/resources/util.js
+++ b/shared-storage/resources/util.js
@@ -196,3 +196,22 @@
   const result = await nextValueFromServer(outerKey);
   assert_equals(result, 'delete_key_loaded');
 }
+
+function getFetchedUrls(worker) {
+  return new Promise(function(resolve) {
+    var channel = new MessageChannel();
+    channel.port1.onmessage = function(msg) {
+      resolve(msg);
+    };
+    worker.postMessage({port: channel.port2}, [channel.port2]);
+  });
+}
+
+function checkInterceptedUrls(worker, expectedRequests) {
+  return getFetchedUrls(worker).then(function(msg) {
+    let actualRequests = msg.data.requests;
+    assert_equals(actualRequests.length, expectedRequests.length);
+    assert_equals(
+        JSON.stringify(actualRequests), JSON.stringify(expectedRequests));
+  });
+}
diff --git a/shared-storage/resources/util.sub.js b/shared-storage/resources/util.sub.js
index 970c33b..f147209 100644
--- a/shared-storage/resources/util.sub.js
+++ b/shared-storage/resources/util.sub.js
@@ -69,3 +69,49 @@
   document.body.appendChild(frame);
   return promise;
 }
+
+async function loadNestedSharedStorageFrameInNewFrame(data) {
+  const SCOPE = '/shared-storage/resources/shared-storage-writ';
+  const INTERMEDIATE_FRAME_SUFFIX =
+      'able-fetch-request-fallback-to-network-iframe.https.html'
+  const CROSS_ORIGIN = 'https://{{domains[www]}}:{{ports[https][0]}}';
+
+  let {key, value, hasSharedStorageWritableAttribute, isSameOrigin} = data;
+
+  const windowPromise = new Promise((resolve, reject) => {
+    window.addEventListener('message', async function handler(evt) {
+      if (evt.data.msg && evt.data.msg === 'iframe loaded') {
+        window.removeEventListener('message', handler);
+        resolve();
+      }
+    });
+    window.addEventListener('error', () => {
+      reject(new Error('Navigation error'));
+    });
+  });
+
+  const framePromise = new Promise((resolve, reject) => {
+    let frame = document.createElement('iframe');
+    frame.src = SCOPE + INTERMEDIATE_FRAME_SUFFIX;
+    frame.onload = function() {
+      resolve(frame);
+    };
+    frame.onerror = function() {
+      reject(new Error('Iframe load failed'));
+    };
+    document.body.appendChild(frame);
+  });
+  let frame = await framePromise;
+
+  let rawWriteHeader = `set;key=${key};value=${value}`;
+  let writeHeader = encodeURIComponent(rawWriteHeader);
+  const sameOriginNestedSrc = `/shared-storage/resources/` +
+      `shared-storage-write.py?write=${writeHeader}`;
+  const nestedSrc =
+      isSameOrigin ? sameOriginNestedSrc : CROSS_ORIGIN + sameOriginNestedSrc;
+
+  let nestedFrame = frame.contentWindow.loadFrame(
+      nestedSrc, hasSharedStorageWritableAttribute);
+  await windowPromise;
+  return {frame: frame, nestedFrame: nestedFrame, nestedFrameUrl: nestedSrc};
+}
diff --git a/shared-storage/shared-storage-writable-service-worker-iframe.tentative.https.sub.html b/shared-storage/shared-storage-writable-service-worker-iframe.tentative.https.sub.html
new file mode 100644
index 0000000..9eb2820
--- /dev/null
+++ b/shared-storage/shared-storage-writable-service-worker-iframe.tentative.https.sub.html
@@ -0,0 +1,98 @@
+<!doctype html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/common/utils.js></script>
+  <script src=/fenced-frame/resources/utils.js></script>
+  <script src=/shared-storage/resources/util.js></script>
+  <script src=/shared-storage/resources/util.sub.js></script>
+  <script src=/service-workers/service-worker/resources/test-helpers.sub.js></script>
+  <script>
+    const SCOPE = '/shared-storage/resources/shared-storage-writ';
+    const INTERMEDIATE_FRAME_SUFFIX =
+      'able-fetch-request-fallback-to-network-iframe.https.html'
+    const SCRIPT = '/shared-storage/resources/'
+      + 'shared-storage-writable-fetch-request-fallback-to-network-worker.js';
+    const WORKER_FRAME = '/shared-storage/resources/'
+      + 'register-service-worker-iframe.https.html';
+    const SAME_ORIGIN = new URL("", location.href).origin;
+    const CROSS_ORIGIN = 'https://{{domains[www]}}:{{ports[https][0]}}';
+
+    promise_test(async t => {
+      await service_worker_unregister(t, SCOPE);
+      let reg = await navigator.serviceWorker.register(SCRIPT,
+                                                       { scope: SCOPE });
+      t.add_cleanup(_ => reg.unregister());
+      let worker = reg.installing;
+      await wait_for_state(t, worker, 'activated');
+      assert_not_equals(worker, null, 'worker is installing');
+
+      let {frame, nestedFrame, nestedFrameUrl} =
+        await loadNestedSharedStorageFrameInNewFrame({
+          key: 'a', value: 'b',
+          hasSharedStorageWritableAttribute: true,
+          isSameOrigin: true,
+        });
+      t.add_cleanup(function() {
+        frame.remove();
+      });
+      checkInterceptedUrls(worker, [
+        {
+           "url": SAME_ORIGIN + SCOPE + INTERMEDIATE_FRAME_SUFFIX,
+           "mode": "navigate",
+           "SSWHeader": "null"
+        },
+        {
+          "url": SAME_ORIGIN + "/resources/testharness.js",
+          "mode": "no-cors",
+          "SSWHeader": "null"
+        },
+        {
+          "url": SAME_ORIGIN + nestedFrameUrl,
+          "mode": "navigate",
+          "SSWHeader": "null"
+        },
+      ]);
+      await verifyKeyValueForOrigin('a', 'b', SAME_ORIGIN);
+      await deleteKeyForOrigin('a', SAME_ORIGIN);
+    }, 'test <iframe sharedstoragewritable src=[url]> via JS from service '
+       + 'worker for same origin iframe');
+
+    promise_test(async t => {
+      const workerFramePromise = new Promise((resolve, reject) => {
+        let workerFrame = document.createElement('iframe');
+        workerFrame.src = CROSS_ORIGIN + WORKER_FRAME;
+        workerFrame.id = 'worker_frame';
+        workerFrame.onload = function() {
+          resolve(workerFrame);
+        };
+        workerFrame.onerror = function() {
+          reject(new Error('Worker frame load failed'));
+        };
+        t.add_cleanup(function() {
+          workerFrame.remove();
+        });
+        document.body.appendChild(workerFrame);
+      });
+      let workerFrame = await workerFramePromise;
+
+      const messagePromise = new Promise((resolve, reject) => {
+        let channel = new MessageChannel();
+        channel.port1.onmessage = function(e) {
+          resolve(e.data);
+        };
+        let message = {
+            script: SCRIPT,
+            scope: SCOPE,
+            port: channel.port2,
+        };
+        document.getElementById('worker_frame').contentWindow
+          .postMessage(message, "*",
+                       [channel.port2]);
+      });
+      let {msg} = await messagePromise;
+      assert_equals(msg, 'test completed');
+    }, 'test <iframe sharedstoragewritable src=[url]> via JS from service '
+       + 'worker for cross origin iframe');
+  </script>
+</body>
diff --git a/shared-storage/shared-storage-writable-service-worker-img.tentative.https.sub.html b/shared-storage/shared-storage-writable-service-worker-img.tentative.https.sub.html
index 9e7326d..6d48155 100644
--- a/shared-storage/shared-storage-writable-service-worker-img.tentative.https.sub.html
+++ b/shared-storage/shared-storage-writable-service-worker-img.tentative.https.sub.html
@@ -7,23 +7,6 @@
   <script src=/shared-storage/resources/util.js></script>
   <script src=/service-workers/service-worker/resources/test-helpers.sub.js></script>
   <script>
-    function getFetchedUrls(worker) {
-      return new Promise(function(resolve) {
-          var channel = new MessageChannel();
-          channel.port1.onmessage = function(msg) { resolve(msg); };
-          worker.postMessage({port: channel.port2}, [channel.port2]);
-        });
-    }
-
-    function checkInterceptedUrls(worker, expectedRequests) {
-      return getFetchedUrls(worker)
-        .then(function(msg) {
-          let actualRequests = msg.data.requests;
-          assert_equals(actualRequests.length, expectedRequests.length);
-          assert_equals(JSON.stringify(actualRequests), JSON.stringify(expectedRequests));
-        });
-    }
-
     const SCOPE = '/shared-storage/resources/'
           + 'shared-storage-writable-fetch-request-fallback-to-network-iframe.https.html';
     const SCRIPT = '/shared-storage/resources/'
@@ -31,10 +14,11 @@
     const SAME_ORIGIN = new URL("", location.href).origin;
     const CROSS_ORIGIN = 'https://{{domains[www]}}:{{ports[https][0]}}';
 
-    async function loadSharedStorageImageFromNewFrame(data) {
-      let {test, key, value, hasSharedStorageWritableAttribute, isSameOrigin} = data;
+    async function loadSharedStorageImageInNewFrame(data) {
+      let {test, key, value, hasSharedStorageWritableAttribute, isSameOrigin}
+        = data;
 
-      const frame_promise = new Promise((resolve, reject) => {
+      const framePromise = new Promise((resolve, reject) => {
         let frame = document.createElement('iframe');
         frame.src = SCOPE;
         frame.onload = function() {
@@ -48,27 +32,30 @@
         });
         document.body.appendChild(frame);
       });
-      let frame = await frame_promise;
+      let frame = await framePromise;
 
       const sameOriginImgSrc = `/shared-storage/resources/` +
         `shared-storage-writable-pixel.png?key=${key}&value=${value}`;
-      const imgSrc = isSameOrigin ? sameOriginImgSrc : CROSS_ORIGIN + sameOriginImgSrc;
+      const imgSrc = isSameOrigin ?
+        sameOriginImgSrc :
+        CROSS_ORIGIN + sameOriginImgSrc;
       return {
         loadedImage: frame.contentWindow.loadImage(imgSrc,
-                                                   hasSharedStorageWritableAttribute),
+          hasSharedStorageWritableAttribute),
         imageUrl: imgSrc,
       };
     }
 
     promise_test(async t => {
       await service_worker_unregister(t, SCOPE);
-      let reg = await navigator.serviceWorker.register(SCRIPT, { scope: SCOPE });
+      let reg = await navigator.serviceWorker.register(SCRIPT,
+                                                       { scope: SCOPE });
       t.add_cleanup(_ => reg.unregister());
       let worker = reg.installing;
       await wait_for_state(t, worker, 'activated');
       assert_not_equals(worker, null, 'worker is installing');
 
-      let {loadedImage, imageUrl} =  await loadSharedStorageImageFromNewFrame({
+      let {loadedImage, imageUrl} = await loadSharedStorageImageInNewFrame({
         test: t,
         key: 'a', value: 'b',
         hasSharedStorageWritableAttribute: true,
@@ -76,7 +63,11 @@
       });
       checkInterceptedUrls(worker, [
         {"url": SAME_ORIGIN + SCOPE, "mode": "navigate", "SSWHeader": "null"},
-        {"url": SAME_ORIGIN + "/resources/testharness.js", "mode": "no-cors", "SSWHeader": "null"},
+        {
+          "url": SAME_ORIGIN + "/resources/testharness.js",
+          "mode": "no-cors",
+          "SSWHeader": "null"
+        },
         {"url": SAME_ORIGIN + imageUrl, "mode": "no-cors", "SSWHeader": "null"},
       ]);
       await verifyKeyValueForOrigin('a', 'b', SAME_ORIGIN);
@@ -86,13 +77,14 @@
 
     promise_test(async t => {
       await service_worker_unregister(t, SCOPE);
-      let reg = await navigator.serviceWorker.register(SCRIPT, { scope: SCOPE });
+      let reg = await navigator.serviceWorker.register(SCRIPT,
+                                                       { scope: SCOPE });
       t.add_cleanup(_ => reg.unregister());
       let worker = reg.installing;
       await wait_for_state(t, worker, 'activated');
       assert_not_equals(worker, null, 'worker is installing');
 
-      let {loadedImage, imageUrl} =  await loadSharedStorageImageFromNewFrame({
+      let {loadedImage, imageUrl} = await loadSharedStorageImageInNewFrame({
         test: t,
         key: 'c', value: 'd',
         hasSharedStorageWritableAttribute: true,
@@ -100,7 +92,11 @@
       });
       checkInterceptedUrls(worker, [
         {"url": SAME_ORIGIN + SCOPE, "mode": "navigate", "SSWHeader": "null"},
-        {"url": SAME_ORIGIN + "/resources/testharness.js", "mode": "no-cors", "SSWHeader": "null"},
+        {
+          "url": SAME_ORIGIN + "/resources/testharness.js",
+          "mode": "no-cors",
+          "SSWHeader": "null"
+        },
         {"url": imageUrl, "mode": "no-cors", "SSWHeader": "null"},
       ]);
       await verifyKeyValueForOrigin('c', 'd', CROSS_ORIGIN);