diff options
Diffstat (limited to '')
46 files changed, 1376 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-insecure.sub.window.js b/testing/web-platform/tests/storage-access-api/hasStorageAccess-insecure.sub.window.js new file mode 100644 index 0000000000..35bb73a80a --- /dev/null +++ b/testing/web-platform/tests/storage-access-api/hasStorageAccess-insecure.sub.window.js @@ -0,0 +1,42 @@ +// META: script=helpers.js +'use strict'; + +const {expectAccessAllowed, testPrefix, topLevelDocument} = processQueryParams(); + +// Common tests to run in all frames. +test(() => { + 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 () => { + const createdDocument = document.implementation.createDocument("", null); + + const hasAccess = await createdDocument.hasStorageAccess(); + assert_false(hasAccess, "Access should be denied to a generated document not part of the DOM."); +}, "[" + testPrefix + "] document.hasStorageAccess() should work on a document object."); + +// 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&rootdocument=false"); + + // 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&rootdocument=false"); + + // 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&rootdocument=false"); + + // 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&rootdocument=false"); +} 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..ede7dd62b5 --- /dev/null +++ b/testing/web-platform/tests/storage-access-api/hasStorageAccess.sub.https.window.js @@ -0,0 +1,42 @@ +// META: script=helpers.js +'use strict'; + +const {expectAccessAllowed, testPrefix, topLevelDocument} = processQueryParams(); + +// Common tests to run in all frames. +test(() => { + assert_not_equals(document.hasStorageAccess, undefined); +}, "[" + testPrefix + "] document.hasStorageAccess() should exist on the document interface"); + +promise_test(async () => { + const hasAccess = await document.hasStorageAccess(); + assert_equals(hasAccess, expectAccessAllowed, "Access should be granted by default: " + expectAccessAllowed); +}, "[" + testPrefix + "] document.hasStorageAccess() should be allowed by default: " + expectAccessAllowed); + +promise_test(async () => { + const createdDocument = document.implementation.createDocument("", null); + + const hasAccess = await createdDocument.hasStorageAccess(); + assert_false(hasAccess, "Access should be denied to a generated document not part of the DOM."); +}, "[" + testPrefix + "] document.hasStorageAccess() should work on a document object."); + +// 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&rootdocument=false"); + + // Create a test with a single-child cross-origin iframe. + RunTestsInIFrame("https://{{domains[www]}}:{{ports[https][0]}}/storage-access-api/resources/hasStorageAccess-iframe.https.html?testCase=cross-origin-frame&rootdocument=false"); + + // 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&rootdocument=false"); + + // 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/hasStorageAccess-iframe.https.html?testCase=nested-cross-origin-frame&rootdocument=false"); +} 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..de9fba2d1c --- /dev/null +++ b/testing/web-platform/tests/storage-access-api/helpers.js @@ -0,0 +1,63 @@ +'use strict'; + +function processQueryParams() { + const queryParams = new URL(window.location).searchParams; + return { + expectAccessAllowed: queryParams.get("allowed") != "false", + topLevelDocument: queryParams.get("rootdocument") != "false", + testPrefix: queryParams.get("testCase") || "top-level-context", + }; +} + +function CreateFrameAndRunTests(setUpFrame) { + const frame = document.createElement('iframe'); + const promise = new Promise((resolve, reject) => { + frame.onload = resolve; + frame.onerror = reject; + }); + + setUpFrame(frame); + + fetch_tests_from_window(frame.contentWindow); + return promise; +} + +function RunTestsInIFrame(sourceURL) { + return CreateFrameAndRunTests((frame) => { + frame.src = sourceURL; + document.body.appendChild(frame); + }); +} + +function RunTestsInNestedIFrame(sourceURL) { + return CreateFrameAndRunTests((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(); + }); +} + +function RunRequestStorageAccessInDetachedFrame() { + const frame = document.createElement('iframe'); + document.body.append(frame); + const inner_doc = frame.contentDocument; + frame.remove(); + return inner_doc.requestStorageAccess(); +} + +function RunRequestStorageAccessViaDomParser() { + const parser = new DOMParser(); + const doc = parser.parseFromString('<html></html>', 'text/html'); + return doc.requestStorageAccess(); +} + +function RunCallbackWithGesture(callback) { + return test_driver.bless('run callback with user gesture', callback); +} 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-cross-origin-iframe.sub.https.window.js b/testing/web-platform/tests/storage-access-api/requestStorageAccess-cross-origin-iframe.sub.https.window.js new file mode 100644 index 0000000000..38e3fd6d9a --- /dev/null +++ b/testing/web-platform/tests/storage-access-api/requestStorageAccess-cross-origin-iframe.sub.https.window.js @@ -0,0 +1,18 @@ +// META: script=helpers.js +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +'use strict'; + +(async function() { + // Set up storage access rules + try { + await test_driver.set_storage_access("https://{{domains[www]}}:{{ports[https][0]}}/", "*", "blocked"); + } 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. + } + + // Create a test with a single-child cross-origin iframe. + RunTestsInIFrame('https://{{domains[www]}}:{{ports[https][0]}}/storage-access-api/resources/requestStorageAccess-iframe.https.html?testCase=cross-origin-frame&rootdocument=false'); +})(); 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..f845f0647c --- /dev/null +++ b/testing/web-platform/tests/storage-access-api/requestStorageAccess-insecure.sub.window.js @@ -0,0 +1,83 @@ +// 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. +test(() => { + 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 RunRequestStorageAccessInDetachedFrame() + .then(t.unreached_func("Should have rejected: " + description), (e) => { + assert_equals(e.name, 'InvalidStateError', description); + }); + }, "[non-fully-active] document.requestStorageAccess() should reject when run in a detached frame"); + + promise_test(t => { + return promise_rejects_dom(t, 'InvalidStateError', RunRequestStorageAccessViaDomParser(), + "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&rootdocument=false'); + + // 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&rootdocument=false'); + + // 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&rootdocument=false'); + + // 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&rootdocument=false'); + + // 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. + Promise + .all([ + sameOriginFramePromise, + crossOriginFramePromise, + nestedSameOriginFramePromise, + nestedCrossOriginFramePromise, + ]) + .then(() => { + promise_test( + async t => { + 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..bdc5429e18 --- /dev/null +++ b/testing/web-platform/tests/storage-access-api/requestStorageAccess-nested-cross-origin-iframe.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'; + +(async function() { + // Set up storage access rules + try { + await test_driver.set_storage_access("https://{{domains[www]}}:{{ports[https][0]}}/", "*", "blocked"); + } 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. + } + + // 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&rootdocument=false'); +})(); 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..b3847bbc94 --- /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&rootdocument=false'); 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..b3aa19c25c --- /dev/null +++ b/testing/web-platform/tests/storage-access-api/requestStorageAccess-non-fully-active.sub.https.window.js @@ -0,0 +1,18 @@ +// META: script=helpers.js +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +'use strict'; + +promise_test(t => { + const promise = RunRequestStorageAccessInDetachedFrame(); + 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); + }); +}, "[non-fully-active] document.requestStorageAccess() should not resolve when run in a detached frame"); + +promise_test(t => { + return promise_rejects_dom(t, 'InvalidStateError', RunRequestStorageAccessViaDomParser(), + "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-origin-iframe.sub.https.window.js b/testing/web-platform/tests/storage-access-api/requestStorageAccess-same-origin-iframe.sub.https.window.js new file mode 100644 index 0000000000..9c41d6cbbe --- /dev/null +++ b/testing/web-platform/tests/storage-access-api/requestStorageAccess-same-origin-iframe.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 single-child same-origin iframe. +RunTestsInIFrame('resources/requestStorageAccess-iframe.html?testCase=same-origin-frame&rootdocument=false'); 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..a74866e56b --- /dev/null +++ b/testing/web-platform/tests/storage-access-api/requestStorageAccess.sub.https.window.js @@ -0,0 +1,67 @@ +// 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. +const {testPrefix} = processQueryParams(); + +if (window !== window.top) { + // 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. +test(() => { + assert_not_equals(document.requestStorageAccess, undefined); +}, "[" + testPrefix + "] document.requestStorageAccess() should exist on the document interface"); + +// Promise tests should all start with the feature in "prompt" state. +promise_setup(async () => { + await test_driver.set_permission( + { name: 'storage-access' }, 'prompt'); +}); + +promise_test(t => { + return promise_rejects_dom(t, "NotAllowedError", document.requestStorageAccess(), + "document.requestStorageAccess() call without user gesture"); +}, "[" + testPrefix + "] document.requestStorageAccess() should be rejected with a NotAllowedError by default with no user gesture"); + +promise_test( + async () => { + await test_driver.set_permission( + {name: 'storage-access'}, 'granted'); + + await RunCallbackWithGesture(() => document.requestStorageAccess()); + }, + '[' + testPrefix + + '] document.requestStorageAccess() should be resolved when called properly with a user gesture'); + +if (testPrefix == 'cross-origin-frame' || testPrefix == 'nested-cross-origin-frame') { + promise_test( + async t => { + await RunCallbackWithGesture(() => { + return promise_rejects_dom(t, "NotAllowedError", document.requestStorageAccess(), + "document.requestStorageAccess() call without permission"); + }); + }, + '[' + testPrefix + + '] document.requestStorageAccess() should be rejected with a NotAllowedError without permission grant'); + + 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 without permission"); + }); + }, + '[' + testPrefix + + '] document.requestStorageAccess() should be rejected with a NotAllowedError with denied permission'); +} 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..95169503c2 --- /dev/null +++ b/testing/web-platform/tests/storage-access-api/resources/hasStorageAccess-iframe.https.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.sub.https.window.js"></script> 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..4880464a25 --- /dev/null +++ b/testing/web-platform/tests/storage-access-api/resources/requestStorageAccess-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/requestStorageAccess.sub.https.window.js"></script> diff --git a/testing/web-platform/tests/storage-access-api/resources/set-cookie.py b/testing/web-platform/tests/storage-access-api/resources/set-cookie.py new file mode 100644 index 0000000000..019697a4a8 --- /dev/null +++ b/testing/web-platform/tests/storage-access-api/resources/set-cookie.py @@ -0,0 +1,27 @@ +def main(request, response): + name = request.GET.first(b"name") + value = request.GET.first(b"value") + testcase = request.GET.first(b"testcase") + response_headers = [(b"Set-Cookie", name + b"=" + value)] + + body = b""" + <!DOCTYPE html> + <meta charset="utf-8"> + <title>Set Storage Access Subframe</title> + <script src="/resources/testharness.js"></script> + + <script> + let querystring = window.location.search.substring(1).split("&"); + const allowed = querystring.some(param => param.toLowerCase() === "allowed=true"); + + test(() => { + if (allowed) { + assert_equals(document.cookie, "%s=%s"); + } else { + assert_equals(document.cookie, ""); + } + }, "[%s] Cookie access is allowed: " + allowed); + </script> + """ % (name, value, testcase) + + return (200, response_headers, 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/storageAccess.testdriver.sub.html b/testing/web-platform/tests/storage-access-api/storageAccess.testdriver.sub.html new file mode 100644 index 0000000000..80108b5190 --- /dev/null +++ b/testing/web-platform/tests/storage-access-api/storageAccess.testdriver.sub.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<head> + <title>TestDriver - Set Storage Access Command Tests</title> + <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"; + + promise_test(async t => { + // Allow a third-party site embedded in this first-party site. + await window.test_driver.set_storage_access("http://{{domains[www]}}:{{ports[http][0]}}/", "http://{{domains[]}}:{{ports[http][0]}}/", "allowed"); + await window.test_driver.set_storage_access("https://{{domains[www]}}:{{ports[https][0]}}/", "https://{{domains[]}}:{{ports[https][0]}}/", "allowed"); + // Block a third-party site embedded in this first-party site. + await window.test_driver.set_storage_access("http://{{domains[www1]}}:{{ports[http][0]}}/", "http://{{domains[]}}:{{ports[http][0]}}/", "blocked"); + await window.test_driver.set_storage_access("https://{{domains[www1]}}:{{ports[https][0]}}/", "https://{{domains[]}}:{{ports[https][0]}}/", "blocked"); + // Block a third-party site on all first-party sites. + await window.test_driver.set_storage_access("http://{{domains[www2]}}:{{ports[http][0]}}/", "*", "blocked"); + await window.test_driver.set_storage_access("https://{{domains[www2]}}:{{ports[https][0]}}/", "*", "blocked"); + }, "Set up storage access rules"); + + RunTestsInIFrame("http://{{domains[]}}:{{ports[http][0]}}/storage-access-api/resources/set-cookie.py?name=hello0&value=world0&allowed=true&testcase=same-site"); + + RunTestsInIFrame("http://{{domains[www]}}:{{ports[http][0]}}/storage-access-api/resources/set-cookie.py?name=hello&value=world&allowed=true&testcase=third-party-allowed-on-first-party-site"); + + RunTestsInIFrame("http://{{domains[www1]}}:{{ports[http][0]}}/storage-access-api/resources/set-cookie.py?name=hello1&value=world1&allowed=false&testcase=third-party-blocked-on-first-party-site"); + + RunTestsInIFrame("http://{{domains[www2]}}:{{ports[http][0]}}/storage-access-api/resources/set-cookie.py?name=hello2&value=world2&allowed=false&testcase=third-party-blocked-all"); + </script> +</body> diff --git a/testing/web-platform/tests/storage/META.yml b/testing/web-platform/tests/storage/META.yml new file mode 100644 index 0000000000..2aad1fb513 --- /dev/null +++ b/testing/web-platform/tests/storage/META.yml @@ -0,0 +1,4 @@ +spec: https://storage.spec.whatwg.org/ +suggested_reviewers: + - annevk + - inexorabletash diff --git a/testing/web-platform/tests/storage/README.md b/testing/web-platform/tests/storage/README.md new file mode 100644 index 0000000000..8d0dca31b4 --- /dev/null +++ b/testing/web-platform/tests/storage/README.md @@ -0,0 +1,7 @@ +This directory contains the Storage test suite. + +To run the tests in this test suite within a browser, go to: <https://wpt.live/storage/>. + +The living standard is: <https://storage.spec.whatwg.org/> + +The API is at: <https://storage.spec.whatwg.org/#api> diff --git a/testing/web-platform/tests/storage/buckets/META.yml b/testing/web-platform/tests/storage/buckets/META.yml new file mode 100644 index 0000000000..4f215060f5 --- /dev/null +++ b/testing/web-platform/tests/storage/buckets/META.yml @@ -0,0 +1,5 @@ +spec: https://github.com/WICG/storage-buckets +suggested_reviewers: + - ayui + - jsbell + - pwnall diff --git a/testing/web-platform/tests/storage/estimate-indexeddb.https.any.js b/testing/web-platform/tests/storage/estimate-indexeddb.https.any.js new file mode 100644 index 0000000000..b0c6b944dd --- /dev/null +++ b/testing/web-platform/tests/storage/estimate-indexeddb.https.any.js @@ -0,0 +1,101 @@ +// META: title=StorageManager: estimate() for indexeddb + +function indexedDbOpenRequest(t, dbname, upgrade_func) { + return new Promise((resolve, reject) => { + const openRequest = indexedDB.open(dbname); + t.add_cleanup(() => { + indexedDbDeleteRequest(dbname); + }); + + openRequest.onerror = () => { + reject(openRequest.error); + }; + openRequest.onsuccess = () => { + resolve(openRequest.result); + }; + openRequest.onupgradeneeded = event => { + upgrade_func(openRequest.result); + }; + }); +} + +function indexedDbDeleteRequest(name) { + return new Promise((resolve, reject) => { + const deleteRequest = indexedDB.deleteDatabase(name); + deleteRequest.onerror = () => { + reject(deleteRequest.error); + }; + deleteRequest.onsuccess = () => { + resolve(); + }; + }); +} + +function transactionPromise(txn) { + return new Promise((resolve, reject) => { + txn.onabort = () => { + reject(txn.error); + }; + txn.oncomplete = () => { + resolve(); + }; + }); +} + +test(t => { + assert_true('estimate' in navigator.storage); + assert_equals(typeof navigator.storage.estimate, 'function'); + assert_true(navigator.storage.estimate() instanceof Promise); +}, 'estimate() method exists and returns a Promise'); + +promise_test(async t => { + const estimate = await navigator.storage.estimate(); + assert_equals(typeof estimate, 'object'); + assert_true('usage' in estimate); + assert_equals(typeof estimate.usage, 'number'); + assert_true('quota' in estimate); + assert_equals(typeof estimate.quota, 'number'); +}, 'estimate() resolves to dictionary with members'); + +promise_test(async t => { + const arraySize = 1e6; + const objectStoreName = "storageManager"; + const dbname = this.window ? window.location.pathname : + "estimate-worker.https.html"; + + await indexedDbDeleteRequest(dbname); + let estimate = await navigator.storage.estimate(); + + const usageBeforeCreate = estimate.usage; + const db = await indexedDbOpenRequest(t, dbname, (db_to_upgrade) => { + db_to_upgrade.createObjectStore(objectStoreName); + }); + + estimate = await navigator.storage.estimate(); + const usageAfterCreate = estimate.usage; + + assert_greater_than( + usageAfterCreate, usageBeforeCreate, + 'estimated usage should increase after object store is created'); + + const txn = db.transaction(objectStoreName, 'readwrite'); + const buffer = new ArrayBuffer(arraySize); + const view = new Uint8Array(buffer); + + for (let i = 0; i < arraySize; i++) { + view[i] = Math.floor(Math.random() * 255); + } + + const testBlob = new Blob([buffer], {type: "binary/random"}); + txn.objectStore(objectStoreName).add(testBlob, 1); + + await transactionPromise(txn); + + estimate = await navigator.storage.estimate(); + const usageAfterPut = estimate.usage; + assert_greater_than( + usageAfterPut, usageAfterCreate, + 'estimated usage should increase after large value is stored'); + + db.close(); +}, 'estimate() shows usage increase after large value is stored'); diff --git a/testing/web-platform/tests/storage/estimate-parallel.https.any.js b/testing/web-platform/tests/storage/estimate-parallel.https.any.js new file mode 100644 index 0000000000..090f004b85 --- /dev/null +++ b/testing/web-platform/tests/storage/estimate-parallel.https.any.js @@ -0,0 +1,13 @@ +// META: title=StorageManager: multiple estimate() calls in parallel + +promise_test(async t => { + let r1, r2; + await Promise.all([ + navigator.storage.estimate().then(r => { r1 = r; }), + navigator.storage.estimate().then(r => { r2 = r; }) + ]); + assert_true(('usage' in r1) && ('quota' in r1), + 'first response should have expected fields'); + assert_true(('usage' in r2) && ('quota' in r2), + 'second response should have expected fields'); +}, 'Multiple estimate() calls in parallel should complete'); diff --git a/testing/web-platform/tests/storage/estimate-usage-details-caches.https.tentative.any.js b/testing/web-platform/tests/storage/estimate-usage-details-caches.https.tentative.any.js new file mode 100644 index 0000000000..bf889f8418 --- /dev/null +++ b/testing/web-platform/tests/storage/estimate-usage-details-caches.https.tentative.any.js @@ -0,0 +1,20 @@ +// META: title=StorageManager: estimate() for caches + +promise_test(async t => { + let estimate = await navigator.storage.estimate(); + + const cachesUsageBeforeCreate = estimate.usageDetails.caches || 0; + + const cacheName = 'testCache'; + const cache = await caches.open(cacheName); + t.add_cleanup(() => caches.delete(cacheName)); + + await cache.put('/test.json', new Response('x'.repeat(1024*1024))); + + estimate = await navigator.storage.estimate(); + assert_true('caches' in estimate.usageDetails); + const cachesUsageAfterPut = estimate.usageDetails.caches; + assert_greater_than( + cachesUsageAfterPut, cachesUsageBeforeCreate, + 'estimated usage should increase after value is stored'); +}, 'estimate() shows usage increase after large value is stored'); diff --git a/testing/web-platform/tests/storage/estimate-usage-details-indexeddb.https.tentative.any.js b/testing/web-platform/tests/storage/estimate-usage-details-indexeddb.https.tentative.any.js new file mode 100644 index 0000000000..551cede9c6 --- /dev/null +++ b/testing/web-platform/tests/storage/estimate-usage-details-indexeddb.https.tentative.any.js @@ -0,0 +1,59 @@ +// META: title=StorageManager: estimate() usage details for indexeddb +// META: script=helpers.js +// META: script=../IndexedDB/resources/support-promises.js + +promise_test(async t => { + const estimate = await navigator.storage.estimate() + assert_equals(typeof estimate.usageDetails, 'object'); +}, 'estimate() resolves to dictionary with usageDetails member'); + +promise_test(async t => { + // We use 100KB here because db compaction usually happens every few MB + // 100KB is large enough to avoid a false positive (small amounts of metadata + // getting written for some random reason), and small enough to avoid + // compaction with a reasonably high probability. + const writeSize = 1024 * 100; + const objectStoreName = 'store'; + const dbname = self.location.pathname; + + await indexedDB.deleteDatabase(dbname); + let usageAfterWrite, usageBeforeWrite; + // TODO(crbug.com/906867): Refactor this test to better deal with db/log + // compaction flakiness + // The for loop here is to help make this test less flaky. The reason it is + // flaky is that database and/or log compaction could happen in the middle of + // this loop. The problem is that this test runs in a large batch of tests, + // and previous tests might have created a lot of garbage which could trigger + // compaction. Suppose the initial estimate shows 1MB usage before creating + // the db. Compaction could happen after this step and before we measure + // usage at the end, meaning the 1MB could be wiped to 0, an extra 1024 * 100 + // is put in, and the actual increase in usage does not reach our expected + // increase. Loop 10 times here to be safe (and reduce the number of bot + // builds that fail); all it takes is one iteration without compaction for + // this to pass. + for (let i = 0; i < 10; i++) { + const db = await createDB(dbname, objectStoreName, t); + let estimate = await navigator.storage.estimate(); + + // If usage is 0, usageDetails does not include the usage (undefined) + usageBeforeWrite = estimate.usageDetails.indexedDB || 0; + + const txn = db.transaction(objectStoreName, 'readwrite'); + const valueToStore = largeValue(writeSize, Math.random() * 255); + txn.objectStore(objectStoreName).add(valueToStore, 1); + + await transactionPromise(txn); + + estimate = await navigator.storage.estimate(); + usageAfterWrite = estimate.usageDetails.indexedDB; + db.close(); + + if (usageAfterWrite - usageBeforeWrite >= writeSize) { + break; + } + } + + assert_greater_than_equal(usageAfterWrite - usageBeforeWrite, + writeSize); +}, 'estimate() usage details reflects increase in indexedDB after large ' + + 'value is stored'); diff --git a/testing/web-platform/tests/storage/estimate-usage-details-service-workers.https.tentative.window.js b/testing/web-platform/tests/storage/estimate-usage-details-service-workers.https.tentative.window.js new file mode 100644 index 0000000000..cf3a2aa943 --- /dev/null +++ b/testing/web-platform/tests/storage/estimate-usage-details-service-workers.https.tentative.window.js @@ -0,0 +1,38 @@ +// META: title=StorageManager: estimate() for service worker registrations +const wait_for_active = worker => new Promise(resolve =>{ + if (worker.active) { resolve(worker.active); } + + const listen_for_active = worker => e => { + if (e.target.state === 'activated') { resolve(worker.active); } + } + + if (worker.waiting) { + worker.waiting + .addEventListener('statechange', listen_for_active(worker.waiting)); + } + if (worker.installing) { + worker.installing + .addEventListener('statechange', listen_for_active(worker.installing)); + } +}); + +promise_test(async t => { + let estimate = await navigator.storage.estimate(); + const usageBeforeCreate = estimate.usageDetails.serviceWorkerRegistrations || + 0; + // Note: helpers.js is an arbitrary file; it could be any file that + // exists, but this test does not depend on the contents of said file. + const serviceWorkerRegistration = await + navigator.serviceWorker.register('./helpers.js'); + + t.add_cleanup(() => serviceWorkerRegistration.unregister()); + await wait_for_active(serviceWorkerRegistration); + + estimate = await navigator.storage.estimate(); + assert_true('serviceWorkerRegistrations' in estimate.usageDetails); + + const usageAfterCreate = estimate.usageDetails.serviceWorkerRegistrations; + assert_greater_than( + usageAfterCreate, usageBeforeCreate, + 'estimated usage should increase after service worker is registered'); +}, 'estimate() shows usage increase after large value is stored'); diff --git a/testing/web-platform/tests/storage/estimate-usage-details.https.tentative.any.js b/testing/web-platform/tests/storage/estimate-usage-details.https.tentative.any.js new file mode 100644 index 0000000000..2a1cea5fb8 --- /dev/null +++ b/testing/web-platform/tests/storage/estimate-usage-details.https.tentative.any.js @@ -0,0 +1,12 @@ +// META: title=StorageManager: estimate() should have usage details + +promise_test(async t => { + const estimate = await navigator.storage.estimate(); + assert_equals(typeof estimate, 'object'); + assert_true('usage' in estimate); + assert_equals(typeof estimate.usage, 'number'); + assert_true('quota' in estimate); + assert_equals(typeof estimate.quota, 'number'); + assert_true('usageDetails' in estimate); + assert_equals(typeof estimate.usageDetails, 'object'); +}, 'estimate() resolves to dictionary with members, including usageDetails'); diff --git a/testing/web-platform/tests/storage/helpers.js b/testing/web-platform/tests/storage/helpers.js new file mode 100644 index 0000000000..b524c1b82c --- /dev/null +++ b/testing/web-platform/tests/storage/helpers.js @@ -0,0 +1,46 @@ +/** + * @description - Function will create a database with the supplied name + * and also create an object store with the specified name. + * If a db with the name dbName exists, this will raze the + * existing DB beforehand. + * @param {string} dbName + * @param {string} objectStoreName + * @param {testCase} t + * @returns {Promise} - A promise that resolves to an indexedDB open request + */ +function createDB(dbName, objectStoreName, t) { + return new Promise((resolve, reject) => { + const openRequest = indexedDB.open(dbName); + t.add_cleanup(() => { + indexedDB.deleteDatabase(dbName); + }); + openRequest.onerror = () => { + reject(openRequest.error); + }; + openRequest.onsuccess = () => { + resolve(openRequest.result); + }; + openRequest.onupgradeneeded = (event) => { + openRequest.result.createObjectStore(objectStoreName); + }; + }); +} + +/** + * @description - This function will wrap an IDBTransaction in a promise, + * resolving in the oncomplete() method and rejecting with the + * transaction error in the onabort() case. + * @param {IDBTransaction} transaction - The transaction to wrap in a promise. + * @returns {Promise} - A promise that resolves when the transaction is either + * aborted or completed. + */ +function transactionPromise(transaction) { + return new Promise((resolve, reject) => { + transaction.onabort = () => { + reject(transaction.error); + }; + transaction.oncomplete = () => { + resolve(); + }; + }); +} diff --git a/testing/web-platform/tests/storage/idlharness.https.any.js b/testing/web-platform/tests/storage/idlharness.https.any.js new file mode 100644 index 0000000000..773fac4e4a --- /dev/null +++ b/testing/web-platform/tests/storage/idlharness.https.any.js @@ -0,0 +1,18 @@ +// META: script=/resources/WebIDLParser.js +// META: script=/resources/idlharness.js +// META: timeout=long + +'use strict'; + +idl_test( + ['storage'], + ['html'], + idl_array => { + idl_array.add_objects({ StorageManager: ['navigator.storage'] }); + if (self.Window) { + idl_array.add_objects({ Navigator: ['navigator'] }); + } else { + idl_array.add_objects({ WorkerNavigator: ['navigator'] }); + } + } +); diff --git a/testing/web-platform/tests/storage/opaque-origin.https.window.js b/testing/web-platform/tests/storage/opaque-origin.https.window.js new file mode 100644 index 0000000000..cc1d31fdf2 --- /dev/null +++ b/testing/web-platform/tests/storage/opaque-origin.https.window.js @@ -0,0 +1,80 @@ +// META: title=StorageManager API and opaque origins + +function load_iframe(src, sandbox) { + return new Promise(resolve => { + const iframe = document.createElement('iframe'); + iframe.onload = () => { resolve(iframe); }; + if (sandbox) + iframe.sandbox = sandbox; + iframe.srcdoc = src; + iframe.style.display = 'none'; + document.documentElement.appendChild(iframe); + }); +} + +function wait_for_message(iframe) { + return new Promise(resolve => { + self.addEventListener('message', function listener(e) { + if (e.source === iframe.contentWindow && "result" in e.data) { + resolve(e.data); + self.removeEventListener('message', listener); + } + }); + }); +} + +function make_script(snippet) { + return '<script src="/resources/testharness.js"></script>' + + '<script>' + + ' window.onmessage = () => {' + + ' try {' + + ' (' + snippet + ')' + + ' .then(' + + ' result => {' + + ' window.parent.postMessage({result: "no rejection"}, "*");' + + ' }, ' + + ' error => {' + + ' try {' + + ' assert_throws_js(TypeError, () => { throw error; });' + + ' window.parent.postMessage({result: "correct rejection"}, "*");' + + ' } catch (e) {' + + ' window.parent.postMessage({result: "incorrect rejection"}, "*");' + + ' }' + + ' });' + + ' } catch (ex) {' + + // Report if not implemented/exposed, rather than time out. + ' window.parent.postMessage({result: "API access threw"}, "*");' + + ' }' + + ' };' + + '<\/script>'; +} + +['navigator.storage.persisted()', + 'navigator.storage.estimate()', + // persist() can prompt, so make sure we test that last + 'navigator.storage.persist()', +].forEach(snippet => { + promise_test(t => { + return load_iframe(make_script(snippet)) + .then(iframe => { + iframe.contentWindow.postMessage({}, '*'); + return wait_for_message(iframe); + }) + .then(message => { + assert_equals(message.result, 'no rejection', + `${snippet} should not reject`); + }); + }, `${snippet} in non-sandboxed iframe should not reject`); + + promise_test(t => { + return load_iframe(make_script(snippet), 'allow-scripts') + .then(iframe => { + iframe.contentWindow.postMessage({}, '*'); + return wait_for_message(iframe); + }) + .then(message => { + assert_equals(message.result, 'correct rejection', + `${snippet} should reject with TypeError`); + }); + }, `${snippet} in sandboxed iframe should reject with TypeError`); +}); diff --git a/testing/web-platform/tests/storage/partitioned-estimate-usage-details-caches.tentative.https.sub.html b/testing/web-platform/tests/storage/partitioned-estimate-usage-details-caches.tentative.https.sub.html new file mode 100644 index 0000000000..2eac826eb1 --- /dev/null +++ b/testing/web-platform/tests/storage/partitioned-estimate-usage-details-caches.tentative.https.sub.html @@ -0,0 +1,69 @@ +<!DOCTYPE html> +<meta name=help href="https://privacycg.github.io/storage-partitioning/"> +<title>Partitioned estimate() usage details for caches test</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> + +<body> + <script> + const usageDetails = async () => + (await navigator.storage.estimate()).usageDetails.caches || 0; + + const createSomeUsage = async () => { + const cache_name = token(); + const cache_url = `/foo-${cache_name}`; + const cache = await caches.open(cache_name); + await cache.put(cache_url, new Response('x'.repeat(128))); + return [cache, cache_url]; + } + + const testPath = () => location.pathname.split("/").slice(0, -1).join("/"); + + let alt_origin = "https://{{hosts[alt][]}}:{{ports[https][0]}}"; + let details = {}; + + const iframe = document.createElement("iframe"); + iframe.src = `https://{{host}}:{{ports[https][0]}}${testPath()}` + + `/resources/partitioned-estimate-usage-details-caches-helper-frame.html` + document.body.appendChild(iframe); + + async_test(test => { + if (location.origin === alt_origin) + return; + + let cache, cache_url; + test.step(async () => { + details.init = await usageDetails(); + [cache, cache_url] = await createSomeUsage(test); + details.after = await usageDetails(); + assert_greater_than(details.after, details.init); + + iframe.contentWindow.postMessage("get-details", iframe.origin); + }); + + window.addEventListener("message", test.step_func(event => { + if (event.data.source === "same-site") { + details.same_site = event.data; + + const cross_site_window = window + .open(`${alt_origin}${location.pathname}`, "", "noopener=false"); + test.add_cleanup(() => cross_site_window.close()); + } + if (event.data.source === "cross-site") { + details.cross_site = event.data; + + // Some cleanup + test.step(async () => await cache.delete(cache_url)); + + test.step(() => { + assert_true(details.cross_site.init == 0, "Usage should be 0."); + assert_equals(details.same_site.init, details.after); + }); + + test.done(); + } + })); + }, "Partitioned estimate() usage details for caches test."); + </script> +</body> diff --git a/testing/web-platform/tests/storage/partitioned-estimate-usage-details-indexeddb.tentative.https.sub.html b/testing/web-platform/tests/storage/partitioned-estimate-usage-details-indexeddb.tentative.https.sub.html new file mode 100644 index 0000000000..a58d542654 --- /dev/null +++ b/testing/web-platform/tests/storage/partitioned-estimate-usage-details-indexeddb.tentative.https.sub.html @@ -0,0 +1,82 @@ +<!DOCTYPE html> +<meta name=help href="https://privacycg.github.io/storage-partitioning/"> +<title>Partitioned estimate() usage details for indexeddb test</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="./helpers.js"></script> +<script src="../IndexedDB/resources/support-promises.js"></script> + +<body> + <script> + const usageDetails = async () => + (await navigator.storage.estimate()).usageDetails.indexedDB || 0; + + const createSomeUsage = async (test) => { + // We use 100KB here because db compaction usually happens every few MB + // 100KB is large enough to avoid a false positive (small amounts of + // metadata getting written for some random reason), and small enough to + // avoid compaction with a reasonably high probability. + const write_size = 1024 * 100; + const object_store_name = token(); + const db_name = self.location.pathname; + + await indexedDB.deleteDatabase(db_name); + const db = await createDB(db_name, object_store_name, test); + const transaction = db.transaction(object_store_name, 'readwrite'); + const value_to_store = largeValue(write_size, Math.random() * 255); + transaction.objectStore(object_store_name).add(value_to_store, 1); + + await transactionPromise(transaction); + return db; + } + + const testPath = () => location.pathname.split("/").slice(0, -1).join("/"); + + let alt_origin = "https://{{hosts[alt][]}}:{{ports[https][0]}}"; + let details = {}; + + const iframe = document.createElement("iframe"); + iframe.src = `https://{{host}}:{{ports[https][0]}}${testPath()}/resources` + + `/partitioned-estimate-usage-details-indexeddb-helper-frame.html`; + document.body.appendChild(iframe); + + async_test(test => { + if (location.origin === alt_origin) + return; + + let db; + test.step(async () => { + details.init = await usageDetails(); + db = await createSomeUsage(test); + details.after = await usageDetails(); + assert_greater_than(details.after, details.init); + + iframe.contentWindow.postMessage("get-details", iframe.origin); + }); + + window.addEventListener("message", test.step_func(event => { + if (event.data.source === "same-site") { + details.same_site = event.data; + + const cross_site_window = window + .open(`${alt_origin}${location.pathname}`, "", "noopener=false"); + test.add_cleanup(() => cross_site_window.close()); + } + if (event.data.source === "cross-site") { + details.cross_site = event.data; + + // Some cleanup + test.step(async () => await db.close()); + + test.step(() => { + assert_true(details.cross_site.init == 0, "Usage should be 0."); + assert_equals(details.same_site.init, details.after); + }); + + test.done(); + } + })); + }, "Partitioned estimate() usage details for indexeddb test."); + </script> +</body> diff --git a/testing/web-platform/tests/storage/partitioned-estimate-usage-details-service-workers.tentative.https.sub.html b/testing/web-platform/tests/storage/partitioned-estimate-usage-details-service-workers.tentative.https.sub.html new file mode 100644 index 0000000000..9f283e300a --- /dev/null +++ b/testing/web-platform/tests/storage/partitioned-estimate-usage-details-service-workers.tentative.https.sub.html @@ -0,0 +1,86 @@ +<!DOCTYPE html> +<meta name=help href="https://privacycg.github.io/storage-partitioning/"> +<title>Partitioned estimate() usage details for service workers test</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<body> + <script> + const usageDetails = async () => { + return (await navigator.storage.estimate()) + .usageDetails.serviceWorkerRegistrations || 0; + } + + const createSomeUsage = async () => { + const wait_for_active = worker => new Promise(resolve => { + if (worker.active) { resolve(worker.active); } + + const listen_for_active = worker => e => { + if (e.target.state === 'activated') { resolve(worker.active); } + } + + if (worker.waiting) { + worker.waiting + .addEventListener('statechange', listen_for_active(worker.waiting)); + } + if (worker.installing) { + worker.installing.addEventListener('statechange', + listen_for_active(worker.installing)); + } + }); + + const service_worker_registration = + await navigator.serviceWorker.register('resources/worker.js'); + await wait_for_active(service_worker_registration); + return service_worker_registration; + } + + const testPath = () => location.pathname.split("/").slice(0, -1).join("/"); + + let alt_origin = "https://{{hosts[alt][]}}:{{ports[https][0]}}"; + let details = {}; + + const iframe = document.createElement("iframe"); + iframe.src = `https://{{host}}:{{ports[https][0]}}${testPath()}/resources`+ + `/partitioned-estimate-usage-details-service-workers-helper-frame.html` + document.body.appendChild(iframe); + + async_test(test => { + if (location.origin === alt_origin) + return; + + let service_worker_registration; + test.step(async () => { + details.init = await usageDetails(); + service_worker_registration = await createSomeUsage(); + details.after = await usageDetails(); + assert_greater_than(details.after, details.init); + + iframe.contentWindow.postMessage("get-details", iframe.origin); + }); + + window.addEventListener("message", test.step_func(event => { + if (event.data.source === "same-site") { + details.same_site = event.data; + + const cross_site_window = window + .open(`${alt_origin}${location.pathname}`, "", "noopener=false"); + test.add_cleanup(() => cross_site_window.close()); + } + if (event.data.source === "cross-site") { + details.cross_site = event.data; + + // More cleanup. + test.step(() => service_worker_registration.unregister()); + + test.step(() => { + assert_true(details.cross_site.init == 0, "Usage should be 0."); + assert_equals(details.same_site.init, details.after); + }); + + test.done(); + } + })); + }, "Partitioned estimate() usage details for service workers test."); + </script> +</body> diff --git a/testing/web-platform/tests/storage/permission-query.https.any.js b/testing/web-platform/tests/storage/permission-query.https.any.js new file mode 100644 index 0000000000..9984bdab79 --- /dev/null +++ b/testing/web-platform/tests/storage/permission-query.https.any.js @@ -0,0 +1,10 @@ +// META: title=The Permission API registration for "persistent-storage" + +promise_test(async t => { + const status = + await navigator.permissions.query({name: 'persistent-storage'}); + assert_equals(status.constructor, PermissionStatus, + 'query() result should resolve to a PermissionStatus'); + assert_true(['granted','denied', 'prompt'].includes(status.state), + 'state should be a PermissionState'); +}, 'The "persistent-storage" permission is recognized'); diff --git a/testing/web-platform/tests/storage/persist-permission-manual.https.html b/testing/web-platform/tests/storage/persist-permission-manual.https.html new file mode 100644 index 0000000000..aa49900d69 --- /dev/null +++ b/testing/web-platform/tests/storage/persist-permission-manual.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>StorageManager: permission state is granted</title> + <p>Clear all persistent storage permissions before running this test.</p> + <p>Test passes if there is a permission prompt and click allow store persistent data</p> + <meta name="help" href="https://storage.spec.whatwg.org/#dom-storagemanager-persist"> + <meta name="author" title="Mozilla" href="https://www.mozilla.org"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <script> + promise_test(function(t) { + return navigator.storage.persist() + .then(function(result) { + assert_true(result); + return navigator.storage.persisted(); + }) + .then(function(result) { + assert_true(result); + }) + }, 'Expect permission state is granted after calling persist()'); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/storage/persisted.https.any.js b/testing/web-platform/tests/storage/persisted.https.any.js new file mode 100644 index 0000000000..57e15f0e81 --- /dev/null +++ b/testing/web-platform/tests/storage/persisted.https.any.js @@ -0,0 +1,14 @@ +// META: title=StorageManager: persisted() + +test(function(t) { + assert_true('persisted' in navigator.storage); + assert_equals(typeof navigator.storage.persisted, 'function'); + assert_true(navigator.storage.persisted() instanceof Promise); +}, 'persisted() method exists and returns a Promise'); + +promise_test(function(t) { + return navigator.storage.persisted().then(function(result) { + assert_equals(typeof result, 'boolean'); + assert_equals(result, false); + }); +}, 'persisted() returns a promise and resolves as boolean with false'); diff --git a/testing/web-platform/tests/storage/quotachange-in-detached-iframe.tentative.https.html b/testing/web-platform/tests/storage/quotachange-in-detached-iframe.tentative.https.html new file mode 100644 index 0000000000..123af50e3c --- /dev/null +++ b/testing/web-platform/tests/storage/quotachange-in-detached-iframe.tentative.https.html @@ -0,0 +1,21 @@ +<!doctype html> +<meta charset="utf-8"> +<title>quotachange event on DOMWindow of detached iframe</title> +<link rel="author" href="jarrydg@chromium.org" title="Jarryd"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="iframe"></iframe> +<script> +'use strict'; + +test(t => { + const iframe = document.getElementById('iframe'); + const frameWindow = iframe.contentWindow; + const storageManager = frameWindow.navigator.storage; + + iframe.parentNode.removeChild(iframe); + const emptyListener = () => {}; + storageManager.addEventListener('quotachange', emptyListener); + storageManager.removeEventListener('quotachange', emptyListener); +}, "Add quotachange listener on detached iframe."); +</script> diff --git a/testing/web-platform/tests/storage/resources/partitioned-estimate-usage-details-caches-helper-frame.html b/testing/web-platform/tests/storage/resources/partitioned-estimate-usage-details-caches-helper-frame.html new file mode 100644 index 0000000000..13786d7cea --- /dev/null +++ b/testing/web-platform/tests/storage/resources/partitioned-estimate-usage-details-caches-helper-frame.html @@ -0,0 +1,28 @@ +<!-- ToDo: Change the virtual suite expected file content once the necessary + partitioning implementation is completed --> +<!DOCTYPE html> +<meta name=help href="https://privacycg.github.io/storage-partitioning/"> +<title>Helper frame</title> + +<script> + const usageDetails = async () => + (await navigator.storage.estimate()).usageDetails.caches || 0; + + let details = {}; + + window.addEventListener("message", async event => { + if (event.data === "get-details") { + details.source = "same-site"; + details.init = await usageDetails(); + event.source.postMessage(details, event.source.origin); + } + }); + + window.addEventListener("load", async () => { + if (parent.opener) { + details.source = "cross-site"; + details.init = await usageDetails(); + parent.opener.postMessage(details, parent.opener.origin); + } + }); +</script> diff --git a/testing/web-platform/tests/storage/resources/partitioned-estimate-usage-details-indexeddb-helper-frame.html b/testing/web-platform/tests/storage/resources/partitioned-estimate-usage-details-indexeddb-helper-frame.html new file mode 100644 index 0000000000..80f9cca27d --- /dev/null +++ b/testing/web-platform/tests/storage/resources/partitioned-estimate-usage-details-indexeddb-helper-frame.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<meta name=help href="https://privacycg.github.io/storage-partitioning/"> +<title>Helper frame</title> + +<script> + const usageDetails = async () => + (await navigator.storage.estimate()).usageDetails.indexedDB || 0; + + let details = {}; + + window.addEventListener("message", async event => { + if (event.data === "get-details") { + details.source = "same-site"; + details.init = await usageDetails(); + event.source.postMessage(details, event.source.origin); + } + }); + + window.addEventListener("load", async () => { + if (parent.opener) { + details.source = "cross-site"; + details.init = await usageDetails(); + parent.opener.postMessage(details, parent.opener.origin); + } + }); +</script> diff --git a/testing/web-platform/tests/storage/resources/partitioned-estimate-usage-details-service-workers-helper-frame.html b/testing/web-platform/tests/storage/resources/partitioned-estimate-usage-details-service-workers-helper-frame.html new file mode 100644 index 0000000000..29d91a95d7 --- /dev/null +++ b/testing/web-platform/tests/storage/resources/partitioned-estimate-usage-details-service-workers-helper-frame.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta name=help href="https://privacycg.github.io/storage-partitioning/"> +<title>Helper frame</title> + +<script> + const usageDetails = async () => { + return (await navigator.storage.estimate()) + .usageDetails.serviceWorkerRegistrations || 0 + } + + let details = {}; + + window.addEventListener("message", async event => { + if (event.data === "get-details") { + details.source = "same-site"; + details.init = await usageDetails(); + event.source.postMessage(details, event.source.origin); + } + }); + + window.addEventListener("load", async () => { + if (parent.opener) { + details.source = "cross-site"; + details.init = await usageDetails(); + parent.opener.postMessage(details, parent.opener.origin); + } + }); +</script> diff --git a/testing/web-platform/tests/storage/resources/worker.js b/testing/web-platform/tests/storage/resources/worker.js new file mode 100644 index 0000000000..9271c769b2 --- /dev/null +++ b/testing/web-platform/tests/storage/resources/worker.js @@ -0,0 +1,3 @@ +// Dummy service worker to observe some weight when querying the storage usage +// details from of the service worker from estimate(). +globalThis.oninstall = e => {}; diff --git a/testing/web-platform/tests/storage/storagemanager-estimate.https.any.js b/testing/web-platform/tests/storage/storagemanager-estimate.https.any.js new file mode 100644 index 0000000000..c2f5c569dc --- /dev/null +++ b/testing/web-platform/tests/storage/storagemanager-estimate.https.any.js @@ -0,0 +1,60 @@ +// META: title=StorageManager: estimate() + +test(function(t) { + assert_true(navigator.storage.estimate() instanceof Promise); +}, 'estimate() method returns a Promise'); + +promise_test(function(t) { + return navigator.storage.estimate().then(function(result) { + assert_equals(typeof result, 'object'); + assert_true('usage' in result); + assert_equals(typeof result.usage, 'number'); + assert_true('quota' in result); + assert_equals(typeof result.quota, 'number'); + }); +}, 'estimate() resolves to dictionary with members'); + +promise_test(function(t) { + const large_value = new Uint8Array(1e6); + const dbname = `db-${location}-${t.name}`; + let db, before, after; + + indexedDB.deleteDatabase(dbname); + return new Promise((resolve, reject) => { + const open = indexedDB.open(dbname); + open.onerror = () => { reject(open.error); }; + open.onupgradeneeded = () => { + const connection = open.result; + connection.createObjectStore('store'); + }; + open.onsuccess = () => { + const connection = open.result; + t.add_cleanup(() => { + connection.close(); + indexedDB.deleteDatabase(dbname); + }); + resolve(connection); + }; + }) + .then(connection => { + db = connection; + return navigator.storage.estimate(); + }) + .then(estimate => { + before = estimate.usage; + return new Promise((resolve, reject) => { + const tx = db.transaction('store', 'readwrite'); + tx.objectStore('store').put(large_value, 'key'); + tx.onabort = () => { reject(tx.error); }; + tx.oncomplete = () => { resolve(); }; + }); + }) + .then(() => { + return navigator.storage.estimate(); + }) + .then(estimate => { + after = estimate.usage; + assert_greater_than(after, before, + 'estimated usage should increase'); + }); +}, 'estimate() shows usage increase after 1MB IndexedDB record is stored'); diff --git a/testing/web-platform/tests/storage/storagemanager-persist.https.window.js b/testing/web-platform/tests/storage/storagemanager-persist.https.window.js new file mode 100644 index 0000000000..13e17a16e1 --- /dev/null +++ b/testing/web-platform/tests/storage/storagemanager-persist.https.window.js @@ -0,0 +1,10 @@ +// META: title=StorageManager: persist() + +promise_test(function() { + var promise = navigator.storage.persist(); + assert_true(promise instanceof Promise, + 'navigator.storage.persist() returned a Promise.'); + return promise.then(function(result) { + assert_equals(typeof result, 'boolean', result + ' should be boolean'); + }); +}, 'navigator.storage.persist() returns a promise that resolves.'); diff --git a/testing/web-platform/tests/storage/storagemanager-persist.https.worker.js b/testing/web-platform/tests/storage/storagemanager-persist.https.worker.js new file mode 100644 index 0000000000..fcf8175f70 --- /dev/null +++ b/testing/web-platform/tests/storage/storagemanager-persist.https.worker.js @@ -0,0 +1,8 @@ +// META: title=StorageManager: persist() (worker) +importScripts("/resources/testharness.js"); + +test(function() { + assert_false('persist' in navigator.storage); +}, 'navigator.storage.persist should not exist in workers'); + +done(); diff --git a/testing/web-platform/tests/storage/storagemanager-persisted.https.any.js b/testing/web-platform/tests/storage/storagemanager-persisted.https.any.js new file mode 100644 index 0000000000..7099940669 --- /dev/null +++ b/testing/web-platform/tests/storage/storagemanager-persisted.https.any.js @@ -0,0 +1,10 @@ +// META: title=StorageManager: persisted() + +promise_test(function() { + var promise = navigator.storage.persisted(); + assert_true(promise instanceof Promise, + 'navigator.storage.persisted() returned a Promise.'); + return promise.then(function (result) { + assert_equals(typeof result, 'boolean', result + ' should be boolean'); + }); +}, 'navigator.storage.persisted() returns a promise that resolves.'); |