diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/storage-access-api | |
parent | Initial commit. (diff) | |
download | firefox-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')
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> |