summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/storage
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--testing/web-platform/tests/storage-access-api/META.yml6
-rw-r--r--testing/web-platform/tests/storage-access-api/hasStorageAccess-insecure.sub.window.js42
-rw-r--r--testing/web-platform/tests/storage-access-api/hasStorageAccess.sub.https.window.js42
-rw-r--r--testing/web-platform/tests/storage-access-api/helpers.js63
-rw-r--r--testing/web-platform/tests/storage-access-api/idlharness.window.js14
-rw-r--r--testing/web-platform/tests/storage-access-api/requestStorageAccess-cross-origin-iframe.sub.https.window.js18
-rw-r--r--testing/web-platform/tests/storage-access-api/requestStorageAccess-insecure.sub.window.js83
-rw-r--r--testing/web-platform/tests/storage-access-api/requestStorageAccess-nested-cross-origin-iframe.sub.https.window.js19
-rw-r--r--testing/web-platform/tests/storage-access-api/requestStorageAccess-nested-same-origin-iframe.sub.https.window.js8
-rw-r--r--testing/web-platform/tests/storage-access-api/requestStorageAccess-non-fully-active.sub.https.window.js18
-rw-r--r--testing/web-platform/tests/storage-access-api/requestStorageAccess-same-origin-iframe.sub.https.window.js7
-rw-r--r--testing/web-platform/tests/storage-access-api/requestStorageAccess.sub.https.window.js67
-rw-r--r--testing/web-platform/tests/storage-access-api/resources/hasStorageAccess-iframe.html8
-rw-r--r--testing/web-platform/tests/storage-access-api/resources/hasStorageAccess-iframe.https.html8
-rw-r--r--testing/web-platform/tests/storage-access-api/resources/requestStorageAccess-iframe.html10
-rw-r--r--testing/web-platform/tests/storage-access-api/resources/requestStorageAccess-iframe.https.html10
-rw-r--r--testing/web-platform/tests/storage-access-api/resources/set-cookie.py27
-rw-r--r--testing/web-platform/tests/storage-access-api/sandboxAttribute.window.js7
-rw-r--r--testing/web-platform/tests/storage-access-api/storageAccess.testdriver.sub.html34
-rw-r--r--testing/web-platform/tests/storage/META.yml4
-rw-r--r--testing/web-platform/tests/storage/README.md7
-rw-r--r--testing/web-platform/tests/storage/buckets/META.yml5
-rw-r--r--testing/web-platform/tests/storage/estimate-indexeddb.https.any.js101
-rw-r--r--testing/web-platform/tests/storage/estimate-parallel.https.any.js13
-rw-r--r--testing/web-platform/tests/storage/estimate-usage-details-caches.https.tentative.any.js20
-rw-r--r--testing/web-platform/tests/storage/estimate-usage-details-indexeddb.https.tentative.any.js59
-rw-r--r--testing/web-platform/tests/storage/estimate-usage-details-service-workers.https.tentative.window.js38
-rw-r--r--testing/web-platform/tests/storage/estimate-usage-details.https.tentative.any.js12
-rw-r--r--testing/web-platform/tests/storage/helpers.js46
-rw-r--r--testing/web-platform/tests/storage/idlharness.https.any.js18
-rw-r--r--testing/web-platform/tests/storage/opaque-origin.https.window.js80
-rw-r--r--testing/web-platform/tests/storage/partitioned-estimate-usage-details-caches.tentative.https.sub.html69
-rw-r--r--testing/web-platform/tests/storage/partitioned-estimate-usage-details-indexeddb.tentative.https.sub.html82
-rw-r--r--testing/web-platform/tests/storage/partitioned-estimate-usage-details-service-workers.tentative.https.sub.html86
-rw-r--r--testing/web-platform/tests/storage/permission-query.https.any.js10
-rw-r--r--testing/web-platform/tests/storage/persist-permission-manual.https.html27
-rw-r--r--testing/web-platform/tests/storage/persisted.https.any.js14
-rw-r--r--testing/web-platform/tests/storage/quotachange-in-detached-iframe.tentative.https.html21
-rw-r--r--testing/web-platform/tests/storage/resources/partitioned-estimate-usage-details-caches-helper-frame.html28
-rw-r--r--testing/web-platform/tests/storage/resources/partitioned-estimate-usage-details-indexeddb-helper-frame.html26
-rw-r--r--testing/web-platform/tests/storage/resources/partitioned-estimate-usage-details-service-workers-helper-frame.html28
-rw-r--r--testing/web-platform/tests/storage/resources/worker.js3
-rw-r--r--testing/web-platform/tests/storage/storagemanager-estimate.https.any.js60
-rw-r--r--testing/web-platform/tests/storage/storagemanager-persist.https.window.js10
-rw-r--r--testing/web-platform/tests/storage/storagemanager-persist.https.worker.js8
-rw-r--r--testing/web-platform/tests/storage/storagemanager-persisted.https.any.js10
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.');