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);