summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/storage-access-api
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/storage-access-api
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/storage-access-api')
-rw-r--r--testing/web-platform/tests/storage-access-api/META.yml6
-rw-r--r--testing/web-platform/tests/storage-access-api/hasStorageAccess-ABA.tentative.sub.https.window.js7
-rw-r--r--testing/web-platform/tests/storage-access-api/hasStorageAccess-insecure.sub.window.js46
-rw-r--r--testing/web-platform/tests/storage-access-api/hasStorageAccess.sub.https.window.js55
-rw-r--r--testing/web-platform/tests/storage-access-api/helpers.js306
-rw-r--r--testing/web-platform/tests/storage-access-api/idlharness.window.js14
-rw-r--r--testing/web-platform/tests/storage-access-api/requestStorageAccess-ABA.tentative.sub.https.window.js9
-rw-r--r--testing/web-platform/tests/storage-access-api/requestStorageAccess-cross-origin-iframe-navigation.sub.https.window.js87
-rw-r--r--testing/web-platform/tests/storage-access-api/requestStorageAccess-cross-site-iframe.sub.https.window.js9
-rw-r--r--testing/web-platform/tests/storage-access-api/requestStorageAccess-cross-site-sibling-iframes.sub.https.window.js84
-rw-r--r--testing/web-platform/tests/storage-access-api/requestStorageAccess-dedicated-worker.tentative.sub.https.window.js67
-rw-r--r--testing/web-platform/tests/storage-access-api/requestStorageAccess-insecure.sub.window.js82
-rw-r--r--testing/web-platform/tests/storage-access-api/requestStorageAccess-nested-cross-origin-iframe.sub.https.window.js10
-rw-r--r--testing/web-platform/tests/storage-access-api/requestStorageAccess-nested-cross-site-iframe.sub.https.window.js10
-rw-r--r--testing/web-platform/tests/storage-access-api/requestStorageAccess-nested-same-origin-iframe.sub.https.window.js8
-rw-r--r--testing/web-platform/tests/storage-access-api/requestStorageAccess-non-fully-active.sub.https.window.js19
-rw-r--r--testing/web-platform/tests/storage-access-api/requestStorageAccess-same-site-iframe.sub.https.window.js8
-rw-r--r--testing/web-platform/tests/storage-access-api/requestStorageAccess-web-socket.tentative.sub.https.window.js51
-rw-r--r--testing/web-platform/tests/storage-access-api/requestStorageAccess.sub.https.window.js104
-rw-r--r--testing/web-platform/tests/storage-access-api/resources/echo-cookie-header.py12
-rw-r--r--testing/web-platform/tests/storage-access-api/resources/embedded_responder.js97
-rw-r--r--testing/web-platform/tests/storage-access-api/resources/embedded_worker.js17
-rw-r--r--testing/web-platform/tests/storage-access-api/resources/hasStorageAccess-ABA-iframe.https.html9
-rw-r--r--testing/web-platform/tests/storage-access-api/resources/hasStorageAccess-ABA-iframe.sub.https.window.js11
-rw-r--r--testing/web-platform/tests/storage-access-api/resources/hasStorageAccess-iframe.html8
-rw-r--r--testing/web-platform/tests/storage-access-api/resources/hasStorageAccess-iframe.https.html10
-rw-r--r--testing/web-platform/tests/storage-access-api/resources/permissions-iframe.https.html10
-rw-r--r--testing/web-platform/tests/storage-access-api/resources/requestStorageAccess-ABA-iframe.https.html7
-rw-r--r--testing/web-platform/tests/storage-access-api/resources/requestStorageAccess-ABA-iframe.sub.https.window.js12
-rw-r--r--testing/web-platform/tests/storage-access-api/resources/requestStorageAccess-iframe.html10
-rw-r--r--testing/web-platform/tests/storage-access-api/resources/requestStorageAccess-iframe.https.html11
-rw-r--r--testing/web-platform/tests/storage-access-api/resources/script-with-cookie-header.py19
-rw-r--r--testing/web-platform/tests/storage-access-api/resources/set-cookie-header.py12
-rw-r--r--testing/web-platform/tests/storage-access-api/resources/storage-access-beyond-cookies-iframe-iframe.html259
-rw-r--r--testing/web-platform/tests/storage-access-api/resources/storage-access-beyond-cookies-iframe.sub.html131
-rw-r--r--testing/web-platform/tests/storage-access-api/sandboxAttribute.window.js7
-rw-r--r--testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.BroadcastChannel.tentative.sub.https.window.js37
-rw-r--r--testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.blobStorage.tentative.sub.https.window.js31
-rw-r--r--testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.caches.tentative.sub.https.window.js32
-rw-r--r--testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.cookies.tentative.sub.https.window.js34
-rw-r--r--testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.estimate.tentative.sub.https.window.js37
-rw-r--r--testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.getDirectory.tentative.sub.https.window.js34
-rw-r--r--testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.indexedDB.tentative.sub.https.window.js33
-rw-r--r--testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.localStorage.tentative.sub.https.window.js40
-rw-r--r--testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.locks.tentative.sub.https.window.js34
-rw-r--r--testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.none.tentative.sub.https.window.js29
-rw-r--r--testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.sessionStorage.tentative.sub.https.window.js40
-rw-r--r--testing/web-platform/tests/storage-access-api/storage-access-permission.sub.https.window.js108
-rw-r--r--testing/web-platform/tests/storage-access-api/storageAccess.testdriver.sub.html31
49 files changed, 2144 insertions, 0 deletions
diff --git a/testing/web-platform/tests/storage-access-api/META.yml b/testing/web-platform/tests/storage-access-api/META.yml
new file mode 100644
index 0000000000..554bd31684
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/META.yml
@@ -0,0 +1,6 @@
+spec: https://privacycg.github.io/storage-access/
+suggested_reviewers:
+ - Brandr0id
+ - cfredric
+ - ehsan
+ - johnwilander
diff --git a/testing/web-platform/tests/storage-access-api/hasStorageAccess-ABA.tentative.sub.https.window.js b/testing/web-platform/tests/storage-access-api/hasStorageAccess-ABA.tentative.sub.https.window.js
new file mode 100644
index 0000000000..8fa4122fbb
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/hasStorageAccess-ABA.tentative.sub.https.window.js
@@ -0,0 +1,7 @@
+// META: script=helpers.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+'use strict';
+
+// Create a test with a nested iframe that is same-site to the top-level frame but has cross-site frame in between.
+RunTestsInIFrame("https://{{hosts[alt][]}}:{{ports[https][0]}}/storage-access-api/resources/hasStorageAccess-ABA-iframe.https.html");
diff --git a/testing/web-platform/tests/storage-access-api/hasStorageAccess-insecure.sub.window.js b/testing/web-platform/tests/storage-access-api/hasStorageAccess-insecure.sub.window.js
new file mode 100644
index 0000000000..866bd97f27
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/hasStorageAccess-insecure.sub.window.js
@@ -0,0 +1,46 @@
+// META: script=helpers.js
+'use strict';
+
+const {testPrefix, topLevelDocument} = processQueryParams();
+
+// Common tests to run in all frames.
+promise_test(async () => {
+ assert_not_equals(document.hasStorageAccess, undefined);
+}, "[" + testPrefix + "] document.hasStorageAccess() should be supported on the document interface");
+
+promise_test(async () => {
+ const hasAccess = await document.hasStorageAccess();
+ assert_false(hasAccess, "Access should be disallowed in insecure contexts");
+}, "[" + testPrefix + "] document.hasStorageAccess() should be disallowed in insecure contexts");
+
+promise_test(async (t) => {
+ const description = "Promise should reject when called on a generated document not part of the DOM.";
+ const createdDocument = document.implementation.createDocument("", null);
+
+ // Can't use `promise_rejects_dom` here, since the error comes from the wrong global.
+ await createdDocument.hasStorageAccess().then(
+ t.unreached_func("Should have rejected: " + description), (e) => {
+ assert_equals(e.name, 'InvalidStateError', description);
+ });
+}, "[" + testPrefix + "] document.hasStorageAccess() should reject in a document that isn't fully active.");
+
+// Logic to load test cases within combinations of iFrames.
+if (topLevelDocument) {
+ // This specific test will run only as a top level test (not as a worker).
+ // Specific hasStorageAccess() scenarios will be tested within the context
+ // of various iFrames
+
+ // Create a test with a single-child same-origin iframe.
+ RunTestsInIFrame("resources/hasStorageAccess-iframe.html?testCase=same-origin-frame");
+
+ // Create a test with a single-child cross-origin iframe.
+ RunTestsInIFrame("http://{{domains[www]}}:{{ports[http][0]}}/storage-access-api/resources/hasStorageAccess-iframe.html?testCase=cross-origin-frame");
+
+ // Validate the nested-iframe scenario where the same-origin frame containing
+ // the tests is not the first child.
+ RunTestsInNestedIFrame("resources/hasStorageAccess-iframe.html?testCase=nested-same-origin-frame");
+
+ // Validate the nested-iframe scenario where the cross-origin frame containing
+ // the tests is not the first child.
+ RunTestsInNestedIFrame("http://{{domains[www]}}:{{ports[http][0]}}/storage-access-api/resources/hasStorageAccess-iframe.html?testCase=nested-cross-origin-frame");
+}
diff --git a/testing/web-platform/tests/storage-access-api/hasStorageAccess.sub.https.window.js b/testing/web-platform/tests/storage-access-api/hasStorageAccess.sub.https.window.js
new file mode 100644
index 0000000000..0efc687199
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/hasStorageAccess.sub.https.window.js
@@ -0,0 +1,55 @@
+// META: script=helpers.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+'use strict';
+
+const {testPrefix, topLevelDocument} = processQueryParams();
+
+// Common tests to run in all frames.
+promise_test(async () => {
+ assert_not_equals(document.hasStorageAccess, undefined);
+}, "[" + testPrefix + "] document.hasStorageAccess() should exist on the document interface");
+
+promise_test(async () => {
+ await MaybeSetStorageAccess("*", "*", "blocked");
+ const hasAccess = await document.hasStorageAccess();
+ if (topLevelDocument || testPrefix.includes('same-origin')) {
+ assert_true(hasAccess, "Access should be granted in top-level frame or iframe that is in first-party context by default.");
+ } else if (testPrefix == 'ABA') {
+ assert_false(hasAccess, "Access should not be granted in secure same-origin iframe that is in a third-party context by default.");
+ } else {
+ assert_false(hasAccess, "Access should not be granted in secure cross-origin iframes.");
+ }
+}, "[" + testPrefix + "] document.hasStorageAccess() should not be allowed by default unless in top-level frame or same-origin iframe.");
+
+promise_test(async (t) => {
+ const description = "Promise should reject when called on a generated document not part of the DOM.";
+ const createdDocument = document.implementation.createDocument("", null);
+
+ // Can't use `promise_rejects_dom` here, since the error comes from the wrong global.
+ await createdDocument.hasStorageAccess().then(
+ t.unreached_func("Should have rejected: " + description), (e) => {
+ assert_equals(e.name, 'InvalidStateError', description);
+ });
+}, "[" + testPrefix + "] document.hasStorageAccess() should reject in a document that isn't fully active.");
+
+// Logic to load test cases within combinations of iFrames.
+if (topLevelDocument) {
+ // This specific test will run only as a top level test (not as a worker).
+ // Specific hasStorageAccess() scenarios will be tested within the context
+ // of various iFrames
+
+ // Create a test with a single-child same-origin iframe.
+ RunTestsInIFrame("resources/hasStorageAccess-iframe.https.html?testCase=same-origin-frame");
+
+ // Create a test with a single-child cross-site iframe.
+ RunTestsInIFrame("https://{{hosts[alt][]}}:{{ports[https][0]}}/storage-access-api/resources/hasStorageAccess-iframe.https.html?testCase=cross-site-frame");
+
+ // Validate the nested-iframe scenario where the same-origin frame containing
+ // the tests is not the first child.
+ RunTestsInNestedIFrame("resources/hasStorageAccess-iframe.https.html?testCase=nested-same-origin-frame");
+
+ // Validate the nested-iframe scenario where the cross-site frame containing
+ // the tests is not the first child.
+ RunTestsInNestedIFrame("https://{{hosts[alt][]}}:{{ports[https][0]}}/storage-access-api/resources/hasStorageAccess-iframe.https.html?testCase=nested-cross-site-frame");
+}
diff --git a/testing/web-platform/tests/storage-access-api/helpers.js b/testing/web-platform/tests/storage-access-api/helpers.js
new file mode 100644
index 0000000000..0fd5d814db
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/helpers.js
@@ -0,0 +1,306 @@
+'use strict';
+
+function processQueryParams() {
+ const url = new URL(window.location);
+ const queryParams = url.searchParams;
+ return {
+ topLevelDocument: window === window.top,
+ testPrefix: queryParams.get("testCase") || "top-level-context",
+ };
+}
+
+// Create an iframe element, set it up using `setUpFrame`, and optionally fetch
+// tests in it. Returns the created frame, after it has loaded.
+async function CreateFrameHelper(setUpFrame, fetchTests) {
+ const frame = document.createElement('iframe');
+ const promise = new Promise((resolve, reject) => {
+ frame.onload = () => resolve(frame);
+ frame.onerror = reject;
+ });
+
+ setUpFrame(frame);
+
+ if (fetchTests) {
+ await fetch_tests_from_window(frame.contentWindow);
+ }
+ return promise;
+}
+
+// Create an iframe element with content loaded from `sourceURL`, append it to
+// the document, and optionally fetch tests. Returns the loaded frame, once
+// ready.
+function CreateFrame(sourceURL, fetchTests = false) {
+ return CreateFrameHelper((frame) => {
+ frame.src = sourceURL;
+ document.body.appendChild(frame);
+ }, fetchTests);
+}
+
+// Create a new iframe with content loaded from `sourceURL`, and fetches tests.
+// Returns the loaded frame, once ready.
+function RunTestsInIFrame(sourceURL) {
+ return CreateFrame(sourceURL, true);
+}
+
+function RunTestsInNestedIFrame(sourceURL) {
+ return CreateFrameHelper((frame) => {
+ document.body.appendChild(frame);
+ frame.contentDocument.write(`
+ <script src="/resources/testharness.js"></script>
+ <script src="helpers.js"></script>
+ <body>
+ <script>
+ RunTestsInIFrame("${sourceURL}");
+ </script>
+ `);
+ frame.contentDocument.close();
+ }, true);
+}
+
+function CreateDetachedFrame() {
+ const frame = document.createElement('iframe');
+ document.body.append(frame);
+ const inner_doc = frame.contentDocument;
+ frame.remove();
+ return inner_doc;
+}
+
+function CreateDocumentViaDOMParser() {
+ const parser = new DOMParser();
+ const doc = parser.parseFromString('<html></html>', 'text/html');
+ return doc;
+}
+
+function RunCallbackWithGesture(callback) {
+ return test_driver.bless('run callback with user gesture', callback);
+}
+
+// Sends a message to the given target window and returns a promise that
+// resolves when a reply was sent.
+function PostMessageAndAwaitReply(message, targetWindow) {
+ const timestamp = window.performance.now();
+ const reply = ReplyPromise(timestamp);
+ targetWindow.postMessage({timestamp, ...message}, "*");
+ return reply;
+}
+
+// Returns a promise that resolves when the next "reply" is received via
+// postMessage. Takes a "timestamp" argument to validate that the received
+// message belongs to its original counterpart.
+function ReplyPromise(timestamp) {
+ return new Promise((resolve) => {
+ const listener = (event) => {
+ if (event.data.timestamp == timestamp) {
+ window.removeEventListener("message", listener);
+ resolve(event.data.data);
+ }
+ };
+ window.addEventListener("message", listener);
+ });
+}
+
+// Returns a promise that resolves when the given frame fires its load event.
+function LoadPromise(frame) {
+ return new Promise((resolve) => {
+ frame.addEventListener("load", (event) => {
+ resolve();
+ }, { once: true });
+ });
+}
+
+// Writes cookies via document.cookie in the given frame.
+function SetDocumentCookieFromFrame(frame, cookie) {
+ return PostMessageAndAwaitReply(
+ { command: "write document.cookie", cookie }, frame.contentWindow);
+}
+
+// Reads cookies via document.cookie in the given frame.
+function GetJSCookiesFromFrame(frame) {
+ return PostMessageAndAwaitReply(
+ { command: "document.cookie" }, frame.contentWindow);
+}
+
+async function DeleteCookieInFrame(frame, name, params) {
+ await SetDocumentCookieFromFrame(frame, `${name}=0; expires=${new Date(0).toUTCString()}; ${params};`);
+ assert_false(cookieStringHasCookie(name, '0', await GetJSCookiesFromFrame(frame)), `Verify that cookie '${name}' has been deleted.`);
+}
+
+// Tests whether the frame can write cookies via document.cookie. Note that this
+// overwrites, then optionally deletes, cookies named "cookie" and "foo".
+//
+// This function requires the caller to have included
+// /cookies/resources/cookie-helper.sub.js.
+async function CanFrameWriteCookies(frame, keep_after_writing = false) {
+ const cookie_suffix = "Secure;SameSite=None;Path=/";
+ await DeleteCookieInFrame(frame, "cookie", cookie_suffix);
+ await DeleteCookieInFrame(frame, "foo", cookie_suffix);
+
+ await SetDocumentCookieFromFrame(frame, `cookie=monster;${cookie_suffix}`);
+ await SetDocumentCookieFromFrame(frame, `foo=bar;${cookie_suffix}`);
+
+ const cookies = await GetJSCookiesFromFrame(frame);
+ const can_write = cookieStringHasCookie("cookie", "monster", cookies) &&
+ cookieStringHasCookie("foo", "bar", cookies);
+
+ if (!keep_after_writing) {
+ await DeleteCookieInFrame(frame, "cookie", cookie_suffix);
+ await DeleteCookieInFrame(frame, "foo", cookie_suffix);
+ }
+
+ return can_write;
+}
+
+// Sets a cookie in an unpartitioned context by creating a new frame
+// and requesting storage access in the frame.
+async function SetFirstPartyCookieAndUnsetStorageAccessPermission(origin) {
+ let frame = await CreateFrame(`${origin}/storage-access-api/resources/script-with-cookie-header.py?script=embedded_responder.js`);
+ await SetPermissionInFrame(frame, [{ name: 'storage-access' }, 'granted']);
+ await RequestStorageAccessInFrame(frame);
+ await SetDocumentCookieFromFrame(frame, `cookie=unpartitioned;Secure;SameSite=None;Path=/`);
+ await SetPermissionInFrame(frame, [{ name: 'storage-access' }, 'prompt']);
+}
+
+// Tests for the presence of the unpartitioned cookie set by SetFirstPartyCookieAndUnsetStorageAccessPermission
+// in both the `document.cookie` variable and same-origin subresource \
+// Request Headers in the given frame
+async function HasUnpartitionedCookie(frame) {
+ let frameDocumentCookie = await GetJSCookiesFromFrame(frame);
+ let jsAccess = cookieStringHasCookie("cookie", "unpartitioned", frameDocumentCookie);
+ const httpCookie = await FetchSubresourceCookiesFromFrame(frame, "");
+ let httpAccess = cookieStringHasCookie("cookie", "unpartitioned", httpCookie);
+ assert_equals(jsAccess, httpAccess, "HTTP and Javascript cookies must be in sync");
+ return jsAccess && httpAccess;
+}
+
+// Tests whether the current frame can read and write cookies via HTTP headers.
+// This deletes, writes, reads, then deletes a cookie named "cookie".
+async function CanAccessCookiesViaHTTP() {
+ // We avoid reusing SetFirstPartyCookieAndUnsetStorageAccessPermission here, since that bypasses the
+ // cookie-accessibility settings that we want to check here.
+ await fetch(`${window.location.origin}/storage-access-api/resources/set-cookie-header.py?cookie=1;path=/;SameSite=None;Secure`);
+ const http_cookies = await fetch(`${window.location.origin}/storage-access-api/resources/echo-cookie-header.py`)
+ .then((resp) => resp.text());
+ const can_access = cookieStringHasCookie("cookie", "1", http_cookies);
+
+ erase_cookie_from_js("cookie", "SameSite=None;Secure;Path=/");
+
+ return can_access;
+}
+
+// Tests whether the current frame can read and write cookies via
+// document.cookie. This deletes, writes, reads, then deletes a cookie named
+// "cookie".
+function CanAccessCookiesViaJS() {
+ erase_cookie_from_js("cookie", "SameSite=None;Secure;Path=/");
+ assert_false(cookieStringHasCookie("cookie", "1", document.cookie));
+
+ document.cookie = "cookie=1;SameSite=None;Secure;Path=/";
+ const can_access = cookieStringHasCookie("cookie", "1", document.cookie);
+
+ erase_cookie_from_js("cookie", "SameSite=None;Secure;Path=/");
+ assert_false(cookieStringHasCookie("cookie", "1", document.cookie));
+
+ return can_access;
+}
+
+// Reads cookies via the `httpCookies` variable in the given frame.
+function GetHTTPCookiesFromFrame(frame) {
+ return PostMessageAndAwaitReply(
+ { command: "httpCookies" }, frame.contentWindow);
+}
+
+// Executes document.hasStorageAccess in the given frame.
+function FrameHasStorageAccess(frame) {
+ return PostMessageAndAwaitReply(
+ { command: "hasStorageAccess" }, frame.contentWindow);
+}
+
+// Executes document.requestStorageAccess in the given frame.
+function RequestStorageAccessInFrame(frame) {
+ return PostMessageAndAwaitReply(
+ { command: "requestStorageAccess" }, frame.contentWindow);
+}
+
+// Executes test_driver.set_permission in the given frame, with the provided
+// arguments.
+function SetPermissionInFrame(frame, args = []) {
+ return PostMessageAndAwaitReply(
+ { command: "set_permission", args }, frame.contentWindow);
+}
+
+// Waits for a storage-access permission change and resolves with the current
+// state.
+function ObservePermissionChange(frame, args = []) {
+ return PostMessageAndAwaitReply(
+ { command: "observe_permission_change", args }, frame.contentWindow);
+}
+
+// Executes `location.reload()` in the given frame. The returned promise
+// resolves when the frame has finished reloading.
+function FrameInitiatedReload(frame) {
+ const reload = LoadPromise(frame);
+ frame.contentWindow.postMessage({ command: "reload" }, "*");
+ return reload;
+}
+
+// Executes `location.href = url` in the given frame. The returned promise
+// resolves when the frame has finished navigating.
+function FrameInitiatedNavigation(frame, url) {
+ const load = LoadPromise(frame);
+ frame.contentWindow.postMessage({ command: "navigate", url }, "*");
+ return load;
+}
+
+// Makes a subresource request to the provided host in the given frame, and
+// returns the cookies that were included in the request.
+function FetchSubresourceCookiesFromFrame(frame, host) {
+ return FetchFromFrame(frame, `${host}/storage-access-api/resources/echo-cookie-header.py`);
+}
+
+// Makes a subresource request to the provided host in the given frame, and
+// returns the response.
+function FetchFromFrame(frame, url) {
+ return PostMessageAndAwaitReply(
+ { command: "cors fetch", url }, frame.contentWindow);
+}
+
+// Makes a subresource request to the provided host in the given frame with
+// the mode set to 'no-cors'
+function NoCorsSubresourceCookiesFromFrame(frame, host) {
+ const url = `${host}/storage-access-api/resources/echo-cookie-header.py`;
+ return PostMessageAndAwaitReply(
+ { command: "no-cors fetch", url }, frame.contentWindow);
+}
+
+// Tries to set storage access policy, ignoring any errors.
+//
+// Note: to discourage the writing of tests that assume unpartitioned cookie
+// access by default, any test that calls this with `value` == "blocked" should
+// do so as the first step in the test.
+async function MaybeSetStorageAccess(origin, embedding_origin, value) {
+ try {
+ await test_driver.set_storage_access(origin, embedding_origin, value);
+ } catch (e) {
+ // Ignore, can be unimplemented if the platform blocks cross-site cookies
+ // by default. If this failed without default blocking we'll notice it later
+ // in the test.
+ }
+}
+
+// Starts a dedicated worker in the given frame.
+function StartDedicatedWorker(frame) {
+ return PostMessageAndAwaitReply(
+ { command: "start_dedicated_worker" }, frame.contentWindow);
+}
+
+// Sends a message to the dedicated worker in the given frame.
+function MessageWorker(frame, message = {}) {
+ return PostMessageAndAwaitReply(
+ { command: "message_worker", message }, frame.contentWindow);
+}
+// Opens a WebSocket connection to origin from within frame, and
+// returns the cookie header that was sent during the handshake.
+function ReadCookiesFromWebSocketConnection(frame, origin) {
+ return PostMessageAndAwaitReply(
+ { command: "get_cookie_via_websocket", origin}, frame.contentWindow);
+}
diff --git a/testing/web-platform/tests/storage-access-api/idlharness.window.js b/testing/web-platform/tests/storage-access-api/idlharness.window.js
new file mode 100644
index 0000000000..41c6b84d68
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/idlharness.window.js
@@ -0,0 +1,14 @@
+// META: global=window,worker
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+'use strict';
+
+idl_test(
+ ['storage-access'],
+ ['dom'],
+ idl_array => {
+ idl_array.add_objects({
+ Document: ['document'],
+ });
+ }
+);
diff --git a/testing/web-platform/tests/storage-access-api/requestStorageAccess-ABA.tentative.sub.https.window.js b/testing/web-platform/tests/storage-access-api/requestStorageAccess-ABA.tentative.sub.https.window.js
new file mode 100644
index 0000000000..428053f3e5
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/requestStorageAccess-ABA.tentative.sub.https.window.js
@@ -0,0 +1,9 @@
+// META: script=helpers.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+'use strict';
+
+// Create a test with a nested iframe that is same-site to the top-level frame
+// but has cross-site frame in between.
+RunTestsInIFrame(
+ 'https://{{hosts[alt][]}}:{{ports[https][0]}}/storage-access-api/resources/requestStorageAccess-ABA-iframe.https.html');
diff --git a/testing/web-platform/tests/storage-access-api/requestStorageAccess-cross-origin-iframe-navigation.sub.https.window.js b/testing/web-platform/tests/storage-access-api/requestStorageAccess-cross-origin-iframe-navigation.sub.https.window.js
new file mode 100644
index 0000000000..691c8c86b6
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/requestStorageAccess-cross-origin-iframe-navigation.sub.https.window.js
@@ -0,0 +1,87 @@
+// META: script=helpers.js
+// META: script=/cookies/resources/cookie-helper.sub.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+'use strict';
+
+(async function() {
+ // This is cross-domain from the current document.
+ const altWww = "https://{{hosts[alt][www]}}:{{ports[https][0]}}";
+ const altRoot = "https://{{hosts[alt][]}}:{{ports[https][0]}}";
+ const responderPath = "/storage-access-api/resources/script-with-cookie-header.py?script=embedded_responder.js";
+
+ const altWwwResponder = `${altWww}${responderPath}`;
+ const altRootResponder = `${altRoot}${responderPath}`;
+
+ async function SetUpResponderFrame(t, url) {
+ const frame = await CreateFrame(url);
+
+ await SetPermissionInFrame(frame, [{ name: 'storage-access' }, 'granted']);
+ t.add_cleanup(async () => {
+ await test_driver.delete_all_cookies();
+ await SetPermissionInFrame(frame, [{ name: 'storage-access' }, 'prompt']);
+ await MaybeSetStorageAccess("*", "*", "allowed");
+ });
+
+ assert_false(await FrameHasStorageAccess(frame), "frame initially does not have storage access.");
+ assert_false(await HasUnpartitionedCookie(frame), "frame initially does not have access to cookies.");
+
+ assert_true(await RequestStorageAccessInFrame(frame), "requestStorageAccess resolves without requiring a gesture.");
+
+ assert_true(await FrameHasStorageAccess(frame), "frame has storage access after request.");
+ assert_true(await HasUnpartitionedCookie(frame), "frame has access to cookies after request.");
+
+ return frame;
+ }
+
+ promise_test(async (t) => {
+ await MaybeSetStorageAccess("*", "*", "blocked");
+ await SetFirstPartyCookieAndUnsetStorageAccessPermission(altWww);
+
+ const frame = await SetUpResponderFrame(t, altWwwResponder);
+
+ await FrameInitiatedReload(frame);
+
+ assert_true(await FrameHasStorageAccess(frame), "frame has storage access after refresh.");
+ assert_true(await HasUnpartitionedCookie(frame), "frame has access to cookies after refresh.");
+ }, "Self-initiated reloads preserve storage access");
+
+ promise_test(async (t) => {
+ await MaybeSetStorageAccess("*", "*", "blocked");
+ await SetFirstPartyCookieAndUnsetStorageAccessPermission(altWww);
+
+ const frame = await SetUpResponderFrame(t, altWwwResponder);
+
+ await FrameInitiatedNavigation(frame, altWwwResponder);
+
+ assert_true(await FrameHasStorageAccess(frame), "frame has storage access after refresh.");
+ assert_true(await HasUnpartitionedCookie(frame), "frame has access to cookies after refresh.");
+ }, "Self-initiated same-origin navigations preserve storage access");
+
+ promise_test(async (t) => {
+ await MaybeSetStorageAccess("*", "*", "blocked");
+ await SetFirstPartyCookieAndUnsetStorageAccessPermission(altWww);
+
+ const frame = await SetUpResponderFrame(t, altWwwResponder);
+
+ await new Promise((resolve) => {
+ frame.addEventListener("load", () => resolve());
+ frame.src = altWwwResponder;
+ });
+
+ assert_false(await FrameHasStorageAccess(frame), "frame does not have storage access after refresh.");
+ assert_false(await HasUnpartitionedCookie(frame), "frame has access to cookies after refresh.");
+ }, "Non-self-initiated same-origin navigations do not preserve storage access");
+
+ promise_test(async (t) => {
+ await MaybeSetStorageAccess("*", "*", "blocked");
+ await SetFirstPartyCookieAndUnsetStorageAccessPermission(altWww);
+
+ const frame = await SetUpResponderFrame(t, altWwwResponder);
+
+ await FrameInitiatedNavigation(frame, altRootResponder);
+
+ assert_false(await FrameHasStorageAccess(frame), "frame does not have storage access after refresh.");
+ assert_false(await HasUnpartitionedCookie(frame), "frame has access to cookies after refresh.");
+ }, "Self-initiated cross-origin navigations do not preserve storage access");
+})();
diff --git a/testing/web-platform/tests/storage-access-api/requestStorageAccess-cross-site-iframe.sub.https.window.js b/testing/web-platform/tests/storage-access-api/requestStorageAccess-cross-site-iframe.sub.https.window.js
new file mode 100644
index 0000000000..53f90de75d
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/requestStorageAccess-cross-site-iframe.sub.https.window.js
@@ -0,0 +1,9 @@
+// META: script=helpers.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+'use strict';
+
+(async function() {
+ // Create a test with a single-child cross-site iframe.
+ RunTestsInIFrame('https://{{hosts[alt][www]}}:{{ports[https][0]}}/storage-access-api/resources/requestStorageAccess-iframe.https.html?testCase=cross-site-frame');
+})();
diff --git a/testing/web-platform/tests/storage-access-api/requestStorageAccess-cross-site-sibling-iframes.sub.https.window.js b/testing/web-platform/tests/storage-access-api/requestStorageAccess-cross-site-sibling-iframes.sub.https.window.js
new file mode 100644
index 0000000000..528b161636
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/requestStorageAccess-cross-site-sibling-iframes.sub.https.window.js
@@ -0,0 +1,84 @@
+// META: script=helpers.js
+// META: script=/cookies/resources/cookie-helper.sub.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+'use strict';
+
+(async function() {
+ // This is on the www subdomain, so it's cross-origin from the current document.
+ const www = "https://{{domains[www]}}:{{ports[https][0]}}";
+ // This is on the alt host, so it's cross-site from the current document.
+ const wwwAlt = "https://{{hosts[alt][]}}:{{ports[https][0]}}";
+ const url_suffix = "/storage-access-api/resources/script-with-cookie-header.py?script=embedded_responder.js";
+
+ promise_test(async (t) => {
+ await MaybeSetStorageAccess("*", "*", "blocked");
+ await SetFirstPartyCookieAndUnsetStorageAccessPermission(wwwAlt);
+ const responder_html = `${wwwAlt}${url_suffix}`;
+ const [frame1, frame2] = await Promise.all([
+ CreateFrame(responder_html),
+ CreateFrame(responder_html),
+ ]);
+
+ t.add_cleanup(async () => {
+ await test_driver.delete_all_cookies();
+ await SetPermissionInFrame(frame1, [{ name: 'storage-access' }, 'prompt']);
+ await MaybeSetStorageAccess("*", "*", "allowed");
+ });
+
+ await SetPermissionInFrame(frame1, [{ name: 'storage-access' }, 'granted']);
+
+ assert_false(await FrameHasStorageAccess(frame1), "frame1 should not have storage access initially.");
+ assert_false(await FrameHasStorageAccess(frame2), "frame2 should not have storage access initially.");
+
+ assert_false(await HasUnpartitionedCookie(frame1), "frame1 should not have cookie access.");
+ assert_false(await HasUnpartitionedCookie(frame2), "frame2 should not have cookie access.");
+
+ assert_true(await RequestStorageAccessInFrame(frame1), "requestStorageAccess doesn't require a gesture since the permission has already been granted.");
+
+ assert_true(await FrameHasStorageAccess(frame1), "frame1 should have storage access now.");
+ assert_true(await HasUnpartitionedCookie(frame1), "frame1 should now have cookie access.");
+
+ assert_false(await FrameHasStorageAccess(frame2), "frame2 should still not have storage access.");
+ assert_false(await HasUnpartitionedCookie(frame2), "frame2 should still have cookie access.");
+
+ assert_true(await RequestStorageAccessInFrame(frame2), "frame2 should be able to get storage access without a gesture.");
+
+ assert_true(await FrameHasStorageAccess(frame2), "frame2 should have storage access after it requested it.");
+ assert_true(await HasUnpartitionedCookie(frame2), "frame2 should have cookie access after getting storage access.");
+ }, "Grants have per-frame scope");
+
+ promise_test(async (t) => {
+ await MaybeSetStorageAccess("*", "*", "blocked");
+ const [crossOriginFrame, crossSiteFrame] = await Promise.all([
+ CreateFrame(`${www}${url_suffix}`),
+ CreateFrame(`${wwwAlt}${url_suffix}`),
+ ]);
+
+ t.add_cleanup(async () => {
+ await test_driver.delete_all_cookies();
+ await SetPermissionInFrame(crossOriginFrame, [{ name: 'storage-access' }, 'prompt']);
+ await SetPermissionInFrame(crossSiteFrame, [{ name: 'storage-access' }, 'prompt']);
+ await MaybeSetStorageAccess("*", "*", "allowed");
+ });
+
+ await SetPermissionInFrame(crossOriginFrame, [{ name: 'storage-access' }, 'granted']);
+ await SetPermissionInFrame(crossSiteFrame, [{ name: 'storage-access' }, 'granted']);
+
+ assert_true(await RequestStorageAccessInFrame(crossOriginFrame), "crossOriginFrame should be able to get storage access without a gesture.");
+ assert_true(await RequestStorageAccessInFrame(crossSiteFrame), "crossSiteFrame should be able to get storage access without a gesture.");
+
+ await SetDocumentCookieFromFrame(crossOriginFrame, `cookie=monster;Secure;SameSite=None;Path=/`);
+ await SetDocumentCookieFromFrame(crossSiteFrame, `foo=bar;Secure;SameSite=None;Path=/`);
+
+ assert_true(cookieStringHasCookie("cookie", "monster", await FetchSubresourceCookiesFromFrame(crossOriginFrame, www)),"crossOriginFrame making same-origin subresource request can access cookies.");
+ assert_true(cookieStringHasCookie("foo", "bar", await FetchSubresourceCookiesFromFrame(crossSiteFrame, wwwAlt)),"crossSiteFrame making same-origin subresource request can access cookies.");
+
+ assert_false(cookieStringHasCookie("foo", "bar", await FetchSubresourceCookiesFromFrame(crossOriginFrame, wwwAlt)), "crossOriginFrame making cross-site subresource request to sibling iframe's host should not include cookies.");
+
+ assert_false(cookieStringHasCookie("foo", "bar", await NoCorsSubresourceCookiesFromFrame(crossOriginFrame, www)), "crossSiteFrame making no-cors cross-site subresource request to sibling iframe's host should not include cookies.");
+ assert_false(cookieStringHasCookie("cookie", "monster", await FetchSubresourceCookiesFromFrame(crossSiteFrame, www)),"crossSiteFrame making cross-site subresource request to sibling iframe's host should not include cookies.");
+
+ }, "Cross-site sibling iframes should not be able to take advantage of the existing permission grant requested by others.");
+
+})();
diff --git a/testing/web-platform/tests/storage-access-api/requestStorageAccess-dedicated-worker.tentative.sub.https.window.js b/testing/web-platform/tests/storage-access-api/requestStorageAccess-dedicated-worker.tentative.sub.https.window.js
new file mode 100644
index 0000000000..f2d766575d
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/requestStorageAccess-dedicated-worker.tentative.sub.https.window.js
@@ -0,0 +1,67 @@
+// META: script=helpers.js
+// META: script=/cookies/resources/cookie-helper.sub.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+'use strict';
+
+(function() {
+ const altRoot = "https://{{hosts[alt][]}}:{{ports[https][0]}}";
+
+ const responderPath = "/storage-access-api/resources/script-with-cookie-header.py?script=embedded_responder.js";
+ const echoCookiesPath = `/storage-access-api/resources/echo-cookie-header.py`;
+
+ const altRootResponder = `${altRoot}${responderPath}`;
+ const altRootEchoCookies = `${altRoot}${echoCookiesPath}`;
+
+ async function SetUpResponderFrame(t, url) {
+ const frame = await CreateFrame(url);
+
+ await SetPermissionInFrame(frame, [{ name: 'storage-access' }, 'granted']);
+ t.add_cleanup(async () => {
+ await test_driver.delete_all_cookies();
+ await SetPermissionInFrame(frame, [{ name: 'storage-access' }, 'prompt']);
+ await MaybeSetStorageAccess("*", "*", "allowed");
+ });
+
+ return frame;
+ }
+
+ promise_test(async (t) => {
+ await MaybeSetStorageAccess("*", "*", "blocked");
+ await SetFirstPartyCookieAndUnsetStorageAccessPermission(altRoot);
+
+ const frame = await SetUpResponderFrame(t, altRootResponder);
+ assert_true(await RequestStorageAccessInFrame(frame), "requestStorageAccess resolves without requiring a gesture.");
+ assert_true(await FrameHasStorageAccess(frame), "frame has storage access after request.");
+ assert_true(await HasUnpartitionedCookie(frame), "frame has access to cookies after request.");
+
+ await StartDedicatedWorker(frame);
+
+ assert_true(cookieStringHasCookie("cookie", "unpartitioned",
+ await MessageWorker(frame, {command: "fetch", url: altRootEchoCookies})),
+ "Worker's fetch is credentialed.");
+ }, "Workers inherit storage access");
+
+ promise_test(async (t) => {
+ await MaybeSetStorageAccess("*", "*", "blocked");
+ await SetFirstPartyCookieAndUnsetStorageAccessPermission(altRoot);
+
+ const frame = await SetUpResponderFrame(t, altRootResponder);
+
+ await StartDedicatedWorker(frame);
+ assert_false(cookieStringHasCookie("cookie", "unpartitioned",
+ await MessageWorker(frame, {command: "fetch", url: altRootEchoCookies})),
+ "Worker's first fetch is uncredentialed.");
+
+ // Since the parent document obtains storage access *after* having created
+ // the worker, this should have no effect on the worker.
+ assert_true(await RequestStorageAccessInFrame(frame), "requestStorageAccess resolves without requiring a gesture.");
+ assert_true(await FrameHasStorageAccess(frame), "frame has storage access after request.");
+ assert_true(await HasUnpartitionedCookie(frame), "frame has access to cookies after request.");
+
+ assert_false(cookieStringHasCookie("cookie", "unpartitioned",
+ await MessageWorker(frame, {command: "fetch", url: altRootEchoCookies})),
+ "Worker's second fetch is uncredentialed.");
+ }, "Workers don't observe parent's storage access");
+
+}());
diff --git a/testing/web-platform/tests/storage-access-api/requestStorageAccess-insecure.sub.window.js b/testing/web-platform/tests/storage-access-api/requestStorageAccess-insecure.sub.window.js
new file mode 100644
index 0000000000..1ad04321db
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/requestStorageAccess-insecure.sub.window.js
@@ -0,0 +1,82 @@
+// META: script=helpers.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+'use strict';
+
+// Document-level test config flags:
+//
+// testPrefix: Prefix each test case with an indicator so we know what context
+// they are run in if they are used in multiple iframes.
+//
+// topLevelDocument: Keep track of if we run these tests in a nested context, we
+// don't want to recurse forever.
+const {testPrefix, topLevelDocument} = processQueryParams();
+
+// Common tests to run in all frames.
+promise_test(async () => {
+ assert_not_equals(document.requestStorageAccess, undefined);
+}, "[" + testPrefix + "] document.requestStorageAccess() should exist on the document interface");
+
+promise_test(t => {
+ return promise_rejects_dom(t, "NotAllowedError", document.requestStorageAccess(),
+ "document.requestStorageAccess() call without user gesture");
+}, "[" + testPrefix + "] document.requestStorageAccess() should be rejected in insecure context");
+
+// Logic to load test cases within combinations of iFrames.
+if (topLevelDocument) {
+ // This specific test will run only as a top level test (not as a worker).
+ // Specific requestStorageAccess() scenarios will be tested within the context
+ // of various iFrames
+ promise_test(t => {
+ const description = "document.requestStorageAccess() call in a detached frame";
+ // Can't use `promise_rejects_dom` here, since the error comes from the wrong global.
+ return CreateDetachedFrame().requestStorageAccess()
+ .then(t.unreached_func("Should have rejected: " + description), (e) => {
+ assert_equals(e.name, 'InvalidStateError', description);
+ t.done();
+ });
+ }, "[non-fully-active] document.requestStorageAccess() should reject when run in a detached frame");
+
+ promise_test(t => {
+ return promise_rejects_dom(t, 'InvalidStateError', CreateDocumentViaDOMParser().requestStorageAccess(),
+ "document.requestStorageAccess() in a detached DOMParser result");
+ }, "[non-fully-active] document.requestStorageAccess() should reject when run in a detached DOMParser document");
+
+ // Create a test with a single-child same-origin iframe.
+ const sameOriginFramePromise = RunTestsInIFrame(
+ 'resources/requestStorageAccess-iframe.html?testCase=same-origin-frame');
+
+ // Create a test with a single-child cross-origin iframe.
+ const crossOriginFramePromise = RunTestsInIFrame(
+ 'http://{{domains[www]}}:{{ports[http][0]}}/storage-access-api/resources/requestStorageAccess-iframe.html?testCase=cross-origin-frame');
+
+ // Validate the nested-iframe scenario where the same-origin frame
+ // containing the tests is not the first child.
+ const nestedSameOriginFramePromise = RunTestsInNestedIFrame(
+ 'resources/requestStorageAccess-iframe.html?testCase=nested-same-origin-frame');
+
+ // Validate the nested-iframe scenario where the cross-origin frame
+ // containing the tests is not the first child.
+ const nestedCrossOriginFramePromise = RunTestsInNestedIFrame(
+ 'http://{{domains[www]}}:{{ports[http][0]}}/storage-access-api/resources/requestStorageAccess-iframe.html?testCase=nested-cross-origin-frame');
+
+ // Because the iframe tests expect no user activation, and because they
+ // load asynchronously, we want to first run those tests before simulating
+ // clicks on the page.
+ const testsWithoutUserActivation = [
+ sameOriginFramePromise,
+ crossOriginFramePromise,
+ nestedSameOriginFramePromise,
+ nestedCrossOriginFramePromise,
+ ];
+
+ promise_test(async t => {
+ await Promise .all(testsWithoutUserActivation);
+ await RunCallbackWithGesture(() => {
+ return promise_rejects_dom(t, "NotAllowedError", document.requestStorageAccess(),
+ "should reject in insecure context");
+ });
+ },
+ '[' + testPrefix +
+ '] document.requestStorageAccess() should be rejected when called with a user gesture in insecure context');
+}
diff --git a/testing/web-platform/tests/storage-access-api/requestStorageAccess-nested-cross-origin-iframe.sub.https.window.js b/testing/web-platform/tests/storage-access-api/requestStorageAccess-nested-cross-origin-iframe.sub.https.window.js
new file mode 100644
index 0000000000..eeac9c2a40
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/requestStorageAccess-nested-cross-origin-iframe.sub.https.window.js
@@ -0,0 +1,10 @@
+// META: script=helpers.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+'use strict';
+
+(async function() {
+ // Validate the nested-iframe scenario where the cross-origin frame
+ // containing the tests is not the first child.
+ RunTestsInNestedIFrame('https://{{domains[www]}}:{{ports[https][0]}}/storage-access-api/resources/requestStorageAccess-iframe.https.html?testCase=nested-cross-origin-frame');
+})();
diff --git a/testing/web-platform/tests/storage-access-api/requestStorageAccess-nested-cross-site-iframe.sub.https.window.js b/testing/web-platform/tests/storage-access-api/requestStorageAccess-nested-cross-site-iframe.sub.https.window.js
new file mode 100644
index 0000000000..59442d97c9
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/requestStorageAccess-nested-cross-site-iframe.sub.https.window.js
@@ -0,0 +1,10 @@
+// META: script=helpers.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+'use strict';
+
+(async function() {
+ // Validate the nested-iframe scenario where the cross-site frame
+ // containing the tests is not the first child.
+ RunTestsInNestedIFrame('https://{{hosts[alt][www]}}:{{ports[https][0]}}/storage-access-api/resources/requestStorageAccess-iframe.https.html?testCase=nested-cross-site-frame');
+})();
diff --git a/testing/web-platform/tests/storage-access-api/requestStorageAccess-nested-same-origin-iframe.sub.https.window.js b/testing/web-platform/tests/storage-access-api/requestStorageAccess-nested-same-origin-iframe.sub.https.window.js
new file mode 100644
index 0000000000..24d82c487f
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/requestStorageAccess-nested-same-origin-iframe.sub.https.window.js
@@ -0,0 +1,8 @@
+// META: script=helpers.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+'use strict';
+
+// Validate the nested-iframe scenario where the same-origin frame
+// containing the tests is not the first child.
+RunTestsInNestedIFrame('resources/requestStorageAccess-iframe.https.html?testCase=nested-same-origin-frame');
diff --git a/testing/web-platform/tests/storage-access-api/requestStorageAccess-non-fully-active.sub.https.window.js b/testing/web-platform/tests/storage-access-api/requestStorageAccess-non-fully-active.sub.https.window.js
new file mode 100644
index 0000000000..79b4a7959f
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/requestStorageAccess-non-fully-active.sub.https.window.js
@@ -0,0 +1,19 @@
+// META: script=helpers.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+'use strict';
+
+promise_test(t => {
+ const promise = CreateDetachedFrame().requestStorageAccess();
+ const description = "document.requestStorageAccess() call in a detached frame";
+ // Can't use `promise_rejects_dom` here, since the error comes from the wrong global.
+ return promise.then(t.unreached_func("Should have rejected: " + description), (e) => {
+ assert_equals(e.name, 'InvalidStateError', description);
+ t.done();
+ });
+}, "[non-fully-active] document.requestStorageAccess() should not resolve when run in a detached frame");
+
+promise_test(t => {
+ return promise_rejects_dom(t, 'InvalidStateError', CreateDocumentViaDOMParser().requestStorageAccess(),
+ "document.requestStorageAccess() in a detached DOMParser result");
+}, "[non-fully-active] document.requestStorageAccess() should not resolve when run in a detached DOMParser document");
diff --git a/testing/web-platform/tests/storage-access-api/requestStorageAccess-same-site-iframe.sub.https.window.js b/testing/web-platform/tests/storage-access-api/requestStorageAccess-same-site-iframe.sub.https.window.js
new file mode 100644
index 0000000000..f646444e67
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/requestStorageAccess-same-site-iframe.sub.https.window.js
@@ -0,0 +1,8 @@
+// META: script=helpers.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+'use strict';
+
+// Create a test with a single-child cross-origin same-site iframe.
+RunTestsInIFrame(
+ 'https://{{domains[www]}}:{{ports[https][0]}}/storage-access-api/resources/requestStorageAccess-iframe.https.html?testCase=same-site-frame');
diff --git a/testing/web-platform/tests/storage-access-api/requestStorageAccess-web-socket.tentative.sub.https.window.js b/testing/web-platform/tests/storage-access-api/requestStorageAccess-web-socket.tentative.sub.https.window.js
new file mode 100644
index 0000000000..bc323bd95a
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/requestStorageAccess-web-socket.tentative.sub.https.window.js
@@ -0,0 +1,51 @@
+// META: script=helpers.js
+// META: script=/cookies/resources/cookie-helper.sub.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+
+'use strict';
+
+const altRoot = "https://{{hosts[alt][]}}:{{ports[https][0]}}";
+const altRootWss = "wss://{{hosts[alt][]}}:{{ports[wss][0]}}";
+
+const responderPath = "/storage-access-api/resources/script-with-cookie-header.py?script=embedded_responder.js";
+const altRootResponder = `${altRoot}${responderPath}`;
+
+async function SetUpResponderFrame(t, url) {
+ const frame = await CreateFrame(url);
+
+ await SetPermissionInFrame(frame, [{ name: 'storage-access' }, 'granted']);
+ t.add_cleanup(async () => {
+ await test_driver.delete_all_cookies();
+ await SetPermissionInFrame(frame, [{ name: 'storage-access' }, 'prompt']);
+ await MaybeSetStorageAccess("*", "*", "allowed");
+ });
+
+ return frame;
+}
+
+promise_test(async (t) => {
+ await MaybeSetStorageAccess("*", "*", "blocked");
+ await SetFirstPartyCookieAndUnsetStorageAccessPermission(altRoot);
+
+ const frame = await SetUpResponderFrame(t, altRootResponder);
+
+ assert_true(await RequestStorageAccessInFrame(frame), "requestStorageAccess resolves without requiring a gesture.");
+ assert_true(await FrameHasStorageAccess(frame), "frame has storage access after request.");
+ assert_true(await HasUnpartitionedCookie(frame), "frame has access to cookies after request.");
+
+ assert_true(cookieStringHasCookie("cookie", "unpartitioned",
+ await ReadCookiesFromWebSocketConnection(frame, altRootWss)),
+ "WebSocket handshake should include unpartitioned cookie");
+}, "WebSocket inherits storage access");
+
+promise_test(async (t) => {
+
+ await MaybeSetStorageAccess("*", "*", "blocked");
+ await SetFirstPartyCookieAndUnsetStorageAccessPermission(altRoot);
+ const frame = await SetUpResponderFrame(t, altRootResponder);
+
+ assert_false(cookieStringHasCookie("cookie", "unpartitioned",
+ await ReadCookiesFromWebSocketConnection(frame, altRootWss)),
+ "request should not contain cookies");
+}, "WebSocket omits unpartitioned cookies without storage access");
diff --git a/testing/web-platform/tests/storage-access-api/requestStorageAccess.sub.https.window.js b/testing/web-platform/tests/storage-access-api/requestStorageAccess.sub.https.window.js
new file mode 100644
index 0000000000..21c451ca61
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/requestStorageAccess.sub.https.window.js
@@ -0,0 +1,104 @@
+// META: script=helpers.js
+// META: script=/cookies/resources/cookie-helper.sub.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+'use strict';
+
+// Document-level test config flags:
+//
+// testPrefix: Prefix each test case with an indicator so we know what context
+// they are run in if they are used in multiple iframes.
+//
+// topLevelDocument: Keep track of if we run these tests in a nested context, we
+// don't want to recurse forever.
+const {testPrefix, topLevelDocument} = processQueryParams();
+
+if (!topLevelDocument) {
+ // WPT synthesizes a top-level HTML test for this JS file, and in that case we
+ // don't want to, or need to, call set_test_context.
+ test_driver.set_test_context(window.top);
+}
+
+// Common tests to run in all frames.
+promise_test(async () => {
+ assert_not_equals(document.requestStorageAccess, undefined);
+}, "[" + testPrefix + "] document.requestStorageAccess() should exist on the document interface");
+
+// Most tests need to start with the feature in "prompt" state.
+async function CommonSetup() {
+ await test_driver.set_permission({ name: 'storage-access' }, 'prompt');
+}
+
+promise_test(
+ async t => {
+ await CommonSetup();
+ if (topLevelDocument || !testPrefix.includes('cross-site') ||
+ testPrefix.includes('ABA')) {
+ await document.requestStorageAccess().catch(t.unreached_func(
+ 'document.requestStorageAccess() call should resolve in top-level frame or same-site iframe.'));
+
+ assert_true(await CanAccessCookiesViaHTTP(), 'After obtaining storage access, subresource requests from the frame should send and set cookies.');
+ assert_true(CanAccessCookiesViaJS(), 'After obtaining storage access, scripts in the frame should be able to access cookies.');
+ } else {
+ return promise_rejects_dom(
+ t, "NotAllowedError", document.requestStorageAccess(),
+ "document.requestStorageAccess() call without user gesture.");
+ }
+ },
+ '[' + testPrefix +
+ '] document.requestStorageAccess() should resolve in top-level frame or same-site iframe, otherwise reject with a NotAllowedError with no user gesture.');
+
+promise_test(
+ async (t) => {
+ await CommonSetup();
+ await MaybeSetStorageAccess("*", "*", "blocked");
+ await test_driver.set_permission({name: 'storage-access'}, 'granted');
+ t.add_cleanup(async () => {
+ await test_driver.delete_all_cookies();
+ });
+
+ await document.requestStorageAccess();
+
+ assert_true(await CanAccessCookiesViaHTTP(), 'After obtaining storage access, subresource requests from the frame should send and set cookies.');
+ assert_true(CanAccessCookiesViaJS(), 'After obtaining storage access, scripts in the frame should be able to access cookies.');
+ },
+ '[' + testPrefix +
+ '] document.requestStorageAccess() should be resolved with no user gesture when a permission grant exists, and ' +
+ 'should allow cookie access');
+
+if (testPrefix.includes('cross-site')) {
+ promise_test(
+ async t => {
+ await test_driver.set_permission(
+ {name: 'storage-access'}, 'denied');
+
+ await RunCallbackWithGesture(() => {
+ return promise_rejects_dom(t, "NotAllowedError", document.requestStorageAccess(),
+ "document.requestStorageAccess() call with denied permission");
+ });
+ },
+ '[' + testPrefix +
+ '] document.requestStorageAccess() should be rejected with a NotAllowedError with denied permission');
+} else {
+ promise_test(
+ async () => {
+ await CommonSetup();
+ await document.requestStorageAccess();
+
+ assert_true(await CanAccessCookiesViaHTTP(), 'After obtaining storage access, subresource requests from the frame should send and set cookies.');
+ assert_true(CanAccessCookiesViaJS(), 'After obtaining storage access, scripts in the frame should be able to access cookies.');
+ },
+ `[${testPrefix}] document.requestStorageAccess() should resolve without permission grant or user gesture`);
+
+ promise_test(
+ async () => {
+ await test_driver.set_permission(
+ {name: 'storage-access'}, 'denied');
+
+ await document.requestStorageAccess();
+
+ assert_true(await CanAccessCookiesViaHTTP(), 'After obtaining storage access, subresource requests from the frame should send and set cookies.');
+ assert_true(CanAccessCookiesViaJS(), 'After obtaining storage access, scripts in the frame should be able to access cookies.');
+ },
+ `[${testPrefix}] document.requestStorageAccess() should resolve with denied permission`);
+}
diff --git a/testing/web-platform/tests/storage-access-api/resources/echo-cookie-header.py b/testing/web-platform/tests/storage-access-api/resources/echo-cookie-header.py
new file mode 100644
index 0000000000..f1599e3a89
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/resources/echo-cookie-header.py
@@ -0,0 +1,12 @@
+def main(request, response):
+ # Set the cors enabled headers.
+ origin = request.headers.get(b"Origin")
+ headers = []
+ if origin is not None and origin != b"null":
+ headers.append((b"Content-Type", b"text/plain"))
+ headers.append((b"Access-Control-Allow-Origin", origin))
+ headers.append((b"Access-Control-Allow-Credentials", 'true'))
+
+ cookie_header = request.headers.get(b"Cookie", b"")
+
+ return (200, headers, cookie_header)
diff --git a/testing/web-platform/tests/storage-access-api/resources/embedded_responder.js b/testing/web-platform/tests/storage-access-api/resources/embedded_responder.js
new file mode 100644
index 0000000000..bc13c7e7e8
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/resources/embedded_responder.js
@@ -0,0 +1,97 @@
+"use strict";
+
+test_driver.set_test_context(window.top);
+
+let worker;
+
+function waitForWorkerMessage(worker) {
+ return new Promise(resolve => {
+ const listener = (event) => {
+ worker.removeEventListener("message", listener);
+ resolve(event.data);
+ };
+ worker.addEventListener("message", listener);
+ });
+}
+
+function connectAndGetRequestCookiesFrom(origin) {
+ return new Promise((resolve, reject) => {
+ const ws = new WebSocket(origin +'/echo-cookie');
+ ws.onmessage = event => {
+ const cookies = event.data;
+ resolve(cookies);
+ ws.onerror = undefined;
+ ws.onclose = undefined;
+ };
+ ws.onerror = () => reject(new Error('Unexpected error event'));
+ ws.onclose = evt => reject('Unexpected close event: ' + JSON.stringify(evt));
+ });
+}
+
+window.addEventListener("message", async (event) => {
+ function reply(data) {
+ event.source.postMessage(
+ {timestamp: event.data.timestamp, data}, event.origin);
+ }
+
+ switch (event.data["command"]) {
+ case "hasStorageAccess":
+ reply(await document.hasStorageAccess());
+ break;
+ case "requestStorageAccess": {
+ const obtainedAccess = await document.requestStorageAccess()
+ .then(() => true, () => false);
+ reply(obtainedAccess);
+ }
+ break;
+ case "write document.cookie":
+ document.cookie = event.data.cookie;
+ reply(undefined);
+ break;
+ case "document.cookie":
+ reply(document.cookie);
+ break;
+ case "set_permission":
+ await test_driver.set_permission(...event.data.args);
+ reply(undefined);
+ break;
+ case "observe_permission_change":
+ const status = await navigator.permissions.query({name: "storage-access"});
+ status.addEventListener("change", (event) => {
+ reply(event.target.state)
+ }, { once: true });
+ break;
+ case "reload":
+ window.location.reload();
+ break;
+ case "navigate":
+ window.location.href = event.data.url;
+ break;
+ case "httpCookies":
+ // The `httpCookies` variable is defined/set by
+ // script-with-cookie-header.py.
+ reply(httpCookies);
+ break;
+ case "cors fetch":
+ reply(await fetch(event.data.url, {mode: 'cors', credentials: 'include'}).then((resp) => resp.text()));
+ break;
+ case "no-cors fetch":
+ reply(await fetch(event.data.url, {mode: 'no-cors', credentials: 'include'}).then((resp) => resp.text()));
+ break;
+ case "start_dedicated_worker":
+ worker = new Worker("embedded_worker.js");
+ reply(undefined);
+ break;
+ case "message_worker": {
+ const p = waitForWorkerMessage(worker);
+ worker.postMessage(event.data.message);
+ reply(await p.then(resp => resp.data))
+ break;
+ }
+ case "get_cookie_via_websocket":{
+ reply(await connectAndGetRequestCookiesFrom(event.data.origin));
+ break;
+ }
+ default:
+ }
+});
diff --git a/testing/web-platform/tests/storage-access-api/resources/embedded_worker.js b/testing/web-platform/tests/storage-access-api/resources/embedded_worker.js
new file mode 100644
index 0000000000..f3a0fb257a
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/resources/embedded_worker.js
@@ -0,0 +1,17 @@
+"use strict";
+
+self.onmessage = async (message) => {
+ function reply(data) {
+ self.postMessage({data});
+ }
+
+ switch (message.data.command) {
+ case "fetch": {
+ const response = await fetch(message.data.url, {mode: 'cors', credentials: 'include'})
+ .then((resp) => resp.text());
+ reply(response);
+ break;
+ }
+ default:
+ }
+};
diff --git a/testing/web-platform/tests/storage-access-api/resources/hasStorageAccess-ABA-iframe.https.html b/testing/web-platform/tests/storage-access-api/resources/hasStorageAccess-ABA-iframe.https.html
new file mode 100644
index 0000000000..fdceefc0ab
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/resources/hasStorageAccess-ABA-iframe.https.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<meta charset=utf-8>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/storage-access-api/helpers.js"></script>
+<body>
+<script src="/storage-access-api/resources/hasStorageAccess-ABA-iframe.sub.https.window.js"></script> \ No newline at end of file
diff --git a/testing/web-platform/tests/storage-access-api/resources/hasStorageAccess-ABA-iframe.sub.https.window.js b/testing/web-platform/tests/storage-access-api/resources/hasStorageAccess-ABA-iframe.sub.https.window.js
new file mode 100644
index 0000000000..d6227ee47e
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/resources/hasStorageAccess-ABA-iframe.sub.https.window.js
@@ -0,0 +1,11 @@
+// META: script=../helpers.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+'use strict';
+
+// This expects to be run in an iframe that is cross-site to the top-level frame.
+(async function() {
+ // Create a test with a single-child iframe that is same-site to the top-level frame but cross-site to the iframe
+ // that is being created here, for the purpose of testing hasStorageAccess in an A(B(A)) frame tree setting.
+ RunTestsInIFrame("https://{{host}}:{{ports[https][0]}}/storage-access-api/resources/hasStorageAccess-iframe.https.html?testCase=ABA");
+})();
diff --git a/testing/web-platform/tests/storage-access-api/resources/hasStorageAccess-iframe.html b/testing/web-platform/tests/storage-access-api/resources/hasStorageAccess-iframe.html
new file mode 100644
index 0000000000..d57c3961e5
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/resources/hasStorageAccess-iframe.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<meta charset=utf-8>
+
+<script src="/resources/testharness.js"></script>
+<!-- no testharnessreport.js -->
+<script src="../helpers.js"></script>
+<div id=log></div>
+<script src="/storage-access-api/hasStorageAccess-insecure.sub.window.js"></script>
diff --git a/testing/web-platform/tests/storage-access-api/resources/hasStorageAccess-iframe.https.html b/testing/web-platform/tests/storage-access-api/resources/hasStorageAccess-iframe.https.html
new file mode 100644
index 0000000000..46194adcf8
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/resources/hasStorageAccess-iframe.https.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<meta charset=utf-8>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<!-- no testharnessreport.js -->
+<script src="../helpers.js"></script>
+<div id=log></div>
+<script src="/storage-access-api/hasStorageAccess.sub.https.window.js"></script>
diff --git a/testing/web-platform/tests/storage-access-api/resources/permissions-iframe.https.html b/testing/web-platform/tests/storage-access-api/resources/permissions-iframe.https.html
new file mode 100644
index 0000000000..b83a05c3f1
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/resources/permissions-iframe.https.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<meta charset=utf-8>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<!-- no testharnessreport.js -->
+<script src="../helpers.js"></script>
+<div id=log></div>
+<script src="/storage-access-api/storage-access-permission.sub.https.window.js"></script>
diff --git a/testing/web-platform/tests/storage-access-api/resources/requestStorageAccess-ABA-iframe.https.html b/testing/web-platform/tests/storage-access-api/resources/requestStorageAccess-ABA-iframe.https.html
new file mode 100644
index 0000000000..7452ff89a0
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/resources/requestStorageAccess-ABA-iframe.https.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<meta charset=utf-8>
+
+<script src="/resources/testharness.js"></script>
+<script src="/storage-access-api/helpers.js"></script>
+<body>
+<script src="/storage-access-api/resources/requestStorageAccess-ABA-iframe.sub.https.window.js"></script> \ No newline at end of file
diff --git a/testing/web-platform/tests/storage-access-api/resources/requestStorageAccess-ABA-iframe.sub.https.window.js b/testing/web-platform/tests/storage-access-api/resources/requestStorageAccess-ABA-iframe.sub.https.window.js
new file mode 100644
index 0000000000..8bfef8022a
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/resources/requestStorageAccess-ABA-iframe.sub.https.window.js
@@ -0,0 +1,12 @@
+// META: script=../helpers.js
+'use strict';
+
+// This expects to be run in an iframe that is cross-site to the top-level
+// frame.
+(async function() {
+ // Create a test with a single-child iframe that is same-site to the top-level
+ // frame but cross-site to the iframe that is being created here, for the
+ // purpose of testing requestStorageAccess in an A(B(A)) frame tree setting.
+ RunTestsInIFrame(
+ 'https://{{domains[www]}}:{{ports[https][0]}}/storage-access-api/resources/requestStorageAccess-iframe.https.html?testCase=ABA');
+})();
diff --git a/testing/web-platform/tests/storage-access-api/resources/requestStorageAccess-iframe.html b/testing/web-platform/tests/storage-access-api/resources/requestStorageAccess-iframe.html
new file mode 100644
index 0000000000..8b47786e17
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/resources/requestStorageAccess-iframe.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<meta charset=utf-8>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<!-- no testharnessreport.js -->
+<script src="../helpers.js"></script>
+<div id=log></div>
+<script src="/storage-access-api/requestStorageAccess-insecure.sub.window.js"></script>
diff --git a/testing/web-platform/tests/storage-access-api/resources/requestStorageAccess-iframe.https.html b/testing/web-platform/tests/storage-access-api/resources/requestStorageAccess-iframe.https.html
new file mode 100644
index 0000000000..828af799e6
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/resources/requestStorageAccess-iframe.https.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<meta charset=utf-8>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<!-- no testharnessreport.js -->
+<script src="../helpers.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<div id=log></div>
+<script src="/storage-access-api/requestStorageAccess.sub.https.window.js"></script>
diff --git a/testing/web-platform/tests/storage-access-api/resources/script-with-cookie-header.py b/testing/web-platform/tests/storage-access-api/resources/script-with-cookie-header.py
new file mode 100644
index 0000000000..83129a5559
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/resources/script-with-cookie-header.py
@@ -0,0 +1,19 @@
+def main(request, response):
+ script = request.GET.first(b"script")
+ cookie_header = request.headers.get(b"Cookie", b"")
+
+ body = b"""
+ <!DOCTYPE html>
+ <meta charset="utf-8">
+ <title>Subframe with HTTP Cookies</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script>
+ var httpCookies = "%s";
+ </script>
+
+ <script src="%s"></script>
+ """ % (cookie_header, script)
+
+ return (200, [], body)
diff --git a/testing/web-platform/tests/storage-access-api/resources/set-cookie-header.py b/testing/web-platform/tests/storage-access-api/resources/set-cookie-header.py
new file mode 100644
index 0000000000..e937ba2ca4
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/resources/set-cookie-header.py
@@ -0,0 +1,12 @@
+from urllib.parse import unquote
+from wptserve.utils import isomorphic_encode
+
+def main(request, response):
+ # Cookies may require whitespace (e.g. in the `Expires` attribute), so the
+ # query string should be decoded.
+ cookie = unquote(request.url_parts.query)
+ headers = []
+ headers.append((b"Set-Cookie", isomorphic_encode(cookie)))
+
+ return (200, headers, "")
+
diff --git a/testing/web-platform/tests/storage-access-api/resources/storage-access-beyond-cookies-iframe-iframe.html b/testing/web-platform/tests/storage-access-api/resources/storage-access-beyond-cookies-iframe-iframe.html
new file mode 100644
index 0000000000..ffb419f799
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/resources/storage-access-beyond-cookies-iframe-iframe.html
@@ -0,0 +1,259 @@
+<!doctype html>
+<meta charset="utf-8">
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/storage-access-api/helpers.js"></script>
+<script>
+(async function() {
+ test_driver.set_test_context(window.top);
+ const type = (new URLSearchParams(window.location.search)).get("type");
+ const id = (new URLSearchParams(window.location.search)).get("id");
+ let message = "HasAccess for " + type;
+ // Step 6 (storage-access-api/storage-access-beyond-cookies.{}.tentative.sub.https.html)
+ try {
+ await MaybeSetStorageAccess("*", "*", "blocked");
+ await test_driver.set_permission({ name: 'storage-access' }, 'granted');
+ switch (type) {
+ case "none": {
+ let couldRequestStorageAccessForNone = true;
+ try {
+ await document.requestStorageAccess({});
+ } catch (_) {
+ couldRequestStorageAccessForNone = false;
+ }
+ if (couldRequestStorageAccessForNone) {
+ message = "Requesting access for {} should fail."
+ }
+ let couldRequestStorageAccessForAllFalse = true;
+ try {
+ await document.requestStorageAccess({all:false});
+ } catch (_) {
+ couldRequestStorageAccessForAllFalse = false;
+ }
+ if (couldRequestStorageAccessForAllFalse) {
+ message = "Requesting access for {all:false} should fail."
+ }
+ let hasUnpartitionedCookieAccess = await document.hasUnpartitionedCookieAccess();
+ if (hasUnpartitionedCookieAccess) {
+ message = "First-party cookies should not be readable if not requested.";
+ }
+ break;
+ }
+ case "cookies": {
+ let hasUnpartitionedCookieAccess = await document.hasUnpartitionedCookieAccess();
+ if (hasUnpartitionedCookieAccess || document.cookie.includes("test="+id)) {
+ message = "First-party cookies should not be readable before handle is loaded.";
+ }
+ await document.requestStorageAccess({cookies: true});
+ hasUnpartitionedCookieAccess = await document.hasUnpartitionedCookieAccess();
+ if (!hasUnpartitionedCookieAccess || !document.cookie.includes("test="+id)) {
+ message = "First-party cookies should be readable if cookies were requested.";
+ }
+ break;
+ }
+ case "sessionStorage": {
+ const handle = await document.requestStorageAccess({sessionStorage: true});
+ let hasUnpartitionedCookieAccess = await document.hasUnpartitionedCookieAccess();
+ if (hasUnpartitionedCookieAccess) {
+ message = "First-party cookies should not be readable if not requested.";
+ }
+ if (id != handle.sessionStorage.getItem("test")) {
+ message = "No first-party Session Storage access";
+ }
+ if (!!window.sessionStorage.getItem("test")) {
+ message = "Handle should not override window Session Storage";
+ }
+ window.onstorage = (e) => {
+ if (e.key == "window_event") {
+ if (e.newValue != id) {
+ handle.sessionStorage.setItem("handle_event", "wrong data " + e.newValue);
+ } else if (e.storageArea != handle.sessionStorage) {
+ handle.sessionStorage.setItem("handle_event", "storage area mismatch");
+ } else {
+ handle.sessionStorage.setItem("handle_event", id);
+ }
+ }
+ };
+ break;
+ }
+ case "localStorage": {
+ const handle = await document.requestStorageAccess({localStorage: true});
+ let hasUnpartitionedCookieAccess = await document.hasUnpartitionedCookieAccess();
+ if (hasUnpartitionedCookieAccess) {
+ message = "First-party cookies should not be readable if not requested.";
+ }
+ if (id != handle.localStorage.getItem("test")) {
+ message = "No first-party Local Storage access";
+ }
+ if (!!window.localStorage.getItem("test")) {
+ message = "Handle should not override window Local Storage";
+ }
+ window.onstorage = (e) => {
+ if (e.key == "window_event") {
+ if (e.newValue != id) {
+ handle.localStorage.setItem("handle_event", "wrong data " + e.newValue);
+ } else if (e.storageArea != handle.localStorage) {
+ handle.localStorage.setItem("handle_event", "storage area mismatch");
+ } else {
+ handle.localStorage.setItem("handle_event", id);
+ }
+ }
+ };
+ break;
+ }
+ case "indexedDB": {
+ const handle = await document.requestStorageAccess({indexedDB: true});
+ let hasUnpartitionedCookieAccess = await document.hasUnpartitionedCookieAccess();
+ if (hasUnpartitionedCookieAccess) {
+ message = "First-party cookies should not be readable if not requested.";
+ }
+ const handle_dbs = await handle.indexedDB.databases();
+ if (handle_dbs.length != 1 || handle_dbs[0].name != id) {
+ message = "No first-party IndexedDB access";
+ }
+ const local_dbs = await window.indexedDB.databases();
+ if (local_dbs.length != 0) {
+ message = "Handle should not override window IndexedDB";
+ }
+ await handle.indexedDB.deleteDatabase(id);
+ break;
+ }
+ case "locks": {
+ const handle = await document.requestStorageAccess({locks: true});
+ let hasUnpartitionedCookieAccess = await document.hasUnpartitionedCookieAccess();
+ if (hasUnpartitionedCookieAccess) {
+ message = "First-party cookies should not be readable if not requested.";
+ }
+ const handle_state = await handle.locks.query();
+ if (handle_state.held.length != 1 || handle_state.held[0].name != id) {
+ message = "No first-party Web Lock access";
+ }
+ const local_state = await window.navigator.locks.query();
+ if (local_state.held.length != 0) {
+ message = "Handle should not override window Web Locks";
+ }
+ await handle.locks.request(id, {steal: true}, async () => {});
+ break;
+ }
+ case "caches": {
+ const handle = await document.requestStorageAccess({caches: true});
+ let hasUnpartitionedCookieAccess = await document.hasUnpartitionedCookieAccess();
+ if (hasUnpartitionedCookieAccess) {
+ message = "First-party cookies should not be readable if not requested.";
+ }
+ const handle_has = await handle.caches.has(id);
+ if (!handle_has) {
+ message = "No first-party Cache Storage access";
+ }
+ const local_has = await window.caches.has(id);
+ if (local_has) {
+ message = "Handle should not override window Cache Storage";
+ }
+ await handle.caches.delete(id);
+ break;
+ }
+ case "getDirectory": {
+ const handle = await document.requestStorageAccess({getDirectory: true});
+ let hasUnpartitionedCookieAccess = await document.hasUnpartitionedCookieAccess();
+ if (hasUnpartitionedCookieAccess) {
+ message = "First-party cookies should not be readable if not requested.";
+ }
+ const handle_root = await handle.getDirectory();
+ let handle_has = await handle_root.getFileHandle(id).then(() => true, () => false);
+ if (!handle_has) {
+ message = "No first-party Origin Private File System access";
+ }
+ const local_root = await window.navigator.storage.getDirectory();
+ let local_has = await local_root.getFileHandle(id).then(() => true, () => false);
+ if (local_has) {
+ message = "Handle should not override window Origin Private File System";
+ }
+ await handle_root.removeEntry(id);
+ break;
+ }
+ case "estimate": {
+ const handle = await document.requestStorageAccess({estimate: true});
+ let hasUnpartitionedCookieAccess = await document.hasUnpartitionedCookieAccess();
+ if (hasUnpartitionedCookieAccess) {
+ message = "First-party cookies should not be readable if not requested.";
+ }
+ const handle_estimate = await handle.estimate();
+ if (handle_estimate.usage <= 0) {
+ message = "No first-party quota access";
+ }
+ const local_estimate = await window.navigator.storage.estimate();
+ if (local_estimate > 0) {
+ message = "Handle should not override window quota";
+ }
+ break;
+ }
+ case "blobStorage": {
+ const handle = await document.requestStorageAccess({createObjectURL: true, revokeObjectURL: true});
+ let hasUnpartitionedCookieAccess = await document.hasUnpartitionedCookieAccess();
+ if (hasUnpartitionedCookieAccess) {
+ message = "First-party cookies should not be readable if not requested.";
+ }
+ let blob = await fetch(atob(id)).then(
+ (response) => response.text(),
+ () => "");
+ if (blob != "TEST") {
+ message = "Blob storage should be readable in this context";
+ }
+ URL.revokeObjectURL(atob(id));
+ blob = await fetch(atob(id)).then(
+ (response) => response.text(),
+ () => "");
+ if (blob != "TEST") {
+ message = "Handle should not override window blob storage";
+ }
+ handle.revokeObjectURL(atob(id));
+ blob = await fetch(atob(id)).then(
+ (response) => response.text(),
+ () => "");
+ if (blob != "") {
+ message = "No first-party blob access";
+ }
+ const url = handle.createObjectURL(new Blob(["TEST2"]));
+ blob = await fetch(url).then(
+ (response) => response.text(),
+ () => "");
+ if (blob != "TEST2") {
+ message = "A blob url created via the handle should be readable";
+ }
+ handle.revokeObjectURL(url);
+ blob = await fetch(url).then(
+ (response) => response.text(),
+ () => "");
+ if (blob != "") {
+ message = "A blob url removed via the handle should be cleared";
+ }
+ break;
+ }
+ case "BroadcastChannel": {
+ const handle = await document.requestStorageAccess({BroadcastChannel: true});
+ let hasUnpartitionedCookieAccess = await document.hasUnpartitionedCookieAccess();
+ if (hasUnpartitionedCookieAccess) {
+ message = "First-party cookies should not be readable if not requested.";
+ }
+ const handle_channel = handle.BroadcastChannel(id);
+ handle_channel.postMessage("Same-origin handle access");
+ handle_channel.close();
+ const local_channel = new BroadcastChannel(id);
+ local_channel.postMessage("Same-origin local access");
+ local_channel.close();
+ break;
+ }
+ default: {
+ message = "Unexpected type " + type;
+ break;
+ }
+ }
+ } catch (_) {
+ message = "Unable to load handle in same-origin context for " + type;
+ }
+ // Step 7 (storage-access-api/storage-access-beyond-cookies.{}.tentative.sub.https.html)
+ await MaybeSetStorageAccess("*", "*", "allowed");
+ await test_driver.set_permission({ name: 'storage-access' }, 'prompt');
+ window.top.postMessage(message, "*");
+})();
+</script>
diff --git a/testing/web-platform/tests/storage-access-api/resources/storage-access-beyond-cookies-iframe.sub.html b/testing/web-platform/tests/storage-access-api/resources/storage-access-beyond-cookies-iframe.sub.html
new file mode 100644
index 0000000000..8c30973416
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/resources/storage-access-beyond-cookies-iframe.sub.html
@@ -0,0 +1,131 @@
+<!doctype html>
+<meta charset="utf-8">
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/storage-access-api/helpers.js"></script>
+<body>
+<script>
+(async function() {
+ test_driver.set_test_context(window.top);
+ const type = (new URLSearchParams(window.location.search)).get("type");
+ const id = (new URLSearchParams(window.location.search)).get("id");
+ let message = "";
+ // Step 4 (storage-access-api/storage-access-beyond-cookies.{}.tentative.sub.https.html)
+ try {
+ await MaybeSetStorageAccess("*", "*", "blocked");
+ await test_driver.set_permission({ name: 'storage-access' }, 'granted');
+ let hasUnpartitionedCookieAccess = await document.hasUnpartitionedCookieAccess();
+ if (hasUnpartitionedCookieAccess) {
+ message = "First-party cookies should not be readable before handle is loaded.";
+ }
+ const handle = await document.requestStorageAccess({all: true});
+ hasUnpartitionedCookieAccess = await document.hasUnpartitionedCookieAccess();
+ if (!hasUnpartitionedCookieAccess) {
+ message = "First-party cookies should be readable after handle is loaded.";
+ }
+ switch (type) {
+ case "none": {
+ break;
+ }
+ case "cookies": {
+ if (document.cookie.includes("test="+id)) {
+ message = "Cross-site first-party cookies should be empty";
+ }
+ break;
+ }
+ case "sessionStorage": {
+ if (!!handle.sessionStorage.getItem("test")) {
+ message = "Cross-site first-party Session Storage should be empty";
+ }
+ handle.sessionStorage.setItem("test2", id);
+ if (window.sessionStorage.getItem("test2") == id) {
+ message = "Handle bound partitioned instead of unpartitioned Session Storage";
+ }
+ handle.sessionStorage.clear();
+ window.sessionStorage.clear();
+ break;
+ }
+ case "localStorage": {
+ if (!!handle.localStorage.getItem("test")) {
+ message = "Cross-site first-party Local Storage should be empty";
+ }
+ handle.localStorage.setItem("test2", id);
+ if (window.localStorage.getItem("test2") == id) {
+ message = "Handle bound partitioned instead of unpartitioned Local Storage";
+ }
+ handle.localStorage.clear();
+ window.localStorage.clear();
+ break;
+ }
+ case "indexedDB": {
+ const dbs = await handle.indexedDB.databases();
+ if (dbs.length != 0) {
+ message = "Cross-site first-party IndexedDB should be empty";
+ }
+ break;
+ }
+ case "locks": {
+ const state = await handle.locks.query();
+ if (state.held.length != 0) {
+ message = "Cross-site first-party Web Locks should be empty";
+ }
+ break;
+ }
+ case "caches": {
+ const has = await handle.caches.has(id);
+ if (has) {
+ message = "Cross-site first-party Cache Storage should be empty";
+ }
+ break;
+ }
+ case "getDirectory": {
+ const root = await handle.getDirectory();
+ let has = await root.getFileHandle(id).then(() => true, () => false);;
+ if (has) {
+ message = "Cross-site first-party Origin Private File System should be empty";
+ }
+ break;
+ }
+ case "estimate": {
+ const estimate = await handle.estimate();
+ if (estimate.usage > 0) {
+ message = "Cross-site first-party estimate should be empty";
+ }
+ break;
+ }
+ case "blobStorage": {
+ const blob = await fetch(atob(id)).then(
+ (response) => response.text(),
+ () => "");
+ if (blob != "") {
+ message = "Cross-site first-party blob storage should be empty";
+ }
+ break;
+ }
+ case "BroadcastChannel": {
+ const channel = handle.BroadcastChannel(id);
+ channel.postMessage("Cross-origin handle access");
+ channel.close();
+ break;
+ }
+ default: {
+ message = "Unexpected type " + type;
+ break;
+ }
+ }
+ } catch (_) {
+ message = "Unable to load handle in cross-site context for all";
+ }
+ await MaybeSetStorageAccess("*", "*", "allowed");
+ await test_driver.set_permission({ name: 'storage-access' }, 'prompt');
+ if (message) {
+ window.top.postMessage(message, "*");
+ return;
+ }
+ // Step 5 (storage-access-api/storage-access-beyond-cookies.{}.tentative.sub.https.html)
+ let iframe = document.createElement("iframe");
+ iframe.src = "https://{{hosts[][]}}:{{ports[https][0]}}/storage-access-api/resources/storage-access-beyond-cookies-iframe-iframe.html?type=" + type + "&id=" + id;
+ document.body.appendChild(iframe);
+})();
+</script>
+</body>
diff --git a/testing/web-platform/tests/storage-access-api/sandboxAttribute.window.js b/testing/web-platform/tests/storage-access-api/sandboxAttribute.window.js
new file mode 100644
index 0000000000..de79cd07a9
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/sandboxAttribute.window.js
@@ -0,0 +1,7 @@
+'use strict';
+
+test(() => {
+ let iframe = document.createElement('iframe');
+ assert_true(iframe.sandbox.supports('allow-storage-access-by-user-activation'), '`allow-storage-access-by-user-activation`' +
+ 'sandbox attribute should be supported');
+}, "`allow-storage-access-by-user-activation` sandbox attribute is supported");
diff --git a/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.BroadcastChannel.tentative.sub.https.window.js b/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.BroadcastChannel.tentative.sub.https.window.js
new file mode 100644
index 0000000000..d709cdcd10
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.BroadcastChannel.tentative.sub.https.window.js
@@ -0,0 +1,37 @@
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+
+'use strict';
+
+// Here's the set-up for this test:
+// Step 1 (top-frame) Set up listener for "HasAccess" message.
+// Step 2 (top-frame) Open channel first-party broadcast.
+// Step 3 (top-frame) Embed an iframe that's cross-site with top-frame.
+// Step 4 (sub-frame) Try to use storage access API and send first-party broadcast.
+// Step 5 (sub-frame) Embed an iframe that's same-origin with top-frame.
+// Step 6 (sub-sub-frame) Try to use storage access API and send first-party broadcast.
+// Step 7 (sub-sub-frame) Send "HasAccess for BroadcastChannel" message to top-frame.
+// Step 8 (top-frame) Receive "HasAccess for BroadcastChannel" message and cleanup.
+
+async_test(t => {
+ let broadcasts = [];
+ // Step 1
+ window.addEventListener("message", t.step_func(e => {
+ // Step 8
+ assert_equals(e.data, "HasAccess for BroadcastChannel", "Storage Access API should be accessible and return first-party data");
+ assert_array_equals(broadcasts, ["Same-origin handle access"], "Should have only seen same-origin handle broadcasts");
+ t.done();
+ }));
+
+ // Step 2
+ const id = Date.now();
+ const channel = new BroadcastChannel(id);
+ channel.onmessage = (event) => {
+ broadcasts.push(event.data);
+ };
+
+ // Step 3
+ let iframe = document.createElement("iframe");
+ iframe.src = "https://{{hosts[alt][]}}:{{ports[https][0]}}/storage-access-api/resources/storage-access-beyond-cookies-iframe.sub.html?type=BroadcastChannel&id="+id;
+ document.body.appendChild(iframe);
+}, "Verify StorageAccessAPIBeyondCookies for Broadcast Channel");
diff --git a/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.blobStorage.tentative.sub.https.window.js b/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.blobStorage.tentative.sub.https.window.js
new file mode 100644
index 0000000000..6ef0bd08d4
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.blobStorage.tentative.sub.https.window.js
@@ -0,0 +1,31 @@
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+
+'use strict';
+
+// Here's the set-up for this test:
+// Step 1 (top-frame) Set up listener for "HasAccess" message.
+// Step 2 (top-frame) Add data to first-party blob storage.
+// Step 3 (top-frame) Embed an iframe that's cross-site with top-frame.
+// Step 4 (sub-frame) Try to use storage access API and read first-party data.
+// Step 5 (sub-frame) Embed an iframe that's same-origin with top-frame.
+// Step 6 (sub-sub-frame) Try to use storage access API and read first-party data.
+// Step 7 (sub-sub-frame) Send "HasAccess for blobStorage" message to top-frame.
+// Step 8 (top-frame) Receive "HasAccess for blobStorage" message and cleanup.
+
+async_test(t => {
+ // Step 1
+ window.addEventListener("message", t.step_func(e => {
+ // Step 8
+ assert_equals(e.data, "HasAccess for blobStorage", "Storage Access API should be accessible and return first-party data");
+ t.done();
+ }));
+
+ // Step 2
+ const id = btoa(URL.createObjectURL(new Blob(["TEST"])));
+
+ // Step 3
+ let iframe = document.createElement("iframe");
+ iframe.src = "https://{{hosts[alt][]}}:{{ports[https][0]}}/storage-access-api/resources/storage-access-beyond-cookies-iframe.sub.html?type=blobStorage&id="+id;
+ document.body.appendChild(iframe);
+}, "Verify StorageAccessAPIBeyondCookies for Blob Storage");
diff --git a/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.caches.tentative.sub.https.window.js b/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.caches.tentative.sub.https.window.js
new file mode 100644
index 0000000000..dda1e54565
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.caches.tentative.sub.https.window.js
@@ -0,0 +1,32 @@
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+
+'use strict';
+
+// Here's the set-up for this test:
+// Step 1 (top-frame) Set up listener for "HasAccess" message.
+// Step 2 (top-frame) Write first-party cache storage.
+// Step 3 (top-frame) Embed an iframe that's cross-site with top-frame.
+// Step 4 (sub-frame) Try to use storage access API and read first-party data.
+// Step 5 (sub-frame) Embed an iframe that's same-origin with top-frame.
+// Step 6 (sub-sub-frame) Try to use storage access API and read first-party data.
+// Step 7 (sub-sub-frame) Send "HasAccess for caches" message to top-frame.
+// Step 8 (top-frame) Receive "HasAccess for caches" message and cleanup.
+
+async_test(t => {
+ // Step 1
+ window.addEventListener("message", t.step_func(e => {
+ // Step 8
+ assert_equals(e.data, "HasAccess for caches", "Storage Access API should be accessible and return first-party data");
+ t.done();
+ }));
+
+ // Step 2
+ const id = Date.now();
+ window.caches.open(id).then(() => {
+ // Step 3
+ let iframe = document.createElement("iframe");
+ iframe.src = "https://{{hosts[alt][]}}:{{ports[https][0]}}/storage-access-api/resources/storage-access-beyond-cookies-iframe.sub.html?type=caches&id="+id;
+ document.body.appendChild(iframe);
+ });
+}, "Verify StorageAccessAPIBeyondCookies for Cache Storage");
diff --git a/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.cookies.tentative.sub.https.window.js b/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.cookies.tentative.sub.https.window.js
new file mode 100644
index 0000000000..c352ab2935
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.cookies.tentative.sub.https.window.js
@@ -0,0 +1,34 @@
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+
+'use strict';
+
+// Here's the set-up for this test:
+// Step 1 (top-frame) Set up listener for "HasAccess" message.
+// Step 2 (top-frame) Add data to first-party cookies.
+// Step 3 (top-frame) Embed an iframe that's cross-site with top-frame.
+// Step 4 (sub-frame) Try to use storage access API and read first-party data.
+// Step 5 (sub-frame) Embed an iframe that's same-origin with top-frame.
+// Step 6 (sub-sub-frame) Try to use storage access API and read first-party data.
+// Step 7 (sub-sub-frame) Send "HasAccess for cookies" message to top-frame.
+// Step 8 (top-frame) Cleanup.
+
+async_test(t => {
+ // Step 1
+ window.addEventListener("message", t.step_func(e => {
+ // Step 8
+ assert_equals(e.data, "HasAccess for cookies", "Storage Access API should be accessible and return first-party data");
+ test_driver.delete_all_cookies().then(t.step_func(() => {
+ t.done();
+ }));
+ }));
+
+ // Step 2
+ const id = String(Date.now());
+ document.cookie = "test=" + id + "; SameSite=None; Secure";
+
+ // Step 3
+ let iframe = document.createElement("iframe");
+ iframe.src = "https://{{hosts[alt][]}}:{{ports[https][0]}}/storage-access-api/resources/storage-access-beyond-cookies-iframe.sub.html?type=cookies&id="+id;
+ document.body.appendChild(iframe);
+}, "Verify StorageAccessAPIBeyondCookies for Cookies");
diff --git a/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.estimate.tentative.sub.https.window.js b/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.estimate.tentative.sub.https.window.js
new file mode 100644
index 0000000000..2e9f6eed12
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.estimate.tentative.sub.https.window.js
@@ -0,0 +1,37 @@
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+
+'use strict';
+
+// Here's the set-up for this test:
+// Step 1 (top-frame) Set up listener for "HasAccess" message.
+// Step 2 (top-frame) Add data to first-party cache storage.
+// Step 3 (top-frame) Embed an iframe that's cross-site with top-frame.
+// Step 4 (sub-frame) Try to use storage access API and estimate first-party data.
+// Step 5 (sub-frame) Embed an iframe that's same-origin with top-frame.
+// Step 6 (sub-sub-frame) Try to use storage access API and estimate first-party data.
+// Step 7 (sub-sub-frame) Send "HasAccess for estimate" message to top-frame.
+// Step 8 (top-frame) Receive "HasAccess for estimate" message and cleanup.
+
+async_test(t => {
+ const id = "test";
+
+ // Step 1
+ window.addEventListener("message", t.step_func(e => {
+ // Step 8
+ assert_equals(e.data, "HasAccess for estimate", "Storage Access API should be accessible and return first-party data");
+ caches.delete(id).then(() => {
+ t.done();
+ });
+ }));
+
+ // Step 2
+ window.caches.open(id).then((cache) => {
+ cache.put('/test.json', new Response('x'.repeat(1024*1024))).then(() => {
+ // Step 3
+ let iframe = document.createElement("iframe");
+ iframe.src = "https://{{hosts[alt][]}}:{{ports[https][0]}}/storage-access-api/resources/storage-access-beyond-cookies-iframe.sub.html?type=estimate&id="+id;
+ document.body.appendChild(iframe);
+ });
+ });
+}, "Verify StorageAccessAPIBeyondCookies for Quota");
diff --git a/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.getDirectory.tentative.sub.https.window.js b/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.getDirectory.tentative.sub.https.window.js
new file mode 100644
index 0000000000..5038afc969
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.getDirectory.tentative.sub.https.window.js
@@ -0,0 +1,34 @@
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+
+'use strict';
+
+// Here's the set-up for this test:
+// Step 1 (top-frame) Set up listener for "HasAccess" message.
+// Step 2 (top-frame) Write first-party file.
+// Step 3 (top-frame) Embed an iframe that's cross-site with top-frame.
+// Step 4 (sub-frame) Try to use storage access API and read first-party data.
+// Step 5 (sub-frame) Embed an iframe that's same-origin with top-frame.
+// Step 6 (sub-sub-frame) Try to use storage access API and read first-party data.
+// Step 7 (sub-sub-frame) Send "HasAccess for getDirectory" message to top-frame.
+// Step 8 (top-frame) Receive "HasAccess for getDirectory" message and cleanup.
+
+async_test(t => {
+ // Step 1
+ window.addEventListener("message", t.step_func(e => {
+ // Step 8
+ assert_equals(e.data, "HasAccess for getDirectory", "Storage Access API should be accessible and return first-party data");
+ t.done();
+ }));
+
+ // Step 2
+ const id = Date.now();
+ window.navigator.storage.getDirectory().then((root) => {
+ root.getFileHandle(id, {create: true}).then(() => {
+ // Step 3
+ let iframe = document.createElement("iframe");
+ iframe.src = "https://{{hosts[alt][]}}:{{ports[https][0]}}/storage-access-api/resources/storage-access-beyond-cookies-iframe.sub.html?type=getDirectory&id="+id;
+ document.body.appendChild(iframe);
+ });
+ });
+}, "Verify StorageAccessAPIBeyondCookies for Origin Private File System");
diff --git a/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.indexedDB.tentative.sub.https.window.js b/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.indexedDB.tentative.sub.https.window.js
new file mode 100644
index 0000000000..18c4317bbe
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.indexedDB.tentative.sub.https.window.js
@@ -0,0 +1,33 @@
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+
+'use strict';
+
+// Here's the set-up for this test:
+// Step 1 (top-frame) Set up listener for "HasAccess" message.
+// Step 2 (top-frame) Add data to first-party indexed db.
+// Step 3 (top-frame) Embed an iframe that's cross-site with top-frame.
+// Step 4 (sub-frame) Try to use storage access API and read first-party data.
+// Step 5 (sub-frame) Embed an iframe that's same-origin with top-frame.
+// Step 6 (sub-sub-frame) Try to use storage access API and read first-party data.
+// Step 7 (sub-sub-frame) Send "HasAccess for indexedDB" message to top-frame.
+// Step 8 (top-frame) Receive "HasAccess for indexedDB" message and cleanup.
+
+async_test(t => {
+ // Step 1
+ window.addEventListener("message", t.step_func(e => {
+ // Step 8
+ assert_equals(e.data, "HasAccess for indexedDB", "Storage Access API should be accessible and return first-party data");
+ t.done();
+ }));
+
+ // Step 2
+ const id = Date.now();
+ let request = window.indexedDB.open(id);
+ request.onsuccess = () => {
+ // Step 3
+ let iframe = document.createElement("iframe");
+ iframe.src = "https://{{hosts[alt][]}}:{{ports[https][0]}}/storage-access-api/resources/storage-access-beyond-cookies-iframe.sub.html?type=indexedDB&id="+id;
+ document.body.appendChild(iframe);
+ };
+}, "Verify StorageAccessAPIBeyondCookies for IndexedDB");
diff --git a/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.localStorage.tentative.sub.https.window.js b/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.localStorage.tentative.sub.https.window.js
new file mode 100644
index 0000000000..6243cb1fa8
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.localStorage.tentative.sub.https.window.js
@@ -0,0 +1,40 @@
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+
+'use strict';
+
+// Here's the set-up for this test:
+// Step 1 (top-frame) Set up listener for "HasAccess" message and set data for window_event if so.
+// Step 2 (top-frame) Add data to first-party local storage and set up listener for handle_event.
+// Step 3 (top-frame) Embed an iframe that's cross-site with top-frame.
+// Step 4 (sub-frame) Try to use storage access API and read first-party data.
+// Step 5 (sub-frame) Embed an iframe that's same-origin with top-frame.
+// Step 6 (sub-sub-frame) Try to use storage access API and read first-party data.
+// Step 7 (sub-sub-frame) Send "HasAccess for localStorage" message to top-frame.
+// Step 8 (top-frame) Expect new data to be set and properly trigger listener.
+
+async_test(t => {
+ // Step 1
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data, "HasAccess for localStorage", "Storage Access API should be accessible and return first-party data");
+ window.localStorage.setItem("window_event", id);
+ }));
+
+ // Step 2
+ const id = String(Date.now());
+ window.localStorage.setItem("test", id);
+ window.addEventListener("storage", t.step_func(e => {
+ if (e.key == "handle_event") {
+ // Step 8
+ assert_equals(e.newValue, id);
+ assert_equals(e.storageArea, window.localStorage);
+ window.localStorage.clear();
+ t.done();
+ }
+ }));
+
+ // Step 3
+ let iframe = document.createElement("iframe");
+ iframe.src = "https://{{hosts[alt][]}}:{{ports[https][0]}}/storage-access-api/resources/storage-access-beyond-cookies-iframe.sub.html?type=localStorage&id="+id;
+ document.body.appendChild(iframe);
+}, "Verify StorageAccessAPIBeyondCookies for Local Storage");
diff --git a/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.locks.tentative.sub.https.window.js b/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.locks.tentative.sub.https.window.js
new file mode 100644
index 0000000000..83aa28c018
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.locks.tentative.sub.https.window.js
@@ -0,0 +1,34 @@
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+
+'use strict';
+
+// Here's the set-up for this test:
+// Step 1 (top-frame) Set up listener for "HasAccess" message.
+// Step 2 (top-frame) Secure first-party web lock.
+// Step 3 (top-frame) Embed an iframe that's cross-site with top-frame.
+// Step 4 (sub-frame) Try to use storage access API and read first-party data.
+// Step 5 (sub-frame) Embed an iframe that's same-origin with top-frame.
+// Step 6 (sub-sub-frame) Try to use storage access API and read first-party data.
+// Step 7 (sub-sub-frame) Send "HasAccess for locks" message to top-frame.
+// Step 8 (top-frame) Receive "HasAccess for locks" message and cleanup.
+
+async_test(t => {
+ // Step 1
+ window.addEventListener("message", t.step_func(e => {
+ // Step 8
+ assert_equals(e.data, "HasAccess for locks", "Storage Access API should be accessible and return first-party data");
+ t.done();
+ }));
+
+ // Step 2
+ const id = Date.now();
+ window.navigator.locks.request(id, async () => {
+ // Step 3
+ let iframe = document.createElement("iframe");
+ iframe.src = "https://{{hosts[alt][]}}:{{ports[https][0]}}/storage-access-api/resources/storage-access-beyond-cookies-iframe.sub.html?type=locks&id="+id;
+ document.body.appendChild(iframe);
+ // We need to sleep so this locks stays engaged until the test succeeds or times out.
+ await new Promise(r => setTimeout(r, 30000))
+ }).catch (() => {/*We expect this lock to be stolen if the test runs correctly.*/});
+}, "Verify StorageAccessAPIBeyondCookies for Web Locks");
diff --git a/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.none.tentative.sub.https.window.js b/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.none.tentative.sub.https.window.js
new file mode 100644
index 0000000000..3715fdf39e
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.none.tentative.sub.https.window.js
@@ -0,0 +1,29 @@
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+
+'use strict';
+
+// Here's the set-up for this test:
+// Step 1 (top-frame) Set up listener for "HasAccess" message.
+// Step 2 (top-frame) Skipped in this test, but numbering must be consistent with other tests.
+// Step 3 (top-frame) Embed an iframe that's cross-site with top-frame.
+// Step 4 (sub-frame) Skipped in this test, but numbering must be consistent with other tests.
+// Step 5 (sub-frame) Embed an iframe that's same-origin with top-frame.
+// Step 6 (sub-sub-frame) Try to use storage access API without requesting anything.
+// Step 7 (sub-sub-frame) Send "HasAccess for none" message to top-frame.
+// Step 8 (top-frame) Cleanup.
+
+async_test(t => {
+ // Step 1
+ window.addEventListener("message", t.step_func(e => {
+ // Step 8
+ assert_equals(e.data, "HasAccess for none", "Storage Access API should not allow access for empty requests.");
+ t.done();
+ }));
+
+ // Step 2
+ // Step 3
+ let iframe = document.createElement("iframe");
+ iframe.src = "https://{{hosts[alt][]}}:{{ports[https][0]}}/storage-access-api/resources/storage-access-beyond-cookies-iframe.sub.html?type=none&id=";
+ document.body.appendChild(iframe);
+}, "Verify StorageAccessAPIBeyondCookies for None");
diff --git a/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.sessionStorage.tentative.sub.https.window.js b/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.sessionStorage.tentative.sub.https.window.js
new file mode 100644
index 0000000000..1b12f133b2
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/storage-access-beyond-cookies.sessionStorage.tentative.sub.https.window.js
@@ -0,0 +1,40 @@
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+
+'use strict';
+
+// Here's the set-up for this test:
+// Step 1 (top-frame) Set up listener for "HasAccess" message and set data for window_event if so.
+// Step 2 (top-frame) Add data to first-party session storage and set up listener for handle_event.
+// Step 3 (top-frame) Embed an iframe that's cross-site with top-frame.
+// Step 4 (sub-frame) Try to use storage access API and read first-party data.
+// Step 5 (sub-frame) Embed an iframe that's same-origin with top-frame.
+// Step 6 (sub-sub-frame) Try to use storage access API and read first-party data.
+// Step 7 (sub-sub-frame) Send "HasAccess for sessionStorage" message to top-frame.
+// Step 8 (top-frame) Expect new data to be set and properly trigger listener.
+
+async_test(t => {
+ // Step 1
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data, "HasAccess for sessionStorage", "Storage Access API should be accessible and return first-party data");
+ window.sessionStorage.setItem("window_event", id);
+ }));
+
+ // Step 2
+ const id = String(Date.now());
+ window.sessionStorage.setItem("test", id);
+ window.addEventListener("storage", t.step_func(e => {
+ if (e.key == "handle_event") {
+ // Step 8
+ assert_equals(e.newValue, id);
+ assert_equals(e.storageArea, window.sessionStorage);
+ window.sessionStorage.clear();
+ t.done();
+ }
+ }));
+
+ // Step 3
+ let iframe = document.createElement("iframe");
+ iframe.src = "https://{{hosts[alt][]}}:{{ports[https][0]}}/storage-access-api/resources/storage-access-beyond-cookies-iframe.sub.html?type=sessionStorage&id=" + id;
+ document.body.appendChild(iframe);
+}, "Verify StorageAccessAPIBeyondCookies for Session Storage");
diff --git a/testing/web-platform/tests/storage-access-api/storage-access-permission.sub.https.window.js b/testing/web-platform/tests/storage-access-api/storage-access-permission.sub.https.window.js
new file mode 100644
index 0000000000..f0aadf4828
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/storage-access-permission.sub.https.window.js
@@ -0,0 +1,108 @@
+// META: script=helpers.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+'use strict';
+
+(async function() {
+ // These are cross-domain from the current document.
+ const wwwAlt = "https://{{hosts[alt][www]}}:{{ports[https][0]}}";
+ const www1Alt = "https://{{hosts[alt][www1]}}:{{ports[https][0]}}";
+ const responder_html = "/storage-access-api/resources/script-with-cookie-header.py?script=embedded_responder.js";
+
+ if (window === window.top) {
+ // Test the interaction between two (same-origin) iframes.
+ promise_test(async (t) => {
+ const [frame1, frame2] = await Promise.all([
+ CreateFrame(wwwAlt + responder_html),
+ CreateFrame(wwwAlt + responder_html),
+ ]);
+
+ t.add_cleanup(async () => {
+ await SetPermissionInFrame(frame1, [{ name: 'storage-access' }, 'prompt']);
+ });
+
+ const observed = ObservePermissionChange(frame2);
+ await SetPermissionInFrame(frame1, [{ name: 'storage-access' }, 'granted']);
+
+ const state = await observed;
+ assert_equals(state, "granted");
+ }, "Permissions grants are observable across same-origin iframes");
+
+ // Test the interaction between two cross-origin but same-site iframes.
+ promise_test(async (t) => {
+ const [frame1, frame2] = await Promise.all([
+ CreateFrame(wwwAlt + responder_html),
+ CreateFrame(www1Alt + responder_html),
+ ]);
+
+ t.add_cleanup(async () => {
+ await SetPermissionInFrame(frame1, [{ name: 'storage-access' }, 'prompt']);
+ });
+
+ const observed = ObservePermissionChange(frame2);
+ await SetPermissionInFrame(frame1, [{ name: 'storage-access' }, 'granted']);
+
+ const state = await observed;
+ assert_equals(state, "granted");
+ }, "Permissions grants are observable across same-site iframes");
+
+ promise_test(async (t) => {
+ // Finally run the simple tests below in a separate cross-origin iframe.
+ await RunTestsInIFrame('https://{{domains[www]}}:{{ports[https][0]}}/storage-access-api/resources/permissions-iframe.https.html');
+ }, "IFrame tests");
+ return;
+ }
+
+ // We're in a cross-origin, same-site iframe test now.
+ test_driver.set_test_context(window.top);
+
+ promise_test(async t => {
+ const permission = await navigator.permissions.query({name: "storage-access"});
+ assert_equals(permission.name, "storage-access");
+ assert_equals(permission.state, "prompt");
+ }, "Permission default state can be queried");
+
+ promise_test(async t => {
+ t.add_cleanup(async () => {
+ await test_driver.set_permission({ name: 'storage-access' }, 'prompt');
+ });
+ await test_driver.set_permission({ name: 'storage-access' }, 'granted');
+
+ const permission = await navigator.permissions.query({name: "storage-access"});
+ assert_equals(permission.name, "storage-access");
+ assert_equals(permission.state, "granted");
+ }, "Permission granted state can be queried");
+
+ promise_test(async t => {
+ t.add_cleanup(async () => {
+ await test_driver.set_permission({ name: 'storage-access' }, 'prompt');
+ });
+ await test_driver.set_permission({ name: 'storage-access' }, 'denied');
+
+ const permission = await navigator.permissions.query({name: "storage-access"});
+ assert_equals(permission.name, "storage-access");
+ assert_equals(permission.state, "prompt");
+
+ await test_driver.set_permission({ name: 'storage-access' }, 'prompt');
+ }, "Permission denied state is hidden");
+
+ promise_test(async t => {
+ t.add_cleanup(async () => {
+ await test_driver.set_permission({ name: 'storage-access' }, 'prompt');
+ });
+
+ const permission = await navigator.permissions.query({name: "storage-access"});
+
+ const p = new Promise(resolve => {
+ permission.addEventListener("change", (event) => resolve(event), { once: true });
+ });
+
+ await test_driver.set_permission({ name: 'storage-access' }, 'granted');
+ await document.requestStorageAccess();
+
+ const event = await p;
+
+ assert_equals(event.target.name, "storage-access");
+ assert_equals(event.target.state, "granted");
+ }, "Permission state can be observed");
+})();
diff --git a/testing/web-platform/tests/storage-access-api/storageAccess.testdriver.sub.html b/testing/web-platform/tests/storage-access-api/storageAccess.testdriver.sub.html
new file mode 100644
index 0000000000..cc945dd182
--- /dev/null
+++ b/testing/web-platform/tests/storage-access-api/storageAccess.testdriver.sub.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<head>
+ <title>TestDriver - Set Storage Access Command Tests</title>
+ <script src="/cookies/resources/cookie-helper.sub.js"></script>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="helpers.js"></script>
+</head>
+<body>
+ <script>
+ "use strict";
+
+ // Use a different domain so that the cookie is cross-site.
+ const wwwAlt = "https://{{hosts[alt][www]}}:{{ports[https][0]}}";
+
+ promise_test(async t => {
+ await MaybeSetStorageAccess("*", "*", "blocked");
+ t.add_cleanup(async () => {
+ await test_driver.delete_all_cookies();
+ await MaybeSetStorageAccess("*", "*", "allowed");
+ });
+
+ const responder_html = `${wwwAlt}/storage-access-api/resources/script-with-cookie-header.py?script=embedded_responder.js`;
+ const frame = await CreateFrame(responder_html);
+
+ assert_false(await CanFrameWriteCookies(frame), "Cross-site iframe should not be allowed to write cookies via document.cookie.");
+ });
+ </script>
+</body>