diff --git a/testing/web-platform/tests/storage/buckets/bucket-quota-indexeddb.tentative.https.any.js b/testing/web-platform/tests/storage/buckets/bucket-quota-indexeddb.tentative.https.any.js index e03aaa802d08..ee9202777e7e 100644 --- a/testing/web-platform/tests/storage/buckets/bucket-quota-indexeddb.tentative.https.any.js +++ b/testing/web-platform/tests/storage/buckets/bucket-quota-indexeddb.tentative.https.any.js @@ -2,6 +2,7 @@ // META: script=/storage/buckets/resources/util.js promise_test(async t => { + prepareForBucketTest(t); const arraySize = 1e6; const objectStoreName = "storageManager"; const dbname = diff --git a/testing/web-platform/tests/storage/buckets/bucket_names.tentative.https.any.js b/testing/web-platform/tests/storage/buckets/bucket_names.tentative.https.any.js new file mode 100644 index 000000000000..e0f122dc47d8 --- /dev/null +++ b/testing/web-platform/tests/storage/buckets/bucket_names.tentative.https.any.js @@ -0,0 +1,93 @@ +// META: title=Buckets API: Basic tests for bucket names. +// META: script=resources/util.js +// META: global=window,worker + +const kGoodBucketNameTests = [ + ['abcdefghijklmnopqrstuvwxyz0123456789-_', 'with allowed characters'], + ['2021-01-01', 'with `-` in the middle'], + ['2021_01_01', 'with `_` in the middle'], + ['2021_01_01_', 'ending with `_`'], + ['2021-01-01-', 'ending with `-`'], +]; + +const kBadBucketNameTests = [ + ['_bucket', 'start with `_`'], + ['-bucket', 'start with `-`'], + ['bucket name', 'have a space'], + ['bUcKet123', 'are not all lower case'], + ['bucket♦♥♠♣', 'are not in ASCII'], + ['2021/01/01', 'include an invalid special character'], + [' ', 'have no characters'], + ['', 'are an empty string'], + ['mjnkhtwsiyjsrxvrzzqafldfvomqopdjfiuxqelfkllcugrhvvblkvmiqlguhhqepoggyu', + 'exceed 64 chars'] +]; + +// Test valid bucket names on open(). +kGoodBucketNameTests.forEach(test_data => { + const bucket_name = test_data[0]; + const test_description = test_data[1]; + + promise_test(async testCase => { + await prepareForBucketTest(testCase); + const bucket = await navigator.storageBuckets.open(bucket_name); + assert_equals(bucket.name, bucket_name); + + const buckets = await navigator.storageBuckets.keys(); + assert_array_equals(buckets, [bucket_name]); + }, `open() allows bucket names ${test_description}`); +}); + +// Test invalid bucket names on open(). +kBadBucketNameTests.forEach(test_data => { + const bucket_name = test_data[0]; + const test_description = test_data[1]; + + promise_test(async testCase => { + await prepareForBucketTest(testCase); + return promise_rejects_js( + testCase, TypeError, + navigator.storageBuckets.open(bucket_name)); + }, `open() throws an error if bucket names ${test_description}`); +}); + +// Test valid bucket names on delete(). +kGoodBucketNameTests.forEach(test_data => { + const bucket_name = test_data[0]; + const test_description = test_data[1]; + + promise_test(async testCase => { + await prepareForBucketTest(testCase); + await navigator.storageBuckets.open(bucket_name); + let buckets = await navigator.storageBuckets.keys(); + assert_equals(buckets.length, 1); + + await navigator.storageBuckets.delete(bucket_name); + + buckets = await navigator.storageBuckets.keys(); + assert_equals(buckets.length, 0); + }, `delete() allows bucket names ${test_description}`); +}); + +// Test invalid bucket names on delete(). +kBadBucketNameTests.forEach(test_data => { + const bucket_name = test_data[0]; + const test_description = test_data[1]; + + promise_test(async testCase => { + await prepareForBucketTest(testCase); + return promise_rejects_js( + testCase, TypeError, + navigator.storageBuckets.delete(bucket_name)); + }, `delete() throws an error if bucket names ${test_description}`); +}); + +promise_test(async testCase => { + await prepareForBucketTest(testCase); + + await navigator.storageBuckets.open('bucket_name'); + await navigator.storageBuckets.open('bucket_name'); + + const buckets = await navigator.storageBuckets.keys(); + assert_array_equals(buckets, ['bucket_name']); +}, 'open() does not store duplicate bucket names'); diff --git a/testing/web-platform/tests/storage/buckets/buckets_basic.tentative.https.any.js b/testing/web-platform/tests/storage/buckets/buckets_basic.tentative.https.any.js new file mode 100644 index 000000000000..20ff227bac1e --- /dev/null +++ b/testing/web-platform/tests/storage/buckets/buckets_basic.tentative.https.any.js @@ -0,0 +1,50 @@ +// META: title=Buckets API: Basic tests for open(), keys(), delete(). +// META: script=resources/util.js +// META: global=window,worker + +'use strict'; + +// This test is for initial IDL version optimized for debugging. +// Split and add extensive testing once implementation for the endpoints are +// added and method definitions are more defined. +promise_test(async testCase => { + await prepareForBucketTest(testCase); + + await navigator.storageBuckets.open('bucket_name3'); + await navigator.storageBuckets.open('bucket_name1'); + await navigator.storageBuckets.open('bucket_name2'); + + const buckets = await navigator.storageBuckets.keys(); + assert_array_equals( + buckets, ['bucket_name1', 'bucket_name2', 'bucket_name3']); +}, 'keys() lists all stored bucket names alphabetically'); + +promise_test(async testCase => { + await prepareForBucketTest(testCase); + + await navigator.storageBuckets.open('bucket_name1'); + await navigator.storageBuckets.open('bucket_name2'); + + let buckets = await navigator.storageBuckets.keys(); + assert_array_equals(buckets, ['bucket_name1', 'bucket_name2']); + + await navigator.storageBuckets.delete('bucket_name1'); + + buckets = await navigator.storageBuckets.keys(); + assert_array_equals(buckets, ['bucket_name2']); +}, 'delete() removes stored bucket name'); + +promise_test(async testCase => { + await prepareForBucketTest(testCase); + + await navigator.storageBuckets.open('bucket_name'); + + let buckets = await navigator.storageBuckets.keys(); + assert_array_equals(buckets, ['bucket_name']); + + await navigator.storageBuckets.delete('does-not-exist'); + + buckets = await navigator.storageBuckets.keys(); + assert_equals(buckets.length, 1); + assert_equals(buckets[0], 'bucket_name'); +}, 'delete() does nothing if bucket name does not exist'); diff --git a/testing/web-platform/tests/storage/buckets/buckets_storage_policy.tentative.https.any.js b/testing/web-platform/tests/storage/buckets/buckets_storage_policy.tentative.https.any.js new file mode 100644 index 000000000000..918364d73152 --- /dev/null +++ b/testing/web-platform/tests/storage/buckets/buckets_storage_policy.tentative.https.any.js @@ -0,0 +1,32 @@ +// META: title=Buckets API: Tests for bucket storage policies. +// META: script=resources/util.js +// META: global=window,worker + +'use strict'; + +function sanitizeQuota(quota) { + return Math.max(1, Math.min(Number.MAX_SAFE_INTEGER, Math.floor(quota))); +} + +async function testQuota(storageKeyQuota, quota, name) { + const safeQuota = sanitizeQuota(quota); + const bucket = await navigator.storageBuckets.open(name, { quota: safeQuota }); + const estimateQuota = (await bucket.estimate()).quota; + assert_equals(estimateQuota, Math.min(safeQuota, storageKeyQuota)); +} + +promise_test(async testCase => { + await prepareForBucketTest(testCase); + + const storageKeyQuota = (await navigator.storage.estimate()).quota; + + testQuota(storageKeyQuota, 1, 'one'); + testQuota(storageKeyQuota, storageKeyQuota / 4, 'quarter'); + testQuota(storageKeyQuota, storageKeyQuota / 2, 'half'); + testQuota(storageKeyQuota, storageKeyQuota - 1, 'one_less'); + testQuota(storageKeyQuota, storageKeyQuota, 'origin_quota'); + testQuota(storageKeyQuota, storageKeyQuota + 1, 'one_more'); + testQuota(storageKeyQuota, storageKeyQuota * 2, 'twice'); + testQuota(storageKeyQuota, storageKeyQuota * 4, 'four_times'); + testQuota(storageKeyQuota, Number.MAX_SAFE_INTEGER, 'max_safe_int'); +}, 'For an individual bucket, the quota is the minimum of the requested quota and the StorageKey quota.'); diff --git a/testing/web-platform/tests/storage/buckets/detached-iframe.https.html b/testing/web-platform/tests/storage/buckets/detached-iframe.https.html new file mode 100644 index 000000000000..a67c89efa3e8 --- /dev/null +++ b/testing/web-platform/tests/storage/buckets/detached-iframe.https.html @@ -0,0 +1,62 @@ + + +Storage Buckets API on detached iframe + + + + + + + diff --git a/testing/web-platform/tests/storage/buckets/idlharness-worker.https.any.js b/testing/web-platform/tests/storage/buckets/idlharness-worker.https.any.js new file mode 100644 index 000000000000..9a1a3f80b9bc --- /dev/null +++ b/testing/web-platform/tests/storage/buckets/idlharness-worker.https.any.js @@ -0,0 +1,22 @@ +// META: global=window,worker +// META: script=/resources/WebIDLParser.js +// META: script=/resources/idlharness.js + +'use strict'; + +idl_test( + ['storage-buckets'], + ['html'], + async (idl_array, t) => { + idl_array.add_objects({ + StorageBucketManager: ['navigator.storageBuckets'], + StorageBucket: [] + }); + + if (self.Window) { + idl_array.add_objects({ Navigator: ['navigator'] }); + } else { + idl_array.add_objects({ WorkerNavigator: ['navigator'] }); + } + } +); diff --git a/testing/web-platform/tests/storage/buckets/opaque-origin.https.window.js b/testing/web-platform/tests/storage/buckets/opaque-origin.https.window.js new file mode 100644 index 000000000000..c91d3faa67a3 --- /dev/null +++ b/testing/web-platform/tests/storage/buckets/opaque-origin.https.window.js @@ -0,0 +1,58 @@ +// META: title=Storage Buckets API: Interface is not exposed in opaque origins. +// META: script=resources/util.js +// META: global=window + +const kSandboxWindowUrl = 'resources/opaque-origin-sandbox.html'; + +function add_iframe(test, src, sandbox) { + const iframe = document.createElement('iframe'); + iframe.src = src; + if (sandbox !== undefined) { + iframe.sandbox = sandbox; + } + document.body.appendChild(iframe); + test.add_cleanup(() => { + iframe.remove(); + }); +} + +// |kSandboxWindowUrl| sends the result of methods on StorageBucketManager. +// For windows using sandbox="allow-scripts", it must produce a rejected +// promise. +async function verify_results_from_sandboxed_child_window(test) { + const event_watcher = new EventWatcher(test, self, 'message'); + + const first_message_event = await event_watcher.wait_for('message'); + assert_equals( + first_message_event.data, + 'navigator.storageBuckets.open(): REJECTED: SecurityError'); + + const second_message_event = await event_watcher.wait_for('message'); + assert_equals( + second_message_event.data, + 'navigator.storageBuckets.keys(): REJECTED: SecurityError'); + + const third_message_event = await event_watcher.wait_for('message'); + assert_equals( + third_message_event.data, + 'navigator.storageBuckets.delete(): REJECTED: SecurityError'); +} + +promise_test(async testCase => { + prepareForBucketTest(testCase); + add_iframe(testCase, kSandboxWindowUrl, /*sandbox=*/ 'allow-scripts'); + await verify_results_from_sandboxed_child_window(testCase); +}, 'StorageBucketManager methods must reject in a sandboxed iframe.'); + +promise_test(async testCase => { + prepareForBucketTest(testCase); + const child_window_url = kSandboxWindowUrl + + '?pipe=header(Content-Security-Policy, sandbox allow-scripts)'; + + const child_window = window.open(child_window_url); + testCase.add_cleanup(() => { + child_window.close(); + }); + + await verify_results_from_sandboxed_child_window(testCase); +}, 'StorageBucketManager methods must reject in a sandboxed opened window.'); diff --git a/testing/web-platform/tests/storage/buckets/resources/opaque-origin-sandbox.html b/testing/web-platform/tests/storage/buckets/resources/opaque-origin-sandbox.html new file mode 100644 index 000000000000..4a1ac39fdf6f --- /dev/null +++ b/testing/web-platform/tests/storage/buckets/resources/opaque-origin-sandbox.html @@ -0,0 +1,52 @@ + + + diff --git a/testing/web-platform/tests/storage/buckets/resources/util.js b/testing/web-platform/tests/storage/buckets/resources/util.js index 425303ce2c9c..5fff4894442a 100644 --- a/testing/web-platform/tests/storage/buckets/resources/util.js +++ b/testing/web-platform/tests/storage/buckets/resources/util.js @@ -4,7 +4,7 @@ // is over (whether it passes or fails). async function prepareForBucketTest(test) { // Verify initial state. - assert_equals('', (await navigator.storageBuckets.keys()).join()); + assert_equals((await navigator.storageBuckets.keys()).join(), ''); // Clean up after test. test.add_cleanup(async function() { const keys = await navigator.storageBuckets.keys(); diff --git a/testing/web-platform/tests/storage/buckets/storage_bucket_object.tentative.https.any.js b/testing/web-platform/tests/storage/buckets/storage_bucket_object.tentative.https.any.js new file mode 100644 index 000000000000..52f1693ccb22 --- /dev/null +++ b/testing/web-platform/tests/storage/buckets/storage_bucket_object.tentative.https.any.js @@ -0,0 +1,143 @@ +// META: title=Buckets API: Tests for the StorageBucket object. +// META: global=window,worker + +'use strict'; + +promise_test(async testCase => { + const bucket = await navigator.storageBuckets.open('bucket_name'); + testCase.add_cleanup(async () => { + await navigator.storageBuckets.delete('bucket_name'); + }); + const persisted = await bucket.persisted(); + assert_false(persisted); + + // Also verify that the promise is rejected after the bucket is deleted. + await navigator.storageBuckets.delete('bucket_name'); + await promise_rejects_dom(testCase, 'UnknownError', bucket.persisted()); +}, 'persisted() should default to false'); + +promise_test(async testCase => { + const bucket = await navigator.storageBuckets.open('bucket_name'); + testCase.add_cleanup(async () => { + await navigator.storageBuckets.delete('bucket_name'); + }); + const estimate = await bucket.estimate(); + assert_greater_than(estimate.quota, 0); + assert_equals(estimate.usage, 0); + + const cacheName = 'attachments'; + const cacheKey = 'receipt1.txt'; + var inboxCache = await bucket.caches.open(cacheName); + await inboxCache.put(cacheKey, new Response('bread x 2')) + + const estimate2 = await bucket.estimate(); + assert_equals(estimate.quota, estimate2.quota); + assert_less_than(estimate.usage, estimate2.usage); +}, 'estimate() should retrieve quota usage'); + +promise_test(async testCase => { + const bucket = await navigator.storageBuckets.open( + 'bucket_name', { durability: 'strict' }); + testCase.add_cleanup(async () => { + await navigator.storageBuckets.delete('bucket_name'); + }); + + const durability = await bucket.durability(); + assert_equals('strict', durability); + + await navigator.storageBuckets.delete('bucket_name'); + await promise_rejects_dom(testCase, 'UnknownError', bucket.durability()); +}, 'durability() should retrieve bucket durability specified during creation'); + +promise_test(async testCase => { + const bucket = await navigator.storageBuckets.open('bucket_name'); + testCase.add_cleanup(async () => { + await navigator.storageBuckets.delete('bucket_name'); + }); + + const durability = await bucket.durability(); + assert_equals('relaxed', durability); +}, 'Bucket durability defaults to relaxed'); + +promise_test(async testCase => { + const oneYear = 365 * 24 * 60 * 60 * 1000; + const expiresDate = Date.now() + oneYear; + const bucket = await navigator.storageBuckets.open( + 'bucket_name', { expires: expiresDate }); + testCase.add_cleanup(async () => { + await navigator.storageBuckets.delete('bucket_name'); + }); + + const expires = await bucket.expires(); + assert_equals(expires, expiresDate); +}, 'expires() should retrieve expires date'); + +promise_test(async testCase => { + const bucket = await navigator.storageBuckets.open('bucket_name'); + testCase.add_cleanup(async () => { + await navigator.storageBuckets.delete('bucket_name'); + }); + + const expires = await bucket.expires(); + assert_equals(expires, null); + + await navigator.storageBuckets.delete('bucket_name'); + await promise_rejects_dom(testCase, 'UnknownError', bucket.expires()); +}, 'expires() should be defaulted to null'); + +promise_test(async testCase => { + const bucket = await navigator.storageBuckets.open('bucket_name'); + testCase.add_cleanup(async () => { + await navigator.storageBuckets.delete('bucket_name'); + }); + + const oneYear = 365 * 24 * 60 * 60 * 1000; + const expiresDate = Date.now() + oneYear; + await bucket.setExpires(expiresDate); + + const expires = await bucket.expires(); + assert_equals(expires, expiresDate); + + await navigator.storageBuckets.delete('bucket_name'); + await promise_rejects_dom(testCase, 'UnknownError', bucket.setExpires(expiresDate)); +}, 'setExpires() should set bucket expires date'); + +promise_test(async testCase => { + const oneDay = 24 * 60 * 60 * 1000; + const expiresDate = Date.now() + oneDay; + const bucket = await navigator.storageBuckets.open('bucket_name', { + expires: expiresDate + }); + testCase.add_cleanup(async () => { + await navigator.storageBuckets.delete('bucket_name'); + }); + let expires = await bucket.expires(); + assert_equals(expires, expiresDate); + + const oneYear = 365 * oneDay; + const newExpiresDate = Date.now() + oneYear; + await bucket.setExpires(newExpiresDate); + + expires = await bucket.expires(); + assert_equals(expires, newExpiresDate); +}, 'setExpires() should update expires date'); + +promise_test(async testCase => { + const bucket = await navigator.storageBuckets.open( + 'bucket_name', { durability: 'strict' }); + testCase.add_cleanup(async () => { + await navigator.storageBuckets.delete('bucket_name'); + }); + + const same_bucket = await navigator.storageBuckets.open('bucket_name'); + const durability = await bucket.durability(); + const other_durability = await same_bucket.durability(); + assert_equals(durability, other_durability); + + // Delete the bucket and remake it. + await navigator.storageBuckets.delete('bucket_name'); + const remade_bucket = await navigator.storageBuckets.open('bucket_name'); + await promise_rejects_dom(testCase, 'UnknownError', bucket.durability()); + const remade_durability = await remade_bucket.durability(); + assert_not_equals(remade_durability, durability); +}, 'two handles can refer to the same bucket, and a bucket name can be reused after deletion');