diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/IndexedDB | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/IndexedDB')
412 files changed, 27440 insertions, 0 deletions
diff --git a/testing/web-platform/tests/IndexedDB/META.yml b/testing/web-platform/tests/IndexedDB/META.yml new file mode 100644 index 0000000000..89cbe2be26 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/META.yml @@ -0,0 +1,5 @@ +spec: https://w3c.github.io/IndexedDB/ +suggested_reviewers: + - odinho + - inexorabletash + - zqzhang diff --git a/testing/web-platform/tests/IndexedDB/README.md b/testing/web-platform/tests/IndexedDB/README.md new file mode 100644 index 0000000000..545f0bec6d --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/README.md @@ -0,0 +1,8 @@ +This directory contains the Indexed Database API test suite. + +To run the tests in this test suite within a browser, go to: <https://wpt.live/IndexedDB/>. + +The latest Editor's Draft of Indexed Database API is: <https://w3c.github.io/IndexedDB/>. + +The latest W3C Technical Report of Indexed Database API is: <https://www.w3.org/TR/IndexedDB/>. + diff --git a/testing/web-platform/tests/IndexedDB/abort-in-initial-upgradeneeded.html b/testing/web-platform/tests/IndexedDB/abort-in-initial-upgradeneeded.html new file mode 100644 index 0000000000..e37056974f --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/abort-in-initial-upgradeneeded.html @@ -0,0 +1,35 @@ +<!doctype html> +<!-- Submitted from TestTWF Paris --> +<title>Test that an abort in the initial upgradeneeded sets version back to 0</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + +var db, open_rq = createdb(async_test(), undefined, 2); + +open_rq.onupgradeneeded = function(e) { + db = e.target.result; + assert_equals(db.version, 2); + var transaction = e.target.transaction; + transaction.oncomplete = fail(this, "unexpected transaction.complete"); + transaction.onabort = function(e) { + assert_equals(e.target.db.version, 0); + } + db.onabort = function() {} + + transaction.abort(); +} + +open_rq.onerror = function(e) { + assert_equals(open_rq, e.target); + assert_equals(e.target.result, undefined); + assert_equals(e.target.error.name, "AbortError"); + assert_equals(db.version, 0); + assert_equals(open_rq.transaction, null); + this.done(); +} +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/back-forward-cache-open-connection.window.js b/testing/web-platform/tests/IndexedDB/back-forward-cache-open-connection.window.js new file mode 100644 index 0000000000..397eadab87 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/back-forward-cache-open-connection.window.js @@ -0,0 +1,31 @@ +// META: title=Testing BFCache support for page with open IndexedDB connection, and eviction behavior when receiving versionchange event. +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/utils.js +// META: script=resources/support.js +// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js +// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js + +'use strict'; + +promise_test(async t => { + const rcHelper = new RemoteContextHelper(); + + // Open a window with noopener so that BFCache will work. + const rc1 = await rcHelper.addWindow( + /*config=*/ null, /*options=*/ {features: 'noopener'}); + + await createIndexedDBForTesting(rc1, 'test_idb', 1); + await assertBFCache(rc1, /*shouldRestoreFromBFCache=*/ true); + + // The page is ensured to be eligible for BFCache even with open connection, + // otherwise the previous assertion will fail with PRECONDITION_FAILED. + // Now we can test if the versionchange event will evict the BFCache. + await createIndexedDBForTesting(rc1, 'test_idb_2', 1); + + const rc2 = await rc1.navigateToNew(); + // Create an IndexedDB database with higher version. + await createIndexedDBForTesting(rc2, 'test_idb_2', 2); + await rc2.historyBack(); + // The previous page receiving versionchange event should be evicted. + await assert_not_bfcached(rc1); +}); diff --git a/testing/web-platform/tests/IndexedDB/bigint_value.htm b/testing/web-platform/tests/IndexedDB/bigint_value.htm new file mode 100644 index 0000000000..acdeebb76f --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/bigint_value.htm @@ -0,0 +1,72 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IndexedDB: BigInt keys and values</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> +// BigInt and BigInt objects are supported in serialization, per +// https://github.com/whatwg/html/pull/3480 +// This support allows them to be used as IndexedDB values. + +function value_test(value, predicate, name) { + async_test(t => { + t.step(function() { + assert_true(predicate(value), + "Predicate should return true for the initial value."); + }); + + createdb(t).onupgradeneeded = t.step_func(e => { + e.target.result + .createObjectStore("store") + .add(value, 1); + + e.target.onsuccess = t.step_func(e => { + e.target.result + .transaction("store", "readonly", {durability: "relaxed"}) + .objectStore("store") + .get(1) + .onsuccess = t.step_func(e => + { + assert_true(predicate(e.target.result), + "Predicate should return true for the deserialized result."); + t.done(); + }); + }); + }); + }, "BigInts as values in IndexedDB - " + name); +} + +value_test(1n, + x => x === 1n, + "primitive BigInt"); +value_test(Object(1n), + x => typeof x === 'object' && + x instanceof BigInt && + x.valueOf() === 1n, + "BigInt object"); +value_test({val: 1n}, + x => x.val === 1n, + "primitive BigInt inside object"); +value_test({val: Object(1n)}, + x => x.val.valueOf() === 1n && + x.val instanceof BigInt && + x.val.valueOf() === 1n, + "BigInt object inside object"); + +// However, BigInt is not supported as an IndexedDB key; support +// has been proposed in the following PR, but that change has not +// landed at the time this patch was written +// https://github.com/w3c/IndexedDB/pull/231 + +function invalidKey(key, name) { + test(t => { + assert_throws_dom("DataError", () => indexedDB.cmp(0, key)); + }, "BigInts as keys in IndexedDB - " + name); +} + +invalidKey(1n, "primitive BigInt"); +// Still an error even if the IndexedDB patch lands +invalidKey(Object(1n), "BigInt object"); +</script> diff --git a/testing/web-platform/tests/IndexedDB/bindings-inject-keys-bypass-setters.html b/testing/web-platform/tests/IndexedDB/bindings-inject-keys-bypass-setters.html new file mode 100644 index 0000000000..e67377fb77 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/bindings-inject-keys-bypass-setters.html @@ -0,0 +1,35 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: ES bindings - Inject a key into a value - Keys bypass setters</title> +<meta name="help" +href="https://w3c.github.io/IndexedDB/#inject-key-into-value-keys-bypass-setters"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script> + +promise_test(async t => { + const db = await createDatabase(t, db => { + db.createObjectStore('store'); + }); + + let setter_called = false; + Object.defineProperty(Object.prototype, '10', { + configurable: true, + set: value => { setter_called = true; }, + }); + t.add_cleanup(() => { delete Object.prototype['10']; }); + + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + const result = await promiseForRequest(t, tx.objectStore('store').put( + 'value', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'key'])); + + assert_false(setter_called, + 'Setter should not be called for key result.'); + assert_true(result.hasOwnProperty('10'), + 'Result should have own-property overriding prototype setter.'); + assert_equals(result[10], 'key', + 'Result should have expected property.'); +}, 'Returning keys to script should bypass prototype setters'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/bindings-inject-values-bypass-chain.html b/testing/web-platform/tests/IndexedDB/bindings-inject-values-bypass-chain.html new file mode 100644 index 0000000000..73505edf1d --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/bindings-inject-values-bypass-chain.html @@ -0,0 +1,35 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: ES bindings - Inject a key into a value - Values bypass chain</title> +<meta name="help" +href="https://w3c.github.io/IndexedDB/#inject-key-into-value-values-bypass-chain"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script> + +promise_test(async t => { + const db = await createDatabase(t, db => { + db.createObjectStore('store', {autoIncrement: true, keyPath: 'a.b.c'}); + }); + + Object.prototype.a = {b: {c: 'on proto'}}; + t.add_cleanup(() => { delete Object.prototype.a; }); + + const tx = db.transaction('store', 'readwrite', {durability: "relaxed"}); + tx.objectStore('store').put({}); + const result = await promiseForRequest(t, tx.objectStore('store').get(1)); + + assert_true(result.hasOwnProperty('a'), + 'Result should have own-properties overriding prototype.'); + assert_true(result.a.hasOwnProperty('b'), + 'Result should have own-properties overriding prototype.'); + assert_true(result.a.b.hasOwnProperty('c'), + 'Result should have own-properties overriding prototype.'); + assert_equals(result.a.b.c, 1, + 'Own property should match primary key generator value'); + assert_equals(Object.prototype.a.b.c, 'on proto', + 'Prototype should not be modified'); +}, 'Returning values to script should bypass prototype chain'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/bindings-inject-values-bypass-setters.html b/testing/web-platform/tests/IndexedDB/bindings-inject-values-bypass-setters.html new file mode 100644 index 0000000000..6c4fb93bfd --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/bindings-inject-values-bypass-setters.html @@ -0,0 +1,36 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: ES bindings - Inject a key into a value - Values bypass + setters</title> +<meta name="help" +href="https://w3c.github.io/IndexedDB/#inject-key-into-value-values-bypass-setters"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script> + +promise_test(async t => { + const db = await createDatabase(t, db => { + db.createObjectStore('store', {autoIncrement: true, keyPath: 'id'}); + }); + + let setter_called = false; + Object.defineProperty(Object.prototype, 'id', { + configurable: true, + set: value => { setter_called = true; }, + }); + t.add_cleanup(() => { delete Object.prototype['id']; }); + + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + tx.objectStore('store').put({}); + const result = await promiseForRequest(t, tx.objectStore('store').get(1)); + + assert_false(setter_called, + 'Setter should not be called for key result.'); + assert_true(result.hasOwnProperty('id'), + 'Result should have own-property overriding prototype setter.'); + assert_equals(result.id, 1, + 'Own property should match primary key generator value'); +}, 'Returning values to script should bypass prototype setters'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/blob-composite-blob-reads.any.js b/testing/web-platform/tests/IndexedDB/blob-composite-blob-reads.any.js new file mode 100644 index 0000000000..7d6ab9be35 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/blob-composite-blob-reads.any.js @@ -0,0 +1,155 @@ +// META: title=IDB-backed composite blobs maintain coherency +// META: script=resources/support-promises.js +// META: timeout=long + +// This test file is intended to help validate browser handling of complex blob +// scenarios where one or more levels of multipart blobs are used and varying +// IPC serialization strategies may be used depending on various complexity +// heuristics. +// +// A variety of approaches of reading the blob's contents are attempted for +// completeness: +// - `fetch-blob-url`: fetch of a URL created via URL.createObjectURL +// - Note that this is likely to involve multi-process behavior in a way that +// the next 2 currently will not unless their Blobs are round-tripped +// through a MessagePort. +// - `file-reader`: FileReader +// - `direct`: Blob.prototype.arrayBuffer() + +function composite_blob_test({ blobCount, blobSize, name }) { + // NOTE: In order to reduce the runtime of this test and due to the similarity + // of the "file-reader" mechanism to the "direct", "file-reader" is commented + // out, but if you are investigating failures detected by this test, you may + // want to uncomment it. + for (const mode of ["fetch-blob-url", /*"file-reader",*/ "direct"]) { + promise_test(async testCase => { + const key = "the-blobs"; + let memBlobs = []; + for (let iBlob = 0; iBlob < blobCount; iBlob++) { + memBlobs.push(new Blob([make_arraybuffer_contents(iBlob, blobSize)])); + } + + const db = await createDatabase(testCase, db => { + db.createObjectStore("blobs"); + }); + + const write_tx = db.transaction("blobs", "readwrite", {durability: "relaxed"}); + let store = write_tx.objectStore("blobs"); + store.put(memBlobs, key); + // Make the blobs eligible for GC which is most realistic and most likely + // to cause problems. + memBlobs = null; + + await promiseForTransaction(testCase, write_tx); + + const read_tx = db.transaction("blobs", "readonly", {durability: "relaxed"}); + store = read_tx.objectStore("blobs"); + const read_req = store.get(key); + + await promiseForTransaction(testCase, read_tx); + + const diskBlobs = read_req.result; + const compositeBlob = new Blob(diskBlobs); + + if (mode === "fetch-blob-url") { + const blobUrl = URL.createObjectURL(compositeBlob); + let urlResp = await fetch(blobUrl); + let urlFetchArrayBuffer = await urlResp.arrayBuffer(); + urlResp = null; + + URL.revokeObjectURL(blobUrl); + validate_arraybuffer_contents("fetched URL", urlFetchArrayBuffer, blobCount, blobSize); + urlFetchArrayBuffer = null; + + } else if (mode === "file-reader") { + let reader = new FileReader(); + let readerPromise = new Promise(resolve => { + reader.onload = () => { + resolve(reader.result); + } + }) + reader.readAsArrayBuffer(compositeBlob); + + let readArrayBuffer = await readerPromise; + readerPromise = null; + reader = null; + + validate_arraybuffer_contents("FileReader", readArrayBuffer, blobCount, blobSize); + readArrayBuffer = null; + } else if (mode === "direct") { + let directArrayBuffer = await compositeBlob.arrayBuffer(); + validate_arraybuffer_contents("arrayBuffer", directArrayBuffer, blobCount, blobSize); + } + }, `Composite Blob Handling: ${name}: ${mode}`); + } +} + +// Create an ArrayBuffer whose even bytes are the index identifier and whose +// odd bytes are a sequence incremented by 3 (wrapping at 256) so that +// discontinuities at power-of-2 boundaries are more detectable. +function make_arraybuffer_contents(index, size) { + const arr = new Uint8Array(size); + for (let i = 0, counter = 0; i < size; i += 2, counter = (counter + 3) % 256) { + arr[i] = index; + arr[i + 1] = counter; + } + return arr.buffer; +} + +function validate_arraybuffer_contents(source, buffer, blobCount, blobSize) { + // Accumulate a list of problems we perceive so we can report what seems to + // have happened all at once. + const problems = []; + + const arr = new Uint8Array(buffer); + + const expectedLength = blobCount * blobSize; + const actualCount = arr.length / blobSize; + if (arr.length !== expectedLength) { + problems.push(`ArrayBuffer only holds ${actualCount} blobs' worth instead of ${blobCount}.`); + problems.push(`Actual ArrayBuffer is ${arr.length} bytes but expected ${expectedLength}`); + } + + const counterBlobStep = (blobSize / 2 * 3) % 256; + let expectedBlob = 0; + let blobSeenSoFar = 0; + let expectedCounter = 0; + let counterDrift = 0; + for (let i = 0; i < arr.length; i += 2) { + if (arr[i] !== expectedBlob || blobSeenSoFar >= blobSize) { + if (blobSeenSoFar !== blobSize) { + problems.push(`Truncated blob ${expectedBlob} after ${blobSeenSoFar} bytes.`); + } else { + expectedBlob++; + } + if (expectedBlob !== arr[i]) { + problems.push(`Expected blob ${expectedBlob} but found ${arr[i]}, compensating.`); + expectedBlob = arr[i]; + } + blobSeenSoFar = 0; + expectedCounter = (expectedBlob * counterBlobStep) % 256; + counterDrift = 0; + } + + if (arr[i + 1] !== (expectedCounter + counterDrift) % 256) { + const newDrift = expectedCounter - arr[i + 1]; + problems.push(`In blob ${expectedBlob} at ${blobSeenSoFar + 1} bytes in, counter drift now ${newDrift} was ${counterDrift}`); + counterDrift = newDrift; + } + + blobSeenSoFar += 2; + expectedCounter = (expectedCounter + 3) % 256; + } + + if (problems.length) { + assert_true(false, `${source} blob payload problem: ${problems.join("\n")}`); + } else { + assert_true(true, `${source} blob payloads validated.`); + } +} + +composite_blob_test({ + blobCount: 16, + blobSize: 256 * 1024, + name: "Many blobs", +}); diff --git a/testing/web-platform/tests/IndexedDB/blob-contenttype.any.js b/testing/web-platform/tests/IndexedDB/blob-contenttype.any.js new file mode 100644 index 0000000000..2d1d6a1ec9 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/blob-contenttype.any.js @@ -0,0 +1,39 @@ +// META: title=Blob Content Type +// META: script=resources/support.js +// META: timeout=long + +indexeddb_test( + function upgrade(t, db) { + db.createObjectStore('store'); + }, + function success(t, db) { + var type = 'x-files/trust-no-one'; + + var blob = new Blob(['mulder', 'scully'], {type: type}); + assert_equals(blob.type, type, 'Blob type should match constructor option'); + + var tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + tx.objectStore('store').put(blob, 'key'); + + tx.oncomplete = t.step_func(function() { + var tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + tx.objectStore('store').get('key').onsuccess = t.step_func(function(e) { + var result = e.target.result; + assert_equals(result.type, type, 'Blob type should survive round-trip'); + + var url = URL.createObjectURL(result); + var xhr = new XMLHttpRequest(), async = true; + xhr.open('GET', url, async); + xhr.send(); + xhr.onreadystatechange = t.step_func(function() { + if (xhr.readyState !== XMLHttpRequest.DONE) + return; + assert_equals(xhr.getResponseHeader('Content-Type'), type, + 'Blob type should be preserved when fetched'); + t.done(); + }); + }); + }); + }, + 'Ensure that content type round trips when reading blob data' +); diff --git a/testing/web-platform/tests/IndexedDB/blob-delete-objectstore-db.any.js b/testing/web-platform/tests/IndexedDB/blob-delete-objectstore-db.any.js new file mode 100644 index 0000000000..1750241df4 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/blob-delete-objectstore-db.any.js @@ -0,0 +1,47 @@ +// META: title=Blob Delete Object Store +// META: script=resources/support.js + +let key = "blob key"; + +indexeddb_test( + function upgrade(t, db) { + const store0 = db.createObjectStore('store0'); + const store1 = db.createObjectStore('store1'); + + const blobAContent = "First blob content"; + const blobA = new Blob([blobAContent], {"type" : "text/plain"}); + + store0.put(blobA, key); + }, + function success(t, db) { + db.close(); + const request = indexedDB.open(db.name, 2); + + request.onupgradeneeded = t.step_func(function(e) { + const db = e.target.result; + db.deleteObjectStore('store0'); + + request.onsuccess = t.step_func(function() { + const blobBContent = "Second blob content"; + const trans = db.transaction('store1', 'readwrite', {durability: 'relaxed'}); + const store1 = trans.objectStore('store1'); + const blobB = new Blob([blobBContent], {"type" : "text/plain"}); + store1.put(blobB, key); + + trans.oncomplete = t.step_func(function() { + db.close(); + const delete_request = indexedDB.deleteDatabase(db.name); + + // The test passes if it successfully completes. + delete_request.onsuccess = t.step_func_done(); + + delete_request.onerror = t.unreached_func("Request should not fail."); + }); + + trans.onabort = t.unreached_func("Transaction should not be aborted."); + }); + }); + request.onsuccess = t.unreached_func("Request should not succeed without an upgrade."); + request.onerror = t.unreached_func("Request should not fail."); + request.onblocked = t.unreached_func("Request should not be blocked."); + }, "Deleting an object store and a database containing blobs doesn't crash."); diff --git a/testing/web-platform/tests/IndexedDB/blob-valid-after-deletion.any.js b/testing/web-platform/tests/IndexedDB/blob-valid-after-deletion.any.js new file mode 100644 index 0000000000..b44fc4e38c --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/blob-valid-after-deletion.any.js @@ -0,0 +1,53 @@ +// META: title=Blob Valid After Deletion +// META: script=resources/support.js + +let key = "key"; + +indexeddb_test( + function upgrade(t, db) { + db.createObjectStore('store'); + }, + function success(t, db) { + const blobAContent = "Blob A content"; + const blobBContent = "Blob B content"; + const blobA = new Blob([blobAContent], {"type" : "text/plain"}); + const blobB = new Blob([blobBContent], {"type" : "text/plain"}); + value = { a0: blobA, a1: blobA, b0: blobB }; + + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + var store = tx.objectStore('store'); + + store.put(value, key); + value = null; + + const trans = db.transaction('store', 'readonly', {durability: 'relaxed'}); + store = trans.objectStore('store'); + const request = store.get(key); + + request.onsuccess = t.step_func(function() { + const record = request.result; + + trans.oncomplete = t.step_func(function() { + const trans = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + store = trans.objectStore('store'); + const request = store.delete(key); + + trans.oncomplete = t.step_func(function() { + const promise1 = record.a0.text().then(t.step_func(text => { assert_equals(text, blobAContent); }, + t.unreached_func())); + + const promise2 = record.a1.text().then(t.step_func(text => { assert_equals(text, blobAContent); }, + t.unreached_func())); + + const promise3 = record.b0.text().then(t.step_func(text => { assert_equals(text, blobBContent); }, + t.unreached_func())); + + Promise.all([promise1, promise2, promise3]).then(function() { + // The test passes if it successfully completes. + t.done(); + }); + }); + }); + }); + }, + "Blobs stay alive after their records are deleted."); diff --git a/testing/web-platform/tests/IndexedDB/blob-valid-before-commit.any.js b/testing/web-platform/tests/IndexedDB/blob-valid-before-commit.any.js new file mode 100644 index 0000000000..ad8b2a1be2 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/blob-valid-before-commit.any.js @@ -0,0 +1,41 @@ +// META: title=Blob Valid Before Commit +// META: script=resources/support.js + +let key = "key"; + +indexeddb_test( + function upgrade(t, db) { + db.createObjectStore('store'); + }, + function success(t, db) { + const blobAContent = "Blob A content"; + const blobBContent = "Blob B content"; + const blobA = new Blob([blobAContent], {"type" : "text/plain"}); + const blobB = new Blob([blobBContent], {"type" : "text/plain"}); + const value = { a0: blobA, a1: blobA, b0: blobB }; + + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + + store.put(value, key); + const request = store.get(key); + + request.onsuccess = t.step_func(function() { + const record = request.result; + + const promise1 = record.a0.text().then(t.step_func(text => { assert_equals(text, blobAContent); }, + t.unreached_func())); + + const promise2 = record.a1.text().then(t.step_func(text => { assert_equals(text, blobAContent); }, + t.unreached_func())); + + const promise3 = record.b0.text().then(t.step_func(text => { assert_equals(text, blobBContent); }, + t.unreached_func())); + + Promise.all([promise1, promise2, promise3]).then(function() { + // The test passes if it successfully completes. + t.done(); + }); + }); + }, + "Blobs can be read back before their records are committed."); diff --git a/testing/web-platform/tests/IndexedDB/clone-before-keypath-eval.html b/testing/web-platform/tests/IndexedDB/clone-before-keypath-eval.html new file mode 100644 index 0000000000..6ef36f5fe3 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/clone-before-keypath-eval.html @@ -0,0 +1,135 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: </title> +<meta name="help" href="https://w3c.github.io/IndexedDB/#dom-idbobjectstore-put"> +<meta name="help" href="https://w3c.github.io/IndexedDB/#dom-idbcursor-update"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +function ProbeObject() { + this.id_count = 0; + this.invalid_id_count = 0; + this.prop_count = 0; + Object.defineProperties(this, { + id: { + enumerable: true, + get() { + ++this.id_count; + return 1000 + this.id_count; + }, + }, + invalid_id: { + enumerable: true, + get() { + ++this.invalid_id_count; + return {}; + }, + }, + prop: { + enumerable: true, + get() { + ++this.prop_count; + return 2000 + this.prop_count; + }, + }, + }); +} + +indexeddb_test( + (t, db) => { + db.createObjectStore('store', {keyPath: 'id', autoIncrement: true}); + }, + (t, db) => { + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + const obj = new ProbeObject(); + store.put(obj); + assert_equals( + obj.id_count, 1, + 'put() operation should access primary key property once'); + assert_equals( + obj.prop_count, 1, + 'put() operation should access other properties once'); + t.done(); + }, 'Key generator and key path validity check operates on a clone'); + +indexeddb_test( + (t, db) => { + db.createObjectStore('store', {keyPath: 'invalid_id', autoIncrement: true}); + }, + (t, db) => { + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + const obj = new ProbeObject(); + assert_throws_dom('DataError', () => { store.put(obj); }, + 'put() should throw if primary key cannot be injected'); + assert_equals( + obj.invalid_id_count, 1, + 'put() operation should access primary key property once'); + assert_equals( + obj.prop_count, 1, + 'put() operation should access other properties once'); + t.done(); + }, 'Failing key path validity check operates on a clone'); + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('store'); + store.createIndex('index', 'prop'); + }, + (t, db) => { + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + const obj = new ProbeObject(); + store.put(obj, 'key'); + assert_equals( + obj.prop_count, 1, 'put() should access index key property once'); + assert_equals( + obj.id_count, 1, + 'put() operation should access other properties once'); + t.done(); + }, 'Index key path evaluations operate on a clone'); + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('store', {keyPath: 'id'}); + store.createIndex('index', 'prop'); + }, + (t, db) => { + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + const obj = new ProbeObject(); + store.put(obj); + assert_equals( + obj.id_count, 1, 'put() should access primary key property once'); + assert_equals( + obj.prop_count, 1, 'put() should access index key property once'); + t.done(); + }, 'Store and index key path evaluations operate on the same clone'); + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('store', {keyPath: 'id'}); + store.createIndex('index', 'prop'); + }, + (t, db) => { + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + store.put(new ProbeObject()); + + store.openCursor().onsuccess = t.step_func((event) => { + const cursor = event.target.result; + + const obj = new ProbeObject(); + cursor.update(obj); + assert_equals( + obj.id_count, 1, 'put() should access primary key property once'); + assert_equals( + obj.prop_count, 1, 'put() should access index key property once'); + + t.done(); + }); + }, 'Cursor update checks and keypath evaluations operate on a clone'); +</script> diff --git a/testing/web-platform/tests/IndexedDB/close-in-upgradeneeded.html b/testing/web-platform/tests/IndexedDB/close-in-upgradeneeded.html new file mode 100644 index 0000000000..59adc31a99 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/close-in-upgradeneeded.html @@ -0,0 +1,40 @@ +<!doctype html> +<!-- Submitted from TestTWF Paris --> +<title>When db.close is called in upgradeneeded, the db is cleaned up on refresh</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + +var db +var open_rq = createdb(async_test()) +var sawTransactionComplete = false + +open_rq.onupgradeneeded = function(e) { + db = e.target.result + assert_equals(db.version, 1) + + db.createObjectStore('os') + db.close() + + e.target.transaction.oncomplete = function() { sawTransactionComplete = true } +} + +open_rq.onerror = function(e) { + assert_true(sawTransactionComplete, "saw transaction.complete") + + assert_equals(e.target.error.name, 'AbortError') + assert_equals(e.result, undefined) + + assert_true(!!db) + assert_equals(db.version, 1) + assert_equals(db.objectStoreNames.length, 1) + assert_throws_dom("InvalidStateError", function() { db.transaction('os', 'readonly', {durability: 'relaxed'}) }) + + this.done() +} + +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/cursor-overloads.htm b/testing/web-platform/tests/IndexedDB/cursor-overloads.htm new file mode 100644 index 0000000000..7beeaa2bb3 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/cursor-overloads.htm @@ -0,0 +1,88 @@ +<!-- +Test converted from WebKit: +http://trac.webkit.org/browser/trunk/LayoutTests/storage/indexeddb/cursor-overloads.html +--> + +<!DOCTYPE html> +<!-- Submitted from TestTWF Paris --> +<meta charset=utf-8> +<title>Validate the overloads of IDBObjectStore.openCursor(), IDBIndex.openCursor() and IDBIndex.openKeyCursor()</title> +<link rel=author href="mailto:romain.huet@gmail.com" title="Romain Huet"> + +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + + var db, trans, store, index; + var t = async_test(); + + var request = createdb(t); + request.onupgradeneeded = function(e) { + db = request.result; + store = db.createObjectStore('store'); + index = store.createIndex('index', 'value'); + store.put({value: 0}, 0); + trans = request.transaction; + trans.oncomplete = verifyOverloads; + }; + + function verifyOverloads() { + trans = db.transaction('store', 'readonly', {durability: 'relaxed'}); + store = trans.objectStore('store'); + index = store.index('index'); + + checkCursorDirection("store.openCursor()", "next"); + checkCursorDirection("store.openCursor(0)", "next"); + checkCursorDirection("store.openCursor(0, 'next')", "next"); + checkCursorDirection("store.openCursor(0, 'nextunique')", "nextunique"); + checkCursorDirection("store.openCursor(0, 'prev')", "prev"); + checkCursorDirection("store.openCursor(0, 'prevunique')", "prevunique"); + + checkCursorDirection("store.openCursor(IDBKeyRange.only(0))", "next"); + checkCursorDirection("store.openCursor(IDBKeyRange.only(0), 'next')", "next"); + checkCursorDirection("store.openCursor(IDBKeyRange.only(0), 'nextunique')", "nextunique"); + checkCursorDirection("store.openCursor(IDBKeyRange.only(0), 'prev')", "prev"); + checkCursorDirection("store.openCursor(IDBKeyRange.only(0), 'prevunique')", "prevunique"); + + checkCursorDirection("index.openCursor()", "next"); + checkCursorDirection("index.openCursor(0)", "next"); + checkCursorDirection("index.openCursor(0, 'next')", "next"); + checkCursorDirection("index.openCursor(0, 'nextunique')", "nextunique"); + checkCursorDirection("index.openCursor(0, 'prev')", "prev"); + checkCursorDirection("index.openCursor(0, 'prevunique')", "prevunique"); + + checkCursorDirection("index.openCursor(IDBKeyRange.only(0))", "next"); + checkCursorDirection("index.openCursor(IDBKeyRange.only(0), 'next')", "next"); + checkCursorDirection("index.openCursor(IDBKeyRange.only(0), 'nextunique')", "nextunique"); + checkCursorDirection("index.openCursor(IDBKeyRange.only(0), 'prev')", "prev"); + checkCursorDirection("index.openCursor(IDBKeyRange.only(0), 'prevunique')", "prevunique"); + + checkCursorDirection("index.openKeyCursor()", "next"); + checkCursorDirection("index.openKeyCursor(0)", "next"); + checkCursorDirection("index.openKeyCursor(0, 'next')", "next"); + checkCursorDirection("index.openKeyCursor(0, 'nextunique')", "nextunique"); + checkCursorDirection("index.openKeyCursor(0, 'prev')", "prev"); + checkCursorDirection("index.openKeyCursor(0, 'prevunique')", "prevunique"); + + checkCursorDirection("index.openKeyCursor(IDBKeyRange.only(0))", "next"); + checkCursorDirection("index.openKeyCursor(IDBKeyRange.only(0), 'next')", "next"); + checkCursorDirection("index.openKeyCursor(IDBKeyRange.only(0), 'nextunique')", "nextunique"); + checkCursorDirection("index.openKeyCursor(IDBKeyRange.only(0), 'prev')", "prev"); + checkCursorDirection("index.openKeyCursor(IDBKeyRange.only(0), 'prevunique')", "prevunique"); + + t.done(); + } + + function checkCursorDirection(statement, direction) { + request = eval(statement); + request.onsuccess = function(event) { + assert_not_equals(event.target.result, null, "Check the result is not null") + assert_equals(event.target.result.direction, direction, "Check the result direction"); + }; + } + +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/database-names-by-origin.html b/testing/web-platform/tests/IndexedDB/database-names-by-origin.html new file mode 100644 index 0000000000..374b05c8fd --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/database-names-by-origin.html @@ -0,0 +1,144 @@ +<!doctype html> +<meta charset="utf8"> +<meta name="timeout" content="long"> +<title>IndexedDB: origins have isolated namespaces</title> +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../common/get-host-info.sub.js"></script> +<script src="resources/support-promises.js"></script> + +<body> +<script> +'use strict'; + +// Returns a Promise that resolves with the helper's response. +function waitForCrossOriginHelperResponse(origin, request) { + return new Promise((resolve, reject) => { + self.addEventListener('message', event => { + if (event.origin !== origin) { + reject(new Error(`Unexpected message from ${event.origin}`)); + return; + } + + if (event.data.action === request.action) { + resolve(event.data.response); + } else { + reject(new Error(`Unexpected message ${JSON.stringify(event.data)}`)); + } + }, { once: true }); + }); +} + +// Returns a Promise that resolves with the helper's response. +async function crossOriginIframeHelper(testCase, origin, request) { + const iframe = document.createElement('iframe'); + iframe.src = origin + '/IndexedDB/resources/cross-origin-helper-frame.html'; + document.body.appendChild(iframe); + testCase.add_cleanup(() => { + try { + document.body.removeChild(iframe); + } catch (e) { + // removeChild() throws if the iframe was already removed, which happens + // if this method runs to completion. + } + }); + + await new Promise((resolve, reject) => { + iframe.onload = resolve; + iframe.onerror = reject; + }); + + iframe.contentWindow.postMessage(request, origin); + const response = await waitForCrossOriginHelperResponse(origin, request); + document.body.removeChild(iframe); + return response; +}; + +// Returns a Promise that resolves with the helper's response. +async function crossOriginWindowHelper(testCase, origin, request) { + const helperWindow = window.open( + origin + '/IndexedDB/resources/cross-origin-helper-frame.html', + '_blank'); + testCase.add_cleanup(() => { helperWindow.close(); }); + + await new Promise((resolve, reject) => { + self.addEventListener('message', event => { + if (event.origin !== origin) { + reject(new Error(`Unexpected message from ${event.origin}`)); + return; + } + + if (event.data.action === null && event.data.response === 'ready') { + resolve(event.data.response); + } else { + reject(new Error(`Unexpected message ${JSON.stringify(event.data)}`)); + } + }, { once: true }); + }); + + helperWindow.postMessage(request, origin); + const response = await waitForCrossOriginHelperResponse(origin, request); + helperWindow.close(); + return response; +}; + +// Returns a Promise that resolves with the helper's response. +function crossOriginHelper(testCase, mode, origin, request) { + switch (mode) { + case 'iframe': + return crossOriginIframeHelper(testCase, origin, request); + case 'window': + return crossOriginWindowHelper(testCase, origin, request); + default: + throw new Error(`Unsupported cross-origin helper mode ${mode}`); + } +} + +const sameOrigin = get_host_info().ORIGIN; +const otherOrigin = get_host_info().REMOTE_ORIGIN; + +// The disclosure that inspired this test demonstrated leaked open database +// connections across windows. +for (const databaseKind of ['open', 'closed']) { + for (const mode of ['iframe', 'window']) { + promise_test(async testCase => { + const dbName = databaseName(testCase); + + assert_true( + await crossOriginHelper( + testCase, mode, sameOrigin, + {action: 'delete-database', name: dbName}), + 'Same-origin setup error'); + assert_true( + await crossOriginHelper( + testCase, mode, otherOrigin, + { action: 'delete-database', name: dbName }), + 'Cross-origin setup error'); + + const db = await createNamedDatabase(testCase, dbName, database => { + database.createObjectStore('store'); + }); + + if (databaseKind === 'closed') + await db.close(); + + const sameOriginDbNames = await crossOriginHelper( + testCase, mode, sameOrigin, { action: 'get-database-names' }); + assert_in_array( + sameOriginDbNames, dbName, + `Database creation should reflect in same-origin ${mode}`); + + const otherOriginDbNames = await crossOriginHelper( + testCase, mode, otherOrigin, { action: 'get-database-names' }); + assert_true( + otherOriginDbNames.indexOf(dbName) === -1, + `Database creation should not impact cross-origin ${mode} list`); + + if (databaseKind !== 'closed') + await db.close(); + }, `${databaseKind} database names don't leak to cross-origin ${mode}`); + } +} +</script> +</body> diff --git a/testing/web-platform/tests/IndexedDB/delete-range.any.js b/testing/web-platform/tests/IndexedDB/delete-range.any.js new file mode 100644 index 0000000000..b86d646354 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/delete-range.any.js @@ -0,0 +1,44 @@ +// META: title=Delete range +// META: script=resources/support.js + +"use strict"; + +let entries = [ + { lower: 3, upper: 8, lowerOpen: false, upperOpen: false, expected: [1, 2, 9, 10]}, + { lower: 3, upper: 8, lowerOpen: true, upperOpen: false, expected: [1, 2, 3, 9, 10]}, + { lower: 3, upper: 8, lowerOpen: false, upperOpen: true, expected: [1, 2, 8, 9, 10]}, + { lower: 3, upper: 8, lowerOpen: true, upperOpen: true, expected: [1, 2, 3, 8, 9, 10]} +]; + +for (const entry of entries) { + indexeddb_test( + function upgrade_func(t, db) { + db.createObjectStore("store"); + }, + function open_func(t, db) { + const store = db.transaction("store", "readwrite", {durability: 'relaxed'}).objectStore("store"); + + for (let i = 1; i <= 10; ++i) { + store.put(i, i); + } + store.delete(IDBKeyRange.bound(entry.lower, + entry.upper, + entry.lowerOpen, + entry.upperOpen)); + + let keys = []; + const cursor_request = store.openCursor(); + cursor_request.onsuccess = t.step_func(function () { + const cursor = cursor_request.result; + if (cursor) { + keys.push(cursor.key); + cursor.continue(); + } else { + assert_array_equals(entry.expected, keys, `Expected: ${entry.expected}, got: ${keys}.`); + t.done(); + } + }); + cursor_request.onerror = t.unreached_func("Failed to open cursor for read request."); + } + ) +} diff --git a/testing/web-platform/tests/IndexedDB/delete-request-queue.html b/testing/web-platform/tests/IndexedDB/delete-request-queue.html new file mode 100644 index 0000000000..d8dfbf9a60 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/delete-request-queue.html @@ -0,0 +1,27 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: delete requests are processed as a FIFO queue</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#request-connection-queue"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +let saw; +indexeddb_test( + (t, db) => { + saw = expect(t, ['delete1', 'delete2']); + let r = indexedDB.deleteDatabase(db.name); + r.onerror = t.unreached_func('delete should succeed'); + r.onsuccess = t.step_func(e => saw('delete1')); + }, + (t, db) => { + let r = indexedDB.deleteDatabase(db.name); + r.onerror = t.unreached_func('delete should succeed'); + r.onsuccess = t.step_func(e => saw('delete2')); + + db.close(); + }, + 'Deletes are processed in order'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/error-attributes.html b/testing/web-platform/tests/IndexedDB/error-attributes.html new file mode 100644 index 0000000000..d65bf21790 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/error-attributes.html @@ -0,0 +1,38 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: Error attributes are DOMExceptions</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#idbrequest"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + function(t, db) { + db.createObjectStore('store'); + }, + function(t, db) { + var tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + var store = tx.objectStore('store'); + var r1 = store.add('value', 'key'); + r1.onerror = t.unreached_func('first add should succeed'); + + var r2 = store.add('value', 'key'); + r2.onsuccess = t.unreached_func('second add should fail'); + + r2.onerror = t.step_func(function() { + assert_true(r2.error instanceof DOMException); + assert_equals(r2.error.name, 'ConstraintError'); + }); + + tx.oncomplete = t.unreached_func('transaction should not complete'); + tx.onabort = t.step_func(function() { + assert_true(tx.error instanceof DOMException); + assert_equals(tx.error.name, 'ConstraintError'); + t.done(); + }); + }, + 'IDBRequest and IDBTransaction error properties should be DOMExceptions' +); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/event-dispatch-active-flag.html b/testing/web-platform/tests/IndexedDB/event-dispatch-active-flag.html new file mode 100644 index 0000000000..46f249c984 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/event-dispatch-active-flag.html @@ -0,0 +1,154 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Transaction active flag is set during event dispatch</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#fire-success-event"> +<link rel="help" href="https://w3c.github.io/IndexedDB/#fire-error-event"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> +<script> + +indexeddb_test( + (t, db, tx) => { + db.createObjectStore('store'); + }, + (t, db) => { + const tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + const release_tx = keep_alive(tx, 'store'); + + assert_true(is_transaction_active(tx, 'store'), + 'Transaction should be active after creation'); + + const request = tx.objectStore('store').get(0); + request.onerror = t.unreached_func('request should succeed'); + request.onsuccess = () => { + assert_true(is_transaction_active(tx, 'store'), + 'Transaction should be active during success handler'); + + let saw_handler_promise = false; + Promise.resolve().then(t.step_func(() => { + saw_handler_promise = true; + assert_true(is_transaction_active(tx, 'store'), + 'Transaction should be active in handler\'s microtasks'); + })); + + setTimeout(t.step_func(() => { + assert_true(saw_handler_promise); + assert_false(is_transaction_active(tx, 'store'), + 'Transaction should be inactive in next task'); + release_tx(); + t.done(); + }), 0); + }; + }, + 'Transactions are active during success handlers'); + +indexeddb_test( + (t, db, tx) => { + db.createObjectStore('store'); + }, + (t, db) => { + const tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + const release_tx = keep_alive(tx, 'store'); + assert_true(is_transaction_active(tx, 'store'), + 'Transaction should be active after creation'); + + const request = tx.objectStore('store').get(0); + request.onerror = t.unreached_func('request should succeed'); + request.addEventListener('success', () => { + assert_true(is_transaction_active(tx, 'store'), + 'Transaction should be active during success listener'); + + let saw_listener_promise = false; + Promise.resolve().then(t.step_func(() => { + saw_listener_promise = true; + assert_true(is_transaction_active(tx, 'store'), + 'Transaction should be active in listener\'s microtasks'); + })); + + setTimeout(t.step_func(() => { + assert_true(saw_listener_promise); + assert_false(is_transaction_active(tx, 'store'), + 'Transaction should be inactive in next task'); + release_tx(); + t.done(); + }), 0); + }); + }, + 'Transactions are active during success listeners'); + +indexeddb_test( + (t, db, tx) => { + db.createObjectStore('store'); + }, + (t, db) => { + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + const release_tx = keep_alive(tx, 'store'); + assert_true(is_transaction_active(tx, 'store'), + 'Transaction should be active after creation'); + + tx.objectStore('store').put(0, 0); + const request = tx.objectStore('store').add(0, 0); + request.onsuccess = t.unreached_func('request should fail'); + request.onerror = e => { + e.preventDefault(); + + assert_true(is_transaction_active(tx, 'store'), + 'Transaction should be active during error handler'); + + let saw_handler_promise = false; + Promise.resolve().then(t.step_func(() => { + saw_handler_promise = true; + assert_true(is_transaction_active(tx, 'store'), + 'Transaction should be active in handler\'s microtasks'); + })); + + setTimeout(t.step_func(() => { + assert_true(saw_handler_promise); + assert_false(is_transaction_active(tx, 'store'), + 'Transaction should be inactive in next task'); + release_tx(); + t.done(); + }), 0); + }; + }, + 'Transactions are active during error handlers'); + +indexeddb_test( + (t, db, tx) => { + db.createObjectStore('store'); + }, + (t, db) => { + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + const release_tx = keep_alive(tx, 'store'); + assert_true(is_transaction_active(tx, 'store'), + 'Transaction should be active after creation'); + + tx.objectStore('store').put(0, 0); + const request = tx.objectStore('store').add(0, 0); + request.onsuccess = t.unreached_func('request should fail'); + request.addEventListener('error', e => { + e.preventDefault(); + + assert_true(is_transaction_active(tx, 'store'), + 'Transaction should be active during error listener'); + + let saw_listener_promise = false; + Promise.resolve().then(t.step_func(() => { + saw_listener_promise = true; + assert_true(is_transaction_active(tx, 'store'), + 'Transaction should be active in listener\'s microtasks'); + })); + + setTimeout(t.step_func(() => { + assert_true(saw_listener_promise); + assert_false(is_transaction_active(tx, 'store'), + 'Transaction should be inactive in next task'); + release_tx(); + t.done(); + }), 0); + }); + }, + 'Transactions are active during error listeners'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/file_support.sub.html b/testing/web-platform/tests/IndexedDB/file_support.sub.html new file mode 100644 index 0000000000..fe4bdf13ed --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/file_support.sub.html @@ -0,0 +1,61 @@ +<!doctype html> +<meta charset=utf8> +<title>File support in IndexedDB</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="resources/support-promises.js"></script> +<form id="form"> + <input id="file_input" name="file_input" type="file"> +</form> +<script> + +function assert_file_metadata_equal(file1, file2) { + assert_true(file1 instanceof File); + assert_true(file2 instanceof File) + assert_equals(file1.lastModified, file2.lastModified); + assert_equals(file1.name, file2.name); + assert_equals(file1.size, file2.size); + assert_equals(file1.type, file2.type); +} + +async function assert_file_contents_equals(file1, file2) { + const file1_text = await file1.text(); + const file2_text = await file2.text(); + assert_equals(file1_text, file2_text); +} + +promise_test(async (testCase) => { + const input = document.getElementById("file_input"); + await test_driver.send_keys(input, String.raw`{{fs_path(resources/file_to_save.txt)}}`); + assert_equals(input.files.length, 1); + + const file = input.files[0]; + + const db = await createDatabase(testCase, db => { + db.createObjectStore('objectStore'); + }); + + const txn = db.transaction(['objectStore'], 'readwrite'); + txn.objectStore('objectStore').add(file, 'key1'); + txn.objectStore('objectStore').add({file: file, other: 'data'}, 'key2'); + await promiseForTransaction(testCase, txn); + + const readTxn = db.transaction(['objectStore'], 'readonly'); + const fileByItself = await promiseForRequest( + testCase, readTxn.objectStore('objectStore').get('key1')); + const fileInDict = await promiseForRequest( + testCase, readTxn.objectStore('objectStore').get('key2')); + + assert_file_metadata_equal(fileByItself, file); + assert_file_metadata_equal(fileInDict.file, file); + assert_file_metadata_equal(fileInDict.file, fileByItself); + + await assert_file_contents_equals(fileByItself, file); + await assert_file_contents_equals(fileInDict.file, file); + + db.close(); +}, "Saves and loads back File objects from IndexedDB"); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/fire-error-event-exception.html b/testing/web-platform/tests/IndexedDB/fire-error-event-exception.html new file mode 100644 index 0000000000..f380e4081f --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/fire-error-event-exception.html @@ -0,0 +1,181 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Fire error event - Exception thrown</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#fire-error-event"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> +<script> +setup({allow_uncaught_exception:true}); + +function fire_error_event_test(func, description) { + indexeddb_test( + (t, db) => { + db.createObjectStore('s'); + }, + (t, db) => { + const tx = db.transaction('s', 'readwrite', {durability: 'relaxed'}); + tx.oncomplete = t.unreached_func('transaction should abort'); + const store = tx.objectStore('s'); + store.put(0, 0); + const request = store.add(0, 0); + request.onsuccess = t.unreached_func('request should fail'); + func(t, db, tx, request); + tx.addEventListener('abort', t.step_func_done(() => { + assert_equals(tx.error.name, 'AbortError'); + })); + }, + description); +} + +// Listeners on the request. + +fire_error_event_test((t, db, tx, request) => { + request.onerror = () => { + throw Error(); + }; +}, 'Exception in error event handler on request'); + +fire_error_event_test((t, db, tx, request) => { + request.onerror = e => { + e.preventDefault(); + throw Error(); + }; +}, 'Exception in error event handler on request, with preventDefault'); + +fire_error_event_test((t, db, tx, request) => { + request.addEventListener('error', () => { + throw Error(); + }); +}, 'Exception in error event listener on request'); + +fire_error_event_test((t, db, tx, request) => { + request.addEventListener('error', { + get handleEvent() { + throw new Error(); + }, + }); +}, 'Exception in error event listener ("handleEvent" lookup) on request'); + +fire_error_event_test((t, db, tx, request) => { + request.addEventListener('error', {}); +}, 'Exception in error event listener (non-callable "handleEvent") on request'); + +fire_error_event_test((t, db, tx, request) => { + request.addEventListener('error', () => { + // no-op + }); + request.addEventListener('error', () => { + throw Error(); + }); +}, 'Exception in second error event listener on request'); + +fire_error_event_test((t, db, tx, request) => { + let second_listener_called = false; + request.addEventListener('error', () => { + throw Error(); + }); + request.addEventListener('error', t.step_func(() => { + second_listener_called = true; + assert_true(is_transaction_active(tx, 's'), + 'Transaction should be active until dispatch completes'); + })); + tx.addEventListener('abort', t.step_func(() => { + assert_true(second_listener_called); + })); +}, 'Exception in first error event listener on request, ' + + 'transaction active in second'); + +// Listeners on the transaction. + +fire_error_event_test((t, db, tx, request) => { + tx.onerror = () => { + throw Error(); + }; +}, 'Exception in error event handler on transaction'); + +fire_error_event_test((t, db, tx, request) => { + tx.onerror = e => { + e.preventDefault(); + throw Error(); + }; +}, 'Exception in error event handler on transaction, with preventDefault'); + +fire_error_event_test((t, db, tx, request) => { + tx.addEventListener('error', () => { + throw Error(); + }); +}, 'Exception in error event listener on transaction'); + +fire_error_event_test((t, db, tx, request) => { + tx.addEventListener('error', () => { + // no-op + }); + tx.addEventListener('error', () => { + throw Error(); + }); +}, 'Exception in second error event listener on transaction'); + +fire_error_event_test((t, db, tx, request) => { + let second_listener_called = false; + tx.addEventListener('error', () => { + throw Error(); + }); + tx.addEventListener('error', t.step_func(() => { + second_listener_called = true; + assert_true(is_transaction_active(tx, 's'), + 'Transaction should be active until dispatch completes'); + })); + tx.addEventListener('abort', t.step_func(() => { + assert_true(second_listener_called); + })); +}, 'Exception in first error event listener on transaction, ' + + 'transaction active in second'); + +// Listeners on the connection. + +fire_error_event_test((t, db, tx, request) => { + db.onerror = () => { + throw Error(); + }; +}, 'Exception in error event handler on connection'); + +fire_error_event_test((t, db, tx, request) => { + db.onerror = e => { + e.preventDefault() + throw Error(); + }; +}, 'Exception in error event handler on connection, with preventDefault'); + +fire_error_event_test((t, db, tx, request) => { + db.addEventListener('error', () => { + throw Error(); + }); +}, 'Exception in error event listener on connection'); + +fire_error_event_test((t, db, tx, request) => { + db.addEventListener('error', () => { + // no-op + }); + db.addEventListener('error', () => { + throw Error(); + }); +}, 'Exception in second error event listener on connection'); + +fire_error_event_test((t, db, tx, request) => { + let second_listener_called = false; + db.addEventListener('error', () => { + throw Error(); + }); + db.addEventListener('error', t.step_func(() => { + second_listener_called = true; + assert_true(is_transaction_active(tx, 's'), + 'Transaction should be active until dispatch completes'); + })); + tx.addEventListener('abort', t.step_func(() => { + assert_true(second_listener_called); + })); +}, 'Exception in first error event listener on connection, ' + + 'transaction active in second'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/fire-success-event-exception.html b/testing/web-platform/tests/IndexedDB/fire-success-event-exception.html new file mode 100644 index 0000000000..eaff1b962a --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/fire-success-event-exception.html @@ -0,0 +1,79 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Fire success event - Exception thrown</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#fire-success-event"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> +<script> +setup({allow_uncaught_exception:true}); + +function fire_success_event_test(func, description) { + indexeddb_test( + (t, db) => { + db.createObjectStore('s'); + }, + (t, db) => { + const tx = db.transaction('s', 'readonly', {durability: 'relaxed'}); + tx.oncomplete = t.unreached_func('transaction should abort'); + const store = tx.objectStore('s'); + const request = store.get(0); + func(t, db, tx, request); + tx.addEventListener('abort', t.step_func_done(() => { + assert_equals(tx.error.name, 'AbortError'); + })); + }, + description); +} + +fire_success_event_test((t, db, tx, request) => { + request.onsuccess = () => { + throw Error(); + }; +}, 'Exception in success event handler on request'); + +fire_success_event_test((t, db, tx, request) => { + request.addEventListener('success', () => { + throw Error(); + }); +}, 'Exception in success event listener on request'); + +fire_success_event_test((t, db, tx, request) => { + request.addEventListener('success', { + get handleEvent() { + throw new Error(); + }, + }); +}, 'Exception in success event listener ("handleEvent" lookup) on request'); + +fire_success_event_test((t, db, tx, request) => { + request.addEventListener('success', { + handleEvent: null, + }); +}, 'Exception in success event listener (non-callable "handleEvent") on request'); + +fire_success_event_test((t, db, tx, request) => { + request.addEventListener('success', () => { + // no-op + }); + request.addEventListener('success', () => { + throw Error(); + }); +}, 'Exception in second success event listener on request'); + +fire_success_event_test((t, db, tx, request) => { + let second_listener_called = false; + request.addEventListener('success', () => { + throw Error(); + }); + request.addEventListener('success', t.step_func(() => { + second_listener_called = true; + assert_true(is_transaction_active(tx, 's'), + 'Transaction should be active until dispatch completes'); + })); + tx.addEventListener('abort', t.step_func(() => { + assert_true(second_listener_called); + })); +}, 'Exception in first success event listener, tx active in second'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/fire-upgradeneeded-event-exception.html b/testing/web-platform/tests/IndexedDB/fire-upgradeneeded-event-exception.html new file mode 100644 index 0000000000..7160dbdfc7 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/fire-upgradeneeded-event-exception.html @@ -0,0 +1,82 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Fire upgradeneeded event - Exception thrown</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#fire-a-version-change-event"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> +<script> +setup({allow_uncaught_exception:true}); + +function fire_upgradeneeded_event_test(func, description) { + async_test(t => { + const dbname = document.location + '-' + t.name; + const del = indexedDB.deleteDatabase(dbname); + del.onerror = t.unreached_func('deleteDatabase should succeed'); + const open = indexedDB.open(dbname, 1); + open.onsuccess = t.unreached_func('open should fail'); + let tx; + open.addEventListener('upgradeneeded', () => { + tx = open.transaction; + }); + func(t, open); + open.addEventListener('error', t.step_func_done(() => { + assert_equals(tx.error.name, 'AbortError'); + })); + }, description); +} + +fire_upgradeneeded_event_test((t, open) => { + open.onupgradeneeded = () => { + throw Error(); + }; +}, 'Exception in upgradeneeded handler'); + +fire_upgradeneeded_event_test((t, open) => { + open.addEventListener('upgradeneeded', () => { + throw Error(); + }); +}, 'Exception in upgradeneeded listener'); + +fire_upgradeneeded_event_test((t, open) => { + open.addEventListener('upgradeneeded', { + get handleEvent() { + throw new Error(); + }, + }); +}, 'Exception in upgradeneeded "handleEvent" lookup'); + +fire_upgradeneeded_event_test((t, open) => { + open.addEventListener('upgradeneeded', { + get handleEvent() { + return 10; + }, + }); +}, 'Exception in upgradeneeded due to non-callable "handleEvent"'); + +fire_upgradeneeded_event_test((t, open) => { + open.addEventListener('upgradeneeded', () => { + // No-op. + }); + open.addEventListener('upgradeneeded', () => { + throw Error(); + }); +}, 'Exception in second upgradeneeded listener'); + +fire_upgradeneeded_event_test((t, open) => { + let second_listener_called = false; + open.addEventListener('upgradeneeded', () => { + open.result.createObjectStore('s'); + throw Error(); + }); + open.addEventListener('upgradeneeded', t.step_func(() => { + second_listener_called = true; + assert_true(is_transaction_active(open.transaction, 's'), + 'Transaction should be active until dispatch completes'); + })); + open.addEventListener('error', t.step_func(() => { + assert_true(second_listener_called); + })); +}, 'Exception in first upgradeneeded listener, tx active in second'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/get-databases.any.js b/testing/web-platform/tests/IndexedDB/get-databases.any.js new file mode 100644 index 0000000000..ac1ab15f27 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/get-databases.any.js @@ -0,0 +1,119 @@ +// META: script=resources/support-promises.js + +promise_test(async testCase => { + let result = indexedDB.databases(); + assert_true(result instanceof Promise, + "databases() should return a promise."); + result.catch(() => {}); +}, "Ensure that databases() returns a promise."); + +promise_test(async testCase => { + // Delete any databases that may not have been cleaned up after previous test + // runs. + await deleteAllDatabases(testCase); + + const db_name = "TestDatabase"; + const db = await createNamedDatabase(testCase, db_name, ()=>{}); + const databases_result = await indexedDB.databases(); + db.close(); + const expected_result = {"name": db_name, "version": 1}; + assert_equals( + databases_result.length, + 1, + "The result of databases() should contain one result per database."); + assert_true( + databases_result[0].name === expected_result.name + && databases_result[0].version === expected_result.version, + "The result of databases() should be a sequence of the correct names " + + "and versions of all databases for the origin."); +}, "Enumerate one database."); + +promise_test(async testCase => { + // Delete any databases that may not have been cleaned up after previous test + // runs. + await deleteAllDatabases(testCase); + + const db_name1 = "TestDatabase1"; + const db_name2 = "TestDatabase2"; + const db_name3 = "TestDatabase3"; + const db1 = await createNamedDatabase(testCase, db_name1, ()=>{}); + const db2 = await createNamedDatabase(testCase, db_name2, ()=>{}); + const db3 = await createNamedDatabase(testCase, db_name3, ()=>{}); + db1.close(); + db2.close(); + db3.close(); + const version_promise = + await migrateNamedDatabase(testCase, db_name2, 2, () => {}); + const databases_result = await indexedDB.databases(); + const expected_result = [ + {"name": db_name1, "version": 1}, + {"name": db_name2, "version": 2}, + {"name": db_name3, "version": 1}, + ]; + assert_equals( + databases_result.length, + expected_result.length, + "The result of databases() should contain one result per database."); + for ( let i = 0; i < expected_result.length; i += 1 ) { + result = expected_result[i]; + assert_true( + databases_result.some( + e => e.name === result.name && e.version === result.version), + "The result of databases() should be a sequence of the correct names " + + "and versions of all databases for the origin."); + } +}, "Enumerate multiple databases."); + +promise_test(async testCase => { + // Add some databases and close their connections. + const db1 = await createNamedDatabase(testCase, "DB1", () => {}); + const db2 = await createNamedDatabase(testCase, "DB2", () => {}); + db1.close(); + db2.close(); + + // Delete any databases that may not have been cleaned up after previous test + // runs as well as the two databases made above. + await deleteAllDatabases(testCase); + + // Make sure the databases are no longer returned. + const databases_result = await indexedDB.databases(); + assert_equals( + databases_result.length, + 0, + "The result of databases() should be an empty sequence for the case of " + + "no databases for the origin."); +}, "Make sure an empty list is returned for the case of no databases."); + +promise_test(async testCase => { + // Delete any databases that may not have been cleaned up after previous test + // runs as well as the two databases made above. + await deleteAllDatabases(testCase); + + const db1 = await createNamedDatabase(testCase, "DB1", ()=>{}); + const db2 = await createNamedDatabase(testCase, "DB2", async () => { + const databases_result1 = await indexedDB.databases(); + assert_equals( + databases_result1.length, + 1, + "The result of databases() should be only those databases which have " + + "been created at the time of calling, regardless of versionchange " + + "transactions currently running."); + }); + db1.close(); + db2.close(); + const databases_result2 = await indexedDB.databases(); + assert_equals( + databases_result2.length, + 2, + "The result of databases() should include all databases which have " + + "been created at the time of calling."); + await migrateNamedDatabase(testCase, "DB2", 2, async () => { + const databases_result3 = await indexedDB.databases(); + assert_true( + databases_result3[0].version === 1 + && databases_result3[1].version === 1, + "The result of databases() should contain the versions of databases " + + "at the time of calling, regardless of versionchange transactions " + + "currently running."); + }); +}, "Ensure that databases() doesn't pick up changes that haven't commited."); diff --git a/testing/web-platform/tests/IndexedDB/globalscope-indexedDB-SameObject.html b/testing/web-platform/tests/IndexedDB/globalscope-indexedDB-SameObject.html new file mode 100644 index 0000000000..7263aaaace --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/globalscope-indexedDB-SameObject.html @@ -0,0 +1,15 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: Verify [SameObject] behavior of the global scope's indexedDB attribute</title> +<meta name="help" href="https://w3c.github.io/IndexedDB/#dom-windoworworkerglobalscope-indexeddb"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + +test(t => { + assert_equals(self.indexedDB, self.indexedDB, + 'Attribute should yield the same object each time'); + +}, 'indexedDB is [SameObject]'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/historical.html b/testing/web-platform/tests/IndexedDB/historical.html new file mode 100644 index 0000000000..8e7097eb46 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/historical.html @@ -0,0 +1,80 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IndexedDB: Historical features</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +test(function() { + // Replaced circa December 2011 by 'error'. + assert_false('errorCode' in IDBRequest.prototype); +}, '"errorCode" should not be supported on IDBRequest.'); + +test(function() { + // Replaced circa May 2012 by a DOMString (later, IDBRequestReadyState enum). + assert_false('LOADING' in IDBRequest); +}, '"LOADING" should not be supported on IDBRequest.'); + +test(function() { + // Replaced circa May 2012 by a DOMString (later, IDBRequestReadyState enum). + assert_false('DONE' in IDBRequest); +}, '"DONE" should not be supported on IDBRequest.'); + +test(function() { + // Replaced circa December 2011 by 'oldVersion'/'newVersion'. + assert_false('version' in IDBVersionChangeEvent.prototype); +}, '"version" should not be supported on IDBVersionChangeEvent.'); + +test(function() { + // Replaced circa December 2011 by open() with version. + assert_false('setVersion' in IDBDatabase.prototype); +}, '"setVersion" should not be supported on IDBDatabase.'); + +test(function() { + // Replaced circa May 2012 by a DOMString (later, IDBCursorDirection enum). + assert_false('NEXT' in IDBCursor); +}, '"NEXT" should not be supported on IDBCursor.'); + +test(function() { + // Replaced circa May 2012 by a DOMString (later, IDBCursorDirection enum). + assert_false('NEXT_NO_DUPLICATE' in IDBCursor); +}, '"NEXT_NO_DUPLICATE" should not be supported on IDBCursor.'); + +test(function() { + // Replaced circa May 2012 by a DOMString (later, IDBCursorDirection enum). + assert_false('PREV' in IDBCursor); +}, '"PREV" should not be supported on IDBCursor.'); + +test(function() { + // Replaced circa May 2012 by a DOMString (later, IDBCursorDirection enum). + assert_false('PREV_NO_DUPLICATE' in IDBCursor); +}, '"PREV_NO_DUPLICATE" should not be supported on IDBCursor.'); + +test(function() { + // Replaced circa May 2012 by a DOMString (later, IDBTransactionMode enum). + assert_false('READ_ONLY' in IDBTransaction); +}, '"READ_ONLY" should not be supported on IDBTransaction.'); + +test(function() { + // Replaced circa May 2012 by a DOMString (later, IDBTransactionMode enum). + assert_false('READ_WRITE' in IDBTransaction); +}, '"READ_WRITE" should not be supported on IDBTransaction.'); + +test(function() { + // Replaced circa May 2012 by a DOMString (later, IDBTransactionMode enum). + assert_false('VERSION_CHANGE' in IDBTransaction); +}, '"VERSION_CHANGE" should not be supported on IDBTransaction.'); + +// Gecko-proprietary interfaces. +var removedFromWindow = [ + 'IDBFileHandle', + 'IDBFileRequest', + 'IDBMutableFile', +]; + +removedFromWindow.forEach(function(name) { + test(function() { + assert_false(name in window); + }, '"' + name + '" should not be supported'); +}); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idb-binary-key-detached.htm b/testing/web-platform/tests/IndexedDB/idb-binary-key-detached.htm new file mode 100644 index 0000000000..ac6fc2ef98 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idb-binary-key-detached.htm @@ -0,0 +1,52 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: Detached buffers supplied as binary keys</title> +<meta name="help" href="http://w3c.github.io/IndexedDB/#convert-a-value-to-a-key"> +<meta name="help" href="https://webidl.spec.whatwg.org/#dfn-get-buffer-source-copy"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + (t, db) => { db.createObjectStore('store'); }, + (t, db) => { + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + + const array = new Uint8Array([1,2,3,4]); + const buffer = array.buffer; + assert_equals(array.byteLength, 4); + + // Detach the ArrayBuffer by transferring it to a worker. + const worker = new Worker(URL.createObjectURL(new Blob([]))); + worker.postMessage('', [buffer]); + assert_equals(array.byteLength, 0); + + assert_throws_js(TypeError, () => { store.put('', buffer); }); + t.done(); + }, + 'Detached ArrayBuffer' +); + +indexeddb_test( + (t, db) => { db.createObjectStore('store'); }, + (t, db) => { + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + + const array = new Uint8Array([1,2,3,4]); + assert_equals(array.length, 4); + + // Detach the ArrayBuffer by transferring it to a worker. + const worker = new Worker(URL.createObjectURL(new Blob([]))); + worker.postMessage('', [array.buffer]); + assert_equals(array.length, 0); + + assert_throws_js(TypeError, () => { store.put('', array); }); + t.done(); + }, + 'Detached TypedArray' +); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idb-binary-key-roundtrip.htm b/testing/web-platform/tests/IndexedDB/idb-binary-key-roundtrip.htm new file mode 100644 index 0000000000..de3889a71c --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idb-binary-key-roundtrip.htm @@ -0,0 +1,116 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: Binary keys written to a database and read back</title> +<meta name=timeout content=long> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +const sample = [0x44, 0x33, 0x22, 0x11, 0xFF, 0xEE, 0xDD, 0xCC]; +const buffer = new Uint8Array(sample).buffer; + +function assert_key_valid(a, message) { + assert_equals(indexedDB.cmp(a, a), 0, message); +} + +function assert_buffer_equals(a, b, message) { + assert_array_equals( + Array.from(new Uint8Array(a)), Array.from(new Uint8Array(b)), message); +} + +// Verifies that a JavaScript value round-trips through IndexedDB as a key. +function check_key_roundtrip_and_done(t, db, key, key_buffer) { + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + + // Verify put with key + const put_request = store.put('value', key); + put_request.onerror = t.unreached_func('put should succeed'); + + // Verify get with key + const get_request = store.get(key); + get_request.onerror = t.unreached_func('get should succeed'); + get_request.onsuccess = t.step_func(() => { + assert_equals( + get_request.result, 'value', + 'get should retrieve the value given to put'); + + // Verify iteration returning key + const cursor_request = store.openCursor(); + cursor_request.onerror = t.unreached_func('openCursor should succeed'); + cursor_request.onsuccess = t.step_func(() => { + assert_not_equals( + cursor_request.result, null, 'cursor should be present'); + const retrieved_key = cursor_request.result.key; + assert_true( + retrieved_key instanceof ArrayBuffer, + 'IndexedDB binary keys should be returned in ArrayBuffer instances'); + assert_key_equals( + retrieved_key, key, + 'The key returned by IndexedDB should equal the key given to put()'); + assert_buffer_equals( + retrieved_key, key_buffer, + 'The ArrayBuffer returned by IndexedDB should equal the buffer ' + + 'backing the key given to put()'); + + t.done(); + }); + }); +} + +// Checks that IndexedDB handles the given view type for binary keys correctly. +function view_type_test(type) { + indexeddb_test( + (t, db) => { db.createObjectStore('store'); }, + (t, db) => { + const key = new self[type](buffer); + assert_key_valid(key, `${type} should be usable as an IndexedDB key`); + assert_key_equals(key, buffer, + 'Binary keys with the same data but different view types should be ' + + ' equal'); + check_key_roundtrip_and_done(t, db, key, buffer); + }, + `Binary keys can be supplied using the view type ${type}`, + ); +} + +[ + 'Uint8Array', + 'Uint8ClampedArray', + 'Int8Array', + 'Uint16Array', + 'Int16Array', + 'Uint32Array', + 'Int32Array', + 'Float32Array', + 'Float64Array' +].forEach((type) => { view_type_test(type); }); + +// Checks that IndexedDB +function value_test(value_description, value, value_buffer) { + indexeddb_test( + (t, db) => { db.createObjectStore('store'); }, + (t, db) => { + assert_key_valid( + value, value_description + ' should be usable as an valid key'); + check_key_roundtrip_and_done(t, db, value, value_buffer); + }, + `${value_description} can be used to supply a binary key` + ); +} + +value_test('ArrayBuffer', buffer, buffer); +value_test('DataView', new DataView(buffer), buffer); +value_test('DataView with explicit offset', new DataView(buffer, 3), + new Uint8Array([0x11, 0xFF, 0xEE, 0xDD, 0xCC]).buffer); +value_test('DataView with explicit offset and length', + new DataView(buffer, 3, 4), + new Uint8Array([0x11, 0xFF, 0xEE, 0xDD]).buffer); +value_test('Uint8Array with explicit offset', new Uint8Array(buffer, 3), + new Uint8Array([0x11, 0xFF, 0xEE, 0xDD, 0xCC]).buffer); +value_test('Uint8Array with explicit offset and length', + new Uint8Array(buffer, 3, 4), + new Uint8Array([0x11, 0xFF, 0xEE, 0xDD]).buffer); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idb-explicit-commit-throw.any.js b/testing/web-platform/tests/IndexedDB/idb-explicit-commit-throw.any.js new file mode 100644 index 0000000000..8a9dd94056 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idb-explicit-commit-throw.any.js @@ -0,0 +1,37 @@ +// META: script=resources/support-promises.js + +setup({allow_uncaught_exception:true}); + +promise_test(async testCase => { + // Register an event listener that will prevent the intentionally thrown + // error from bubbling up to the window and failing the testharness. This + // is necessary because currently allow_uncaught_exception does not behave + // as expected for promise_test. + // + // Git issue: https://github.com/web-platform-tests/wpt/issues/14041 + self.addEventListener('error', (event) => { event.preventDefault(); }); + + const db = await createDatabase(testCase, async db => { + await createBooksStore(testCase, db); + }); + + const txn = db.transaction(['books'], 'readwrite'); + const objectStore = txn.objectStore('books'); + const putRequest = objectStore.put({isbn:'one', title:'title'}); + txn.commit(); + putRequest.onsuccess = () => { + throw new Error('This error thrown after an explicit commit should not ' + + 'prevent the transaction from committing.'); + } + await promiseForTransaction(testCase, txn); + + // Ensure that despite the uncaught error after the put request, the explicit + // commit still causes the request to be committed. + const txn2 = db.transaction(['books'], 'readwrite'); + const objectStore2 = txn2.objectStore('books'); + const getRequest = objectStore2.get('one'); + await promiseForTransaction(testCase, txn2); + + assert_equals(getRequest.result.title, 'title'); +}, 'Any errors in callbacks that run after an explicit commit will not stop ' + + 'the commit from being processed.'); diff --git a/testing/web-platform/tests/IndexedDB/idb-explicit-commit.any.js b/testing/web-platform/tests/IndexedDB/idb-explicit-commit.any.js new file mode 100644 index 0000000000..898d461d7b --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idb-explicit-commit.any.js @@ -0,0 +1,288 @@ +// META: script=resources/support-promises.js + +promise_test(async testCase => { + const db = await createDatabase(testCase, db => { + createBooksStore(testCase, db); + }); + const txn = db.transaction(['books'], 'readwrite'); + const objectStore = txn.objectStore('books'); + objectStore.put({isbn: 'one', title: 'title1'}); + objectStore.put({isbn: 'two', title: 'title2'}); + objectStore.put({isbn: 'three', title: 'title3'}); + txn.commit(); + await promiseForTransaction(testCase, txn); + + const txn2 = db.transaction(['books'], 'readonly'); + const objectStore2 = txn2.objectStore('books'); + const getRequestitle1 = objectStore2.get('one'); + const getRequestitle2 = objectStore2.get('two'); + const getRequestitle3 = objectStore2.get('three'); + txn2.commit(); + await promiseForTransaction(testCase, txn2); + assert_array_equals( + [getRequestitle1.result.title, + getRequestitle2.result.title, + getRequestitle3.result.title], + ['title1', 'title2', 'title3'], + 'All three retrieved titles should match those that were put.'); + db.close(); +}, 'Explicitly committed data can be read back out.'); + + +promise_test(async testCase => { + let db = await createDatabase(testCase, () => {}); + assert_equals(1, db.version, 'A database should be created as version 1'); + db.close(); + + // Upgrade the versionDB database and explicitly commit its versionchange + // transaction. + db = await migrateDatabase(testCase, 2, (db, txn) => { + txn.commit(); + }); + assert_equals(2, db.version, + 'The database version should have been incremented regardless of ' + + 'whether the versionchange transaction was explicitly or implicitly ' + + 'committed.'); + db.close(); +}, 'commit() on a version change transaction does not cause errors.'); + + +promise_test(async testCase => { + const db = await createDatabase(testCase, db => { + createBooksStore(testCase, db); + }); + const txn = db.transaction(['books'], 'readwrite'); + const objectStore = txn.objectStore('books'); + txn.commit(); + assert_throws_dom('TransactionInactiveError', + () => { objectStore.put({isbn: 'one', title: 'title1'}); }, + 'After commit is called, the transaction should be inactive.'); + db.close(); +}, 'A committed transaction becomes inactive immediately.'); + + +promise_test(async testCase => { + const db = await createDatabase(testCase, db => { + createBooksStore(testCase, db); + }); + const txn = db.transaction(['books'], 'readwrite'); + const objectStore = txn.objectStore('books'); + const putRequest = objectStore.put({isbn: 'one', title: 'title1'}); + putRequest.onsuccess = testCase.step_func(() => { + assert_throws_dom('TransactionInactiveError', + () => { objectStore.put({isbn:'two', title:'title2'}); }, + 'The transaction should not be active in the callback of a request after ' + + 'commit() is called.'); + }); + txn.commit(); + await promiseForTransaction(testCase, txn); + db.close(); +}, 'A committed transaction is inactive in future request callbacks.'); + + +promise_test(async testCase => { + const db = await createDatabase(testCase, db => { + createBooksStore(testCase, db); + }); + const txn = db.transaction(['books'], 'readwrite'); + const objectStore = txn.objectStore('books'); + txn.commit(); + + assert_throws_dom('TransactionInactiveError', + () => { objectStore.put({isbn:'one', title:'title1'}); }, + 'After commit is called, the transaction should be inactive.'); + + const txn2 = db.transaction(['books'], 'readonly'); + const objectStore2 = txn2.objectStore('books'); + const getRequest = objectStore2.get('one'); + await promiseForTransaction(testCase, txn2); + assert_equals(getRequest.result, undefined); + + db.close(); +}, 'Puts issued after commit are not fulfilled.'); + + +promise_test(async testCase => { + const db = await createDatabase(testCase, db => { + createBooksStore(testCase, db); + }); + const txn = db.transaction(['books'], 'readwrite'); + const objectStore = txn.objectStore('books'); + txn.abort(); + assert_throws_dom('InvalidStateError', + () => { txn.commit(); }, + 'The transaction should have been aborted.'); + db.close(); +}, 'Calling commit on an aborted transaction throws.'); + + +promise_test(async testCase => { + const db = await createDatabase(testCase, db => { + createBooksStore(testCase, db); + }); + const txn = db.transaction(['books'], 'readwrite'); + const objectStore = txn.objectStore('books'); + txn.commit(); + assert_throws_dom('InvalidStateError', + () => { txn.commit(); }, + 'The transaction should have already committed.'); + db.close(); +}, 'Calling commit on a committed transaction throws.'); + + +promise_test(async testCase => { + const db = await createDatabase(testCase, db => { + createBooksStore(testCase, db); + }); + const txn = db.transaction(['books'], 'readwrite'); + const objectStore = txn.objectStore('books'); + const putRequest = objectStore.put({isbn:'one', title:'title1'}); + txn.commit(); + assert_throws_dom('InvalidStateError', + () => { txn.abort(); }, + 'The transaction should already have committed.'); + const txn2 = db.transaction(['books'], 'readwrite'); + const objectStore2 = txn2.objectStore('books'); + const getRequest = objectStore2.get('one'); + await promiseForTransaction(testCase, txn2); + assert_equals( + getRequest.result.title, + 'title1', + 'Explicitly committed data should be gettable.'); + db.close(); +}, 'Calling abort on a committed transaction throws and does not prevent ' + + 'persisting the data.'); + + +promise_test(async testCase => { + const db = await createDatabase(testCase, db => { + createBooksStore(testCase, db); + }); + const txn = db.transaction(['books'], 'readwrite'); + const objectStore = txn.objectStore('books'); + const releaseTxnFunction = keepAlive(testCase, txn, 'books'); + + // Break up the scope of execution to force the transaction into an inactive + // state. + await timeoutPromise(0); + + assert_throws_dom('InvalidStateError', + () => { txn.commit(); }, + 'The transaction should be inactive so calling commit should throw.'); + releaseTxnFunction(); + db.close(); +}, 'Calling txn.commit() when txn is inactive should throw.'); + + +promise_test(async testCase => { + const db = await createDatabase(testCase, db => { + createBooksStore(testCase, db); + createNotBooksStore(testCase, db); + }); + // Txn1 should commit before txn2, even though txn2 uses commit(). + const txn1 = db.transaction(['books'], 'readwrite'); + txn1.objectStore('books').put({isbn: 'one', title: 'title1'}); + const releaseTxnFunction = keepAlive(testCase, txn1, 'books'); + + const txn2 = db.transaction(['books'], 'readwrite'); + txn2.objectStore('books').put({isbn:'one', title:'title2'}); + txn2.commit(); + + // Exercise the IndexedDB transaction ordering by executing one with a + // different scope. A readonly transaction is used here because + // implementations are not required to run non-overlapping readwrite + // transactions in parallel, and some implementations (ex: Firefox) + // will not. + const txn3 = db.transaction(['not_books'], 'readonly'); + txn3.objectStore('not_books').getAllKeys(); + txn3.oncomplete = function() { + releaseTxnFunction(); + } + await Promise.all([promiseForTransaction(testCase, txn1), + promiseForTransaction(testCase, txn2)]); + + // Read the data back to verify that txn2 executed last. + const txn4 = db.transaction(['books'], 'readonly'); + const getRequest4 = txn4.objectStore('books').get('one'); + await promiseForTransaction(testCase, txn4); + assert_equals(getRequest4.result.title, 'title2'); + db.close(); +}, 'Transactions with same scope should stay in program order, even if one ' + + 'calls commit.'); + + +promise_test(async testCase => { + const db = await createDatabase(testCase, db => { + createBooksStore(testCase, db); + }); + // Txn1 creates the book 'one' so the 'add()' below fails. + const txn1 = db.transaction(['books'], 'readwrite'); + txn1.objectStore('books').add({isbn:'one', title:'title1'}); + txn1.commit(); + await promiseForTransaction(testCase, txn1); + + // Txn2 should abort, because the 'add' call is invalid, and commit() was + // called. + const txn2 = db.transaction(['books'], 'readwrite'); + const objectStore2 = txn2.objectStore('books'); + objectStore2.put({isbn:'two', title:'title2'}); + const addRequest = objectStore2.add({isbn:'one', title:'title2'}); + txn2.commit(); + txn2.oncomplete = () => { assert_unreached( + 'Transaction with invalid "add" call should not be completed.'); }; + + // Wait for the transaction to complete. We have to explicitly wait for the + // error signal on the transaction because of the nature of the test tooling. + await Promise.all([ + requestWatcher(testCase, addRequest).wait_for('error'), + transactionWatcher(testCase, txn2).wait_for(['error', 'abort']) + ]); + + // Read the data back to verify that txn2 was aborted. + const txn3 = db.transaction(['books'], 'readonly'); + const objectStore3 = txn3.objectStore('books'); + const getRequest1 = objectStore3.get('one'); + const getRequest2 = objectStore3.count('two'); + await promiseForTransaction(testCase, txn3); + assert_equals(getRequest1.result.title, 'title1'); + assert_equals(getRequest2.result, 0); + db.close(); +}, 'Transactions that explicitly commit and have errors should abort.'); + + +promise_test(async testCase => { + const db = await createDatabase(testCase, db => { + createBooksStore(testCase, db); + }); + const txn1 = db.transaction(['books'], 'readwrite'); + txn1.objectStore('books').add({isbn: 'one', title: 'title1'}); + txn1.commit(); + await promiseForTransaction(testCase, txn1); + + // The second add request will throw an error, but the onerror handler will + // appropriately catch the error allowing the valid put request on the + // transaction to commit. + const txn2 = db.transaction(['books'], 'readwrite'); + const objectStore2 = txn2.objectStore('books'); + objectStore2.put({isbn: 'two', title:'title2'}); + const addRequest = objectStore2.add({isbn: 'one', title:'unreached_title'}); + addRequest.onerror = (event) => { + event.preventDefault(); + addRequest.transaction.commit(); + }; + + // Wait for the transaction to complete. We have to explicitly wait for the + // error signal on the transaction because of the nature of the test tooling. + await transactionWatcher(testCase,txn2).wait_for(['error', 'complete']) + + // Read the data back to verify that txn2 was committed. + const txn3 = db.transaction(['books'], 'readonly'); + const objectStore3 = txn3.objectStore('books'); + const getRequest1 = objectStore3.get('one'); + const getRequest2 = objectStore3.get('two'); + await promiseForTransaction(testCase, txn3); + assert_equals(getRequest1.result.title, 'title1'); + assert_equals(getRequest2.result.title, 'title2'); + db.close(); +}, 'Transactions that handle all errors properly should behave as ' + + 'expected when an explicit commit is called in an onerror handler.'); diff --git a/testing/web-platform/tests/IndexedDB/idb-partitioned-basic.tentative.sub.html b/testing/web-platform/tests/IndexedDB/idb-partitioned-basic.tentative.sub.html new file mode 100644 index 0000000000..16bdacf6c6 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idb-partitioned-basic.tentative.sub.html @@ -0,0 +1,55 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: partitioned storage test</title> +<meta name=help href="https://privacycg.github.io/storage-partitioning/"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="shared-iframe" src="http://{{host}}:{{ports[http][0]}}/IndexedDB/resources/idb-partitioned-basic-iframe.tentative.html"></iframe> +<body> +<script> +// Here's the set-up for this test: +// Step 1. (window) set up listeners for main window. +// Step 2. (same-site iframe) loads, creates a database, and sends "same-site iframe loaded" message. +// Step 3. (window) receives "same-site iframe loaded" message and opens cross-site window. +// Step 4. (cross-site iframe) loads, checks if database exists, and sends "cross-site iframe loaded" message. +// Step 5. (window) receives "cross-site iframe loaded" message, asserts that database should not exist, sends "delete database" message. +// Step 6. (same-site iframe) receives "delete database" message, deletes the database, sends "database deleted" message. +// Step 7. (window) receives the "database deleted" and then exits. +const altOrigin = "http://{{hosts[alt][]}}:{{ports[http][0]}}"; + +async_test(t => { + const iframe = document.getElementById("shared-iframe"); + + // Step 1 + window.addEventListener("message", t.step_func(e => { + + // Step 3 + if (e.data.message === "same-site iframe loaded") { + if (location.origin !== altOrigin) { + const crossSiteWindow = window.open(`${altOrigin}/IndexedDB/idb-partitioned-basic.tentative.sub.html`, "", "noopener=false"); + t.add_cleanup(() => crossSiteWindow.close()); + } + } + + // Step 5 + if (e.data.message === "cross-site iframe loaded") { + t.step(() => { + assert_false( + e.data.doesDatabaseExist, + "The cross-site iframe should not see the same-site database", + ); + }); + iframe.contentWindow.postMessage( + {message: "delete database"}, + iframe.contentWindow.origin, + ); + }; + + // Step 7 + if (e.data.message === "database deleted") { + t.done(); + }; + })); +}, "Simple test for partitioned IndexedDB"); +</script> +</body> diff --git a/testing/web-platform/tests/IndexedDB/idb-partitioned-coverage.tentative.sub.html b/testing/web-platform/tests/IndexedDB/idb-partitioned-coverage.tentative.sub.html new file mode 100644 index 0000000000..ae0884cb52 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idb-partitioned-coverage.tentative.sub.html @@ -0,0 +1,12 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: partitioned storage test</title> +<meta name=help href="https://privacycg.github.io/storage-partitioning/"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<iframe id="iframe" src="http://{{hosts[alt][]}}:{{ports[http][0]}}/IndexedDB/resources/idb-partitioned-coverage-iframe.tentative.html"></iframe> +<script> +fetch_tests_from_window(document.getElementById("iframe").contentWindow); +</script> +</body> diff --git a/testing/web-platform/tests/IndexedDB/idb-partitioned-persistence.tentative.sub.html b/testing/web-platform/tests/IndexedDB/idb-partitioned-persistence.tentative.sub.html new file mode 100644 index 0000000000..65a0a085e2 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idb-partitioned-persistence.tentative.sub.html @@ -0,0 +1,63 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: partitioned storage test</title> +<meta name=help href="https://privacycg.github.io/storage-partitioning/"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="iframe1"></iframe> +<iframe id="iframe2"></iframe> +<body> +<script> +// Here's the set-up for this test: +// Step 1. (window) set up listeners for main window. +// Step 2. (iframe1 & iframe2) loads and sends "iframe loaded" message. +// Step 3. (window) receives two "iframe loaded" message and sends "create database" message to iframe1. +// Step 4. (iframe1) receives "create database", creates database, and sends "database created" message. +// Step 5. (window) receives "database created" message and sends "check database" message to iframe2. +// Step 6. (iframe2) receives "check database" message, checks if database exists, sends "database checked" message. +// Step 7. (window) receives the "database checked" message, asserts database existed, and then exits. + +async_test(t => { + const iframe1 = document.getElementById("iframe1"); + const iframe2 = document.getElementById("iframe2"); + let iframes_loaded = 0; + + // Step 1 + window.addEventListener("message", t.step_func(e => { + + // Step 3 + if (e.data.message === "iframe loaded") { + iframes_loaded++; + if (iframes_loaded === 2) { + iframe1.contentWindow.postMessage( + {message: "create database"}, + "*", + ); + } + } + + // Step 5 + if (e.data.message === "database created") { + iframe2.contentWindow.postMessage( + {message: "check database"}, + "*", + ); + } + + // Step 7 + if (e.data.message === "database checked") { + t.step(() => { + assert_true( + e.data.doesDatabaseExist, + "The same database should exist in both frames", + ); + }); + t.done(); + } + })); + + iframe1.src = "http://{{hosts[alt][]}}:{{ports[http][0]}}/IndexedDB/resources/idb-partitioned-persistence-iframe.tentative.html"; + iframe2.src = "http://{{hosts[alt][]}}:{{ports[http][0]}}/IndexedDB/resources/idb-partitioned-persistence-iframe.tentative.html"; +}, "Persistence test for partitioned IndexedDB"); +</script> +</body> diff --git a/testing/web-platform/tests/IndexedDB/idb_binary_key_conversion.htm b/testing/web-platform/tests/IndexedDB/idb_binary_key_conversion.htm new file mode 100644 index 0000000000..b55e6324b3 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idb_binary_key_conversion.htm @@ -0,0 +1,79 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Verify the coversion of various types of BufferSource</title> +<link rel="author" title="Mozilla" href="https://www.mozilla.org"> +<link rel="help" href="http://w3c.github.io/IndexedDB/#key-construct"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> + +<script> + test(function() { + let binary = new ArrayBuffer(0); + let key = IDBKeyRange.lowerBound(binary).lower; + + assert_true(key instanceof ArrayBuffer); + assert_equals(key.byteLength, 0); + assert_equals(key.byteLength, binary.byteLength); + }, "Empty ArrayBuffer"); + + test(function() { + let binary = new ArrayBuffer(4); + let dataView = new DataView(binary); + dataView.setUint32(0, 1234567890); + + let key = IDBKeyRange.lowerBound(binary).lower; + + assert_true(key instanceof ArrayBuffer); + assert_equals(key.byteLength, 4); + assert_equals(dataView.getUint32(0), new DataView(key).getUint32(0)); + }, "ArrayBuffer"); + + test(function() { + let binary = new ArrayBuffer(4); + let dataView = new DataView(binary); + dataView.setUint32(0, 1234567890); + + let key = IDBKeyRange.lowerBound(dataView).lower; + + assert_true(key instanceof ArrayBuffer); + assert_equals(key.byteLength, 4); + assert_equals(dataView.getUint32(0), new DataView(key).getUint32(0)); + }, "DataView"); + + test(function() { + let binary = new ArrayBuffer(4); + let dataView = new DataView(binary); + let int8Array = new Int8Array(binary); + int8Array.set([16, -32, 64, -128]); + + let key = IDBKeyRange.lowerBound(int8Array).lower; + let keyInInt8Array = new Int8Array(key); + + assert_true(key instanceof ArrayBuffer); + assert_equals(key.byteLength, 4); + for(let i = 0; i < int8Array.length; i++) { + assert_equals(keyInInt8Array[i], int8Array[i]); + } + }, "TypedArray(Int8Array)"); + + test(function() { + let binary = new ArrayBuffer(4); + let dataView = new DataView(binary); + let int8Array = new Int8Array(binary); + int8Array.set([16, -32, 64, -128]); + + let key = IDBKeyRange.lowerBound([int8Array]).lower; + + assert_true(key instanceof Array); + assert_true(key[0] instanceof ArrayBuffer); + assert_equals(key[0].byteLength, 4); + + let keyInInt8Array = new Int8Array(key[0]); + + for(let i = 0; i < int8Array.length; i++) { + assert_equals(keyInInt8Array[i], int8Array[i]); + } + }, "Array of TypedArray(Int8Array)"); +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idb_webworkers.htm b/testing/web-platform/tests/IndexedDB/idb_webworkers.htm new file mode 100644 index 0000000000..5a061c2650 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idb_webworkers.htm @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<title>IndexedDB inside of a WebWorker </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, count = 0, + t = async_test(); + t.add_cleanup(function() { indexedDB.deleteDatabase('webworker101'); }); + + t.step(function() { + var worker = new Worker("resources/idbworker.js"); + worker.onmessage = t.step_func(function (e) { + switch(count) { + case 0: + assert_equals(e.data, true, 'worker has idb object') + break + + case 1: + assert_equals(e.data, "test", "get(1) in worker") + t.done() + } + + count++ + }); + + worker.postMessage(1); + }) +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor-advance-continue-async.htm b/testing/web-platform/tests/IndexedDB/idbcursor-advance-continue-async.htm new file mode 100644 index 0000000000..ff37515063 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor-advance-continue-async.htm @@ -0,0 +1,181 @@ +<!DOCTYPE html> +<title>IDBCursor asyncness</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + +function upgrade_func(t, db, tx) { + var objStore = db.createObjectStore("test"); + objStore.createIndex("index", ""); + + objStore.add("data", 1); + objStore.add("data2", 2); +} + +indexeddb_test( + upgrade_func, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").openCursor(); + + rq.onsuccess = t.step_func(function(e) { + if (!e.target.result) { + assert_equals(count, 2, 'count'); + t.done(); + return; + } + var cursor = e.target.result; + + switch(count) { + case 0: + assert_equals(cursor.value, "data") + assert_equals(cursor.key, 1) + cursor.advance(1) + assert_equals(cursor.value, "data") + assert_equals(cursor.key, 1) + break + + case 1: + assert_equals(cursor.value, "data2") + assert_equals(cursor.key, 2) + cursor.advance(1) + assert_equals(cursor.value, "data2") + assert_equals(cursor.key, 2) + break + + default: + assert_unreached("Unexpected count: " + count) + } + + count++; + }); + rq.onerror = t.unreached_func("unexpected error") + }, + document.title + " - advance" +); + +indexeddb_test( + upgrade_func, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").index("index").openCursor(); + + rq.onsuccess = t.step_func(function(e) { + if (!e.target.result) { + assert_equals(count, 2, 'count'); + t.done(); + return; + } + var cursor = e.target.result; + + switch(count) { + case 0: + assert_equals(cursor.value, "data") + assert_equals(cursor.key, "data") + assert_equals(cursor.primaryKey, 1) + cursor.continue("data2") + assert_equals(cursor.value, "data") + assert_equals(cursor.key, "data") + assert_equals(cursor.primaryKey, 1) + break + + case 1: + assert_equals(cursor.value, "data2") + assert_equals(cursor.key, "data2") + assert_equals(cursor.primaryKey, 2) + cursor.continue() + assert_equals(cursor.value, "data2") + assert_equals(cursor.key, "data2") + assert_equals(cursor.primaryKey, 2) + break + + default: + assert_unreached("Unexpected count: " + count) + } + + count++; + }); + rq.onerror = t.unreached_func("unexpected error") + }, + document.title + " - continue" +); + +indexeddb_test( + upgrade_func, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").index("index").openCursor(); + + rq.onsuccess = t.step_func(function(e) { + if (!e.target.result) { + assert_equals(count, 2, 'count'); + t.done(); + return; + } + var cursor = e.target.result; + cursor.advance(1) + + switch(count) { + case 0: + assert_equals(cursor.value, "data") + assert_equals(cursor.key, "data") + assert_equals(cursor.primaryKey, 1) + break + + case 1: + assert_equals(cursor.value, "data2") + assert_equals(cursor.key, "data2") + assert_equals(cursor.primaryKey, 2) + break + + default: + assert_unreached("Unexpected count: " + count) + } + + count++; + }); + rq.onerror = t.unreached_func("unexpected error") + }, + document.title + " - fresh advance still async" +); + +indexeddb_test( + upgrade_func, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").openCursor(); + + rq.onsuccess = t.step_func(function(e) { + if (!e.target.result) { + assert_equals(count, 2, 'count'); + t.done(); + return; + } + var cursor = e.target.result; + cursor.continue() + + switch(count) { + case 0: + assert_equals(cursor.value, "data") + assert_equals(cursor.key, 1) + break + + case 1: + assert_equals(cursor.value, "data2") + assert_equals(cursor.key, 2) + break + + default: + assert_unreached("Unexpected count: " + count) + } + + count++; + }); + rq.onerror = t.unreached_func("unexpected error") + }, + document.title + " - fresh continue still async" +); +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor-advance-exception-order.html b/testing/web-platform/tests/IndexedDB/idbcursor-advance-exception-order.html new file mode 100644 index 0000000000..1dfe4ff55b --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor-advance-exception-order.html @@ -0,0 +1,91 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: IDBCursor advance() Exception Ordering</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbcursor-advance"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('s'); + store.put('value', 'key'); + }, + (t, db) => { + const tx = db.transaction('s', 'readonly', {durability: 'relaxed'}); + const store = tx.objectStore('s'); + + const r = store.openKeyCursor(); + r.onsuccess = t.step_func(() => { + r.onsuccess = null; + + const cursor = r.result; + + setTimeout(t.step_func(() => { + assert_throws_js(TypeError, () => { cursor.advance(0); }, + '"zero" check (TypeError) should precede ' + + '"not active" check (TransactionInactiveError)'); + t.done(); + }), 0); + }); + }, + 'IDBCursor.advance exception order: TypeError vs. TransactionInactiveError' +); + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('s'); + + const s = db.createObjectStore('s2'); + s.put('value', 'key'); + + const r = s.openKeyCursor(); + r.onsuccess = t.step_func(() => { + r.onsuccess = null; + + const cursor = r.result; + db.deleteObjectStore('s2'); + + setTimeout(t.step_func(() => { + assert_throws_dom('TransactionInactiveError', () => { cursor.advance(1); }, + '"not active" check (TransactionInactiveError) ' + + 'should precede "deleted" check (InvalidStateError)'); + t.done(); + }), 0); + }); + }, + (t, db) => {}, + 'IDBCursor.advance exception order: ' + + 'TransactionInactiveError vs. InvalidStateError #1' +); + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('s'); + store.put('value', 'key'); + }, + (t, db) => { + const tx = db.transaction('s', 'readonly', {durability: 'relaxed'}); + const store = tx.objectStore('s'); + + const r = store.openKeyCursor(); + r.onsuccess = t.step_func(() => { + r.onsuccess = null; + + const cursor = r.result; + cursor.advance(1); + + setTimeout(t.step_func(() => { + assert_throws_dom('TransactionInactiveError', () => { cursor.advance(1); }, + '"not active" check (TransactionInactiveError) ' + + 'should precede "got value" check (InvalidStateError)'); + t.done(); + }), 0); + }); + }, + 'IDBCursor.advance exception order: ' + + 'TransactionInactiveError vs. InvalidStateError #2' +); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor-advance-invalid.htm b/testing/web-platform/tests/IndexedDB/idbcursor-advance-invalid.htm new file mode 100644 index 0000000000..67c5e93df0 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor-advance-invalid.htm @@ -0,0 +1,192 @@ +<!DOCTYPE html> +<title>IDBCursor.advance() - invalid</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBCursor-advance-void-unsigned-long-count"> +<link rel=assert title="If the value for count is 0 (zero) or a negative number, this method must throw a JavaScript TypeError exception."> +<link rel=assert title="TypeError The value passed into the count parameter was zero or a negative number."> +<link rel=assert title="InvalidStateError The cursor is currently being iterated, or has iterated past its end."> +<link rel=assert title="Calling this method more than once before new cursor data has been loaded is not allowed and results in a DOMException of type InvalidStateError being thrown. For example, calling advance() twice from the same onsuccess handler results in a DOMException of type InvalidStateError being thrown on the second call."> +<link rel=assert title="Before this method returns, unless an exception was thrown, it sets the got value flag on the cursor to false."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + +function upgrade_func(t, db, tx) { + var objStore = db.createObjectStore("test"); + objStore.createIndex("index", ""); + + objStore.add("data", 1); + objStore.add("data2", 2); +} + +indexeddb_test( + upgrade_func, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").index("index").openCursor(); + + rq.onsuccess = t.step_func(function(e) { + if (!e.target.result) { + assert_equals(count, 2, 'count'); + t.done(); + return; + } + var cursor = e.target.result; + + cursor.advance(1); + + // Second try + assert_throws_dom('InvalidStateError', + function() { cursor.advance(1); }, 'second advance'); + + assert_throws_dom('InvalidStateError', + function() { cursor.advance(3); }, 'third advance'); + + count++; + }); + rq.onerror = t.unreached_func("unexpected error") + }, + document.title + " - attempt to call advance twice" +); + +indexeddb_test( + upgrade_func, + function(t, db) { + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").index("index").openCursor(); + + rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + assert_throws_js(TypeError, + function() { cursor.advance(document); }); + + assert_throws_js(TypeError, + function() { cursor.advance({}); }); + + assert_throws_js(TypeError, + function() { cursor.advance([]); }); + + assert_throws_js(TypeError, + function() { cursor.advance(""); }); + + assert_throws_js(TypeError, + function() { cursor.advance("1 2"); }); + + t.done(); + }); + rq.onerror = t.unreached_func("unexpected error") + }, + document.title + " - pass something other than number" +); + + +indexeddb_test( + upgrade_func, + function(t, db) { + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").index("index").openCursor(); + + rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + assert_throws_js(TypeError, + function() { cursor.advance(null); }); + + assert_throws_js(TypeError, + function() { cursor.advance(undefined); }); + + var myvar = null; + assert_throws_js(TypeError, + function() { cursor.advance(myvar); }); + + t.done(); + }); + rq.onerror = t.unreached_func("unexpected error") + }, + document.title + " - pass null/undefined" +); + + +indexeddb_test( + upgrade_func, + function(t, db) { + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").index("index").openCursor(); + + rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + assert_throws_js(TypeError, + function() { cursor.advance(); }); + + t.done(); + }); + rq.onerror = t.unreached_func("unexpected error") + }, + document.title + " - missing argument" +); + +indexeddb_test( + upgrade_func, + function(t, db) { + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").index("index").openCursor(); + + rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + assert_throws_js(TypeError, + function() { cursor.advance(-1); }); + + assert_throws_js(TypeError, + function() { cursor.advance(NaN); }); + + assert_throws_js(TypeError, + function() { cursor.advance(0); }); + + assert_throws_js(TypeError, + function() { cursor.advance(-0); }); + + assert_throws_js(TypeError, + function() { cursor.advance(Infinity); }); + + assert_throws_js(TypeError, + function() { cursor.advance(-Infinity); }); + + var myvar = -999999; + assert_throws_js(TypeError, + function() { cursor.advance(myvar); }); + + t.done(); + }); + rq.onerror = t.unreached_func("unexpected error") + }, + document.title + " - pass negative numbers" +); + +indexeddb_test( + upgrade_func, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").index("index").openCursor(); + + rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + if (!cursor) + { + assert_equals(count, 2, "count runs"); + t.done(); + return; + } + + assert_throws_js(TypeError, + function() { cursor.advance(0); }); + + cursor.advance(1); + count++; + }); + rq.onerror = t.unreached_func("unexpected error") + }, + document.title + " - got value not set on exception" +); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor-advance.htm b/testing/web-platform/tests/IndexedDB/idbcursor-advance.htm new file mode 100644 index 0000000000..624ed14f1f --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor-advance.htm @@ -0,0 +1,247 @@ +<!DOCTYPE html> +<title>IDBCursor.advance()</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + +function upgrade_func(t, db, tx) { + var objStore = db.createObjectStore("test"); + objStore.createIndex("index", ""); + + objStore.add("cupcake", 5); + objStore.add("pancake", 3); // Yes, it is intended + objStore.add("pie", 1); + objStore.add("pie", 4); + objStore.add("taco", 2); +} + +indexeddb_test( + upgrade_func, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").index("index").openCursor(); + + rq.onsuccess = t.step_func(function(e) { + if (!e.target.result) { + assert_equals(count, 3, "count"); + t.done(); + return; + } + var cursor = e.target.result; + + switch(count) { + case 0: + assert_equals(cursor.value, "cupcake"); + assert_equals(cursor.primaryKey, 5); + break; + + case 1: + assert_equals(cursor.value, "pie"); + assert_equals(cursor.primaryKey, 1); + break; + + case 2: + assert_equals(cursor.value, "taco"); + assert_equals(cursor.primaryKey, 2); + break; + + default: + assert_unreached("Unexpected count: " + count); + } + + count++; + cursor.advance(2); + }); + rq.onerror = t.unreached_func("unexpected error") + }, + document.title + " - advances" +); + +indexeddb_test( + upgrade_func, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").index("index").openCursor(null, "prev"); + + rq.onsuccess = t.step_func(function(e) { + if (!e.target.result) { + assert_equals(count, 3, "count"); + t.done(); + return; + } + var cursor = e.target.result; + + switch(count) { + case 0: + assert_equals(cursor.value, "taco"); + assert_equals(cursor.primaryKey, 2); + break; + + case 1: + assert_equals(cursor.value, "pie"); + assert_equals(cursor.primaryKey, 1); + break; + + case 2: + assert_equals(cursor.value, "cupcake"); + assert_equals(cursor.primaryKey, 5); + break; + + default: + assert_unreached("Unexpected count: " + count); + } + + count++; + cursor.advance(2); + }); + rq.onerror = t.unreached_func("unexpected error") + }, + document.title + " - advances backwards" +); + +indexeddb_test( + upgrade_func, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").index("index") + .openCursor(); + + rq.onsuccess = t.step_func(function(e) { + if (!e.target.result) { + assert_equals(count, 1, "count"); + t.done(); + return; + } + var cursor = e.target.result; + + switch(count) { + case 0: + assert_equals(cursor.value, "cupcake"); + assert_equals(cursor.primaryKey, 5); + break; + + default: + assert_unreached("Unexpected count: " + count); + } + + count++; + cursor.advance(100000); + }); + rq.onerror = t.unreached_func("unexpected error") + }, + document.title + " - skip far forward" +); + +indexeddb_test( + upgrade_func, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").index("index") + .openCursor(IDBKeyRange.lowerBound("cupcake", true)); + + rq.onsuccess = t.step_func(function(e) { + if (!e.target.result) { + assert_equals(count, 2, "count"); + t.done(); + return; + } + var cursor = e.target.result; + + switch(count) { + case 0: + assert_equals(cursor.value, "pancake"); + assert_equals(cursor.primaryKey, 3); + break; + + case 1: + assert_equals(cursor.value, "pie"); + assert_equals(cursor.primaryKey, 4); + break; + + default: + assert_unreached("Unexpected count: " + count); + } + + count++; + cursor.advance(2); + }); + rq.onerror = t.unreached_func("unexpected error") + }, + document.title + " - within range" +); + +indexeddb_test( + upgrade_func, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").index("index") + .openCursor("pancake"); + + rq.onsuccess = t.step_func(function(e) { + if (!e.target.result) { + assert_equals(count, 1, "count"); + t.done(); + return; + } + var cursor = e.target.result; + + switch(count) { + case 0: + assert_equals(cursor.value, "pancake"); + assert_equals(cursor.primaryKey, 3); + break; + + default: + assert_unreached("Unexpected count: " + count); + } + + count++; + cursor.advance(1); + }); + rq.onerror = t.unreached_func("unexpected error") + }, + document.title + " - within single key range" +); + +indexeddb_test( + upgrade_func, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").index("index") + .openCursor("pie"); + + rq.onsuccess = t.step_func(function(e) { + if (!e.target.result) { + assert_equals(count, 2, "count"); + t.done(); + return; + } + var cursor = e.target.result; + + switch(count) { + case 0: + assert_equals(cursor.value, "pie"); + assert_equals(cursor.primaryKey, 1); + break; + + case 1: + assert_equals(cursor.value, "pie"); + assert_equals(cursor.primaryKey, 4); + break; + + default: + assert_unreached("Unexpected count: " + count); + } + + count++; + cursor.advance(1); + }); + rq.onerror = t.unreached_func("unexpected error") + }, + document.title + " - within single key range, with several results" +); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor-continue-exception-order.htm b/testing/web-platform/tests/IndexedDB/idbcursor-continue-exception-order.htm new file mode 100644 index 0000000000..29c874fdb6 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor-continue-exception-order.htm @@ -0,0 +1,81 @@ +<!DOCTYPE html> +<title>IndexedDB: IDBCursor continue() Exception Ordering</title> +<meta charset=utf-8> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbcursor-continue"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + (t, db) => { + const s = db.createObjectStore('s'); + s.put('value', 'key'); + }, + (t, db) => { + const s = db.transaction('s', 'readonly', {durability: 'relaxed'}).objectStore('s'); + const r = s.openKeyCursor(); + r.onsuccess = t.step_func(() => { + r.onsuccess = null; + const cursor = r.result; + setTimeout(t.step_func(() => { + assert_throws_dom('TransactionInactiveError', () => { + cursor.continue({not: "a valid key"}); + }, '"Transaction inactive" check (TransactionInactiveError) ' + + 'should precede "invalid key" check (DataError)'); + t.done(); + }), 0); + }); + }, + 'IDBCursor.continue exception order: TransactionInactiveError vs. DataError' +); + +indexeddb_test( + (t, db) => { + const s = db.createObjectStore('s'); + s.put('value', 'key'); + }, + (t, db) => { + const s = db.transaction('s', 'readonly', {durability: 'relaxed'}).objectStore('s'); + const r = s.openKeyCursor(); + r.onsuccess = t.step_func(() => { + r.onsuccess = null; + const cursor = r.result; + cursor.continue(); + r.onsuccess = t.step_func(() => { + setTimeout(t.step_func(() => { + assert_throws_dom('TransactionInactiveError', () => { + cursor.continue(); + }, '"Transaction inactive" check (TransactionInactiveError) ' + + 'should precede "got value flag" check (InvalidStateError)'); + t.done(); + }), 0); + }); + }); + }, + 'IDBCursor.continue exception order: TransactionInactiveError vs. InvalidStateError' +); + +indexeddb_test( + (t, db) => { + const s = db.createObjectStore('s'); + s.put('value', 'key'); + }, + (t, db) => { + const s = db.transaction('s', 'readonly', {durability: 'relaxed'}).objectStore('s'); + const r = s.openKeyCursor(); + r.onsuccess = t.step_func(() => { + r.onsuccess = null; + const cursor = r.result; + cursor.continue(); + assert_throws_dom('InvalidStateError', () => { + cursor.continue({not: "a valid key"}); + }, '"got value flag" check (InvalidStateError) should precede ' + + '"invalid key" check (DataError)'); + t.done(); + }); + }, + 'IDBCursor.continue exception order: InvalidStateError vs. DataError' +); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor-continue.htm b/testing/web-platform/tests/IndexedDB/idbcursor-continue.htm new file mode 100644 index 0000000000..db98c2261b --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor-continue.htm @@ -0,0 +1,248 @@ +<!DOCTYPE html> +<title>IDBCursor.continue()</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBCursor-continue-void-any-key"> +<link rel=assert title="The next key to position this cursor at"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + +var store = [ { value: "cupcake", key: 5 }, + { value: "pancake", key: 3 }, + { value: "pie", key: 1 }, + { value: "pie", key: 4 }, + { value: "taco", key: 2 } ]; + +function upgrade_func(t, db, tx) { + var db, open; + + var os, i; + os = db.createObjectStore("test"); + os.createIndex("index", ""); + + for (i = 0; i < store.length; i++) + os.add(store[i].value, store[i].key); +} + +indexeddb_test( + upgrade_func, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").index("index").openCursor(); + + rq.onsuccess = t.step_func(function(e) { + if (!e.target.result) { + assert_equals(count, 5, 'count'); + t.done(); + return; + } + var cursor = e.target.result; + + assert_equals(cursor.value, store[count].value); + assert_equals(cursor.primaryKey, store[count].key); + + cursor.continue(); + + count++; + }); + rq.onerror = t.unreached_func("unexpected error") + }, + document.title + " - continues" +); + + +indexeddb_test( + upgrade_func, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").index("index").openCursor(); + + rq.onsuccess = t.step_func(function(e) { + if (!e.target.result) { + assert_equals(count, 3, 'count'); + t.done(); + return; + } + var cursor = e.target.result; + + switch(count) { + case 0: + assert_equals(cursor.value, "cupcake"); + assert_equals(cursor.primaryKey, 5); + cursor.continue("pie"); + break; + + case 1: + assert_equals(cursor.value, "pie"); + assert_equals(cursor.primaryKey, 1); + cursor.continue("taco"); + break; + + case 2: + assert_equals(cursor.value, "taco"); + assert_equals(cursor.primaryKey, 2); + cursor.continue(); + break; + + default: + assert_unreached("Unexpected count: " + count); + } + + count++; + }); + rq.onerror = t.unreached_func("unexpected error") + }, + document.title + " - with given key" +); + + +indexeddb_test( + upgrade_func, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").index("index") + .openCursor(); + + rq.onsuccess = t.step_func(function(e) { + if (!e.target.result) { + assert_equals(count, 1, 'count'); + t.done(); + return; + } + var cursor = e.target.result; + + switch(count) { + case 0: + assert_equals(cursor.value, "cupcake"); + assert_equals(cursor.primaryKey, 5); + break; + + default: + assert_unreached("Unexpected count: " + count); + } + + count++; + cursor.continue([]); // Arrays are always bigger than strings + + }); + rq.onerror = t.unreached_func("unexpected error2") + }, + document.title + " - skip far forward" +); + + +indexeddb_test( + upgrade_func, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").index("index") + .openCursor(IDBKeyRange.lowerBound("cupcake", true)); + + rq.onsuccess = t.step_func(function(e) { + if (!e.target.result) { + assert_equals(count, 2, 'count'); + t.done(); + return; + } + var cursor = e.target.result; + + switch(count) { + case 0: + assert_equals(cursor.value, "pancake"); + assert_equals(cursor.primaryKey, 3); + cursor.continue("pie"); + break; + + case 1: + assert_equals(cursor.value, "pie"); + assert_equals(cursor.primaryKey, 1); + cursor.continue("zzz"); + break; + + default: + assert_unreached("Unexpected count: " + count); + } + + count++; + }); + rq.onerror = t.unreached_func("unexpected error1") + }, + document.title + " - within range" +); + + +indexeddb_test( + upgrade_func, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").index("index") + .openCursor("pancake"); + + rq.onsuccess = t.step_func(function(e) { + if (!e.target.result) { + assert_equals(count, 1, 'count'); + t.done(); + return; + } + var cursor = e.target.result; + + switch(count) { + case 0: + assert_equals(cursor.value, "pancake"); + assert_equals(cursor.primaryKey, 3); + cursor.continue("pie"); + break; + + default: + assert_unreached("Unexpected count: " + count); + } + + count++; + }); + rq.onerror = t.unreached_func("unexpected error1") + }, + document.title + " - within single key range" +); + +indexeddb_test( + upgrade_func, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").index("index") + .openCursor("pie"); + + rq.onsuccess = t.step_func(function(e) { + if (!e.target.result) { + assert_equals(count, 2, 'count'); + t.done(); + return; + } + var cursor = e.target.result; + + switch(count) { + case 0: + assert_equals(cursor.value, "pie"); + assert_equals(cursor.primaryKey, 1); + cursor.continue(); + break; + + case 1: + assert_equals(cursor.value, "pie"); + assert_equals(cursor.primaryKey, 4); + cursor.continue(); + break; + + default: + assert_unreached("Unexpected count: " + count); + } + + count++; + }); + rq.onerror = t.unreached_func("unexpected error1") + }, + document.title + " - within single key range, with several results" +); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor-continuePrimaryKey-exception-order.htm b/testing/web-platform/tests/IndexedDB/idbcursor-continuePrimaryKey-exception-order.htm new file mode 100644 index 0000000000..477dabc60e --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor-continuePrimaryKey-exception-order.htm @@ -0,0 +1,380 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<meta name="timeout" content="long"> +<title>IDBCursor.continuePrimaryKey() - Exception Orders </title> +<link rel="author" title="Mozilla" href="https://www.mozilla.org"> +<link rel="help" href="http://w3c.github.io/IndexedDB/#dom-idbcursor-continueprimarykey"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> +function setup_test_store(db) { + var records = [ { iKey: "A", pKey: 1 }, + { iKey: "A", pKey: 2 }, + { iKey: "A", pKey: 3 }, + { iKey: "A", pKey: 4 }, + { iKey: "B", pKey: 5 }, + { iKey: "B", pKey: 6 }, + { iKey: "B", pKey: 7 }, + { iKey: "C", pKey: 8 }, + { iKey: "C", pKey: 9 }, + { iKey: "D", pKey: 10 } ]; + + var store = db.createObjectStore("test", { keyPath: "pKey" }); + var index = store.createIndex("idx", "iKey"); + + for(var i = 0; i < records.length; i++) { + store.add(records[i]); + } + + return store; +} + +indexeddb_test( + function(t, db, txn) { + var store = setup_test_store(db); + var index = store.index("idx"); + var cursor_rq = index.openCursor(); + var cursor; + + cursor_rq.onerror = t.unreached_func('openCursor should succeed'); + cursor_rq.onsuccess = t.step_func(function(e) { + cursor = e.target.result; + assert_true(!!cursor, "acquire cursor"); + + store.deleteIndex("idx"); + }); + txn.oncomplete = t.step_func(function() { + assert_throws_dom("TransactionInactiveError", function() { + cursor.continuePrimaryKey("A", 4); + }, "transaction-state check should precede deletion check"); + t.done(); + }); + }, + null, + "TransactionInactiveError v.s. InvalidStateError(deleted index)" +); + +indexeddb_test( + function(t, db, txn) { + var store = setup_test_store(db); + var cursor_rq = store.openCursor(); + var cursor; + + cursor_rq.onerror = t.unreached_func('openCursor should succeed'); + cursor_rq.onsuccess = t.step_func(function(e) { + cursor = e.target.result; + assert_true(!!cursor, "acquire cursor"); + + db.deleteObjectStore("test"); + + assert_throws_dom("InvalidStateError", function() { + cursor.continuePrimaryKey("A", 4); + }, "deletion check should precede index source check"); + t.done(); + }); + }, + null, + "InvalidStateError(deleted source) v.s. InvalidAccessError(incorrect source)" +); + +indexeddb_test( + function(t, db, txn) { + var store = setup_test_store(db); + var index = store.index("idx"); + var cursor_rq = index.openCursor(null, "nextunique"); + var cursor; + + cursor_rq.onerror = t.unreached_func('openCursor should succeed'); + cursor_rq.onsuccess = t.step_func(function(e) { + cursor = e.target.result; + assert_true(!!cursor, "acquire cursor"); + + store.deleteIndex("idx"); + + assert_throws_dom("InvalidStateError", function() { + cursor.continuePrimaryKey("A", 4); + }, "deletion check should precede cursor direction check"); + t.done(); + }); + }, + null, + "InvalidStateError(deleted source) v.s. InvalidAccessError(incorrect direction)" +); + +indexeddb_test( + function(t, db, txn) { + var store = db.createObjectStore("test", {keyPath:"pKey"}); + var index = store.createIndex("idx", "iKey"); + + store.add({ iKey: "A", pKey: 1 }); + + var cursor_rq = index.openCursor(null, "nextunique"); + var cursor; + + cursor_rq.onerror = t.unreached_func('openCursor should succeed'); + cursor_rq.onsuccess = t.step_func(function(e) { + if (e.target.result) { + cursor = e.target.result; + cursor.continue(); + return; + } + + assert_throws_dom("InvalidAccessError", function() { + cursor.continuePrimaryKey("A", 4); + }, "direction check should precede got_value_flag check"); + t.done(); + }); + }, + null, + "InvalidAccessError(incorrect direction) v.s. InvalidStateError(iteration complete)" +); + +indexeddb_test( + function(t, db, txn) { + var store = db.createObjectStore("test", {keyPath:"pKey"}); + var index = store.createIndex("idx", "iKey"); + + store.add({ iKey: "A", pKey: 1 }); + + var cursor_rq = index.openCursor(null, "nextunique"); + var cursor; + + cursor_rq.onerror = t.unreached_func('openCursor should succeed'); + cursor_rq.onsuccess = t.step_func(function(e) { + if (!cursor) { + cursor = e.target.result; + assert_true(!!cursor, "acquire cursor"); + + cursor.continue(); + + assert_throws_dom("InvalidAccessError", function() { + cursor.continuePrimaryKey("A", 4); + }, "direction check should precede iteration ongoing check"); + t.done(); + } + }); + }, + null, + "InvalidAccessError(incorrect direction) v.s. InvalidStateError(iteration ongoing)" +); + +indexeddb_test( + function(t, db, txn) { + var store = setup_test_store(db); + var cursor_rq = store.openCursor(); + var cursor; + + cursor_rq.onerror = t.unreached_func('openCursor should succeed'); + cursor_rq.onsuccess = t.step_func(function(e) { + if (!cursor) { + cursor = e.target.result; + assert_true(!!cursor, "acquire cursor"); + + cursor.continue(); + + assert_throws_dom("InvalidAccessError", function() { + cursor.continuePrimaryKey("A", 4); + }, "index source check should precede iteration ongoing check"); + t.done(); + } + }); + }, + null, + "InvalidAccessError(incorrect source) v.s. InvalidStateError(iteration ongoing)" +); + +indexeddb_test( + function(t, db, txn) { + var store = db.createObjectStore("test", {keyPath:"pKey"}); + + store.add({ iKey: "A", pKey: 1 }); + + var cursor_rq = store.openCursor(); + var cursor; + + cursor_rq.onerror = t.unreached_func('openCursor should succeed'); + cursor_rq.onsuccess = t.step_func(function(e) { + if (e.target.result) { + cursor = e.target.result; + cursor.continue(); + return; + } + + assert_throws_dom("InvalidAccessError", function() { + cursor.continuePrimaryKey("A", 4); + }, "index source check should precede got_value_flag check"); + t.done(); + }); + }, + null, + "InvalidAccessError(incorrect source) v.s. InvalidStateError(iteration complete)" +); + +indexeddb_test( + function(t, db, txn) { + var store = setup_test_store(db); + var index = store.index("idx"); + var cursor_rq = index.openCursor(); + var cursor; + + cursor_rq.onerror = t.unreached_func('openCursor should succeed'); + cursor_rq.onsuccess = t.step_func(function(e) { + if (!cursor) { + cursor = e.target.result; + assert_true(!!cursor, "acquire cursor"); + + cursor.continue(); + + assert_throws_dom("InvalidStateError", function() { + cursor.continuePrimaryKey(null, 4); + }, "iteration ongoing check should precede unset key check"); + t.done(); + } + }); + }, + null, + "InvalidStateError(iteration ongoing) v.s. DataError(unset key)" +); + +indexeddb_test( + function(t, db, txn) { + var store = db.createObjectStore("test", {keyPath:"pKey"}); + var index = store.createIndex("idx", "iKey"); + + store.add({ iKey: "A", pKey: 1 }); + + var cursor_rq = index.openCursor(); + var cursor; + + cursor_rq.onerror = t.unreached_func('openCursor should succeed'); + cursor_rq.onsuccess = t.step_func(function(e) { + if (e.target.result) { + cursor = e.target.result; + cursor.continue(); + return; + } + + assert_throws_dom("InvalidStateError", function() { + cursor.continuePrimaryKey(null, 4); + }, "got_value_flag check should precede unset key check"); + t.done(); + }); + }, + null, + "InvalidStateError(iteration complete) v.s. DataError(unset key)" +); + +indexeddb_test( + function(t, db, txn) { + var store = setup_test_store(db); + var index = store.index("idx"); + var cursor_rq = index.openCursor(); + var cursor; + + cursor_rq.onerror = t.unreached_func('openCursor should succeed'); + cursor_rq.onsuccess = t.step_func(function(e) { + cursor = e.target.result; + assert_true(!!cursor, "acquire cursor"); + + assert_throws_dom("DataError", function() { + cursor.continuePrimaryKey(null, 4); + }, "DataError is expected if key is unset."); + t.done(); + }); + }, + null, + "DataError(unset key)" +); + +indexeddb_test( + function(t, db, txn) { + var store = setup_test_store(db); + var index = store.index("idx"); + var cursor_rq = index.openCursor(); + var cursor; + + cursor_rq.onerror = t.unreached_func('openCursor should succeed'); + cursor_rq.onsuccess = t.step_func(function(e) { + cursor = e.target.result; + assert_true(!!cursor, "acquire cursor"); + + assert_throws_dom("DataError", function() { + cursor.continuePrimaryKey("A", null); + }, "DataError is expected if primary key is unset."); + t.done(); + }); + }, + null, + "DataError(unset primary key)" +); + +indexeddb_test( + function(t, db, txn) { + var store = setup_test_store(db); + var index = store.index("idx"); + var cursor_rq = index.openCursor(IDBKeyRange.lowerBound("B")); + var cursor; + + cursor_rq.onerror = t.unreached_func('openCursor should succeed'); + cursor_rq.onsuccess = t.step_func(function(e) { + cursor = e.target.result; + assert_true(!!cursor, "acquire cursor"); + + assert_equals(cursor.key, "B", "expected key"); + assert_equals(cursor.primaryKey, 5, "expected primary key"); + + assert_throws_dom("DataError", function() { + cursor.continuePrimaryKey("A", 6); + }, "DataError is expected if key is lower then current one."); + + assert_throws_dom("DataError", function() { + cursor.continuePrimaryKey("B", 5); + }, "DataError is expected if primary key is equal to current one."); + + assert_throws_dom("DataError", function() { + cursor.continuePrimaryKey("B", 4); + }, "DataError is expected if primary key is lower than current one."); + + t.done(); + }); + }, + null, + "DataError(keys are lower then current one) in 'next' direction" +); + +indexeddb_test( + function(t, db, txn) { + var store = setup_test_store(db); + var index = store.index("idx"); + var cursor_rq = index.openCursor(IDBKeyRange.upperBound("B"), "prev"); + var cursor; + + cursor_rq.onerror = t.unreached_func('openCursor should succeed'); + cursor_rq.onsuccess = t.step_func(function(e) { + cursor = e.target.result; + assert_true(!!cursor, "acquire cursor"); + + assert_equals(cursor.key, "B", "expected key"); + assert_equals(cursor.primaryKey, 7, "expected primary key"); + + assert_throws_dom("DataError", function() { + cursor.continuePrimaryKey("C", 6); + }, "DataError is expected if key is larger then current one."); + + assert_throws_dom("DataError", function() { + cursor.continuePrimaryKey("B", 7); + }, "DataError is expected if primary key is equal to current one."); + + assert_throws_dom("DataError", function() { + cursor.continuePrimaryKey("B", 8); + }, "DataError is expected if primary key is larger than current one."); + + t.done(); + }); + }, + null, + "DataError(keys are larger then current one) in 'prev' direction" +); +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor-continuePrimaryKey-exceptions.htm b/testing/web-platform/tests/IndexedDB/idbcursor-continuePrimaryKey-exceptions.htm new file mode 100644 index 0000000000..862c9c951b --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor-continuePrimaryKey-exceptions.htm @@ -0,0 +1,107 @@ +<!doctype html> +<meta charset="utf-8" /> +<title>IndexedDB: IDBCursor continuePrimaryKey() exception throwing</title> +<link rel="help" + href="https://w3c.github.io/IndexedDB/#dom-idbcursor-continueprimarykey" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + +async_test(function(t) { + var dbname = document.location + '-' + t.name; + var del = indexedDB.deleteDatabase(dbname); + del.onerror = t.unreached_func('deleteDatabase should succeed'); + var open = indexedDB.open(dbname); + open.onerror = t.unreached_func('open should succeed'); + + open.onupgradeneeded = t.step_func(function() { + var db = open.result; + t.add_cleanup(function() { + db.close(); + indexedDB.deleteDatabase(db.name); + }); + var store = db.createObjectStore('store'); + store.put('a', 1).onerror = t.unreached_func('put should not fail'); + var request = store.openCursor(); + request.onerror = t.unreached_func('openCursor should not fail'); + request.onsuccess = t.step_func(function() { + var cursor = request.result; + assert_class_string(cursor, 'IDBCursorWithValue', + 'result should be a cursor'); + + assert_throws_dom('InvalidAccessError', function() { + cursor.continuePrimaryKey(2, 2); + }, 'continuePrimaryKey() should throw if source is not an index'); + }); + }); + + open.onsuccess = t.step_func(function() { + var db = open.result; + db.close(); + t.done(); + }); + +}, 'IDBCursor continuePrimaryKey() on object store cursor'); + +[ + { + direction: 'nextunique', + expected_key: 1, expected_primaryKey: 'a', + continue_key: 2, continue_primaryKey: 'a' + }, + { + direction: 'prevunique', + expected_key: 3, expected_primaryKey: 'a', + continue_key: 2, continue_primaryKey: 'a' + } +].forEach(function(testcase) { + async_test(function(t) { + var dbname = document.location + '-' + t.name; + var del = indexedDB.deleteDatabase(dbname); + del.onerror = t.unreached_func('deleteDatabase should succeed'); + var open = indexedDB.open(dbname); + open.onerror = t.unreached_func('open should succeed'); + + open.onupgradeneeded = t.step_func(function() { + var db = open.result; + t.add_cleanup(function() { + db.close(); + indexedDB.deleteDatabase(db.name); + }); + var store = db.createObjectStore('store', {keyPath: 'pk'}); + var index = store.createIndex('index', 'ik', {multiEntry: true}); + store.put({pk: 'a', ik: [1,2,3]}).onerror = + t.unreached_func('put should not fail'); + store.put({pk: 'b', ik: [1,2,3]}).onerror = + t.unreached_func('put should not fail'); + var request = index.openKeyCursor(null, testcase.direction); + request.onerror = t.unreached_func('openCursor should not fail'); + request.onsuccess = t.step_func(function() { + var cursor = request.result; + assert_class_string(cursor, 'IDBCursor', + 'result should be a cursor'); + assert_equals(cursor.direction, testcase.direction, + 'direction should be as specified'); + assert_equals(cursor.key, testcase.expected_key, + 'key should match'); + assert_equals(cursor.primaryKey, testcase.expected_primaryKey, + 'primaryKey should match'); + + assert_throws_dom('InvalidAccessError', function() { + cursor.continuePrimaryKey( + testcase.continue_key, + testcase.continue_primaryKey); + }, 'continuePrimaryKey() should throw if direction is unique'); + }); + }); + + open.onsuccess = t.step_func(function() { + var db = open.result; + db.close(); + t.done(); + }); + + }, 'IDBCursor continuePrimaryKey() on "' + testcase.direction + '" cursor'); +}); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor-continuePrimaryKey.htm b/testing/web-platform/tests/IndexedDB/idbcursor-continuePrimaryKey.htm new file mode 100644 index 0000000000..0de88c0aef --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor-continuePrimaryKey.htm @@ -0,0 +1,134 @@ +<!doctype html> +<meta charset="utf8"> +<title>IndexedDB: IDBCursor method continuePrimaryKey()</title> +<link rel="help" + href="http://w3c.github.io/IndexedDB/#dom-idbcursor-continueprimarykey"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> +'use strict'; + +indexeddb_test( + (t, db, txn) => { + const store = db.createObjectStore('store'); + const index = store.createIndex('index', 'indexKey', {multiEntry: true}); + + store.put({indexKey: ['a', 'b']}, 1); + store.put({indexKey: ['a', 'b']}, 2); + store.put({indexKey: ['a', 'b']}, 3); + store.put({indexKey: ['b']}, 4); + + const expectedIndexEntries = [ + {key: "a", primaryKey: 1}, + {key: "a", primaryKey: 2}, + {key: "a", primaryKey: 3}, + {key: "b", primaryKey: 1}, + {key: "b", primaryKey: 2}, + {key: "b", primaryKey: 3}, + {key: "b", primaryKey: 4}, + ]; + + const request = index.openCursor(); + request.onerror = t.unreached_func('IDBIndex.openCursor should not fail'); + request.onsuccess = t.step_func(() => { + const cursor = request.result; + const expectedEntry = expectedIndexEntries.shift(); + if (expectedEntry) { + assert_equals(cursor.key, expectedEntry.key, + 'The index entry keys should reflect the object store contents'); + assert_equals(cursor.primaryKey, expectedEntry.primaryKey, + 'The index entry primary keys should reflect the object store ' + + 'contents'); + cursor.continue(); + } else { + assert_equals(cursor, null, + 'The index should not have entries that do not reflect the ' + + 'object store contents'); + } + }); + }, + (t, db) => { + const testCases = [ + // Continuing index key + { call: cursor => { cursor.continue(); }, + result: { key: "a", primaryKey: 2 } }, + { call: cursor => { cursor.continue('a'); }, + exception: 'DataError' }, + { call: cursor => { cursor.continue('b'); }, + result: { key: "b", primaryKey: 1 } }, + { call: cursor => { cursor.continue('c'); }, result: null }, + + // Called w/ index key and primary key: + { call: cursor => { cursor.continuePrimaryKey('a', 3); }, + result: { key: 'a', primaryKey: 3 } }, + { call: cursor => { cursor.continuePrimaryKey('a', 4); }, + result: { key: 'b', primaryKey: 1 } }, + { call: cursor => { cursor.continuePrimaryKey('b', 1); }, + result: {key: 'b', primaryKey: 1} }, + { call: cursor => { cursor.continuePrimaryKey('b', 4); }, + result: {key: 'b', primaryKey: 4} }, + { call: cursor => { cursor.continuePrimaryKey('b', 5); }, + result: null }, + { call: cursor => { cursor.continuePrimaryKey('c', 1); }, + result: null }, + + // Called w/ primary key but w/o index key + { call: cursor => { cursor.continuePrimaryKey(null, 1); }, + exception: 'DataError' }, + { call: cursor => { cursor.continuePrimaryKey(null, 2); }, + exception: 'DataError' }, + { call: cursor => { cursor.continuePrimaryKey(null, 3); }, + exception: 'DataError' }, + { call: cursor => { cursor.continuePrimaryKey(null, 4); }, + exception: 'DataError' }, + { call: cursor => { cursor.continuePrimaryKey(null, 5); }, + exception: 'DataError' }, + + // Called w/ index key but w/o primary key + { call: cursor => { cursor.continuePrimaryKey('a', null); }, + exception: 'DataError' }, + ]; + + const verifyContinueCalls = () => { + if (!testCases.length) { + t.done(); + return; + } + + const testCase = testCases.shift(); + + const txn = db.transaction('store', 'readonly', {durability: 'relaxed'}); + txn.oncomplete = t.step_func(verifyContinueCalls); + + const request = txn.objectStore('store').index('index').openCursor(); + let calledContinue = false; + request.onerror = + t.unreached_func('IDBIndex.openCursor should not fail'); + request.onsuccess = t.step_func(() => { + const cursor = request.result; + if (calledContinue) { + if (testCase.result) { + assert_equals(cursor.key, testCase.result.key, + `${testCase.call.toString()} - result key`); + assert_equals(cursor.primaryKey, testCase.result.primaryKey, + `${testCase.call.toString()} - result primary key`); + } else { + assert_equals(cursor, null); + } + } else { + calledContinue = true; + if('exception' in testCase) { + assert_throws_dom( + testCase.exception, () => { testCase.call(cursor); }, + testCase.call.toString()); + } else { + testCase.call(cursor); + } + } + }); + }; + verifyContinueCalls(); + }); +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor-delete-exception-order.htm b/testing/web-platform/tests/IndexedDB/idbcursor-delete-exception-order.htm new file mode 100644 index 0000000000..b84ca11ed4 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor-delete-exception-order.htm @@ -0,0 +1,76 @@ +<!DOCTYPE html> +<title>IndexedDB: IDBCursor delete() Exception Ordering</title> +<meta charset=utf-8> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbcursor-delete"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + (t, db) => { + const s = db.createObjectStore('s'); + s.put('value', 'key'); + }, + (t, db) => { + const s = db.transaction('s', 'readonly', {durability: 'relaxed'}).objectStore('s'); + const r = s.openCursor(); + r.onsuccess = t.step_func(() => { + r.onsuccess = null; + const cursor = r.result; + setTimeout(t.step_func(() => { + assert_throws_dom('TransactionInactiveError', () => { + cursor.delete(); + }, '"Transaction inactive" check (TransactionInactivError) ' + + 'should precede "read only" check (ReadOnlyError)'); + t.done(); + }), 0); + }); + }, + 'IDBCursor.delete exception order: TransactionInactiveError vs. ReadOnlyError' +); + +indexeddb_test( + (t, db) => { + const s = db.createObjectStore('s'); + s.put('value', 'key'); + }, + (t, db) => { + const s = db.transaction('s', 'readonly', {durability: 'relaxed'}).objectStore('s'); + const r = s.openCursor(); + r.onsuccess = t.step_func(() => { + r.onsuccess = null; + const cursor = r.result; + cursor.continue(); + assert_throws_dom('ReadOnlyError', () => { + cursor.delete(); + }, '"Read only" check (ReadOnlyError) should precede ' + + '"got value flag" (InvalidStateError) check'); + t.done(); + }); + }, + 'IDBCursor.delete exception order: ReadOnlyError vs. InvalidStateError #1' +); + +indexeddb_test( + (t, db) => { + const s = db.createObjectStore('s'); + s.put('value', 'key'); + }, + (t, db) => { + const s = db.transaction('s', 'readonly', {durability: 'relaxed'}).objectStore('s'); + const r = s.openKeyCursor(); + r.onsuccess = t.step_func(() => { + r.onsuccess = null; + const cursor = r.result; + assert_throws_dom('ReadOnlyError', () => { + cursor.delete(); + }, '"Read only" check (ReadOnlyError) should precede ' + + '"key only flag" (InvalidStateError) check'); + t.done(); + }); + }, + 'IDBCursor.delete exception order: ReadOnlyError vs. InvalidStateError #2' +); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor-direction-index-keyrange.htm b/testing/web-platform/tests/IndexedDB/idbcursor-direction-index-keyrange.htm new file mode 100644 index 0000000000..2b3ff34318 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor-direction-index-keyrange.htm @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBCursor direction - index with keyrange</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#cursor-iteration-operation"> +<link rel=assert title='If direction is "next", let found record be the first record in records which satisfy all of the following requirements'> +<link rel=assert title="If position is defined, and source is an index, the record's key is equal to position and the record's value is greater than object store position or the record's key is greater than position."> +<link rel=assert title='If direction is "prev", let found record be the last record in records which satisfy all of the following requirements'> +<link rel=assert title="If position is defined, and source is an index, the record's key is equal to position and the record's value is less than object store position or the record's key is less than position."> +<link rel=assert title="If range is defined, the record's key is in range."> +<link rel=assert title="If temp record is defined, let found record be the first record in records whose key is equal to temp record's key."> +<link rel=assert title="records is always sorted in ascending key order. In the case of source being an index, records is secondarily sorted in ascending value order."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> +var records = [ 1337, "Alice", "Bob", "Bob", "Greg", "Åke", ["Anne"] ]; +var cases = [ + {dir: 'next', expect: ['Alice:1', 'Bob:2', 'Bob:3', 'Greg:4']}, + {dir: 'prev', expect: ['Greg:4', 'Bob:3', 'Bob:2', 'Alice:1']}, + {dir: 'nextunique', expect: ['Alice:1', 'Bob:2', 'Greg:4']}, + {dir: 'prevunique', expect: ['Greg:4', 'Bob:2', 'Alice:1']} +]; + + +cases.forEach(function(testcase) { + var dir = testcase.dir; + var expect = testcase.expect; + indexeddb_test( + function(t, db, tx) { + var objStore = db.createObjectStore("test"); + objStore.createIndex("idx", "name"); + + for (var i = 0; i < records.length; i++) + objStore.add({ name: records[i] }, i); + }, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").index("idx").openCursor(IDBKeyRange.bound("AA", "ZZ"), dir); + rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + if (!cursor) { + assert_equals(count, expect.length, "cursor runs"); + t.done(); + } + assert_equals(cursor.value.name + ":" + cursor.primaryKey, expect[count], "cursor.value"); + count++; + cursor.continue(); + }); + rq.onerror = t.step_func(function(e) { + e.preventDefault(); + e.stopPropagation(); + assert_unreached("rq.onerror - " + e.message); + }); + }, + document.title + ' - ' + dir + ) +}); +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor-direction-index.htm b/testing/web-platform/tests/IndexedDB/idbcursor-direction-index.htm new file mode 100644 index 0000000000..be22337884 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor-direction-index.htm @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<title>IDBCursor direction - index</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#cursor-iteration-operation"> +<link rel=assert title='If direction is "next", let found record be the first record in records which satisfy all of the following requirements'> +<link rel=assert title="If position is defined, and source is an object store, the record's key is greater than position."> +<link rel=assert title='If direction is "prev", let found record be the last record in records which satisfy all of the following requirements'> +<link rel=assert title="If position is defined, and source is an object store, the record's key is less than position."> +<link rel=assert title="Set cursor's position to found record's key. If source is an index, set cursor's object store position to found record's value."> +<link rel=assert title="Set cursor's key to found record's key."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> +var records = [ "Alice", "Bob", "Bob", "Greg" ]; +var cases = [ + {dir: 'next', expect: ['Alice:0', 'Bob:1', 'Bob:2', 'Greg:3']}, + {dir: 'prev', expect: ['Greg:3', 'Bob:2', 'Bob:1', 'Alice:0']}, + {dir: 'nextunique', expect: ['Alice:0', 'Bob:1', 'Greg:3']}, + {dir: 'prevunique', expect: ['Greg:3', 'Bob:1', 'Alice:0']}, +]; + +cases.forEach(function(testcase) { + var dir = testcase.dir; + var expect = testcase.expect; + indexeddb_test( + function(t, db, tx) { + var objStore = db.createObjectStore("test"); + objStore.createIndex("idx", "name"); + + for (var i = 0; i < records.length; i++) + objStore.add({ name: records[i] }, i); + }, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").index("idx").openCursor(undefined, dir); + rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + if (!cursor) { + assert_equals(count, expect.length, "cursor runs"); + t.done(); + } + assert_equals(cursor.value.name + ":" + cursor.primaryKey, expect[count], "cursor.value"); + count++; + cursor.continue(); + }); + rq.onerror = t.step_func(function(e) { + e.preventDefault(); + e.stopPropagation(); + assert_unreached("rq.onerror - " + e.message); + }); + }, + document.title + ' - ' + dir + ); +}); +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor-direction-objectstore-keyrange.htm b/testing/web-platform/tests/IndexedDB/idbcursor-direction-objectstore-keyrange.htm new file mode 100644 index 0000000000..b458ef0cbf --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor-direction-objectstore-keyrange.htm @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBCursor direction - object store with keyrange</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#cursor-iteration-operation"> +<link rel=assert title='If direction is "next", let found record be the first record in records which satisfy all of the following requirements'> +<link rel=assert title='If direction is "prev", let found record be the last record in records which satisfy all of the following requirements'> +<link rel=assert title="If range is defined, the record's key is in range."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> +var records = [ 1337, "Alice", "Bob", "Greg", "Åke", ["Anne"] ]; +var directions = ["next", "prev", "nextunique", "prevunique"]; +var cases = [ + {dir: 'next', expect: ['Alice', 'Bob', 'Greg']}, + {dir: 'prev', expect: ['Greg', 'Bob', 'Alice']}, + {dir: 'nextunique', expect: ['Alice', 'Bob', 'Greg']}, + {dir: 'prevunique', expect: ['Greg', 'Bob', 'Alice']}, +]; + +cases.forEach(function(testcase) { + var dir = testcase.dir; + var expect = testcase.expect; + indexeddb_test( + function(t, db, tx) { + var objStore = db.createObjectStore("test"); + for (var i = 0; i < records.length; i++) + objStore.add(records[i], records[i]); + }, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").openCursor(IDBKeyRange.bound("AA", "ZZ"), dir); + rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + if (!cursor) { + assert_equals(count, expect.length, "cursor runs"); + t.done(); + } + assert_equals(cursor.value, expect[count], "cursor.value"); + count++; + cursor.continue(); + }); + rq.onerror = t.step_func(function(e) { + e.preventDefault(); + e.stopPropagation(); + assert_unreached("rq.onerror - " + e.message); + }); + }, + document.title + ' - ' + dir + ); +}); +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor-direction-objectstore.htm b/testing/web-platform/tests/IndexedDB/idbcursor-direction-objectstore.htm new file mode 100644 index 0000000000..c099651a29 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor-direction-objectstore.htm @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<title>IDBCursor direction - object store</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#cursor-iteration-operation"> +<link rel=assert title='If direction is "next", let found record be the first record in records which satisfy all of the following requirements'> +<link rel=assert title="If position is defined, and source is an object store, the record's key is greater than position."> +<link rel=assert title='If direction is "prev", let found record be the last record in records which satisfy all of the following requirements'> +<link rel=assert title="If position is defined, and source is an object store, the record's key is less than position."> +<link rel=assert title="Set cursor's position to found record's key. If source is an index, set cursor's object store position to found record's value."> +<link rel=assert title="Set cursor's key to found record's key."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> +var records = [ "Alice", "Bob", "Greg" ]; +var directions = ["next", "prev", "nextunique", "prevunique"]; +var cases = [ + {dir: 'next', expect: ['Alice', 'Bob', 'Greg']}, + {dir: 'prev', expect: ['Greg', 'Bob', 'Alice']}, + {dir: 'nextunique', expect: ['Alice', 'Bob', 'Greg']}, + {dir: 'prevunique', expect: ['Greg', 'Bob', 'Alice']}, +]; + +cases.forEach(function(testcase) { + var dir = testcase.dir; + var expect = testcase.expect; + indexeddb_test( + function(t, db, tx) { + var objStore = db.createObjectStore("test"); + for (var i = 0; i < records.length; i++) + objStore.add(records[i], records[i]); + }, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").openCursor(undefined, dir); + rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + if (!cursor) { + assert_equals(count, expect.length, "cursor runs"); + t.done(); + } + assert_equals(cursor.value, expect[count], "cursor.value"); + count++; + cursor.continue(); + }); + rq.onerror = t.step_func(function(e) { + e.preventDefault(); + e.stopPropagation(); + assert_unreached("rq.onerror - " + e.message); + }); + }, + document.title + ' - ' + dir + ); +}); +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor-direction.htm b/testing/web-platform/tests/IndexedDB/idbcursor-direction.htm new file mode 100644 index 0000000000..1f2dedd76b --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor-direction.htm @@ -0,0 +1,76 @@ +<!DOCTYPE html> +<title>IDBCursor.direction</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + + function cursor_direction(constant, dir) + { + var db, + t = async_test(document.title + " - " + dir), + expected = dir ? dir : "next"; + + var open_rq = createdb(t); + + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + t.add_cleanup(function() { + db.close(); + indexedDB.deleteDatabase(db.name); + }); + + var objStore = db.createObjectStore("test"); + + objStore.add("data", "key"); + }; + + open_rq.onsuccess = t.step_func(function(e) { + var cursor_rq, count = 0; + var os = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test"); + if (dir) + cursor_rq = os.openCursor(undefined, dir); + else + cursor_rq = os.openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + assert_equals(cursor.direction, constant, 'direction constant'); + assert_equals(cursor.direction, expected, 'direction'); + assert_readonly(cursor, 'direction'); + + count++; + if (count >= 2) + t.done(); + }); + + var cursor_rq2 = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .openCursor(undefined, constant); + + cursor_rq2.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + assert_equals(cursor.direction, constant, 'direction constant (second try)'); + assert_equals(cursor.direction, expected, 'direction (second try)'); + assert_readonly(cursor, 'direction'); + + count++; + if (count >= 2) + t.done(); + }); + + }); + } + + cursor_direction("next"); + cursor_direction("next", "next"); + cursor_direction("prev", "prev"); + cursor_direction("nextunique", "nextunique"); + cursor_direction("prevunique", "prevunique"); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor-iterating-update.htm b/testing/web-platform/tests/IndexedDB/idbcursor-iterating-update.htm new file mode 100644 index 0000000000..0a7282b68f --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor-iterating-update.htm @@ -0,0 +1,58 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: Index iteration with cursor updates/deletes</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> +const objStoreValues = [ + {name: 'foo', id: 1}, + {name: 'bar', id: 2}, + {name: 'foo', id: 3}, + {name: 'bar', id: 4}, +]; + +const objStoreValuesByIndex = [ + objStoreValues[1], + objStoreValues[3], + objStoreValues[0], + objStoreValues[2], +]; + +const functionsThatShouldNotAffectIteration = [ + cursor => cursor.update({}), + cursor => cursor.delete(), +]; + +functionsThatShouldNotAffectIteration.forEach((func) => indexeddb_test( + (t, db) => { + const objStore = db.createObjectStore('items', {autoIncrement: true}); + objStore.createIndex('name', 'name', {unique: false}); + objStoreValues.forEach((value) => objStore.add(value)); + }, + (t, db) => { + const txn = db.transaction('items', 'readwrite', {durability: 'relaxed'}); + const objStore = txn.objectStore('items'); + const nameIndex = objStore.index('name'); + + const cursorValues = []; + nameIndex.openCursor().onsuccess = (evt) => { + const cursor = evt.target.result; + if (cursor) { + func(cursor); + cursorValues.push(cursor.value); + cursor.continue(); + } else { + assert_equals(cursorValues.length, 4, + `Cursor should iterate over 4 records`); + + cursorValues.forEach((value, i) => { + assert_object_equals(value, objStoreValuesByIndex[i]); + }); + t.done(); + } + } + }, + `Calling ${func} doesn't affect index iteration` +)); +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor-key.htm b/testing/web-platform/tests/IndexedDB/idbcursor-key.htm new file mode 100644 index 0000000000..e3b22f8bd6 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor-key.htm @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<title>IDBCursor.key</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + + function cursor_key(key) + { + var db, + t = async_test(document.title + " - " + key); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test"); + + objStore.add("data", key); + }; + + open_rq.onsuccess = t.step_func(function(e) { + var cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + assert_equals(cursor.value, "data", "prequisite cursor.value"); + + assert_key_equals(cursor.key, key, 'key'); + assert_readonly(cursor, 'key'); + + if (key instanceof Array) { + cursor.key.push("new"); + key.push("new"); + + assert_key_equals(cursor.key, key, 'key after array push'); + + // But we can not change key (like readonly, just a bit different) + cursor.key = 10; + assert_key_equals(cursor.key, key, 'key after assignment'); + } + + t.done(); + }); + }); + } + + cursor_key(1); + cursor_key("key"); + cursor_key(["my", "key"]); + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor-primarykey.htm b/testing/web-platform/tests/IndexedDB/idbcursor-primarykey.htm new file mode 100644 index 0000000000..d6832c5ede --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor-primarykey.htm @@ -0,0 +1,61 @@ +<!DOCTYPE html> +<title>IDBCursor.primaryKey</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + + function cursor_primarykey(key) + { + var db, + t = async_test(document.title + " - " + key); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test"); + objStore.createIndex("index", ""); + + objStore.add("data", key); + }; + + open_rq.onsuccess = t.step_func(function(e) { + var cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .index("index") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + assert_equals(cursor.value, "data", "prequisite cursor.value"); + assert_equals(cursor.key, "data", "prequisite cursor.key"); + + assert_key_equals(cursor.primaryKey, key, 'primaryKey'); + assert_readonly(cursor, 'primaryKey'); + + if (key instanceof Array) { + cursor.primaryKey.push("new"); + key.push("new"); + + assert_key_equals(cursor.primaryKey, key, 'primaryKey after array push'); + + // But we can not change key (like readonly, just a bit different) + cursor.key = 10; + assert_key_equals(cursor.primaryKey, key, 'key after assignment'); + } + + t.done(); + }); + }); + } + + cursor_primarykey(1); + cursor_primarykey("key"); + cursor_primarykey(["my", "key"]); + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor-request-source.html b/testing/web-platform/tests/IndexedDB/idbcursor-request-source.html new file mode 100644 index 0000000000..8ed4a8dee2 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor-request-source.html @@ -0,0 +1,31 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: The source of requests made against cursors</title> +<meta name="help" href="https://w3c.github.io/IndexedDB/#dom-idbrequest-source"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +[ + cursor => cursor.update(0), + cursor => cursor.delete() +].forEach(func => indexeddb_test( + (t, db) => { + db.createObjectStore('store', {autoIncrement: true}); + }, + (t, db) => { + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + store.put('value'); + store.openCursor().onsuccess = t.step_func(e => { + const cursor = e.target.result; + assert_equals(func(cursor).source, cursor, + `${func}.source should be the cursor itself`); + t.done(); + }); + }, + `The source of the request from ${func} is the cursor itself` +)); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor-request.any.js b/testing/web-platform/tests/IndexedDB/idbcursor-request.any.js new file mode 100644 index 0000000000..2e1216a736 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor-request.any.js @@ -0,0 +1,41 @@ +// META: script=resources/support.js + +function cursorRequestTest({ useIndex, useKeyCursor }) { + indexeddb_test( + (t, db) => { + const objStore = db.createObjectStore("my_objectstore"); + objStore.add("data", 1); + objStore.createIndex("my_index", ""); + }, + (t, db) => { + const tx = db.transaction("my_objectstore", "readonly", {durability: 'relaxed'}); + let source = tx.objectStore("my_objectstore"); + if (useIndex) source = source.index('my_index'); + const req = useKeyCursor ? source.openKeyCursor() : source.openCursor(); + let cursor; + + req.onsuccess = t.step_func(() => { + cursor = req.result; + assert_equals(cursor.request, req, 'cursor.request'); + assert_readonly(cursor, 'request'); + assert_equals(cursor.request, cursor.request, 'cursor.request does not change'); + }); + + req.transaction.oncomplete = t.step_func(() => { + setTimeout(t.step_func(() => { + assert_equals(cursor.request, req, 'cursor.request after transaction complete'); + t.done(); + }), 0); + }); + + req.transaction.onerror = t.unreached_func('Transaction error'); + }, + `cursor.request from ${useIndex ? 'IDBIndex' : 'IDBObjectStore'}.${useKeyCursor ? 'openKeyCursor' : 'openCursor'}` + ); +} + +for (const useIndex of [false, true]) { + for (const useKeyCursor of [false, true]) { + cursorRequestTest({ useIndex, useKeyCursor }); + } +} diff --git a/testing/web-platform/tests/IndexedDB/idbcursor-reused.htm b/testing/web-platform/tests/IndexedDB/idbcursor-reused.htm new file mode 100644 index 0000000000..feb35a55f8 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor-reused.htm @@ -0,0 +1,69 @@ +<!DOCTYPE html> +<title>IDBCursor is reused</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<meta rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBCursor-continue-void-any-key"> +<meta rel=assert title="Otherwise this method runs the steps for asynchronously executing a request. However, the steps are slightly modified such that instead of creating a new IDBRequest, it reuses the request originally created when this cursor was created. The done flag on the request is set to false before the request is returned. The steps are run with the cursor's source as source and the steps for iterating a cursor as operation, using this cursor as cursor and the key parameter as key."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + + var db + var open_rq = createdb(async_test()) + + open_rq.onupgradeneeded = function(e) { + db = e.target.result + var os = db.createObjectStore("test") + + os.add("data", "k") + os.add("data2", "k2") + } + + open_rq.onsuccess = function(e) { + var cursor + var count = 0 + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").openCursor() + + rq.onsuccess = this.step_func(function(e) + { + switch(count) + { + case 0: + cursor = e.target.result + + assert_equals(cursor.value, "data", "prequisite cursor.value") + cursor.custom_cursor_value = 1 + e.target.custom_request_value = 2 + + cursor.continue() + break + + case 1: + assert_equals(cursor.value, "data2", "prequisite cursor.value") + assert_equals(cursor.custom_cursor_value, 1, "custom cursor value") + assert_equals(e.target.custom_request_value, 2, "custom request value") + + cursor.advance(1) + break + + case 2: + assert_false(!!e.target.result, "got cursor") + assert_equals(cursor.custom_cursor_value, 1, "custom cursor value") + assert_equals(e.target.custom_request_value, 2, "custom request value") + break + } + count++ + }) + + rq.transaction.oncomplete = this.step_func(function() { + assert_equals(count, 3, "cursor callback runs") + assert_equals(rq.custom_request_value, 2, "variable placed on old IDBRequest") + assert_equals(cursor.custom_cursor_value, 1, "custom cursor value (transaction.complete)") + this.done() + }) + } + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor-source.htm b/testing/web-platform/tests/IndexedDB/idbcursor-source.htm new file mode 100644 index 0000000000..81acda33cc --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor-source.htm @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<title>IDBCursor.source</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> +function cursor_source_test(test_name, name, stringified_object, cursor_rq_func) { + indexeddb_test( + function(t, db, tx) { + var objStore = db.createObjectStore("my_objectstore"); + objStore.createIndex("my_index", ""); + + objStore.add("data", 1); + objStore.add("data2", 2); + }, + function(t, db) { + var cursor_rq = cursor_rq_func(db); + + cursor_rq.onsuccess = t.step_func(function(e) { + if (!e.target.result) { + return; + } + var cursor = e.target.result; + assert_readonly(cursor, 'source'); + + // Direct try + assert_true(cursor.source instanceof Object, "source isobject"); + assert_equals(cursor.source + "", stringified_object, "source"); + assert_equals(cursor.source.name, name, "name"); + + cursor.continue(); + }); + + cursor_rq.transaction.oncomplete = t.step_func(function(e) { + t.done(); + }); + + cursor_rq.transaction.onerror = t.step_func(function(e) { + assert_unreached("Transaction got error. " + (e.target.error ? e.target.error.name : "unknown")); + }); + }, + test_name + ); +} + +cursor_source_test( + document.title + ' - IDBObjectStore', + "my_objectstore", + "[object IDBObjectStore]", + function(db) { return db.transaction("my_objectstore", "readonly", {durability: 'relaxed'}) + .objectStore("my_objectstore") + .openCursor(); } +); + +cursor_source_test( + document.title + ' - IDBIndex', + "my_index", + "[object IDBIndex]", + function(db) { return db.transaction("my_objectstore", "readonly", {durability: 'relaxed'}) + .objectStore("my_objectstore") + .index("my_index") + .openCursor(); } +); +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor-update-exception-order.htm b/testing/web-platform/tests/IndexedDB/idbcursor-update-exception-order.htm new file mode 100644 index 0000000000..8ac08635bb --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor-update-exception-order.htm @@ -0,0 +1,98 @@ +<!DOCTYPE html> +<title>IndexedDB: IDBCursor update() Exception Ordering</title> +<meta charset=utf-8> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbcursor-update"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + (t, db) => { + const s = db.createObjectStore('s'); + s.put('value', 'key'); + }, + (t, db) => { + const s = db.transaction('s', 'readonly', {durability: 'relaxed'}).objectStore('s'); + const r = s.openCursor(); + r.onsuccess = t.step_func(() => { + r.onsuccess = null; + const cursor = r.result; + setTimeout(t.step_func(() => { + assert_throws_dom('TransactionInactiveError', () => { + cursor.update('value2'); + }, '"Transaction inactive" check (TransactionInactiveError) ' + + 'should precede "read only" check (ReadOnlyError)'); + t.done(); + }), 0); + }); + }, + 'IDBCursor.update exception order: TransactionInactiveError vs. ReadOnlyError' +); + +indexeddb_test( + (t, db) => { + const s = db.createObjectStore('s'); + s.put('value', 'key'); + }, + (t, db) => { + const s = db.transaction('s', 'readonly', {durability: 'relaxed'}).objectStore('s'); + const r = s.openCursor(); + r.onsuccess = t.step_func(() => { + r.onsuccess = null; + const cursor = r.result; + cursor.continue(); + assert_throws_dom('ReadOnlyError', () => { + cursor.update('value2'); + }, '"Read only" check (ReadOnlyError) should precede '+ + '"got value flag" check (InvalidStateError)'); + t.done(); + }); + }, + 'IDBCursor.update exception order: ReadOnlyError vs. InvalidStateError #1' +); + +indexeddb_test( + (t, db) => { + const s = db.createObjectStore('s'); + s.put('value', 'key'); + }, + (t, db) => { + const s = db.transaction('s', 'readonly', {durability: 'relaxed'}).objectStore('s'); + const r = s.openKeyCursor(); + r.onsuccess = t.step_func(() => { + r.onsuccess = null; + const cursor = r.result; + assert_throws_dom('ReadOnlyError', () => { + cursor.update('value2'); + }, '"Read only" check (ReadOnlyError) should precede '+ + '"key only flag" check (InvalidStateError)'); + t.done(); + }); + }, + 'IDBCursor.update exception order: ReadOnlyError vs. InvalidStateError #2' +); + +indexeddb_test( + (t, db) => { + const s = db.createObjectStore('s', {keyPath: 'id'}); + s.put({id: 123, data: 'value'}); + }, + (t, db) => { + const s = db.transaction('s', 'readwrite', {durability: 'relaxed'}).objectStore('s'); + const r = s.openCursor(); + r.onsuccess = t.step_func(() => { + r.onsuccess = null; + const cursor = r.result; + cursor.continue(); + assert_throws_dom('InvalidStateError', () => { + cursor.update({id: 123, data: 'value2'}); + }, '"Got value flag" check (InvalidStateError) should precede ' + + '"modified key" check (DataError)'); + t.done(); + }); + }, + 'IDBCursor.update exception order: InvalidStateError vs. DataError' +); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_advance_index.htm b/testing/web-platform/tests/IndexedDB/idbcursor_advance_index.htm new file mode 100644 index 0000000000..4c0c0b697a --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_advance_index.htm @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<title>IDBCursor.advance() - index - iterate cursor number of times specified by count </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var db, + count = 0, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" }, + { pKey: "primaryKey_2", iKey: "indexKey_2" }, + { pKey: "primaryKey_3", iKey: "indexKey_3" }]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var store = db.createObjectStore("test", {keyPath:"pKey"}); + store.createIndex("idx", "iKey"); + + for(var i = 0; i < records.length; i++) { + store.add(records[i]); + } + }; + + open_rq.onsuccess = function (e) { + var cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .index("idx") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + assert_true(cursor instanceof IDBCursor); + + switch(count) { + case 0: + count += 3; + cursor.advance(3); + break; + case 3: + var record = cursor.value; + assert_equals(record.pKey, records[count].pKey, "record.pKey"); + assert_equals(record.iKey, records[count].iKey, "record.iKey"); + t.done(); + break; + default: + assert_unreached("unexpected count"); + break; + } + }); + } +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_advance_index2.htm b/testing/web-platform/tests/IndexedDB/idbcursor_advance_index2.htm new file mode 100644 index 0000000000..04842f71c3 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_advance_index2.htm @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<title>IDBCursor.advance() - attempt to pass a count parameter that is not a number</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBCursor-advance-void-unsigned-long-count"> +<link rel=assert title="The value passed into the count parameter was zero or a negative number."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", {keyPath:"pKey"}); + + objStore.createIndex("index", "iKey"); + + for(var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .index("index") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + assert_true(cursor != null, "cursor exist"); + assert_throws_js(TypeError, + function() { cursor.advance(document); }); + + t.done(); + }); + }; + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_advance_index3.htm b/testing/web-platform/tests/IndexedDB/idbcursor_advance_index3.htm new file mode 100644 index 0000000000..a797286e7c --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_advance_index3.htm @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<title>IDBCursor.advance() - index - attempt to advance backwards</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBCursor-advance-void-unsigned-long-count"> +<link rel=assert title="The value passed into the count parameter was zero or a negative number."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script type="text/javascript"> + + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", { keyPath:"pKey" }); + + objStore.createIndex("index", "iKey"); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .index("index") + .openCursor(undefined, "next"); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + assert_true(cursor != null, "cursor exist"); + assert_throws_js(TypeError, + function() { cursor.advance(-1); }); + + t.done(); + }); + }; + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_advance_index5.htm b/testing/web-platform/tests/IndexedDB/idbcursor_advance_index5.htm new file mode 100644 index 0000000000..5e60aefd5a --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_advance_index5.htm @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<title>IDBCursor.advance() - index - iterate to the next record</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBCursor-advance-void-unsigned-long-count"> +<link rel=assert title="The operation runs the steps for iterating a cursor count number of times with null as key and this cursor as cursor."> +<link rel=assert title="The number of advances forward the cursor should make."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + count = 0, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" }, + { pKey: "primaryKey_1-2", iKey: "indexKey_1" } ], + expected = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1-2", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result + var objStore = db.createObjectStore("test", { keyPath:"pKey" }) + + objStore.createIndex("index", "iKey") + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]) + }; + + open_rq.onsuccess = function(e) { + var cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .index("index") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + if (!cursor) { + assert_equals(count, expected.length, "cursor run count") + t.done() + } + + var record = cursor.value; + assert_equals(record.pKey, expected[count].pKey, "primary key"); + assert_equals(record.iKey, expected[count].iKey, "index key"); + + cursor.advance(2); + count++; + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_advance_index6.htm b/testing/web-platform/tests/IndexedDB/idbcursor_advance_index6.htm new file mode 100644 index 0000000000..3a1168f57a --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_advance_index6.htm @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBCursor.advance() - index - throw TypeError</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBCursor-advance-void-unsigned-long-count"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + t = async_test(), + records = [{ pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" }]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + var objStore = db.createObjectStore("store", {keyPath : "pKey"}); + objStore.createIndex("index", "iKey"); + for (var i = 0; i < records.length; i++) { + objStore.add(records[i]); + } + var rq = objStore.index("index").openCursor(); + rq.onsuccess = t.step_func(function(event) { + var cursor = event.target.result; + assert_true(cursor instanceof IDBCursor); + + assert_throws_js(TypeError, function() { + cursor.advance(0); + }, "Calling advance() with count argument 0 should throw TypeError."); + + t.done(); + }); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_advance_index7.htm b/testing/web-platform/tests/IndexedDB/idbcursor_advance_index7.htm new file mode 100644 index 0000000000..f5ecc6144c --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_advance_index7.htm @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBCursor.advance() - index - throw TransactionInactiveError</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBCursor-advance-void-unsigned-long-count"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + t = async_test(), + records = [{ pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" }]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + var objStore = db.createObjectStore("store", {keyPath : "pKey"}); + objStore.createIndex("index", "iKey"); + for (var i = 0; i < records.length; i++) { + objStore.add(records[i]); + } + var rq = objStore.index("index").openCursor(); + rq.onsuccess = t.step_func(function(event) { + var cursor = event.target.result; + assert_true(cursor instanceof IDBCursor); + + event.target.transaction.abort(); + assert_throws_dom("TransactionInactiveError", function() { + cursor.advance(1); + }, "Calling advance() should throws an exception TransactionInactiveError when the transaction is not active."); + + t.done(); + }); + } + +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_advance_index8.htm b/testing/web-platform/tests/IndexedDB/idbcursor_advance_index8.htm new file mode 100644 index 0000000000..1f9ff9c453 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_advance_index8.htm @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBCursor.advance() - index - throw InvalidStateError</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBCursor-advance-void-unsigned-long-count"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + t = async_test(), + records = [{ pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" }]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + var objStore = db.createObjectStore("store", {keyPath : "pKey"}); + objStore.createIndex("index", "iKey"); + for (var i = 0; i < records.length; i++) { + objStore.add(records[i]); + } + var rq = objStore.index("index").openCursor(); + rq.onsuccess = t.step_func(function(event) { + var cursor = event.target.result; + assert_true(cursor instanceof IDBCursor); + + cursor.advance(1); + assert_throws_dom("InvalidStateError", function() { + cursor.advance(1); + }, "Calling advance() should throw DOMException when the cursor is currently being iterated."); + + t.done(); + }); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_advance_index9.htm b/testing/web-platform/tests/IndexedDB/idbcursor_advance_index9.htm new file mode 100644 index 0000000000..1756419c6d --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_advance_index9.htm @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBCursor.advance() - index - throw InvalidStateError caused by object store been deleted</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + t = async_test(), + records = [{ pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" }]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + var objStore = db.createObjectStore("store", {keyPath : "pKey"}); + objStore.createIndex("index", "iKey"); + for (var i = 0; i < records.length; i++) { + objStore.add(records[i]); + } + var rq = objStore.index("index").openCursor(); + rq.onsuccess = t.step_func(function(event) { + var cursor = event.target.result; + assert_true(cursor instanceof IDBCursor, "cursor exist"); + + db.deleteObjectStore("store"); + assert_throws_dom("InvalidStateError", function() { + cursor.advance(1); + }, "If the cursor's source or effective object store has been deleted, the implementation MUST throw a DOMException of type InvalidStateError"); + + t.done(); + }); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_advance_objectstore.htm b/testing/web-platform/tests/IndexedDB/idbcursor_advance_objectstore.htm new file mode 100644 index 0000000000..c9226c5667 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_advance_objectstore.htm @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<title> IDBCursor.advance() - object store - iterate cursor number of times specified by count </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var db, + count = 0, + t = async_test(), + records = [ { pKey: "primaryKey_0" }, + { pKey: "primaryKey_1" }, + { pKey: "primaryKey_2" }, + { pKey: "primaryKey_3" }]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var store = db.createObjectStore("test", {keyPath:"pKey"}); + + for(var i = 0; i < records.length; i++) { + store.add(records[i]); + } + }; + + open_rq.onsuccess = function (e) { + var cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + assert_true(cursor instanceof IDBCursor); + + switch(count) { + case 0: + count += 3; + cursor.advance(3); + break; + case 3: + assert_equals(cursor.value.pKey, records[count].pKey, "cursor.value.pKey"); + t.done(); + break; + default: + assert_unreached("unexpected count"); + break; + } + }); + } + +</script> + +<div id=log> </div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_advance_objectstore2.htm b/testing/web-platform/tests/IndexedDB/idbcursor_advance_objectstore2.htm new file mode 100644 index 0000000000..3d46f0c675 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_advance_objectstore2.htm @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBCursor.advance() - object store - throw TypeError</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBCursor-advance-void-unsigned-long-count"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + t = async_test(), + records = [{ pKey: "primaryKey_0"}, + { pKey: "primaryKey_1"}]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + var objStore = db.createObjectStore("store", {keyPath:"pKey"}); + for (var i = 0; i < records.length; i++) { + objStore.add(records[i]); + } + } + + open_rq.onsuccess = function (event) { + var txn = db.transaction("store", "readwrite", {durability: 'relaxed'}); + var rq = txn.objectStore("store").openCursor(); + rq.onsuccess = t.step_func(function(event) { + var cursor = event.target.result; + assert_true(cursor instanceof IDBCursor); + + assert_throws_js(TypeError, function() { + cursor.advance(0); + }, "Calling advance() with count argument 0 should throw TypeError."); + + t.done(); + }); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_advance_objectstore3.htm b/testing/web-platform/tests/IndexedDB/idbcursor_advance_objectstore3.htm new file mode 100644 index 0000000000..c91e4acaab --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_advance_objectstore3.htm @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBCursor.advance() - object store - throw TransactionInactiveError</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBCursor-advance-void-unsigned-long-count"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + t = async_test(), + records = [{ pKey: "primaryKey_0"}, + { pKey: "primaryKey_1"}]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + var objStore = db.createObjectStore("store", {keyPath:"pKey"}); + for (var i = 0; i < records.length; i++) { + objStore.add(records[i]); + } + } + + open_rq.onsuccess = function (event) { + var txn = db.transaction("store", "readwrite", {durability: 'relaxed'}); + var rq = txn.objectStore("store").openCursor(); + rq.onsuccess = t.step_func(function(event) { + var cursor = event.target.result; + assert_true(cursor instanceof IDBCursor); + + event.target.transaction.abort(); + assert_throws_dom("TransactionInactiveError", function() { + cursor.advance(1); + }, "Calling advance() should throws an exception TransactionInactiveError when the transaction is not active"); + + t.done(); + }); + } + +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_advance_objectstore4.htm b/testing/web-platform/tests/IndexedDB/idbcursor_advance_objectstore4.htm new file mode 100644 index 0000000000..766a56daf4 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_advance_objectstore4.htm @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBCursor.advance() - object store - throw InvalidStateError</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBCursor-advance-void-unsigned-long-count"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + t = async_test(), + records = [{ pKey: "primaryKey_0"}, + { pKey: "primaryKey_1"}]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + var objStore = db.createObjectStore("store", {keyPath:"pKey"}); + for (var i = 0; i < records.length; i++) { + objStore.add(records[i]); + } + } + + open_rq.onsuccess = function (event) { + var txn = db.transaction("store", "readwrite", {durability: 'relaxed'}); + var rq = txn.objectStore("store").openCursor(); + rq.onsuccess = t.step_func(function(event) { + var cursor = event.target.result; + assert_true(cursor instanceof IDBCursor); + + cursor.advance(1); + assert_throws_dom("InvalidStateError", function() { + cursor.advance(1); + }, "Calling advance() should throw DOMException when the cursor is currently being iterated."); + + t.done(); + }); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_advance_objectstore5.htm b/testing/web-platform/tests/IndexedDB/idbcursor_advance_objectstore5.htm new file mode 100644 index 0000000000..2c6fa640ae --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_advance_objectstore5.htm @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBCursor.advance() - object store - throw InvalidStateError caused by object store been deleted</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + t = async_test(), + records = [{ pKey: "primaryKey_0"}, + { pKey: "primaryKey_1"}]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + var objStore = db.createObjectStore("store", {keyPath:"pKey"}); + for (var i = 0; i < records.length; i++) { + objStore.add(records[i]); + } + var rq = objStore.openCursor(); + rq.onsuccess = t.step_func(function(event) { + var cursor = event.target.result; + assert_true(cursor instanceof IDBCursor, "cursor exist"); + + db.deleteObjectStore("store"); + assert_throws_dom("InvalidStateError", function() { + cursor.advance(1); + }, "If the cursor's source or effective object store has been deleted, the implementation MUST throw a DOMException of type InvalidStateError"); + + t.done(); + }); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_continue_delete_objectstore.htm b/testing/web-platform/tests/IndexedDB/idbcursor_continue_delete_objectstore.htm new file mode 100644 index 0000000000..86b4abf113 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_continue_delete_objectstore.htm @@ -0,0 +1,114 @@ +<!DOCTYPE html> +<title>IDBObjectStore.delete() and IDBCursor.continue() - object store - remove a record from the object store while iterating cursor</title> +<link rel="author" title="Mozilla" href="https://www.mozilla.org"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + /* The goal here is to test that any prefetching of cursor values performs + * correct invalidation of prefetched data. This test is motivated by the + * particularities of the Firefox implementation of preloading, and is + * specifically motivated by an edge case when prefetching prefetches at + * least 2 extra records and at most determines whether a mutation is + * potentially relevant based on current cursor position and direction and + * does not test for key equivalence. Future implementations may want to + * help refine this test if their cursors are more clever. + * + * Step-wise we: + * - Open a cursor, returning key 0. + * - When the cursor request completes, without yielding control: + * - Issue a delete() call that won't actually delete anything but looks + * relevant. This should purge prefetched records 1 and 2. + * - Issue a continue() which should result in record 1 being fetched + * again and record 2 being prefetched again. + * - Delete record 2. Unless there's a synchronously available source + * of truth, the data from continue() above will not be present and + * we'll expect the implementation to need to set a flag to invalidate + * the prefetched data when it arrives. + * - When the cursor request completes, validate we got record 1 and issue + * a continue. + * - When the request completes, we should have a null cursor result value + * because 2 was deleted. + */ + var db, + count = 0, + t = async_test(), + records = [ { pKey: "primaryKey_0" }, + { pKey: "primaryKey_1" }, + { pKey: "primaryKey_2" } ]; + + // This is a key that is not present in the database, but that is known to + // be relevant to a forward iteration of the above keys by comparing to be + // greater than all of them. + var plausibleFutureKey = "primaryKey_9"; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var objStore = db.createObjectStore("test", { keyPath: "pKey" }); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = t.step_func(CursorDeleteRecord); + + + function CursorDeleteRecord(e) { + var txn = db.transaction("test", "readwrite", {durability: 'relaxed'}), + object_store = txn.objectStore("test"), + cursor_rq = object_store.openCursor(); + + var iteration = 0; + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + switch (iteration) { + case 0: + object_store.delete(plausibleFutureKey); + assert_true(cursor != null, "cursor valid"); + assert_equals(cursor.value.pKey, records[iteration].pKey); + cursor.continue(); + object_store.delete(records[2].pKey); + break; + case 1: + assert_true(cursor != null, "cursor valid"); + assert_equals(cursor.value.pKey, records[iteration].pKey); + cursor.continue(); + break; + case 2: + assert_equals(cursor, null, "cursor no longer valid"); + break; + }; + iteration++; + }); + + txn.oncomplete = t.step_func(VerifyRecordWasDeleted); + } + + + function VerifyRecordWasDeleted(e) { + var cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + if (!cursor) { + assert_equals(count, 2, 'count'); + t.done(); + } + + assert_equals(cursor.value.pKey, records[count].pKey); + count++; + cursor.continue(); + }); + } + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_continue_index.htm b/testing/web-platform/tests/IndexedDB/idbcursor_continue_index.htm new file mode 100644 index 0000000000..cfc0ae3fe9 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_continue_index.htm @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<title>IDBCursor.continue() - index - iterate to the next record</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBCursor-continue-void-any-key"> +<link rel=assert title="Otherwise this method runs the steps for asynchronously executing a request."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + count = 0, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" }, + { pKey: "primaryKey_1-2", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", { keyPath:"pKey" }); + + objStore.createIndex("index", "iKey"); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .index("index") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + if (!cursor) { + assert_equals(count, records.length, "cursor run count"); + t.done(); + } + + var record = cursor.value; + assert_equals(record.pKey, records[count].pKey, "primary key"); + assert_equals(record.iKey, records[count].iKey, "index key"); + + cursor.continue(); + count++; + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_continue_index2.htm b/testing/web-platform/tests/IndexedDB/idbcursor_continue_index2.htm new file mode 100644 index 0000000000..56b9218fd1 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_continue_index2.htm @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<title>IDBCursor.continue() - index - attempt to pass a key parameter that is not a valid key</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBCursor-continue-void-any-key"> +<link rel=assert title="If the key parameter is specified and fulfills any of these conditions this method must throw a DOMException of type DataError"> +<link rel=assert title="The parameter is not a valid key."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", {keyPath:"pKey"}); + + objStore.createIndex("index", "iKey"); + + for(var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .index("index") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + assert_throws_dom("DataError", + function() { cursor.continue(document); }); + + assert_true(cursor instanceof IDBCursorWithValue, "cursor"); + + t.done(); + }); + }; + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_continue_index3.htm b/testing/web-platform/tests/IndexedDB/idbcursor_continue_index3.htm new file mode 100644 index 0000000000..b9f5945402 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_continue_index3.htm @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<title>IDBCursor.continue() - index - attempt to iterate to the previous record when the direction is set for the next record </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBCursor-continue-void-any-key"> +<link rel=assert title="The parameter is less than or equal to this cursor's position and this cursor's direction is 'next' or 'nextunique'."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", {keyPath:"pKey"}); + + objStore.createIndex("index", "iKey"); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var count = 0; + var cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .index("index") + .openCursor(undefined, "next"); // XXX: Fx has issue with "undefined" + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + if (!cursor) { + assert_equals(count, 2, "ran number of times"); + t.done(); + } + + // First time checks key equal, second time checks key less than + assert_throws_dom("DataError", + function() { cursor.continue(records[0].iKey); }); + + cursor.continue(); + + count++; + }); + }; + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_continue_index4.htm b/testing/web-platform/tests/IndexedDB/idbcursor_continue_index4.htm new file mode 100644 index 0000000000..f851b87ddf --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_continue_index4.htm @@ -0,0 +1,65 @@ +<!DOCTYPE html> +<title>IDBCursor.continue() - index - attempt to iterate to the next record when the direction is set for the previous record</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBCursor-continue-void-any-key"> +<link rel=assert title="The parameter is greater than or equal to this cursor's position and this cursor's direction is 'prev' or 'prevunique'."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" }, + { pKey: "primaryKey_2", iKey: "indexKey_2" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", { keyPath: "pKey" }); + + objStore.createIndex("index", "iKey"); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var count = 0, + cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .index("index") + .openCursor(undefined, "prev"); // XXX Fx issues w undefined + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result, + record = cursor.value; + + switch(count) { + case 0: + assert_equals(record.pKey, records[2].pKey, "first pKey"); + assert_equals(record.iKey, records[2].iKey, "first iKey"); + cursor.continue(); + break; + + case 1: + assert_equals(record.pKey, records[1].pKey, "second pKey"); + assert_equals(record.iKey, records[1].iKey, "second iKey"); + assert_throws_dom("DataError", + function() { cursor.continue("indexKey_2"); }); + t.done(); + break; + + default: + assert_unreached("Unexpected count value: " + count); + } + + count++; + }); + }; + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_continue_index5.htm b/testing/web-platform/tests/IndexedDB/idbcursor_continue_index5.htm new file mode 100644 index 0000000000..3e70873bbb --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_continue_index5.htm @@ -0,0 +1,64 @@ +<!DOCTYPE html> +<title>IDBCursor.continue() - index - iterate using 'prevunique'</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#cursor-iteration-operation"> +<link rel=assert title='If direction is "prevunique", let temp record be the last record in records which satisfy all of the following requirements:'> +<link rel=assert title="If position is defined, the record's key is less than position."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" }, + { pKey: "primaryKey_1-2", iKey: "indexKey_1" }, + { pKey: "primaryKey_2", iKey: "indexKey_2" } ], + + expected = [ { pKey: "primaryKey_2", iKey: "indexKey_2" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" }, + { pKey: "primaryKey_0", iKey: "indexKey_0" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", { keyPath: "pKey" }); + + objStore.createIndex("index", "iKey"); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var count = 0, + cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .index("index") + .openCursor(undefined, 'prevunique'); + + cursor_rq.onsuccess = t.step_func(function(e) { + if (!e.target.result) { + assert_equals(count, expected.length, 'count'); + t.done(); + return; + } + var cursor = e.target.result, + record = cursor.value; + + assert_equals(record.pKey, expected[count].pKey, "pKey #" + count); + assert_equals(record.iKey, expected[count].iKey, "iKey #" + count); + + assert_equals(cursor.key, expected[count].iKey, "cursor.key #" + count); + assert_equals(cursor.primaryKey, expected[count].pKey, "cursor.primaryKey #" + count); + + count++; + cursor.continue(expected[count] ? expected[count].iKey : undefined); + }); + }; + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_continue_index6.htm b/testing/web-platform/tests/IndexedDB/idbcursor_continue_index6.htm new file mode 100644 index 0000000000..1933bfcf99 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_continue_index6.htm @@ -0,0 +1,64 @@ +<!DOCTYPE html> +<title>IDBCursor.continue() - index - iterate using nextunique</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#cursor-iteration-operation"> +<link rel=assert title='If direction is "nextunique", let found record be the first record in records which satisfy all of the following requirements:'> +<link rel=assert title="If position is defined, the record's key is greater than position."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" }, + { pKey: "primaryKey_1-2", iKey: "indexKey_1" }, + { pKey: "primaryKey_2", iKey: "indexKey_2" } ], + + expected = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" }, + { pKey: "primaryKey_2", iKey: "indexKey_2" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", { keyPath: "pKey" }); + + objStore.createIndex("index", "iKey"); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var count = 0, + cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .index("index") + .openCursor(undefined, "nextunique"); + + cursor_rq.onsuccess = t.step_func(function(e) { + if (!e.target.result) { + assert_equals(count, expected.length, 'count'); + t.done(); + return; + } + var cursor = e.target.result, + record = cursor.value; + + assert_equals(record.pKey, expected[count].pKey, "pKey #" + count); + assert_equals(record.iKey, expected[count].iKey, "iKey #" + count); + + assert_equals(cursor.key, expected[count].iKey, "cursor.key #" + count); + assert_equals(cursor.primaryKey, expected[count].pKey, "cursor.primaryKey #" + count); + + count++; + cursor.continue(expected[count] ? expected[count].iKey : undefined); + }); + }; + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_continue_index7.htm b/testing/web-platform/tests/IndexedDB/idbcursor_continue_index7.htm new file mode 100644 index 0000000000..61bfe85b27 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_continue_index7.htm @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBCursor.continue() - index - throw TransactionInactiveError</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + var objStore = db.createObjectStore("store", {keyPath : "pKey"}); + objStore.createIndex("index", "iKey"); + for (var i = 0; i < records.length; i++) { + objStore.add(records[i]); + } + var rq = objStore.index("index").openCursor(); + rq.onsuccess = t.step_func(function(event) { + var cursor = event.target.result; + assert_true(cursor instanceof IDBCursor); + + event.target.transaction.abort(); + assert_throws_dom("TransactionInactiveError", function() { + cursor.continue(); + }, "Calling continue() should throws an exception TransactionInactiveError when the transaction is not active."); + + t.done(); + }); + } + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_continue_index8.htm b/testing/web-platform/tests/IndexedDB/idbcursor_continue_index8.htm new file mode 100644 index 0000000000..e4cbe7261f --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_continue_index8.htm @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBCursor.continue() - index - throw InvalidStateError caused by object store been deleted</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + var objStore = db.createObjectStore("store", {keyPath : "pKey"}); + objStore.createIndex("index", "iKey"); + for (var i = 0; i < records.length; i++) { + objStore.add(records[i]); + } + var rq = objStore.index("index").openCursor(); + rq.onsuccess = t.step_func(function(event) { + var cursor = event.target.result; + assert_true(cursor instanceof IDBCursor); + + db.deleteObjectStore("store"); + assert_throws_dom("InvalidStateError", function() { + cursor.continue(); + }, "If the cursor's source or effective object store has been deleted, the implementation MUST throw a DOMException of type InvalidStateError"); + + t.done(); + }); + } + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_continue_invalid.htm b/testing/web-platform/tests/IndexedDB/idbcursor_continue_invalid.htm new file mode 100644 index 0000000000..c177c7f565 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_continue_invalid.htm @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<title>IDBCursor.continue() - attempt to call continue two times</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test"); + + objStore.createIndex("index", ""); + + objStore.add("data", 1); + objStore.add("data2", 2); + }; + + open_rq.onsuccess = function(e) { + var count = 0; + var cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .index("index") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + if (!e.target.result) { + assert_equals(count, 2, 'count'); + t.done(); + return; + } + var cursor = e.target.result; + + cursor.continue(undefined); + + // Second try + assert_throws_dom('InvalidStateError', + function() { cursor.continue(); }, 'second continue'); + + assert_throws_dom('InvalidStateError', + function() { cursor.continue(3); }, 'third continue'); + + count++; + }); + }; + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_continue_objectstore.htm b/testing/web-platform/tests/IndexedDB/idbcursor_continue_objectstore.htm new file mode 100644 index 0000000000..f82aa99949 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_continue_objectstore.htm @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<title>IDBCursor.continue() - object store - iterate to the next record</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var db, + count = 0, + t = async_test(), + records = [ { pKey: "primaryKey_0" }, + { pKey: "primaryKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", {autoIncrement:true, keyPath:"pKey"}); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var store = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test"); + + var cursor_rq = store.openCursor(); + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + if (!cursor) { + assert_equals(count, records.length, "cursor run count"); + t.done(); + } + + var record = cursor.value; + assert_equals(record.pKey, records[count].pKey, "primary key"); + assert_equals(record.iKey, records[count].iKey, "index key"); + + cursor.continue(); + count++; + }); + }; +</script> + +<div id=log> </div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_continue_objectstore2.htm b/testing/web-platform/tests/IndexedDB/idbcursor_continue_objectstore2.htm new file mode 100644 index 0000000000..8b79c64615 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_continue_objectstore2.htm @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<title>IDBCursor.continue() - object store - attempt to pass a key parameter is not a valid key </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0" }, + { pKey: "primaryKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", {keyPath:"pKey"}); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + assert_true(cursor instanceof IDBCursor, "cursor exists"); + assert_throws_dom("DataError", + function() { cursor.continue(document); }); + + t.done(); + }); + }; +</script> + +<div id="log"> </div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_continue_objectstore3.htm b/testing/web-platform/tests/IndexedDB/idbcursor_continue_objectstore3.htm new file mode 100644 index 0000000000..7d96d7fec9 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_continue_objectstore3.htm @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<title>IDBCursor.continue() - object store - attempt to iterate to the previous record when the direction is set for the next record</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0" }, + { pKey: "primaryKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", {keyPath:"pKey"}); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .openCursor(undefined, "next"); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + assert_true(cursor instanceof IDBCursor, "cursor exist"); + assert_throws_dom("DataError", + function() { cursor.continue(records[0].pKey); }); + + t.done(); + }); + }; +</script> + +<div id="log"> </div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_continue_objectstore4.htm b/testing/web-platform/tests/IndexedDB/idbcursor_continue_objectstore4.htm new file mode 100644 index 0000000000..5056a7364f --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_continue_objectstore4.htm @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<title>IDBCursor.continue() - object store - attempt to iterate to the next record when the direction is set for the previous record </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0" }, + { pKey: "primaryKey_1" }, + { pKey: "primaryKey_2" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", { keyPath: "pKey" }); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var count = 0, + cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .openCursor(null, "prev"); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + assert_true(cursor != null, "cursor exist"); + + switch(count) { + case 0: + assert_equals(cursor.value.pKey, records[2].pKey, "first cursor pkey"); + cursor.continue(records[1].pKey); + break; + + case 1: + assert_equals(cursor.value.pKey, records[1].pKey, "second cursor pkey"); + assert_throws_dom("DataError", + function() { cursor.continue(records[2].pKey); }); + t.done(); + break; + + default: + assert_unreached("Unexpected count value: " + count); + } + + count++; + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_continue_objectstore5.htm b/testing/web-platform/tests/IndexedDB/idbcursor_continue_objectstore5.htm new file mode 100644 index 0000000000..a0c3663ab4 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_continue_objectstore5.htm @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBCursor.continue() - object store - throw TransactionInactiveError</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"> </div> +<script> + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0" }, + { pKey: "primaryKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", {keyPath:"pKey"}); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + assert_true(cursor instanceof IDBCursor, "cursor exists"); + + e.target.transaction.abort(); + assert_throws_dom("TransactionInactiveError", function() { + cursor.continue(); + }, "Calling continue() should throws an exception TransactionInactiveError when the transaction is not active."); + + + t.done(); + }); + }; +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_continue_objectstore6.htm b/testing/web-platform/tests/IndexedDB/idbcursor_continue_objectstore6.htm new file mode 100644 index 0000000000..7a2b50752c --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_continue_objectstore6.htm @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBCursor.continue() - object store - throw InvalidStateError caused by object store been deleted</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"> </div> +<script> + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0" }, + { pKey: "primaryKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", {keyPath:"pKey"}); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + + var cursor_rq = objStore.openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + assert_true(cursor instanceof IDBCursor, "cursor exists"); + + db.deleteObjectStore("test"); + assert_throws_dom("InvalidStateError", function() { + cursor.continue(); + }, "If the cursor's source or effective object store has been deleted, the implementation MUST throw a DOMException of type InvalidStateError"); + + t.done(); + }); + } +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_delete_index.htm b/testing/web-platform/tests/IndexedDB/idbcursor_delete_index.htm new file mode 100644 index 0000000000..6d1b4e35c9 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_delete_index.htm @@ -0,0 +1,69 @@ +<!DOCTYPE html> +<title>IDBCursor.delete() - index - remove a record from the object store</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + + var db, + count = 0, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var objStore = db.createObjectStore("test", { keyPath: "pKey" }); + objStore.createIndex("index", "iKey"); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = t.step_func(CursorDeleteRecord); + + + function CursorDeleteRecord(e) { + var txn = db.transaction("test", "readwrite", {durability: 'relaxed'}), + cursor_rq = txn.objectStore("test") + .index("index") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + assert_true(cursor instanceof IDBCursor, "cursor exist"); + cursor.delete(); + }); + + txn.oncomplete = t.step_func(VerifyRecordWasDeleted); + } + + + function VerifyRecordWasDeleted(e) { + var cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + if (!cursor) { + assert_equals(count, 1, 'count'); + t.done(); + } + + assert_equals(cursor.value.pKey, records[1].pKey); + assert_equals(cursor.value.iKey, records[1].iKey); + cursor.continue(); + count++; + }); + } + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_delete_index2.htm b/testing/web-platform/tests/IndexedDB/idbcursor_delete_index2.htm new file mode 100644 index 0000000000..054432d65d --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_delete_index2.htm @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<title>IDBCursor.delete() - index - attempt to remove a record in a read-only transaction</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var objStore = db.createObjectStore("test", { keyPath: "pKey" }); + objStore.createIndex("index", "iKey"); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .index("index") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + assert_true(cursor instanceof IDBCursor, "cursor exist"); + assert_throws_dom('ReadOnlyError', function() { cursor.delete(); }); + t.done(); + }); + } + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_delete_index3.htm b/testing/web-platform/tests/IndexedDB/idbcursor_delete_index3.htm new file mode 100644 index 0000000000..afe945e646 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_delete_index3.htm @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<title>IDBCursor.delete() - index - attempt to remove a record in an inactive transaction</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", { keyPath: "pKey" }); + var index = objStore.createIndex("index", "iKey"); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + + var cursor_rq = index.openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + assert_true(cursor instanceof IDBCursor, "cursor exist"); + window.cursor = cursor; + }); + + e.target.transaction.oncomplete = t.step_func(function(e) { + assert_throws_dom('TransactionInactiveError', function() { window.cursor.delete(); }) + t.done(); + }); + } + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_delete_index4.htm b/testing/web-platform/tests/IndexedDB/idbcursor_delete_index4.htm new file mode 100644 index 0000000000..8eb6915b88 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_delete_index4.htm @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBCursor.delete() - index - throw InvalidStateError caused by object store been deleted</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + + var db, + t = async_test(), + records = [{ pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" }]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + var objStore = db.createObjectStore("store", {keyPath : "pKey"}); + objStore.createIndex("index", "iKey"); + for (var i = 0; i < records.length; i++) { + objStore.add(records[i]); + } + var rq = objStore.index("index").openCursor(); + rq.onsuccess = t.step_func(function(event) { + var cursor = event.target.result; + assert_true(cursor instanceof IDBCursor, "cursor exist"); + + db.deleteObjectStore("store"); + assert_throws_dom("InvalidStateError", function() { + cursor.delete(); + }, "If the cursor's source or effective object store has been deleted, the implementation MUST throw a DOMException of type InvalidStateError"); + + t.done(); + }); + } + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_delete_index5.htm b/testing/web-platform/tests/IndexedDB/idbcursor_delete_index5.htm new file mode 100644 index 0000000000..76ce6d0e50 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_delete_index5.htm @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBCursor.delete() - index - throw InvalidStateError when the cursor is being iterated</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBCursor-delete-IDBRequest"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + t = async_test(), + records = [{ pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" }]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + var objStore = db.createObjectStore("store", {keyPath : "pKey"}); + objStore.createIndex("index", "iKey"); + for (var i = 0; i < records.length; i++) { + objStore.add(records[i]); + } + + var rq = objStore.index("index").openCursor(); + rq.onsuccess = t.step_func(function(event) { + var cursor = event.target.result; + assert_true(cursor instanceof IDBCursor, "cursor exist"); + + cursor.continue(); + assert_throws_dom("InvalidStateError", function() { + cursor.delete(); + }); + + t.done(); + }); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_delete_objectstore.htm b/testing/web-platform/tests/IndexedDB/idbcursor_delete_objectstore.htm new file mode 100644 index 0000000000..f79206295a --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_delete_objectstore.htm @@ -0,0 +1,65 @@ +<!DOCTYPE html> +<title>IDBCursor.delete() - object store - remove a record from the object store </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + + var db, + count = 0, + t = async_test(), + records = [ { pKey: "primaryKey_0" }, + { pKey: "primaryKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var objStore = db.createObjectStore("test", { keyPath: "pKey" }); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = t.step_func(CursorDeleteRecord); + + + function CursorDeleteRecord(e) { + var txn = db.transaction("test", "readwrite", {durability: 'relaxed'}), + cursor_rq = txn.objectStore("test").openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + assert_true(cursor != null, "cursor exist"); + cursor.delete(); + }); + + txn.oncomplete = t.step_func(VerifyRecordWasDeleted); + } + + + function VerifyRecordWasDeleted(e) { + var cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + if (!cursor) { + assert_equals(count, 1, 'count'); + t.done(); + } + + assert_equals(cursor.value.pKey, records[1].pKey); + count++; + cursor.continue(); + }); + } + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_delete_objectstore2.htm b/testing/web-platform/tests/IndexedDB/idbcursor_delete_objectstore2.htm new file mode 100644 index 0000000000..ef3c1e57a2 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_delete_objectstore2.htm @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<title>IDBCursor.delete() - object store - attempt to remove a record in a read-only transaction</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var objStore = db.createObjectStore("test", { keyPath: "pKey" }); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + assert_true(cursor != null, "cursor exist"); + assert_throws_dom('ReadOnlyError', function() { cursor.delete(); }); + t.done(); + }); + } + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_delete_objectstore3.htm b/testing/web-platform/tests/IndexedDB/idbcursor_delete_objectstore3.htm new file mode 100644 index 0000000000..4a4ea736c4 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_delete_objectstore3.htm @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<title>IDBCursor.delete() - index - attempt to remove a record in an inactive transaction</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", { keyPath: "pKey" }); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + + var cursor_rq = objStore.openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + assert_true(cursor instanceof IDBCursor, "cursor exist"); + window.cursor = cursor; + }); + + e.target.transaction.oncomplete = t.step_func(function(e) { + assert_throws_dom('TransactionInactiveError', function() { window.cursor.delete(); }) + t.done(); + }); + } + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_delete_objectstore4.htm b/testing/web-platform/tests/IndexedDB/idbcursor_delete_objectstore4.htm new file mode 100644 index 0000000000..5ecdb4bf17 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_delete_objectstore4.htm @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBCursor.delete() - object store - throw InvalidStateError caused by object store been deleted</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + + var db, + t = async_test(), + records = [{ pKey: "primaryKey_0"}, + { pKey: "primaryKey_1"}]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + var objStore = db.createObjectStore("store", {keyPath:"pKey"}); + for (var i = 0; i < records.length; i++) { + objStore.add(records[i]); + } + var rq = objStore.openCursor(); + rq.onsuccess = t.step_func(function(event) { + var cursor = event.target.result; + assert_true(cursor instanceof IDBCursor, "cursor exist"); + + db.deleteObjectStore("store"); + assert_throws_dom("InvalidStateError", function() { + cursor.delete(); + }, "If the cursor's source or effective object store has been deleted, the implementation MUST throw a DOMException of type InvalidStateError"); + + t.done(); + }); + } + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_delete_objectstore5.htm b/testing/web-platform/tests/IndexedDB/idbcursor_delete_objectstore5.htm new file mode 100644 index 0000000000..e17ade88f5 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_delete_objectstore5.htm @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBCursor.delete() - object store - throw InvalidStateError when the cursor is being iterated</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBCursor-delete-IDBRequest"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + t = async_test(), + records = [{ pKey: "primaryKey_0"}, + { pKey: "primaryKey_1"}]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + var objStore = db.createObjectStore("store", {keyPath:"pKey"}); + for (var i = 0; i < records.length; i++) { + objStore.add(records[i]); + } + } + + open_rq.onsuccess = function (event) { + var txn = db.transaction("store", "readwrite", {durability: 'relaxed'}); + var rq = txn.objectStore("store").openCursor(); + rq.onsuccess = t.step_func(function(event) { + var cursor = event.target.result; + assert_true(cursor instanceof IDBCursor, "cursor exist"); + + cursor.continue(); + assert_throws_dom("InvalidStateError", function() { + cursor.delete(); + }); + + t.done(); + }); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_iterating.htm b/testing/web-platform/tests/IndexedDB/idbcursor_iterating.htm new file mode 100644 index 0000000000..4930d1eb1e --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_iterating.htm @@ -0,0 +1,109 @@ +<!DOCTYPE html> +<title>IDBCursor.continue() - objectstore - delete next element, and iterate to it</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + count = 0, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + t.add_cleanup(function() { db.close(); indexedDB.deleteDatabase(db.name); }); + var objStore = db.createObjectStore("test", { keyPath: "key" }); + + for (var i = 0; i < 500; i++) + objStore.add({ key: i, val: "val_"+i }); + + var rq = objStore.add({ key: 500, val: "val_500" }); + + rq.onsuccess = t.step_func(function() { + for (var i = 999; i > 500; i--) + objStore.add({ key: i, val: "val_"+i }); + }); + + objStore.createIndex('index', ['key', 'val']); + }; + + open_rq.onsuccess = function(e) { + var cursor_rq = db.transaction("test", "readwrite", {durability: 'relaxed'}) + .objectStore("test") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result, + store = e.target.source; + if (!cursor) { + assert_equals(count, 997, "cursor run count"); + + var rq = e.target.source.count(); + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result, 995, "object count"); + t.done(); + }); + return; + } + + switch (cursor.key) { + case 10: + assert_equals(count, cursor.key, "count"); + store.delete(11); + break; + + case 12: + case 499: + case 500: + case 501: + assert_equals(count, cursor.key - 1, "count"); + break; + + // Delete the next key + case 510: + store.delete(511); + break; + + // Delete randomly + case 512: + store.delete(611); + store.delete(499); + store.delete(500); + break; + + // Delete and add a new key + case 520: + store.delete(521); + store.add({ key: 521, val: "new"}); + break; + + case 521: + assert_equals(cursor.value.val, "new"); + break; + + // We should only be here once although we're basically making the index + // "heavier" with its new key. + case 530: + assert_equals(cursor.value.val, "val_530"); + cursor.update({ key: 530, val: "val_531" }) + + store.get(530).onsuccess = t.step_func(function(e) { + assert_equals(e.target.result.val, "val_531"); + }); + break; + + // Shouldn't happen. + case 11: + case 511: + case 611: + assert_unreached(cursor.key + " should be deleted and never run"); + break; + } + + cursor.continue(); + count++; + }); + }; +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_iterating_index.htm b/testing/web-platform/tests/IndexedDB/idbcursor_iterating_index.htm new file mode 100644 index 0000000000..be480a0815 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_iterating_index.htm @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<title>IDBCursor.continue() - index - delete next element, and iterate to it</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + count = 0, + t = async_test(), + records = [ { pKey: "primaryKey_0", obj: { iKey: "iKey_0" }}, + { pKey: "primaryKey_1", obj: { iKey: "iKey_1" }}, + { pKey: "primaryKey_2", obj: { iKey: "iKey_2" }} ], + + expected = [ [ "primaryKey_2", "iKey_2" ], + [ "primaryKey_0", "iKey_0" ] ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", {keyPath:["pKey", "obj.iKey"]}); + objStore.createIndex("index", [ "pKey", "obj.iKey" ]); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var cursor_rq = db.transaction("test", "readwrite", {durability: 'relaxed'}) + .objectStore("test") + .index("index") + .openCursor(null, "prev"); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + if (!cursor) { + assert_equals(count, 2, "cursor run count"); + t.done(); + } + + if (count === 0) { + e.target.source.objectStore.delete(["primaryKey_1", "iKey_1"]); + } + assert_array_equals(cursor.key, expected[count], "primary key"); + + cursor.continue(); + count++; + }); + }; +</script> + +<div id="log"> </div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_iterating_index2.htm b/testing/web-platform/tests/IndexedDB/idbcursor_iterating_index2.htm new file mode 100644 index 0000000000..d7af6afdc7 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_iterating_index2.htm @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<title>IDBCursor.continue() - index - add next element, and iterate to it</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + count = 0, + t = async_test(), + records = [ { pKey: "primaryKey_0", obj: { iKey: "iKey_0" }}, + { pKey: "primaryKey_2", obj: { iKey: "iKey_2" }} ], + + expected = [ [ "primaryKey_2", "iKey_2" ], + [ "primaryKey_1", "iKey_1" ], + [ "primaryKey_0", "iKey_0" ] ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", {keyPath:"pKey"}); + objStore.createIndex("index", [ "pKey", "obj.iKey" ]); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var cursor_rq = db.transaction("test", "readwrite", {durability: 'relaxed'}) + .objectStore("test") + .index("index") + .openCursor(null, "prev"); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + if (!cursor) { + assert_equals(count, 3, "cursor run count"); + t.done(); + } + + if (count === 0) { + e.target.source.objectStore.add({ pKey: "primaryKey_1", obj: { iKey: "iKey_1" } }); + } + assert_array_equals(cursor.key, expected[count], "primary key"); + + cursor.continue(); + count++; + }); + }; +</script> + +<div id="log"> </div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_iterating_objectstore.htm b/testing/web-platform/tests/IndexedDB/idbcursor_iterating_objectstore.htm new file mode 100644 index 0000000000..12cec14570 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_iterating_objectstore.htm @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<title>IDBCursor.continue() - objectstore - delete next element, and iterate to it</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + count = 0, + t = async_test(), + records = [ { pKey: "primaryKey_0" }, + { pKey: "primaryKey_1" }, + { pKey: "primaryKey_2" } ], + expected_records = [ { pKey: "primaryKey_0" }, + { pKey: "primaryKey_2" }]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + t.add_cleanup(function() { db.close(); indexedDB.deleteDatabase(db.name); }); + var objStore = db.createObjectStore("test", {keyPath:"pKey"}); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var cursor_rq = db.transaction("test", "readwrite", {durability: 'relaxed'}) + .objectStore("test") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + if (!cursor) { + assert_equals(count, 2, "cursor run count"); + t.done(); + } + + var record = cursor.value; + if (record.pKey == "primaryKey_0") { + e.target.source.delete("primaryKey_1"); + } + assert_equals(record.pKey, expected_records[count].pKey, "primary key"); + + cursor.continue(); + count++; + }); + }; +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_iterating_objectstore2.htm b/testing/web-platform/tests/IndexedDB/idbcursor_iterating_objectstore2.htm new file mode 100644 index 0000000000..2bd9749edf --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_iterating_objectstore2.htm @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<title>IDBCursor.continue() - objectstore - add next element, and iterate to it</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + count = 0, + t = async_test(), + records = [ { pKey: "primaryKey_0" }, + { pKey: "primaryKey_2" } ], + expected_records = [ { pKey: "primaryKey_0" }, + { pKey: "primaryKey_1" }, + { pKey: "primaryKey_2" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + t.add_cleanup(function() { db.close(); indexedDB.deleteDatabase(db.name); }); + var objStore = db.createObjectStore("test", {keyPath:"pKey"}); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var cursor_rq = db.transaction("test", "readwrite", {durability: 'relaxed'}) + .objectStore("test") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + if (!cursor) { + assert_equals(count, 3, "cursor run count"); + t.done(); + } + + var record = cursor.value; + if (record.pKey == "primaryKey_0") { + e.target.source.add({ pKey: "primaryKey_1" }); + } + assert_equals(record.pKey, expected_records[count].pKey, "primary key"); + + cursor.continue(); + count++; + }); + }; +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_update_index.htm b/testing/web-platform/tests/IndexedDB/idbcursor_update_index.htm new file mode 100644 index 0000000000..ce7def4e3c --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_update_index.htm @@ -0,0 +1,64 @@ +<!DOCTYPE html> +<title>IDBCursor.update() - index - modify a record in the object store </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + + var db, + count = 0, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var objStore = db.createObjectStore("test", { keyPath: "pKey" }); + objStore.createIndex("index", "iKey"); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + + // XXX: Gecko doesn't like this + //e.target.transaction.oncomplete = t.step_func(CursorUpdateRecord); + }; + + open_rq.onsuccess = CursorUpdateRecord; + + + function CursorUpdateRecord(e) { + var txn = db.transaction("test", "readwrite", {durability: 'relaxed'}), + cursor_rq = txn.objectStore("test") + .index("index") + .openCursor(); + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + cursor.value.iKey += "_updated"; + cursor.update(cursor.value); + }); + + txn.oncomplete = t.step_func(VerifyRecordWasUpdated); + } + + + function VerifyRecordWasUpdated(e) { + var cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + assert_equals(cursor.value.iKey, records[0].iKey + "_updated"); + t.done(); + }); + } + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_update_index2.htm b/testing/web-platform/tests/IndexedDB/idbcursor_update_index2.htm new file mode 100644 index 0000000000..dee4d19435 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_update_index2.htm @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<title>IDBCursor.update() - index - attempt to modify a record in a read-only transaction</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var objStore = db.createObjectStore("test", { keyPath: "pKey" }); + objStore.createIndex("index", "iKey"); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .index("index") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + assert_throws_dom('ReadOnlyError', function() { cursor.update(cursor.value); }); + t.done(); + }); + } + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_update_index3.htm b/testing/web-platform/tests/IndexedDB/idbcursor_update_index3.htm new file mode 100644 index 0000000000..a7d87eed82 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_update_index3.htm @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<title>IDBCursor.update() - index - attempt to modify a record in an inactive transaction</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", { keyPath: "pKey" }); + var index = objStore.createIndex("index", "iKey"); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + + var cursor_rq = index.openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + assert_true(cursor instanceof IDBCursor, "cursor exist"); + window.cursor = cursor; + window.record = cursor.value; + }); + + e.target.transaction.oncomplete = t.step_func(function(e) { + assert_throws_dom('TransactionInactiveError', function() { window.cursor.update(window.record); }) + t.done(); + }); + } + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_update_index4.htm b/testing/web-platform/tests/IndexedDB/idbcursor_update_index4.htm new file mode 100644 index 0000000000..c7b05270da --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_update_index4.htm @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBCursor.update() - index - attempt to modify a record when object store been deleted</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + var objStore = db.createObjectStore("store", {keyPath : "pKey"}); + objStore.createIndex("index", "iKey"); + for (var i = 0; i < records.length; i++) { + objStore.add(records[i]); + } + var rq = objStore.index("index").openCursor(); + rq.onsuccess = t.step_func(function(event) { + var cursor = event.target.result; + assert_true(cursor instanceof IDBCursor); + + db.deleteObjectStore("store"); + cursor.value.iKey += "_updated"; + assert_throws_dom("InvalidStateError", function() { + cursor.update(cursor.value); + }, "If the cursor's source or effective object store has been deleted, the implementation MUST throw a DOMException of type InvalidStateError"); + + t.done(); + }); + } + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_update_index5.htm b/testing/web-platform/tests/IndexedDB/idbcursor_update_index5.htm new file mode 100644 index 0000000000..b4395f901b --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_update_index5.htm @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<title>IDBCursor.update() - index - throw DataCloneError</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBCursor-update-IDBRequest-any-value"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var objStore = db.createObjectStore("test", { keyPath: "pKey" }); + objStore.createIndex("index", "iKey"); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var cursor_rq = db.transaction("test", "readwrite", {durability: 'relaxed'}) + .objectStore("test") + .index("index") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + assert_true(cursor instanceof IDBCursor); + + var record = cursor.value; + record.data = document; + assert_throws_dom('DataCloneError', function() { + cursor.update(record); + }); + t.done(); + }); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_update_index6.htm b/testing/web-platform/tests/IndexedDB/idbcursor_update_index6.htm new file mode 100644 index 0000000000..dd39329035 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_update_index6.htm @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<title>IDBCursor.update() - index - no argument</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBCursor-update-IDBRequest-any-value"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var objStore = db.createObjectStore("test", { keyPath: "pKey" }); + objStore.createIndex("index", "iKey"); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .index("index") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + assert_true(cursor instanceof IDBCursor); + + assert_throws_js(TypeError, function() { cursor.update(); }); + t.done(); + }); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_update_index7.htm b/testing/web-platform/tests/IndexedDB/idbcursor_update_index7.htm new file mode 100644 index 0000000000..5cba5ababf --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_update_index7.htm @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<title>IDBCursor.update() - index - throw DataError</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBCursor-update-IDBRequest-any-value"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var objStore = db.createObjectStore("test", { keyPath: "pKey" }); + objStore.createIndex("index", "iKey"); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var cursor_rq = db.transaction("test", "readwrite", {durability: 'relaxed'}) + .objectStore("test") + .index("index") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + assert_true(cursor instanceof IDBCursor); + + assert_throws_dom('DataError', function() { cursor.update(null); }); + t.done(); + }); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_update_index8.htm b/testing/web-platform/tests/IndexedDB/idbcursor_update_index8.htm new file mode 100644 index 0000000000..14b72ba3fd --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_update_index8.htm @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<title>IDBCursor.update() - index - throw InvalidStateError when the cursor is being iterated</title> +<link rel="author" title="Mozilla" href="https://www.mozilla.org"> +<link rel="help" href="https://www.w3.org/TR/IndexedDB/#widl-IDBCursor-update-IDBRequest-any-value"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var objStore = db.createObjectStore("store", { keyPath: "pKey" }); + objStore.createIndex("index", "iKey"); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var cursor_rq = db.transaction("store", "readwrite", {durability: 'relaxed'}) + .objectStore("store") + .index("index") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + assert_true(cursor instanceof IDBCursor, "cursor exists"); + + cursor.continue(); + assert_throws_dom("InvalidStateError", function() { + cursor.update({ pKey: "primaryKey_0", iKey: "indexKey_0_updated" }); + }); + + t.done(); + }); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_update_index9.any.js b/testing/web-platform/tests/IndexedDB/idbcursor_update_index9.any.js new file mode 100644 index 0000000000..aec277e56b --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_update_index9.any.js @@ -0,0 +1,43 @@ +// META: script=resources/support-promises.js + +promise_test(async t => { + const db = await createDatabase(t, db => { + const store = db.createObjectStore('store'); + store.createIndex('index', 'value'); + store.put({value: 1}, 1); + store.put({value: 2}, 2); + store.put({value: 3}, 3); + }); + + { + // Iterate over all index entries until an upper bound is reached. + // On each record found, increment the value used as the index + // key, which will make it show again up later in the iteration. + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + const range = IDBKeyRange.upperBound(9); + const index = tx.objectStore('store').index('index'); + const request = index.openCursor(range); + request.onsuccess = t.step_func(e => { + const cursor = e.target.result; + if (!cursor) + return; + + const record = cursor.value; + record.value += 1; + cursor.update(record); + + cursor.continue(); + }); + + await promiseForTransaction(t, tx); + } + + { + const tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + const results = await promiseForRequest(t, tx.objectStore('store').getAll()); + assert_array_equals( + results.map(record => record.value), + [10, 10, 10], + 'Values should all be incremented until bound reached'); + } +}, 'Index cursor - indexed values updated during iteration'); diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore.htm b/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore.htm new file mode 100644 index 0000000000..e1fdff6047 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore.htm @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<title>IDBCursor.update() - objectstore - modify a record in the object store </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0" }, + { pKey: "primaryKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", { keyPath: "pKey" }); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + + // XXX: Gecko doesn't like this + //e.target.transaction.oncomplete = t.step_func(CursorUpdateRecord); + }; + + open_rq.onsuccess = CursorUpdateRecord; + + + function CursorUpdateRecord(e) { + var txn = db.transaction("test", "readwrite", {durability: 'relaxed'}), + cursor_rq = txn.objectStore("test") + .openCursor(); + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + cursor.value.data = "New information!"; + cursor.update(cursor.value); + }); + + txn.oncomplete = t.step_func(VerifyRecordWasUpdated); + } + + + function VerifyRecordWasUpdated(e) { + var cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + assert_equals(cursor.value.data, "New information!"); + t.done(); + }); + } + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore2.htm b/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore2.htm new file mode 100644 index 0000000000..73dcd34f5b --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore2.htm @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<title>IDBCursor.update() - object store - attempt to modify a record in a read-only transaction</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var objStore = db.createObjectStore("test", { keyPath: "pKey" }); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var cursor_rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + assert_throws_dom('ReadOnlyError', function() { cursor.update(cursor.value); }); + t.done(); + }); + } + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore3.htm b/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore3.htm new file mode 100644 index 0000000000..3582f0967c --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore3.htm @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<title>IDBCursor.update() - object store - attempt to modify a record in an inactive transaction</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", { keyPath: "pKey" }); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + + var cursor_rq = objStore.openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + assert_true(cursor instanceof IDBCursor, "cursor exist"); + window.cursor = cursor; + window.record = cursor.value; + }); + + e.target.transaction.oncomplete = t.step_func(function(e) { + assert_throws_dom('TransactionInactiveError', function() { window.cursor.update(window.record); }) + t.done(); + }); + } + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore4.htm b/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore4.htm new file mode 100644 index 0000000000..b467c296ed --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore4.htm @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<title>IDBCursor.update() - index - modify a record in the object store </title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + + var db, + t = async_test() + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test"); + + objStore.add("data", "key"); + }; + + open_rq.onsuccess = t.step_func(function(e) { + var txn = db.transaction("test", "readwrite", {durability: 'relaxed'}), + cursor_rq = txn.objectStore("test") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + cursor.value = "new data!"; + cursor.update(cursor.value).onsuccess = t.step_func(function(e) { + assert_equals(e.target.result, "key"); + t.done(); + }); + }); + }); + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore5.htm b/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore5.htm new file mode 100644 index 0000000000..e358caa392 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore5.htm @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBCursor.update() - object store - attempt to modify a record when object store been deleted</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0" }, + { pKey: "primaryKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", {keyPath:"pKey"}); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + + var cursor_rq = objStore.openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + assert_true(cursor instanceof IDBCursor, "cursor exists"); + + db.deleteObjectStore("test"); + cursor.value += "_updated"; + assert_throws_dom("InvalidStateError", function() { + cursor.update(cursor.value); + }, "If the cursor's source or effective object store has been deleted, the implementation MUST throw a DOMException of type InvalidStateError"); + + + t.done(); + }); + } + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore6.htm b/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore6.htm new file mode 100644 index 0000000000..26d42c116d --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore6.htm @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<title>IDBCursor.update() - object store - throw DataCloneError</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBCursor-update-IDBRequest-any-value"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var objStore = db.createObjectStore("test", { keyPath: "pKey" }); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var cursor_rq = db.transaction("test", "readwrite", {durability: 'relaxed'}) + .objectStore("test") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + assert_true(cursor instanceof IDBCursor); + + var record = cursor.value; + record.data = document; + assert_throws_dom('DataCloneError', function() { + cursor.update(record); + }); + t.done(); + }); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore7.htm b/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore7.htm new file mode 100644 index 0000000000..fc850bbcf1 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore7.htm @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<title>IDBCursor.update() - object store - no argument</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBCursor-update-IDBRequest-any-value"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var objStore = db.createObjectStore("test", { keyPath: "pKey" }); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var cursor_rq = db.transaction("test", "readwrite", {durability: 'relaxed'}) + .objectStore("test") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + assert_true(cursor instanceof IDBCursor); + + assert_throws_js(TypeError, function() { cursor.update(); }); + t.done(); + }); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore8.htm b/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore8.htm new file mode 100644 index 0000000000..4d3439794e --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore8.htm @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<title>IDBCursor.update() - object store - throw DataError</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBCursor-update-IDBRequest-any-value"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + t = async_test(), + records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, + { pKey: "primaryKey_1", iKey: "indexKey_1" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var objStore = db.createObjectStore("test", { keyPath: "pKey" }); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var cursor_rq = db.transaction("test", "readwrite", {durability: 'relaxed'}) + .objectStore("test") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + assert_true(cursor instanceof IDBCursor); + + assert_throws_dom('DataError', function() { cursor.update(null); }); + t.done(); + }); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore9.htm b/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore9.htm new file mode 100644 index 0000000000..39220f67be --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbcursor_update_objectstore9.htm @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBCursor.update() - object store - throw InvalidStateError when the cursor is being iterated</title> +<link rel="author" title="Mozilla" href="https://www.mozilla.org"> +<link rel="help" href="https://www.w3.org/TR/IndexedDB/#widl-IDBCursor-update-IDBRequest-any-value"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + t = async_test(), + records = [{ pKey: "primaryKey_0", value: "value_0" }, + { pKey: "primaryKey_1", value: "value_1" }]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + + var objStore = db.createObjectStore("store", {keyPath : "pKey"}); + + for (var i = 0; i < records.length; i++) { + objStore.add(records[i]); + } + } + + open_rq.onsuccess = function(e) { + var cursor_rq = db.transaction("store", "readwrite", {durability: 'relaxed'}) + .objectStore("store") + .openCursor(); + + cursor_rq.onsuccess = t.step_func(function(event) { + var cursor = event.target.result; + assert_true(cursor instanceof IDBCursor, "cursor exists"); + + cursor.continue(); + assert_throws_dom("InvalidStateError", function() { + cursor.update({ pKey: "primaryKey_0", value: "value_0_updated" }); + }); + + t.done(); + }); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase-createObjectStore-exception-order.htm b/testing/web-platform/tests/IndexedDB/idbdatabase-createObjectStore-exception-order.htm new file mode 100644 index 0000000000..1ca2ce1d7c --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase-createObjectStore-exception-order.htm @@ -0,0 +1,84 @@ +<!DOCTYPE html> +<title>IndexedDB: IDBDatabase createObjectStore() Exception Ordering</title> +<meta charset=utf-8> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbdatabase-createobjectstore"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + (t, db, txn, rq) => { + db.createObjectStore('s'); + + // Acknowledge the error, to prevent window.error from firing in + // browsers that implement that. + rq.onerror = e => { e.preventDefault(); }; + + txn.onabort = () => { + setTimeout(t.step_func(() => { + assert_throws_dom( + 'InvalidStateError', () => { db.createObjectStore('s2'); }, + '"running an upgrade transaction" check (InvalidStateError) ' + + 'should precede "not active" check (TransactionInactiveError)'); + + t.done(); + }), 0); + }; + txn.abort(); + }, + (t, db) => { t.assert_unreached('open should fail'); }, + 'IDBDatabase.createObjectStore exception order: ' + + 'InvalidStateError vs. TransactionInactiveError', + { upgrade_will_abort: true } +); + +indexeddb_test( + (t, db, txn) => { + const store = db.createObjectStore('s'); + + txn.abort(); + + assert_throws_dom( + 'TransactionInactiveError', + () => { db.createObjectStore('s2', {keyPath: '-invalid-'}); }, + '"not active" check (TransactionInactiveError) should precede ' + + '"valid key path" check (SyntaxError)'); + + t.done(); + }, + (t, db) => { t.assert_unreached('open should fail'); }, + 'IDBDatabase.createObjectStore exception order: ' + + 'TransactionInactiveError vs. SyntaxError', + { upgrade_will_abort: true } +); + +indexeddb_test( + (t, db) => { + db.createObjectStore('s'); + assert_throws_dom('SyntaxError', () => { + db.createObjectStore('s', {keyPath: 'not a valid key path'}); + }, '"Invalid key path" check (SyntaxError) should precede ' + + '"duplicate store name" check (ConstraintError)'); + t.done(); + }, + (t, db) => {}, + 'IDBDatabase.createObjectStore exception order: ' + + 'SyntaxError vs. ConstraintError' +); + +indexeddb_test( + (t, db) => { + db.createObjectStore('s'); + assert_throws_dom('ConstraintError', () => { + db.createObjectStore('s', {autoIncrement: true, keyPath: ''}); + }, '"already exists" check (ConstraintError) should precede ' + + '"autoIncrement vs. keyPath" check (InvalidAccessError)'); + t.done(); + }, + (t, db) => {}, + 'IDBDatabase.createObjectStore exception order: ' + + 'ConstraintError vs. InvalidAccessError' +); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase-deleteObjectStore-exception-order.htm b/testing/web-platform/tests/IndexedDB/idbdatabase-deleteObjectStore-exception-order.htm new file mode 100644 index 0000000000..4d98a31f12 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase-deleteObjectStore-exception-order.htm @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<title>IndexedDB: IDBDatabase deleteObjectStore() Exception Ordering</title> +<meta charset=utf-8> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbdatabase-deleteobjectstore"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + (t, db, txn) => { + db.createObjectStore('s'); + txn.onabort = () => { + setTimeout(t.step_func(() => { + assert_throws_dom( + 'InvalidStateError', () => { db.deleteObjectStore('s'); }, + '"running an upgrade transaction" check (InvalidStateError) ' + + 'should precede "not active" check (TransactionInactiveError)'); + t.done(); + }), 0); + }; + txn.abort(); + }, + (t, db) => { t.assert_unreached('open should fail'); }, + 'IDBDatabase.deleteObjectStore exception order: ' + + 'InvalidStateError vs. TransactionInactiveError', + { upgrade_will_abort: true } +); + +indexeddb_test( + (t, db, txn) => { + txn.abort(); + assert_throws_dom( + 'TransactionInactiveError', () => { db.deleteObjectStore('nope'); }, + '"not active" check (TransactionInactiveError) should precede ' + + '"name in database" check (NotFoundError)'); + t.done(); + }, + (t, db) => { t.assert_unreached('open should fail'); }, + 'IDBDatabase.deleteObjectStore exception order: ' + + 'TransactionInactiveError vs. NotFoundError', + { upgrade_will_abort: true } +); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase-transaction-exception-order.html b/testing/web-platform/tests/IndexedDB/idbdatabase-transaction-exception-order.html new file mode 100644 index 0000000000..d06efe1d2f --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase-transaction-exception-order.html @@ -0,0 +1,68 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: IDBDatabase transaction() Exception Ordering</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbdatabase-transaction"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + (t, db) => { + db.createObjectStore('s'); + }, + (t, db) => { + db.close(); + assert_throws_dom('InvalidStateError', () => { + db.transaction('no-such-store'); + }, '"Connection is closed" check (InvalidStateError) should precede ' + + '"store names" check (NotFoundError)'); + t.done(); + }, + 'IDBDatabase.transaction exception order: InvalidStateError vs. NotFoundError' +); + +indexeddb_test( + (t, db) => { + db.createObjectStore('s'); + }, + (t, db) => { + db.close(); + assert_throws_dom('InvalidStateError', () => { + db.transaction([]); + }, '"Connection is closed" check (InvalidStateError) should precede ' + + '"stores is empty" check (InvalidAccessError)'); + t.done(); + }, + 'IDBDatabase.transaction exception order: InvalidStateError vs. InvalidAccessError' +); + +// Verify that the invalid mode check actually throws an exception +indexeddb_test( + (t, db) => { + db.createObjectStore('s'); + }, + (t, db) => { + assert_throws_js(TypeError, () => { + db.transaction('s', 'versionchange'); + }, '"invalid mode" check should throw TypeError'); + t.done(); + }, + 'IDBDatabase.transaction throws exception on invalid mode' +); + +indexeddb_test( + (t, db) => { + db.createObjectStore('s'); + }, + (t, db) => { + assert_throws_dom('NotFoundError', () => { + db.transaction('no-such-store', 'versionchange'); + }, '"No such store" check (NotFoundError) should precede ' + + '"invalid mode" check (TypeError)'); + t.done(); + }, + 'IDBDatabase.transaction exception order: NotFoundError vs. TypeError' +); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase_close.htm b/testing/web-platform/tests/IndexedDB/idbdatabase_close.htm new file mode 100644 index 0000000000..8ebebaf1b8 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase_close.htm @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<title>IDBDatabase.close() - unblock the version change transaction created by an open database request</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> +var db; +var versionchange_fired; +var blocked_fired; +var upgradeneeded_fired; +var t = async_test(); +var open_rq = createdb(t); +var counter = 0; + +open_rq.onupgradeneeded = function() {} +open_rq.onsuccess = function(e) { + db = e.target.result; + db.onversionchange = t.step_func(function(e) { + versionchange_fired = counter++; + }); + var rq = window.indexedDB.open(db.name, db.version + 1); + rq.onblocked = t.step_func(function (e) { + blocked_fired = counter++; + db.close(); + }); + rq.onupgradeneeded = t.step_func(function (e) { + upgradeneeded_fired = counter++; + }); + rq.onsuccess = t.step_func(function (e) { + assert_equals(versionchange_fired, 0, 'versionchange event fired #') + assert_equals(blocked_fired, 1, 'block event fired #') + assert_equals(upgradeneeded_fired, 2, 'second upgradeneeded event fired #') + + rq.result.close(); + t.done(); + }); + rq.onerror = fail(t, 'Unexpected database deletion error'); +}; + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase_close2.htm b/testing/web-platform/tests/IndexedDB/idbdatabase_close2.htm new file mode 100644 index 0000000000..26bc4ce638 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase_close2.htm @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<title>IDBDatabase.close() - unblock the delete database request</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + +var db; +var blocked_fired = false; +var versionchange_fired = false; +var t = async_test(); +var open_rq = createdb(t); + +open_rq.onupgradeneeded = t.step_func(function() {}); +open_rq.onsuccess = t.step_func(function(e) { + db = e.target.result; + + db.onversionchange = t.step_func(function (e) { + versionchange_fired = true; + }); + + var rq = window.indexedDB.deleteDatabase(db.name); + rq.onblocked = t.step_func(function (e) { + blocked_fired = true; + db.close(); + }); + rq.onsuccess = t.step_func(function (e) { + assert_true(versionchange_fired, "versionchange event fired") + assert_true(blocked_fired, "block event fired") + t.done(); + }); + rq.onerror = fail(t, 'Unexpected database deletion error'); +}); + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore-createIndex-emptyname.htm b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore-createIndex-emptyname.htm new file mode 100644 index 0000000000..161902c2ab --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore-createIndex-emptyname.htm @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBDatabase.createObjectStore() and IDBObjectStore.createIndex() - both with empty name</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var db + + var open_rq = createdb(async_test()) + open_rq.onupgradeneeded = function(e) { + db = e.target.result + var store = db.createObjectStore("") + + for (var i = 0; i < 5; i++) + store.add({ idx: "object_" + i }, i) + + store.createIndex("", "idx") + + store.get(4) + .onsuccess = this.step_func(function(e) { + assert_equals(e.target.result.idx, 'object_4', 'result') + }) + assert_equals(store.indexNames[0], "", "indexNames[0]") + assert_equals(store.indexNames.length, 1, "indexNames.length") + } + + open_rq.onsuccess = function() { + var store = db.transaction("").objectStore("") + + assert_equals(store.indexNames[0], "", "indexNames[0]") + assert_equals(store.indexNames.length, 1, "indexNames.length") + + store.index("") + .get('object_4') + .onsuccess = this.step_func(function(e) { + assert_equals(e.target.result.idx, 'object_4', 'result') + this.done() + }) + } +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore.htm b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore.htm new file mode 100644 index 0000000000..3379ab58f5 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore.htm @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<title>IDBDatabase.createObjectStore() - returns an instance of IDBObjectStore</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + +var t = async_test(), + open_rq = createdb(t) + +open_rq.onupgradeneeded = function(e) { + var db = e.target.result + var objStore = db.createObjectStore('instancetest') + + assert_true(objStore instanceof IDBObjectStore, 'instanceof IDBObjectStore') +} + +open_rq.onsuccess = function(e) { + var db = e.target.result + var objStore = db.transaction('instancetest', 'readonly', {durability: 'relaxed'}).objectStore('instancetest') + + assert_true(objStore instanceof IDBObjectStore, 'instanceof IDBObjectStore') + t.done() +} +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore10-1000ends.htm b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore10-1000ends.htm new file mode 100644 index 0000000000..6997562d3a --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore10-1000ends.htm @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<title>IDBDatabase.createObjectStore() - create 1000 object stores, add one item and delete</title> +<meta name=timeout content=long> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> +var db, + t = async_test(), + open_rq = createdb(t) + +open_rq.onupgradeneeded = function(e) { + db = e.target.result + var st, i; + for (i = 0; i < 1000; i++) + { + st = db.createObjectStore("object_store_" + i) + st.add("test", 1); + } + + st.get(1).onsuccess = t.step_func(function(e) { + assert_equals(e.target.result, "test") + }) +} +open_rq.onsuccess = function(e) { + db.close() + window.indexedDB.deleteDatabase(db.name).onsuccess = function(e) { + t.done() + } +} +</script> + + +<div id="log"></div> + + diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore10-emptyname.htm b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore10-emptyname.htm new file mode 100644 index 0000000000..0ed28e9e7e --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore10-emptyname.htm @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBDatabase.createObjectStore() - empty name</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var db + + var open_rq = createdb(async_test()) + open_rq.onupgradeneeded = function(e) { + db = e.target.result + var store = db.createObjectStore("") + + for (var i = 0; i < 5; i++) + store.add("object_" + i, i) + + assert_equals(db.objectStoreNames[0], "", "db.objectStoreNames[0]") + assert_equals(db.objectStoreNames.length, 1, "objectStoreNames.length") + } + + open_rq.onsuccess = function() { + var store = db.transaction("").objectStore("") + + store.get(2).onsuccess = this.step_func(function(e) { + assert_equals(e.target.result, "object_2") + }) + + assert_equals(db.objectStoreNames[0], "", "db.objectStoreNames[0]") + assert_equals(db.objectStoreNames.length, 1, "objectStoreNames.length") + + this.done() + } +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore11.htm b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore11.htm new file mode 100644 index 0000000000..f1a9310c07 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore11.htm @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBDatabase.createObjectStore() - attempting to create an existing object store with a different keyPath throw ConstraintError </title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBDatabase-createObjectStore-IDBObjectStore-DOMString-name-IDBObjectStoreParameters-optionalParameters"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> +var t = async_test(), + open_rq = createdb(t); + +open_rq.onupgradeneeded = function (e) { + var db = e.target.result; + db.createObjectStore("store"); + assert_throws_dom("ConstraintError", function(){ + db.createObjectStore("store", { + keyPath: "key1", + }); + }); + t.done(); +} +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore2.htm b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore2.htm new file mode 100644 index 0000000000..55c73d6228 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore2.htm @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<title>IDBDatabase.createObjectStore() - object store 'name' and 'keyPath' properties are correctly set </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + +var t = async_test(), + open_rq = createdb(t) + +open_rq.onupgradeneeded = function(e) { + var db = e.target.result, + objStore = db.createObjectStore("prop", { keyPath: "mykeypath" }) + + assert_equals(objStore.name, "prop", "object store name") + assert_equals(objStore.keyPath, "mykeypath", "key path") + assert_equals(objStore.autoIncrement, false, "auto increment") +} + +open_rq.onsuccess = function(e) { + var db = e.target.result + var objStore = db.transaction('prop', 'readonly', {durability: 'relaxed'}).objectStore('prop') + + assert_equals(objStore.name, "prop", "object store name") + assert_equals(objStore.keyPath, "mykeypath", "key path") + assert_equals(objStore.autoIncrement, false, "auto increment") + t.done() +} +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore3.htm b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore3.htm new file mode 100644 index 0000000000..d0006bfe1f --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore3.htm @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<title>IDBDatabase.createObjectStore() - attempt to create an object store outside of a version change transaction </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + +var t = async_test(), + open_rq = createdb(t) + +open_rq.onupgradeneeded = function() {} +open_rq.onsuccess = function (e) { + var db = e.target.result + assert_throws_dom( + 'InvalidStateError', + function() { db.createObjectStore('fails') }) + t.done(); +} + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore4.htm b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore4.htm new file mode 100644 index 0000000000..bd5989335a --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore4.htm @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<title>IDBDatabase.createObjectStore() - attempt to create an object store that already exists </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + +var t = async_test(), + open_rq = createdb(t) + +open_rq.onupgradeneeded = function(e) { + var db = e.target.result + db.createObjectStore("dupe") + assert_throws_dom( + 'ConstraintError', + function() { db.createObjectStore("dupe") }) + + // Bonus test creating a new objectstore after the exception + db.createObjectStore("dupe ") + t.done() +} + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore5.htm b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore5.htm new file mode 100644 index 0000000000..a30b73c594 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore5.htm @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<title>IDBDatabase.createObjectStore() - object store's name appears in database's list </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + +var t = async_test(), + open_rq = createdb(t) + +open_rq.onupgradeneeded = function(e) { + var db = e.target.result + + db.createObjectStore("My cool object store name") + assert_true( + db.objectStoreNames.contains("My cool object store name"), + 'objectStoreNames.contains') +} + +open_rq.onsuccess = function(e) { + var db = e.target.result + + assert_true( + db.objectStoreNames.contains("My cool object store name"), + 'objectStoreNames.contains (in success)') + t.done() +} + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore6.htm b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore6.htm new file mode 100644 index 0000000000..54a2df81e9 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore6.htm @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<title>IDBDatabase.createObjectStore() - attempt to create an object store with an invalid key path </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + +var t = async_test(), + open_rq = createdb(t) + +open_rq.onupgradeneeded = function(e) { + var db = e.target.result + + assert_throws_dom('SyntaxError', function() { + db.createObjectStore("invalidkeypath", { keyPath: "Invalid Keypath" }) + }) + + assert_throws_dom('SyntaxError', function() { + db.createObjectStore("invalidkeypath", { autoIncrement: true, + keyPath: "Invalid Keypath" }) + }) + + t.done() +} + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore7.htm b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore7.htm new file mode 100644 index 0000000000..b2a7a03b1c --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore7.htm @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<title>IDBDatabase.createObjectStore() - create an object store with an unknown optional parameter </title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + +var t = async_test(), + open_rq = createdb(t) + +open_rq.onupgradeneeded = function(e) { + var db = e.target.result + db.createObjectStore("with unknown param", { parameter: 0 }); + + t.done() +} + +</script> + + +<div id="log"></div> + + diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore8-parameters.htm b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore8-parameters.htm new file mode 100644 index 0000000000..99a473d418 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore8-parameters.htm @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStoreParameters</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<meta name=timeout content=long> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + function optionalParameters(desc, params) { + var t = async_test(document.title + " - " + desc); + + createdb(t).onupgradeneeded = function(e) { + e.target.result.createObjectStore("store", params); + + this.done(); + }; + } + + + optionalParameters("autoInc true", {autoIncrement: true}); + optionalParameters("autoInc true, keyPath null", {autoIncrement: true, keyPath: null}); + optionalParameters("autoInc true, keyPath undefined", {autoIncrement: true, keyPath: undefined}); + optionalParameters("autoInc true, keyPath string", {autoIncrement: true, keyPath: "a"}); + + optionalParameters("autoInc false, keyPath empty", {autoIncrement: false, keyPath: ""}); + optionalParameters("autoInc false, keyPath array", {autoIncrement: false, keyPath: ["h", "j"]}); + optionalParameters("autoInc false, keyPath string", {autoIncrement: false, keyPath: "abc"}); + + optionalParameters("keyPath empty", {keyPath: ""}); + optionalParameters("keyPath array", {keyPath: ["a","b"]}); + optionalParameters("keyPath string", {keyPath: "abc"}); + optionalParameters("keyPath null", {keyPath: null}); + optionalParameters("keyPath undefined", {keyPath: undefined}); + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore9-invalidparameters.htm b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore9-invalidparameters.htm new file mode 100644 index 0000000000..65b7535abb --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase_createObjectStore9-invalidparameters.htm @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>createObjectStore: Invalid optionalParameters</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + function invalid_optionalParameters(desc, params, exception = "InvalidAccessError") { + var t = async_test(document.title + " - " + desc); + + createdb(t).onupgradeneeded = function(e) { + assert_throws_dom(exception, function() { + e.target.result.createObjectStore("store", params); + }); + + this.done(); + }; + } + + invalid_optionalParameters("autoInc and empty keyPath", {autoIncrement: true, keyPath: ""}); + invalid_optionalParameters("autoInc and keyPath array", {autoIncrement: true, keyPath: []}, "SyntaxError"); + invalid_optionalParameters("autoInc and keyPath array 2", {autoIncrement: true, keyPath: ["hey"]}); + invalid_optionalParameters("autoInc and keyPath object", {autoIncrement: true, keyPath: {a:"hey", b:2}}, "SyntaxError"); + +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase_deleteObjectStore.htm b/testing/web-platform/tests/IndexedDB/idbdatabase_deleteObjectStore.htm new file mode 100644 index 0000000000..e3f6a775c5 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase_deleteObjectStore.htm @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<title>IDBDatabase.deleteObjectStore() - object store's name is removed from database's list </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + +var t = async_test(), + open_rq = createdb(t) + +open_rq.onupgradeneeded = function(e) { + var db = e.target.result + + db.createObjectStore("deleted"); + db.deleteObjectStore("deleted"); + assert_false(db.objectStoreNames.contains("deleted")) + + t.done() +} + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase_deleteObjectStore2.htm b/testing/web-platform/tests/IndexedDB/idbdatabase_deleteObjectStore2.htm new file mode 100644 index 0000000000..91f118ef62 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase_deleteObjectStore2.htm @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<title>IDBDatabase.deleteObjectStore() - attempt to remove an object store outside of a version change transaction </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + +var t = async_test(), + open_rq = createdb(t); + +open_rq.onupgradeneeded = function(e) +{ + var db = e.target.result, + objStore = db.createObjectStore("delete_outside"); + + e.target.transaction.oncomplete = t.step_func(function (e) + { + assert_throws_dom('InvalidStateError', + function() { db.deleteObjectStore("delete_outside"); }); + t.done(); + }); +} + +</script> + + +<div id="log"></div> + + diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase_deleteObjectStore3.htm b/testing/web-platform/tests/IndexedDB/idbdatabase_deleteObjectStore3.htm new file mode 100644 index 0000000000..3ddbe8ec62 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase_deleteObjectStore3.htm @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<title>IDBDatabase.deleteObjectStore() - attempt to remove an object store that does not exist </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + +var t = async_test(), + open_rq = createdb(t); + +open_rq.onupgradeneeded = function(e) +{ + var db = e.target.result; + assert_throws_dom('NotFoundError', + function() { db.deleteObjectStore('whatever'); }); + t.done(); +} + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase_deleteObjectStore4-not_reused.htm b/testing/web-platform/tests/IndexedDB/idbdatabase_deleteObjectStore4-not_reused.htm new file mode 100644 index 0000000000..0a5e1b83cf --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase_deleteObjectStore4-not_reused.htm @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBDatabase.deleteObjectStore() - the object store is not reused</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + +var t = async_test(), + keys = [], + open_rq = createdb(t) + +open_rq.onupgradeneeded = function(e) { + var db = e.target.result + + var objStore = db.createObjectStore("resurrected", { autoIncrement: true, keyPath: "k" }); + objStore.add({k:5}).onsuccess = function(e) { keys.push(e.target.result); } + objStore.add({}).onsuccess = function(e) { keys.push(e.target.result); } + objStore.createIndex("idx", "i"); + assert_true(objStore.indexNames.contains("idx")); + assert_equals(objStore.keyPath, "k", "keyPath"); + + db.deleteObjectStore("resurrected"); + + var objStore2 = db.createObjectStore("resurrected", { autoIncrement: true }); + objStore2.add("Unicorns'R'us").onsuccess = function(e) { keys.push(e.target.result); }; + assert_false(objStore2.indexNames.contains("idx"), "index exist on new objstore"); + assert_equals(objStore2.keyPath, null, "keyPath"); + + assert_throws_dom("NotFoundError", function() { objStore2.index("idx"); }); +} + +open_rq.onsuccess = function(e) { + assert_array_equals(keys, [5, 6, 1], "keys"); + t.done(); +} + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase_transaction.htm b/testing/web-platform/tests/IndexedDB/idbdatabase_transaction.htm new file mode 100644 index 0000000000..8e8264f8ea --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase_transaction.htm @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<title>IDBDatabase.transaction() - attempt to open a transaction with invalid scope</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + +var db, + t = async_test(), + open_rq = createdb(t); + +open_rq.onupgradeneeded = function() {}; +open_rq.onsuccess = function(e) { + db = e.target.result; + + assert_throws_dom('NotFoundError', function() { db.transaction('non-existing'); }); + t.done(); +}; + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase_transaction2.htm b/testing/web-platform/tests/IndexedDB/idbdatabase_transaction2.htm new file mode 100644 index 0000000000..37b1122919 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase_transaction2.htm @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<title>IDBDatabase.transaction() - opening a transaction defaults to a read-only mode </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + +var db, + t = async_test(), + open_rq = createdb(t); + +open_rq.onupgradeneeded = function(e) { + db = e.target.result; + db.createObjectStore('readonly'); +}; +open_rq.onsuccess = function(e) { + var txn = db.transaction('readonly', 'readonly', {durability: 'relaxed'}); + assert_equals(txn.mode, "readonly", 'txn.mode'); + + t.done(); +}; + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase_transaction3.htm b/testing/web-platform/tests/IndexedDB/idbdatabase_transaction3.htm new file mode 100644 index 0000000000..1eea31f764 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase_transaction3.htm @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<title>IDBDatabase.transaction() - attempt to open a transaction from closed database connection </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + open_rq = createdb(t); + + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + db.createObjectStore('test'); + }; + + open_rq.onsuccess = function(e) { + db.close(); + + assert_throws_dom('InvalidStateError', + function() { db.transaction('test', 'readonly', {durability: 'relaxed'}); }); + + t.done(); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase_transaction4.htm b/testing/web-platform/tests/IndexedDB/idbdatabase_transaction4.htm new file mode 100644 index 0000000000..3a164c25f5 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase_transaction4.htm @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<title>IDBDatabase.transaction() - attempt to open a transaction with invalid mode </title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + open_rq = createdb(t); + + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + db.createObjectStore('test'); + }; + + open_rq.onsuccess = function(e) { + assert_throws_js(TypeError, + function() { db.transaction('test', 'whatever'); }); + + t.done(); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbdatabase_transaction5.htm b/testing/web-platform/tests/IndexedDB/idbdatabase_transaction5.htm new file mode 100644 index 0000000000..451939731f --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbdatabase_transaction5.htm @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBDatabase.transaction() - If storeNames is an empty list, the implementation must throw a DOMException of type InvalidAccessError</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBDatabase-transaction-IDBTransaction-DOMString-sequence-DOMString--storeNames-IDBTransactionMode-mode"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> +var db, + t = async_test(), + open_rq = createdb(t); + +open_rq.onupgradeneeded = function() {}; +open_rq.onsuccess = function(e) { + db = e.target.result; + assert_throws_dom('InvalidAccessError', function() { db.transaction([]); }); + t.done(); +}; +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbfactory-databases-opaque-origin.html b/testing/web-platform/tests/IndexedDB/idbfactory-databases-opaque-origin.html new file mode 100644 index 0000000000..ff5b668664 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory-databases-opaque-origin.html @@ -0,0 +1,97 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBFactory.databases() and opaque origins</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + +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(recipient, source) { + return new Promise(resolve => { + recipient.onmessage = function listener(e) { + if (e.source === source) { + resolve(e.data); + recipient.removeEventListener('message', listener); + } + }; + }) +} + +const test_code = + ' const handler = (reply) => {' + + ' try { ' + + ' if (!indexedDB || !indexedDB.databases) {' + + ' reply({result: "indexedDB.databases undefined"});' + + ' }' + + ' indexedDB.databases().then(' + + ' () => reply({result: "no exception"}),' + + ' ex => reply({result: ex.name}));' + + ' } catch(e) { ' + + ' reply({result: e.name + " thrown, not rejected"});' + + ' }' + + ' };'; + +const iframe_script = + '<script>' + + test_code + + ' window.onmessage = () => {' + + ' handler(msg => window.parent.postMessage(msg, "*"));' + + ' };' + + '<\/script>'; + +promise_test(async t => { + const iframe = await load_iframe(iframe_script); + iframe.contentWindow.postMessage({}, '*'); + const message = await wait_for_message(self, iframe.contentWindow); + assert_equals(message.result, 'no exception', + 'IDBFactory.databases() should not reject'); +}, 'IDBFactory.databases() in non-sandboxed iframe should not reject'); + +promise_test(async t => { + const iframe = await load_iframe(iframe_script, 'allow-scripts'); + iframe.contentWindow.postMessage({}, '*'); + const message = await wait_for_message(self, iframe.contentWindow); + assert_equals(message.result, 'SecurityError', + 'Promise should be rejected with SecurityError'); +}, 'IDBFactory.databases() in sandboxed iframe should reject'); + +const worker_script = ` +${test_code} +// For dedicated workers: +self.addEventListener("message", () => handler(self.postMessage)); +// For shared workers: +self.addEventListener("connect", (e) => { + var port = e.ports[0]; + handler(msg => port.postMessage(msg)); +}); +`; +const worker_data_url = "data:,".concat(encodeURIComponent(worker_script)); + +promise_test(async t => { + let worker = new Worker(worker_data_url); + t.add_cleanup(() => worker.terminate()); + worker.postMessage({}); + const message = await wait_for_message(worker, null); + assert_equals(message.result, 'SecurityError', + 'Promise should be rejected with SecurityError'); +}, 'IDBFactory.databases() in data URL dedicated worker should throw SecurityError'); + +promise_test(async t => { + let worker = new SharedWorker(worker_data_url, 'idb_databases_opaque'); + worker.port.postMessage({}); + const message = await wait_for_message(worker.port, null); + assert_equals(message.result, 'SecurityError', + 'Promise should be rejected with SecurityError'); +}, 'IDBFactory.databases() in data URL shared worker should throw SecurityError'); +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory-deleteDatabase-opaque-origin.html b/testing/web-platform/tests/IndexedDB/idbfactory-deleteDatabase-opaque-origin.html new file mode 100644 index 0000000000..0714389537 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory-deleteDatabase-opaque-origin.html @@ -0,0 +1,101 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBFactory.deleteDatabase() and opaque origins</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script> + +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(recipient, source) { + return new Promise(resolve => { + recipient.onmessage = function listener(e) { + if (e.source === source) { + resolve(e.data); + recipient.removeEventListener('message', listener); + } + }; + }) +} + +const test_code = + ' const handler = (reply) => {' + + ' try {' + + ' const r = indexedDB.deleteDatabase("opaque-origin-test");' + + ' reply({result: "no exception"});' + + ' } catch (ex) {' + + ' reply({result: ex.name});' + + ' };' + + ' };'; + +const iframe_script = + '<script>' + + test_code + + ' window.onmessage = () => {' + + ' handler(msg => window.parent.postMessage(msg, "*"));' + + ' };' + + '<\/script>'; + +promise_test(t => { + return load_iframe(iframe_script) + .then(iframe => { + iframe.contentWindow.postMessage({}, '*'); + return wait_for_message(self, iframe.contentWindow); + }) + .then(message => { + assert_equals(message.result, 'no exception', + 'IDBFactory.deleteDatabase() should not throw'); + }); +}, 'IDBFactory.deleteDatabase() in non-sandboxed iframe should not throw'); + +promise_test(t => { + return load_iframe(iframe_script, 'allow-scripts') + .then(iframe => { + iframe.contentWindow.postMessage({}, '*'); + return wait_for_message(self, iframe.contentWindow); + }) + .then(message => { + assert_equals(message.result, 'SecurityError', + 'Exception should be SecurityError'); + }); +}, 'IDBFactory.deleteDatabase() in sandboxed iframe should throw SecurityError'); + +const worker_script = ` +${test_code} +// For dedicated workers: +self.addEventListener("message", () => handler(self.postMessage)); +// For shared workers: +self.addEventListener("connect", (e) => { + var port = e.ports[0]; + handler(msg => port.postMessage(msg)); +}); +`; +const worker_data_url = "data:,".concat(encodeURIComponent(worker_script)); + +promise_test(async t => { + let worker = new Worker(worker_data_url); + t.add_cleanup(() => worker.terminate()); + worker.postMessage({}); + const message = await wait_for_message(worker, null); + assert_equals(message.result, 'SecurityError', + 'Promise should be rejected with SecurityError'); +}, 'IDBFactory.deleteDatabase() in data URL dedicated worker should throw SecurityError'); + +promise_test(async t => { + let worker = new SharedWorker(worker_data_url, 'idb_deleteDatabase_opaque'); + worker.port.postMessage({}); + const message = await wait_for_message(worker.port, null); + assert_equals(message.result, 'SecurityError', + 'Promise should be rejected with SecurityError'); +}, 'IDBFactory.deleteDatabase() in data URL shared worker should throw SecurityError'); +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory-deleteDatabase-request-success.html b/testing/web-platform/tests/IndexedDB/idbfactory-deleteDatabase-request-success.html new file mode 100644 index 0000000000..130c427a40 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory-deleteDatabase-request-success.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBFactory deleteDatabase(): request properties on success</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbfactory-deleteDatabase"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> +<script> + +async_test(t => { + const dbname = document.location + '-' + t.name; + const rq = indexedDB.deleteDatabase(dbname); + rq.onerror = t.unreached_func('deleteDatabase should succeed'); + rq.onsuccess = t.step_func(() => { + assert_equals( + rq.readyState, 'done', + 'request done flag should be set on success'); + assert_equals( + rq.result, undefined, + 'request result should still be set to undefined on success'); + assert_equals( + rq.error, null, + 'request error should be null on success'); + t.done(); + }); +}, 'Properties of IDBOpenDBRequest during IDBFactory deleteDatabase()'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory-open-error-properties.html b/testing/web-platform/tests/IndexedDB/idbfactory-open-error-properties.html new file mode 100644 index 0000000000..c4bc3ffaa5 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory-open-error-properties.html @@ -0,0 +1,28 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: Test IDBFactory open() error event properties</title> +<meta name=help href="https://w3c.github.io/IndexedDB/#dom-idbfactory-open"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +async_test(t => { + const dbname = document.location + '-' + t.name; + indexedDB.deleteDatabase(dbname); + const open = indexedDB.open(dbname); + open.onsuccess = t.unreached_func('open should not succeed'); + open.onupgradeneeded = t.step_func(() => { + const tx = open.transaction; + tx.abort(); + }); + open.onerror = t.step_func(e => { + assert_equals(e.target, open, 'event target should be request'); + assert_equals(e.type, 'error', 'Event type should be error'); + assert_true(e.bubbles, 'Event should bubble'); + assert_true(e.cancelable, 'Event should be cancelable'); + t.done(); + }); +}, 'Properties of error event from failed open()'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory-open-opaque-origin.html b/testing/web-platform/tests/IndexedDB/idbfactory-open-opaque-origin.html new file mode 100644 index 0000000000..a71da9afb3 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory-open-opaque-origin.html @@ -0,0 +1,105 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBFactory.open() and opaque origins</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script> + +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(recipient, source) { + return new Promise(resolve => { + recipient.onmessage = function listener(e) { + if (e.source === source) { + resolve(e.data); + recipient.removeEventListener('message', listener); + } + }; + }) +} + +const test_code = + ' const handler = (reply) => {' + + ' try {' + + ' indexedDB.deleteDatabase("opaque-origin-test");' + + ' } catch {}' + + ' try {' + + ' const r = indexedDB.open("opaque-origin-test");' + + ' r.onupgradeneeded = () => { r.transaction.abort(); };' + + ' reply({result: "no exception"});' + + ' } catch (ex) {' + + ' reply({result: ex.name});' + + ' };' + + ' };'; + +const iframe_script = + '<script>' + + test_code + + ' window.onmessage = () => {' + + ' handler(msg => window.parent.postMessage(msg, "*"));' + + ' };' + + '<\/script>'; + +promise_test(t => { + return load_iframe(iframe_script) + .then(iframe => { + iframe.contentWindow.postMessage({}, '*'); + return wait_for_message(self, iframe.contentWindow); + }) + .then(message => { + assert_equals(message.result, 'no exception', + 'IDBFactory.open() should not throw'); + }); +}, 'IDBFactory.open() in non-sandboxed iframe should not throw'); + +promise_test(t => { + return load_iframe(iframe_script, 'allow-scripts') + .then(iframe => { + iframe.contentWindow.postMessage({}, '*'); + return wait_for_message(self, iframe.contentWindow); + }) + .then(message => { + assert_equals(message.result, 'SecurityError', + 'Exception should be SecurityError'); + }); +}, 'IDBFactory.open() in sandboxed iframe should throw SecurityError'); + +const worker_script = ` +${test_code} +// For dedicated workers: +self.addEventListener("message", () => handler(self.postMessage)); +// For shared workers: +self.addEventListener("connect", (e) => { + var port = e.ports[0]; + handler(msg => port.postMessage(msg)); +}); +`; +const worker_data_url = "data:,".concat(encodeURIComponent(worker_script)); + +promise_test(async t => { + let worker = new Worker(worker_data_url); + t.add_cleanup(() => worker.terminate()); + worker.postMessage({}); + const message = await wait_for_message(worker, null); + assert_equals(message.result, 'SecurityError', + 'Promise should be rejected with SecurityError'); +}, 'IDBFactory.open() in data URL dedicated workers should throw SecurityError'); + +promise_test(async t => { + let worker = new SharedWorker(worker_data_url, 'idb_open_opaque'); + worker.port.postMessage({}); + const message = await wait_for_message(worker.port, null); + assert_equals(message.result, 'SecurityError', + 'Promise should be rejected with SecurityError'); +}, 'IDBFactory.open() in data URL shared workers should throw SecurityError'); +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory-open-request-error.html b/testing/web-platform/tests/IndexedDB/idbfactory-open-request-error.html new file mode 100644 index 0000000000..cfd6862afa --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory-open-request-error.html @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBFactory open(): request properties on error</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbfactory-open"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> +<script> + +let saw_abort = false; + +indexeddb_test( + (t, db, tx, rq) => { + const store = db.createObjectStore('store'); + store.put({name: 'a'}, 1); + store.put({name: 'a'}, 2); + store.createIndex('index', 'name', {unique: true}); + + assert_equals( + rq.readyState, 'done', + 'request done flag should be set during upgradeneeded'); + assert_equals( + rq.result, db, + 'request result should be set (to connection) during upgradeneeded'); + assert_equals( + rq.error, null, + 'request result should be null during upgradeneeded'); + + tx.oncomplete = t.unreached_func('transaction should abort'); + tx.onabort = t.step_func(() => { + saw_abort = true; + + assert_equals( + rq.readyState, 'done', + 'request done flag should still be set during abort'); + + // Chrome is flaky here. See: https://crbug.com/723846 + /* + assert_equals( + rq.result, db, + 'request result should still be set (to connection) during abort'); + assert_equals( + rq.error, null, + 'request result should still be null during abort'); + */ + }); + + rq.onerror = t.step_func(() => { + assert_true(saw_abort, 'abort event should fire before error'); + assert_equals( + rq.readyState, 'done', + 'request done flag should be set on error'); + assert_equals( + rq.result, undefined, + 'request result should be undefined on error'); + assert_equals( + rq.error.name, 'AbortError', + 'request error should be AbortError on error'); + t.done(); + }); + }, + (t, db) => {}, + 'Properties of IDBOpenDBRequest during failed IDBFactory open()', + {upgrade_will_abort: true}); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory-open-request-success.html b/testing/web-platform/tests/IndexedDB/idbfactory-open-request-success.html new file mode 100644 index 0000000000..fb6ff9034d --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory-open-request-success.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBFactory open(): request properties on success</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbfactory-open"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> +<script> + +let saw_complete = false; + +indexeddb_test( + (t, db, tx, rq) => { + assert_equals( + rq.readyState, 'done', + 'request done flag should be set during upgradeneeded'); + assert_equals( + rq.result, db, + 'request result should be set (to connection) during upgradeneeded'); + assert_equals( + rq.error, null, + 'request result should be null during upgradeneeded'); + + tx.onabort = t.unreached_func('transaction should complete'); + tx.oncomplete = t.step_func(() => { + saw_complete = true; + + assert_equals( + rq.readyState, 'done', + 'request done flag should still be set during complete'); + assert_equals( + rq.result, db, + 'request result should still be set (to connection) during complete'); + assert_equals( + rq.error, null, + 'request result should still be null during complete'); + }); + }, + (t, db, rq) => { + assert_true(saw_complete, 'complete event should fire before success'); + assert_equals( + rq.readyState, 'done', + 'request done flag should be set on success'); + assert_equals( + rq.result, db, + 'request result should still be set (to connection) on success'); + assert_equals( + rq.error, null, + 'request error should be null on success'); + t.done(); + }, + 'Properties of IDBOpenDBRequest during successful IDBFactory open()'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory-origin-isolation.html b/testing/web-platform/tests/IndexedDB/idbfactory-origin-isolation.html new file mode 100644 index 0000000000..eaf9800d7c --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory-origin-isolation.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<title>Databases on different origins use separate locking</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="resources/support.js"></script> +<script src="resources/support-promises.js"></script> +<script> + +var host_info = get_host_info(); + +promise_test(async testCase => { + await deleteAllDatabases(testCase); + + // Create an iframe to open and hold a database on a different origin. + var iframe = document.createElement('iframe'); + var newLocation = window.location.href.replace('www', 'www2'); + + const keepalive_watcher = new EventWatcher(testCase, window, 'message'); + iframe.src = host_info.HTTP_REMOTE_ORIGIN + + '/IndexedDB/resources/idbfactory-origin-isolation-iframe.html'; + document.body.appendChild(iframe); + + // Wait until the iframe starts its transaction. + var event = await keepalive_watcher.wait_for('message'); + assert_equals("keep_alive_started", event.data); + + // Create our own database with the same name, and perform a simple get. + const db = await createNamedDatabase( + testCase, 'db-isolation-test', database => { + database.createObjectStore('s'); + }); + const tx = db.transaction('s', 'readonly', {durability: 'relaxed'}); + var request = tx.objectStore('s').get(0); + request.onsuccess = testCase.step_func_done(); + request.onerror = testCase.unreached_func("There should be no errors."); +}, "Test to make sure that origins have separate locking schemes"); + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory_cmp.htm b/testing/web-platform/tests/IndexedDB/idbfactory_cmp.htm new file mode 100644 index 0000000000..e896743ff6 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory_cmp.htm @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBFactory.cmp() - compared keys return correct value</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + test(function() { + var greater = window.indexedDB.cmp(2, 1); + var equal = window.indexedDB.cmp(2, 2); + var less = window.indexedDB.cmp(1, 2); + + assert_equals(greater, 1, "greater"); + assert_equals(equal, 0, "equal"); + assert_equals(less, -1, "less"); + }, "IDBFactory.cmp()"); +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory_cmp2.htm b/testing/web-platform/tests/IndexedDB/idbfactory_cmp2.htm new file mode 100644 index 0000000000..68f87333db --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory_cmp2.htm @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBFactory.cmp() - invalid key</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBFactory-cmp-short-any-first-any-second"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> +<div id=log></div> +<script> + test( function() { + assert_throws_js(TypeError, function() { + indexedDB.cmp(); + }); + }, "IDBFactory.cmp() - no argument"); + + test( function() { + assert_throws_dom("DataError", function() { + indexedDB.cmp(null, null); + }); + assert_throws_dom("DataError", function() { + indexedDB.cmp(1, null); + }); + assert_throws_dom("DataError", function() { + indexedDB.cmp(null, 1); + }); + }, "IDBFactory.cmp() - null"); + + test( function() { + assert_throws_dom("DataError", function() { + indexedDB.cmp(NaN, NaN); + }); + assert_throws_dom("DataError", function() { + indexedDB.cmp(1, NaN); + }); + assert_throws_dom("DataError", function() { + indexedDB.cmp(NaN, 1); + }); + }, "IDBFactory.cmp() - NaN"); +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbfactory_cmp3.htm b/testing/web-platform/tests/IndexedDB/idbfactory_cmp3.htm new file mode 100644 index 0000000000..749fd7b861 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory_cmp3.htm @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBFactory.cmp() - compared keys in different types</title> +<link rel="author" title="Mozilla" href="https://www.mozilla.org"> +<link rel="help" href="http://w3c.github.io/IndexedDB/#key-construct"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> + +<script> + test(function() { + assert_equals(indexedDB.cmp([0], new Uint8Array([0])), 1, "Array > Binary"); + }, "Array v.s. Binary"); + + test(function() { + assert_equals(indexedDB.cmp(new Uint8Array([0]), "0"), 1, "Binary > String"); + }, "Binary v.s. String"); + + test(function() { + assert_equals(indexedDB.cmp("", new Date(0)), 1, "String > Date"); + }, "String v.s. Date"); + + test(function() { + assert_equals(indexedDB.cmp(new Date(0), 0), 1, "Date > Number"); + }, "Date v.s. Number"); +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory_cmp4.htm b/testing/web-platform/tests/IndexedDB/idbfactory_cmp4.htm new file mode 100644 index 0000000000..757e7c2397 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory_cmp4.htm @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBFactory.cmp() - comparison of binary keys</title> +<link rel="author" title="Mozilla" href="https://www.mozilla.org"> +<link rel="help" href="http://w3c.github.io/IndexedDB/#key-construct"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> + +<script> + test(function() { + assert_equals(indexedDB.cmp(new Int8Array([-1]), new Uint8Array([0])), 1, + "255(-1) shall be larger than 0"); + }, "Compare in unsigned octet values (in the range [0, 255])"); + + test(function() { + assert_equals(indexedDB.cmp( + new Uint8Array([255, 254, 253]), + new Uint8Array([255, 253, 254])), + 1, + "[255, 254, 253] shall be larger than [255, 253, 254]"); + }, "Compare values in then same length"); + + test(function() { + assert_equals(indexedDB.cmp( + new Uint8Array([255, 254]), + new Uint8Array([255, 253, 254])), + 1, + "[255, 254] shall be larger than [255, 253, 254]"); + }, "Compare values in different lengths"); + + test(function() { + assert_equals(indexedDB.cmp( + new Uint8Array([255, 253, 254]), + new Uint8Array([255, 253])), + 1, + "[255, 253, 254] shall be larger than [255, 253]"); + }, "Compare when the values in the range of their minimal length are the same"); +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory_deleteDatabase.htm b/testing/web-platform/tests/IndexedDB/idbfactory_deleteDatabase.htm new file mode 100644 index 0000000000..28c18cccbb --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory_deleteDatabase.htm @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<title>IDBFactory.deleteDatabase() - request has no source </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var open_rq = createdb(async_test(), undefined, 9); + + open_rq.onupgradeneeded = function(e) {}; + open_rq.onsuccess = function(e) { + var db = e.target.result; + db.close(); + + var delete_rq = window.indexedDB.deleteDatabase(db.name); + delete_rq.onerror = fail(this, "Unexpected delete_rq.error event"); + delete_rq.onsuccess = this.step_func( function (e) { + assert_equals(e.target.source, null, "event.target.source") + this.done(); + }); + } +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory_deleteDatabase2.htm b/testing/web-platform/tests/IndexedDB/idbfactory_deleteDatabase2.htm new file mode 100644 index 0000000000..e503c3437f --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory_deleteDatabase2.htm @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<title>IDBFactory.deleteDatabase() - result of the request is set to undefined</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBFactory-deleteDatabase-IDBOpenDBRequest-DOMString-name"> +<meta name=assert title="If the steps above are successful, the implementation must set the result of the request to undefined and fire a success event at the request."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var open_rq = createdb(async_test(), undefined, 9); + + open_rq.onupgradeneeded = function(e) {}; + open_rq.onsuccess = function(e) { + var db = e.target.result; + db.close(); + + var delete_rq = window.indexedDB.deleteDatabase(db.name); + delete_rq.onerror = fail(this, "Unexpected delete_rq.error event"); + delete_rq.onsuccess = this.step_func( function (e) { + assert_equals(e.target.result, undefined, "result"); + this.done(); + }); + } +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory_deleteDatabase3.htm b/testing/web-platform/tests/IndexedDB/idbfactory_deleteDatabase3.htm new file mode 100644 index 0000000000..2895013f27 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory_deleteDatabase3.htm @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<title>IDBFactory.deleteDatabase() - success event</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBFactory-deleteDatabase-IDBOpenDBRequest-DOMString-name"> +<meta name=assert title="If the steps above are successful, the implementation must set the result of the request to undefined and fire a success event at the request. The event must implement the IDBVersionChangeEvent interface and have oldVersion set to database version and have the newVersion property set to null."> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var db + var open_rq = createdb(async_test(), undefined, 9) + + open_rq.onupgradeneeded = function(e) { + db = e.target.result + db.createObjectStore('os') + } + open_rq.onsuccess = function(e) { + db.close() + + var delete_rq = window.indexedDB.deleteDatabase(db.name) + delete_rq.onerror = fail(this, "Unexpected delete_rq.error event") + delete_rq.onsuccess = this.step_func( function (e) { + assert_equals(e.oldVersion, 9, "oldVersion") + assert_equals(e.newVersion, null, "newVersion") + assert_equals(e.target.result, undefined, "result") + assert_true(e instanceof IDBVersionChangeEvent, "e instanceof IDBVersionChangeEvent") + this.done() + }) + } +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory_deleteDatabase4.htm b/testing/web-platform/tests/IndexedDB/idbfactory_deleteDatabase4.htm new file mode 100644 index 0000000000..5a0d90bf50 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory_deleteDatabase4.htm @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<title>Test events opening a second database when one connection is open already</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<div id="log"></div> + +<script> + + var t = async_test("Delete an existing database"), + dbname = location + '-' + t.name; + + t.step(function() { + indexedDB.deleteDatabase(dbname); + + var db; + var openrq = indexedDB.open(dbname, 3); + + openrq.onupgradeneeded = function(e) { + e.target.result.createObjectStore('store'); + }; + openrq.onsuccess = t.step_func(function(e) { + db = e.target.result; + + // Errors + db.onversionchange = fail(t, "db.versionchange"); + db.onerror = fail(t, "db.error"); + db.abort = fail(t, "db.abort"); + + step_timeout(t.step_func(Second), 4); + db.close(); + }); + + // Errors + openrq.onerror = fail(t, "open.error"); + openrq.onblocked = fail(t, "open.blocked"); + }); + + function Second(e) { + var deleterq = indexedDB.deleteDatabase(dbname); + + deleterq.onsuccess = function(e) { t.done(); } + + deleterq.onerror = fail(t, "delete.error"); + deleterq.onblocked = fail(t, "delete.blocked"); + deleterq.onupgradeneeded = fail(t, "delete.upgradeneeded"); + } + + async_test("Delete a nonexistent database").step(function(e) { + var deleterq = indexedDB.deleteDatabase('nonexistent'); + + deleterq.onsuccess = this.step_func(function(e) { this.done(); }); + + deleterq.onerror = fail(this, "delete.error"); + deleterq.onblocked = fail(this, "delete.blocked"); + deleterq.onupgradeneeded = fail(this, "delete.upgradeneeded"); + }); + +</script> +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory_open.htm b/testing/web-platform/tests/IndexedDB/idbfactory_open.htm new file mode 100644 index 0000000000..8e0b2412c9 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory_open.htm @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<title>IDBFactory.open() - request has no source</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var open_rq = createdb(async_test(), undefined, 9); + + open_rq.onupgradeneeded = function(e) {}; + open_rq.onsuccess = function(e) { + assert_equals(e.target.source, null, "source") + this.done(); + } +</script> + +<div id="log"> </div> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory_open10.htm b/testing/web-platform/tests/IndexedDB/idbfactory_open10.htm new file mode 100644 index 0000000000..6340bd3cdf --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory_open10.htm @@ -0,0 +1,93 @@ +<!DOCTYPE html> +<title>IDBFactory.open() - error in upgradeneeded resets db</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var db, db2; + var open_rq = createdb(async_test(), undefined, 9); + + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var st = db.createObjectStore("store"); + st.createIndex("index", "i"); + + assert_equals(db.version, 9, "first db.version"); + assert_true(db.objectStoreNames.contains("store"), "objectStoreNames contains store"); + assert_true(st.indexNames.contains("index"), "indexNames contains index"); + + st.add({i: "Joshua"}, 1); + st.add({i: "Jonas"}, 2); + }; + open_rq.onsuccess = function(e) { + db.close(); + var open_rq2 = window.indexedDB.open(db.name, 10); + open_rq2.onupgradeneeded = this.step_func(function(e) { + db2 = e.target.result; + + db2.createObjectStore("store2"); + + var store = open_rq2.transaction.objectStore("store") + store.createIndex("index2", "i"); + + assert_equals(db2.version, 10, "db2.version"); + + assert_true(db2.objectStoreNames.contains("store"), "second objectStoreNames contains store"); + assert_true(db2.objectStoreNames.contains("store2"), "second objectStoreNames contains store2"); + assert_true(store.indexNames.contains("index"), "second indexNames contains index"); + assert_true(store.indexNames.contains("index2"), "second indexNames contains index2"); + + store.add({i: "Odin"}, 3); + store.put({i: "Sicking"}, 2); + + open_rq2.transaction.abort(); + }); + open_rq2.onerror = this.step_func(function(e) { + assert_equals(db2.version, 9, "db2.version after error"); + assert_true(db2.objectStoreNames.contains("store"), "objectStoreNames contains store after error"); + assert_false(db2.objectStoreNames.contains("store2"), "objectStoreNames not contains store2 after error"); + + var open_rq3 = window.indexedDB.open(db.name); + open_rq3.onsuccess = this.step_func(function(e) { + var db3 = e.target.result; + + assert_true(db3.objectStoreNames.contains("store"), "third objectStoreNames contains store"); + assert_false(db3.objectStoreNames.contains("store2"), "third objectStoreNames contains store2"); + + var st = db3.transaction("store", "readonly", {durability: 'relaxed'}).objectStore("store"); + + assert_equals(db3.version, 9, "db3.version"); + + assert_true(st.indexNames.contains("index"), "third indexNames contains index"); + assert_false(st.indexNames.contains("index2"), "third indexNames contains index2"); + + st.openCursor(null, "prev").onsuccess = this.step_func(function(e) { + assert_equals(e.target.result.key, 2, "opencursor(prev) key"); + assert_equals(e.target.result.value.i, "Jonas", "opencursor(prev) value"); + }); + st.get(3).onsuccess = this.step_func(function(e) { + assert_equals(e.target.result, undefined, "get(3)"); + }); + + var idx = st.index("index"); + idx.getKey("Jonas").onsuccess = this.step_func(function(e) { + assert_equals(e.target.result, 2, "getKey(Jonas)"); + }); + idx.getKey("Odin").onsuccess = this.step_func(function(e) { + assert_equals(e.target.result, undefined, "getKey(Odin)"); + }); + idx.getKey("Sicking").onsuccess = this.step_func(function(e) { + assert_equals(e.target.result, undefined, "getKey(Sicking)"); + + db3.close(); + this.done(); + }); + }); + }); + }; +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory_open11.htm b/testing/web-platform/tests/IndexedDB/idbfactory_open11.htm new file mode 100644 index 0000000000..d4012bea55 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory_open11.htm @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<title>IDBFactory.open() - second open's transaction is available to get objectStores</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var db; + var count_done = 0; + var open_rq = createdb(async_test()); + + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + db.createObjectStore("store"); + assert_true(db.objectStoreNames.contains("store"), "objectStoreNames contains store"); + + var store = e.target.transaction.objectStore("store"); + assert_equals(store.name, "store", "store.name"); + + store.add("data", 1); + + store.count().onsuccess = this.step_func(function(e) { + assert_equals(e.target.result, 1, "count()"); + count_done++; + }); + + store.add("data2", 2); + }; + open_rq.onsuccess = function(e) { + var store = db.transaction("store", "readonly", {durability: 'relaxed'}).objectStore("store"); + assert_equals(store.name, "store", "store.name"); + store.count().onsuccess = this.step_func(function(e) { + assert_equals(e.target.result, 2, "count()"); + count_done++; + }); + db.close(); + + var open_rq2 = window.indexedDB.open(db.name, 10); + open_rq2.onupgradeneeded = this.step_func(function(e) { + var db2 = e.target.result; + assert_true(db2.objectStoreNames.contains("store"), "objectStoreNames contains store"); + var store = open_rq2.transaction.objectStore("store"); + assert_equals(store.name, "store", "store.name"); + + store.add("data3", 3); + + store.count().onsuccess = this.step_func(function(e) { + assert_equals(e.target.result, 3, "count()"); + count_done++; + + assert_equals(count_done, 3, "count_done"); + + db2.close(); + this.done(); + }); + }); + }; +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory_open12.htm b/testing/web-platform/tests/IndexedDB/idbfactory_open12.htm new file mode 100644 index 0000000000..ee713ef352 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory_open12.htm @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<title>IDBFactory.open() - upgradeneeded gets VersionChangeEvent</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var db; + var open_rq = createdb(async_test(document.title), undefined, 9); + var open2_t = async_test(document.title + " - second upgrade"); + + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + assert_true(e instanceof IDBVersionChangeEvent, "e instanceof IDBVersionChangeEvent"); + assert_equals(e.oldVersion, 0, "oldVersion"); + assert_equals(e.newVersion, 9, "newVersion"); + assert_equals(e.type, "upgradeneeded", "event type"); + + assert_equals(db.version, 9, "db.version"); + }; + open_rq.onsuccess = function(e) { + assert_true(e instanceof Event, "e instanceof Event"); + assert_false(e instanceof IDBVersionChangeEvent, "e not instanceof IDBVersionChangeEvent"); + assert_equals(e.type, "success", "event type"); + this.done(); + + + /** + * Second test + */ + db.onversionchange = function() { db.close(); }; + + var open_rq2 = createdb(open2_t, db.name, 10); + open_rq2.onupgradeneeded = function(e) { + var db2 = e.target.result; + assert_true(e instanceof IDBVersionChangeEvent, "e instanceof IDBVersionChangeEvent"); + assert_equals(e.oldVersion, 9, "oldVersion"); + assert_equals(e.newVersion, 10, "newVersion"); + assert_equals(e.type, "upgradeneeded", "event type"); + + assert_equals(db2.version, 10, "new db.version"); + + this.done(); + }; + }; +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory_open2.htm b/testing/web-platform/tests/IndexedDB/idbfactory_open2.htm new file mode 100644 index 0000000000..00ebbd3d0f --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory_open2.htm @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<title>IDBFactory.open() - database 'name' and 'version' are correctly set</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var database_name = document.location + '-database_name'; + var open_rq = createdb(async_test(), database_name, 13); + + open_rq.onupgradeneeded = function(e) {}; + open_rq.onsuccess = function(e) { + var db = e.target.result; + assert_equals(db.name, database_name, 'db.name'); + assert_equals(db.version, 13, 'db.version'); + this.done(); + } +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory_open3.htm b/testing/web-platform/tests/IndexedDB/idbfactory_open3.htm new file mode 100644 index 0000000000..19cd5c5254 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory_open3.htm @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<title>IDBFactory.open() - no version opens current database</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var open_rq = createdb(async_test(), undefined, 13); + var did_upgrade = false; + + open_rq.onupgradeneeded = function() {}; + open_rq.onsuccess = function(e) { + var db = e.target.result; + db.close(); + + var open_rq2 = window.indexedDB.open(db.name); + open_rq2.onsuccess = this.step_func(function(e) { + assert_equals(e.target.result.version, 13, "db.version") + e.target.result.close(); + this.done(); + }); + open_rq2.onupgradeneeded = fail(this, 'Unexpected upgradeneeded') + open_rq2.onerror = fail(this, 'Unexpected error') + } +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory_open4.htm b/testing/web-platform/tests/IndexedDB/idbfactory_open4.htm new file mode 100644 index 0000000000..6983ca0737 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory_open4.htm @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<title>IDBFactory.open() - new database has default version</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var open_rq = createdb(async_test(), document.location + '-database_name'); + + open_rq.onupgradeneeded = function(e) { + assert_equals(e.target.result.version, 1, "db.version"); + }; + open_rq.onsuccess = function(e) { + assert_equals(e.target.result.version, 1, "db.version"); + this.done(); + }; +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory_open5.htm b/testing/web-platform/tests/IndexedDB/idbfactory_open5.htm new file mode 100644 index 0000000000..f6ddb17001 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory_open5.htm @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<title>IDBFactory.open() - new database is empty</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var open_rq = createdb(async_test(), document.location + '-database_name'); + + open_rq.onupgradeneeded = function() {}; + open_rq.onsuccess = function(e) { + assert_equals(e.target.result.objectStoreNames.length, 0, "objectStoreNames.length"); + this.done(); + }; +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory_open6.htm b/testing/web-platform/tests/IndexedDB/idbfactory_open6.htm new file mode 100644 index 0000000000..2b2cb334e4 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory_open6.htm @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<title>IDBFactory.open() - open database with a lower version than current</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var open_rq = createdb(async_test(), undefined, 13); + var did_upgrade = false; + var open_rq2; + + open_rq.onupgradeneeded = function() {}; + open_rq.onsuccess = function(e) { + var db = e.target.result; + db.close(); + + open_rq2 = window.indexedDB.open(db.name, 14); + open_rq2.onupgradeneeded = function() {}; + open_rq2.onsuccess = this.step_func(open_previous_db); + open_rq2.onerror = fail(this, 'Unexpected error') + } + + function open_previous_db(e) { + var open_rq3 = window.indexedDB.open(e.target.result.name, 13); + open_rq3.onerror = this.step_func(function(e) { + assert_equals(e.target.error.name, 'VersionError', 'e.target.error.name') + open_rq2.result.close(); + this.done(); + }); + open_rq3.onupgradeneeded = fail(this, 'Unexpected upgradeneeded') + open_rq3.onsuccess = fail(this, 'Unexpected success') + } +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory_open7.htm b/testing/web-platform/tests/IndexedDB/idbfactory_open7.htm new file mode 100644 index 0000000000..ccb3d06748 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory_open7.htm @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<title>IDBFactory.open() - open database with a higher version than current</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var open_rq = createdb(async_test(), undefined, 13); + var did_upgrade = false; + var open_rq2; + + open_rq.onupgradeneeded = function() {}; + open_rq.onsuccess = function(e) { + var db = e.target.result; + db.close(); + + open_rq2 = window.indexedDB.open(db.name, 14); + open_rq2.onupgradeneeded = function() { + did_upgrade = true; + }; + open_rq2.onsuccess = this.step_func(open_current_db); + open_rq2.onerror = fail(this, 'Unexpected error') + } + + function open_current_db(e) { + var open_rq3 = window.indexedDB.open(e.target.result.name); + open_rq3.onsuccess = this.step_func(function(e) { + assert_equals(e.target.result.version, 14, "db.version") + open_rq2.result.close(); + open_rq3.result.close(); + this.done(); + }); + open_rq3.onupgradeneeded = fail(this, 'Unexpected upgradeneeded') + open_rq3.onerror = fail(this, 'Unexpected error') + + assert_true(did_upgrade, 'did upgrade'); + } +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory_open8.htm b/testing/web-platform/tests/IndexedDB/idbfactory_open8.htm new file mode 100644 index 0000000000..04c57df2ed --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory_open8.htm @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<title>IDBFactory.open() - error in version change transaction aborts open</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var open_rq = createdb(async_test(), undefined, 13); + var did_upgrade = false; + var did_db_abort = false; + + open_rq.onupgradeneeded = function(e) { + did_upgrade = true; + e.target.result.onabort = function() { + did_db_abort = true; + } + e.target.transaction.abort(); + }; + open_rq.onerror = function(e) { + assert_true(did_upgrade); + assert_equals(e.target.error.name, 'AbortError', 'target.error'); + this.done() + }; +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbfactory_open9.htm b/testing/web-platform/tests/IndexedDB/idbfactory_open9.htm new file mode 100644 index 0000000000..36a9ef814c --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbfactory_open9.htm @@ -0,0 +1,71 @@ +<!DOCTYPE html> +<title>IDBFactory.open() - errors in version argument</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> + +<script> +function should_throw(val, name) { + if (!name) { + name = ((typeof val == "object" && val) ? "object" : format_value(val)) + } + test(function() { + assert_throws_js(TypeError, function() { + window.indexedDB.open('test', val); + }); + }, "Calling open() with version argument " + name + " should throw TypeError.") +} + +should_throw(-1) +should_throw(-0.5) +should_throw(0) +should_throw(0.5) +should_throw(0.8) +should_throw(0x20000000000000) // Number.MAX_SAFE_INTEGER + 1 +should_throw(NaN) +should_throw(Infinity) +should_throw(-Infinity) +should_throw("foo") +should_throw(null) +should_throw(false) + +should_throw({ + toString: function() { assert_unreached("toString should not be called for ToPrimitive [Number]"); }, + valueOf: function() { return 0; } +}) +should_throw({ + toString: function() { return 0; }, + valueOf: function() { return {}; } +}, 'object (second)') +should_throw({ + toString: function() { return {}; }, + valueOf: function() { return {}; }, +}, 'object (third)') + + +/* Valid */ + +function should_work(val, expected_version) { + var name = format_value(val); + var dbname = 'test-db-does-not-exist'; + async_test(function(t) { + window.indexedDB.deleteDatabase(dbname); + var rq = window.indexedDB.open(dbname, val); + rq.onupgradeneeded = t.step_func(function() { + var db = rq.result; + assert_equals(db.version, expected_version, 'version'); + rq.transaction.abort(); + }); + rq.onsuccess = t.unreached_func("open should fail"); + rq.onerror = t.step_func(function() { + t.done() + }); + }, "Calling open() with version argument " + name + " should not throw.") +} + +should_work(1.5, 1) +should_work(Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER) // 0x20000000000000 - 1 +should_work(undefined, 1) + +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbindex-cross-realm-methods.html b/testing/web-platform/tests/IndexedDB/idbindex-cross-realm-methods.html new file mode 100644 index 0000000000..1e431d3772 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex-cross-realm-methods.html @@ -0,0 +1,99 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Cross-realm IDBIndex methods from detached iframe work as expected</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<body> +<script> +"use strict"; +const INDEX_LOWER = 1000; +const INDEX_UPPER = 1001; +const INDEX_RANGE = IDBKeyRange.bound(INDEX_LOWER, INDEX_UPPER); + +const testCases = [ + { + methodName: "get", + arguments: [INDEX_LOWER], + validateResult: e => { + assert_equals(e.target.result.indexedKey, INDEX_LOWER); + }, + }, + { + methodName: "getKey", + arguments: [INDEX_UPPER], + validateResult: e => { + assert_equals(e.target.result, INDEX_UPPER); + }, + }, + { + methodName: "getAll", + arguments: [INDEX_RANGE], + validateResult: e => { + assert_array_equals(e.target.result.map(v => v.indexedKey), [INDEX_LOWER, INDEX_UPPER]); + }, + }, + { + methodName: "getAllKeys", + arguments: [INDEX_RANGE], + validateResult: e => { + assert_array_equals(e.target.result, [INDEX_LOWER, INDEX_UPPER]); + }, + }, + { + methodName: "count", + arguments: [INDEX_RANGE], + validateResult: e => { + assert_equals(e.target.result, 2); + }, + }, + { + methodName: "openCursor", + arguments: [], + validateResult: e => { + const cursor = e.target.result; + assert_true(cursor instanceof IDBCursor); + assert_equals(cursor.value.indexedKey, INDEX_LOWER); + }, + }, + { + methodName: "openKeyCursor", + arguments: [], + validateResult: e => { + const cursor = e.target.result; + assert_true(cursor instanceof IDBCursor); + assert_equals(cursor.key, INDEX_LOWER); + }, + }, +]; + +for (const testCase of testCases) { + async_test(t => { + const iframe = document.createElement("iframe"); + iframe.onload = t.step_func(() => { + const method = iframe.contentWindow.IDBIndex.prototype[testCase.methodName]; + iframe.remove(); + + let db; + const open_rq = createdb(t); + open_rq.onupgradeneeded = t.step_func(e => { + db = e.target.result; + const objectStore = db.createObjectStore("store"); + objectStore.createIndex("index", "indexedKey"); + objectStore.add({ indexedKey: INDEX_LOWER }, INDEX_LOWER); + objectStore.add({ indexedKey: INDEX_UPPER }, INDEX_UPPER); + }); + + open_rq.onsuccess = t.step_func(() => { + const index = db.transaction("store", "readonly", {durability: 'relaxed'}).objectStore("store").index("index"); + const rq = method.call(index, ...testCase.arguments); + rq.onsuccess = t.step_func_done(e => { + testCase.validateResult(e); + }); + }); + }); + document.body.append(iframe); + }, `Cross-realm IDBIndex::${testCase.methodName}() method from detached <iframe> works as expected`); +} +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbindex-getAll-enforcerange.html b/testing/web-platform/tests/IndexedDB/idbindex-getAll-enforcerange.html new file mode 100644 index 0000000000..73b55b7d86 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex-getAll-enforcerange.html @@ -0,0 +1,27 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: IDBIndex getAll() uses [EnforceRange]</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#index-interface"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('store'); + const index = store.createIndex('index', 'keyPath'); + }, + (t, db) => { + const tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + const index = store.index('index'); + [NaN, Infinity, -Infinity, -1, -Number.MAX_SAFE_INTEGER].forEach(count => { + assert_throws_js(TypeError, () => { index.getAll(null, count); }, + `getAll with count ${count} count should throw TypeError`); + }); + t.done(); + }, + `IDBIndex.getAll() uses [EnforceRange]` +); +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbindex-getAllKeys-enforcerange.html b/testing/web-platform/tests/IndexedDB/idbindex-getAllKeys-enforcerange.html new file mode 100644 index 0000000000..31dc787f68 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex-getAllKeys-enforcerange.html @@ -0,0 +1,27 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: IDBIndex getAllKeys() uses [EnforceRange]</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#index-interface"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('store'); + const index = store.createIndex('index', 'keyPath'); + }, + (t, db) => { + const tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + const index = store.index('index'); + [NaN, Infinity, -Infinity, -1, -Number.MAX_SAFE_INTEGER].forEach(count => { + assert_throws_js(TypeError, () => { index.getAllKeys(null, count); }, + `getAllKeys with count ${count} count should throw TypeError`); + }); + t.done(); + }, + `IDBIndex.getAllKeys() uses [EnforceRange]` +); +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbindex-multientry-arraykeypath.htm b/testing/web-platform/tests/IndexedDB/idbindex-multientry-arraykeypath.htm new file mode 100644 index 0000000000..303eef7e92 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex-multientry-arraykeypath.htm @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.multiEntry: array keyPath with multiEntry</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#key-construct"> +<link rel=assert title="XXX"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + createdb(async_test()).onupgradeneeded = function(e) { + var store = e.target.result.createObjectStore("store"); + + assert_throws_dom('InvalidAccessError', function() { + store.createIndex('actors', ['name'], { multiEntry: true }) + }); + + this.done(); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbindex-multientry-big.htm b/testing/web-platform/tests/IndexedDB/idbindex-multientry-big.htm new file mode 100644 index 0000000000..43eb4034e7 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex-multientry-big.htm @@ -0,0 +1,59 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.multiEntry - a 1000 entry multiEntry array</title> +<meta name="timeout" content="long"> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#key-construct"> +<link rel=assert title="XXX"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t_add = async_test("Adding one item with 1000 multiEntry keys"), + t_get = async_test("Getting the one item by 1000 indeced keys "); + + var open_rq = createdb(t_add); + var obj = { test: 'yo', idxkeys: [] }; + + for (var i = 0; i < 1000; i++) + obj.idxkeys.push('index_no_' + i); + + + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + db.createObjectStore('store') + .createIndex('index', 'idxkeys', { multiEntry: true }); + }; + open_rq.onsuccess = function(e) { + var tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + tx.objectStore('store') + .put(obj, 1) + .onsuccess = t_add.step_func(function(e) + { + assert_equals(e.target.result, 1, "put'd key"); + this.done(); + }); + + tx.oncomplete = t_get.step_func(function() { + var idx = db.transaction('store', 'readonly', {durability: 'relaxed'}).objectStore('store').index('index') + + for (var i = 0; i < 1000; i++) + { + idx.get('index_no_' + i).onsuccess = t_get.step_func(function(e) { + assert_equals(e.target.result.test, "yo"); + }); + } + + idx.get('index_no_999').onsuccess = t_get.step_func(function(e) { + assert_equals(e.target.result.test, "yo"); + assert_equals(e.target.result.idxkeys.length, 1000); + this.done(); + }); + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbindex-multientry.htm b/testing/web-platform/tests/IndexedDB/idbindex-multientry.htm new file mode 100644 index 0000000000..d5aee222ea --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex-multientry.htm @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.multiEntry - adding keys</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#key-construct"> +<link rel=assert title="XXX"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + expected_keys = [1, 2, 2, 3, 3]; + + var open_rq = createdb(async_test()) + + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var store = db.createObjectStore("store") + + store.createIndex('actors', 'name', { multiEntry: true }) + + store.add({name: 'Odin'}, 1); + store.add({name: ['Rita', 'Scheeta', {Bobby:'Bobby'}]}, 2); + store.add({name: [ {s: 'Robert'}, 'Neil', 'Bobby']}, 3); + }; + open_rq.onsuccess = function(e) { + var gotten_keys = []; + var idx = db.transaction('store', 'readonly', {durability: 'relaxed'}).objectStore('store').index('actors'); + + idx.getKey('Odin').onsuccess = this.step_func(function(e) { + gotten_keys.push(e.target.result) + }); + idx.getKey('Rita').onsuccess = this.step_func(function(e) { + gotten_keys.push(e.target.result) + }); + idx.getKey('Scheeta').onsuccess = this.step_func(function(e) { + gotten_keys.push(e.target.result) + }); + idx.getKey('Neil').onsuccess = this.step_func(function(e) { + gotten_keys.push(e.target.result) + }); + idx.getKey('Bobby').onsuccess = this.step_func(function(e) { + gotten_keys.push(e.target.result) + + assert_array_equals(gotten_keys, expected_keys); + this.done(); + }); + } +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbindex-objectStore-SameObject.html b/testing/web-platform/tests/IndexedDB/idbindex-objectStore-SameObject.html new file mode 100644 index 0000000000..018c818d1d --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex-objectStore-SameObject.html @@ -0,0 +1,28 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: Verify [SameObject] behavior of IDBIndex's objectStore attribute</title> +<meta name="help" href="https://w3c.github.io/IndexedDB/#dom-idbindex-objectstore"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('store'); + const index = store.createIndex('index', 'keyPath'); + assert_equals(index.objectStore, index.objectStore, + 'Attribute should yield the same object each time'); + + }, + (t, db) => { + const tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + const index = store.index('index'); + assert_equals(index.objectStore, index.objectStore, + 'Attribute should yield the same object each time'); + t.done(); + }, + 'IDBIndex.objectStore [SameObject]' +); +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbindex-query-exception-order.html b/testing/web-platform/tests/IndexedDB/idbindex-query-exception-order.html new file mode 100644 index 0000000000..2c1a335112 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex-query-exception-order.html @@ -0,0 +1,67 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: IDBIndex query method Ordering</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbindex-get"> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbindex-getall"> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbindex-getallkeys"> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbindex-count"> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbindex-opencursor"> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbindex-openkeycursor"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +['get', + 'getAll', + 'getAllKeys', + 'count', + 'openCursor', + 'openKeyCursor' +].forEach(method => { + + indexeddb_test( + (t, db) => { + const store = db.createObjectStore('s'); + const store2 = db.createObjectStore('s2'); + const index = store2.createIndex('i', 'keyPath'); + + store2.deleteIndex('i'); + + setTimeout(t.step_func(() => { + assert_throws_dom( + 'InvalidStateError', () => { index[method]('key'); }, + '"has been deleted" check (InvalidStateError) should precede ' + + '"not active" check (TransactionInactiveError)'); + t.done(); + }), 0); + }, + (t, db) => {}, + `IDBIndex.${method} exception order: ` + + 'InvalidStateError vs. TransactionInactiveError' + ); + + indexeddb_test( + (t, db) => { + const store = db.createObjectStore('s'); + const index = store.createIndex('i', 'keyPath'); + }, + (t, db) => { + const tx = db.transaction('s', 'readonly', {durability: 'relaxed'}); + const store = tx.objectStore('s'); + const index = store.index('i'); + + setTimeout(t.step_func(() => { + assert_throws_dom( + 'TransactionInactiveError', () => { index[method]({}); }, + '"not active" check (TransactionInactiveError) should precede ' + + 'query check (DataError)'); + t.done(); + }), 0); + }, + `IDBIndex.${method} exception order: ` + + 'TransactionInactiveError vs. DataError' + ); +}); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbindex-rename-abort.html b/testing/web-platform/tests/IndexedDB/idbindex-rename-abort.html new file mode 100644 index 0000000000..07863081b2 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex-rename-abort.html @@ -0,0 +1,110 @@ +<!doctype html> +<meta charset="utf8"> +<title>IndexedDB: index renaming support in aborted transactions</title> +<link rel="help" + href="https://w3c.github.io/IndexedDB/#dom-idbindex-name"> +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script> +'use strict'; + +promise_test(testCase => { + const dbName = databaseName(testCase); + let authorIndex = null, authorIndex2 = null; + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + const store = transaction.objectStore('books'); + authorIndex = store.index('by_author'); + authorIndex.name = 'renamed_by_author'; + + transaction.abort(); + + assert_equals( + authorIndex.name, 'by_author', + 'IDBIndex.name should not reflect the rename any more ' + + 'immediately after transaction.abort() returns'); + assert_array_equals( + store.indexNames, ['by_author', 'by_title'], + 'IDBObjectStore.indexNames should not reflect the rename any ' + + 'more immediately after transaction.abort() returns'); + })).then(event => { + assert_equals( + authorIndex.name, 'by_author', + 'IDBIndex.name should not reflect the rename any more after the ' + + 'versionchange transaction is aborted'); + + const request = indexedDB.open(dbName, 1); + return promiseForRequest(testCase, request); + }).then(database => { + const transaction = database.transaction('books', 'readonly', {durability: 'relaxed'}); + const store = transaction.objectStore('books'); + assert_array_equals( + store.indexNames, ['by_author', 'by_title'], + 'IDBDatabase.objectStoreNames should not reflect the rename ' + + 'after the versionchange transaction is aborted'); + + authorIndex2 = store.index('by_author'); + return checkAuthorIndexContents( + testCase, authorIndex2, + 'Aborting an index rename transaction should not change the ' + + "index's records").then(() => database.close()); + }).then(() => { + assert_equals( + authorIndex.name, 'by_author', + 'IDBIndex used in aborted rename transaction should not reflect ' + + 'the rename after the transaction is aborted'); + assert_equals(authorIndex2.name, 'by_author', + 'IDBIndex obtained after an aborted rename transaction should ' + + 'not reflect the rename'); + }); +}, 'IndexedDB index rename in aborted transaction'); + +promise_test(testCase => { + const dbName = databaseName(testCase); + let authorIndex = null; + return createDatabase(testCase, (database, transaction) => { + createNotBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + const store = transaction.objectStore('not_books'); + authorIndex = store.createIndex('by_author', 'author'); + authorIndex.name = 'by_author_renamed'; + authorIndex.name = 'by_author_renamed_again'; + + transaction.abort(); + + assert_equals( + authorIndex.name, 'by_author_renamed_again', + 'IDBIndex.name should reflect the last rename immediately after ' + + 'transaction.abort() returns'); + assert_array_equals( + store.indexNames, ['not_by_author', 'not_by_title'], + 'IDBObjectStore.indexNames should not reflect the creation or ' + + 'the rename immediately after transaction.abort() returns'); + })).then(event => { + assert_equals( + authorIndex.name, 'by_author_renamed_again', + 'IDBIndex.name should reflect the last rename after the ' + + 'versionchange transaction is aborted'); + + const request = indexedDB.open(dbName, 1); + return promiseForRequest(testCase, request); + }).then(database => { + const transaction = database.transaction('not_books', 'readonly', {durability: 'relaxed'}); + const store = transaction.objectStore('not_books'); + assert_array_equals( + store.indexNames, ['not_by_author', 'not_by_title'], + 'IDBDatabase.objectStoreNames should not reflect the creation or ' + + 'the rename after the versionchange transaction is aborted'); + + database.close(); + }); +}, 'IndexedDB index creation and rename in an aborted transaction'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbindex-rename-errors.html b/testing/web-platform/tests/IndexedDB/idbindex-rename-errors.html new file mode 100644 index 0000000000..2339cf1022 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex-rename-errors.html @@ -0,0 +1,132 @@ +<!doctype html> +<meta charset="utf8"> +<title>IndexedDB: index renaming error handling</title> +<link rel="help" + href="https://w3c.github.io/IndexedDB/#dom-idbindex-name"> +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script> +'use strict'; + +promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + const store = transaction.objectStore('books'); + const index = store.index('by_author'); + store.deleteIndex('by_author'); + assert_throws_dom( + 'InvalidStateError', () => index.name = 'renamed_by_author'); + })).then(database => database.close()); +}, 'IndexedDB deleted index rename throws'); + +promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + const transaction = database.transaction('books', 'readonly', {durability: 'relaxed'}); + const store = transaction.objectStore('books'); + const index = store.index('by_author'); + + assert_throws_dom( + 'InvalidStateError', () => index.name = 'renamed_by_author'); + database.close(); + }); +}, 'IndexedDB index rename throws in a readonly transaction'); + +promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + const transaction = database.transaction('books', 'readwrite', {durability: 'relaxed'}); + const store = transaction.objectStore('books'); + const index = store.index('by_author'); + + assert_throws_dom( + 'InvalidStateError', () => index.name = 'renamed_by_author'); + database.close(); + }); +}, 'IndexedDB index rename throws in a readwrite transaction'); + +promise_test(testCase => { + let authorIndex = null; + return createDatabase(testCase, (database, transaction) => { + const store = createBooksStore(testCase, database); + authorIndex = store.index('by_author'); + }).then(database => { + assert_throws_dom( + 'TransactionInactiveError', + () => authorIndex.name = 'renamed_by_author'); + database.close(); + }); +}, 'IndexedDB index rename throws in an inactive transaction'); + +promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + const store = transaction.objectStore('books'); + const index = store.index('by_author'); + + assert_throws_dom('ConstraintError', () => index.name = 'by_title'); + assert_array_equals( + store.indexNames, ['by_author', 'by_title'], + 'An index rename that throws an exception should not change the ' + + "index's IDBObjectStore.indexNames"); + })).then(database => { + const transaction = database.transaction('books', 'readonly', {durability: 'relaxed'}); + const store = transaction.objectStore('books'); + assert_array_equals( + store.indexNames, ['by_author', 'by_title'], + 'Committing a transaction with a failed store rename attempt ' + + "should not change the index's IDBObjectStore.indexNames"); + const index = store.index('by_author'); + return checkAuthorIndexContents( + testCase, index, + 'Committing a transaction with a failed rename attempt should ' + + "not change the index's contents").then(() => database.close()); + }); +}, 'IndexedDB index rename to the name of another index throws'); + +promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + const store = transaction.objectStore('books'); + const index = store.index('by_author'); + const exception = { name: 'Custom stringifying error' }; + assert_throws_exactly( + exception, + () => { + index.name = { + toString: () => { throw exception; } + }; + }, 'IDBObjectStore rename should re-raise toString() exception'); + assert_array_equals( + store.indexNames, ['by_author', 'by_title'], + 'An index rename that throws an exception should not change the ' + + "index's IDBObjectStore.indexNames"); + })).then(database => { + const transaction = database.transaction('books', 'readonly', {durability: 'relaxed'}); + const store = transaction.objectStore('books'); + assert_array_equals( + store.indexNames, ['by_author', 'by_title'], + 'Committing a transaction with a failed store rename attempt ' + + "should not change the index's IDBObjectStore.indexNames"); + const index = store.index('by_author'); + return checkAuthorIndexContents( + testCase, index, + 'Committing a transaction with a failed rename attempt should ' + + "not change the index's contents").then(() => database.close()); + }); +}, 'IndexedDB index rename handles exceptions when stringifying names'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbindex-rename.html b/testing/web-platform/tests/IndexedDB/idbindex-rename.html new file mode 100644 index 0000000000..db06a7e237 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex-rename.html @@ -0,0 +1,301 @@ +<!doctype html> +<meta charset="utf8"> +<meta name="timeout" content="long"> +<title>IndexedDB: index renaming support</title> +<link rel="help" + href="https://w3c.github.io/IndexedDB/#dom-idbindex-name"> +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script> +'use strict'; + +promise_test(testCase => { + let authorIndex = null, authorIndex2 = null; + let renamedAuthorIndex = null, renamedAuthorIndex2 = null; + return createDatabase(testCase, (database, transaction) => { + const store = createBooksStore(testCase, database); + authorIndex = store.index('by_author'); + }).then(database => { + const transaction = database.transaction('books', 'readonly', {durability: 'relaxed'}); + const store = transaction.objectStore('books'); + assert_array_equals( + store.indexNames, ['by_author', 'by_title'], + 'Test setup should have created two indexes'); + authorIndex2 = store.index('by_author'); + return checkAuthorIndexContents( + testCase, authorIndex2, + 'The index should have the expected contents before any renaming'). + then(() => database.close()); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + const store = transaction.objectStore('books'); + renamedAuthorIndex = store.index('by_author'); + renamedAuthorIndex.name = 'renamed_by_author'; + + assert_equals( + renamedAuthorIndex.name, 'renamed_by_author', + 'IDBIndex name should change immediately after a rename'); + assert_array_equals( + store.indexNames, ['by_title', 'renamed_by_author'], + 'IDBObjectStore.indexNames should immediately reflect the rename'); + assert_equals( + store.index('renamed_by_author'), renamedAuthorIndex, + 'IDBObjectStore.index should return the renamed index store when ' + + 'queried using the new name immediately after the rename'); + assert_throws_dom( + 'NotFoundError', () => store.index('by_author'), + 'IDBObjectStore.index should throw when queried using the ' + + "renamed index's old name immediately after the rename"); + })).then(database => { + const transaction = database.transaction('books', 'readonly', {durability: 'relaxed'}); + const store = transaction.objectStore('books'); + assert_array_equals( + store.indexNames, ['by_title', 'renamed_by_author'], + 'IDBObjectStore.indexNames should still reflect the rename after ' + + 'the versionchange transaction commits'); + renamedAuthorIndex2 = store.index('renamed_by_author'); + return checkAuthorIndexContents( + testCase, renamedAuthorIndex2, + 'Renaming an index should not change its contents').then( + () => database.close()); + }).then(() => { + assert_equals( + authorIndex.name, 'by_author', + 'IDBIndex obtained before the rename transaction should not ' + + 'reflect the rename'); + assert_equals( + authorIndex2.name, 'by_author', + 'IDBIndex obtained before the rename transaction should not ' + + 'reflect the rename'); + assert_equals( + renamedAuthorIndex.name, 'renamed_by_author', + 'IDBIndex used in the rename transaction should keep reflecting ' + + 'the new name after the transaction is committed'); + assert_equals( + renamedAuthorIndex2.name, 'renamed_by_author', + 'IDBIndex obtained after the rename transaction should reflect ' + + 'the new name'); + }); +}, 'IndexedDB index rename in new transaction'); + +promise_test(testCase => { + let renamedAuthorIndex = null, renamedAuthorIndex2 = null; + return createDatabase(testCase, (database, transaction) => { + const store = createBooksStore(testCase, database); + renamedAuthorIndex = store.index('by_author'); + renamedAuthorIndex.name = 'renamed_by_author'; + + assert_equals( + renamedAuthorIndex.name, 'renamed_by_author', + 'IDBIndex name should change immediately after a rename'); + assert_array_equals( + store.indexNames, ['by_title', 'renamed_by_author'], + 'IDBObjectStore.indexNames should immediately reflect the rename'); + assert_equals( + store.index('renamed_by_author'), renamedAuthorIndex, + 'IDBObjectStore.index should return the renamed index store when ' + + 'queried using the new name immediately after the rename'); + assert_throws_dom( + 'NotFoundError', () => store.index('by_author'), + 'IDBObjectStore.index should throw when queried using the ' + + "renamed index's old name immediately after the rename"); + }).then(database => { + const transaction = database.transaction('books', 'readonly', {durability: 'relaxed'}); + const store = transaction.objectStore('books'); + assert_array_equals( + store.indexNames, ['by_title', 'renamed_by_author'], + 'IDBObjectStore.indexNames should still reflect the rename after ' + + 'the versionchange transaction commits'); + renamedAuthorIndex2 = store.index('renamed_by_author'); + return checkAuthorIndexContents( + testCase, renamedAuthorIndex2, + 'Renaming an index should not change its contents').then( + () => database.close()); + }).then(() => { + assert_equals( + renamedAuthorIndex.name, 'renamed_by_author', + 'IDBIndex used in the rename transaction should keep reflecting ' + + 'the new name after the transaction is committed'); + assert_equals( + renamedAuthorIndex2.name, 'renamed_by_author', + 'IDBIndex obtained after the rename transaction should reflect ' + + 'the new name'); + }); +}, 'IndexedDB index rename in the transaction where it is created'); + +promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + const store = transaction.objectStore('books'); + const index = store.index('by_author'); + index.name = 'by_author'; + assert_array_equals( + store.indexNames, ['by_author', 'by_title'], + 'Renaming an index to the same name should not change the ' + + "index's IDBObjectStore.indexNames"); + })).then(database => { + const transaction = database.transaction('books', 'readonly', {durability: 'relaxed'}); + const store = transaction.objectStore('books'); + assert_array_equals( + store.indexNames, ['by_author', 'by_title'], + 'Committing a transaction that renames a store to the same name ' + + "should not change the index's IDBObjectStore.indexNames"); + const index = store.index('by_author'); + return checkAuthorIndexContents( + testCase, index, + 'Committing a transaction that renames an index to the same name ' + + "should not change the index's contents").then( + () => database.close()); + }); +}, 'IndexedDB index rename to the same name succeeds'); + +promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + const store = transaction.objectStore('books'); + const index = store.index('by_author'); + store.deleteIndex('by_title'); + index.name = 'by_title'; + assert_array_equals( + store.indexNames, ['by_title'], + 'IDBObjectStore.indexNames should immediately reflect the rename'); + })).then(database => { + const transaction = database.transaction('books', 'readonly', {durability: 'relaxed'}); + const store = transaction.objectStore('books'); + assert_array_equals( + store.indexNames, ['by_title'], + 'IDBObjectStore.indexNames should still reflect the rename after ' + + 'the versionchange transaction commits'); + const index = store.index('by_title'); + return checkAuthorIndexContents( + testCase, index, + 'Renaming an index should not change its contents').then( + () => database.close()); + }); +}, 'IndexedDB index rename to the name of a deleted index succeeds'); + +promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + const store = transaction.objectStore('books'); + store.index('by_author').name = 'tmp'; + store.index('by_title').name = 'by_author'; + store.index('tmp').name = 'by_title'; + assert_array_equals( + store.indexNames, ['by_author', 'by_title'], + 'IDBObjectStore.indexNames should reflect the swap immediately ' + + 'after the renames'); + return checkTitleIndexContents( + testCase, store.index('by_author'), + 'Renaming an index should not change its contents'); + })).then(database => { + const transaction = database.transaction('books', 'readonly', {durability: 'relaxed'}); + const store = transaction.objectStore('books'); + assert_array_equals( + store.indexNames, ['by_author', 'by_title'], + 'IDBObjectStore.indexNames should still reflect the swap after ' + + 'the versionchange transaction commits'); + const index = store.index('by_title'); + return checkAuthorIndexContents( + testCase, index, + 'Renaming an index should not change its contents').then( + () => database.close()); + }); +}, 'IndexedDB index swapping via renames succeeds'); + +promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + const store = transaction.objectStore('books'); + const index = store.index('by_author'); + + index.name = 42; + assert_equals(index.name, '42', + 'IDBIndex name should change immediately after a rename to a ' + + 'number'); + assert_array_equals( + store.indexNames, ['42', 'by_title'], + 'IDBObjectStore.indexNames should immediately reflect the ' + + 'stringifying rename'); + + index.name = true; + assert_equals(index.name, 'true', + 'IDBIndex name should change immediately after a rename to a ' + + 'boolean'); + + index.name = {}; + assert_equals(index.name, '[object Object]', + 'IDBIndex name should change immediately after a rename to an ' + + 'object'); + + index.name = () => null; + assert_equals(index.name, '() => null', + 'IDBIndex name should change immediately after a rename to a ' + + 'function'); + + index.name = undefined; + assert_equals(index.name, 'undefined', + 'IDBIndex name should change immediately after a rename to ' + + 'undefined'); + })).then(database => { + const transaction = database.transaction('books', 'readonly', {durability: 'relaxed'}); + const store = transaction.objectStore('books'); + assert_array_equals( + store.indexNames, ['by_title', 'undefined'], + 'IDBObjectStore.indexNames should reflect the last rename ' + + 'after the versionchange transaction commits'); + const index = store.index('undefined'); + return checkAuthorIndexContents( + testCase, index, + 'Renaming an index should not change its contents').then( + () => database.close()); + }); +}, 'IndexedDB index rename stringifies non-string names'); + +for (let escapedName of ['', '\\u0000', '\\uDC00\\uD800']) ((escapedName) => { + const name = JSON.parse('"' + escapedName + '"'); + promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + const store = transaction.objectStore('books'); + const index = store.index('by_author'); + + index.name = name; + assert_equals(index.name, name, + 'IDBIndex name should change immediately after the rename'); + assert_array_equals( + store.indexNames, [name, 'by_title'].sort(), + 'IDBObjectStore.indexNames should immediately reflect the rename'); + })).then(database => { + const transaction = database.transaction('books', 'readonly', {durability: 'relaxed'}); + const store = transaction.objectStore('books'); + assert_array_equals( + store.indexNames, [name, 'by_title'].sort(), + 'IDBObjectStore.indexNames should reflect the rename ' + + 'after the versionchange transaction commits'); + const index = store.index(name); + return checkAuthorIndexContents( + testCase, index, + 'Renaming an index should not change its contents').then( + () => database.close()); + }); + }, 'IndexedDB index can be renamed to "' + escapedName + '"'); +})(escapedName); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbindex-request-source.html b/testing/web-platform/tests/IndexedDB/idbindex-request-source.html new file mode 100644 index 0000000000..72688c467a --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex-request-source.html @@ -0,0 +1,34 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: The source of requests made against indexes</title> +<meta name="help" href="https://w3c.github.io/IndexedDB/#dom-idbrequest-source"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +[ + index => index.get(0), + index => index.getKey(0), + index => index.getAll(), + index => index.getAllKeys(), + index => index.count(), + + index => index.openCursor(), + index => index.openKeyCursor() +].forEach(func => indexeddb_test( + (t, db) => { + const store = db.createObjectStore('store', {autoIncrement: true}); + store.createIndex('index', 'kp'); + }, + (t, db) => { + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + const index = tx.objectStore('store').index('index'); + assert_equals(func(index).source, index, + `${func}.source should be the index itself`); + t.done(); + }, + `The source of the request from ${func} is the index itself` +)); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbindex_batchGetAll.tentative.any.js b/testing/web-platform/tests/IndexedDB/idbindex_batchGetAll.tentative.any.js new file mode 100644 index 0000000000..fe21445e83 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_batchGetAll.tentative.any.js @@ -0,0 +1,185 @@ +// META: title=Batch Get All Index +// META: script=resources/support.js + +'use strict'; + +const alphabet = 'abcdefghijklmnopqrstuvwxyz'.split(''); +const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); + +function batchgetall_test(storeName, func, name) { + indexeddb_test((t, connection, tx) => { + var store; + var index; + switch (storeName) { + case 'generated': + store = connection.createObjectStore( + 'generated', {autoIncrement: true, keyPath: 'id'}); + index = store.createIndex('test_idx', 'upper'); + alphabet.forEach(letter => { + store.put({ch: letter, upper: letter.toUpperCase()}); + }); + break; + case 'out-of-line': + store = connection.createObjectStore('out-of-line', null); + index = store.createIndex('test_idx', 'upper'); + alphabet.forEach(letter => { + store.put({ch: letter, upper: letter.toUpperCase()}, letter); + }); + break; + case 'out-of-line-not-unique': + store = connection.createObjectStore('out-of-line-not-unique', null); + index = store.createIndex('test_idx', 'half'); + alphabet.forEach(letter => { + if (letter <= 'd') + store.put({ch: letter, half: 'first'}, letter); + else if (letter < 'x') + store.put({ch: letter, half: 'second'}, letter); + else + store.put({ch: letter, half: 'third'}, letter); + }); + break; + case 'out-of-line-multi': + store = connection.createObjectStore('out-of-line-multi', null); + index = store.createIndex('test_idx', 'attribs', {multiEntry: true}); + alphabet.forEach(function(letter) { + let attrs = []; + if (['a', 'e', 'i', 'o', 'u'].indexOf(letter) != -1) + attrs.push('vowel'); + else + attrs.push('consonant'); + if (letter == 'a') + attrs.push('first'); + if (letter == 'z') + attrs.push('last'); + store.put({ch: letter, attribs: attrs}, letter); + }); + break; + case 'empty': + store = connection.createObjectStore('empty', null); + store.createIndex('test_idx', 'upper'); + break; + default: + t.fail(`Unsupported storeName: ${storeName}`); + } + }, func, name); +} + +function createBatchGetAllRequest(t, storeName, connection, ranges, maxCount) { + const transaction = connection.transaction(storeName, 'readonly'); + const store = transaction.objectStore(storeName); + const index = store.index('test_idx'); + const req = index.batchGetAll(ranges, maxCount); + req.onerror = t.unreached_func('batchGetsAll request should succeed'); + return req; +} + +function assertTwoDArrayEquals(result, expected) { + assert_equals(JSON.stringify(result), JSON.stringify(expected)); +} + +batchgetall_test('out-of-line', (t, connection) => { + const req = createBatchGetAllRequest(t, 'out-of-line', connection, ['C']); + req.onsuccess = t.step_func(evt => { + let expected = [[{'ch': 'c', 'upper': 'C'}]]; + let result = evt.target.result; + assert_class_string(result, 'Array', 'result should be an array'); + assertTwoDArrayEquals(result, expected); + t.done(); + }); +}, 'Single getAll'); + +batchgetall_test('out-of-line', (t, connection) => { + const req = + createBatchGetAllRequest(t, 'out-of-line', connection, ['C', 'D', 'E']); + req.onsuccess = t.step_func(evt => { + let result = evt.target.result; + let expected = [ + [{'ch': 'c', 'upper': 'C'}], [{'ch': 'd', 'upper': 'D'}], + [{'ch': 'e', 'upper': 'E'}] + ]; + assert_class_string(result, 'Array', 'result should be an array'); + assertTwoDArrayEquals(result, expected); + t.done(); + }); +}, 'Multiple getAll'); + +batchgetall_test('out-of-line', (t, connection) => { + var req = createBatchGetAllRequest( + t, 'out-of-line', connection, [IDBKeyRange.bound('C', 'E')]); + req.onsuccess = t.step_func(function(evt) { + let result = evt.target.result; + let expected = [[ + {'ch': 'c', 'upper': 'C'}, {'ch': 'd', 'upper': 'D'}, + {'ch': 'e', 'upper': 'E'} + ]]; + assertTwoDArrayEquals(result, expected); + t.done(); + }); +}, 'Get bound range'); + +batchgetall_test('out-of-line', (t, connection) => { + var req = createBatchGetAllRequest( + t, 'out-of-line', connection, + [IDBKeyRange.bound('C', 'M'), IDBKeyRange.bound('O', 'Z')], 3); + req.onsuccess = t.step_func(function(evt) { + let result = evt.target.result; + let expected = [ + [ + {'ch': 'c', 'upper': 'C'}, {'ch': 'd', 'upper': 'D'}, + {'ch': 'e', 'upper': 'E'} + ], + [ + {'ch': 'o', 'upper': 'O'}, {'ch': 'p', 'upper': 'P'}, + {'ch': 'q', 'upper': 'Q'} + ] + ]; + assertTwoDArrayEquals(result, expected); + t.done(); + }); +}, 'Get bound range with maxCount'); + +batchgetall_test('out-of-line', (t, connection) => { + var req = createBatchGetAllRequest( + t, 'out-of-line', connection, ['Doesn\'t exist1', 'Doesn\'t exist2']); + req.onsuccess = t.step_func(function(evt) { + let result = evt.target.result; + let expected = [[], []]; + assertTwoDArrayEquals(result, expected); + t.done(); + }); +}, 'Non existent key'); + +batchgetall_test('out-of-line-not-unique', (t, connection) => { + var req = createBatchGetAllRequest( + t, 'out-of-line-not-unique', connection, ['first', 'third']); + req.onsuccess = t.step_func(function(evt) { + let result = evt.target.result; + let expected = [ + [ + {'ch': 'a', 'half': 'first'}, {'ch': 'b', 'half': 'first'}, + {'ch': 'c', 'half': 'first'}, {'ch': 'd', 'half': 'first'} + ], + [ + {'ch': 'x', 'half': 'third'}, {'ch': 'y', 'half': 'third'}, + {'ch': 'z', 'half': 'third'} + ] + ]; + assertTwoDArrayEquals(result, expected); + t.done(); + }); +}, 'Retrieve multiEntry key'); + +batchgetall_test('out-of-line-multi', (t, connection) => { + var req = + createBatchGetAllRequest(t, 'out-of-line-multi', connection, ['vowel']); + req.onsuccess = t.step_func(function(evt) { + let result = evt.target.result; + let expected = [[ + {'ch': 'a', 'attribs': ['vowel', 'first']}, + {'ch': 'e', 'attribs': ['vowel']}, {'ch': 'i', 'attribs': ['vowel']}, + {'ch': 'o', 'attribs': ['vowel']}, {'ch': 'u', 'attribs': ['vowel']} + ]]; + assertTwoDArrayEquals(result, expected); + t.done(); + }); +}, 'Retrieve one key multiple values');
\ No newline at end of file diff --git a/testing/web-platform/tests/IndexedDB/idbindex_count.htm b/testing/web-platform/tests/IndexedDB/idbindex_count.htm new file mode 100644 index 0000000000..5b45b5223b --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_count.htm @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBIndex.count() - returns the number of records in the index </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var db, t = async_test(); + + var open_rq = createdb(t); + + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var store = db.createObjectStore("store", { autoIncrement: true }); + store.createIndex("index", "indexedProperty"); + + for(var i = 0; i < 10; i++) { + store.add({ indexedProperty: "data" + i }); + } + } + + open_rq.onsuccess = function(e) { + var rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .index("index") + .count(); + + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result, 10); + t.done(); + }); + } +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbindex_count2.htm b/testing/web-platform/tests/IndexedDB/idbindex_count2.htm new file mode 100644 index 0000000000..2a68770df9 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_count2.htm @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBIndex.count() - returns the number of records that have keys within the range </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var db, t = async_test(); + + var open_rq = createdb(t); + + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var store = db.createObjectStore("store", { autoIncrement: true }); + store.createIndex("index", "indexedProperty"); + + for(var i = 0; i < 10; i++) { + store.add({ indexedProperty: "data" + i }); + } + } + + open_rq.onsuccess = function(e) { + var rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .index("index") + .count(IDBKeyRange.bound('data0', 'data4')); + + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result, 5); + t.done(); + }); + } +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbindex_count3.htm b/testing/web-platform/tests/IndexedDB/idbindex_count3.htm new file mode 100644 index 0000000000..a94e898554 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_count3.htm @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBIndex.count() - returns the number of records that have keys with the key</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var db + + createdb(async_test()).onupgradeneeded = function(e) { + db = e.target.result + + var store = db.createObjectStore("store", { autoIncrement: true }) + store.createIndex("myindex", "idx") + + for (var i = 0; i < 10; i++) + store.add({ idx: "data_" + (i%2) }); + + store.index("myindex").count("data_0").onsuccess = this.step_func(function(e) { + assert_equals(e.target.result, 5, "count(data_0)") + this.done() + }) + } + +</script> +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbindex_count4.htm b/testing/web-platform/tests/IndexedDB/idbindex_count4.htm new file mode 100644 index 0000000000..ce19968bf1 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_count4.htm @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.count() - throw DataError when using invalid key</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBIndex-count-IDBRequest-any-key"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, t = async_test(); + + var open_rq = createdb(t); + + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var store = db.createObjectStore("store", { autoIncrement: true }); + store.createIndex("index", "indexedProperty"); + + for(var i = 0; i < 10; i++) { + store.add({ indexedProperty: "data" + i }); + } + } + + open_rq.onsuccess = function(e) { + var index = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .index("index"); + + assert_throws_dom("DataError", function() { + index.count(NaN); + }); + + t.done(); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbindex_get.htm b/testing/web-platform/tests/IndexedDB/idbindex_get.htm new file mode 100644 index 0000000000..861d7e54d1 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_get.htm @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.get() - returns the record </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, index, + t = async_test(), + record = { key: 1, indexedProperty: "data" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store", { keyPath: "key" }); + index = objStore.createIndex("index", "indexedProperty"); + + objStore.add(record); + } + + open_rq.onsuccess = function(e) { + var rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .index("index") + .get(record.indexedProperty); + + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result.key, record.key); + t.done(); + }); + } +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbindex_get2.htm b/testing/web-platform/tests/IndexedDB/idbindex_get2.htm new file mode 100644 index 0000000000..66d2fee5cb --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_get2.htm @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.get() - returns the record where the index contains duplicate values </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + records = [ { key:1, indexedProperty:"data" }, + { key:2, indexedProperty:"data" }, + { key:3, indexedProperty:"data" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", { keyPath: "key" }); + objStore.createIndex("index", "indexedProperty"); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .index("index") + .get("data"); + + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result.key, records[0].key); + t.done(); + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbindex_get3.htm b/testing/web-platform/tests/IndexedDB/idbindex_get3.htm new file mode 100644 index 0000000000..62a0a4f3ba --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_get3.htm @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.get() - attempt to retrieve a record that doesn't exist </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var rq = db.createObjectStore("test", { keyPath: "key" }) + .createIndex("index", "indexedProperty") + .get(1); + + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result, undefined); + t.done(); + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbindex_get4.htm b/testing/web-platform/tests/IndexedDB/idbindex_get4.htm new file mode 100644 index 0000000000..369c288071 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_get4.htm @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBIndex.get() - returns the record with the first key in the range </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var db, t = async_test(); + + var open_rq = createdb(t); + + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var store = db.createObjectStore("store", { keyPath: "key" }); + store.createIndex("index", "indexedProperty"); + + for(var i = 0; i < 10; i++) { + store.add({ key: i, indexedProperty: "data" + i }); + } + } + + open_rq.onsuccess = function(e) { + var rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .index("index") + .get(IDBKeyRange.bound('data4', 'data7')); + + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result.key, 4); + assert_equals(e.target.result.indexedProperty, 'data4'); + + step_timeout(function() { t.done(); }, 4) + }); + } +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbindex_get5.htm b/testing/web-platform/tests/IndexedDB/idbindex_get5.htm new file mode 100644 index 0000000000..9b0bfe4b35 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_get5.htm @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.get() - throw DataError when using invalid key </title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBIndex-get-IDBRequest-any-key"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var index = db.createObjectStore("test", { keyPath: "key" }) + .createIndex("index", "indexedProperty"); + assert_throws_dom("DataError",function(){ + index.get(NaN); + }); + t.done(); + }; +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbindex_get6.htm b/testing/web-platform/tests/IndexedDB/idbindex_get6.htm new file mode 100644 index 0000000000..ca51b8492f --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_get6.htm @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.get() - throw InvalidStateError when the index is deleted</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBIndex-get-IDBRequest-any-key"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> +<div id="log"></div> +<script> + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var store = db.createObjectStore("store", { keyPath: "key" }); + var index = store.createIndex("index", "indexedProperty"); + + store.add({ key: 1, indexedProperty: "data" }); + store.deleteIndex("index"); + + assert_throws_dom("InvalidStateError", function(){ + index.get("data"); + }); + t.done(); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbindex_get7.htm b/testing/web-platform/tests/IndexedDB/idbindex_get7.htm new file mode 100644 index 0000000000..5e9511a0b1 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_get7.htm @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.get() - throw TransactionInactiveError on aborted transaction</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBIndex-get-IDBRequest-any-key"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> +<div id="log"></div> +<script> + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var store = db.createObjectStore("store", { keyPath: "key" }); + var index = store.createIndex("index", "indexedProperty"); + store.add({ key: 1, indexedProperty: "data" }); + } + open_rq.onsuccess = function(e) { + db = e.target.result; + var tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + var index = tx.objectStore('store').index('index'); + tx.abort(); + + assert_throws_dom("TransactionInactiveError", function(){ + index.get("data"); + }); + t.done(); + } +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbindex_get8.htm b/testing/web-platform/tests/IndexedDB/idbindex_get8.htm new file mode 100644 index 0000000000..a2565419b9 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_get8.htm @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.get() - throw InvalidStateError on index deleted by aborted upgrade</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idb"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> +<div id="log"></div> +<script> + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var store = db.createObjectStore("store", { keyPath: "key" }); + var index = store.createIndex("index", "indexedProperty"); + store.add({ key: 1, indexedProperty: "data" }); + + e.target.transaction.abort(); + + assert_throws_dom("InvalidStateError", function(){ + index.get("data"); + }); + t.done(); + } +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbindex_getAll.html b/testing/web-platform/tests/IndexedDB/idbindex_getAll.html new file mode 100644 index 0000000000..bd2a021386 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_getAll.html @@ -0,0 +1,216 @@ +<!DOCTYPE html> +<title>IndexedDB: Test IDBIndex.getAll.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> +var alphabet = 'abcdefghijklmnopqrstuvwxyz'.split(''); +var ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); + +function getall_test(func, name) { + indexeddb_test( + function(t, connection, tx) { + var store = connection.createObjectStore('generated', + {autoIncrement: true, keyPath: 'id'}); + var index = store.createIndex('test_idx', 'upper'); + alphabet.forEach(function(letter) { + store.put({ch: letter, upper: letter.toUpperCase()}); + }); + + store = connection.createObjectStore('out-of-line', null); + index = store.createIndex('test_idx', 'upper'); + alphabet.forEach(function(letter) { + store.put({ch: letter, upper: letter.toUpperCase()}, letter); + }); + + store = connection.createObjectStore('out-of-line-not-unique', null); + index = store.createIndex('test_idx', 'half'); + alphabet.forEach(function(letter) { + if (letter <= 'm') + store.put({ch: letter, half: 'first'}, letter); + else + store.put({ch: letter, half: 'second'}, letter); + }); + + store = connection.createObjectStore('out-of-line-multi', null); + index = store.createIndex('test_idx', 'attribs', {multiEntry: true}); + alphabet.forEach(function(letter) { + attrs = []; + if (['a', 'e', 'i', 'o', 'u'].indexOf(letter) != -1) + attrs.push('vowel'); + else + attrs.push('consonant'); + if (letter == 'a') + attrs.push('first'); + if (letter == 'z') + attrs.push('last'); + store.put({ch: letter, attribs: attrs}, letter); + }); + + store = connection.createObjectStore('empty', null); + index = store.createIndex('test_idx', 'upper'); + }, + func, + name + ); +} + +function createGetAllRequest(t, storeName, connection, range, maxCount) { + var transaction = connection.transaction(storeName, 'readonly'); + var store = transaction.objectStore(storeName); + var index = store.index('test_idx'); + var req = index.getAll(range, maxCount); + req.onerror = t.unreached_func('getAll request should succeed'); + return req; +} + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'out-of-line', connection, 'C'); + req.onsuccess = t.step_func(function(evt) { + var data = evt.target.result; + assert_class_string(data, 'Array', 'result should be an array'); + assert_array_equals(data.map(function(e) { return e.ch; }), ['c']); + assert_array_equals(data.map(function(e) { return e.upper; }), ['C']); + t.done(); + }); + }, 'Single item get'); + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'empty', connection); + req.onsuccess = t.step_func(function(evt) { + assert_array_equals(evt.target.result, [], + 'getAll() on empty object store should return an empty array'); + t.done(); + }); + }, 'Empty object store'); + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'out-of-line', connection); + req.onsuccess = t.step_func(function(evt) { + var data = evt.target.result; + assert_class_string(data, 'Array', 'result should be an array'); + assert_array_equals(data.map(function(e) { return e.ch; }), alphabet); + assert_array_equals(data.map(function(e) { return e.upper; }), ALPHABET); + t.done(); + }); + }, 'Get all keys'); + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'out-of-line', connection, undefined, + 10); + req.onsuccess = t.step_func(function(evt) { + var data = evt.target.result; + assert_class_string(data, 'Array', 'result should be an array'); + assert_array_equals(data.map(function(e) { return e.ch; }), 'abcdefghij'.split('')); + assert_array_equals(data.map(function(e) { return e.upper; }), 'ABCDEFGHIJ'.split('')); + t.done(); + }); + }, 'maxCount=10'); + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'out-of-line', connection, + IDBKeyRange.bound('G', 'M')); + req.onsuccess = t.step_func(function(evt) { + var data = evt.target.result; + assert_array_equals(data.map(function(e) { return e.ch; }), 'ghijklm'.split('')); + assert_array_equals(data.map(function(e) { return e.upper; }), 'GHIJKLM'.split('')); + t.done(); + }); + }, 'Get bound range'); + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'out-of-line', connection, + IDBKeyRange.bound('G', 'M'), 3); + req.onsuccess = t.step_func(function(evt) { + var data = evt.target.result; + assert_class_string(data, 'Array', 'result should be an array'); + assert_array_equals(data.map(function(e) { return e.ch; }), 'ghi'.split('')); + assert_array_equals(data.map(function(e) { return e.upper; }), 'GHI'.split('')); + t.done(); + }); + }, 'Get bound range with maxCount'); + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'out-of-line', connection, + IDBKeyRange.bound('G', 'K', false, true)); + req.onsuccess = t.step_func(function(evt) { + var data = evt.target.result; + assert_class_string(data, 'Array', 'result should be an array'); + assert_array_equals(data.map(function(e) { return e.ch; }), 'ghij'.split('')); + assert_array_equals(data.map(function(e) { return e.upper; }), 'GHIJ'.split('')); + t.done(); + }); + }, 'Get upper excluded'); + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'out-of-line', connection, + IDBKeyRange.bound('G', 'K', true, false)); + req.onsuccess = t.step_func(function(evt) { + var data = evt.target.result; + assert_class_string(data, 'Array', 'result should be an array'); + assert_array_equals(data.map(function(e) { return e.ch; }), 'hijk'.split('')); + assert_array_equals(data.map(function(e) { return e.upper; }), 'HIJK'.split('')); + t.done(); + }); + }, 'Get lower excluded'); + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'generated', + connection, IDBKeyRange.bound(4, 15), 3); + req.onsuccess = t.step_func(function(evt) { + var data = evt.target.result; + assert_true(Array.isArray(data)); + assert_equals(data.length, 0); + t.done(); + }); + }, 'Get bound range (generated) with maxCount'); + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'out-of-line', + connection, "Doesn't exist"); + req.onsuccess = t.step_func(function(evt) { + assert_array_equals(evt.target.result, [], + 'getAll() using a nonexistent key should return an empty array'); + t.done(); + req.onerror = t.unreached_func('getAll request should succeed'); + }); + }, 'Non existent key'); + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'out-of-line', connection, + undefined, 0); + req.onsuccess = t.step_func(function(evt) { + var data = evt.target.result; + assert_class_string(data, 'Array', 'result should be an array'); + assert_array_equals(data.map(function(e) { return e.ch; }), alphabet); + assert_array_equals(data.map(function(e) { return e.upper; }), ALPHABET); + t.done(); + }); + }, 'maxCount=0'); + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'out-of-line-not-unique', connection, + 'first'); + req.onsuccess = t.step_func(function(evt) { + var data = evt.target.result; + assert_class_string(data, 'Array', 'result should be an array'); + assert_array_equals(data.map(function(e) { return e.ch; }), 'abcdefghijklm'.split('')); + assert_true(data.every(function(e) { return e.half === 'first'; })); + t.done(); + }); + }, 'Retrieve multiEntry key'); + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'out-of-line-multi', connection, + 'vowel'); + req.onsuccess = t.step_func(function(evt) { + var data = evt.target.result; + assert_class_string(data, 'Array', 'result should be an array'); + assert_array_equals(data.map(function(e) { return e.ch; }), ['a', 'e', 'i', 'o', 'u']); + assert_array_equals(data[0].attribs, ['vowel', 'first']); + assert_true(data.every(function(e) { return e.attribs[0] === 'vowel'; })); + t.done(); + }); + }, 'Retrieve one key multiple values'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbindex_getAllKeys.html b/testing/web-platform/tests/IndexedDB/idbindex_getAllKeys.html new file mode 100644 index 0000000000..5640bfdee7 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_getAllKeys.html @@ -0,0 +1,192 @@ +<!DOCTYPE html> +<title>IndexedDB: Test IDBIndex.getAllKeys.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +var alphabet = 'abcdefghijklmnopqrstuvwxyz'.split(''); + +function getall_test(func, name) { + indexeddb_test( + function(t, connection, tx) { + var store = connection.createObjectStore('generated', + {autoIncrement: true, keyPath: 'id'}); + var index = store.createIndex('test_idx', 'upper'); + alphabet.forEach(function(letter) { + store.put({ch: letter, upper: letter.toUpperCase()}); + }); + + store = connection.createObjectStore('out-of-line', null); + index = store.createIndex('test_idx', 'upper'); + alphabet.forEach(function(letter) { + store.put({ch: letter, upper: letter.toUpperCase()}, letter); + }); + + store = connection.createObjectStore('out-of-line-multi', null); + index = store.createIndex('test_idx', 'attribs', {multiEntry: true}); + alphabet.forEach(function(letter) { + attrs = []; + if (['a', 'e', 'i', 'o', 'u'].indexOf(letter) != -1) + attrs.push('vowel'); + else + attrs.push('consonant'); + if (letter == 'a') + attrs.push('first'); + if (letter == 'z') + attrs.push('last'); + store.put({ch: letter, attribs: attrs}, letter.toUpperCase()); + }); + + store = connection.createObjectStore('empty', null); + index = store.createIndex('test_idx', 'upper'); + }, + func, + name + ); +} + +function createGetAllKeysRequest(t, storeName, connection, range, maxCount) { + var transaction = connection.transaction(storeName, 'readonly'); + var store = transaction.objectStore(storeName); + var index = store.index('test_idx'); + var req = index.getAllKeys(range, maxCount); + req.onerror = t.unreached_func('getAllKeys request should succeed'); + return req; +} + +getall_test(function(t, connection) { + var req = createGetAllKeysRequest(t, 'out-of-line', connection, 'C'); + req.onsuccess = t.step_func(function(evt) { + var data = evt.target.result; + assert_array_equals(evt.target.result, ['c']); + t.done(); + }); + }, 'Single item get'); + +getall_test(function(t, connection) { + var req = createGetAllKeysRequest(t, 'empty', connection); + req.onsuccess = t.step_func(function(evt) { + assert_array_equals(evt.target.result, [], + 'getAllKeys() on empty object store should return empty array'); + t.done(); + }); + }, 'Empty object store'); + +getall_test(function(t, connection) { + var req = createGetAllKeysRequest(t, 'out-of-line', connection); + req.onsuccess = t.step_func(function(evt) { + assert_array_equals(evt.target.result, alphabet, + 'getAllKeys() should return a..z'); + t.done(); + }); + }, 'Get all keys'); + +getall_test(function(t, connection) { + var req = createGetAllKeysRequest(t, 'generated', connection); + req.onsuccess = t.step_func(function(evt) { + assert_array_equals(evt.target.result, + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26], + 'getAllKeys() should return 1..26'); + t.done(); + }); + }, 'Get all generated keys'); + +getall_test(function(t, connection) { + var req = createGetAllKeysRequest(t, 'out-of-line', connection, undefined, + 10); + req.onsuccess = t.step_func(function(evt) { + assert_array_equals(evt.target.result, + 'abcdefghij'.split(''), + 'getAllKeys() should return a..j'); + t.done(); + }); + }, 'maxCount=10'); + +getall_test(function(t, connection) { + var req = createGetAllKeysRequest(t, 'out-of-line', connection, + IDBKeyRange.bound('G', 'M')); + req.onsuccess = t.step_func(function(evt) { + assert_array_equals(evt.target.result, + 'ghijklm'.split(''), + 'getAllKeys() should return g..m'); + t.done(); + }); + }, 'Get bound range'); + +getall_test(function(t, connection) { + var req = createGetAllKeysRequest(t, 'out-of-line', connection, + IDBKeyRange.bound('G', 'M'), 3); + req.onsuccess = t.step_func(function(evt) { + assert_array_equals(evt.target.result, + ['g', 'h', 'i'], + 'getAllKeys() should return g..i'); + t.done(); + }); + }, 'Get bound range with maxCount'); + +getall_test(function(t, connection) { + var req = createGetAllKeysRequest(t, 'out-of-line', connection, + IDBKeyRange.bound('G', 'K', false, true)); + req.onsuccess = t.step_func(function(evt) { + assert_array_equals(evt.target.result, + ['g', 'h', 'i', 'j'], + 'getAllKeys() should return g..j'); + t.done(); + }); + }, 'Get upper excluded'); + +getall_test(function(t, connection) { + var req = createGetAllKeysRequest(t, 'out-of-line', connection, + IDBKeyRange.bound('G', 'K', true, false)); + req.onsuccess = t.step_func(function(evt) { + assert_array_equals(evt.target.result, + ['h', 'i', 'j', 'k'], + 'getAllKeys() should return h..k'); + t.done(); + }); + }, 'Get lower excluded'); + +getall_test(function(t, connection) { + var req = createGetAllKeysRequest(t, 'generated', + connection, IDBKeyRange.bound(4, 15), 3); + req.onsuccess = t.step_func(function(evt) { + assert_array_equals(evt.target.result, [], + 'getAllKeys() should return []'); + t.done(); + }); + }, 'Get bound range (generated) with maxCount'); + +getall_test(function(t, connection) { + var req = createGetAllKeysRequest(t, 'out-of-line', + connection, "Doesn't exist"); + req.onsuccess = t.step_func(function(evt) { + assert_array_equals(evt.target.result, [], + 'getAllKeys() using a nonexistent key should return empty array'); + t.done(); + req.onerror = t.unreached_func('getAllKeys request should succeed'); + }); + }, 'Non existent key'); + +getall_test(function(t, connection) { + var req = createGetAllKeysRequest(t, 'out-of-line', connection, + undefined, 0); + req.onsuccess = t.step_func(function(evt) { + assert_array_equals(evt.target.result, alphabet, + 'getAllKeys() should return a..z'); + t.done(); + }); + }, 'maxCount=0'); + +getall_test(function(t, connection) { + var req = createGetAllKeysRequest(t, 'out-of-line-multi', connection, + 'vowel'); + req.onsuccess = t.step_func(function(evt) { + assert_array_equals(evt.target.result, ['A','E','I','O','U']) + t.done(); + }); + req.onerror = t.unreached_func('getAllKeys request should succeed'); + }, 'Retrieve multiEntry keys'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbindex_getKey.htm b/testing/web-platform/tests/IndexedDB/idbindex_getKey.htm new file mode 100644 index 0000000000..101e8f06b7 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_getKey.htm @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.getKey() - returns the record's primary key </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + record = { key:1, indexedProperty:"data" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", { keyPath: "key" }); + objStore.createIndex("index", "indexedProperty"); + + objStore.add(record); + }; + + open_rq.onsuccess = function(e) { + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test"); + + rq = rq.index("index"); + + rq = rq.getKey("data"); + + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result, record.key); + t.done(); + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbindex_getKey2.htm b/testing/web-platform/tests/IndexedDB/idbindex_getKey2.htm new file mode 100644 index 0000000000..488368d7f4 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_getKey2.htm @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.getKey() - returns the record's primary key where the index contains duplicate values </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + records = [ { key:1, indexedProperty:"data" }, + { key:2, indexedProperty:"data" }, + { key:3, indexedProperty:"data" } ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", { keyPath: "key" }); + objStore.createIndex("index", "indexedProperty"); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .index("index") + .getKey("data"); + + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result, records[0].key); + t.done(); + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbindex_getKey3.htm b/testing/web-platform/tests/IndexedDB/idbindex_getKey3.htm new file mode 100644 index 0000000000..40ed76d174 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_getKey3.htm @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.getKey() - attempt to retrieve the primary key of a record that doesn't exist</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(); + + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var rq = db.createObjectStore("test", { keyPath: "key" }) + .createIndex("index", "indexedProperty") + .getKey(1); + + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result, undefined); + t.done(); + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbindex_getKey4.htm b/testing/web-platform/tests/IndexedDB/idbindex_getKey4.htm new file mode 100644 index 0000000000..8c81a272ae --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_getKey4.htm @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBIndex.getKey() - returns the key of the first record within the range </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var db, t = async_test(); + + var open_rq = createdb(t); + + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var store = db.createObjectStore("store", { keyPath: "key" }); + store.createIndex("index", "indexedProperty"); + + for(var i = 0; i < 10; i++) { + store.add({ key: i, indexedProperty: "data" + i }); + } + } + + open_rq.onsuccess = function(e) { + var rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .index("index") + .getKey(IDBKeyRange.bound('data4', 'data7')); + + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result, 4); + + step_timeout(function() { t.done(); }, 4) + }); + } +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbindex_getKey5.htm b/testing/web-platform/tests/IndexedDB/idbindex_getKey5.htm new file mode 100644 index 0000000000..3155131397 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_getKey5.htm @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.getKey() - throw DataError when using invalid key </title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBIndex-getKey-IDBRequest-any-key"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var index = db.createObjectStore("test", { keyPath: "key" }) + .createIndex("index", "indexedProperty"); + assert_throws_dom("DataError",function(){ + index.getKey(NaN); + }); + t.done(); + }; +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbindex_getKey6.htm b/testing/web-platform/tests/IndexedDB/idbindex_getKey6.htm new file mode 100644 index 0000000000..b71967d4fb --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_getKey6.htm @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.getKey() - throw InvalidStateError when the index is deleted</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBIndex-getKey-IDBRequest-any-key"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> +<div id="log"></div> +<script> + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var store = db.createObjectStore("store", { keyPath: "key" }); + var index = store.createIndex("index", "indexedProperty"); + + store.add({ key: 1, indexedProperty: "data" }); + store.deleteIndex("index"); + + assert_throws_dom("InvalidStateError", function(){ + index.getKey("data"); + }); + t.done(); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbindex_getKey7.htm b/testing/web-platform/tests/IndexedDB/idbindex_getKey7.htm new file mode 100644 index 0000000000..6a64df3116 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_getKey7.htm @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.getKey() - throw TransactionInactiveError on aborted transaction</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBIndex-getKey-IDBRequest-any-key"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> +<div id="log"></div> +<script> + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var store = db.createObjectStore("store", { keyPath: "key" }); + var index = store.createIndex("index", "indexedProperty"); + store.add({ key: 1, indexedProperty: "data" }); + } + open_rq.onsuccess = function(e) { + db = e.target.result; + var tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + var index = tx.objectStore('store').index('index'); + tx.abort(); + + assert_throws_dom("TransactionInactiveError", function(){ + index.getKey("data"); + }); + t.done(); + } +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbindex_getKey8.htm b/testing/web-platform/tests/IndexedDB/idbindex_getKey8.htm new file mode 100644 index 0000000000..cf0affb210 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_getKey8.htm @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.getKey() - throw InvalidStateError on index deleted by aborted upgrade</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbindex-getkey"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> +<div id="log"></div> +<script> + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var store = db.createObjectStore("store", { keyPath: "key" }); + var index = store.createIndex("index", "indexedProperty"); + store.add({ key: 1, indexedProperty: "data" }); + + e.target.transaction.abort(); + + assert_throws_dom("InvalidStateError", function(){ + index.getKey("data"); + }); + t.done(); + } +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbindex_indexNames.htm b/testing/web-platform/tests/IndexedDB/idbindex_indexNames.htm new file mode 100644 index 0000000000..7da76021c8 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_indexNames.htm @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.getKey() - returns the record's primary key</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("test", { keyPath: "key" }); + objStore.createIndex("index", "data"); + + assert_equals(objStore.indexNames[0], "index", "indexNames"); + assert_equals(objStore.indexNames.length, 1, "indexNames.length"); + }; + + open_rq.onsuccess = function(e) { + var objStore = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test"); + + assert_equals(objStore.indexNames[0], "index", "indexNames (second)"); + assert_equals(objStore.indexNames.length, 1, "indexNames.length (second)"); + + t.done(); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbindex_keyPath.any.js b/testing/web-platform/tests/IndexedDB/idbindex_keyPath.any.js new file mode 100644 index 0000000000..40721462a3 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_keyPath.any.js @@ -0,0 +1,74 @@ +// META: title=IndexedDB: IDBIndex keyPath attribute +// META: script=resources/support.js + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('store', {keyPath: ['a', 'b']}); + store.createIndex('index', ['a', 'b']); + }, + (t, db) => { + const tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + const index = store.index('index'); + assert_equals(typeof index.keyPath, 'object', 'keyPath is an object'); + assert_true(Array.isArray(index.keyPath), 'keyPath is an array'); + + assert_equals( + index.keyPath, index.keyPath, + 'Same object instance is returned each time keyPath is inspected'); + + const tx2 = db.transaction('store', 'readonly', {durability: 'relaxed'}); + const store2 = tx2.objectStore('store'); + const index2 = store2.index('index'); + + assert_not_equals( + index.keyPath, index2.keyPath, + 'Different instances are returned from different index instances.'); + + t.done(); + }, + `IDBIndex's keyPath attribute returns the same object.`); + + indexeddb_test( + (t, db) => { + const store = db.createObjectStore('store', {autoIncrement: true}); + store.createIndex('index', ['a']); + + store.add({a: 1, b: 2, c: 3}) + }, + (t, db) => { + const tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + const index = store.index('index'); + const cursorReq = index.openCursor(); + + cursorReq.onsuccess = t.step_func_done((e) => { + const expectedKeyValue = [1]; + const actualKeyValue = e.target.result.key; + + assert_array_equals(actualKeyValue, expectedKeyValue, "An array keypath should yield an array key"); + }); + }, + `IDBIndex's keyPath array with a single value`); + + indexeddb_test( + (t, db) => { + const store = db.createObjectStore('store', {autoIncrement: true}); + store.createIndex('index', ['a', 'b']); + + store.add({a: 1, b: 2, c: 3}) + }, + (t, db) => { + const tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + const index = store.index('index'); + const cursorReq = index.openCursor(); + + cursorReq.onsuccess = t.step_func_done((e) => { + const expectedKeyValue = [1, 2]; + const actualKeyValue = e.target.result.key; + + assert_array_equals(actualKeyValue, expectedKeyValue, "An array keypath should yield an array key"); + }); + }, + `IDBIndex's keyPath array with multiple values`); diff --git a/testing/web-platform/tests/IndexedDB/idbindex_openCursor.htm b/testing/web-platform/tests/IndexedDB/idbindex_openCursor.htm new file mode 100644 index 0000000000..7baf2cf9ed --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_openCursor.htm @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.openCursor() - throw InvalidStateError when the index is deleted</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBIndex-openCursor-IDBRequest-any-range-IDBCursorDirection-direction"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> +<div id="log"></div> +<script> + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var store = db.createObjectStore("store", { keyPath: "key" }); + var index = store.createIndex("index", "indexedProperty"); + + store.add({ key: 1, indexedProperty: "data" }); + store.deleteIndex("index"); + + assert_throws_dom("InvalidStateError", function(){ + index.openCursor(); + }); + t.done(); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbindex_openCursor2.htm b/testing/web-platform/tests/IndexedDB/idbindex_openCursor2.htm new file mode 100644 index 0000000000..88dcdbac36 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_openCursor2.htm @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.openCursor() - throw TransactionInactiveError on aborted transaction</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBIndex-openCursor-IDBRequest-any-range-IDBCursorDirection-direction"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> +<div id="log"></div> +<script> + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var store = db.createObjectStore("store", { keyPath: "key" }); + var index = store.createIndex("index", "indexedProperty"); + store.add({ key: 1, indexedProperty: "data" }); + } + open_rq.onsuccess = function(e) { + db = e.target.result; + var tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + var index = tx.objectStore('store').index('index'); + tx.abort(); + + assert_throws_dom("TransactionInactiveError", function(){ + index.openCursor(); + }); + t.done(); + } +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbindex_openCursor3.htm b/testing/web-platform/tests/IndexedDB/idbindex_openCursor3.htm new file mode 100644 index 0000000000..91a98464e0 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_openCursor3.htm @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.openCursor() - throw InvalidStateError on index deleted by aborted upgrade</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbindex-opencursor"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> +<div id="log"></div> +<script> + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var store = db.createObjectStore("store", { keyPath: "key" }); + var index = store.createIndex("index", "indexedProperty"); + store.add({ key: 1, indexedProperty: "data" }); + + e.target.transaction.abort(); + + assert_throws_dom("InvalidStateError", function(){ + index.openCursor(); + }); + t.done(); + } +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbindex_openKeyCursor.htm b/testing/web-platform/tests/IndexedDB/idbindex_openKeyCursor.htm new file mode 100644 index 0000000000..9436684c3d --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_openKeyCursor.htm @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.openKeyCursor() - throw DataError when using a invalid key</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBIndex-openKeyCursor-IDBRequest-any-range-IDBCursorDirection-direction"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> +<div id="log"></div> +<script> + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var store = db.createObjectStore("store", { keyPath: "key" }); + var index = store.createIndex("index", "indexedProperty"); + + store.add({ key: 1, indexedProperty: "data" }); + + assert_throws_dom("DataError", function(){ + index.openKeyCursor(NaN); + }); + t.done(); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbindex_openKeyCursor2.htm b/testing/web-platform/tests/IndexedDB/idbindex_openKeyCursor2.htm new file mode 100644 index 0000000000..ec97345a87 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_openKeyCursor2.htm @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.openKeyCursor() - throw InvalidStateError when the index is deleted</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBIndex-openKeyCursor-IDBRequest-any-range-IDBCursorDirection-direction"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> +<div id="log"></div> +<script> + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var store = db.createObjectStore("store", { keyPath: "key" }); + var index = store.createIndex("index", "indexedProperty"); + + store.add({ key: 1, indexedProperty: "data" }); + store.deleteIndex("index"); + + assert_throws_dom("InvalidStateError", function(){ + index.openKeyCursor(); + }); + t.done(); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbindex_openKeyCursor3.htm b/testing/web-platform/tests/IndexedDB/idbindex_openKeyCursor3.htm new file mode 100644 index 0000000000..c7416be38d --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_openKeyCursor3.htm @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.openKeyCursor() - throw TransactionInactiveError on aborted transaction</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBIndex-openKeyCursor-IDBRequest-any-range-IDBCursorDirection-direction"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> +<div id="log"></div> +<script> + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var store = db.createObjectStore("store", { keyPath: "key" }); + var index = store.createIndex("index", "indexedProperty"); + store.add({ key: 1, indexedProperty: "data" }); + } + open_rq.onsuccess = function(e) { + db = e.target.result; + var tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + var index = tx.objectStore('store').index('index'); + tx.abort(); + + assert_throws_dom("TransactionInactiveError", function(){ + index.openKeyCursor(); + }); + t.done(); + } +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbindex_openKeyCursor4.htm b/testing/web-platform/tests/IndexedDB/idbindex_openKeyCursor4.htm new file mode 100644 index 0000000000..bcc1511c90 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_openKeyCursor4.htm @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBIndex.openKeyCursor() - throw InvalidStateError on index deleted by aborted upgrade</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbindex-openkeycursor"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> +<div id="log"></div> +<script> + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var store = db.createObjectStore("store", { keyPath: "key" }); + var index = store.createIndex("index", "indexedProperty"); + store.add({ key: 1, indexedProperty: "data" }); + + e.target.transaction.abort(); + + assert_throws_dom("InvalidStateError", function(){ + index.openKeyCursor(); + }); + t.done(); + } +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbindex_reverse_cursor.any.js b/testing/web-platform/tests/IndexedDB/idbindex_reverse_cursor.any.js new file mode 100644 index 0000000000..88c367466d --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_reverse_cursor.any.js @@ -0,0 +1,60 @@ +// META: title=Reverse Cursor Validity +// META: script=resources/support-promises.js + +async function iterateAndReturnAllCursorResult(testCase, cursor) { + return new Promise((resolve, reject) => { + let results = []; + cursor.onsuccess = testCase.step_func(function(e) { + let cursor = e.target.result; + if (!cursor) { + resolve(results); + return; + } + results.push(cursor.value); + cursor.continue(); + }); + cursor.onerror = reject; + }); +} + +promise_test(async testCase => { + const db = await createDatabase(testCase, db => { + db.createObjectStore('objectStore', {keyPath: 'key'}) + .createIndex('index', 'indexedOn'); + }); + const txn1 = db.transaction(['objectStore'], 'readwrite'); + txn1.objectStore('objectStore').add({'key': 'firstItem', 'indexedOn': 3}); + const txn2 = db.transaction(['objectStore'], 'readwrite'); + txn2.objectStore('objectStore').put({'key': 'firstItem', 'indexedOn': -1}); + const txn3= db.transaction(['objectStore'], 'readwrite'); + txn3.objectStore('objectStore').add({'key': 'secondItem', 'indexedOn': 2}); + + const txn4 = db.transaction(['objectStore'], 'readonly'); + cursor = txn4.objectStore('objectStore').index('index').openCursor(IDBKeyRange.bound(0, 10), "prev"); + let results = await iterateAndReturnAllCursorResult(testCase, cursor); + + assert_equals(results.length, 1); + + await promiseForTransaction(testCase, txn4); + db.close(); +}, 'Reverse cursor sees update from separate transactions.'); + +promise_test(async testCase => { + const db = await createDatabase(testCase, db => { + db.createObjectStore('objectStore', {keyPath: 'key'}) + .createIndex('index', 'indexedOn'); + }); + const txn = db.transaction(['objectStore'], 'readwrite'); + txn.objectStore('objectStore').add({'key': '1', 'indexedOn': 2}); + txn.objectStore('objectStore').put({'key': '1', 'indexedOn': -1}); + txn.objectStore('objectStore').add({'key': '2', 'indexedOn': 1}); + + const txn2 = db.transaction(['objectStore'], 'readonly'); + cursor = txn2.objectStore('objectStore').index('index').openCursor(IDBKeyRange.bound(0, 10), "prev"); + let results = await iterateAndReturnAllCursorResult(testCase, cursor); + + assert_equals(1, results.length); + + await promiseForTransaction(testCase, txn2); + db.close(); +}, 'Reverse cursor sees in-transaction update.'); diff --git a/testing/web-platform/tests/IndexedDB/idbindex_tombstones.any.js b/testing/web-platform/tests/IndexedDB/idbindex_tombstones.any.js new file mode 100644 index 0000000000..7a2dcc9cb1 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbindex_tombstones.any.js @@ -0,0 +1,66 @@ +// META: title=Index Tombstones +// META: script=resources/support-promises.js + +// This test is used to trigger a special case in Chrome with how it deals with +// index creation & modification. This had caused issues before. +// See https://crbug.com/1033996 + +async function iterateAndReturnAllCursorResult(testCase, cursorRequest) { + return new Promise((resolve, reject) => { + let results = []; + cursorRequest.onsuccess = testCase.step_func(function(event) { + const cursor = event.target.result; + if (!cursor) { + resolve(results); + return; + } + results.push(cursor.value); + cursor.continue(); + }); + cursorRequest.onerror = reject; + }); +} + +async function createTombstones(testCase, db) { + const txn1 = db.transaction(['objectStore'], 'readwrite'); + txn1.objectStore('objectStore').add({key: 'firstItem', indexedOn: 1}); + txn1.objectStore('objectStore').add({key: 'secondItem', indexedOn: 2}); + txn1.objectStore('objectStore').add({key: 'thirdItem', indexedOn: 3}); + const txn2 = db.transaction(['objectStore'], 'readwrite'); + txn2.objectStore('objectStore').put({key: 'firstItem', indexedOn: -10}); + txn2.objectStore('objectStore').put({key: 'secondItem', indexedOn: 4}); + txn2.objectStore('objectStore').put({key: 'thirdItem', indexedOn: 10}); + await promiseForTransaction(testCase, txn1); + await promiseForTransaction(testCase, txn2); +} + +async function run_test(testCase, transactionMode, direction) { + const db = await createDatabase(testCase, db => { + db.createObjectStore('objectStore', {keyPath: 'key'}) + .createIndex('index', 'indexedOn'); + }); + await createTombstones(testCase, db); + + const txn = db.transaction(['objectStore'], transactionMode); + cursor = txn.objectStore('objectStore').index('index').openCursor( + IDBKeyRange.bound(-11, 11), direction); + let results = await iterateAndReturnAllCursorResult(testCase, cursor); + assert_equals(results.length, 3); + db.close(); +} + +promise_test(async testCase => { + await run_test(testCase, 'readonly', 'next'); +}, 'Forward iteration over an index in a readonly transaction'); + +promise_test(async testCase => { + await run_test(testCase, 'readonly', 'prev'); +}, 'Backward iteration over an index in a readonly transaction'); + +promise_test(async testCase => { + await run_test(testCase, 'readwrite', 'next'); +}, 'Forward iteration over an index in a readwrite transaction'); + +promise_test(async testCase => { + await run_test(testCase, 'readwrite', 'prev'); +}, 'Backward iteration over an index in a readwrite transaction'); diff --git a/testing/web-platform/tests/IndexedDB/idbkeyrange-includes.htm b/testing/web-platform/tests/IndexedDB/idbkeyrange-includes.htm new file mode 100644 index 0000000000..96769ae0a9 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbkeyrange-includes.htm @@ -0,0 +1,140 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: IDBKeyRange.includes()</title> +<meta name="help" href="https://w3c.github.io/IndexedDB/#keyrange"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script> + +test(function() { + var range = IDBKeyRange.bound(12, 34); + assert_throws_js(TypeError, function() { range.includes(); }, + 'throws if key is not specified'); + + assert_throws_dom('DataError', function() { range.includes(undefined); }, + 'throws if key is undefined'); + assert_throws_dom('DataError', function() { range.includes(null); }, + 'throws if key is null'); + assert_throws_dom('DataError', function() { range.includes({}); }, + 'throws if key is not valid type'); + assert_throws_dom('DataError', function() { range.includes(NaN); }, + 'throws if key is not valid number'); + assert_throws_dom('DataError', function() { range.includes(new Date(NaN)); }, + 'throws if key is not valid date'); + assert_throws_dom('DataError', function() { + var a = []; a[0] = a; range.includes(a); + }, 'throws if key is not valid array'); +}, "IDBKeyRange.includes() with invalid input"); + +test(function() { + var closedRange = IDBKeyRange.bound(5, 20); + assert_true(!!closedRange.includes, "IDBKeyRange has a .includes"); + assert_true(closedRange.includes(7), "in range"); + assert_false(closedRange.includes(1), "below range"); + assert_false(closedRange.includes(42), "above range"); + assert_true(closedRange.includes(5.01), "at the lower end of the range"); + assert_true(closedRange.includes(19.99), "at the upper end of the range"); + assert_false(closedRange.includes(4.99), "right below range"); + assert_false(closedRange.includes(21.01), "right above range"); + + assert_true(closedRange.includes(5), "lower boundary"); + assert_true(closedRange.includes(20), "upper boundary"); +}, "IDBKeyRange.includes() with a closed range"); + +test(function() { + var closedRange = IDBKeyRange.bound(5, 20, true, true); + assert_true(closedRange.includes(7), "in range"); + assert_false(closedRange.includes(1), "below range"); + assert_false(closedRange.includes(42), "above range"); + assert_true(closedRange.includes(5.01), "at the lower end of the range"); + assert_true(closedRange.includes(19.99), "at the upper end of the range"); + assert_false(closedRange.includes(4.99), "right below range"); + assert_false(closedRange.includes(21.01), "right above range"); + + assert_false(closedRange.includes(5), "lower boundary"); + assert_false(closedRange.includes(20), "upper boundary"); +}, "IDBKeyRange.includes() with an open range"); + +test(function() { + var range = IDBKeyRange.bound(5, 20, true); + assert_true(range.includes(7), "in range"); + assert_false(range.includes(1), "below range"); + assert_false(range.includes(42), "above range"); + assert_true(range.includes(5.01), "at the lower end of the range"); + assert_true(range.includes(19.99), "at the upper end of the range"); + assert_false(range.includes(4.99), "right below range"); + assert_false(range.includes(21.01), "right above range"); + + assert_false(range.includes(5), "lower boundary"); + assert_true(range.includes(20), "upper boundary"); +}, "IDBKeyRange.includes() with a lower-open upper-closed range"); + +test(function() { + var range = IDBKeyRange.bound(5, 20, false, true); + assert_true(range.includes(7), "in range"); + assert_false(range.includes(1), "below range"); + assert_false(range.includes(42), "above range"); + assert_true(range.includes(5.01), "at the lower end of the range"); + assert_true(range.includes(19.99), "at the upper end of the range"); + assert_false(range.includes(4.99), "right below range"); + assert_false(range.includes(21.01), "right above range"); + + assert_true(range.includes(5), "lower boundary"); + assert_false(range.includes(20), "upper boundary"); +}, "IDBKeyRange.includes() with a lower-closed upper-open range"); + +test(function() { + var onlyRange = IDBKeyRange.only(42); + assert_true(onlyRange.includes(42), "in range"); + assert_false(onlyRange.includes(1), "below range"); + assert_false(onlyRange.includes(9000), "above range"); + assert_false(onlyRange.includes(41), "right below range"); + assert_false(onlyRange.includes(43), "right above range"); +}, "IDBKeyRange.includes() with an only range"); + +test(function() { + var range = IDBKeyRange.lowerBound(5); + assert_false(range.includes(4), 'value before closed lower bound'); + assert_true(range.includes(5), 'value at closed lower bound'); + assert_true(range.includes(6), 'value after closed lower bound'); + assert_true(range.includes(42), 'value way after open lower bound'); +}, "IDBKeyRange.includes() with an closed lower-bounded range"); + +test(function() { + var range = IDBKeyRange.lowerBound(5, true); + assert_false(range.includes(4), 'value before open lower bound'); + assert_false(range.includes(5), 'value at open lower bound'); + assert_true(range.includes(6), 'value after open lower bound'); + assert_true(range.includes(42), 'value way after open lower bound'); +}, "IDBKeyRange.includes() with an open lower-bounded range"); + +test(function() { + var range = IDBKeyRange.upperBound(5); + assert_true(range.includes(-42), 'value way before closed upper bound'); + assert_true(range.includes(4), 'value before closed upper bound'); + assert_true(range.includes(5), 'value at closed upper bound'); + assert_false(range.includes(6), 'value after closed upper bound'); +}, "IDBKeyRange.includes() with an closed upper-bounded range"); + +test(function() { + var range = IDBKeyRange.upperBound(5, true); + assert_true(range.includes(-42), 'value way before closed upper bound'); + assert_true(range.includes(4), 'value before open upper bound'); + assert_false(range.includes(5), 'value at open upper bound'); + assert_false(range.includes(6), 'value after open upper bound'); +}, "IDBKeyRange.includes() with an open upper-bounded range"); + +test(function(t) { + assert_true(IDBKeyRange.bound(new Date(0), new Date()) + .includes(new Date(102729600000))); + assert_false(IDBKeyRange.bound(new Date(0), new Date(1e11)) + .includes(new Date(1e11 + 1))); + + assert_true(IDBKeyRange.bound('a', 'c').includes('b')); + assert_false(IDBKeyRange.bound('a', 'c').includes('d')); + + assert_true(IDBKeyRange.bound([], [[], []]).includes([[]])); + assert_false(IDBKeyRange.bound([], [[]]).includes([[[]]])); +}, 'IDBKeyRange.includes() with non-numeric keys'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbkeyrange.htm b/testing/web-platform/tests/IndexedDB/idbkeyrange.htm new file mode 100644 index 0000000000..a387dc74e6 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbkeyrange.htm @@ -0,0 +1,94 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBKeyRange Tests</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + // only + test( function() { + var keyRange = IDBKeyRange.only(1); + assert_true(keyRange instanceof IDBKeyRange, "keyRange instanceof IDBKeyRange"); + assert_equals(keyRange.lower, 1, "keyRange"); + assert_equals(keyRange.upper, 1, "keyRange"); + assert_false(keyRange.lowerOpen, "keyRange.lowerOpen"); + assert_false(keyRange.upperOpen, "keyRange.upperOpen"); + }, "IDBKeyRange.only() - returns an IDBKeyRange and the properties are set correctly"); + + test( function() { + assert_throws_dom('DataError', function() { IDBKeyRange.only(undefined); }, 'undefined is not a valid key'); + assert_throws_dom('DataError', function() { IDBKeyRange.only(null); }, 'null is not a valid key'); + assert_throws_dom('DataError', function() { IDBKeyRange.only({}); }, 'Object is not a valid key'); + assert_throws_dom('DataError', function() { IDBKeyRange.only(Symbol()); }, 'Symbol is not a valid key'); + assert_throws_dom('DataError', function() { IDBKeyRange.only(true); }, 'boolean is not a valid key'); + assert_throws_dom('DataError', function() { IDBKeyRange.only(() => {}); }, 'function is not a valid key'); + }, "IDBKeyRange.only() - throws on invalid keys"); + + // lowerBound + test( function() { + var keyRange = IDBKeyRange.lowerBound(1, true) + assert_true(keyRange instanceof IDBKeyRange, "keyRange instanceof IDBKeyRange"); + assert_equals(keyRange.lower, 1, "keyRange.lower"); + assert_equals(keyRange.upper, undefined, "keyRange.upper"); + assert_true(keyRange.lowerOpen, "keyRange.lowerOpen"); + assert_true(keyRange.upperOpen, "keyRange.upperOpen"); + }, "IDBKeyRange.lowerBound() - returns an IDBKeyRange and the properties are set correctly"); + + test( function() { + var keyRange = IDBKeyRange.lowerBound(1); + assert_false(keyRange.lowerOpen, "keyRange.lowerOpen"); + }, "IDBKeyRange.lowerBound() - 'open' parameter has correct default set"); + + test( function() { + assert_throws_dom('DataError', function() { IDBKeyRange.lowerBound(undefined); }, 'undefined is not a valid key'); + assert_throws_dom('DataError', function() { IDBKeyRange.lowerBound(null); }, 'null is not a valid key'); + assert_throws_dom('DataError', function() { IDBKeyRange.lowerBound({}); }, 'Object is not a valid key'); + assert_throws_dom('DataError', function() { IDBKeyRange.lowerBound(Symbol()); }, 'Symbol is not a valid key'); + assert_throws_dom('DataError', function() { IDBKeyRange.lowerBound(true); }, 'boolean is not a valid key'); + assert_throws_dom('DataError', function() { IDBKeyRange.lowerBound(() => {}); }, 'function is not a valid key'); + }, "IDBKeyRange.lowerBound() - throws on invalid keys"); + + // upperBound + test( function() { + var keyRange = IDBKeyRange.upperBound(1, true); + assert_true(keyRange instanceof IDBKeyRange, "keyRange instanceof IDBKeyRange"); + assert_equals(keyRange.lower, undefined, "keyRange.lower"); + assert_equals(keyRange.upper, 1, "keyRange.upper"); + assert_true(keyRange.lowerOpen, "keyRange.lowerOpen"); + assert_true(keyRange.upperOpen, "keyRange.upperOpen"); + }, "IDBKeyRange.upperBound() - returns an IDBKeyRange and the properties are set correctly"); + + test( function() { + var keyRange = IDBKeyRange.upperBound(1); + assert_false(keyRange.upperOpen, "keyRange.upperOpen"); + }, "IDBKeyRange.upperBound() - 'open' parameter has correct default set"); + + test( function() { + assert_throws_dom('DataError', function() { IDBKeyRange.upperBound(undefined); }, 'undefined is not a valid key'); + assert_throws_dom('DataError', function() { IDBKeyRange.upperBound(null); }, 'null is not a valid key'); + assert_throws_dom('DataError', function() { IDBKeyRange.upperBound({}); }, 'Object is not a valid key'); + assert_throws_dom('DataError', function() { IDBKeyRange.upperBound(Symbol()); }, 'Symbol is not a valid key'); + assert_throws_dom('DataError', function() { IDBKeyRange.upperBound(true); }, 'boolean is not a valid key'); + assert_throws_dom('DataError', function() { IDBKeyRange.upperBound(() => {}); }, 'function is not a valid key'); + }, "IDBKeyRange.upperBound() - throws on invalid keys"); + + // bound + test( function() { + var keyRange = IDBKeyRange.bound(1, 2, true, true); + assert_true(keyRange instanceof IDBKeyRange, "keyRange instanceof IDBKeyRange"); + assert_equals(keyRange.lower, 1, "keyRange"); + assert_equals(keyRange.upper, 2, "keyRange"); + assert_true(keyRange.lowerOpen, "keyRange.lowerOpen"); + assert_true(keyRange.upperOpen, "keyRange.upperOpen"); + }, "IDBKeyRange.bound() - returns an IDBKeyRange and the properties are set correctly"); + + test( function() { + var keyRange = IDBKeyRange.bound(1, 2); + assert_false(keyRange.lowerOpen, "keyRange.lowerOpen"); + assert_false(keyRange.upperOpen, "keyRange.upperOpen"); + }, "IDBKeyRange.bound() - 'lowerOpen' and 'upperOpen' parameters have correct defaults set"); +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbkeyrange_incorrect.htm b/testing/web-platform/tests/IndexedDB/idbkeyrange_incorrect.htm new file mode 100644 index 0000000000..ec72a7e7ae --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbkeyrange_incorrect.htm @@ -0,0 +1,92 @@ +<!DOCTYPE html> +<!-- Submitted from TestTWF Paris --> +<html> + <head> + <meta charset=utf-8> + <title id='desc'>IDBKeyRange Tests - Incorrect</title> + <link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#range-concept"> + <link rel=assert title="If the lower key is greater than the upper key, then a DOMException of type DataError must be thrown."> + <link rel=author href="mailto:chrcharles67@gmail.com" title="Christophe CHARLES"> + + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="resources/support.js"></script> + + <script type="text/javascript"> + + // TypeError: bound requires more than 0 arguments + test( function() { + assert_throws_js(TypeError, function() { + IDBKeyRange.bound(); + }); + }, "IDBKeyRange.bound() - bound requires more than 0 arguments."); + + // Null parameters + test( function() { + assert_throws_dom("DataError", function() { + IDBKeyRange.bound(null, null); + }); + }, "IDBKeyRange.bound(null, null) - null parameters are incorrect."); + + // // Null parameter + test( function() { + assert_throws_dom("DataError", function() { + IDBKeyRange.bound(1, null); + }); + assert_throws_dom("DataError", function() { + IDBKeyRange.bound(null, 1); + }); + }, "IDBKeyRange.bound(1, null / null, 1) - null parameter is incorrect."); + + // bound incorrect + test( function() { + var lowerBad = Math.floor(Math.random()*31) + 5; + var upper = lowerBad - 1; + assert_throws_dom("DataError", function() { + IDBKeyRange.bound(lowerBad, upper); + }); + assert_throws_dom("DataError", function() { + IDBKeyRange.bound('b', 'a'); + }); + }, "IDBKeyRange.bound(lower, upper / lower > upper) - 'lower' is greater than 'upper'." + ); + + test( function() { + assert_throws_dom("DataError", function() { + IDBKeyRange.bound('a', 1); + }); + assert_throws_dom("DataError", function() { + IDBKeyRange.bound(new Date(), 1); + }); + assert_throws_dom("DataError", function() { + IDBKeyRange.bound([1, 2], 1); + }); + }, "IDBKeyRange.bound(DOMString/Date/Array, 1) - A DOMString, Date and Array are greater than a float."); + + + // ReferenceError: the variable is not defined + test( function() { + var goodVariable = 1; + assert_throws_js(ReferenceError, function() { + IDBKeyRange.bound(noExistingVariable, 1); + }); + assert_throws_js(ReferenceError, function() { + IDBKeyRange.bound(goodVariable, noExistingVariable); + }); + }, "IDBKeyRange.bound(noExistingVariable, 1 / goodVariable, noExistingVariable) - noExistingVariable is not defined."); + + // Valid type key error + test( function() { + assert_throws_dom("DataError", function() { + IDBKeyRange.bound(true, 1); + }); + }, "IDBKeyRange.bound(true, 1) - boolean is not a valid key type."); + + + </script> + </head> + + <body> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore-add-put-exception-order.html b/testing/web-platform/tests/IndexedDB/idbobjectstore-add-put-exception-order.html new file mode 100644 index 0000000000..bbcc120a7e --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore-add-put-exception-order.html @@ -0,0 +1,74 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: IDBObjectStore add()/put() Exception Ordering</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbobjectstore-put"> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbobjectstore-add"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +['put', 'add'].forEach(method => { + indexeddb_test( + (t, db) => { + const store = db.createObjectStore('s'); + const store2 = db.createObjectStore('s2'); + + db.deleteObjectStore('s2'); + + setTimeout(t.step_func(() => { + assert_throws_dom( + 'InvalidStateError', () => { store2[method]('key', 'value'); }, + '"has been deleted" check (InvalidStateError) should precede ' + + '"not active" check (TransactionInactiveError)'); + t.done(); + }), 0); + }, + (t, db) => {}, + `IDBObjectStore.${method} exception order: ` + + 'InvalidStateError vs. TransactionInactiveError' + ); + + indexeddb_test( + (t, db) => { + const store = db.createObjectStore('s'); + }, + (t, db) => { + const tx = db.transaction('s', 'readonly', {durability: 'relaxed'}); + const store = tx.objectStore('s'); + + setTimeout(t.step_func(() => { + assert_throws_dom( + 'TransactionInactiveError', () => { store[method]('key', 'value'); }, + '"not active" check (TransactionInactiveError) should precede ' + + '"read only" check (ReadOnlyError)'); + t.done(); + }), 0); + }, + + `IDBObjectStore.${method} exception order: ` + + 'TransactionInactiveError vs. ReadOnlyError' + ); + + indexeddb_test( + (t, db) => { + const store = db.createObjectStore('s'); + }, + (t, db) => { + const tx = db.transaction('s', 'readonly', {durability: 'relaxed'}); + const store = tx.objectStore('s'); + + assert_throws_dom( + 'ReadOnlyError', () => { store[method]({}, 'value'); }, + '"read only" check (ReadOnlyError) should precede ' + + 'key/data check (DataError)'); + + t.done(); + }, + + `IDBObjectStore.${method} exception order: ` + + 'ReadOnlyError vs. DataError' + ); +}); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore-clear-exception-order.html b/testing/web-platform/tests/IndexedDB/idbobjectstore-clear-exception-order.html new file mode 100644 index 0000000000..206d21300a --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore-clear-exception-order.html @@ -0,0 +1,51 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: IDBObjectStore clear() Exception Ordering</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbobjectstore-clear"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('s'); + const store2 = db.createObjectStore('s2'); + + db.deleteObjectStore('s2'); + + setTimeout(t.step_func(() => { + assert_throws_dom( + 'InvalidStateError', () => { store2.clear(); }, + '"has been deleted" check (InvalidStateError) should precede ' + + '"not active" check (TransactionInactiveError)'); + t.done(); + }), 0); + }, + (t, db) => {}, + 'IDBObjectStore.clear exception order: ' + + 'InvalidStateError vs. TransactionInactiveError' +); + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('s'); + }, + (t, db) => { + const tx = db.transaction('s', 'readonly', {durability: 'relaxed'}); + const store = tx.objectStore('s'); + + setTimeout(t.step_func(() => { + assert_throws_dom( + 'TransactionInactiveError', () => { store.clear(); }, + '"not active" check (TransactionInactiveError) should precede ' + + '"read only" check (ReadOnlyError)'); + t.done(); + }), 0); + }, + + 'IDBObjectStore.clear exception order: ' + + 'TransactionInactiveError vs. ReadOnlyError' +); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore-cross-realm-methods.html b/testing/web-platform/tests/IndexedDB/idbobjectstore-cross-realm-methods.html new file mode 100644 index 0000000000..edb4b9b373 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore-cross-realm-methods.html @@ -0,0 +1,154 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Cross-realm IDBObjectStore methods from detached iframe work as expected</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<body> +<script> +"use strict"; +const KEY_EXISTING_LOWER = 1000; +const KEY_EXISTING_UPPER = 1001; +const KEY_EXISTING_RANGE = IDBKeyRange.bound(KEY_EXISTING_LOWER, KEY_EXISTING_UPPER); +const KEY_NEWLY_ADDED = 1002; + +const VALUE_EXISTING_LOWER = "VALUE_EXISTING_LOWER"; +const VALUE_EXISTING_UPPER = "VALUE_EXISTING_UPPER"; +const VALUE_NEWLY_ADDED = "VALUE_NEWLY_ADDED"; + +const testCases = [ + { + methodName: "put", + arguments: [KEY_NEWLY_ADDED, KEY_EXISTING_LOWER], + validateResult: (t, e) => { + assert_equals(e.target.result, KEY_EXISTING_LOWER); + const rq = e.target.source.getAll(); + rq.onsuccess = t.step_func_done(e => { + assert_array_equals(e.target.result, [KEY_NEWLY_ADDED, VALUE_EXISTING_UPPER]); + }); + }, + }, + { + methodName: "add", + arguments: [VALUE_NEWLY_ADDED, KEY_NEWLY_ADDED], + validateResult: (t, e) => { + assert_equals(e.target.result, KEY_NEWLY_ADDED); + const rq = e.target.source.getAll(); + rq.onsuccess = t.step_func_done(e => { + assert_array_equals(e.target.result, [VALUE_EXISTING_LOWER, VALUE_EXISTING_UPPER, VALUE_NEWLY_ADDED]); + }); + }, + }, + { + methodName: "delete", + arguments: [KEY_EXISTING_LOWER], + validateResult: (t, e) => { + assert_equals(e.target.result, undefined); + const rq = e.target.source.getAllKeys(); + rq.onsuccess = t.step_func_done(e => { + assert_array_equals(e.target.result, [KEY_EXISTING_UPPER]); + }); + }, + }, + { + methodName: "clear", + arguments: [], + validateResult: (t, e) => { + assert_equals(e.target.result, undefined); + const rq = e.target.source.count(); + rq.onsuccess = t.step_func_done(e => { + assert_equals(e.target.result, 0); + }); + }, + }, + { + methodName: "get", + arguments: [KEY_EXISTING_UPPER], + validateResult: (t, e) => { + assert_equals(e.target.result, VALUE_EXISTING_UPPER); + t.done(); + }, + }, + { + methodName: "getKey", + arguments: [KEY_EXISTING_LOWER], + validateResult: (t, e) => { + assert_equals(e.target.result, KEY_EXISTING_LOWER); + t.done(); + }, + }, + { + methodName: "getAll", + arguments: [KEY_EXISTING_RANGE], + validateResult: (t, e) => { + assert_array_equals(e.target.result, [VALUE_EXISTING_LOWER, VALUE_EXISTING_UPPER]); + t.done(); + }, + }, + { + methodName: "getAllKeys", + arguments: [KEY_EXISTING_RANGE], + validateResult: (t, e) => { + assert_array_equals(e.target.result, [KEY_EXISTING_LOWER, KEY_EXISTING_UPPER]); + t.done(); + }, + }, + { + methodName: "count", + arguments: [], + validateResult: (t, e) => { + assert_equals(e.target.result, 2); + t.done(); + }, + }, + { + methodName: "openCursor", + arguments: [], + validateResult: (t, e) => { + const cursor = e.target.result; + assert_true(cursor instanceof IDBCursor); + assert_equals(cursor.value, VALUE_EXISTING_LOWER); + t.done(); + }, + }, + { + methodName: "openKeyCursor", + arguments: [], + validateResult: (t, e) => { + const cursor = e.target.result; + assert_true(cursor instanceof IDBCursor); + assert_equals(cursor.key, KEY_EXISTING_LOWER); + t.done(); + }, + }, +]; + +for (const testCase of testCases) { + async_test(t => { + const iframe = document.createElement("iframe"); + iframe.onload = t.step_func(() => { + const method = iframe.contentWindow.IDBObjectStore.prototype[testCase.methodName]; + iframe.remove(); + + let db; + const open_rq = createdb(t); + open_rq.onupgradeneeded = t.step_func(e => { + db = e.target.result; + const objectStore = db.createObjectStore("store"); + objectStore.add(VALUE_EXISTING_LOWER, KEY_EXISTING_LOWER); + objectStore.add(VALUE_EXISTING_UPPER, KEY_EXISTING_UPPER); + }); + + open_rq.onsuccess = t.step_func(() => { + const objectStore = db.transaction("store", "readwrite", {durability: 'relaxed'}).objectStore("store"); + const rq = method.call(objectStore, ...testCase.arguments); + rq.onsuccess = t.step_func(e => { + testCase.validateResult(t, e); + }); + }); + }); + document.body.append(iframe); + }, `Cross-realm IDBObjectStore::${testCase.methodName}() method from detached <iframe> works as expected`); +} +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore-delete-exception-order.html b/testing/web-platform/tests/IndexedDB/idbobjectstore-delete-exception-order.html new file mode 100644 index 0000000000..aa6ae1c285 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore-delete-exception-order.html @@ -0,0 +1,69 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: IDBObjectStore delete() Exception Ordering</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbobjectstore-delete"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('s'); + const store2 = db.createObjectStore('s2'); + + db.deleteObjectStore('s2'); + + setTimeout(t.step_func(() => { + assert_throws_dom( + 'InvalidStateError', () => { store2.delete('key'); }, + '"has been deleted" check (InvalidStateError) should precede ' + + '"not active" check (TransactionInactiveError)'); + t.done(); + }), 0); + }, + (t, db) => {}, + 'IDBObjectStore.delete exception order: ' + + 'InvalidStateError vs. TransactionInactiveError' +); + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('s'); + }, + (t, db) => { + const tx = db.transaction('s', 'readonly', {durability: 'relaxed'}); + const store = tx.objectStore('s'); + + setTimeout(t.step_func(() => { + assert_throws_dom( + 'TransactionInactiveError', () => { store.delete('key'); }, + '"not active" check (TransactionInactiveError) should precede ' + + '"read only" check (ReadOnlyError)'); + t.done(); + }), 0); + }, + 'IDBObjectStore.delete exception order: ' + + 'TransactionInactiveError vs. ReadOnlyError' +); + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('s'); + }, + (t, db) => { + const tx = db.transaction('s', 'readonly', {durability: 'relaxed'}); + const store = tx.objectStore('s'); + + assert_throws_dom( + 'ReadOnlyError', () => { store.delete({}); }, + '"read only" check (ReadOnlyError) should precede ' + + 'key/data check (DataError)'); + + t.done(); + }, + 'IDBObjectStore.delete exception order: ' + + 'ReadOnlyError vs. DataError' +); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore-deleteIndex-exception-order.html b/testing/web-platform/tests/IndexedDB/idbobjectstore-deleteIndex-exception-order.html new file mode 100644 index 0000000000..48bbe40c4d --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore-deleteIndex-exception-order.html @@ -0,0 +1,68 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: IDBObjectStore deleteIndex() Exception Ordering</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbobjectstore-deleteindex"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('s'); + store.createIndex('i', 'keyPath'); + }, + (t, db) => { + const tx = db.transaction('s', 'readonly', {durability: 'relaxed'}); + const store = tx.objectStore('s'); + + setTimeout(t.step_func(() => { + assert_throws_dom( + 'InvalidStateError', () => { store.deleteIndex('i'); }, + '"running an upgrade transaction" check (InvalidStateError) ' + + 'should precede "not active" check (TransactionInactiveError)'); + t.done(); + }), 0); + }, + 'IDBObjectStore.deleteIndex exception order: ' + + 'InvalidStateError #1 vs. TransactionInactiveError' +); + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('s'); + const index = store.createIndex('i', 'keyPath'); + + db.deleteObjectStore('s'); + + setTimeout(t.step_func(() => { + assert_throws_dom( + 'InvalidStateError', () => { store.deleteIndex('i'); }, + '"deleted" check (InvalidStateError) ' + + 'should precede "not active" check (TransactionInactiveError)'); + t.done(); + }), 0); + }, + (t, db) => {}, + 'IDBObjectStore.deleteIndex exception order: ' + + 'InvalidStateError #2 vs. TransactionInactiveError' +); + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('s'); + + setTimeout(t.step_func(() => { + assert_throws_dom( + 'TransactionInactiveError', () => { store.deleteIndex('nope'); }, + '"not active" check (TransactionInactiveError) should precede ' + + '"name in store" check (NotFoundError)'); + t.done(); + }), 0); + }, + (t, db) => {}, + 'IDBObjectStore.deleteIndex exception order: ' + + 'TransactionInactiveError vs. NotFoundError' +); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore-getAll-enforcerange.html b/testing/web-platform/tests/IndexedDB/idbobjectstore-getAll-enforcerange.html new file mode 100644 index 0000000000..c408aff715 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore-getAll-enforcerange.html @@ -0,0 +1,25 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: IDBObjectStore getAll() uses [EnforceRange]</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#object-store-interface"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('store'); + }, + (t, db) => { + const tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + [NaN, Infinity, -Infinity, -1, -Number.MAX_SAFE_INTEGER].forEach(count => { + assert_throws_js(TypeError, () => { store.getAll(null, count); }, + `getAll with count ${count} count should throw TypeError`); + }); + t.done(); + }, + `IDBObjectStore.getAll() uses [EnforceRange]` +); +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore-getAllKeys-enforcerange.html b/testing/web-platform/tests/IndexedDB/idbobjectstore-getAllKeys-enforcerange.html new file mode 100644 index 0000000000..f8e77aecba --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore-getAllKeys-enforcerange.html @@ -0,0 +1,25 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: IDBIObjectStore getAllKeys() uses [EnforceRange]</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#object-store-interface"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('store'); + }, + (t, db) => { + const tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + [NaN, Infinity, -Infinity, -1, -Number.MAX_SAFE_INTEGER].forEach(count => { + assert_throws_js(TypeError, () => { store.getAllKeys(null, count); }, + `getAllKeys with count ${count} count should throw TypeError`); + }); + t.done(); + }, + `IDBObjectStore.getAllKeys() uses [EnforceRange]` +); +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore-index-finished.html b/testing/web-platform/tests/IndexedDB/idbobjectstore-index-finished.html new file mode 100644 index 0000000000..a45c66ed70 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore-index-finished.html @@ -0,0 +1,26 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: IDBObjectStore index() when transaction is finished</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbobjectstore-index"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('store'); + store.createIndex('index', 'key_path'); + }, + (t, db) => { + const tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + tx.abort(); + assert_throws_dom('InvalidStateError', () => store.index('index'), + 'index() should throw if transaction is finished'); + t.done(); + }, + 'IDBObjectStore index() behavior when transaction is finished' +); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore-query-exception-order.html b/testing/web-platform/tests/IndexedDB/idbobjectstore-query-exception-order.html new file mode 100644 index 0000000000..53ae04b088 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore-query-exception-order.html @@ -0,0 +1,65 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: IDBObjectStore query method Ordering</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbobjectstore-get"> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbobjectstore-getall"> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbobjectstore-getallkeys"> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbobjectstore-count"> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbobjectstore-opencursor"> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbobjectstore-openkeycursor"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +['get', + 'getAll', + 'getAllKeys', + 'count', + 'openCursor', + 'openKeyCursor' +].forEach(method => { + + indexeddb_test( + (t, db) => { + const store = db.createObjectStore('s'); + const store2 = db.createObjectStore('s2'); + + db.deleteObjectStore('s2'); + + setTimeout(t.step_func(() => { + assert_throws_dom( + 'InvalidStateError', () => { store2[method]('key'); }, + '"has been deleted" check (InvalidStateError) should precede ' + + '"not active" check (TransactionInactiveError)'); + + t.done(); + }), 0); + }, + (t, db) => {}, + `IDBObjectStore.${method} exception order: ` + + 'InvalidStateError vs. TransactionInactiveError' + ); + + indexeddb_test( + (t, db) => { + const store = db.createObjectStore('s'); + }, + (t, db) => { + const tx = db.transaction('s', 'readonly', {durability: 'relaxed'}); + const store = tx.objectStore('s'); + + setTimeout(t.step_func(() => { + assert_throws_dom( + 'TransactionInactiveError', () => { store[method]({}); }, + '"not active" check (TransactionInactiveError) should precede ' + + 'query check (DataError)'); + t.done(); + }), 0); + }, + `IDBObjectStore.${method} exception order: ` + + 'TransactionInactiveError vs. DataError' + ); +}); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore-rename-abort.html b/testing/web-platform/tests/IndexedDB/idbobjectstore-rename-abort.html new file mode 100644 index 0000000000..b4bfc914ed --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore-rename-abort.html @@ -0,0 +1,120 @@ +<!doctype html> +<meta charset="utf8"> +<title>IndexedDB: object store renaming support in aborted transactions</title> +<link rel="help" + href="https://w3c.github.io/IndexedDB/#dom-idbobjectstore-name"> +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script> +'use strict'; + +promise_test(testCase => { + const dbName = databaseName(testCase); + let bookStore = null, bookStore2 = null; + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + bookStore = transaction.objectStore('books'); + bookStore.name = 'renamed_books'; + + transaction.abort(); + + assert_equals( + bookStore.name, 'books', + 'IDBObjectStore.name should not reflect the rename any more ' + + 'immediately after transaction.abort() returns'); + assert_array_equals( + database.objectStoreNames, ['books'], + 'IDBDatabase.objectStoreNames should not reflect the rename ' + + 'any more immediately after transaction.abort() returns'); + assert_array_equals( + transaction.objectStoreNames, ['books'], + 'IDBTransaction.objectStoreNames should not reflect the ' + + 'rename any more immediately after transaction.abort() returns'); + })).then(event => { + assert_equals(bookStore.name, 'books', + 'IDBObjectStore.name should not reflect the rename any more ' + + 'after the versionchange transaction is aborted'); + const request = indexedDB.open(dbName, 1); + return promiseForRequest(testCase, request); + }).then(database => { + assert_array_equals( + database.objectStoreNames, ['books'], + 'IDBDatabase.objectStoreNames should not reflect the rename ' + + 'after the versionchange transaction is aborted'); + + const transaction = database.transaction('books', 'readonly', {durability: 'relaxed'}); + bookStore2 = transaction.objectStore('books'); + return checkStoreContents( + testCase, bookStore2, + 'Aborting an object store rename transaction should not change ' + + "the store's records").then(() => database.close()); + }).then(() => { + assert_equals( + bookStore.name, 'books', + 'IDBObjectStore used in aborted rename transaction should not ' + + 'reflect the rename after the transaction is aborted'); + assert_equals( + bookStore2.name, 'books', + 'IDBObjectStore obtained after an aborted rename transaction ' + + 'should not reflect the rename'); + }); +}, 'IndexedDB object store rename in aborted transaction'); + +promise_test(testCase => { + const dbName = databaseName(testCase); + let notBookStore = null; + return createDatabase(testCase, (database, transaction) => { + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + notBookStore = createNotBooksStore(testCase, database); + notBookStore.name = 'not_books_renamed'; + notBookStore.name = 'not_books_renamed_again'; + + transaction.abort(); + + assert_equals( + notBookStore.name, 'not_books_renamed_again', + 'IDBObjectStore.name should reflect the last rename ' + + 'immediately after transaction.abort() returns'); + assert_array_equals( + database.objectStoreNames, [], + 'IDBDatabase.objectStoreNames should not reflect the creation ' + + 'or the rename any more immediately after transaction.abort() ' + + 'returns'); + assert_array_equals( + transaction.objectStoreNames, [], + 'IDBTransaction.objectStoreNames should not reflect the ' + + 'creation or the rename any more immediately after ' + + 'transaction.abort() returns'); + assert_array_equals(notBookStore.indexNames, [], + 'IDBObjectStore.indexNames for the newly created store ' + + 'should be empty immediately after transaction.abort() ' + + 'returns'); + })).then(event => { + assert_equals( + notBookStore.name, 'not_books_renamed_again', + 'IDBObjectStore.name should reflect the last rename after the ' + + 'versionchange transaction is aborted'); + assert_array_equals(notBookStore.indexNames, [], + 'IDBObjectStore.indexNames for the newly created store ' + + 'should be empty after the versionchange transaction is aborted ' + + 'returns'); + const request = indexedDB.open(dbName, 1); + return promiseForRequest(testCase, request); + }).then(database => { + assert_array_equals( + database.objectStoreNames, [], + 'IDBDatabase.objectStoreNames should not reflect the creation or ' + + 'the rename after the versionchange transaction is aborted'); + + database.close(); + }); +}, 'IndexedDB object store creation and rename in an aborted transaction'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore-rename-errors.html b/testing/web-platform/tests/IndexedDB/idbobjectstore-rename-errors.html new file mode 100644 index 0000000000..de72c5cf48 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore-rename-errors.html @@ -0,0 +1,121 @@ +<!doctype html> +<meta charset="utf8"> +<title>IndexedDB: object store renaming error handling</title> +<link rel="help" + href="https://w3c.github.io/IndexedDB/#dom-idbobjectstore-name"> +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script> +'use strict'; + +promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + const store = transaction.objectStore('books'); + database.deleteObjectStore('books'); + assert_throws_dom('InvalidStateError', () => store.name = 'renamed_books'); + })).then(database => { + database.close(); + }); +}, 'IndexedDB deleted object store rename throws'); + +promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + const transaction = database.transaction('books', 'readonly', {durability: 'relaxed'}); + const store = transaction.objectStore('books'); + assert_throws_dom('InvalidStateError', () => store.name = 'renamed_books'); + database.close(); + }); +}, 'IndexedDB object store rename throws in a readonly transaction'); + +promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + const transaction = database.transaction('books', 'readwrite', {durability: 'relaxed'}); + const store = transaction.objectStore('books'); + + assert_throws_dom('InvalidStateError', () => store.name = 'renamed_books'); + database.close(); + }); +}, 'IndexedDB object store rename throws in a readwrite transaction'); + +promise_test(testCase => { + let bookStore = null; + return createDatabase(testCase, (database, transaction) => { + bookStore = createBooksStore(testCase, database); + }).then(database => { + assert_throws_dom('TransactionInactiveError', + () => { bookStore.name = 'renamed_books'; }); + database.close(); + }); +}, 'IndexedDB object store rename throws in an inactive transaction'); + +promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + createNotBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + const store = transaction.objectStore('books'); + assert_throws_dom('ConstraintError', () => store.name = 'not_books'); + assert_array_equals( + database.objectStoreNames, ['books', 'not_books'], + 'A store rename that throws an exception should not change the ' + + "store's IDBDatabase.objectStoreNames"); + })).then(database => { + assert_array_equals( + database.objectStoreNames, ['books', 'not_books'], + 'Committing a transaction with a failed store rename attempt ' + + "should not change the store's IDBDatabase.objectStoreNames"); + const transaction = database.transaction('books', 'readonly', {durability: 'relaxed'}); + const store = transaction.objectStore('books'); + return checkStoreContents( + testCase, store, + 'Committing a transaction with a failed rename attempt should ' + + "not change the store's contents").then(() => database.close()); + }); +}, 'IndexedDB object store rename to the name of another store throws'); + +promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + const store = transaction.objectStore('books'); + var exception = { name: 'Custom stringifying error' } + assert_throws_exactly( + exception, + () => { + store.name = { + toString: () => { throw exception; } + }; + }, 'IDBObjectStore rename should re-raise toString() exception'); + assert_array_equals( + database.objectStoreNames, ['books'], + 'A store rename that throws an exception should not change the ' + + "store's IDBDatabase.objectStoreNames"); + })).then(database => { + assert_array_equals( + database.objectStoreNames, ['books'], + 'Committing a transaction with a failed store rename attempt ' + + "should not change the store's IDBDatabase.objectStoreNames"); + const transaction = database.transaction('books', 'readonly', {durability: 'relaxed'}); + const store = transaction.objectStore('books'); + return checkStoreContents( + testCase, store, + 'Committing a transaction with a failed rename attempt should ' + + "not change the store's contents").then(() => database.close()); + }); +}, 'IndexedDB object store rename handles exceptions when stringifying names'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore-rename-store.html b/testing/web-platform/tests/IndexedDB/idbobjectstore-rename-store.html new file mode 100644 index 0000000000..649f9faef3 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore-rename-store.html @@ -0,0 +1,369 @@ +<!doctype html> +<meta charset="utf8"> +<meta name="timeout" content="long"> +<title>IndexedDB: object store renaming support</title> +<link rel="help" + href="https://w3c.github.io/IndexedDB/#dom-idbobjectstore-name"> +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script> +'use strict'; + +// Renames the 'books' store to 'renamed_books'. +// +// Returns a promise that resolves to an IndexedDB database. The caller must +// close the database. +const renameBooksStore = (testCase) => { + return migrateDatabase(testCase, 2, (database, transaction) => { + const store = transaction.objectStore('books'); + store.name = 'renamed_books'; + }); +}; + +promise_test(testCase => { + let bookStore = null, bookStore2 = null; + let renamedBookStore = null, renamedBookStore2 = null; + return createDatabase(testCase, (database, transaction) => { + bookStore = createBooksStore(testCase, database); + }).then(database => { + assert_array_equals( + database.objectStoreNames, ['books'], + 'Test setup should have created a "books" object store'); + const transaction = database.transaction('books', 'readonly', {durability: 'relaxed'}); + bookStore2 = transaction.objectStore('books'); + return checkStoreContents( + testCase, bookStore2, + 'The store should have the expected contents before any renaming'). + then(() => database.close()); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + renamedBookStore = transaction.objectStore('books'); + renamedBookStore.name = 'renamed_books'; + + assert_equals( + renamedBookStore.name, 'renamed_books', + 'IDBObjectStore name should change immediately after a rename'); + assert_array_equals( + database.objectStoreNames, ['renamed_books'], + 'IDBDatabase.objectStoreNames should immediately reflect the ' + + 'rename'); + assert_array_equals( + transaction.objectStoreNames, ['renamed_books'], + 'IDBTransaction.objectStoreNames should immediately reflect the ' + + 'rename'); + assert_equals( + transaction.objectStore('renamed_books'), renamedBookStore, + 'IDBTransaction.objectStore should return the renamed object ' + + 'store when queried using the new name immediately after the ' + + 'rename'); + assert_throws_dom( + 'NotFoundError', () => transaction.objectStore('books'), + 'IDBTransaction.objectStore should throw when queried using the ' + + "renamed object store's old name immediately after the rename"); + })).then(database => { + assert_array_equals( + database.objectStoreNames, ['renamed_books'], + 'IDBDatabase.objectStoreNames should still reflect the rename ' + + 'after the versionchange transaction commits'); + const transaction = database.transaction('renamed_books', 'readonly', {durability: 'relaxed'}); + renamedBookStore2 = transaction.objectStore('renamed_books'); + return checkStoreContents( + testCase, renamedBookStore2, + 'Renaming an object store should not change its records').then( + () => database.close()); + }).then(() => { + assert_equals( + bookStore.name, 'books', + 'IDBObjectStore obtained before the rename transaction should ' + + 'not reflect the rename'); + assert_equals( + bookStore2.name, 'books', + 'IDBObjectStore obtained before the rename transaction should ' + + 'not reflect the rename'); + assert_equals( + renamedBookStore.name, 'renamed_books', + 'IDBObjectStore used in the rename transaction should keep ' + + 'reflecting the new name after the transaction is committed'); + assert_equals( + renamedBookStore2.name, 'renamed_books', + 'IDBObjectStore obtained after the rename transaction should ' + + 'reflect the new name'); + }); +}, 'IndexedDB object store rename in new transaction'); + +promise_test(testCase => { + let renamedBookStore = null, renamedBookStore2 = null; + return createDatabase(testCase, (database, transaction) => { + renamedBookStore = createBooksStore(testCase, database); + renamedBookStore.name = 'renamed_books'; + + assert_equals( + renamedBookStore.name, 'renamed_books', + 'IDBObjectStore name should change immediately after a rename'); + assert_array_equals( + database.objectStoreNames, ['renamed_books'], + 'IDBDatabase.objectStoreNames should immediately reflect the ' + + 'rename'); + assert_array_equals( + transaction.objectStoreNames, ['renamed_books'], + 'IDBTransaction.objectStoreNames should immediately reflect the ' + + 'rename'); + assert_equals( + transaction.objectStore('renamed_books'), renamedBookStore, + 'IDBTransaction.objectStore should return the renamed object ' + + 'store when queried using the new name immediately after the ' + + 'rename'); + assert_throws_dom( + 'NotFoundError', () => transaction.objectStore('books'), + 'IDBTransaction.objectStore should throw when queried using the ' + + "renamed object store's old name immediately after the rename"); + }).then(database => { + assert_array_equals( + database.objectStoreNames, ['renamed_books'], + 'IDBDatabase.objectStoreNames should still reflect the rename ' + + 'after the versionchange transaction commits'); + const transaction = database.transaction('renamed_books', 'readonly', {durability: 'relaxed'}); + renamedBookStore2 = transaction.objectStore('renamed_books'); + return checkStoreContents( + testCase, renamedBookStore2, + 'Renaming an object store should not change its records').then( + () => database.close()); + }).then(() => { + assert_equals( + renamedBookStore.name, 'renamed_books', + 'IDBObjectStore used in the rename transaction should keep ' + + 'reflecting the new name after the transaction is committed'); + assert_equals( + renamedBookStore2.name, 'renamed_books', + 'IDBObjectStore obtained after the rename transaction should ' + + 'reflect the new name'); + }); +}, 'IndexedDB object store rename in the transaction where it is created'); + +promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + const transaction = database.transaction('books', 'readonly', {durability: 'relaxed'}); + const store = transaction.objectStore('books'); + return checkStoreIndexes( + testCase, store, + 'The object store index should have the expected contens before ' + + 'any renaming').then( + () => database.close()); + }).then(() => renameBooksStore(testCase) + ).then(database => { + const transaction = database.transaction('renamed_books', 'readonly', {durability: 'relaxed'}); + const store = transaction.objectStore('renamed_books'); + return checkStoreIndexes( + testCase, store, + 'Renaming an object store should not change its indexes').then( + () => database.close()); + }); +}, 'IndexedDB object store rename covers index'); + +promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + const transaction = database.transaction('books', 'readwrite', {durability: 'relaxed'}); + const store = transaction.objectStore('books'); + return checkStoreGenerator( + testCase, store, 345679, + 'The object store key generator should have the expected state ' + + 'before any renaming').then(() => database.close()); + }).then(() => renameBooksStore(testCase) + ).then(database => { + const transaction = database.transaction('renamed_books', 'readwrite', {durability: 'relaxed'}); + const store = transaction.objectStore('renamed_books'); + return checkStoreGenerator( + testCase, store, 345680, + 'Renaming an object store should not change the state of its key ' + + 'generator').then(() => database.close()); + }); +}, 'IndexedDB object store rename covers key generator'); + +promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + const store = transaction.objectStore('books'); + store.name = 'books'; + assert_array_equals( + database.objectStoreNames, ['books'], + 'Renaming a store to the same name should not change ' + + "the store's IDBDatabase.objectStoreNames"); + })).then(database => { + assert_array_equals( + database.objectStoreNames, ['books'], + 'Committing a transaction that renames a store to the same name ' + + "should not change the store's IDBDatabase.objectStoreNames"); + const transaction = database.transaction('books', 'readonly', {durability: 'relaxed'}); + const store = transaction.objectStore('books'); + return checkStoreContents( + testCase, store, + 'Committing a transaction that renames a store to the same name ' + + "should not change the store's contents").then( + () => database.close()); + }); +}, 'IndexedDB object store rename to the same name succeeds'); + +promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + createNotBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + const store = transaction.objectStore('books'); + database.deleteObjectStore('not_books'); + store.name = 'not_books'; + assert_array_equals( + database.objectStoreNames, ['not_books'], + 'IDBDatabase.objectStoreNames should immediately reflect the ' + + 'rename'); + })).then(database => { + assert_array_equals( + database.objectStoreNames, ['not_books'], + 'IDBDatabase.objectStoreNames should still reflect the rename ' + + 'after the versionchange transaction commits'); + const transaction = database.transaction('not_books', 'readonly', {durability: 'relaxed'}); + const store = transaction.objectStore('not_books'); + return checkStoreContents( + testCase, store, + 'Renaming an object store should not change its records').then( + () => database.close()); + }); +}, 'IndexedDB object store rename to the name of a deleted store succeeds'); + +promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + createNotBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + const bookStore = transaction.objectStore('books'); + const notBookStore = transaction.objectStore('not_books'); + + transaction.objectStore('books').name = 'tmp'; + transaction.objectStore('not_books').name = 'books'; + transaction.objectStore('tmp').name = 'not_books'; + + assert_array_equals( + database.objectStoreNames, ['books', 'not_books'], + 'IDBDatabase.objectStoreNames should immediately reflect the swap'); + + assert_equals( + transaction.objectStore('books'), notBookStore, + 'IDBTransaction.objectStore should return the original "books" ' + + 'store when queried with "not_books" after the swap'); + assert_equals( + transaction.objectStore('not_books'), bookStore, + 'IDBTransaction.objectStore should return the original ' + + '"not_books" store when queried with "books" after the swap'); + })).then(database => { + assert_array_equals( + database.objectStoreNames, ['books', 'not_books'], + 'IDBDatabase.objectStoreNames should still reflect the swap ' + + 'after the versionchange transaction commits'); + const transaction = database.transaction('not_books', 'readonly', {durability: 'relaxed'}); + const store = transaction.objectStore('not_books'); + assert_array_equals( + store.indexNames, ['by_author', 'by_title'], + '"not_books" index names should still reflect the swap after the ' + + 'versionchange transaction commits'); + return checkStoreContents( + testCase, store, + 'Swapping two object stores should not change their records').then( + () => database.close()); + }); +}, 'IndexedDB object store swapping via renames succeeds'); + +promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + const store = transaction.objectStore('books'); + + store.name = 42; + assert_equals(store.name, '42', + 'IDBObjectStore name should change immediately after a ' + + 'rename to a number'); + assert_array_equals( + database.objectStoreNames, ['42'], + 'IDBDatabase.objectStoreNames should immediately reflect the ' + + 'stringifying rename'); + + store.name = true; + assert_equals(store.name, 'true', + 'IDBObjectStore name should change immediately after a ' + + 'rename to a boolean'); + + store.name = {}; + assert_equals(store.name, '[object Object]', + 'IDBObjectStore name should change immediately after a ' + + 'rename to an object'); + + store.name = () => null; + assert_equals(store.name, '() => null', + 'IDBObjectStore name should change immediately after a ' + + 'rename to a function'); + + store.name = undefined; + assert_equals(store.name, 'undefined', + 'IDBObjectStore name should change immediately after a ' + + 'rename to undefined'); + })).then(database => { + assert_array_equals( + database.objectStoreNames, ['undefined'], + 'IDBDatabase.objectStoreNames should reflect the last rename ' + + 'after the versionchange transaction commits'); + const transaction = database.transaction('undefined', 'readonly', {durability: 'relaxed'}); + const store = transaction.objectStore('undefined'); + return checkStoreContents( + testCase, store, + 'Renaming an object store should not change its records').then( + () => database.close()); + }); +}, 'IndexedDB object store rename stringifies non-string names'); + +for (let escapedName of ['', '\\u0000', '\\uDC00\\uD800']) ((escapedName) => { + const name = JSON.parse('"' + escapedName + '"'); + promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + const store = transaction.objectStore('books'); + + store.name = name; + assert_equals(store.name, name, + 'IDBObjectStore name should change immediately after the ' + + 'rename'); + assert_array_equals( + database.objectStoreNames, [name], + 'IDBDatabase.objectStoreNames should immediately reflect the ' + + 'rename'); + })).then(database => { + assert_array_equals( + database.objectStoreNames, [name], + 'IDBDatabase.objectStoreNames should reflect the rename ' + + 'after the versionchange transaction commits'); + const transaction = database.transaction(name, 'readonly'); + const store = transaction.objectStore(name); + return checkStoreContents( + testCase, store, + 'Renaming an object store should not change its records').then( + () => database.close()); + }); + }, 'IndexedDB object store can be renamed to "' + escapedName + '"'); +})(escapedName); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore-request-source.html b/testing/web-platform/tests/IndexedDB/idbobjectstore-request-source.html new file mode 100644 index 0000000000..c5947564ea --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore-request-source.html @@ -0,0 +1,39 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: The source of requests made against object stores</title> +<meta name="help" href="https://w3c.github.io/IndexedDB/#dom-idbrequest-source"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +[ + store => store.put(0), + store => store.add(0), + store => store.delete(0), + store => store.clear(), + + store => store.get(0), + store => store.getKey(0), + store => store.getAll(), + store => store.getAllKeys(), + store => store.count(), + + store => store.openCursor(), + store => store.openKeyCursor() +].forEach(func => indexeddb_test( + (t, db) => { + db.createObjectStore('store', {autoIncrement: true}); + }, + (t, db) => { + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + + assert_equals(func(store).source, store, + `${func}.source should be the object store itself`); + t.done(); + }, + `The source of the request from ${func} is the object store itself` +)); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore-transaction-SameObject.html b/testing/web-platform/tests/IndexedDB/idbobjectstore-transaction-SameObject.html new file mode 100644 index 0000000000..2b5a027c45 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore-transaction-SameObject.html @@ -0,0 +1,26 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: Verify [SameObject] behavior of IDBObjectStore's transaction attribute</title> +<meta name="help" href="https://w3c.github.io/IndexedDB/#dom-idbobjectstore-transaction"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('store'); + assert_equals(store.transaction, store.transaction, + 'Attribute should yield the same object each time'); + + }, + (t, db) => { + const tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + assert_equals(store.transaction, store.transaction, + 'Attribute should yield the same object each time'); + t.done(); + }, + 'IDBObjectStore.transaction [SameObject]' +); +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_add.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_add.htm new file mode 100644 index 0000000000..33ea094388 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_add.htm @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.add() - add with an inline key </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + record = { key: 1, property: "data" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store", { keyPath: "key" }); + + objStore.add(record); + }; + + open_rq.onsuccess = function(e) { + var rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .get(record.key); + + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result.property, record.property); + assert_equals(e.target.result.key, record.key); + t.done(); + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_add10.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_add10.htm new file mode 100644 index 0000000000..d84004250d --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_add10.htm @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.add() - Attempt to call 'add' without an key parameter when the object store uses out-of-line keys </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + record = { property: "data" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var rq, + objStore = db.createObjectStore("store"); + + assert_throws_dom("DataError", + function() { rq = objStore.add(record); }); + + assert_equals(rq, undefined); + t.done(); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_add11.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_add11.htm new file mode 100644 index 0000000000..b1d9f71fd0 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_add11.htm @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.add() - Attempt to add a record where the record's key does not meet the constraints of a valid key </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + record = { key: { value: 1 }, property: "data" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var rq, + objStore = db.createObjectStore("store", { keyPath: "key" }); + + assert_throws_dom("DataError", + function() { rq = objStore.add(record); }); + + assert_equals(rq, undefined); + t.done(); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_add12.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_add12.htm new file mode 100644 index 0000000000..fc5a7bcebb --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_add12.htm @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.add() - Attempt to add a record where the record's in-line key is not defined </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + record = { property: "data" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var rq, + objStore = db.createObjectStore("store", { keyPath: "key" }); + + assert_throws_dom("DataError", + function() { rq = objStore.add(record); }); + + assert_equals(rq, undefined); + t.done(); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_add13.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_add13.htm new file mode 100644 index 0000000000..ec82989b01 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_add13.htm @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.add() - Attempt to add a record where the out of line key provided does not meet the constraints of a valid key </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + record = { property: "data" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var rq, + objStore = db.createObjectStore("store"); + + assert_throws_dom("DataError", + function() { rq = objStore.add(record, { value: 1 }); }); + + assert_equals(rq, undefined); + t.done(); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_add14.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_add14.htm new file mode 100644 index 0000000000..10bf8a18ce --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_add14.htm @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.add() - Add a record where a value being indexed does not meet the constraints of a valid key </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + record = { key: 1, indexedProperty: { property: "data" } }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var rq, + objStore = db.createObjectStore("store", { keyPath: "key" }); + + objStore.createIndex("index", "indexedProperty"); + + rq = objStore.add(record); + + assert_true(rq instanceof IDBRequest); + rq.onsuccess = function() { + t.done(); + } + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_add15.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_add15.htm new file mode 100644 index 0000000000..a6f897911b --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_add15.htm @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.add() - If the transaction this IDBObjectStore belongs to has its mode set to readonly, throw ReadOnlyError </title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBObjectStore-add-IDBRequest-any-value-any-key"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + db.createObjectStore("store", {keyPath:"pKey"}); + } + + open_rq.onsuccess = function (event) { + var txn = db.transaction("store", "readonly", {durability: 'relaxed'}); + var ostore = txn.objectStore("store"); + t.step(function(){ + assert_throws_dom("ReadOnlyError", function(){ + ostore.add({ pKey: "primaryKey_0"}); + }); + }); + t.done(); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_add16.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_add16.htm new file mode 100644 index 0000000000..453082340f --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_add16.htm @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.add() - If the object store has been deleted, the implementation must throw a DOMException of type InvalidStateError </title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBObjectStore-add-IDBRequest-any-value-any-key"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + ostore, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + ostore = db.createObjectStore("store", {keyPath:"pKey"}); + db.deleteObjectStore("store"); + assert_throws_dom("InvalidStateError", function(){ + ostore.add({ pKey: "primaryKey_0"}); + }); + t.done(); + } +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_add2.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_add2.htm new file mode 100644 index 0000000000..6f6e3eab27 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_add2.htm @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.add() - add with an out-of-line key </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + key = 1, + record = { property: "data" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store"); + + objStore.add(record, key); + }; + + open_rq.onsuccess = function(e) { + var rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .get(key); + + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result.property, record.property); + + t.done(); + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_add3.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_add3.htm new file mode 100644 index 0000000000..9209e7505d --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_add3.htm @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.add() - record with same key already exists </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + record = { key: 1, property: "data" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store", { keyPath: "key" }); + objStore.add(record); + + var rq = objStore.add(record); + rq.onsuccess = fail(t, "success on adding duplicate record") + + rq.onerror = t.step_func(function(e) { + assert_equals(e.target.error.name, "ConstraintError"); + assert_equals(rq.error.name, "ConstraintError"); + assert_equals(e.type, "error"); + + e.preventDefault(); + e.stopPropagation(); + }); + }; + + // Defer done, giving rq.onsuccess a chance to run + open_rq.onsuccess = function(e) { + t.done(); + } +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_add4.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_add4.htm new file mode 100644 index 0000000000..35d88fe3a2 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_add4.htm @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.add() - add where an index has unique:true specified </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + record = { key: 1, property: "data" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store", { autoIncrement: true }); + objStore.createIndex("i1", "property", { unique: true }); + objStore.add(record); + + var rq = objStore.add(record); + rq.onsuccess = fail(t, "success on adding duplicate indexed record") + + rq.onerror = t.step_func(function(e) { + assert_equals(rq.error.name, "ConstraintError"); + assert_equals(e.target.error.name, "ConstraintError"); + assert_equals(e.type, "error"); + + e.preventDefault(); + e.stopPropagation(); + }); + }; + + // Defer done, giving a spurious rq.onsuccess a chance to run + open_rq.onsuccess = function(e) { + t.done(); + } +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_add5.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_add5.htm new file mode 100644 index 0000000000..0cb124ff4b --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_add5.htm @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.add() - object store's key path is an object attribute </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + record = { test: { obj: { key: 1 } }, property: "data" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store", { keyPath: "test.obj.key" }); + objStore.add(record); + }; + + open_rq.onsuccess = function(e) { + var rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .get(record.test.obj.key); + + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result.property, record.property); + + t.done(); + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_add6.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_add6.htm new file mode 100644 index 0000000000..b6ff5a5a50 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_add6.htm @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.add() - autoIncrement and inline keys </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + record = { property: "data" }, + expected_keys = [ 1, 2, 3, 4 ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store", { keyPath: "key", autoIncrement: true }); + + objStore.add(record); + objStore.add(record); + objStore.add(record); + objStore.add(record); + }; + + open_rq.onsuccess = function(e) { + var actual_keys = [], + rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .openCursor(); + + rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + if (cursor) { + actual_keys.push(cursor.value.key); + cursor.continue(); + } + else { + assert_array_equals(actual_keys, expected_keys); + t.done(); + } + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_add7.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_add7.htm new file mode 100644 index 0000000000..cd63917c3f --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_add7.htm @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.add() - autoIncrement and out-of-line keys </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + record = { property: "data" }, + expected_keys = [ 1, 2, 3, 4 ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store", { autoIncrement: true }); + + objStore.add(record); + objStore.add(record); + objStore.add(record); + objStore.add(record); + }; + + open_rq.onsuccess = function(e) { + var actual_keys = [], + rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .openCursor(); + + rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + if (cursor) { + actual_keys.push(cursor.key); + cursor.continue(); + } + else { + assert_array_equals(actual_keys, expected_keys); + t.done(); + } + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_add8.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_add8.htm new file mode 100644 index 0000000000..c827adf6ef --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_add8.htm @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.add() - object store has autoIncrement:true and the key path is an object attribute </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + record = { property: "data" }, + expected_keys = [ 1, 2, 3, 4 ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store", { keyPath: "test.obj.key", autoIncrement: true }); + + objStore.add(record); + objStore.add(record); + objStore.add(record); + objStore.add(record); + }; + + open_rq.onsuccess = function(e) { + var actual_keys = [], + rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .openCursor(); + + rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + if (cursor) { + actual_keys.push(cursor.value.test.obj.key); + cursor.continue(); + } + else { + assert_array_equals(actual_keys, expected_keys); + t.done(); + } + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_add9.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_add9.htm new file mode 100644 index 0000000000..27061afd9c --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_add9.htm @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.add() - Attempt to add a record that does not meet the constraints of an object store's inline key requirements </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var t = async_test(), + record = { key: 1, property: "data" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + var rq, + db = e.target.result, + objStore = db.createObjectStore("store", { keyPath: "key" }); + + assert_throws_dom("DataError", + function() { rq = objStore.add(record, 1); }); + + assert_equals(rq, undefined); + t.done(); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_batchGetAll.tentative.any.js b/testing/web-platform/tests/IndexedDB/idbobjectstore_batchGetAll.tentative.any.js new file mode 100644 index 0000000000..3aeb855359 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_batchGetAll.tentative.any.js @@ -0,0 +1,143 @@ +// META: title=Batch Get All +// META: script=resources/support.js + +'use strict'; + +const alphabet = 'abcdefghijklmnopqrstuvwxyz'.split(''); + +function batchgetall_test(storeName, func, name) { + indexeddb_test((t, connection, tx) => { + var store; + switch (storeName) { + case 'generated': + store = connection.createObjectStore( + 'generated', {autoIncrement: true, keyPath: 'id'}); + alphabet.forEach(letter => { + store.put({ch: letter}); + }); + break; + case 'out-of-line': + store = connection.createObjectStore('out-of-line', null); + alphabet.forEach(letter => { + store.put(`value-${letter}`, letter); + }); + break; + case 'empty': + store = connection.createObjectStore('empty', null); + break; + default: + t.fail(`Unsupported storeName: ${storeName}`); + } + }, func, name); +} + +function createBatchGetAllRequest(t, storeName, connection, ranges, maxCount) { + const transaction = connection.transaction(storeName, 'readonly'); + const store = transaction.objectStore(storeName); + const req = store.batchGetAll(ranges, maxCount); + req.onerror = t.unreached_func('batchGetAll request should succeed'); + return req; +} + +function assertTwoDArrayEquals(result, expected) { + assert_equals(JSON.stringify(result), JSON.stringify(expected)); +} + +batchgetall_test('out-of-line', (t, connection) => { + const req = createBatchGetAllRequest(t, 'out-of-line', connection, ['c']); + req.onsuccess = t.step_func(evt => { + let result = evt.target.result; + let expected = [['value-c']]; + assertTwoDArrayEquals(result, expected) + t.done(); + }); +}, 'Single item get'); + +batchgetall_test('empty', (t, connection) => { + const req = createBatchGetAllRequest(t, 'empty', connection); + req.onsuccess = t.step_func(evt => { + assert_array_equals( + evt.target.result, [], + 'getAll() on empty object store should return an empty array'); + t.done(); + }); +}, 'batchGetAll on empty object store'); + + +batchgetall_test('out-of-line', (t, connection) => { + const req = createBatchGetAllRequest( + t, 'out-of-line', connection, ['c', 'dd', 'e', 'ff']); + req.onsuccess = t.step_func(evt => { + let result = evt.target.result; + let expected = [['value-c'], [], ['value-e'], []]; + assertTwoDArrayEquals(result, expected); + t.done(); + }); +}, 'batchGetAll with non-existing values'); + + +batchgetall_test('out-of-line', (t, connection) => { + const req = createBatchGetAllRequest( + t, 'out-of-line', connection, [IDBKeyRange.bound('a', 'z')], 5); + req.onsuccess = t.step_func(evt => { + let result = evt.target.result; + let expected = [['value-a', 'value-b', 'value-c', 'value-d', 'value-e']]; + assertTwoDArrayEquals(result, expected); + t.done(); + }); +}, 'Get bound range with maxCount'); + + +batchgetall_test('out-of-line', (t, connection) => { + const req = createBatchGetAllRequest( + t, 'out-of-line', connection, [IDBKeyRange.bound('a', 'e')]); + req.onsuccess = t.step_func(evt => { + let result = evt.target.result; + let expected = [['value-a', 'value-b', 'value-c', 'value-d', 'value-e']]; + assertTwoDArrayEquals(result, expected); + t.done(); + }); +}, 'Get bound range'); + +batchgetall_test('out-of-line', (t, connection) => { + const req = createBatchGetAllRequest(t, 'out-of-line', connection, [ + IDBKeyRange.bound('g', 'k', false, true), + IDBKeyRange.bound('g', 'k', true, false) + ]); + req.onsuccess = t.step_func(evt => { + let result = evt.target.result; + let expected = [ + ['value-g', 'value-h', 'value-i', 'value-j'], + ['value-h', 'value-i', 'value-j', 'value-k'] + ]; + assertTwoDArrayEquals(result, expected); + t.done(); + }); +}, 'Get upper/lower excluded'); + +batchgetall_test('generated', (t, connection) => { + const req = createBatchGetAllRequest( + t, 'generated', connection, + [IDBKeyRange.bound(4, 15), IDBKeyRange.bound(5, 15)], 3); + req.onsuccess = t.step_func(evt => { + let result = evt.target.result; + let expected = [ + [{ch: 'd', id: 4}, {ch: 'e', id: 5}, {ch: 'f', id: 6}], + [{ch: 'e', id: 5}, {ch: 'f', id: 6}, {ch: 'g', id: 7}] + ]; + assertTwoDArrayEquals(result, expected); + t.done(); + }); +}, 'Get bound range (generated) with maxCount'); + + +batchgetall_test('out-of-line', (t, connection) => { + const req = createBatchGetAllRequest( + t, 'out-of-line', connection, [IDBKeyRange.bound('a', 'e')], 0); + req.onsuccess = t.step_func(evt => { + let result = evt.target.result; + let expected = [['value-a', 'value-b', 'value-c', 'value-d', 'value-e']]; + assertTwoDArrayEquals(result, expected); + t.done(); + }); +}, 'zero maxCount');
\ No newline at end of file diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_batchGetAll_largeValue.tentative.any.js b/testing/web-platform/tests/IndexedDB/idbobjectstore_batchGetAll_largeValue.tentative.any.js new file mode 100644 index 0000000000..497196e642 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_batchGetAll_largeValue.tentative.any.js @@ -0,0 +1,118 @@ +// META: title=Batch Get All (big value) +// META: script=resources/support.js +// META: script=resources/support-promises.js + +'use strict'; + +// engines that have special code paths for large values. +const wrapThreshold = 128 * 1024; +const keys = Array.from({length: 10}, (item, index) => index); +const values = + Array.from(keys, (item, index) => largeValue(wrapThreshold, index)); + +function batchgetall_test(storeName, func, name) { + indexeddb_test((t, connection, tx) => { + let store = connection.createObjectStore(storeName, null); + for (let i = 0; i < keys.length; i++) { + store.put(values[i], keys[i]) + } + }, func, name); +} + +function createBatchGetAllRequest(t, storeName, connection, ranges, maxCount) { + const transaction = connection.transaction(storeName, 'readonly'); + const store = transaction.objectStore(storeName); + const req = store.batchGetAll(ranges, maxCount); + req.onerror = t.unreached_func('batchGetAll request should succeed'); + return req; +} + +function assertTwoDArrayEquals(result, expected) { + assert_equals(JSON.stringify(result), JSON.stringify(expected)); +} + +batchgetall_test('out-of-line', (t, connection) => { + const req = createBatchGetAllRequest(t, 'out-of-line', connection, [2]); + req.onsuccess = t.step_func(evt => { + let result = evt.target.result; + let expected = [[values[2]]]; + assertTwoDArrayEquals(result, expected); + t.done(); + }); +}, 'Single item get'); + + +batchgetall_test('empty', (t, connection) => { + const req = createBatchGetAllRequest(t, 'empty', connection); + req.onsuccess = t.step_func(evt => { + assert_array_equals( + evt.target.result, [], + 'getAll() on empty object store should return an empty array'); + t.done(); + }); +}, 'batchGetAll on empty object store'); + + +batchgetall_test('out-of-line', (t, connection) => { + const req = + createBatchGetAllRequest(t, 'out-of-line', connection, [1, 'a', 4, 'z']); + req.onsuccess = t.step_func(evt => { + let result = evt.target.result; + let expected = [[values[1]], [], [values[4]], []]; + assertTwoDArrayEquals(result, expected); + t.done(); + }); +}, 'batchGetAll with non-existing values'); + + +batchgetall_test('out-of-line', (t, connection) => { + const req = createBatchGetAllRequest( + t, 'out-of-line', connection, [IDBKeyRange.bound(0, 10)], 5); + req.onsuccess = t.step_func(evt => { + let result = evt.target.result; + let expected = [[values[0], values[1], values[2], values[3], values[4]]]; + assertTwoDArrayEquals(result, expected); + t.done(); + }); +}, 'Get bound range with maxCount'); + + + +batchgetall_test('out-of-line', (t, connection) => { + const req = createBatchGetAllRequest( + t, 'out-of-line', connection, [IDBKeyRange.bound(0, 4)]); + req.onsuccess = t.step_func(evt => { + let result = evt.target.result; + let expected = [[values[0], values[1], values[2], values[3], values[4]]]; + assertTwoDArrayEquals(result, expected); + t.done(); + }); +}, 'Get bound range'); + + +batchgetall_test('out-of-line', (t, connection) => { + const req = createBatchGetAllRequest(t, 'out-of-line', connection, [ + IDBKeyRange.bound(0, 4, false, true), IDBKeyRange.bound(0, 4, true, false) + ]); + req.onsuccess = t.step_func(evt => { + let result = evt.target.result; + let expected = [ + [values[0], values[1], values[2], values[3]], + [values[1], values[2], values[3], values[4]] + ]; + assertTwoDArrayEquals(result, expected); + t.done(); + }); +}, 'Get upper/lower excluded'); + + +batchgetall_test('out-of-line', (t, connection) => { + const req = createBatchGetAllRequest( + t, 'out-of-line', connection, [IDBKeyRange.bound(1, 4)], 0); + req.onsuccess = t.step_func(evt => { + let result = evt.target.result; + let expected = [[values[1], values[2], values[3], values[4]]]; + assertTwoDArrayEquals(result, expected); + t.done(); + }); +}, 'zero maxCount');
\ No newline at end of file diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_clear.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_clear.htm new file mode 100644 index 0000000000..51b6da72d7 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_clear.htm @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.clear() - Verify clear removes all records </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store", { autoIncrement: true }); + + objStore.add({ property: "data" }); + objStore.add({ something_different: "Yup, totally different" }); + objStore.add(1234); + objStore.add([1, 2, 1234]); + + objStore.clear().onsuccess = t.step_func(function(e) { + assert_equals(e.target.result, undefined); + }); + }; + + + open_rq.onsuccess = function(e) { + var rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .openCursor(); + + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result, null, 'cursor'); + t.done(); + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_clear2.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_clear2.htm new file mode 100644 index 0000000000..a4f15bde02 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_clear2.htm @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.clear() - clear removes all records from an index </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store", { autoIncrement: true }); + objStore.createIndex("index", "indexedProperty"); + + objStore.add({ indexedProperty: "data" }); + objStore.add({ indexedProperty: "yo, man", something_different: "Yup, totally different" }); + objStore.add({ indexedProperty: 1234 }); + objStore.add({ indexedProperty: [1, 2, 1234] }); + objStore.add(1234); + + objStore.clear().onsuccess = t.step_func(function(e) { + assert_equals(e.target.result, undefined); + }); + }; + + open_rq.onsuccess = function(e) { + var rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .index("index") + .openCursor(); + + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result, null, 'cursor'); + t.done(); + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_clear3.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_clear3.htm new file mode 100644 index 0000000000..2bb44ec31c --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_clear3.htm @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.clear() - If the transaction this IDBObjectStore belongs to has its mode set to readonly, throw ReadOnlyError </title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBObjectStore-clear-IDBRequest"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + t = async_test(), + records = [{ pKey: "primaryKey_0"}, + { pKey: "primaryKey_1"}]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + var objStore = db.createObjectStore("store", {keyPath:"pKey"}); + for (var i = 0; i < records.length; i++) { + objStore.add(records[i]); + } + } + + open_rq.onsuccess = function (event) { + var txn = db.transaction("store", "readonly", {durability: 'relaxed'}); + var ostore = txn.objectStore("store"); + t.step(function(){ + assert_throws_dom("ReadOnlyError", function(){ + ostore.clear(); + }); + }); + t.done(); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_clear4.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_clear4.htm new file mode 100644 index 0000000000..f5e1cad563 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_clear4.htm @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.clear() - If the object store has been deleted, the implementation must throw a DOMException of type InvalidStateError </title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBObjectStore-clear-IDBRequest"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + ostore, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + ostore = db.createObjectStore("store", {keyPath:"pKey"}); + db.deleteObjectStore("store"); + assert_throws_dom("InvalidStateError", function(){ + ostore.clear(); + }); + t.done(); + } +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_count.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_count.htm new file mode 100644 index 0000000000..6466d58041 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_count.htm @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBObjectStore.count() - returns the number of records in the object store </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var db, t = async_test(); + + var open_rq = createdb(t); + + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var store = db.createObjectStore("store"); + + for(var i = 0; i < 10; i++) { + store.add({ data: "data" + i }, i); + } + } + + open_rq.onsuccess = function(e) { + var rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .count(); + + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result, 10); + t.done(); + }); + } +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_count2.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_count2.htm new file mode 100644 index 0000000000..6152eaf76e --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_count2.htm @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBObjectStore.count() - returns the number of records that have keys within the range </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var db, t = async_test(); + + var open_rq = createdb(t); + + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var store = db.createObjectStore("store"); + + for(var i = 0; i < 10; i++) { + store.add({ data: "data" + i }, i); + } + } + + open_rq.onsuccess = function(e) { + var rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .count(IDBKeyRange.bound(5,20)); + + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result, 5); + t.done(); + }); + } +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_count3.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_count3.htm new file mode 100644 index 0000000000..3d5fd17a22 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_count3.htm @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBObjectStore.count() - returns the number of records that have keys with the key</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var db + + createdb(async_test()).onupgradeneeded = function(e) { + db = e.target.result + + var store = db.createObjectStore("store", { keyPath: "k" }) + + for (var i = 0; i < 5; i++) + store.add({ k: "key_" + i }); + + store.count("key_2").onsuccess = this.step_func(function(e) { + assert_equals(e.target.result, 1, "count(key_2)") + + store.count("key_").onsuccess = this.step_func(function(e) { + assert_equals(e.target.result, 0, "count(key_)") + this.done() + }) + }) + } + +</script> +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_count4.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_count4.htm new file mode 100644 index 0000000000..c328a91e33 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_count4.htm @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.count() - If the object store has been deleted, the implementation must throw a DOMException of type InvalidStateError </title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBObjectStore-count-IDBRequest-any-key"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + ostore, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + ostore = db.createObjectStore("store", {keyPath:"pKey"}); + db.deleteObjectStore("store"); + assert_throws_dom("InvalidStateError", function(){ + ostore.count(); + }); + t.done(); + } +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex.htm new file mode 100644 index 0000000000..ebd6377e49 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex.htm @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.createIndex() - returns an IDBIndex and the properties are set correctly</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store"); + var index = objStore.createIndex("index", "indexedProperty", { unique: true }); + + assert_true(index instanceof IDBIndex, "IDBIndex"); + assert_equals(index.name, "index", "name"); + assert_equals(index.objectStore, objStore, "objectStore"); + assert_equals(index.keyPath, "indexedProperty", "keyPath"); + assert_true(index.unique, "unique"); + assert_false(index.multiEntry, "multiEntry"); + + t.done(); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex10.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex10.htm new file mode 100644 index 0000000000..46678b1b6c --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex10.htm @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBDatabase.createIndex() - If an index with the name name already exists in this object store, the implementation must throw a DOMException of type ConstraintError </title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBObjectStore-createIndex-IDBIndex-DOMString-name-DOMString-sequence-DOMString--keyPath-IDBIndexParameters-optionalParameters"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> +var t = async_test(), + open_rq = createdb(t); + +open_rq.onupgradeneeded = function (e) { + var db = e.target.result; + var ostore = db.createObjectStore("store"); + ostore.createIndex("a", "a"); + assert_throws_dom("ConstraintError", function(){ + ostore.createIndex("a", "a"); + }); + t.done(); +} +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex11.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex11.htm new file mode 100644 index 0000000000..e458977212 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex11.htm @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBDatabase.createIndex() - If keyPath is not a valid key path, the implementation must throw a DOMException of type SyntaxError </title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBObjectStore-createIndex-IDBIndex-DOMString-name-DOMString-sequence-DOMString--keyPath-IDBIndexParameters-optionalParameters"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> +var t = async_test(), + open_rq = createdb(t); + +open_rq.onupgradeneeded = function (e) { + var db = e.target.result; + var ostore = db.createObjectStore("store"); + assert_throws_dom("SyntaxError", function(){ + ostore.createIndex("ab", "."); + }); + t.done(); +} +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex12.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex12.htm new file mode 100644 index 0000000000..a74ac6f5a1 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex12.htm @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBDatabase.createIndex() - If the object store has been deleted, the implementation must throw a DOMException of type InvalidStateError </title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBObjectStore-createIndex-IDBIndex-DOMString-name-DOMString-sequence-DOMString--keyPath-IDBIndexParameters-optionalParameters"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + ostore, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + ostore = db.createObjectStore("store"); + db.deleteObjectStore("store"); + } + + open_rq.onsuccess = function (event) { + t.step(function(){ + assert_throws_dom("InvalidStateError", function(){ + ostore.createIndex("index", "indexedProperty"); + }); + }); + t.done(); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex13.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex13.htm new file mode 100644 index 0000000000..673a54fb00 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex13.htm @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBDatabase.createIndex() - Operate out versionchange throw InvalidStateError </title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBObjectStore-createIndex-IDBIndex-DOMString-name-DOMString-sequence-DOMString--keyPath-IDBIndexParameters-optionalParameters"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + db.createObjectStore("store"); + } + + open_rq.onsuccess = function (event) { + var txn = db.transaction("store", "readwrite", {durability: 'relaxed'}); + var ostore = txn.objectStore("store"); + t.step(function(){ + assert_throws_dom("InvalidStateError", function(){ + ostore.createIndex("index", "indexedProperty"); + }); + }); + t.done(); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex14-exception_order.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex14-exception_order.htm new file mode 100644 index 0000000000..53aaec2a87 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex14-exception_order.htm @@ -0,0 +1,89 @@ +<!DOCTYPE html> +<title>IndexedDB: Exception Order of IDBObjectStore.createIndex()</title> +<link rel="author" title="Mozilla" href="https://www.mozilla.org"> +<link rel="help" href="http://w3c.github.io/IndexedDB/#dom-idbobjectstore-createindex"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + function(t, db, txn) { + var store = db.createObjectStore("s"); + }, + function(t, db) { + var txn = db.transaction("s", "readonly", {durability: 'relaxed'}); + var store = txn.objectStore("s"); + txn.oncomplete = function() { + assert_throws_dom("InvalidStateError", function() { + store.createIndex("index", "foo"); + }, "Mode check should precede state check of the transaction"); + t.done(); + }; + }, + "InvalidStateError(Incorrect mode) vs. TransactionInactiveError" +); + +var gDeletedObjectStore; +indexeddb_test( + function(t, db, txn) { + gDeletedObjectStore = db.createObjectStore("s"); + db.deleteObjectStore("s"); + txn.oncomplete = function() { + assert_throws_dom("InvalidStateError", function() { + gDeletedObjectStore.createIndex("index", "foo"); + }, "Deletion check should precede transaction-state check"); + t.done(); + }; + }, + null, + "InvalidStateError(Deleted ObjectStore) vs. TransactionInactiveError" +); + +indexeddb_test( + function(t, db, txn) { + var store = db.createObjectStore("s"); + store.createIndex("index", "foo"); + txn.oncomplete = function() { + assert_throws_dom("TransactionInactiveError", function() { + store.createIndex("index", "foo"); + }, "Transaction-state check should precede index name check"); + t.done(); + }; + }, + null, + "TransactionInactiveError vs. ConstraintError" +); + +indexeddb_test( + function(t, db) { + var store = db.createObjectStore("s"); + store.createIndex("index", "foo"); + assert_throws_dom("ConstraintError", function() { + store.createIndex("index", "invalid key path"); + }, "Index name check should precede syntax check of the key path"); + assert_throws_dom("ConstraintError", function() { + store.createIndex("index", + ["invalid key path 1", "invalid key path 2"]); + }, "Index name check should precede syntax check of the key path"); + t.done(); + }, + null, + "ConstraintError vs. SyntaxError" +); + +indexeddb_test( + function(t, db) { + var store = db.createObjectStore("s"); + assert_throws_dom("SyntaxError", function() { + store.createIndex("index", + ["invalid key path 1", "invalid key path 2"], + { multiEntry: true }); + }, "Syntax check should precede multiEntry check of the key path"); + t.done(); + }, + null, + "SyntaxError vs. InvalidAccessError" +); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex15-autoincrement.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex15-autoincrement.htm new file mode 100644 index 0000000000..679b5d05aa --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex15-autoincrement.htm @@ -0,0 +1,106 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBObjectStore.createIndex() - AutoIncrement in Compound Index</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + indexeddb_test( + function(t, db, txn) { + // No auto-increment + var store = db.createObjectStore("Store1", {keyPath: "id"}); + store.createIndex("CompoundKey", ["num", "id"]); + + // Add data + store.put({id: 1, num: 100}); + }, + function(t, db) { + var store = db.transaction("Store1", "readwrite", {durability: 'relaxed'}).objectStore("Store1"); + + store.openCursor().onsuccess = t.step_func(function(e) { + var item = e.target.result.value; + store.index("CompoundKey").get([item.num, item.id]).onsuccess = t.step_func(function(e) { + assert_equals(e.target.result ? e.target.result.num : null, 100, 'Expected 100.'); + t.done(); + }); + }); + }, + "Explicit Primary Key" + ); + + indexeddb_test( + function(t, db, txn) { + // Auto-increment + var store = db.createObjectStore("Store2", {keyPath: "id", autoIncrement: true}); + store.createIndex("CompoundKey", ["num", "id"]); + + // Add data + store.put({num: 100}); + }, + function(t, db) { + var store = db.transaction("Store2", "readwrite", {durability: 'relaxed'}).objectStore("Store2"); + store.openCursor().onsuccess = t.step_func(function(e) { + var item = e.target.result.value; + store.index("CompoundKey").get([item.num, item.id]).onsuccess = t.step_func(function(e) { + assert_equals(e.target.result ? e.target.result.num : null, 100, 'Expected 100.'); + t.done(); + }); + }); + }, + "Auto-Increment Primary Key" + ); + + indexeddb_test( + function(t, db, txn) { + // Auto-increment + var store = db.createObjectStore("Store3", {keyPath: "id", autoIncrement: true}); + store.createIndex("CompoundKey", ["num", "id", "other"]); + + var num = 100; + + // Add data to Store3 - valid keys + // Objects will be stored in Store3 and keys will get added + // to the CompoundKeys index. + store.put({num: num++, other: 0}); + store.put({num: num++, other: [0]}); + + // Add data - missing key + // Objects will be stored in Store3 but keys won't get added to + // the CompoundKeys index because the 'other' keypath doesn't + // resolve to a value. + store.put({num: num++}); + + // Add data to Store3 - invalid keys + // Objects will be stored in Store3 but keys won't get added to + // the CompoundKeys index because the 'other' property values + // aren't valid keys. + store.put({num: num++, other: null}); + store.put({num: num++, other: {}}); + store.put({num: num++, other: [null]}); + store.put({num: num++, other: [{}]}); + }, + function(t, db) { + var store = db.transaction("Store3", "readwrite", {durability: 'relaxed'}).objectStore("Store3"); + const keys = []; + let count; + store.count().onsuccess = t.step_func(e => { count = e.target.result; }); + store.index("CompoundKey").openCursor().onsuccess = t.step_func(function(e) { + const cursor = e.target.result; + if (cursor !== null) { + keys.push(cursor.key); + cursor.continue(); + return; + } + + // Done iteration, check results. + assert_equals(count, 7, 'Expected all 7 records to be stored.'); + assert_equals(keys.length, 2, 'Expected exactly two index entries.'); + assert_array_equals(keys[0], [100, 1, 0]); + assert_object_equals(keys[1], [101, 2, [0]]); + t.done(); + }); + }, + "Auto-Increment Primary Key - invalid key values elsewhere" + ); +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex2.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex2.htm new file mode 100644 index 0000000000..cac6c79967 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex2.htm @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.createIndex() - attempt to create an index that requires unique values on an object store already contains duplicates </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, aborted, + t = async_test(), + record = { indexedProperty: "bar" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var txn = e.target.transaction, + objStore = db.createObjectStore("store"); + + objStore.add(record, 1); + objStore.add(record, 2); + var index = objStore.createIndex("index", "indexedProperty", { unique: true }); + + assert_true(index instanceof IDBIndex, "IDBIndex"); + + e.target.transaction.onabort = t.step_func(function(e) { + aborted = true; + assert_equals(e.type, "abort", "event type"); + }); + + db.onabort = function(e) { + assert_true(aborted, "transaction.abort event has fired"); + t.done(); + }; + + e.target.transaction.oncomplete = fail(t, "got complete, expected abort"); + }; + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex3-usable-right-away.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex3-usable-right-away.htm new file mode 100644 index 0000000000..510f17f173 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex3-usable-right-away.htm @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.createIndex() - the index is usable right after being made</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<meta name=timeout content=long> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, aborted, + t = async_test() + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var txn = e.target.transaction, + objStore = db.createObjectStore("store", { keyPath: 'key' }); + + for (var i = 0; i < 100; i++) + objStore.add({ key: "key_" + i, indexedProperty: "indexed_" + i }); + + var idx = objStore.createIndex("index", "indexedProperty") + + idx.get('indexed_99').onsuccess = t.step_func(function(e) { + assert_equals(e.target.result.key, 'key_99', 'key'); + }); + idx.get('indexed_9').onsuccess = t.step_func(function(e) { + assert_equals(e.target.result.key, 'key_9', 'key'); + }); + } + + open_rq.onsuccess = function() { + t.done(); + } +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex4-deleteIndex-event_order.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex4-deleteIndex-event_order.htm new file mode 100644 index 0000000000..bc5915f54a --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex4-deleteIndex-event_order.htm @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.createIndex() - Event ordering for a later deleted index</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + events = [], + t = async_test() + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + e.target.transaction.oncomplete = log("transaction.complete"); + + var txn = e.target.transaction, + objStore = db.createObjectStore("store"); + + var rq_add1 = objStore.add({ animal: "Unicorn" }, 1); + rq_add1.onsuccess = log("rq_add1.success"); + rq_add1.onerror = log("rq_add1.error"); + + objStore.createIndex("index", "animal", { unique: true }); + + var rq_add2 = objStore.add({ animal: "Unicorn" }, 2); + rq_add2.onsuccess = log("rq_add2.success"); + rq_add2.onerror = function(e) { + log("rq_add2.error")(e); + e.preventDefault(); + e.stopPropagation(); + } + + objStore.deleteIndex("index"); + + var rq_add3 = objStore.add({ animal: "Unicorn" }, 3); + rq_add3.onsuccess = log("rq_add3.success"); + rq_add3.onerror = log("rq_add3.error"); + } + + open_rq.onsuccess = function(e) { + log("open_rq.success")(e); + assert_array_equals(events, [ "rq_add1.success", + "rq_add2.error: ConstraintError", + "rq_add3.success", + + "transaction.complete", + + "open_rq.success" ], + "events"); + t.done(); + } + + function log(msg) { + return function(e) { + if(e && e.target && e.target.error) + events.push(msg + ": " + e.target.error.name); + else + events.push(msg); + }; + } +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex5-emptykeypath.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex5-emptykeypath.htm new file mode 100644 index 0000000000..b004a218ff --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex5-emptykeypath.htm @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.createIndex() - empty keyPath</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, aborted, + t = async_test() + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var txn = e.target.transaction, + objStore = db.createObjectStore("store"); + + for (var i = 0; i < 5; i++) + objStore.add("object_" + i, i); + + var rq = objStore.createIndex("index", "") + rq.onerror = function() { assert_unreached("error: " + rq.error.name); } + rq.onsuccess = function() { } + + objStore.index("index") + .get('object_4') + .onsuccess = t.step_func(function(e) { + assert_equals(e.target.result, 'object_4', 'result'); + }); + } + + open_rq.onsuccess = function() { + t.done(); + } +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex6-event_order.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex6-event_order.htm new file mode 100644 index 0000000000..db7398d360 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex6-event_order.htm @@ -0,0 +1,75 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBObjectStore.createIndex() - event order when unique constraint is triggered</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<meta rel=help href=http://odinho.html5.org/IndexedDB/spec/Overview.html#widl-IDBObjectStore-createIndex-IDBIndex-DOMString-name-any-keyPath-IDBIndexParameters-optionalParameters> +<meta rel=assert title="The index that is requested to be created can contain constraints on the data allowed in the index's referenced object store, such as requiring uniqueness of the values referenced by the index's keyPath. If the referenced object store already contains data which violates these constraints, this must not cause the implementation of createIndex to throw an exception or affect what it returns. The implementation must still create and return an IDBIndex object. Instead the implementation must queue up an operation to abort the 'versionchange' transaction which was used for the createIndex call."> +<meta rel=assert title="In some implementations it's possible for the implementation to asynchronously run into problems creating the index after the createIndex function has returned. For example in implementations where metadata about the newly created index is queued up to be inserted into the database asynchronously, or where the implementation might need to ask the user for permission for quota reasons. Such implementations must still create and return an IDBIndex object. Instead, once the implementation realizes that creating the index has failed, it must abort the transaction using the steps for aborting a transaction using the appropriate error as error parameter."> +<meta rel=assert title="if the index can't be created due to unique constraints, ConstraintError must be used as error"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + // Transaction may fire window.onerror in some implementations. + setup({allow_uncaught_exception:true}); + + var db, + events = [], + t = async_test() + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + db.onerror = log("db.error"); + db.onabort = log("db.abort"); + e.target.transaction.onabort = log("transaction.abort") + e.target.transaction.onerror = log("transaction.error") + e.target.transaction.oncomplete = log("transaction.complete") + + var txn = e.target.transaction, + objStore = db.createObjectStore("store"); + + var rq_add1 = objStore.add({ animal: "Unicorn" }, 1); + rq_add1.onsuccess = log("rq_add1.success"); + rq_add1.onerror = log("rq_add1.error"); + + var rq_add2 = objStore.add({ animal: "Unicorn" }, 2); + rq_add2.onsuccess = log("rq_add2.success"); + rq_add2.onerror = log("rq_add2.error"); + + objStore.createIndex("index", "animal", { unique: true }) + + var rq_add3 = objStore.add({ animal: "Unicorn" }, 3); + rq_add3.onsuccess = log("rq_add3.success"); + rq_add3.onerror = log("rq_add3.error"); + } + + open_rq.onerror = function(e) { + log("open_rq.error")(e); + assert_array_equals(events, [ "rq_add1.success", + "rq_add2.success", + + "rq_add3.error: AbortError", + "transaction.error: AbortError", + "db.error: AbortError", + + "transaction.abort: ConstraintError", + "db.abort: ConstraintError", + + "open_rq.error: AbortError" ], + "events"); + t.done(); + } + + function log(msg) { + return function(e) { + if(e && e.target && e.target.error) + events.push(msg + ": " + e.target.error.name); + else + events.push(msg); + }; + } +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex7-event_order.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex7-event_order.htm new file mode 100644 index 0000000000..9be4c563f4 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex7-event_order.htm @@ -0,0 +1,80 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.createIndex() - Event ordering for ConstraintError on request</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<meta rel=help href=http://odinho.html5.org/IndexedDB/spec/Overview.html#dfn-steps-for-aborting-a-transaction> +<meta rel=assert title="Unless error was set to null, create a DOMException object and set its name to error. Set transaction's error property to this newly created DOMException."> +<meta rel=assert title="If the transaction's request list contain any requests whose done flag is still false, abort the steps for asynchronously executing a request for each such request and queue a task to perform the following steps:"> +<meta rel=assert title="set the request's error attribute to a DOMException with a type of AbortError."> +<meta rel=assert title="Dispatch an event at request. The event must use the Event interface and have its type set to 'error'. The event bubbles and is cancelable. The propagation path for the event is transaction's connection, then transaction and finally the request. There is no default action for the event."> +<meta rel=assert title="Queue up an operation to dispatch an event at transaction. The event must use the Event interface and have its type set to 'abort'. The event does bubble but is not cancelable. The propagation path for the event is transaction's connection and then transaction."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + // Transaction may fire window.onerror in some implementations. + setup({allow_uncaught_exception:true}); + + var db, + events = [], + t = async_test() + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var txn = e.target.transaction; + db.onerror = log("db.error"); + db.onabort = log("db.abort"); + txn.onabort = log("transaction.abort") + txn.onerror = log("transaction.error") + txn.oncomplete = log("transaction.complete") + + var objStore = db.createObjectStore("store"); + + var rq_add1 = objStore.add({ animal: "Unicorn" }, 1); + rq_add1.onsuccess = log("rq_add1.success"); + rq_add1.onerror = log("rq_add1.error"); + + objStore.createIndex("index", "animal", { unique: true }) + + var rq_add2 = objStore.add({ animal: "Unicorn" }, 2); + rq_add2.onsuccess = log("rq_add2.success"); + rq_add2.onerror = log("rq_add2.error"); + + var rq_add3 = objStore.add({ animal: "Horse" }, 3); + rq_add3.onsuccess = log("rq_add3.success"); + rq_add3.onerror = log("rq_add3.error"); + } + + open_rq.onerror = function(e) { + log("open_rq.error")(e); + assert_array_equals(events, [ "rq_add1.success", + + "rq_add2.error: ConstraintError", + "transaction.error: ConstraintError", + "db.error: ConstraintError", + + "rq_add3.error: AbortError", + "transaction.error: AbortError", + "db.error: AbortError", + + "transaction.abort: ConstraintError", + "db.abort: ConstraintError", + + "open_rq.error: AbortError" ], + "events"); + t.done(); + } + + function log(msg) { + return function(e) { + if(e && e.target && e.target.error) + events.push(msg + ": " + e.target.error.name); + else + events.push(msg); + }; + } +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex8-valid_keys.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex8-valid_keys.htm new file mode 100644 index 0000000000..10c2b29c57 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex8-valid_keys.htm @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.createIndex() - index can be valid keys</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<meta name=timeout content=long> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + now = new Date(), + mar18 = new Date(1111111111111), + ar = ["Yay", 2, -Infinity], + num = 1337 + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var txn = e.target.transaction, + objStore = db.createObjectStore("store", { keyPath: 'key' }); + + objStore.add({ key: "now", i: now }); + objStore.add({ key: "mar18", i: mar18 }); + objStore.add({ key: "array", i: ar }); + objStore.add({ key: "number", i: num }); + + var idx = objStore.createIndex("index", "i") + + idx.get(now).onsuccess = t.step_func(function(e) { + assert_equals(e.target.result.key, 'now', 'key'); + assert_equals(e.target.result.i.getTime(), now.getTime(), 'getTime'); + }); + idx.get(mar18).onsuccess = t.step_func(function(e) { + assert_equals(e.target.result.key, 'mar18', 'key'); + assert_equals(e.target.result.i.getTime(), mar18.getTime(), 'getTime'); + }); + idx.get(ar).onsuccess = t.step_func(function(e) { + assert_equals(e.target.result.key, 'array', 'key'); + assert_array_equals(e.target.result.i, ar, 'array is the same'); + }); + idx.get(num).onsuccess = t.step_func(function(e) { + assert_equals(e.target.result.key, 'number', 'key'); + assert_equals(e.target.result.i, num, 'number is the same'); + }); + } + + open_rq.onsuccess = function() { + t.done(); + } +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex9-emptyname.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex9-emptyname.htm new file mode 100644 index 0000000000..3e1ef3c600 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_createIndex9-emptyname.htm @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBObjectStore.createIndex() - empty name</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var db + + var open_rq = createdb(async_test()) + open_rq.onupgradeneeded = function(e) { + db = e.target.result + var store = db.createObjectStore("store") + + for (var i = 0; i < 5; i++) + store.add({ idx: "object_" + i }, i) + + store.createIndex("", "idx") + + store.index("") + .get('object_4') + .onsuccess = this.step_func(function(e) { + assert_equals(e.target.result.idx, 'object_4', 'result') + }) + assert_equals(store.indexNames[0], "", "indexNames[0]") + assert_equals(store.indexNames.length, 1, "indexNames.length") + } + + open_rq.onsuccess = function() { + var store = db.transaction("store", "readonly", {durability: 'relaxed'}).objectStore("store") + + assert_equals(store.indexNames[0], "", "indexNames[0]") + assert_equals(store.indexNames.length, 1, "indexNames.length") + + this.done() + } +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_delete.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_delete.htm new file mode 100644 index 0000000000..880309d01a --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_delete.htm @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.delete() - delete removes record (inline keys)</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + record = { key: 1, property: "data" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var objStore = db.createObjectStore("test", { keyPath: "key" }); + objStore.add(record); + }; + + open_rq.onsuccess = function(e) { + var delete_rq = db.transaction("test", "readwrite", {durability: 'relaxed'}) + .objectStore("test") + .delete(record.key); + + delete_rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result, undefined); + + e.target.transaction.oncomplete = t.step_func(VerifyRecordRemoved); + }); + }; + + function VerifyRecordRemoved() { + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .get(record.key); + + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result, undefined); + t.done(); + }); + } +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_delete2.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_delete2.htm new file mode 100644 index 0000000000..eb71169905 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_delete2.htm @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.delete() - key doesn't match any records </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var delete_rq = db.createObjectStore("test") + .delete(1); + + delete_rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result, undefined); + t.done(); + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_delete3.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_delete3.htm new file mode 100644 index 0000000000..1ea9dd9958 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_delete3.htm @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.delete() - object store's key path is an object attribute </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + record = { test: { obj: { key: 1 } }, property: "data" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var objStore = db.createObjectStore("test", { keyPath: "test.obj.key" }); + objStore.add(record); + }; + + open_rq.onsuccess = function(e) { + var delete_rq = db.transaction("test", "readwrite", {durability: 'relaxed'}) + .objectStore("test") + .delete(record.test.obj.key); + + delete_rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result, undefined); + + e.target.transaction.oncomplete = t.step_func(VerifyRecordRemoved); + }); + }; + + function VerifyRecordRemoved() { + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .get(record.test.obj.key); + + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result, undefined); + t.done(); + }); + } + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_delete4.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_delete4.htm new file mode 100644 index 0000000000..9d074bff85 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_delete4.htm @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.delete() - delete removes record (out-of-line keys) </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + key = 1, + record = { property: "data" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var objStore = db.createObjectStore("test"); + objStore.add(record, key); + }; + + open_rq.onsuccess = function(e) { + var delete_rq = db.transaction("test", "readwrite", {durability: 'relaxed'}) + .objectStore("test") + .delete(key); + + delete_rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result, undefined); + + e.target.transaction.oncomplete = t.step_func(VerifyRecordRemoved); + }); + }; + + function VerifyRecordRemoved() { + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test") + .get(key); + + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result, undefined); + t.done(); + }); + } + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_delete5.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_delete5.htm new file mode 100644 index 0000000000..f7696e6efa --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_delete5.htm @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<title>IDBObjectStore.delete() - removes all of the records in the range</title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> +<script> + var db + var open_rq = createdb(async_test()) + + open_rq.onupgradeneeded = function(e) { + db = e.target.result + var os = db.createObjectStore("store") + + for(var i = 0; i < 10; i++) + os.add("data" + i, i) + } + + open_rq.onsuccess = function (e) { + var os = db.transaction("store", "readwrite", {durability: 'relaxed'}) + .objectStore("store") + + os.delete( IDBKeyRange.bound(3, 6) ) + os.count().onsuccess = this.step_func(function(e) + { + assert_equals(e.target.result, 6, "Count after deleting 3-6 from 10"); + this.done(); + }) + } +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_delete6.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_delete6.htm new file mode 100644 index 0000000000..70d8af32fe --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_delete6.htm @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.delete() - If the transaction this IDBObjectStore belongs to has its mode set to readonly, throw ReadOnlyError</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBObjectStore-delete-IDBRequest-any-key"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + t = async_test(), + records = [{ pKey: "primaryKey_0"}, + { pKey: "primaryKey_1"}]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + var objStore = db.createObjectStore("store", {keyPath:"pKey"}); + for (var i = 0; i < records.length; i++) { + objStore.add(records[i]); + } + } + + open_rq.onsuccess = function (event) { + var txn = db.transaction("store", "readonly", {durability: 'relaxed'}); + var ostore = txn.objectStore("store"); + t.step(function(){ + assert_throws_dom("ReadOnlyError", function(){ + ostore.delete("primaryKey_0"); + }); + }); + t.done(); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_delete7.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_delete7.htm new file mode 100644 index 0000000000..a65885cc2b --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_delete7.htm @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.delete() - If the object store has been deleted, the implementation must throw a DOMException of type InvalidStateError</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBObjectStore-delete-IDBRequest-any-key"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + ostore, + t = async_test(), + records = [{ pKey: "primaryKey_0"}, + { pKey: "primaryKey_1"}]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + ostore = db.createObjectStore("store", {keyPath:"pKey"}); + db.deleteObjectStore("store"); + assert_throws_dom("InvalidStateError", function(){ + ostore.delete("primaryKey_0"); + }); + t.done(); + } +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_deleteIndex.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_deleteIndex.htm new file mode 100644 index 0000000000..f12af6fc53 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_deleteIndex.htm @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.deleteIndex() - removes the index </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + key = 1, + record = { property: "data" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + db.createObjectStore("test") + .createIndex("index", "indexedProperty") + }; + + open_rq.onsuccess = function(e) { + db.close(); + var new_version = createdb(t, db.name, 2); + new_version.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = e.target.transaction.objectStore("test") + objStore.deleteIndex("index"); + } + new_version.onsuccess = function(e) { + var index, + objStore = db.transaction("test", "readonly", {durability: 'relaxed'}) + .objectStore("test"); + + assert_throws_dom('NotFoundError', + function() { index = objStore.index("index") }); + assert_equals(index, undefined); + db.close(); + t.done(); + } + } +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_deleted.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_deleted.htm new file mode 100644 index 0000000000..5ccc8fdb1b --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_deleted.htm @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Attempting to use deleted IDBObjectStore</title> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#object-store"> +<link rel=assert title="InvalidStateError Occurs if a request is made on a source object that has been deleted or removed."> +<link rel=author href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var db, + add_success = false, + t = async_test() + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var objStore = db.createObjectStore("store", { autoIncrement: true }); + assert_equals(db.objectStoreNames[0], "store", "objectStoreNames"); + + var rq_add = objStore.add(1); + rq_add.onsuccess = function() { add_success = true; }; + rq_add.onerror = fail(t, 'rq_add.error'); + + objStore.createIndex("idx", "a"); + db.deleteObjectStore("store"); + assert_equals(db.objectStoreNames.length, 0, "objectStoreNames.length after delete"); + + const exc = "InvalidStateError" + assert_throws_dom(exc, function() { objStore.add(2); }); + assert_throws_dom(exc, function() { objStore.put(3); }); + assert_throws_dom(exc, function() { objStore.get(1); }); + assert_throws_dom(exc, function() { objStore.clear(); }); + assert_throws_dom(exc, function() { objStore.count(); }); + assert_throws_dom(exc, function() { objStore.delete(1); }); + assert_throws_dom(exc, function() { objStore.openCursor(); }); + assert_throws_dom(exc, function() { objStore.index("idx"); }); + assert_throws_dom(exc, function() { objStore.deleteIndex("idx"); }); + assert_throws_dom(exc, function() { objStore.createIndex("idx2", "a"); }); + } + + open_rq.onsuccess = function() { + assert_true(add_success, "First add was successful"); + t.done(); + } +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_get.any.js b/testing/web-platform/tests/IndexedDB/idbobjectstore_get.any.js new file mode 100644 index 0000000000..638ec6e814 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_get.any.js @@ -0,0 +1,28 @@ +// META: title=IDBObjectStore.get() - key is a number +// META: script=resources/support.js +// @author Microsoft <https://www.microsoft.com> + +"use strict"; + +let db; +const t = async_test(); +const record = { key: 3.14159265, property: "data" }; + +const open_rq = createdb(t); +open_rq.onupgradeneeded = event => { + db = event.target.result; + db.createObjectStore("store", { keyPath: "key" }) + .add(record); +} + +open_rq.onsuccess = event => { + const rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .get(record.key); + + rq.onsuccess = t.step_func(event => { + assert_equals(event.target.result.key, record.key); + assert_equals(event.target.result.property, record.property); + t.done(); + }); +} diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_get2.any.js b/testing/web-platform/tests/IndexedDB/idbobjectstore_get2.any.js new file mode 100644 index 0000000000..488a9043ef --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_get2.any.js @@ -0,0 +1,28 @@ +// META: title=IDBObjectStore.get() - key is a string +// META: script=resources/support.js +// @author Microsoft <https://www.microsoft.com> + +"use strict"; + +let db; +const t = async_test(); +const record = { key: "this is a key that's a string", property: "data" }; + +const open_rq = createdb(t); +open_rq.onupgradeneeded = event => { + db = event.target.result; + db.createObjectStore("store", { keyPath: "key" }) + .add(record); +}; + +open_rq.onsuccess = event => { + const rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .get(record.key); + + rq.onsuccess = t.step_func(event => { + assert_equals(event.target.result.key, record.key); + assert_equals(event.target.result.property, record.property); + t.done(); + }); +}; diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_get3.any.js b/testing/web-platform/tests/IndexedDB/idbobjectstore_get3.any.js new file mode 100644 index 0000000000..3ab6c7c754 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_get3.any.js @@ -0,0 +1,28 @@ +// META: title=IDBObjectStore.get() - key is a Date +// META: script=resources/support.js +// @author Microsoft <https://www.microsoft.com> + +"use strict"; + +let db; +const t = async_test(); +const record = { key: new Date(), property: "data" }; + +const open_rq = createdb(t); +open_rq.onupgradeneeded = event => { + db = event.target.result; + db.createObjectStore("store", { keyPath: "key" }) + .add(record); +}; + +open_rq.onsuccess = event => { + const rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .get(record.key); + + rq.onsuccess = t.step_func(event => { + assert_equals(event.target.result.key.valueOf(), record.key.valueOf()); + assert_equals(event.target.result.property, record.property); + t.done(); + }); +}; diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_get4.any.js b/testing/web-platform/tests/IndexedDB/idbobjectstore_get4.any.js new file mode 100644 index 0000000000..a69717ce4e --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_get4.any.js @@ -0,0 +1,19 @@ +// META: title=IDBObjectStore.get() - attempt to retrieve a record that doesn't exist +// META: script=resources/support.js +// @author Microsoft <https://www.microsoft.com> + +"use strict"; + +let db; +const t = async_test(); + +const open_rq = createdb(t); +open_rq.onupgradeneeded = event => { + db = event.target.result; + const rq = db.createObjectStore("store", { keyPath: "key" }) + .get(1); + rq.onsuccess = t.step_func(event => { + assert_equals(event.target.results, undefined); + t.done(); + }); +}; diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_get5.any.js b/testing/web-platform/tests/IndexedDB/idbobjectstore_get5.any.js new file mode 100644 index 0000000000..1e3bb0fe64 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_get5.any.js @@ -0,0 +1,29 @@ +// META: title=IDBObjectStore.get() - returns the record with the first key in the range +// META: script=resources/support.js +// @author Microsoft <https://www.microsoft.com> + +"use strict"; + +let db; +const t = async_test(); +const open_rq = createdb(t); + +open_rq.onupgradeneeded = event => { + db = event.target.result; + const os = db.createObjectStore("store"); + + for (let i = 0; i < 10; i++) { + os.add(`data${i}`, i); + } +}; + +open_rq.onsuccess = event => { + const rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .get(IDBKeyRange.bound(3, 6)); + + rq.onsuccess = t.step_func(event => { + assert_equals(event.target.result, "data3", "get(3-6)"); + t.done(); + }); +}; diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_get6.any.js b/testing/web-platform/tests/IndexedDB/idbobjectstore_get6.any.js new file mode 100644 index 0000000000..e20af4afc9 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_get6.any.js @@ -0,0 +1,24 @@ +// META: title=IDBObjectStore.get() - throw TransactionInactiveError on aborted transaction +// META: script=resources/support.js +// @author YuichiNukiyama <https://github.com/YuichiNukiyama> + +"use strict"; + +let db; +const t = async_test(); + +const open_rq = createdb(t); +open_rq.onupgradeneeded = event => { + db = event.target.result; + db.createObjectStore("store", { keyPath: "key" }); +}; + +open_rq.onsuccess = event => { + const store = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store"); + store.transaction.abort(); + assert_throws_dom("TransactionInactiveError", function () { + store.get(1); + }, "throw TransactionInactiveError on aborted transaction."); + t.done(); +}; diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_get7.any.js b/testing/web-platform/tests/IndexedDB/idbobjectstore_get7.any.js new file mode 100644 index 0000000000..863ba0ac69 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_get7.any.js @@ -0,0 +1,23 @@ +// META: title=IDBObjectStore.get() - throw DataError when using invalid key +// META: script=resources/support.js +// @author YuichiNukiyama <https://github.com/YuichiNukiyama> + +"use strict"; + +let db; +const t = async_test(); + +const open_rq = createdb(t); +open_rq.onupgradeneeded = event => { + db = event.target.result; + db.createObjectStore("store", { keyPath: "key" }); +} + +open_rq.onsuccess = () => { + const store = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store"); + assert_throws_dom("DataError", () => { + store.get(null) + }, "throw DataError when using invalid key."); + t.done(); +} diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_getAll.any.js b/testing/web-platform/tests/IndexedDB/idbobjectstore_getAll.any.js new file mode 100644 index 0000000000..db7098f71e --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_getAll.any.js @@ -0,0 +1,148 @@ +// META: title=IndexedDB: Test IDBObjectStore.getAll +// META: script=resources/support.js + +'use strict'; + +const alphabet = 'abcdefghijklmnopqrstuvwxyz'.split(''); + +function getall_test(func, name) { + indexeddb_test( + (t, connection, tx) => { + let store = connection.createObjectStore('generated', + { autoIncrement: true, keyPath: 'id' }); + alphabet.forEach(letter => { + store.put({ ch: letter }); + }); + + store = connection.createObjectStore('out-of-line', null); + alphabet.forEach(letter => { + store.put(`value-${letter}`, letter); + }); + + store = connection.createObjectStore('empty', null); + }, + func, + name + ); +} + +function createGetAllRequest(t, storeName, connection, keyRange, maxCount) { + const transaction = connection.transaction(storeName, 'readonly'); + const store = transaction.objectStore(storeName); + const req = store.getAll(keyRange, maxCount); + req.onerror = t.unreached_func('getAll request should succeed'); + return req; +} + +getall_test((t, connection) => { + const req = createGetAllRequest(t, 'out-of-line', connection, 'c'); + req.onsuccess = t.step_func(evt => { + assert_array_equals(evt.target.result, ['value-c']); + t.done(); + }); +}, 'Single item get'); + +getall_test((t, connection) => { + const req = createGetAllRequest(t, 'generated', connection, 3); + req.onsuccess = t.step_func(evt => { + const data = evt.target.result; + assert_true(Array.isArray(data)); + assert_equals(data.length, 1); + assert_equals(data[0].id, 3); + assert_equals(data[0].ch, 'c'); + t.done(); + }); +}, 'Single item get (generated key)'); + +getall_test((t, connection) => { + const req = createGetAllRequest(t, 'empty', connection); + req.onsuccess = t.step_func(evt => { + assert_array_equals(evt.target.result, [], + 'getAll() on empty object store should return an empty array'); + t.done(); + }); +}, 'getAll on empty object store'); + +getall_test((t, connection) => { + const req = createGetAllRequest(t, 'out-of-line', connection); + req.onsuccess = t.step_func(evt => { + assert_array_equals(evt.target.result, alphabet.map(c => `value-${c}`)); + t.done(); + }); +}, 'Get all values'); + +getall_test((t, connection) => { + const req = createGetAllRequest(t, 'out-of-line', connection, undefined, + 10); + req.onsuccess = t.step_func(evt => { + assert_array_equals(evt.target.result, 'abcdefghij'.split('').map(c => `value-${c}`)); + t.done(); + }); +}, 'Test maxCount'); + +getall_test((t, connection) => { + const req = createGetAllRequest(t, 'out-of-line', connection, + IDBKeyRange.bound('g', 'm')); + req.onsuccess = t.step_func(evt => { + assert_array_equals(evt.target.result, 'ghijklm'.split('').map(c => `value-${c}`)); + t.done(); + }); +}, 'Get bound range'); + +getall_test((t, connection) => { + const req = createGetAllRequest(t, 'out-of-line', connection, + IDBKeyRange.bound('g', 'm'), 3); + req.onsuccess = t.step_func(evt => { + assert_array_equals(evt.target.result, ['g', 'h', 'i'].map(c => `value-${c}`)); + t.done(); + }); +}, 'Get bound range with maxCount'); + +getall_test((t, connection) => { + const req = createGetAllRequest(t, 'out-of-line', connection, + IDBKeyRange.bound('g', 'k', false, true)); + req.onsuccess = t.step_func(evt => { + assert_array_equals(evt.target.result, ['g', 'h', 'i', 'j'].map(c => `value-${c}`)); + t.done(); + }); +}, 'Get upper excluded'); + +getall_test((t, connection) => { + const req = createGetAllRequest(t, 'out-of-line', connection, + IDBKeyRange.bound('g', 'k', true, false)); + req.onsuccess = t.step_func(evt => { + assert_array_equals(evt.target.result, ['h', 'i', 'j', 'k'].map(c => `value-${c}`)); + t.done(); + }); +}, 'Get lower excluded'); + +getall_test((t, connection) => { + const req = createGetAllRequest(t, 'generated', connection, + IDBKeyRange.bound(4, 15), 3); + req.onsuccess = t.step_func(evt => { + const data = evt.target.result; + assert_true(Array.isArray(data)); + assert_array_equals(data.map(e => e.ch), ['d', 'e', 'f']); + assert_array_equals(data.map(e => e.id), [4, 5, 6]); + t.done(); + }); +}, 'Get bound range (generated) with maxCount'); + +getall_test((t, connection) => { + const req = createGetAllRequest(t, 'out-of-line', connection, + "Doesn't exist"); + req.onsuccess = t.step_func(evt => { + assert_array_equals(evt.target.result, [], + 'getAll() using a nonexistent key should return an empty array'); + t.done(); + }); + req.onerror = t.unreached_func('getAll request should succeed'); +}, 'Non existent key'); + +getall_test((t, connection) => { + const req = createGetAllRequest(t, 'out-of-line', connection, undefined, 0); + req.onsuccess = t.step_func(evt => { + assert_array_equals(evt.target.result, alphabet.map(c => `value-${c}`)); + t.done(); + }); +}, 'zero maxCount'); diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_getAllKeys.any.js b/testing/web-platform/tests/IndexedDB/idbobjectstore_getAllKeys.any.js new file mode 100644 index 0000000000..951c479873 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_getAllKeys.any.js @@ -0,0 +1,148 @@ +// META: title=IndexedDB: Test IDBObjectStore.getAllKeys +// META: script=resources/support.js + +'use strict'; + +const alphabet = 'abcdefghijklmnopqrstuvwxyz'.split(''); + +function getall_test(func, name) { + indexeddb_test( + (t, connection, tx) => { + let store = connection.createObjectStore('generated', + { autoIncrement: true, keyPath: 'id' }); + alphabet.forEach(letter => { + store.put({ ch: letter }); + }); + + store = connection.createObjectStore('out-of-line', null); + alphabet.forEach(letter => { + store.put(`value-${letter}`, letter); + }); + + store = connection.createObjectStore('empty', null); + }, + func, + name + ); +} + +function createGetAllKeysRequest(t, storeName, connection, keyRange, maxCount) { + const transaction = connection.transaction(storeName, 'readonly'); + const store = transaction.objectStore(storeName); + const req = store.getAllKeys(keyRange, maxCount); + req.onerror = t.unreached_func('getAllKeys request should succeed'); + return req; +} + +getall_test((t, connection) => { + const req = createGetAllKeysRequest(t, 'out-of-line', connection, 'c'); + req.onsuccess = t.step_func(evt => { + assert_array_equals(evt.target.result, ['c']); + t.done(); + }); +}, 'Single item get'); + +getall_test((t, connection) => { + const req = createGetAllKeysRequest(t, 'generated', connection, 3); + req.onsuccess = t.step_func(evt => { + const data = evt.target.result; + assert_true(Array.isArray(data)); + assert_array_equals(data, [3]); + t.done(); + }); +}, 'Single item get (generated key)'); + +getall_test((t, connection) => { + const req = createGetAllKeysRequest(t, 'empty', connection); + req.onsuccess = t.step_func(evt => { + assert_array_equals(evt.target.result, [], + 'getAllKeys() on empty object store should return an empty ' + + 'array'); + t.done(); + }); +}, 'getAllKeys on empty object store'); + +getall_test((t, connection) => { + const req = createGetAllKeysRequest(t, 'out-of-line', connection); + req.onsuccess = t.step_func(evt => { + assert_array_equals(evt.target.result, alphabet); + t.done(); + }); +}, 'Get all values'); + +getall_test((t, connection) => { + const req = createGetAllKeysRequest(t, 'out-of-line', connection, undefined, + 10); + req.onsuccess = t.step_func(evt => { + assert_array_equals(evt.target.result, 'abcdefghij'.split('')); + t.done(); + }); +}, 'Test maxCount'); + +getall_test((t, connection) => { + const req = createGetAllKeysRequest(t, 'out-of-line', connection, + IDBKeyRange.bound('g', 'm')); + req.onsuccess = t.step_func(evt => { + assert_array_equals(evt.target.result, 'ghijklm'.split('')); + t.done(); + }); +}, 'Get bound range'); + +getall_test((t, connection) => { + const req = createGetAllKeysRequest(t, 'out-of-line', connection, + IDBKeyRange.bound('g', 'm'), 3); + req.onsuccess = t.step_func(evt => { + assert_array_equals(evt.target.result, ['g', 'h', 'i']); + t.done(); + }); +}, 'Get bound range with maxCount'); + +getall_test((t, connection) => { + const req = createGetAllKeysRequest(t, 'out-of-line', connection, + IDBKeyRange.bound('g', 'k', false, true)); + req.onsuccess = t.step_func(evt => { + assert_array_equals(evt.target.result, ['g', 'h', 'i', 'j']); + t.done(); + }); +}, 'Get upper excluded'); + +getall_test((t, connection) => { + const req = createGetAllKeysRequest(t, 'out-of-line', connection, + IDBKeyRange.bound('g', 'k', true, false)); + req.onsuccess = t.step_func(evt => { + assert_array_equals(evt.target.result, ['h', 'i', 'j', 'k']); + t.done(); + }); +}, 'Get lower excluded'); + +getall_test((t, connection) => { + const req = createGetAllKeysRequest(t, 'generated', connection, + IDBKeyRange.bound(4, 15), 3); + req.onsuccess = t.step_func(evt => { + const data = evt.target.result; + assert_true(Array.isArray(data)); + assert_array_equals(data, [4, 5, 6]); + t.done(); + }); +}, 'Get bound range (generated) with maxCount'); + +getall_test((t, connection) => { + const req = createGetAllKeysRequest(t, 'out-of-line', connection, + "Doesn't exist"); + req.onsuccess = t.step_func(evt => { + assert_array_equals(evt.target.result, [], + 'getAllKeys() using a nonexistent key should return an ' + + 'empty array'); + t.done(); + }); + req.onerror = t.unreached_func('getAllKeys request should succeed'); +}, 'Non existent key'); + +getall_test((t, connection) => { + const req = createGetAllKeysRequest(t, 'out-of-line', connection, undefined, + 0); + req.onsuccess = t.step_func(evt => { + assert_array_equals(evt.target.result, alphabet); + t.done(); + }); +}, 'zero maxCount'); diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_getKey.any.js b/testing/web-platform/tests/IndexedDB/idbobjectstore_getKey.any.js new file mode 100644 index 0000000000..f0dc13d45c --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_getKey.any.js @@ -0,0 +1,89 @@ +// META: title=IndexedDB: Test IDBObjectStore.getKey() +// META: script=resources/support.js + +'use strict'; + +function getkey_test(func, name) { + indexeddb_test( + (t, db, tx) => { + const basic = db.createObjectStore('basic'); + const key_path_store = db.createObjectStore('key path', + { keyPath: 'id' }); + const key_generator_store = db.createObjectStore('key generator', + { autoIncrement: true }); + const key_generator_and_path_store = db.createObjectStore( + 'key generator and key path', + { autoIncrement: true, key_path: 'id' }); + + for (let i = 1; i <= 10; ++i) { + basic.put(`value: ${i}`, i); + key_path_store.put({ id: i }); + key_generator_store.put(`value: ${i}`); + key_generator_and_path_store.put({}); + } + }, + func, + name + ); +} + +getkey_test((t, db) => { + const tx = db.transaction('basic', 'readonly', {durability: 'relaxed'}); + const store = tx.objectStore('basic'); + assert_throws_js(TypeError, () => store.getKey()); + assert_throws_dom('DataError', () => store.getKey(null)); + assert_throws_dom('DataError', () => store.getKey({})); + t.done(); +}, 'IDBObjectStore.getKey() - invalid parameters'); + +[ + 'basic', + 'key path', + 'key generator', + 'key generator and key path' +].forEach(store_name => { + getkey_test((t, db) => { + const tx = db.transaction(store_name); + const store = tx.objectStore(store_name); + const request = store.getKey(5); + request.onerror = t.unreached_func('request failed'); + request.onsuccess = t.step_func(() => + assert_equals(request.result, 5)); + tx.onabort = t.unreached_func('transaction aborted'); + tx.oncomplete = t.step_func(() => t.done()); + }, `IDBObjectStore.getKey() - ${store_name} - key`); + + getkey_test((t, db) => { + const tx = db.transaction(store_name); + const store = tx.objectStore(store_name); + const request = store.getKey(IDBKeyRange.lowerBound(4.5)); + request.onerror = t.unreached_func('request failed'); + request.onsuccess = t.step_func(() => + assert_equals(request.result, 5)); + tx.onabort = t.unreached_func('transaction aborted'); + tx.oncomplete = t.step_func(() => t.done()); + }, `IDBObjectStore.getKey() - ${store_name} - range`); + + getkey_test((t, db) => { + const tx = db.transaction(store_name); + const store = tx.objectStore(store_name); + const request = store.getKey(11); + request.onerror = t.unreached_func('request failed'); + request.onsuccess = t.step_func(() => + assert_equals(request.result, undefined)); + tx.onabort = t.unreached_func('transaction aborted'); + tx.oncomplete = t.step_func(() => t.done()); + }, `IDBObjectStore.getKey() - ${store_name} - key - no match`); + + getkey_test((t, db) => { + const tx = db.transaction(store_name); + const store = tx.objectStore(store_name); + const request = store.getKey(IDBKeyRange.lowerBound(11)); + request.onerror = t.unreached_func('request failed'); + request.onsuccess = t.step_func(() => + assert_equals(request.result, undefined) + ); + tx.onabort = t.unreached_func('transaction aborted'); + tx.oncomplete = t.step_func(() => t.done()); + }, `IDBObjectStore.getKey() - ${store_name} - range - no match`); +}); diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_index.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_index.htm new file mode 100644 index 0000000000..74d473a5ed --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_index.htm @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.index() - returns an index </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + db.createObjectStore("store") + .createIndex("index", "indexedProperty"); + }; + + open_rq.onsuccess = function(e) { + var index = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .index("index"); + + assert_true(index instanceof IDBIndex, 'instanceof IDBIndex'); + t.done(); + }; + +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_keyPath.any.js b/testing/web-platform/tests/IndexedDB/idbobjectstore_keyPath.any.js new file mode 100644 index 0000000000..b12958bc8a --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_keyPath.any.js @@ -0,0 +1,27 @@ +// META: title=IndexedDB: IDBObjectStore keyPath attribute - same object +// META: script=resources/support.js + +indexeddb_test( + (t, db) => { + db.createObjectStore('store', {keyPath: ['a', 'b']}); + }, + (t, db) => { + const tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + assert_equals(typeof store.keyPath, 'object', 'keyPath is an object'); + assert_true(Array.isArray(store.keyPath), 'keyPath is an array'); + + assert_equals( + store.keyPath, store.keyPath, + 'Same object instance is returned each time keyPath is inspected'); + + const tx2 = db.transaction('store', 'readonly', {durability: 'relaxed'}); + const store2 = tx2.objectStore('store'); + + assert_not_equals( + store.keyPath, store2.keyPath, + 'Different instances are returned from different store instances.'); + + t.done(); + }, + `IDBObjectStore's keyPath attribute returns the same object.`); diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_openCursor.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_openCursor.htm new file mode 100644 index 0000000000..3085ecf013 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_openCursor.htm @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<title>IDBObjectStore.openCursor() - iterate through 100 objects</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + + var db + var open_rq = createdb(async_test()) + + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var store = db.createObjectStore("store"); + + for (var i=0; i < 100; i++) + store.add("record_" + i, i); + }; + + open_rq.onsuccess = function(e) { + var count = 0 + var txn = db.transaction("store", "readonly", {durability: 'relaxed'}) + + txn.objectStore("store") + .openCursor().onsuccess = this.step_func(function(e) + { + if (e.target.result) { + count += 1; + e.target.result.continue() + } + }) + + txn.oncomplete = this.step_func(function() { + assert_equals(count, 100); + this.done() + }) + } + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_openCursor_invalid.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_openCursor_invalid.htm new file mode 100644 index 0000000000..e12db3bb64 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_openCursor_invalid.htm @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<title>IDBObjectStore.openCursor() - invalid</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + function(t, db, tx) { + var objStore = db.createObjectStore("test"); + objStore.createIndex("index", ""); + + objStore.add("data", 1); + objStore.add("data2", 2); + }, + function(t, db, tx) { + var idx = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").index("index"); + + assert_throws_dom("DataError", + function() { idx.openCursor({ lower: "a" }); }); + + assert_throws_dom("DataError", + function() { idx.openCursor({ lower: "a", lowerOpen: false }); }); + + assert_throws_dom("DataError", + function() { idx.openCursor({ lower: "a", lowerOpen: false, upper: null, upperOpen: false }); }); + + t.done(); + }, + document.title + " - pass something other than number" +); +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_openKeyCursor.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_openKeyCursor.htm new file mode 100644 index 0000000000..483fe29a48 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_openKeyCursor.htm @@ -0,0 +1,133 @@ +<!DOCTYPE html> +<title>IDBObjectStore.openKeyCursor()</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> +function store_test(func, name) { + indexeddb_test( + function(t, db, tx) { + var store = db.createObjectStore("store"); + for (var i = 0; i < 10; ++i) { + store.put("value: " + i, i); + } + }, + function(t, db) { + var tx = db.transaction("store", "readonly", {durability: 'relaxed'}); + var store = tx.objectStore("store"); + func(t, db, tx, store); + }, name); +} + +store_test(function(t, db, tx, store) { + var expected = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + var actual = []; + var request = store.openKeyCursor(); + request.onsuccess = t.step_func(function() { + var cursor = request.result; + if (!cursor) + return; + assert_equals(cursor.direction, "next"); + assert_false("value" in cursor); + assert_equals(indexedDB.cmp(cursor.key, cursor.primaryKey), 0); + actual.push(cursor.key); + cursor.continue(); + }); + + tx.onabort = t.unreached_func("transaction aborted"); + tx.oncomplete = t.step_func(function() { + assert_array_equals(expected, actual, "keys should match"); + t.done(); + }); + +}, "IDBObjectStore.openKeyCursor() - forward iteration"); + +store_test(function(t, db, tx, store) { + var expected = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]; + var actual = []; + var request = store.openKeyCursor(null, "prev"); + request.onsuccess = t.step_func(function() { + var cursor = request.result; + if (!cursor) + return; + assert_equals(cursor.direction, "prev"); + assert_false("value" in cursor); + assert_equals(indexedDB.cmp(cursor.key, cursor.primaryKey), 0); + actual.push(cursor.key); + cursor.continue(); + }); + + tx.onabort = t.unreached_func("transaction aborted"); + tx.oncomplete = t.step_func(function() { + assert_array_equals(expected, actual, "keys should match"); + t.done(); + }); + +}, "IDBObjectStore.openKeyCursor() - reverse iteration"); + +store_test(function(t, db, tx, store) { + var expected = [4, 5, 6]; + var actual = []; + var request = store.openKeyCursor(IDBKeyRange.bound(4, 6)); + request.onsuccess = t.step_func(function() { + var cursor = request.result; + if (!cursor) + return; + assert_equals(cursor.direction, "next"); + assert_false("value" in cursor); + assert_equals(indexedDB.cmp(cursor.key, cursor.primaryKey), 0); + actual.push(cursor.key); + cursor.continue(); + }); + + tx.onabort = t.unreached_func("transaction aborted"); + tx.oncomplete = t.step_func(function() { + assert_array_equals(expected, actual, "keys should match"); + t.done(); + }); + +}, "IDBObjectStore.openKeyCursor() - forward iteration with range"); + +store_test(function(t, db, tx, store) { + var expected = [6, 5, 4]; + var actual = []; + var request = store.openKeyCursor(IDBKeyRange.bound(4, 6), "prev"); + request.onsuccess = t.step_func(function() { + var cursor = request.result; + if (!cursor) + return; + assert_equals(cursor.direction, "prev"); + assert_false("value" in cursor); + assert_equals(indexedDB.cmp(cursor.key, cursor.primaryKey), 0); + actual.push(cursor.key); + cursor.continue(); + }); + + tx.onabort = t.unreached_func("transaction aborted"); + tx.oncomplete = t.step_func(function() { + assert_array_equals(expected, actual, "keys should match"); + t.done(); + }); + +}, "IDBObjectStore.openKeyCursor() - reverse iteration with range"); + +store_test(function(t, db, tx, store) { + assert_throws_dom("DataError", function() { store.openKeyCursor(NaN); }, + "openKeyCursor should throw on invalid number key"); + assert_throws_dom("DataError", function() { store.openKeyCursor(new Date(NaN)); }, + "openKeyCursor should throw on invalid date key"); + assert_throws_dom("DataError", function() { + var cycle = []; + cycle.push(cycle); + store.openKeyCursor(cycle); + }, "openKeyCursor should throw on invalid array key"); + assert_throws_dom("DataError", function() { store.openKeyCursor({}); }, + "openKeyCursor should throw on invalid key type"); + setTimeout(t.step_func(function() { + assert_throws_dom("TransactionInactiveError", function() { store.openKeyCursor(); }, + "openKeyCursor should throw if transaction is inactive"); + t.done(); + }), 0); + +}, "IDBObjectStore.openKeyCursor() - invalid inputs"); +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_put.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_put.htm new file mode 100644 index 0000000000..e277ce54a2 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_put.htm @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.put() - put with an inline key </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + record = { key: 1, property: "data" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store", { keyPath: "key" }); + + objStore.put(record); + }; + + open_rq.onsuccess = function(e) { + var rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .get(record.key); + + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result.property, record.property); + assert_equals(e.target.result.key, record.key); + t.done(); + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_put10.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_put10.htm new file mode 100644 index 0000000000..6882e8e4b5 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_put10.htm @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.put() - Attempt to call 'put' without an key parameter when the object store uses out-of-line keys </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + record = { property: "data" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var rq, + objStore = db.createObjectStore("store", { keyPath: "key" }); + + assert_throws_dom("DataError", + function() { rq = objStore.put(record); }); + + assert_equals(rq, undefined); + t.done(); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_put11.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_put11.htm new file mode 100644 index 0000000000..a5ed2db357 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_put11.htm @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.put() - Attempt to put a record where the record's key does not meet the constraints of a valid key </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + record = { key: { value: 1 }, property: "data" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var rq, + objStore = db.createObjectStore("store", { keyPath: "key" }); + + assert_throws_dom("DataError", + function() { rq = objStore.put(record); }); + + assert_equals(rq, undefined); + t.done(); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_put12.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_put12.htm new file mode 100644 index 0000000000..0693980277 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_put12.htm @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.put() - Attempt to put a record where the record's in-line key is not defined </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + record = { property: "data" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var rq, + objStore = db.createObjectStore("store", { keyPath: "key" }); + + assert_throws_dom("DataError", + function() { rq = objStore.put(record); }); + + assert_equals(rq, undefined); + t.done(); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_put13.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_put13.htm new file mode 100644 index 0000000000..8ae6561fc5 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_put13.htm @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.put() - Attempt to put a record where the out of line key provided does not meet the constraints of a valid key </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + record = { property: "data" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var rq, + objStore = db.createObjectStore("store"); + + assert_throws_dom("DataError", + function() { rq = objStore.put(record, { value: 1 }); }); + + assert_equals(rq, undefined); + t.done(); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_put14.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_put14.htm new file mode 100644 index 0000000000..bc5647f4c1 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_put14.htm @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.put() - Put a record where a value being indexed does not meet the constraints of a valid key </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + record = { key: 1, indexedProperty: { property: "data" } }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + var rq, + objStore = db.createObjectStore("store", { keyPath: "key" }); + + objStore.createIndex("index", "indexedProperty"); + + rq = objStore.put(record); + + assert_true(rq instanceof IDBRequest); + rq.onsuccess = function() { + t.done(); + } + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_put15.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_put15.htm new file mode 100644 index 0000000000..e7affaddd4 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_put15.htm @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.put() - If the transaction this IDBObjectStore belongs to has its mode set to readonly, throw ReadOnlyError</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBObjectStore-put-IDBRequest-any-value-any-key"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + db.createObjectStore("store", {keyPath:"pKey"}); + } + + open_rq.onsuccess = function (event) { + var txn = db.transaction("store", "readonly", {durability: 'relaxed'}); + var ostore = txn.objectStore("store"); + t.step(function(){ + assert_throws_dom("ReadOnlyError", function(){ + ostore.put({pKey: "primaryKey_0"}); + }); + }); + t.done(); + } +</script> + diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_put16.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_put16.htm new file mode 100644 index 0000000000..e298ba8849 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_put16.htm @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.put() - If the object store has been deleted, the implementation must throw a DOMException of type InvalidStateError</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBObjectStore-put-IDBRequest-any-value-any-key"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<div id="log"></div> +<script> + var db, + ostore, + t = async_test(); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function (event) { + db = event.target.result; + ostore = db.createObjectStore("store", {keyPath:"pKey"}); + db.deleteObjectStore("store"); + assert_throws_dom("InvalidStateError", function(){ + ostore.put({pKey: "primaryKey_0"}); + }); + t.done(); + } +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_put2.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_put2.htm new file mode 100644 index 0000000000..733e2cb154 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_put2.htm @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.put() - put with an out-of-line key </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + key = 1, + record = { property: "data" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store"); + + objStore.put(record, key); + }; + + open_rq.onsuccess = function(e) { + var rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .get(key); + + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result.property, record.property); + + t.done(); + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_put3.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_put3.htm new file mode 100644 index 0000000000..b7792bdaf1 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_put3.htm @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.put() - record with same key already exists </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, success_event, + t = async_test(), + record = { key: 1, property: "data" }, + record_put = { key: 1, property: "changed", more: ["stuff", 2] }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store", { keyPath: "key" }); + objStore.put(record); + + var rq = objStore.put(record_put); + rq.onerror = fail(t, "error on put"); + + rq.onsuccess = t.step_func(function(e) { + success_event = true; + }); + }; + + open_rq.onsuccess = function(e) { + assert_true(success_event); + + var rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .get(1); + + rq.onsuccess = t.step_func(function(e) { + var rec = e.target.result; + + assert_equals(rec.key, record_put.key); + assert_equals(rec.property, record_put.property); + assert_array_equals(rec.more, record_put.more); + + t.done(); + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_put4.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_put4.htm new file mode 100644 index 0000000000..4a59836eb6 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_put4.htm @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.put() - put where an index has unique:true specified </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + record = { key: 1, property: "data" }; + + var open_rq = createdb(async_test()); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store", { autoIncrement: true }); + objStore.createIndex("i1", "property", { unique: true }); + objStore.put(record); + + var rq = objStore.put(record); + rq.onsuccess = fail(this, "success on putting duplicate indexed record") + + rq.onerror = this.step_func(function(e) { + assert_equals(rq.error.name, "ConstraintError"); + assert_equals(e.target.error.name, "ConstraintError"); + + assert_equals(e.type, "error"); + + e.preventDefault(); + e.stopPropagation(); + }); + }; + + // Defer done, giving a spurious rq.onsuccess a chance to run + open_rq.onsuccess = function(e) { + this.done(); + } +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_put5.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_put5.htm new file mode 100644 index 0000000000..6e945e27d2 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_put5.htm @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.put() - object store's key path is an object attribute </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + record = { test: { obj: { key: 1 } }, property: "data" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store", { keyPath: "test.obj.key" }); + objStore.put(record); + }; + + open_rq.onsuccess = function(e) { + var rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .get(record.test.obj.key); + + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result.property, record.property); + + t.done(); + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_put6.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_put6.htm new file mode 100644 index 0000000000..f0b6f0b98d --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_put6.htm @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.put() - autoIncrement and inline keys </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + record = { property: "data" }, + expected_keys = [ 1, 2, 3, 4 ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store", { keyPath: "key", autoIncrement: true }); + + objStore.put(record); + objStore.put(record); + objStore.put(record); + objStore.put(record); + }; + + open_rq.onsuccess = function(e) { + var actual_keys = [], + rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .openCursor(); + + rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + if (cursor) { + actual_keys.push(cursor.value.key); + cursor.continue(); + } + else { + assert_array_equals(actual_keys, expected_keys); + t.done(); + } + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_put7.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_put7.htm new file mode 100644 index 0000000000..e41959f211 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_put7.htm @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.put() - autoIncrement and out-of-line keys </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + record = { property: "data" }, + expected_keys = [ 1, 2, 3, 4 ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store", { autoIncrement: true }); + + objStore.put(record); + objStore.put(record); + objStore.put(record); + objStore.put(record); + }; + + open_rq.onsuccess = function(e) { + var actual_keys = [], + rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .openCursor(); + + rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + if (cursor) { + actual_keys.push(cursor.key); + cursor.continue(); + } + else { + assert_array_equals(actual_keys, expected_keys); + t.done(); + } + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_put8.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_put8.htm new file mode 100644 index 0000000000..2bec639d32 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_put8.htm @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.put() - object store has autoIncrement:true and the key path is an object attribute </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + t = async_test(), + record = { property: "data" }, + expected_keys = [ 1, 2, 3, 4 ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store", { keyPath: "test.obj.key", autoIncrement: true }); + + objStore.put(record); + objStore.put(record); + objStore.put(record); + objStore.put(record); + }; + + open_rq.onsuccess = function(e) { + var actual_keys = [], + rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .openCursor(); + + rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + if (cursor) { + actual_keys.push(cursor.value.test.obj.key); + cursor.continue(); + } + else { + assert_array_equals(actual_keys, expected_keys); + t.done(); + } + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbobjectstore_put9.htm b/testing/web-platform/tests/IndexedDB/idbobjectstore_put9.htm new file mode 100644 index 0000000000..dff9415d69 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbobjectstore_put9.htm @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IDBObjectStore.put() - Attempt to put a record that does not meet the constraints of an object store's inline key requirements </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var t = async_test(), + record = { key: 1, property: "data" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + var rq, + db = e.target.result, + objStore = db.createObjectStore("store", { keyPath: "key" }); + + assert_throws_dom("DataError", + function() { rq = objStore.put(record, 1); }); + + assert_equals(rq, undefined); + t.done(); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbrequest-onupgradeneeded.htm b/testing/web-platform/tests/IndexedDB/idbrequest-onupgradeneeded.htm new file mode 100644 index 0000000000..c156dba619 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbrequest-onupgradeneeded.htm @@ -0,0 +1,146 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: UpgradeNeeded Tests</title> +<meta name="help" href="https://w3c.github.io/IndexedDB/#commit-transaction"> +<meta name="help" href="https://w3c.github.io/IndexedDB/#dom-idbfactory-open"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + +function upgradeneeded_test(upgrade_func, success_func, error_func, description) { + async_test(function(t) { + var dbName = 'db' + self.location.pathname + '-' + description; + var delete_request = indexedDB.deleteDatabase(dbName); + delete_request.onerror = t.unreached_func('deleteDatabase should not fail'); + delete_request.onsuccess = t.step_func(function() { + var open_request = indexedDB.open(dbName); + + open_request.onupgradeneeded = t.step_func(function() { + t.add_cleanup(function() { + if (open_request.result) { + open_request.result.close(), + indexedDB.deleteDatabase(dbName); + } + }); + upgrade_func(t, open_request); + }); + open_request.onsuccess = t.step_func(function() { + success_func(t, open_request); + }); + if (error_func) { + open_request.onerror = function() { error_func(t, open_request); }; + } else { + open_request.onerror = t.unreached_func('open failed'); + } + }); + }, description); +} + +(function() { + var order = []; + upgradeneeded_test( + function upgrade(t, request) { + order.push('Upgrade'); + var db = request.result; + var deleteRequest = indexedDB.deleteDatabase(db.name); + deleteRequest.onsuccess = t.step_func(function() { + assert_array_equals(order, ['Upgrade', 'Open Success']); + t.done(); + }); + deleteRequest.onerror = t.unreached_func('delete failed'); + }, + function success(t, request) { + var db = request.result; + db.close(); + order.push('Open Success'); + }, + null, + 'indexedDB.delete called from upgradeneeded handler' + ); +}()); + +(function() { + var order = []; + upgradeneeded_test( + function upgrade(t, request) { + order.push('Upgrade'); + request.transaction.abort(); + order.push('Upgrade Transaction Aborted'); + var db = request.result; + var deleteRequest = indexedDB.deleteDatabase(db.name); + deleteRequest.onsuccess = t.step_func(function() { + assert_array_equals( + order, ['Upgrade', 'Upgrade Transaction Aborted', 'Open Error']); + t.done(); + }); + deleteRequest.onerror = t.unreached_func('delete failed'); + }, + function success(t, request) { + t.unreached_func('open should not succeed'); + }, + function error_func(t, request) { + assert_array_equals(order, ['Upgrade', 'Upgrade Transaction Aborted']); + order.push('Open Error'); + }, + 'Abort transaction before deleting database in upgradeneeded handler' + ); +}()); + +(function() { + var order = []; + upgradeneeded_test( + function upgrade(t, request) { + order.push('Upgrade'); + var db = request.result; + var deleteRequest = indexedDB.deleteDatabase(db.name); + request.transaction.abort(); + order.push('Upgrade Transaction Aborted'); + deleteRequest.onsuccess = t.step_func(function() { + assert_array_equals( + order, ['Upgrade', 'Upgrade Transaction Aborted', 'Open Error']); + t.done(); + }); + deleteRequest.onerror = t.unreached_func('delete failed'); + }, + function success(t, request) { + t.unreached_func('open should not succeed'); + }, + function error_func(t, request) { + assert_array_equals(order, ['Upgrade', 'Upgrade Transaction Aborted']); + order.push('Open Error'); + }, + 'Abort transaction after deleting database in upgradeneeded event handler' + ); +}()); + +(function() { + var order = []; + upgradeneeded_test( + function upgrade(t, request) { + order.push('Upgrade'); + var db = request.result; + db.createObjectStore('store'); + request.transaction.oncomplete = t.step_func(function() { + order.push('Upgrade transaction complete'); + var txn = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + var store = txn.objectStore('store'); + store.put('value', 'key'); + txn.oncomplete = t.step_func(function() { + assert_array_equals( + order, + ['Upgrade', 'Upgrade transaction complete', 'Open Success']); + t.done(); + }); + txn.onerror = t.unreached_func('error on transaction'); + txn.onabort = t.unreached_func('aborting transaction'); + }); + }, + function success(t, request) { + order.push('Open Success'); + }, + null, + 'transaction oncomplete ordering relative to open request onsuccess' + ); +}()); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbrequest_error.html b/testing/web-platform/tests/IndexedDB/idbrequest_error.html new file mode 100644 index 0000000000..73dd13383f --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbrequest_error.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBRequest.error</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> +async_test(t => { + var open = createdb(t); + open.onupgradeneeded = t.step_func(e => { + var db = e.target.result; + db.createObjectStore('store'); + }); + open.onsuccess = t.step_func(e => { + var db = e.target.result; + var request = db.transaction('store', 'readonly', {durability: 'relaxed'}).objectStore('store').get(0); + + assert_equals(request.readyState, 'pending'); + assert_throws_dom('InvalidStateError', () => request.error, + 'IDBRequest.error should throw if request is pending'); + t.done(); + }); +}, 'IDBRequest.error throws if ready state is pending'); +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbrequest_result.html b/testing/web-platform/tests/IndexedDB/idbrequest_result.html new file mode 100644 index 0000000000..69c4853246 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbrequest_result.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBRequest.result</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> +async_test(t => { + var open = createdb(t); + open.onupgradeneeded = t.step_func(e => { + var db = e.target.result; + db.createObjectStore('store'); + }); + open.onsuccess = t.step_func(e => { + var db = e.target.result; + var request = db.transaction('store', 'readonly', {durability: 'relaxed'}).objectStore('store').get(0); + + assert_equals(request.readyState, 'pending'); + assert_throws_dom('InvalidStateError', () => request.result, + 'IDBRequest.result should throw if request is pending'); + t.done(); + }); +}, 'IDBRequest.result throws if ready state is pending'); +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbtransaction-db-SameObject.html b/testing/web-platform/tests/IndexedDB/idbtransaction-db-SameObject.html new file mode 100644 index 0000000000..5116dff5ff --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbtransaction-db-SameObject.html @@ -0,0 +1,24 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: Verify [SameObject] behavior of IDBTransaction's db attribute</title> +<meta name="help" href="https://w3c.github.io/IndexedDB/#dom-idbtransaction-db"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + (t, db, tx) => { + const store = db.createObjectStore('store'); + assert_equals(tx.db, tx.db, + 'Attribute should yield the same object each time'); + }, + (t, db) => { + const tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + assert_equals(tx.db, tx.db, + 'Attribute should yield the same object each time'); + t.done(); + }, + 'IDBTransaction.db [SameObject]' +); +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbtransaction-objectStore-exception-order.html b/testing/web-platform/tests/IndexedDB/idbtransaction-objectStore-exception-order.html new file mode 100644 index 0000000000..2ce4e13e2d --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbtransaction-objectStore-exception-order.html @@ -0,0 +1,26 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: IDBTransaction objectStore() Exception Ordering</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbtransaction-objectstore"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('s'); + }, + (t, db) => { + const tx = db.transaction('s', 'readonly', {durability: 'relaxed'}); + tx.oncomplete = t.step_func(() => { + assert_throws_dom('InvalidStateError', () => { tx.objectStore('nope'); }, + '"finished" check (InvalidStateError) should precede ' + + '"name in scope" check (NotFoundError)'); + t.done(); + }); + }, + 'IDBTransaction.objectStore exception order: InvalidStateError vs. NotFoundError' +); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbtransaction-objectStore-finished.html b/testing/web-platform/tests/IndexedDB/idbtransaction-objectStore-finished.html new file mode 100644 index 0000000000..afe5bec0f8 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbtransaction-objectStore-finished.html @@ -0,0 +1,24 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: IDBTransaction objectStore() when transaction is finished</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbtransaction-objectstore"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + (t, db) => { + db.createObjectStore('store'); + }, + (t, db) => { + const tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + tx.abort(); + assert_throws_dom('InvalidStateError', () => tx.objectStore('store'), + 'objectStore() should throw if transaction is finished'); + t.done(); + }, + 'IDBTransaction objectStore() behavior when transaction is finished' +); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbtransaction-oncomplete.htm b/testing/web-platform/tests/IndexedDB/idbtransaction-oncomplete.htm new file mode 100644 index 0000000000..722802c1cc --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbtransaction-oncomplete.htm @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<title>IDBTransaction - complete event</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, store, + t = async_test(), + open_rq = createdb(t), + stages = []; + + open_rq.onupgradeneeded = function(e) { + stages.push("upgradeneeded"); + + db = e.target.result; + store = db.createObjectStore('store'); + + e.target.transaction.oncomplete = function() { + stages.push("complete"); + }; + }; + + open_rq.onsuccess = function(e) { + stages.push("success"); + + // Making a totally new transaction to check + db.transaction('store', 'readonly', {durability: 'relaxed'}).objectStore('store').count().onsuccess = t.step_func(function(e) { + assert_array_equals(stages, [ "upgradeneeded", + "complete", + "success" ]); + t.done(); + }); + // XXX: Make one with real transactions, not only open() versionchange one + + /*db.transaction.objectStore('store').openCursor().onsuccess = function(e) { + stages.push("opencursor1"); + } + + store.openCursor().onsuccess = function(e) { + stages.push("opencursor2"); + } + + e.target.transaction.objectStore('store').openCursor().onsuccess = function(e) { + stages.push("opencursor3"); + } + */ + } + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/idbtransaction.htm b/testing/web-platform/tests/IndexedDB/idbtransaction.htm new file mode 100644 index 0000000000..d08e170eb6 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbtransaction.htm @@ -0,0 +1,63 @@ +<!DOCTYPE html> +<title>IDBTransaction</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> +async_test(function(t) { + var dbname = "idbtransaction-" + document.location + t.name; + indexedDB.deleteDatabase(dbname); + var open_rq = indexedDB.open(dbname); + + open_rq.onblocked = t.unreached_func('open_rq.onblocked'); + open_rq.onerror = t.unreached_func('open_rq.onerror'); + + open_rq.onupgradeneeded = t.step_func(function(e) { + t.add_cleanup(function() { + open_rq.onerror = function(e) { + e.preventDefault(); + }; + open_rq.result.close(); + indexedDB.deleteDatabase(open_rq.result.name); + }); + + assert_equals(e.target, open_rq, "e.target is reusing the same IDBOpenDBRequest"); + assert_equals(e.target.transaction, open_rq.transaction, "IDBOpenDBRequest.transaction"); + + assert_true(e.target.transaction instanceof IDBTransaction, "transaction instanceof IDBTransaction"); + t.done(); + }); + +}, document.title + " - request gotten by the handler"); + +async_test(function(t) { + var dbname = "idbtransaction-" + document.location + t.name; + indexedDB.deleteDatabase(dbname); + var open_rq = indexedDB.open(dbname); + + assert_equals(open_rq.transaction, null, "IDBOpenDBRequest.transaction"); + assert_equals(open_rq.source, null, "IDBOpenDBRequest.source"); + assert_equals(open_rq.readyState, "pending", "IDBOpenDBRequest.readyState"); + + assert_true(open_rq instanceof IDBOpenDBRequest, "open_rq instanceof IDBOpenDBRequest"); + assert_equals(open_rq + "", "[object IDBOpenDBRequest]", "IDBOpenDBRequest (open_rq)"); + + open_rq.onblocked = t.unreached_func('open_rq.onblocked'); + open_rq.onerror = t.unreached_func('open_rq.onerror'); + + open_rq.onupgradeneeded = t.step_func(function() { + t.add_cleanup(function() { + open_rq.onerror = function(e) { + e.preventDefault(); + }; + open_rq.result.close(); + indexedDB.deleteDatabase(open_rq.result.name); + }); + t.done(); + }); + +}, document.title + " - request returned by open()"); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbtransaction_abort.htm b/testing/web-platform/tests/IndexedDB/idbtransaction_abort.htm new file mode 100644 index 0000000000..3c64352b08 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbtransaction_abort.htm @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>IDBTransaction - abort</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var db, aborted, + t = async_test(), + record = { indexedProperty: "bar" }; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var txn = e.target.transaction, + objStore = db.createObjectStore("store"); + + objStore.add(record, 1); + objStore.add(record, 2); + var index = objStore.createIndex("index", "indexedProperty", { unique: true }); + + assert_true(index instanceof IDBIndex, "IDBIndex"); + + e.target.transaction.onabort = t.step_func(function(e) { + aborted = true; + assert_equals(e.type, "abort", "event type"); + }); + + db.onabort = function(e) { + assert_true(aborted, "transaction.abort event has fired"); + t.done(); + }; + + e.target.transaction.oncomplete = fail(t, "got complete, expected abort"); + }; + +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/idbtransaction_objectStoreNames.html b/testing/web-platform/tests/IndexedDB/idbtransaction_objectStoreNames.html new file mode 100644 index 0000000000..efec1d2470 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbtransaction_objectStoreNames.html @@ -0,0 +1,165 @@ +<!DOCTYPE html> +<title>IndexedDB: IDBTransaction.objectStoreNames attribute</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +function with_stores_test(store_names, open_func, description) { + indexeddb_test(function(t, db, tx) { + store_names.forEach(function(name) { + db.createObjectStore(name); + }); + }, open_func, description); +} + +indexeddb_test(function(t, db, tx) { + assert_array_equals(tx.objectStoreNames, [], + 'transaction objectStoreNames should be empty'); + assert_array_equals(db.objectStoreNames, tx.objectStoreNames, + 'connection and transacton objectStoreNames should match'); + + db.createObjectStore('s1'); + assert_array_equals(tx.objectStoreNames, ['s1'], + 'transaction objectStoreNames should have new store'); + assert_array_equals(db.objectStoreNames, tx.objectStoreNames, + 'connection and transacton objectStoreNames should match'); + + db.createObjectStore('s3'); + assert_array_equals(tx.objectStoreNames, ['s1', 's3'], + 'transaction objectStoreNames should have new store'); + assert_array_equals(db.objectStoreNames, tx.objectStoreNames, + 'connection and transacton objectStoreNames should match'); + + db.createObjectStore('s2'); + assert_array_equals(tx.objectStoreNames, ['s1', 's2', 's3'], + 'transaction objectStoreNames should be sorted'); + assert_array_equals(db.objectStoreNames, tx.objectStoreNames, + 'connection and transacton objectStoreNames should match'); + + db.deleteObjectStore('s1'); + assert_array_equals(tx.objectStoreNames, ['s2', 's3'], + 'transaction objectStoreNames should be updated after delete'); + assert_array_equals(db.objectStoreNames, tx.objectStoreNames, + 'connection and transacton objectStoreNames should match'); +}, function(t, db) { + t.done(); +}, 'IDBTransaction.objectStoreNames - during upgrade transaction'); + +(function() { + var saved_tx; + indexeddb_test(function(t, db, tx) { + saved_tx = tx; + db.createObjectStore('s2'); + db.createObjectStore('s3'); + }, function(t, db) { + db.close(); + var open2 = indexedDB.open(db.name, db.version + 1); + open2.onerror = t.unreached_func('open should succeed'); + open2.onupgradeneeded = t.step_func(function() { + var db2 = open2.result; + var tx2 = open2.transaction; + assert_array_equals(tx2.objectStoreNames, ['s2', 's3'], + 'transaction should have previous stores in scope'); + assert_array_equals(db2.objectStoreNames, tx2.objectStoreNames, + 'connection and transacton objectStoreNames should match'); + + db2.createObjectStore('s4'); + assert_array_equals(tx2.objectStoreNames, ['s2', 's3', 's4'], + 'transaction should have new store in scope'); + assert_array_equals(db2.objectStoreNames, tx2.objectStoreNames, + 'connection and transacton objectStoreNames should match'); + + assert_array_equals(saved_tx.objectStoreNames, ['s2', 's3'], + 'previous transaction objectStoreNames should be unchanged'); + assert_array_equals(db.objectStoreNames, saved_tx.objectStoreNames, + 'connection and transaction objectStoreNames should match'); + db2.close(); + t.done(); + }); + }, 'IDBTransaction.objectStoreNames - value after close'); +}()); + +with_stores_test(['s1', 's2'], function(t, db) { + assert_array_equals(db.transaction('s1', 'readonly', {durability: 'relaxed'}).objectStoreNames, ['s1'], + 'transaction should have one store in scope'); + assert_array_equals(db.transaction(['s1', 's2']).objectStoreNames, + ['s1', 's2'], + 'transaction should have two stores in scope'); + t.done(); +}, 'IDBTransaction.objectStoreNames - transaction scope'); + +with_stores_test(['s1', 's2'], function(t, db) { + var tx = db.transaction(['s1', 's2'], 'readwrite'); + tx.objectStore('s1').put(0, 0); + tx.onabort = t.unreached_func('transaction should complete'); + tx.oncomplete = t.step_func(function() { + assert_array_equals(tx.objectStoreNames, ['s1', 's2'], + 'objectStoreNames should return scope after transaction commits'); + t.done(); + }); +}, 'IDBTransaction.objectStoreNames - value after commit'); + +with_stores_test(['s1', 's2'], function(t, db) { + var tx = db.transaction(['s1', 's2'], 'readwrite'); + tx.objectStore('s1').put(0, 0); + tx.objectStore('s1').add(0, 0); + tx.oncomplete = t.unreached_func('transaction should abort'); + tx.onabort = t.step_func(function() { + assert_array_equals(tx.objectStoreNames, ['s1', 's2'], + 'objectStoreNames should return scope after transaction aborts'); + t.done(); + }); +}, 'IDBTransaction.objectStoreNames - value after abort'); + +with_stores_test(['s1', 's2', 's3'], function(t, db) { + assert_array_equals(db.transaction(['s3', 's2', 's1']).objectStoreNames, + ['s1', 's2', 's3'], + 'transaction objectStoreNames should be sorted'); + t.done(); +}, 'IDBTransaction.objectStoreNames - sorting'); + +with_stores_test(['s1', 's2'], function(t, db) { + assert_array_equals( + db.transaction(['s2', 's1', 's2']).objectStoreNames, + ['s1', 's2'], + 'transaction objectStoreNames should not have duplicates'); + t.done(); +}, 'IDBTransaction.objectStoreNames - no duplicates'); + +var unusual_names = [ + '', // empty string + + '\x00', // U+0000 NULL + '\xFF', // U+00FF LATIN SMALL LETTER Y WITH DIAERESIS + + '1', // basic ASCII + '12', // basic ASCII + '123', // basic ASCII + 'abc', // basic ASCII + 'ABC', // basic ASCII + + '\xA2', // U+00A2 CENT SIGN + '\u6C34', // U+6C34 CJK UNIFIED IDEOGRAPH (water) + '\uD834\uDD1E', // U+1D11E MUSICAL SYMBOL G-CLEF (UTF-16 surrogate pair) + '\uFFFD', // U+FFFD REPLACEMENT CHARACTER + + '\uD800', // UTF-16 surrogate lead + '\uDC00', // UTF-16 surrogate trail +]; +unusual_names.sort(); + +indexeddb_test(function(t, db, tx) { + unusual_names.slice().reverse().forEach(function(name) { + db.createObjectStore(name); + }); + assert_array_equals(tx.objectStoreNames, unusual_names, + 'transaction should have names sorted'); +}, function(t, db) { + var tx = db.transaction(unusual_names.slice().reverse().concat(unusual_names)); + assert_array_equals(tx.objectStoreNames, unusual_names, + 'transaction should have names sorted with no duplicates'); + t.done(); +}, 'IDBTransaction.objectStoreNames - unusual names'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idbversionchangeevent.htm b/testing/web-platform/tests/IndexedDB/idbversionchangeevent.htm new file mode 100644 index 0000000000..9ea7e6d491 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbversionchangeevent.htm @@ -0,0 +1,87 @@ +<!DOCTYPE html> +<title>IDBVersionChangeEvent fired in upgradeneeded, versionchange and deleteDatabase</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#versionchange--transaction-steps"> +<link rel=assert title="Fire a versionchange event at each object in openDatabases that is open. The event must not be fired on objects which has the closePending flag set. The event must use the IDBVersionChangeEvent interface and have the oldVersion property set to db's version and have the newVersion property set to version."> +<link rel=assert title="Fire a upgradeneeded event targeted at request. The event must use the IDBVersionChangeEvent interface and have the oldVersion property set to old version and have the newVersion property set to version."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<div id="log"></div> + +<script> + + var db, + t = async_test(), + dbname = location + '-' + t.name; + + t.step(function() { + indexedDB.deleteDatabase(dbname); + + var openrq = indexedDB.open(dbname, 3); + + openrq.onupgradeneeded = t.step_func(function(e) { + assert_equals(e.oldVersion, 0, "old version (upgradeneeded)"); + assert_equals(e.newVersion, 3, "new version (upgradeneeded)"); + assert_true(e instanceof IDBVersionChangeEvent, "upgradeneeded instanceof IDBVersionChangeEvent"); + }); + + openrq.onsuccess = t.step_func(function(e) { + db = e.target.result; + + db.onversionchange = t.step_func(function(e) { + assert_equals(e.oldVersion, 3, "old version (versionchange)"); + assert_equals(e.newVersion, null, "new version (versionchange)"); + assert_true(e instanceof IDBVersionChangeEvent, "versionchange instanceof IDBVersionChangeEvent"); + db.close(); + }); + + // Errors + db.onerror = fail(t, "db.error"); + db.onabort = fail(t, "db.abort"); + + setTimeout(t.step_func(deleteDB), 10); + }); + + // Errors + openrq.onerror = fail(t, "open.error"); + openrq.onblocked = fail(t, "open.blocked"); + + }); + + function deleteDB (e) { + var deleterq = indexedDB.deleteDatabase(dbname); + + deleterq.onsuccess = t.step_func(function(e) { + assert_equals(e.result, undefined, "result (delete.success for nonexistent db)"); + assert_equals(e.oldVersion, 3, "old version (delete.success)"); + assert_equals(e.newVersion, null, "new version (delete.success)"); + assert_true(e instanceof IDBVersionChangeEvent, "delete.success instanceof IDBVersionChangeEvent"); + + setTimeout(deleteNonExistentDB, 10); + }); + + // Errors + deleterq.onerror = fail(t, "delete.error"); + deleterq.onblocked = fail(t, "delete.blocked"); + } + + function deleteNonExistentDB (e) { + var deleterq = indexedDB.deleteDatabase('db-does-not-exist'); + + deleterq.onsuccess = t.step_func(function(e) { + assert_equals(e.result, undefined, "result (delete.success for nonexistent db)"); + assert_equals(e.oldVersion, 0, "old version (delete.success for nonexistent db)"); + assert_equals(e.newVersion, null, "new version (delete.success for nonexistent db)"); + assert_true(e instanceof IDBVersionChangeEvent, "delete.success instanceof IDBVersionChangeEvent"); + + setTimeout(function() { t.done(); }, 10); + }); + + // Errors + deleterq.onerror = fail(t, "delete.error"); + deleterq.onblocked = fail(t, "delete.blocked"); + } + +</script> diff --git a/testing/web-platform/tests/IndexedDB/idlharness.any.js b/testing/web-platform/tests/IndexedDB/idlharness.any.js new file mode 100644 index 0000000000..12c304589a --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idlharness.any.js @@ -0,0 +1,25 @@ +// META: global=window,worker +// META: script=/resources/WebIDLParser.js +// META: script=/resources/idlharness.js +// META: timeout=long + +idl_test( + ['IndexedDB'], + ['html', 'dom'], + idl_array => { + idl_array.add_objects({ + IDBCursor: [], + IDBCursorWithValue: [], + IDBDatabase: [], + IDBFactory: [self.indexedDB], + IDBIndex: [], + IDBKeyRange: [IDBKeyRange.only(0)], + IDBObjectStore: [], + IDBOpenDBRequest: [], + IDBRequest: [], + IDBTransaction: [], + IDBVersionChangeEvent: ['new IDBVersionChangeEvent("type")'], + DOMStringList: [], + }); + } +); diff --git a/testing/web-platform/tests/IndexedDB/index_sort_order.htm b/testing/web-platform/tests/IndexedDB/index_sort_order.htm new file mode 100644 index 0000000000..2f6d474ea6 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/index_sort_order.htm @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Verify key sort order in an index is 'number < Date < DOMString' </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + d = new Date(), + t = async_test(), + records = [ { foo: d }, + { foo: "test" }, + { foo: 1 }, + { foo: 2.55 } ], + expectedKeyOrder = [ 1, 2.55, d.valueOf(), "test" ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store", { autoIncrement: true }); + objStore.createIndex("index", "foo"); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var actual_keys = [], + rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .index("index") + .openCursor(); + + rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + if (cursor) { + actual_keys.push(cursor.key.valueOf()); + cursor.continue(); + } + else { + assert_array_equals(actual_keys, expectedKeyOrder); + t.done(); + } + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/interleaved-cursors-large.html b/testing/web-platform/tests/IndexedDB/interleaved-cursors-large.html new file mode 100644 index 0000000000..9e17f9e112 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/interleaved-cursors-large.html @@ -0,0 +1,12 @@ +<!doctype html> +<meta charset="utf-8"> +<meta name="timeout" content="long"> +<title>IndexedDB: Interleaved iteration of multiple cursors</title> +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script src="resources/interleaved-cursors-common.js"></script> +<script> +cursorTest(250); +</script> diff --git a/testing/web-platform/tests/IndexedDB/interleaved-cursors-small.html b/testing/web-platform/tests/IndexedDB/interleaved-cursors-small.html new file mode 100644 index 0000000000..2751113f5f --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/interleaved-cursors-small.html @@ -0,0 +1,14 @@ +<!doctype html> +<meta charset="utf-8"> +<meta name="timeout" content="long"> +<title>IndexedDB: Interleaved iteration of multiple cursors</title> +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script src="resources/interleaved-cursors-common.js"></script> +<script> +cursorTest(1); +cursorTest(10); +cursorTest(100); +</script> diff --git a/testing/web-platform/tests/IndexedDB/key-conversion-exceptions.htm b/testing/web-platform/tests/IndexedDB/key-conversion-exceptions.htm new file mode 100644 index 0000000000..9fdab58eb1 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/key-conversion-exceptions.htm @@ -0,0 +1,199 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: Exceptions thrown during key conversion</title> +<meta name=timeout content=long> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +// Convenience function for tests that only need to run code in onupgradeneeded. +function indexeddb_upgrade_only_test(upgrade_callback, description) { + indexeddb_test(upgrade_callback, t => { t.done(); }, description); +} + +// Key that throws during conversion. +function throwing_key(name) { + var throws = []; + throws.length = 1; + const err = new Error('throwing from getter'); + err.name = name; + Object.defineProperty(throws, '0', {get: function() { + throw err; + }, enumerable: true}); + return [throws, err]; +} + +var valid_key = []; +var invalid_key = {}; + +// Calls method on receiver with the specified number of args (default 1) +// and asserts that the method fails appropriately (rethrowing if +// conversion throws, or DataError if not a valid key), and that +// the first argument is fully processed before the second argument +// (if appropriate). +function check_method(receiver, method, args) { + args = args || 1; + if (args < 2) { + const [key, err] = throwing_key('getter'); + assert_throws_exactly(err, () => { + receiver[method](key); + }, 'key conversion with throwing getter should rethrow'); + + assert_throws_dom('DataError', () => { + receiver[method](invalid_key); + }, 'key conversion with invalid key should throw DataError'); + } else { + const [key1, err1] = throwing_key('getter 1'); + const [key2, err2] = throwing_key('getter 2'); + assert_throws_exactly(err1, () => { + receiver[method](key1, key2); + }, 'first key conversion with throwing getter should rethrow'); + + assert_throws_dom('DataError', () => { + receiver[method](invalid_key, key2); + }, 'first key conversion with invalid key should throw DataError'); + + assert_throws_exactly(err2, () => { + receiver[method](valid_key, key2); + }, 'second key conversion with throwing getter should rethrow'); + + assert_throws_dom('DataError', () => { + receiver[method](valid_key, invalid_key); + }, 'second key conversion with invalid key should throw DataError'); + } +} + +// Static key comparison utility on IDBFactory. +test(t => { + check_method(indexedDB, 'cmp', 2); +}, 'IDBFactory cmp() static with throwing/invalid keys'); + +// Continue methods on IDBCursor. +indexeddb_upgrade_only_test((t, db) => { + var store = db.createObjectStore('store'); + store.put('a', 1).onerror = t.unreached_func('put should succeed'); + + var request = store.openCursor(); + request.onerror = t.unreached_func('openCursor should succeed'); + request.onsuccess = t.step_func(() => { + var cursor = request.result; + assert_not_equals(cursor, null, 'cursor should find a value'); + check_method(cursor, 'continue'); + }); +}, 'IDBCursor continue() method with throwing/invalid keys'); + +indexeddb_upgrade_only_test((t, db) => { + var store = db.createObjectStore('store'); + var index = store.createIndex('index', 'prop'); + store.put({prop: 'a'}, 1).onerror = t.unreached_func('put should succeed'); + + var request = index.openCursor(); + request.onerror = t.unreached_func('openCursor should succeed'); + request.onsuccess = t.step_func(() => { + var cursor = request.result; + assert_not_equals(cursor, null, 'cursor should find a value'); + + check_method(cursor, 'continuePrimaryKey', 2); + }); +}, null, 'IDBCursor continuePrimaryKey() method with throwing/invalid keys'); + +// Mutation methods on IDBCursor. +indexeddb_upgrade_only_test((t, db) => { + var store = db.createObjectStore('store', {keyPath: 'prop'}); + store.put({prop: 1}).onerror = t.unreached_func('put should succeed'); + + var request = store.openCursor(); + request.onerror = t.unreached_func('openCursor should succeed'); + request.onsuccess = t.step_func(() => { + var cursor = request.result; + assert_not_equals(cursor, null, 'cursor should find a value'); + + var value = {}; + var err; + [value.prop, err] = throwing_key('getter'); + assert_throws_exactly(err, () => { + cursor.update(value); + }, 'throwing getter should rethrow during clone'); + + // Throwing from the getter during key conversion is + // not possible since (1) a clone is used, (2) only own + // properties are cloned, and (3) only own properties + // are used for key path evaluation. + + value.prop = invalid_key; + assert_throws_dom('DataError', () => { + cursor.update(value); + }, 'key conversion with invalid key should throw DataError'); + }); +}, 'IDBCursor update() method with throwing/invalid keys'); + +// Static constructors on IDBKeyRange +['only', 'lowerBound', 'upperBound'].forEach(method => { + test(t => { + check_method(IDBKeyRange, method); + }, 'IDBKeyRange ' + method + '() static with throwing/invalid keys'); +}); + +test(t => { + check_method(IDBKeyRange, 'bound', 2); +}, 'IDBKeyRange bound() static with throwing/invalid keys'); + +// Insertion methods on IDBObjectStore. +['add', 'put'].forEach(method => { + indexeddb_upgrade_only_test((t, db) => { + var out_of_line = db.createObjectStore('out-of-line keys'); + var in_line = db.createObjectStore('in-line keys', {keyPath: 'prop'}); + var [key, err] = throwing_key('getter'); + assert_throws_exactly(err, () => { + out_of_line[method]('value', key); + }, 'key conversion with throwing getter should rethrow'); + + assert_throws_dom('DataError', () => { + out_of_line[method]('value', invalid_key); + }, 'key conversion with invalid key should throw DataError'); + + var value = {}; + [value.prop, err] = throwing_key('getter'); + assert_throws_exactly(err, () => { + in_line[method](value); + }, 'throwing getter should rethrow during clone'); + + // Throwing from the getter during key conversion is + // not possible since (1) a clone is used, (2) only own + // properties are cloned, and (3) only own properties + // are used for key path evaluation. + + value.prop = invalid_key; + assert_throws_dom('DataError', () => { + in_line[method](value); + }, 'key conversion with invalid key should throw DataError'); + }, `IDBObjectStore ${method}() method with throwing/invalid keys`); +}); + +// Generic (key-or-key-path) methods on IDBObjectStore. +[ + 'delete', 'get', 'getKey', 'getAll', 'getAllKeys', 'count', 'openCursor', + 'openKeyCursor' +].forEach(method => { + indexeddb_upgrade_only_test((t, db) => { + var store = db.createObjectStore('store'); + + check_method(store, method); + }, `IDBObjectStore ${method}() method with throwing/invalid keys`); +}); + +// Generic (key-or-key-path) methods on IDBIndex. +[ + 'get', 'getKey', 'getAll', 'getAllKeys', 'count', 'openCursor', + 'openKeyCursor' +].forEach(method => { + indexeddb_upgrade_only_test((t, db) => { + var store = db.createObjectStore('store'); + var index = store.createIndex('index', 'keyPath'); + + check_method(index, method); + }, `IDBIndex ${method}() method with throwing/invalid keys`); +}); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/key_invalid.htm b/testing/web-platform/tests/IndexedDB/key_invalid.htm new file mode 100644 index 0000000000..cf649b07d0 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/key_invalid.htm @@ -0,0 +1,131 @@ +<!DOCTYPE html> +<!-- Submitted from TestTWF Paris --> +<meta charset=utf-8"> +<title>Invalid key</title> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#key-construct"> +<link rel=assert title="A value is said to be a valid key if it is one of the following types: Array JavaScript objects [ECMA-262], DOMString [WEBIDL], Date [ECMA-262] or float [WEBIDL]. However Arrays are only valid keys if every item in the array is defined and is a valid key (i.e. sparse arrays can not be valid keys) and if the Array doesn't directly or indirectly contain itself. Any non-numeric properties are ignored, and thus does not affect whether the Array is a valid key. Additionally, if the value is of type float, it is only a valid key if it is not NaN, and if the value is of type Date it is only a valid key if its [[PrimitiveValue]] internal property, as defined by [ECMA-262], is not NaN. Conforming user agents must support all valid keys as keys."> +<!-- original author --> +<link rel=author href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<!-- some additions by Baptiste Fontaine (batifon@yahoo.fr) --> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var db = createdb_for_multiple_tests(), + // cache for ObjectStores + objStore = null, + objStore2 = null; + + function is_cloneable(o) { + try { + self.postMessage(o, '*'); + return true; + } catch (ex) { + return false; + } + } + + function invalid_key(desc, key) { + var t = async_test(document.title + " - " + desc); + + // set the current test, and run it + db.setTest(t).onupgradeneeded = function(e) { + objStore = objStore || e.target.result.createObjectStore("store"); + assert_throws_dom('DataError', function() { + objStore.add("value", key); + }); + + if (is_cloneable(key)) { + objStore2 = objStore2 || e.target.result.createObjectStore("store2", { keyPath: ["x", "keypath"] }); + assert_throws_dom('DataError', function() { + objStore2.add({ x: "value", keypath: key }); + }); + } + this.done(); + }; + } + + var fake_array = { + length : 0, + constructor : Array + }; + + var ArrayClone = function(){}; + ArrayClone.prototype = Array; + var ArrayClone_instance = new ArrayClone(); + + // booleans + invalid_key( 'true' , true ); + invalid_key( 'false' , false ); + + // null/NaN/undefined + invalid_key( 'null' , null ); + invalid_key( 'NaN' , NaN ); + invalid_key( 'undefined' , undefined ); + invalid_key( 'undefined2'); + + // functions + invalid_key( 'function() {}', function(){} ); + + // objects + invalid_key( '{}' , {} ); + invalid_key( '{ obj: 1 }' , { obj: 1 }); + invalid_key( 'Math' , Math ); + invalid_key( 'window' , window ); + invalid_key( '{length:0,constructor:Array}' , fake_array ); + invalid_key( 'Array clone’s instance' , ArrayClone_instance ); + invalid_key( 'Array (object)' , Array ); + invalid_key( 'String (object)' , String ); + invalid_key( 'new String()' , new String() ); + invalid_key( 'new Number()' , new Number() ); + invalid_key( 'new Boolean()' , new Boolean() ); + + // arrays + invalid_key( '[{}]' , [{}] ); + invalid_key( '[[], [], [], [[ Date ]]]' , [ [], [], [], [[ Date ]] ] ); + invalid_key( '[undefined]' , [undefined] ); + invalid_key( '[,1]' , [,1] ); + + invalid_key( 'document.getElements' + +'ByTagName("script")' , document.getElementsByTagName("script") ); + + // dates + invalid_key( 'new Date(NaN)' , new Date(NaN) ); + invalid_key( 'new Date(Infinity)' , new Date(Infinity) ); + + // regexes + invalid_key( '/foo/' , /foo/ ); + invalid_key( 'new RegExp()' , new RegExp() ); + + var sparse = []; + sparse[10] = "hei"; + invalid_key('sparse array', sparse); + + var sparse2 = []; + sparse2[0] = 1; + sparse2[""] = 2; + sparse2[2] = 3; + invalid_key('sparse array 2', sparse2); + + invalid_key('[[1], [3], [7], [[ sparse array ]]]', [ [1], [3], [7], [[ sparse2 ]] ]); + + // sparse3 + invalid_key( '[1,2,3,,]', [1,2,3,,] ); + + var recursive = []; + recursive.push(recursive); + invalid_key('array directly contains self', recursive); + + var recursive2 = []; + recursive2.push([recursive2]); + invalid_key('array indirectly contains self', recursive2); + + var recursive3 = [recursive]; + invalid_key('array member contains self', recursive3); + + invalid_key('proxy of an array', new Proxy([1,2,3], {})); + +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/key_valid.html b/testing/web-platform/tests/IndexedDB/key_valid.html new file mode 100644 index 0000000000..678b4c4f4e --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/key_valid.html @@ -0,0 +1,75 @@ +<!DOCTYPE html> +<!-- Submitted from TestTWF Paris --> +<meta charset=utf-8"> +<meta name="timeout" content="long"> +<title>Valid key</title> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#key-construct"> +<link rel=assert title="A value is said to be a valid key if it is one of the following types: Array JavaScript objects [ECMA-262], DOMString [WEBIDL], Date [ECMA-262] or float [WEBIDL]. However Arrays are only valid keys if every item in the array is defined and is a valid key (i.e. sparse arrays can not be valid keys) and if the Array doesn't directly or indirectly contain itself. Any non-numeric properties are ignored, and thus does not affect whether the Array is a valid key. Additionally, if the value is of type float, it is only a valid key if it is not NaN, and if the value is of type Date it is only a valid key if its [[PrimitiveValue]] internal property, as defined by [ECMA-262], is not NaN. Conforming user agents must support all valid keys as keys."> +<link rel=author href="mailto:batifon@yahoo.fr" title="Baptiste Fontaine"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + function valid_key(desc, key) { + var db; + var t = async_test(document.title + " - " + desc); + var open_rq = createdb(t); + + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + + store = db.createObjectStore("store"); + assert_true(store.add('value', key) instanceof IDBRequest); + + store2 = db.createObjectStore("store2", { keyPath: ["x", "keypath"] }); + assert_true(store2.add({ x: 'v', keypath: key }) instanceof IDBRequest); + }; + open_rq.onsuccess = function(e) { + var rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .get(key) + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result, 'value') + var rq = db.transaction("store2", "readonly", {durability: 'relaxed'}) + .objectStore("store2") + .get(['v', key]) + rq.onsuccess = t.step_func(function(e) { + assert_equals(e.target.result.x, 'v'); + assert_key_equals(e.target.result.keypath, key); + t.done() + }) + }) + } + } + + // Date + valid_key( 'new Date()' , new Date() ); + valid_key( 'new Date(0)' , new Date(0) ); + + // Array + valid_key( '[]' , [] ); + valid_key( 'new Array()' , new Array() ); + + valid_key( '["undefined"]' , ['undefined'] ); + + // Float + valid_key( 'Infinity' , Infinity ); + valid_key( '-Infinity' , -Infinity ); + valid_key( '0' , 0 ); + valid_key( '1.5' , 1.5 ); + valid_key( '3e38' , 3e38 ); + valid_key( '3e-38' , 3e38 ); + + // String + valid_key( '"foo"' , "foo" ); + valid_key( '"\\n"' , "\n" ); + valid_key( '""' , "" ); + valid_key( '"\\""' , "\"" ); + valid_key( '"\\u1234"' , "\u1234" ); + valid_key( '"\\u0000"' , "\u0000" ); + valid_key( '"NaN"' , "NaN" ); + +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/keygenerator-constrainterror.htm b/testing/web-platform/tests/IndexedDB/keygenerator-constrainterror.htm new file mode 100644 index 0000000000..6d61ea2f06 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/keygenerator-constrainterror.htm @@ -0,0 +1,73 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Keygenerator ConstraintError when using same id as already generated</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + + var db, + t = async_test(), + objects = [1, null, {id: 2}, null, 2.00001, 5, null, {id: 6} ], + expected = [1, 2, 2.00001, 3, 5, 6], + errors = 0; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store", { keyPath: "id", autoIncrement: true }); + + for (var i = 0; i < objects.length; i++) + { + if (objects[i] === null) + { + objStore.add({}); + } + else if (typeof objects[i] === "object") + { + var rq = objStore.add(objects[i]) + rq.yeh = objects[i]; + rq.onerror = t.step_func(function(e) { + errors++; + + assert_equals(e.target.error.name, "ConstraintError"); + assert_equals(e.type, "error"); + + e.stopPropagation(); + e.preventDefault(); + }); + rq.onsuccess = t.step_func(function(e) { + assert_unreached("Got rq.success when adding duplicate id " + objects[i]); + }); + } + else + objStore.add({ id: objects[i] }); + } + }; + + open_rq.onsuccess = function(e) { + var actual_keys = [], + rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .openCursor(); + + rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + if (cursor) { + actual_keys.push(cursor.key.valueOf()); + cursor.continue(); + } + else { + assert_equals(errors, 2, "expected ConstraintError's"); + assert_array_equals(actual_keys, expected, "keygenerator array"); + t.done(); + } + }); + }; + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/keygenerator-explicit.html b/testing/web-platform/tests/IndexedDB/keygenerator-explicit.html new file mode 100644 index 0000000000..e2d65e14b6 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/keygenerator-explicit.html @@ -0,0 +1,146 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Key Generator behavior with explicit keys generator overflow</title> +<link rel=help href="https://w3c.github.io/IndexedDB/#key-generator-construct"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +function big_key_test(key, description) { + indexeddb_test( + (t, db) => { + assert_equals(indexedDB.cmp(key, key), 0, 'Key is valid'); + + db.createObjectStore('store', {autoIncrement: true}); + }, + (t, db) => { + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + const value = 0; + let request; + + request = store.put(value); + request.onerror = t.unreached_func('put should succeed'); + request.onsuccess = t.step_func(e => { + assert_equals(e.target.result, 1, + 'Key generator should initially be 1'); + }); + + request = store.put(value); + request.onerror = t.unreached_func('put should succeed'); + request.onsuccess = t.step_func(e => { + assert_equals(e.target.result, 2, + 'Key generator should increment'); + }); + + request = store.put(value, 1000); + request.onerror = t.unreached_func('put should succeed'); + request.onsuccess = t.step_func(e => { + assert_equals(e.target.result, 1000, + 'Explicit key should be used'); + }); + + request = store.put(value); + request.onerror = t.unreached_func('put should succeed'); + request.onsuccess = t.step_func(e => { + assert_equals(e.target.result, 1001, + 'Key generator should have updated'); + }); + + request = store.put(value, key); + request.onerror = t.unreached_func('put should succeed'); + request.onsuccess = t.step_func(e => { + assert_equals(e.target.result, key, + 'Explicit key should be used'); + }); + + if (key >= 0) { + // Large positive values will max out the key generator, so it + // can no longer produce keys. + request = store.put(value); + request.onsuccess = t.unreached_func('put should fail'); + request.onerror = t.step_func(e => { + e.preventDefault(); + assert_equals(e.target.error.name, 'ConstraintError', + 'Key generator should have returned failure'); + }); + } else { + // Large negative values are always lower than the key generator's + // current number, so have no effect on the generator. + request = store.put(value); + request.onerror = t.unreached_func('put should succeed'); + request.onsuccess = t.step_func(e => { + assert_equals(e.target.result, 1002, + 'Key generator should have updated'); + }); + } + + request = store.put(value, 2000); + request.onerror = t.unreached_func('put should succeed'); + request.onsuccess = t.step_func(e => { + assert_equals(e.target.result, 2000, + 'Explicit key should be used'); + }); + + tx.onabort = t.step_func(() => { + assert_unreached(`Transaction aborted: ${tx.error.message}`); + }); + tx.oncomplete = t.step_func(() => { t.done(); }); + }, + description); +} + +[ + { + key: Number.MAX_SAFE_INTEGER + 1, + description: '53 bits' + }, + { + key: Math.pow(2, 60), + description: 'greater than 53 bits, less than 64 bits' + }, + { + key: -Math.pow(2, 60), + description: 'greater than 53 bits, less than 64 bits (negative)' + }, + { + key: Math.pow(2, 63), + description: '63 bits' + }, + { + key: -Math.pow(2, 63), + description: '63 bits (negative)' + }, + { + key: Math.pow(2, 64), + description: '64 bits' + }, + { + key: -Math.pow(2, 64), + description: '64 bits (negative)' + }, + { + key: Math.pow(2, 70), + description: 'greater than 64 bits, but still finite' + }, + { + key: -Math.pow(2, 70), + description: 'greater than 64 bits, but still finite (negative)' + }, + { + key: Infinity, + description: 'equal to Infinity' + }, + { + key: -Infinity, + description: 'equal to -Infinity' + } +].forEach(function(testCase) { + big_key_test(testCase.key, + `Key generator vs. explicit key ${testCase.description}`); +}); + + + +</script> diff --git a/testing/web-platform/tests/IndexedDB/keygenerator-inject.html b/testing/web-platform/tests/IndexedDB/keygenerator-inject.html new file mode 100644 index 0000000000..57f3a53848 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/keygenerator-inject.html @@ -0,0 +1,119 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Key Generator behavior with explicit keys and value injection</title> +<link rel=help href="https://w3c.github.io/IndexedDB/#inject-key-into-value"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + (t, db) => { + db.createObjectStore('store', {autoIncrement: true, keyPath: 'id'}); + }, + (t, db) => { + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + t.onabort = t.unreached_func('transaction should not abort'); + + const store = tx.objectStore('store'); + + store.put({name: 'n'}).onsuccess = t.step_func(e => { + const key = e.target.result; + assert_equals(key, 1, 'Key generator initial value should be 1'); + store.get(key).onsuccess = t.step_func(e => { + const value = e.target.result; + assert_equals(typeof value, 'object', 'Result should be object'); + assert_equals(value.name, 'n', 'Result should have name property'); + assert_equals(value.id, key, 'Key should be injected'); + t.done(); + }); + }); + }, + 'Key is injected into value - single segment path'); + +indexeddb_test( + (t, db) => { + db.createObjectStore('store', {autoIncrement: true, keyPath: 'a.b.id'}); + }, + (t, db) => { + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + t.onabort = t.unreached_func('transaction should not abort'); + + const store = tx.objectStore('store'); + + store.put({name: 'n'}).onsuccess = t.step_func(e => { + const key = e.target.result; + assert_equals(key, 1, 'Key generator initial value should be 1'); + store.get(key).onsuccess = t.step_func(e => { + const value = e.target.result; + assert_equals(typeof value, 'object', 'Result should be object'); + assert_equals(value.name, 'n', 'Result should have name property'); + assert_equals(value.a.b.id, key, 'Key should be injected'); + t.done(); + }); + }); + }, + 'Key is injected into value - multi-segment path'); + +indexeddb_test( + (t, db) => { + db.createObjectStore('store', {autoIncrement: true, keyPath: 'a.b.id'}); + }, + (t, db) => { + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + t.onabort = t.unreached_func('transaction should not abort'); + + const store = tx.objectStore('store'); + + store.put({name: 'n1', b: {name: 'n2'}}).onsuccess = t.step_func(e => { + const key = e.target.result; + assert_equals(key, 1, 'Key generator initial value should be 1'); + store.get(key).onsuccess = t.step_func(e => { + const value = e.target.result; + assert_equals(typeof value, 'object', 'Result should be object'); + assert_equals(value.name, 'n1', 'Result should have name property'); + assert_equals(value.b.name, 'n2', 'Result should have name property'); + assert_equals(value.a.b.id, key, 'Key should be injected'); + t.done(); + }); + }); + }, + 'Key is injected into value - multi-segment path, partially populated'); + +indexeddb_test( + (t, db) => { + db.createObjectStore('store', {autoIncrement: true, keyPath: 'id'}); + }, + (t, db) => { + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + + assert_throws_dom('DataError', () => { + store.put(123); + }, 'Key path should be checked against value'); + + t.done(); + }, + 'put() throws if key cannot be injected - single segment path'); + +indexeddb_test( + (t, db) => { + db.createObjectStore('store', {autoIncrement: true, keyPath: 'a.b.id'}); + }, + (t, db) => { + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + + assert_throws_dom('DataError', () => { + store.put({a: 123}); + }, 'Key path should be checked against value'); + + assert_throws_dom('DataError', () => { + store.put({a: {b: 123} }); + }, 'Key path should be checked against value'); + + t.done(); + }, + 'put() throws if key cannot be injected - multi-segment path'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/keygenerator-overflow.htm b/testing/web-platform/tests/IndexedDB/keygenerator-overflow.htm new file mode 100644 index 0000000000..2dd6a8dca8 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/keygenerator-overflow.htm @@ -0,0 +1,70 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Keygenerator overflow</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#key-generator-concept"> +<link rel=assert title="When the current number of a key generator reaches above the value 2^53 (9007199254740992) any attempts to use the key generator to generate a new key will result in an error. It's still possible to insert records into the object store by specifying an explicit key, however the only way to use a key generator again for the object store is to delete the object store and create a new one."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + + var db, + t = async_test(), + overflow_error_fired = false, + objects = [9007199254740991, null, "error", 2, "error" ], + expected_keys = [2, 9007199254740991, 9007199254740992]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store", { keyPath: "id", autoIncrement: true }); + + for (var i = 0; i < objects.length; i++) + { + if (objects[i] === null) + { + objStore.add({}); + } + else if (objects[i] === "error") + { + var rq = objStore.add({}); + rq.onsuccess = fail(t, 'When "current number" overflows, error event is expected'); + rq.onerror = t.step_func(function(e) { + overflow_error_fired = true; + assert_equals(e.target.error.name, "ConstraintError", "error name"); + e.preventDefault(); + e.stopPropagation(); + }); + } + else + objStore.add({ id: objects[i] }); + } + }; + + open_rq.onsuccess = function(e) { + var actual_keys = [], + rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .openCursor(); + + rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + if (cursor) { + actual_keys.push(cursor.key.valueOf()); + cursor.continue(); + } + else { + assert_true(overflow_error_fired, "error fired on 'current number' overflow"); + assert_array_equals(actual_keys, expected_keys, "keygenerator array"); + + t.done(); + } + }); + }; + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/keygenerator.htm b/testing/web-platform/tests/IndexedDB/keygenerator.htm new file mode 100644 index 0000000000..b4eeef9515 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/keygenerator.htm @@ -0,0 +1,65 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Keygenerator</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#key-generator-concept"> +<link rel=assert title="The current number of a key generator is always set to 1 when the object store for that key generator is first created."> +<link rel=assert title="When a key generator is used to generate a new key for a object store, the key generator's current number is used as the new key value and then the key generator's current number is increased by 1."> +<link rel=assert title="When a record is stored in a object store which uses a key generator, and an explicit key is defined, if the key's value is a float greater than or equal to the key generator's current number, then the key generator's current number is set to the smallest integer number larger than the explicit key. Only explicit keys which are float values affect the current number of the key generator."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + function keygenerator(objects, expected_keys, desc, func) { + var db, + t = async_test(document.title + " - " + desc); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store", { keyPath: "id", autoIncrement: true }); + + for (var i = 0; i < objects.length; i++) + { + if (objects[i] === null) + objStore.add({}); + else + objStore.add({ id: objects[i] }); + } + }; + + open_rq.onsuccess = function(e) { + var actual_keys = [], + rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .openCursor(); + + rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + if (cursor) { + actual_keys.push(cursor.key.valueOf()); + cursor.continue(); + } + else { + assert_key_equals(actual_keys, expected_keys, "keygenerator array"); + t.done(); + } + }); + }; + } + + + keygenerator([null, null, null, null], [1, 2, 3, 4], + "starts at one, and increments by one"); + + keygenerator([2, null, 5, null, 6.66, 7], [2, 3, 5, 6, 6.66, 7], + "increments by one from last set key"); + + keygenerator([-10, null, "6", 6.3, [10], -2, 4, null], [-10, -2, 1, 4, 6.3, 7, "6", [10]], + "don't increment when new key is not bigger than current"); + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/keyorder.htm b/testing/web-platform/tests/IndexedDB/keyorder.htm new file mode 100644 index 0000000000..7e8b3d4126 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/keyorder.htm @@ -0,0 +1,175 @@ +<!DOCTYPE html> +<!-- Submitted from TestTWF Paris --> +<meta charset="utf-8"> +<title>Key sort order</title> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#key-construct"> +<link rel=assert title="For purposes of comparison, all Arrays are greater than all DOMString, Date and float values; all DOMString values are greater than all Date and float values; and all Date values are greater than all float values. Values of type float are compared to other float values numerically. Values of type Date are compared to other Date values chronologically. Values of type DOMString are compared to other values of type DOMString by using the algorithm defined by step 4 of section 11.8.5, The Abstract Relational Comparison Algorithm, of the ECMAScript Language Specification [ECMA-262]. Values of type Array are compared to other values of type Array as follows: + +1. Let A be the first Array value and B be the second Array value. +2. Let length be the lesser of A's length and B's length. +3. Let i be 0. +4. If the ith value of A is less than the ith value of B, then A is less than B. Skip the remaining steps. +5. If the ith value of A is greater than the ith value of B, then A is greater than B. Skip the remaining steps. +6. Increase i by 1. +7. If i is not equal to length, go back to step 4. Otherwise continue to next step. +8. If A's length is less than B's length, then A is less than B. If A's length is greater than B's length, then A is greater than B. Otherwise A and B are equal."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var global_db = createdb_for_multiple_tests(); + + function keysort(desc, unsorted, expected) { + var db, + t = async_test("Database readback sort - " + desc), + store_name = 'store-' + Date.now() + Math.random(); + + // The database test + var open_rq = global_db.setTest(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore(store_name); + + for (var i = 0; i < unsorted.length; i++) + objStore.add("value", unsorted[i]); + }; + + open_rq.onsuccess = function(e) { + var actual_keys = [], + rq = db.transaction(store_name) + .objectStore(store_name) + .openCursor(); + + rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + if (cursor) { + actual_keys.push(cursor.key); + cursor.continue(); + } + else { + assert_key_equals(actual_keys, expected, "keyorder array"); + assert_equals(actual_keys.length, expected.length, "array length"); + + t.done(); + } + }); + }; + + // The IDBKey.cmp test + test(function () { + var sorted = unsorted.slice(0).sort(function(a, b) { return indexedDB.cmp(a, b)}); + assert_key_equals(sorted, expected, "sorted array"); + + }, "IDBKey.cmp sorted - " + desc); + } + + var now = new Date(), + one_sec_ago = new Date(now - 1000), + one_min_future = new Date(now.getTime() + (1000*60)); + + keysort('String < Array', + [ [0], "yo", "", [] ], + [ "", "yo", [], [0] ]); + + keysort('float < String', + [ Infinity, "yo", 0, "", 100 ], + [ 0, 100, Infinity, "", "yo" ]); + + keysort('float < Date', + [ now, 0, 9999999999999, -0.22 ], + [ -0.22, 0, 9999999999999, now ]); + + keysort('float < Date < String < Array', + [ [], "", now, [0], "-1", 0, 9999999999999, ], + [ 0, 9999999999999, now, "", "-1", [], [0] ]); + + + keysort('Date(1 sec ago) < Date(now) < Date(1 minute in future)', + [ now, one_sec_ago, one_min_future ], + [ one_sec_ago, now, one_min_future ]); + + keysort('-1.1 < 1 < 1.01337 < 1.013373 < 2', + [ 1.013373, 2, 1.01337, -1.1, 1 ], + [ -1.1, 1, 1.01337, 1.013373, 2 ]); + + keysort('-Infinity < -0.01 < 0 < Infinity', + [ 0, -0.01, -Infinity, Infinity ], + [ -Infinity, -0.01, 0, Infinity ]); + + keysort('"" < "a" < "ab" < "b" < "ba"', + [ "a", "ba", "", "b", "ab" ], + [ "", "a", "ab", "b", "ba" ]); + + keysort('Arrays', + [ [[0]], [0], [], [0, 0], [0, [0]] ], + [ [], [0], [0, 0], [0, [0]], [[0]] ]); + + var big_array = [], bigger_array = []; + for (var i=0; i < 10000; i++) { + big_array.push(i); + bigger_array.push(i); + } + bigger_array.push(0); + + keysort('Array.length: 10,000 < Array.length: 10,001', + [ bigger_array, [0, 2, 3], [0], [9], big_array ], + [ [0], big_array, bigger_array, [0, 2, 3], [9] ]); + + keysort('Infinity inside arrays', + [ [Infinity, 1], [Infinity, Infinity], [1, 1], + [1, Infinity], [1, -Infinity], [-Infinity, Infinity] ], + [ [-Infinity, Infinity], [1, -Infinity], [1, 1], + [1, Infinity], [Infinity, 1], [Infinity, Infinity] ]); + + + keysort('Test different stuff at once', + [ + now, + [0, []], + "test", + 1, + ["a", [1, [-1]]], + ["b", "a"], + [ 0, 2, "c"], + ["a", [1, 2]], + [], + [0, [], 3], + ["a", "b"], + [ 1, 2 ], + ["a", "b", "c"], + one_sec_ago, + [ 0, "b", "c"], + Infinity, + -Infinity, + 2.55, + [ 0, now ], + [1] + ], + [ + -Infinity, + 1, + 2.55, + Infinity, + one_sec_ago, + now, + "test", + [], + [0 ,2, "c"], + [0, now], + [0, "b", "c"], + [0, []], + [0, [], 3], + [1], + [1, 2], + ["a", "b"], + ["a", "b", "c"], + ["a", [1, 2]], + ["a", [1, [-1]]], + ["b", "a"] + ]); + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/keypath-exceptions.htm b/testing/web-platform/tests/IndexedDB/keypath-exceptions.htm new file mode 100644 index 0000000000..c23d4d2268 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/keypath-exceptions.htm @@ -0,0 +1,281 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: Exceptions in extracting keys from values (ES bindings)</title> +<meta name="help" href="https://w3c.github.io/IndexedDB/#extract-key-from-value"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + (t, db) => { + db.createObjectStore('store', {autoIncrement: true, keyPath: 'a.b.c'}); + }, + (t, db) => { + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + assert_throws_dom('DataError', () => { + tx.objectStore('store').put({a: {b: "foo"}}); + }, 'Put should throw if key can not be inserted at key path location.'); + t.done(); + }, + 'The last element of keypath is validated' +); + +const err = Error(); +err.name = 'getter'; + +function throwingGetter() { + throw err; +} + +indexeddb_test( + function(t, db) { + const o = {}; + Object.defineProperty(o, 'throws', {get: throwingGetter, + enumerable: false, configurable: true}); + + // Value should be cloned before key path is evaluated, + // and non-enumerable getter will be ignored. The clone + // will have no such property, so key path evaluation + // will fail. + const s1 = db.createObjectStore('s1', {keyPath: 'throws'}); + assert_throws_dom('DataError', () => { + s1.put(o); + }, 'Key path failing to resolve should throw'); + + // Value should be cloned before key path is evaluated, + // and non-enumerable getter will be ignored. The clone + // will have no such property, so key path evaluation + // will fail. + const s2 = db.createObjectStore('s2', {keyPath: 'throws.x'}); + assert_throws_dom('DataError', () => { + s2.put(o); + }, 'Key path failing to resolve should throw'); + + // Value should be cloned before key path is evaluated, + // and non-enumerable getter will be ignored. The clone + // will have no such property, so generated key can be + // inserted. + const s3 = db.createObjectStore('s3', + {keyPath: 'throws', autoIncrement: true}); + assert_class_string(s3.put(o), 'IDBRequest', + 'Key injectability test at throwing getter should succeed'); + + // Value should be cloned before key path is evaluated, + // and non-enumerable getter will be ignored. The clone + // will have no such property, so intermediate object + // and generated key can be inserted. + const s4 = db.createObjectStore('s4', + {keyPath: 'throws.x', autoIncrement: true}); + assert_class_string(s4.put(o), 'IDBRequest', + 'Key injectability test past throwing getter should succeed'); + }, + (t, db) => { + t.done(); + }, + 'Key path evaluation: Exceptions from non-enumerable getters' +); + +indexeddb_test( + function(t, db) { + const o = {}; + Object.defineProperty(o, 'throws', {get: throwingGetter, + enumerable: true, configurable: true}); + + // Value should be cloned before key path is evaluated, + // and enumerable getter will rethrow. + const s1 = db.createObjectStore('s1', {keyPath: 'throws'}); + assert_throws_exactly(err, () => { + s1.put(o); + }, 'Key path resolving to throwing getter rethrows'); + + // Value should be cloned before key path is evaluated, + // and enumerable getter will rethrow. + const s2 = db.createObjectStore('s2', {keyPath: 'throws.x'}); + assert_throws_exactly(err, () => { + s2.put(o); + }, 'Key path resolving past throwing getter rethrows'); + + // Value should be cloned before key path is evaluated, + // and enumerable getter will rethrow. + const s3 = db.createObjectStore('s3', + {keyPath: 'throws', autoIncrement: true}); + assert_throws_exactly(err, () => { + s3.put(o); + }, 'Key injectability test at throwing getter should rethrow'); + + // Value should be cloned before key path is evaluated, + // and enumerable getter will rethrow. + const s4 = db.createObjectStore('s4', + {keyPath: 'throws.x', autoIncrement: true}); + assert_throws_exactly(err, () => { + s4.put(o); + }, 'Key injectability test past throwing getter should rethrow'); + }, + (t, db) => { + t.done(); + }, + 'Key path evaluation: Exceptions from enumerable getters' +); + +indexeddb_test( + (t, db) => { + // Implemented as function wrapper to clean up + // immediately after use, otherwise it may + // interfere with the test harness. + function with_proto_getter(f) { + return function() { + Object.defineProperty(Object.prototype, 'throws', { + get: throwingGetter, + enumerable: false, configurable: true + }); + try { + f(); + } finally { + delete Object.prototype['throws']; + } + }; + } + + // Value should be cloned before key path is evaluated, + // and non-enumerable getter will be ignored. The clone + // will have no own property, so key path evaluation will + // fail and DataError should be thrown. + const s1 = db.createObjectStore('s1', {keyPath: 'throws'}); + assert_throws_dom('DataError', with_proto_getter(function() { + s1.put({}); + }), 'Key path resolving to no own property throws DataError'); + + // Value should be cloned before key path is evaluated, + // and non-enumerable getter will be ignored. The clone + // will have no own property, so key path evaluation will + // fail and DataError should be thrown. + const s2 = db.createObjectStore('s2', {keyPath: 'throws.x'}); + assert_throws_dom('DataError', with_proto_getter(function() { + s2.put({}); + }), 'Key path resolving past no own property throws DataError'); + + // Value should be cloned before key path is evaluated, + // and non-enumerable getter will be ignored. The clone + // will have no own property, so key path evaluation will + // fail and injection can succeed. + const s3 = db.createObjectStore('s3', + {keyPath: 'throws', autoIncrement: true}); + assert_equals(s3.put({}).readyState, 'pending', + 'put should not throw due to inherited property'); + + // Value should be cloned before key path is evaluated, + // and non-enumerable getter will be ignored. The clone + // will have no own property, so key path evaluation will + // fail and injection can succeed. + const s4 = db.createObjectStore('s4', + {keyPath: 'throws.x', autoIncrement: true}); + assert_equals(s4.put({}).readyState, 'pending', + 'put should not throw due to inherited property'); + }, + (t, db) => { + t.done(); + }, + 'Key path evaluation: Exceptions from non-enumerable getters on prototype' +); + +indexeddb_test( + (t, db) => { + // Implemented as function wrapper to clean up + // immediately after use, otherwise it may + // interfere with the test harness. + function with_proto_getter(f) { + return () => { + Object.defineProperty(Object.prototype, 'throws', { + get: throwingGetter, + enumerable: true, configurable: true + }); + try { + f(); + } finally { + delete Object.prototype['throws']; + } + }; + } + + // Value should be cloned before key path is evaluated. + // The clone will have no own property, so key path + // evaluation will fail and DataError should be thrown. + const s1 = db.createObjectStore('s1', {keyPath: 'throws'}); + assert_throws_dom('DataError', with_proto_getter(function() { + s1.put({}); + }), 'Key path resolving to no own property throws DataError'); + + // Value should be cloned before key path is evaluated. + // The clone will have no own property, so key path + // evaluation will fail and DataError should be thrown. + const s2 = db.createObjectStore('s2', {keyPath: 'throws.x'}); + assert_throws_dom('DataError', with_proto_getter(function() { + s2.put({}); + }), 'Key path resolving past throwing getter rethrows'); + + // Value should be cloned before key path is evaluated. + // The clone will have no own property, so key path + // evaluation will fail and injection can succeed. + var s3 = db.createObjectStore('s3', + {keyPath: 'throws', autoIncrement: true}); + assert_equals(s3.put({}).readyState, 'pending', + 'put should not throw due to inherited property'); + + // Value should be cloned before key path is evaluated. + // The clone will have no own property, so key path + // evaluation will fail and injection can succeed. + var s4 = db.createObjectStore('s4', + {keyPath: 'throws.x', autoIncrement: true}); + assert_equals(s4.put({}).readyState, 'pending', + 'put should not throw due to inherited property'); + }, + (t, db) => { + t.done(); + }, + 'Key path evaluation: Exceptions from enumerable getters on prototype' +); + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('store'); + store.createIndex('index', 'index0'); + }, + (t, db) => { + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + + const array = []; + array[99] = 1; + + // Implemented as function wrapper to clean up + // immediately after use, otherwise it may + // interfere with the test harness. + let getter_called = 0; + function with_proto_getter(f) { + const prop = '50'; + Object.defineProperty(Object.prototype, prop, { + enumerable: true, configurable: true, + get: () => { + ++getter_called; + return 'foo'; + } + }); + try { + return f(); + } finally { + delete Object.prototype[prop]; + } + } + + const request = with_proto_getter( + () => tx.objectStore('store').put({index0: array}, 'key')); + request.onerror = t.unreached_func('put should not fail'); + request.onsuccess = t.step_func(function() { + assert_equals(getter_called, 0, 'Prototype getter should not be called'); + t.done(); + }); + }, + 'Array key conversion should not invoke prototype getters' +); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/keypath-special-identifiers.htm b/testing/web-platform/tests/IndexedDB/keypath-special-identifiers.htm new file mode 100644 index 0000000000..55f40314ee --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/keypath-special-identifiers.htm @@ -0,0 +1,66 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: Special-cased identifiers in extracting keys from values (ES bindings)</title> +<meta name="help" href="https://w3c.github.io/IndexedDB/#extract-key-from-value"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +[ + { + type: 'String', + property: 'length', + instance: 'abc', + }, + { + type: 'Array', + property: 'length', + instance: ['a', 'b', 'c'], + }, + { + type: 'Blob', + property: 'size', + instance: new Blob(['abc']), + }, + { + type: 'Blob', + property: 'type', + instance: new Blob([''], {type:'foo/bar'}), + }, + { + type: 'File', + property: 'name', + instance: new File([''], 'foo'), + }, + { + type: 'File', + property: 'lastModified', + instance: new File([''], '', {lastModified: 123}), + }, +].forEach(function(testcase) { + indexeddb_test( + (t, db) => { + db.createObjectStore( + 'store', {autoIncrement: true, keyPath: testcase.property}); + }, + (t, db) => { + const key = testcase.instance[testcase.property]; + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + tx.objectStore('store').put(testcase.instance); + const request = tx.objectStore('store').get(key); + request.onerror = t.unreached_func('request should not fail'); + request.onsuccess = t.step_func(function() { + const result = request.result; + assert_key_equals(result[testcase.property], key, + 'Property should be used as key'); + }); + tx.oncomplete = t.step_func(function() { + t.done(); + }); + }, + 'Type: ' + testcase.type + ', identifier: ' + testcase.property + ); +}); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/keypath.htm b/testing/web-platform/tests/IndexedDB/keypath.htm new file mode 100644 index 0000000000..06f5947d35 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/keypath.htm @@ -0,0 +1,150 @@ +<!DOCTYPE html> +<!-- Submitted from TestTWF Paris --> +<meta charset="utf-8"> +<title>Keypath</title> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#key-path-construct"> +<link rel=assert title="A key path is a DOMString that defines how to extract a key from a value. A valid key path is either the empty string, a JavaScript identifier, or multiple Javascript identifiers separated by periods (ASCII character code 46) [ECMA-262]."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + + var global_db = createdb_for_multiple_tests(); + + function keypath(keypath, objects, expected_keys, desc) { + var db, + t = async_test(document.title + " - " + (desc ? desc : keypath)), + + store_name = "store-"+(Date.now())+Math.random(); + + var open_rq = global_db.setTest(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore(store_name, { keyPath: keypath }); + + for (var i = 0; i < objects.length; i++) + objStore.add(objects[i]); + }; + + open_rq.onsuccess = function(e) { + var actual_keys = [], + rq = db.transaction(store_name) + .objectStore(store_name) + .openCursor(); + + rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + if (cursor) { + actual_keys.push(cursor.key.valueOf()); + cursor.continue(); + } + else { + assert_key_equals(actual_keys, expected_keys, "keyorder array"); + t.done(); + } + }); + }; + } + + keypath('my.key', + [ { my: { key: 10 } } ], + [ 10 ]); + + keypath('my.køi', + [ { my: { køi: 5 } } ], + [ 5 ]); + + keypath('my.key_ya', + [ { my: { key_ya: 10 } } ], + [ 10 ]); + + keypath('public.key$ya', + [ { public: { key$ya: 10 } } ], + [ 10 ]); + + keypath('true.$', + [ { true: { $: 10 } } ], + [ 10 ]); + + keypath('my._', + [ { my: { _: 10 } } ], + [ 10 ]); + + keypath('delete.a7', + [ { delete: { a7: 10 } } ], + [ 10 ]); + + keypath('p.p.p.p.p.p.p.p.p.p.p.p.p.p', + [ {p:{p:{p:{p:{p:{p:{p:{p:{p:{p:{p:{p:{p:{p:10}}}}}}}}}}}}}} ], + [ 10 ]); + + keypath('str.length', + [ { str: "pony" }, { str: "my" }, { str: "little" }, { str: "" } ], + [ 0, 2, 4, 6 ]); + + keypath('arr.length', + [ {arr: [0, 0, 0, 0]}, {arr: [{}, 0, "hei", "length", Infinity, []]}, {arr: [10, 10]}, { arr: []} ], + [ 0, 2, 4, 6 ]); + + keypath('length', + [ [10, 10], "123", { length: 20 } ], + [ 2, 3, 20 ]); + + keypath('', + [ ["bags"], "bean", 10 ], + [ 10, "bean", ["bags"] ], + "'' uses value as key"); + + keypath([''], + [ ["bags"], "bean", 10 ], + [ [10], ["bean"] , [["bags"]] ], + "[''] uses value as [key]"); + + keypath(['x', 'y'], + [ {x:10, y:20}, {y:1.337, x:100} ], + [ [10, 20], [100, 1.337] ], + "['x', 'y']"); + + keypath([['x'], ['y']], + [ {x:10, y:20}, {y:1.337, x:100} ], + [ [10, 20], [100, 1.337] ], + "[['x'], 'y'] (stringifies)"); + + keypath(['x', {toString:function(){return 'y'}}], + [ {x:10, y:20}, {y:1.337, x:100} ], + [ [10, 20], [100, 1.337] ], + "['x', {toString->'y'}] (stringifies)"); + + if (false) { + var myblob = Blob(["Yoda"], {type:'suprawsum'}); + keypath(['length', 'type'], + [ myblob ], + [ 4, 'suprawsum' ], + "[Blob.length, Blob.type]"); + } + + // File.name and File.lastModified is not testable automatically + + keypath(['name', 'type'], + [ { name: "orange", type: "fruit" }, { name: "orange", type: ["telecom", "french"] } ], + [ ["orange", "fruit"], ["orange", ["telecom", "french"]] ]); + + keypath(['name', 'type.name'], + [ { name: "orange", type: { name: "fruit" }}, { name: "orange", type: { name: "telecom" }} ], + [ ["orange", "fruit"], ["orange", "telecom" ] ]); + + keypath(['type'], + [ { name: "orange", type: "fruit" }, { name: "cucumber", type: "vegetable" } ], + [ ["fruit"], ["vegetable"] ], "list with 1 field"); + + loop_array = []; + loop_array.push(loop_array); + keypath(loop_array, + [ "a", 1, ["k"] ], + [ [1], ["a"], [["k"]] ], + "array loop -> stringify becomes ['']"); +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/keypath_invalid.htm b/testing/web-platform/tests/IndexedDB/keypath_invalid.htm new file mode 100644 index 0000000000..6aa33c854a --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/keypath_invalid.htm @@ -0,0 +1,65 @@ +<!DOCTYPE html> +<!-- Submitted from TestTWF Paris --> +<meta charset="utf-8"> +<title>Invalid keypath</title> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#key-path-construct"> +<link rel=assert title="A key path is a DOMString that defines how to extract a key from a value. A valid key path is either the empty string, a JavaScript identifier, or multiple Javascript identifiers separated by periods (ASCII character code 46) [ECMA-262]."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + + var global_db = createdb_for_multiple_tests(); + + function invalid_keypath(keypath, desc) { + var t = async_test("Invalid keyPath - " + (desc ? desc : format_value(keypath)), undefined, 2); + + var openrq = global_db.setTest(t), + store_name = "store-" + Date.now() + Math.random(); + + openrq.onupgradeneeded = function(e) { + var db = e.target.result; + assert_throws_dom('SyntaxError', function() { + db.createObjectStore(store_name, { keyPath: keypath }) + }, "createObjectStore with keyPath"); + + var store = db.createObjectStore(store_name); + assert_throws_dom('SyntaxError', function() { + store.createIndex('index', keypath); + }, "createIndex with keyPath"); + + db.close(); + + this.done(); + }; + } + + invalid_keypath('j a'); + invalid_keypath('.yo'); + invalid_keypath('yo,lo'); + invalid_keypath([]); + invalid_keypath(['array with space']); + invalid_keypath(['multi_array', ['a', 'b']], "multidimensional array (invalid toString)"); // => ['multi_array', 'a,b'] + invalid_keypath('3m'); + invalid_keypath({toString:function(){return '3m'}}, '{toString->3m}'); + invalid_keypath('my.1337'); + invalid_keypath('..yo'); + invalid_keypath('y..o'); + invalid_keypath('y.o.'); + invalid_keypath('y.o..'); + invalid_keypath('m.*'); + invalid_keypath('"m"'); + invalid_keypath('m%'); + invalid_keypath('m/'); + invalid_keypath('m/a'); + invalid_keypath('m&'); + invalid_keypath('m!'); + invalid_keypath('*'); + invalid_keypath('*.*'); + invalid_keypath('^m'); + invalid_keypath('/m/'); + +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/keypath_maxsize.htm b/testing/web-platform/tests/IndexedDB/keypath_maxsize.htm new file mode 100644 index 0000000000..069f4f7eca --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/keypath_maxsize.htm @@ -0,0 +1,63 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Keypath</title> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#key-path-construct"> +<link rel=assert title="A key path is a DOMString that defines how to extract a key from a value. A valid key path is either the empty string, a JavaScript identifier, or multiple Javascript identifiers separated by periods (ASCII character code 46) [ECMA-262]."> +<meta name=timeout content=long> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + function keypath(keypath, objects, expected_keys, desc) { + var db, + t = async_test(document.title + " - " + (desc ? desc : keypath)), + open_rq = createdb(t); + + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store", { keyPath: keypath }); + + for (var i = 0; i < objects.length; i++) + objStore.add(objects[i]); + }; + + open_rq.onerror = function(e) { + assert_unreached(e.target.error.name); + }; + + open_rq.onsuccess = function(e) { + var actual_keys = [], + rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .openCursor(); + + rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + if (cursor) { + actual_keys.push(cursor.key.valueOf()); + cursor.continue(); + } else { + assert_array_equals(actual_keys, expected_keys, "keyorder array"); + t.done(); + } + }); + }; + } + + keypath('maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaai.keeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeey', + [ { maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaai: { keeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeey: 10 } } ], + [ 10 ], '~260 chars'); + + keypath('maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaai_maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaai.keeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeey_keeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeey', + [ { maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaai_maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaai: { keeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeey_keeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeey: 10 } } ], + [ 10 ], '~530 chars'); + + keypath('maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaai_maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaai_maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaai_maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaai.keeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeey_keeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeey_keeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeey_keeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeey', + [ { maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaai_maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaai_maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaai_maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaai: { keeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeey_keeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeey_keeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeey_keeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeey: 10 } } ], + [ 10 ], '~1050 chars'); + +</script> + +<div id=log></log> diff --git a/testing/web-platform/tests/IndexedDB/large-requests-abort.html b/testing/web-platform/tests/IndexedDB/large-requests-abort.html new file mode 100644 index 0000000000..38c73ff406 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/large-requests-abort.html @@ -0,0 +1,244 @@ +<!doctype html> +<meta charset="utf8"> +<meta name="timeout" content="long"> +<title>IndexedDB: transactions with large request results are aborted correctly</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#abort-transaction"> +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script> +'use strict'; + +// Should be large enough to trigger large value handling in the IndexedDB +// engines that have special code paths for large values. +const wrapThreshold = 128 * 1024; + +function populateStore(store) { + store.put({id: 1, key: 'k1', value: largeValue(wrapThreshold, 1) }); + store.put({id: 2, key: 'k2', value: ['small-2'] }); + store.put({id: 3, key: 'k3', value: largeValue(wrapThreshold, 3) }); + store.put({id: 4, key: 'k4', value: ['small-4'] }); +} + +// Opens index cursors for operations that require open cursors. +// +// onsuccess is called if all cursors are opened successfully. Otherwise, +// onerror will be called at least once. +function openCursors(testCase, index, operations, onerror, onsuccess) { + let pendingCursors = 0; + + for (let operation of operations) { + const opcode = operation[0]; + const primaryKey = operation[1]; + let request; + switch (opcode) { + case 'continue': + request = index.openCursor( + IDBKeyRange.lowerBound(`k${primaryKey - 1}`)); + break; + case 'continue-empty': + // k4 is the last key in the data set, so calling continue() will get + // the cursor past the end of the store. + request = index.openCursor(IDBKeyRange.lowerBound('k4')); + break; + default: + continue; + } + + operation[2] = request; + ++pendingCursors; + + request.onsuccess = testCase.step_func(() => { + --pendingCursors; + if (!pendingCursors) + onsuccess(); + }); + request.onerror = testCase.step_func(onerror); + } + + if (!pendingCursors) + onsuccess(); +} + +function doOperation(testCase, store, index, operation, requestId, results) { + const opcode = operation[0]; + const primaryKey = operation[1]; + const cursor = operation[2]; + + return new Promise((resolve, reject) => { + let request; + switch (opcode) { + case 'add': // Tests returning a primary key. + request = store.add( + { key: `k${primaryKey}`, value: [`small-${primaryKey}`] }); + break; + case 'put': // Tests returning a primary key. + request = store.put( + { key: `k${primaryKey}`, value: [`small-${primaryKey}`] }); + break; + case 'put-with-id': // Tests returning success or a primary key. + request = store.put( + { key: `k${primaryKey}`, value: [`small-${primaryKey}`], + id: primaryKey }); + break; + case 'get': // Tests returning a value. + case 'get-empty': // Tests returning undefined. + request = store.get(primaryKey); + break; + case 'getall': // Tests returning an array of values. + request = store.getAll(); + break; + case 'error': // Tests returning an error. + request = store.put( + { key: `k${primaryKey}`, value: [`small-${primaryKey}`] }); + break; + case 'continue': // Tests returning a key, primary key, and value. + case 'continue-empty': // Tests returning null. + request = cursor; + cursor.result.continue(); + break; + case 'open': // Tests returning a cursor, key, primary key, and value. + case 'open-empty': // Tests returning null. + request = index.openCursor(IDBKeyRange.lowerBound(`k${primaryKey}`)); + break; + case 'count': // Tests returning a numeric result. + request = index.count(); + break; + }; + + request.onsuccess = testCase.step_func(() => { + reject(new Error( + 'requests should not succeed after the transaction is aborted')); + }); + request.onerror = testCase.step_func(event => { + event.preventDefault(); + results.push([requestId, request.error]); + resolve(); + }); + }); +} + +function abortTest(label, operations) { + promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + const store = database.createObjectStore( + 'test-store', { autoIncrement: true, keyPath: 'id' }); + store.createIndex('test-index', 'key', { unique: true }); + populateStore(store); + }).then(database => { + const transaction = database.transaction(['test-store'], 'readwrite'); + const store = transaction.objectStore('test-store'); + const index = store.index('test-index'); + return new Promise((resolve, reject) => { + openCursors(testCase, index, operations, reject, () => { + const results = []; + const promises = []; + for (let i = 0; i < operations.length; ++i) { + const promise = doOperation( + testCase, store, index, operations[i], i, results); + promises.push(promise); + }; + transaction.abort(); + resolve(Promise.all(promises).then(() => results)); + }); + }); + }).then(results => { + assert_equals( + results.length, operations.length, + 'Promise.all should resolve after all sub-promises resolve'); + for (let i = 0; i < operations.length; ++i) { + assert_equals( + results[i][0], i, + 'error event order should match request order'); + assert_equals( + results[i][1].name, 'AbortError', + 'transaction aborting should result in AbortError on all requests'); + } + }); + }, label); +} + +abortTest('small values', [ + ['get', 2], + ['count', null], + ['continue-empty', null], + ['get-empty', 5], + ['add', 5], + ['open', 2], + ['continue', 2], + ['get', 4], + ['get-empty', 6], + ['count', null], + ['put-with-id', 5], + ['put', 6], + ['error', 3], + ['continue', 4], + ['count', null], + ['get-empty', 7], + ['open', 4], + ['open-empty', 7], + ['add', 7], +]); + +abortTest('large values', [ + ['open', 1], + ['get', 1], + ['getall', 4], + ['get', 3], + ['continue', 3], + ['open', 3], +]); + +abortTest('large value followed by small values', [ + ['get', 1], + ['getall', null], + ['open', 2], + ['continue-empty', null], + ['get', 2], + ['get-empty', 5], + ['count', null], + ['continue-empty', null], + ['open-empty', 5], + ['add', 5], + ['error', 1], + ['continue', 2], + ['get-empty', 6], + ['put-with-id', 5], + ['put', 6], +]); + +abortTest('large values mixed with small values', [ + ['get', 1], + ['get', 2], + ['get-empty', 5], + ['count', null], + ['continue-empty', null], + ['open', 1], + ['continue', 2], + ['open-empty', 5], + ['getall', 4], + ['open', 2], + ['continue-empty', null], + ['add', 5], + ['get', 3], + ['count', null], + ['get-empty', 6], + ['put-with-id', 5], + ['getall', null], + ['continue', 3], + ['open-empty', 6], + ['put', 6], + ['error', 1], + ['continue', 2], + ['open', 4], + ['get-empty', 7], + ['count', null], + ['continue', 3], + ['add', 7], + ['getall', null], + ['error', 3], + ['count', null], +]); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/list_ordering.htm b/testing/web-platform/tests/IndexedDB/list_ordering.htm new file mode 100644 index 0000000000..a3c36de389 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/list_ordering.htm @@ -0,0 +1,61 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>objectStoreNames and indexNames order</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBDatabase-objectStoreNames"> +<link rel=assert title="The list must be sorted in ascending order using the algorithm defined by step 4 of section 11.8.5, The Abstract Relational Comparison Algorithm of the ECMAScript Language Specification [ECMA-262]."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + function list_order(desc, unsorted, expected) { + var objStore, db, + t = async_test(document.title + " - " + desc); + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + for (var i = 0; i < unsorted.length; i++) + objStore = db.createObjectStore(unsorted[i]); + + assert_equals(db.objectStoreNames.length, expected.length, "objectStoreNames length"); + for (var i = 0; i < expected.length; i++) + assert_equals(db.objectStoreNames[i], expected[i], "objectStoreNames["+i+"]"); + + for (var i = 0; i < unsorted.length; i++) + objStore.createIndex(unsorted[i], "length"); + + assert_equals(objStore.indexNames.length, expected.length, "indexNames length"); + for (var i = 0; i < expected.length; i++) + assert_equals(objStore.indexNames[i], expected[i], "indexNames["+i+"]"); + }; + + open_rq.onsuccess = function(e) { + assert_equals(db.objectStoreNames.length, expected.length, "objectStoreNames length"); + for (var i = 0; i < expected.length; i++) + assert_equals(db.objectStoreNames[i], expected[i], "objectStoreNames["+i+"]"); + + assert_equals(objStore.indexNames.length, expected.length, "indexNames length"); + for (var i = 0; i < expected.length; i++) + assert_equals(objStore.indexNames[i], expected[i], "indexNames["+i+"]"); + + t.done(); + }; + } + + list_order("numbers", + [123456, -12345, -123, 123, 1234, -1234, 0, 12345, -123456], + ["-123", "-1234", "-12345", "-123456", "0", "123", "1234", "12345", "123456"]); + + list_order("numbers 'overflow'", + [9, 1, 1000000000, 200000000000000000], + ["1", "1000000000", "200000000000000000", "9"]); + + list_order("lexigraphical string sort", + [ "cc", "c", "aa", "a", "bb", "b", "ab", "", "ac" ], + [ "", "a", "aa", "ab", "ac", "b", "bb", "c", "cc" ]); + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/name-scopes.html b/testing/web-platform/tests/IndexedDB/name-scopes.html new file mode 100644 index 0000000000..d92f706e0a --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/name-scopes.html @@ -0,0 +1,134 @@ +<!doctype html> +<meta charset="utf-8"> +<title> + IndexedDB: scoping for database / object store / index names, and index keys +</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#constructs"> +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script> +'use strict'; + +// Creates the structure inside a test database. +// +// The structure includes two stores with identical indexes and nearly-similar +// records. The records differ in the "path" attribute values, which are used to +// verify that IndexedDB returns the correct records when queried. +// +// databaseName appears redundant, but we don't want to rely on database.name. +const buildStores = (database, databaseName, useUniqueKeys) => { + for (let storeName of ['x', 'y']) { + const store = database.createObjectStore( + storeName, { keyPath: 'pKey', autoIncrement: true }); + for (let indexName of ['x', 'y']) { + store.createIndex( + indexName, `${indexName}Key`, { unique: useUniqueKeys }); + } + + for (let xKeyRoot of ['x', 'y']) { + for (let yKeyRoot of ['x', 'y']) { + let xKey, yKey; + if (useUniqueKeys) { + xKey = `${xKeyRoot}${yKeyRoot}`; + yKey = `${yKeyRoot}${xKeyRoot}`; + } else { + xKey = xKeyRoot; + yKey = yKeyRoot; + } + const path = `${databaseName}-${storeName}-${xKeyRoot}-${yKeyRoot}`; + store.put({ xKey: xKey, yKey: yKey, path: path }); + } + } + } +}; + +// Creates two databases with identical structures. +const buildDatabases = (testCase, useUniqueKeys) => { + return createNamedDatabase( + testCase, 'x', database => buildStores(database, 'x', useUniqueKeys)) + .then(database => database.close()) + .then(() => createNamedDatabase( + testCase, 'y', database => buildStores(database, 'y', useUniqueKeys))) + .then(database => database.close()); +}; + +// Reads all the store's values using an index. +// +// Returns a Promise that resolves with an array of values. +const readIndex = (testCase, index) => { + return new Promise((resolve, reject) => { + const results = []; + const request = index.openCursor(IDBKeyRange.bound('a', 'z'), 'next'); + request.onsuccess = () => { + const cursor = request.result; + if (cursor) { + results.push(cursor.value); + cursor.continue(); + } else { + resolve(results); + } + } + }); +} + +// Verifies that a database contains the expected records. +const checkDatabaseContent = + (testCase, database, databaseName, usedUniqueKeys) => { + const promises = []; + const transaction = database.transaction(['x', 'y'], 'readonly'); + for (let storeName of ['x', 'y']) { + const store = transaction.objectStore(storeName); + for (let indexName of ['x', 'y']) { + const index = store.index(indexName); + + const promise = readIndex(testCase, index).then((results) => { + assert_array_equals( + results.map(result => `${result.path}:${result.pKey}`).sort(), + [`${databaseName}-${storeName}-x-x:1`, + `${databaseName}-${storeName}-x-y:2`, + `${databaseName}-${storeName}-y-x:3`, + `${databaseName}-${storeName}-y-y:4`], + 'The results should include all records put into the store'); + + let expectedKeys = (usedUniqueKeys) ? + ['xx:xx', 'xy:yx', 'yx:xy', 'yy:yy'] : ['x:x', 'x:y', 'y:x', 'y:y']; + assert_array_equals( + results.map(result => `${result.xKey}:${result.yKey}`).sort(), + expectedKeys, + 'The results should include all the index keys put in the store'); + + assert_array_equals( + results.map(result => result[`${indexName}Key`]), + results.map(result => result[`${indexName}Key`]).sort(), + 'The results should be sorted by the index key'); + }); + promises.push(promise); + } + } + + return Promise.all(promises).then(() => database); +} + +promise_test(testCase => { + return buildDatabases(testCase, false) + .then(() => openNamedDatabase(testCase, 'x', 1)) + .then(database => checkDatabaseContent(testCase, database, 'x', false)) + .then(database => database.close()) + .then(() => openNamedDatabase(testCase, 'y', 1)) + .then(database => checkDatabaseContent(testCase, database, 'y', false)) + .then(database => database.close()); +}, 'Non-unique index keys'); + +promise_test(testCase => { + return buildDatabases(testCase, true) + .then(() => openNamedDatabase(testCase, 'x', 1)) + .then(database => checkDatabaseContent(testCase, database, 'x', true)) + .then(database => database.close()) + .then(() => openNamedDatabase(testCase, 'y', 1)) + .then(database => checkDatabaseContent(testCase, database, 'y', true)) + .then(database => database.close()); +}, 'Unique index keys'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/nested-cloning-large-multiple.html b/testing/web-platform/tests/IndexedDB/nested-cloning-large-multiple.html new file mode 100644 index 0000000000..97bcaddfb2 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/nested-cloning-large-multiple.html @@ -0,0 +1,54 @@ +<!doctype html> +<meta charset="utf8"> +<meta name="timeout" content="long"> +<title>IndexedDB: large nested objects are cloned correctly</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#abort-transaction"> +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script src="resources/nested-cloning-common.js"></script> +<script> + +cloningTestWithKeyGenerator( + 'multiple requests of objects with blobs and large typed arrays', [ + { + blob: { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink1', + seed: 1 }, + more: [ + { type: 'buffer', size: wrapThreshold, seed: 2 }, + { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink3', seed: 3 }, + { type: 'buffer', size: wrapThreshold, seed: 4 }, + ], + blob2: { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink5', + seed: 5 }, + }, + [ + { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink06', seed: 6 }, + { type: 'buffer', size: wrapThreshold, seed: 7 }, + { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink08', seed: 8 }, + { type: 'buffer', size: wrapThreshold, seed: 9 }, + { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink10', seed: 10 }, + ], + { + data: [ + { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-11', + seed: 11 }, + { type: 'buffer', size: wrapThreshold, seed: 12 }, + { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-13', + seed: 13 }, + { type: 'buffer', size: wrapThreshold, seed: 14 }, + { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-15', + seed: 15 }, + ], + }, + [ + { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink16', seed: 16 }, + { type: 'buffer', size: wrapThreshold, seed: 17 }, + { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink18', seed: 18 }, + { type: 'buffer', size: wrapThreshold, seed: 19 }, + { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink20', seed: 20 }, + ], +]); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/nested-cloning-large.html b/testing/web-platform/tests/IndexedDB/nested-cloning-large.html new file mode 100644 index 0000000000..8b3cbc5726 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/nested-cloning-large.html @@ -0,0 +1,49 @@ +<!doctype html> +<meta charset="utf8"> +<meta name="timeout" content="long"> +<title>IndexedDB: large nested objects are cloned correctly</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#abort-transaction"> +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script src="resources/nested-cloning-common.js"></script> +<script> + +cloningTest('large typed array', [ + { type: 'buffer', size: wrapThreshold, seed: 1 }, +]) + +cloningTestWithKeyGenerator('blob with large typed array', [ + { + blob: { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-01', + seed: 1 }, + buffer: { type: 'buffer', size: wrapThreshold, seed: 2 }, + }, +]); + +cloningTestWithKeyGenerator('array of blobs and large typed arrays', [ + [ + { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-01', seed: 1 }, + { type: 'buffer', size: wrapThreshold, seed: 2 }, + { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-03', seed: 3 }, + { type: 'buffer', size: wrapThreshold, seed: 4 }, + { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-05', seed: 5 }, + ], +]); + +cloningTestWithKeyGenerator('object with blobs and large typed arrays', [ + { + blob: { type: 'blob', size: wrapThreshold, + mimeType: 'text/x-blink1', seed: 1 }, + more: [ + { type: 'buffer', size: wrapThreshold, seed: 2 }, + { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink3', seed: 3 }, + { type: 'buffer', size: wrapThreshold, seed: 4 }, + ], + blob2: { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink5', + seed: 5 }, + }, +]); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/nested-cloning-small.html b/testing/web-platform/tests/IndexedDB/nested-cloning-small.html new file mode 100644 index 0000000000..e92251410a --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/nested-cloning-small.html @@ -0,0 +1,47 @@ +<!doctype html> +<meta charset="utf8"> +<meta name="timeout" content="long"> +<title>IndexedDB: small nested objects are cloned correctly</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#abort-transaction"> +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script src="resources/nested-cloning-common.js"></script> +<script> + +cloningTest('small typed array', [ + { type: 'buffer', size: 64, seed: 1 }, +]); + +cloningTest('blob', [ + { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-1', seed: 1 }, +]); + +cloningTestWithKeyGenerator('blob with small typed array', [ + { + blob: { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-01', + seed: 1 }, + buffer: { type: 'buffer', size: 64, seed: 2 }, + }, +]); + +cloningTestWithKeyGenerator('blob array', [ + [ + { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-1', seed: 1 }, + { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-2', seed: 2 }, + { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-3', seed: 3 }, + ], +]); + +cloningTestWithKeyGenerator('array of blobs and small typed arrays', [ + [ + { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-01', seed: 1 }, + { type: 'buffer', size: 64, seed: 2 }, + { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-03', seed: 3 }, + { type: 'buffer', size: 64, seed: 4 }, + { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-05', seed: 5 }, + ], +]); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/objectstore_keyorder.htm b/testing/web-platform/tests/IndexedDB/objectstore_keyorder.htm new file mode 100644 index 0000000000..ad294f66a1 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/objectstore_keyorder.htm @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Verify key sort order in an object store is 'number < Date < DOMString' </title> +<link rel="author" title="Microsoft" href="http://www.microsoft.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> + var db, + d = new Date(), + t = async_test(), + records = [ { key: d }, + { key: "test" }, + { key: 1 }, + { key: 2.55 } ], + expectedKeyOrder = [ 1, 2.55, d.valueOf(), "test" ]; + + var open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + var objStore = db.createObjectStore("store", { keyPath: "key" }); + + for (var i = 0; i < records.length; i++) + objStore.add(records[i]); + }; + + open_rq.onsuccess = function(e) { + var actual_keys = [], + rq = db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .openCursor(); + + rq.onsuccess = t.step_func(function(e) { + var cursor = e.target.result; + + if (cursor) { + actual_keys.push(cursor.key.valueOf()); + cursor.continue(); + } + else { + assert_array_equals(actual_keys, expectedKeyOrder); + t.done(); + } + }); + }; +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/open-request-queue.html b/testing/web-platform/tests/IndexedDB/open-request-queue.html new file mode 100644 index 0000000000..b4371f2a2e --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/open-request-queue.html @@ -0,0 +1,63 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: open and delete requests are processed as a FIFO queue</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#request-connection-queue"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +async_test(t => { + let db_name = 'db' + self.location.pathname + '-' + t.name; + indexedDB.deleteDatabase(db_name); + + // Open and hold connection while other requests are queued up. + let r = indexedDB.open(db_name, 1); + r.onerror = t.unreached_func('open should succeed'); + r.onsuccess = t.step_func(e => { + let db = r.result; + + let saw = expect(t, [ + 'open1 success', + 'open1 versionchange', + 'delete1 blocked', + 'delete1 success', + 'open2 success', + 'open2 versionchange', + 'delete2 blocked', + 'delete2 success' + ]); + + function open(token, version) { + let r = indexedDB.open(db_name, version); + r.onsuccess = t.step_func(e => { + saw(token + ' success'); + let db = r.result; + db.onversionchange = t.step_func(e => { + saw(token + ' versionchange'); + setTimeout(t.step_func(() => db.close()), 0); + }); + }); + r.onblocked = t.step_func(e => saw(token + ' blocked')); + r.onerror = t.unreached_func('open should succeed'); + } + + function deleteDatabase(token) { + let r = indexedDB.deleteDatabase(db_name); + r.onsuccess = t.step_func(e => saw(token + ' success')); + r.onblocked = t.step_func(e => saw(token + ' blocked')); + r.onerror = t.unreached_func('deleteDatabase should succeed'); + } + + open('open1', 2); + deleteDatabase('delete1'); + open('open2', 3); + deleteDatabase('delete2'); + + // Now unblock the queue. + db.close(); + }); + +}, 'Opens and deletes are processed in order'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/parallel-cursors-upgrade.html b/testing/web-platform/tests/IndexedDB/parallel-cursors-upgrade.html new file mode 100644 index 0000000000..99ea65d9d6 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/parallel-cursors-upgrade.html @@ -0,0 +1,52 @@ +<!doctype html> +<meta charset="utf-8"> +<meta name="timeout" content="long"> +<title>IndexedDB: Parallel iteration of cursors in upgradeneeded</title> +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script> +'use strict'; + +for (let cursorCount of [2, 20, 200, 2000]) { + promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + const store = database.createObjectStore('cache', { keyPath: 'key' }); + store.put({ key: '42' }); + + const promises = []; + + for (let j = 0; j < 2; j += 1) { + const promise = new Promise((resolve, reject) => { + let request = null; + for (let i = 0; i < cursorCount / 2; i += 1) { + request = store.openCursor(); + } + + let continued = false; + request.onsuccess = testCase.step_func(() => { + const cursor = request.result; + + if (!continued) { + assert_equals(cursor.key, '42'); + assert_equals(cursor.value.key, '42'); + continued = true; + cursor.continue(); + } else { + assert_equals(cursor, null); + resolve(); + } + }); + request.onerror = () => reject(request.error); + }); + promises.push(promise); + } + return Promise.all(promises); + }).then(database => { + database.close(); + }); + }, `${cursorCount} cursors`); +} + +</script> diff --git a/testing/web-platform/tests/IndexedDB/reading-autoincrement-indexes-cursors.any.js b/testing/web-platform/tests/IndexedDB/reading-autoincrement-indexes-cursors.any.js new file mode 100644 index 0000000000..ff71000683 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/reading-autoincrement-indexes-cursors.any.js @@ -0,0 +1,88 @@ +// META: global=window,dedicatedworker,sharedworker,serviceworker +// META: script=resources/support-promises.js +// META: script=resources/reading-autoincrement-common.js + +promise_test(async testCase => { + const database = await setupAutoincrementDatabase(testCase); + + const transaction = database.transaction(['store'], 'readonly'); + const store = transaction.objectStore('store'); + const index = store.index('by_id'); + + const result = await getAllViaCursor(testCase, index); + assert_equals(result.length, 32); + for (let i = 1; i <= 32; ++i) { + assert_equals(result[i - 1].key, i, 'Autoincrement index key'); + assert_equals(result[i - 1].primaryKey, i, 'Autoincrement primary key'); + assert_equals(result[i - 1].value.id, i, 'Autoincrement key in value'); + assert_equals(result[i - 1].value.name, nameForId(i), + 'String property in value'); + } + + database.close(); +}, 'IDBIndex.openCursor() iterates over an index on the autoincrement key'); + +promise_test(async testCase => { + const database = await setupAutoincrementDatabase(testCase); + + const transaction = database.transaction(['store'], 'readonly'); + const store = transaction.objectStore('store'); + const index = store.index('by_id'); + + const result = await getAllKeysViaCursor(testCase, index); + assert_equals(result.length, 32); + for (let i = 1; i <= 32; ++i) { + assert_equals(result[i - 1].key, i, 'Autoincrement index key'); + assert_equals(result[i - 1].primaryKey, i, 'Autoincrement primary key'); + } + + database.close(); +}, 'IDBIndex.openKeyCursor() iterates over an index on the autoincrement key'); + +promise_test(async testCase => { + const database = await setupAutoincrementDatabase(testCase); + + const transaction = database.transaction(['store'], 'readonly'); + const store = transaction.objectStore('store'); + const index = store.index('by_name'); + + const stringSortedIds = idsSortedByStringCompare(); + + const result = await getAllViaCursor(testCase, index); + assert_equals(result.length, 32); + for (let i = 1; i <= 32; ++i) { + assert_equals(result[i - 1].key, nameForId(stringSortedIds[i - 1]), + 'Index key'); + assert_equals(result[i - 1].primaryKey, stringSortedIds[i - 1], + 'Autoincrement primary key'); + assert_equals(result[i - 1].value.id, stringSortedIds[i - 1], + 'Autoincrement key in value'); + assert_equals(result[i - 1].value.name, nameForId(stringSortedIds[i - 1]), + 'String property in value'); + } + + database.close(); +}, 'IDBIndex.openCursor() iterates over an index not covering the ' + + 'autoincrement key'); + +promise_test(async testCase => { + const database = await setupAutoincrementDatabase(testCase); + + const transaction = database.transaction(['store'], 'readonly'); + const store = transaction.objectStore('store'); + const index = store.index('by_name'); + + const stringSortedIds = idsSortedByStringCompare(); + + const result = await getAllKeysViaCursor(testCase, index); + assert_equals(result.length, 32); + for (let i = 1; i <= 32; ++i) { + assert_equals(result[i - 1].key, nameForId(stringSortedIds[i - 1]), + 'Index key'); + assert_equals(result[i - 1].primaryKey, stringSortedIds[i - 1], + 'Autoincrement primary key'); + } + + database.close(); +}, 'IDBIndex.openKeyCursor() iterates over an index not covering the ' + + 'autoincrement key'); diff --git a/testing/web-platform/tests/IndexedDB/reading-autoincrement-indexes.any.js b/testing/web-platform/tests/IndexedDB/reading-autoincrement-indexes.any.js new file mode 100644 index 0000000000..abff3dd8b5 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/reading-autoincrement-indexes.any.js @@ -0,0 +1,108 @@ +// META: global=window,dedicatedworker,sharedworker,serviceworker +// META: script=resources/support-promises.js +// META: script=resources/reading-autoincrement-common.js + +promise_test(async testCase => { + const database = await setupAutoincrementDatabase(testCase); + + const transaction = database.transaction(['store'], 'readonly'); + const store = transaction.objectStore('store'); + const index = store.index('by_id'); + const request = index.getAll(); + const result = await promiseForRequest(testCase, request); + assert_equals(result.length, 32); + for (let i = 1; i <= 32; ++i) { + assert_equals(result[i - 1].id, i, 'Autoincrement key'); + assert_equals(result[i - 1].name, nameForId(i), 'String property'); + } + + database.close(); +}, 'IDBIndex.getAll() for an index on the autoincrement key'); + +promise_test(async testCase => { + const database = await setupAutoincrementDatabase(testCase); + + const transaction = database.transaction(['store'], 'readonly'); + const store = transaction.objectStore('store'); + const index = store.index('by_id'); + const request = index.getAllKeys(); + const result = await promiseForRequest(testCase, request); + assert_equals(result.length, 32); + for (let i = 1; i <= 32; ++i) + assert_equals(result[i - 1], i, 'Autoincrement key'); + + database.close(); +}, 'IDBIndex.getAllKeys() for an index on the autoincrement key'); + +promise_test(async testCase => { + const database = await setupAutoincrementDatabase(testCase); + + const transaction = database.transaction(['store'], 'readonly'); + const store = transaction.objectStore('store'); + const index = store.index('by_id'); + + for (let i = 1; i <= 32; ++i) { + const request = index.get(i); + const result = await promiseForRequest(testCase, request); + assert_equals(result.id, i, 'autoincrement key'); + assert_equals(result.name, nameForId(i), 'string property'); + } + + database.close(); +}, 'IDBIndex.get() for an index on the autoincrement key'); + +promise_test(async testCase => { + const database = await setupAutoincrementDatabase(testCase); + + const stringSortedIds = idsSortedByStringCompare(); + + const transaction = database.transaction(['store'], 'readonly'); + const store = transaction.objectStore('store'); + const index = store.index('by_name'); + const request = index.getAll(); + const result = await promiseForRequest(testCase, request); + assert_equals(result.length, 32); + for (let i = 1; i <= 32; ++i) { + assert_equals(result[i - 1].id, stringSortedIds[i - 1], + 'autoincrement key'); + assert_equals(result[i - 1].name, nameForId(stringSortedIds[i - 1]), + 'string property'); + } + + database.close(); +}, 'IDBIndex.getAll() for an index not covering the autoincrement key'); + +promise_test(async testCase => { + const database = await setupAutoincrementDatabase(testCase); + + const stringSortedIds = idsSortedByStringCompare(); + + const transaction = database.transaction(['store'], 'readonly'); + const store = transaction.objectStore('store'); + const index = store.index('by_name'); + const request = index.getAllKeys(); + const result = await promiseForRequest(testCase, request); + assert_equals(result.length, 32); + for (let i = 1; i <= 32; ++i) + assert_equals(result[i - 1], stringSortedIds[i - 1], 'String property'); + + database.close(); +}, 'IDBIndex.getAllKeys() returns correct result for an index not covering ' + + 'the autoincrement key'); + +promise_test(async testCase => { + const database = await setupAutoincrementDatabase(testCase); + + const transaction = database.transaction(['store'], 'readonly'); + const store = transaction.objectStore('store'); + const index = store.index('by_name'); + + for (let i = 1; i <= 32; ++i) { + const request = index.get(nameForId(i)); + const result = await promiseForRequest(testCase, request); + assert_equals(result.id, i, 'Autoincrement key'); + assert_equals(result.name, nameForId(i), 'String property'); + } + + database.close(); +}, 'IDBIndex.get() for an index not covering the autoincrement key'); diff --git a/testing/web-platform/tests/IndexedDB/reading-autoincrement-store-cursors.any.js b/testing/web-platform/tests/IndexedDB/reading-autoincrement-store-cursors.any.js new file mode 100644 index 0000000000..da02057e89 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/reading-autoincrement-store-cursors.any.js @@ -0,0 +1,38 @@ +// META: global=window,dedicatedworker,sharedworker,serviceworker +// META: script=resources/support-promises.js +// META: script=resources/reading-autoincrement-common.js + +promise_test(async testCase => { + const database = await setupAutoincrementDatabase(testCase); + + const transaction = database.transaction(['store'], 'readonly'); + const store = transaction.objectStore('store'); + + const result = await getAllViaCursor(testCase, store); + assert_equals(result.length, 32); + for (let i = 1; i <= 32; ++i) { + assert_equals(result[i - 1].key, i, 'Autoincrement key'); + assert_equals(result[i - 1].primaryKey, i, 'Autoincrement primary key'); + assert_equals(result[i - 1].value.id, i, 'Autoincrement key in value'); + assert_equals(result[i - 1].value.name, nameForId(i), + 'string property in value'); + } + + database.close(); +}, 'IDBObjectStore.openCursor() iterates over an autoincrement store'); + +promise_test(async testCase => { + const database = await setupAutoincrementDatabase(testCase); + + const transaction = database.transaction(['store'], 'readonly'); + const store = transaction.objectStore('store'); + + const result = await getAllKeysViaCursor(testCase, store); + assert_equals(result.length, 32); + for (let i = 1; i <= 32; ++i) { + assert_equals(result[i - 1].key, i, 'Incorrect autoincrement key'); + assert_equals(result[i - 1].primaryKey, i, 'Incorrect primary key'); + } + + database.close(); +}, 'IDBObjectStore.openKeyCursor() iterates over an autoincrement store'); diff --git a/testing/web-platform/tests/IndexedDB/reading-autoincrement-store.any.js b/testing/web-platform/tests/IndexedDB/reading-autoincrement-store.any.js new file mode 100644 index 0000000000..531d5417bb --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/reading-autoincrement-store.any.js @@ -0,0 +1,49 @@ +// META: global=window,dedicatedworker,sharedworker,serviceworker +// META: script=resources/support-promises.js +// META: script=resources/reading-autoincrement-common.js + +promise_test(async testCase => { + const database = await setupAutoincrementDatabase(testCase); + + const transaction = database.transaction(['store'], 'readonly'); + const store = transaction.objectStore('store'); + const request = store.getAll(); + const result = await promiseForRequest(testCase, request); + assert_equals(result.length, 32); + for (let i = 1; i <= 32; ++i) { + assert_equals(result[i - 1].id, i, 'Autoincrement key'); + assert_equals(result[i - 1].name, nameForId(i), 'String property'); + } + + database.close(); +}, 'IDBObjectStore.getAll() for an autoincrement store'); + +promise_test(async testCase => { + const database = await setupAutoincrementDatabase(testCase); + + const transaction = database.transaction(['store'], 'readonly'); + const store = transaction.objectStore('store'); + const request = store.getAllKeys(); + const result = await promiseForRequest(testCase, request); + assert_equals(result.length, 32); + for (let i = 1; i <= 32; ++i) + assert_equals(result[i - 1], i, 'Autoincrement key'); + + database.close(); +}, 'IDBObjectStore.getAllKeys() for an autoincrement store'); + +promise_test(async testCase => { + const database = await setupAutoincrementDatabase(testCase); + + const transaction = database.transaction(['store'], 'readonly'); + const store = transaction.objectStore('store'); + + for (let i = 1; i <= 32; ++i) { + const request = store.get(i); + const result = await promiseForRequest(testCase, request); + assert_equals(result.id, i, 'Autoincrement key'); + assert_equals(result.name, nameForId(i), 'String property'); + } + + database.close(); +}, 'IDBObjectStore.get() for an autoincrement store'); diff --git a/testing/web-platform/tests/IndexedDB/ready-state-destroyed-execution-context.html b/testing/web-platform/tests/IndexedDB/ready-state-destroyed-execution-context.html new file mode 100644 index 0000000000..8194052391 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/ready-state-destroyed-execution-context.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>readyState is valid when the execution context is destroyed</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbrequest-readystate"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> +<script> + +function load_iframe() { + return new Promise(resolve => { + const iframe = document.createElement('iframe'); + iframe.onload = () => { resolve(iframe); }; + document.documentElement.appendChild(iframe); + }); +} + +promise_test(async t => { + const iframe = await load_iframe(); + const dbname = location + '-' + t.name; + const openRequest = iframe.contentWindow.indexedDB.open(dbname); + assert_equals(openRequest.readyState, 'pending'); + iframe.remove(); + assert_equals(openRequest.readyState, 'done'); +}, 'readyState accessor is valid after execution context is destroyed'); + +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/IndexedDB/request-abort-ordering.html b/testing/web-platform/tests/IndexedDB/request-abort-ordering.html new file mode 100644 index 0000000000..4374d8de70 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/request-abort-ordering.html @@ -0,0 +1,83 @@ +<!doctype html> +<meta charset="utf8"> +<meta name="timeout" content="long"> +<title>IndexedDB: request abort events are delivered in order</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#abort-transaction"> +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script> +'use strict'; + +promise_test(testCase => { + let requests; + + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + const transaction = database.transaction(['books'], 'readwrite'); + const store = transaction.objectStore('books'); + const index = store.index('by_author'); + const cursorRequest = store.openCursor(IDBKeyRange.lowerBound(0)); + + return new Promise((resolve, reject) => { + cursorRequest.onerror = testCase.step_func(event => { + event.preventDefault(); + reject(cursorRequest.error); + }); + + cursorRequest.onsuccess = testCase.step_func(() => { + const cursor = cursorRequest.result; + requests = [ + () => store.get(123456), + () => index.get('Fred'), + () => store.count(), + () => index.count(), + () => store.put({title: 'Bedrock II', author: 'Barney', isbn: 987 }), + () => store.getAll(), + () => index.getAll(), + () => store.get(999999), + () => index.get('Nobody'), + () => store.openCursor(IDBKeyRange.lowerBound(0)), + () => index.openCursor(IDBKeyRange.lowerBound('')), + () => { cursor.continue(); return cursorRequest; }, + ]; + + const results = []; + const promises = []; + for (let i = 0; i < requests.length; ++i) { + promises.push(new Promise((resolve, reject) => { + const requestId = i; + const request = requests[i](store); + request.onsuccess = testCase.step_func(() => { + reject(new Error( + 'IDB requests should not succeed after transaction abort')); + }); + request.onerror = testCase.step_func(event => { + event.preventDefault(); + results.push([requestId, request.error]); + resolve(); + }); + })); + }; + transaction.abort(); + resolve(Promise.all(promises).then(() => results)); + }); + }); + }).then(results => { + assert_equals( + results.length, requests.length, + 'Promise.all should resolve after all sub-promises resolve'); + for (let i = 0; i < requests.length; ++i) { + assert_equals( + results[i][0], i, + 'error event order should match request order'); + assert_equals( + results[i][1].name, 'AbortError', + 'transaction aborting should result in AbortError on all requests'); + } + }); +}); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/request-event-ordering.html b/testing/web-platform/tests/IndexedDB/request-event-ordering.html new file mode 100644 index 0000000000..71eda0dd1d --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/request-event-ordering.html @@ -0,0 +1,369 @@ +<!doctype html> +<meta charset="utf8"> +<meta name="timeout" content="long"> +<title>IndexedDB: request result events are delivered in order</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#abort-transaction"> +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script> +'use strict'; + +// Should be large enough to trigger large value handling in the IndexedDB +// engines that have special code paths for large values. +const wrapThreshold = 128 * 1024; + +function populateStore(store) { + store.put({id: 1, key: 'k1', value: largeValue(wrapThreshold, 1) }); + store.put({id: 2, key: 'k2', value: ['small-2'] }); + store.put({id: 3, key: 'k3', value: largeValue(wrapThreshold, 3) }); + store.put({id: 4, key: 'k4', value: ['small-4'] }); +} + +// Assigns cursor indexes for operations that require open cursors. +// +// Returns the number of open cursors required to perform all operations. +function assignCursors(operations) { + return cursorCount; +} + +// Opens index cursors for operations that require open cursors. +// +// onsuccess is called if all cursors are opened successfully. Otherwise, +// onerror will be called at least once. +function openCursors(testCase, index, operations, onerror, onsuccess) { + let pendingCursors = 0; + + for (let operation of operations) { + const opcode = operation[0]; + const primaryKey = operation[1]; + let request; + switch (opcode) { + case 'continue': + request = index.openCursor( + IDBKeyRange.lowerBound(`k${primaryKey - 1}`)); + break; + case 'continue-empty': + // k4 is the last key in the data set, so calling continue() will get + // the cursor past the end of the store. + request = index.openCursor(IDBKeyRange.lowerBound('k4')); + break; + default: + continue; + } + + operation[2] = request; + ++pendingCursors; + + request.onsuccess = testCase.step_func(() => { + --pendingCursors; + if (!pendingCursors) + onsuccess(); + }); + request.onerror = testCase.step_func(onerror); + } + + if (!pendingCursors) + onsuccess(); +} + +function doOperation(testCase, store, index, operation, requestId, results) { + const opcode = operation[0]; + const primaryKey = operation[1]; + const cursor = operation[2]; + + return new Promise((resolve, reject) => { + let request; + switch (opcode) { + case 'add': // Tests returning a primary key. + request = store.add( + { key: `k${primaryKey}`, value: [`small-${primaryKey}`] }); + break; + case 'put': // Tests returning a primary key. + request = store.put( + { key: `k${primaryKey}`, value: [`small-${primaryKey}`] }); + break; + case 'put-with-id': // Tests returning success or a primary key. + request = store.put( + { key: `k${primaryKey}`, value: [`small-${primaryKey}`], + id: primaryKey }); + break; + case 'get': // Tests returning a value. + case 'get-empty': // Tests returning undefined. + request = store.get(primaryKey); + break; + case 'getall': // Tests returning an array of values. + request = store.getAll(); + break; + case 'error': // Tests returning an error. + request = store.put( + { key: `k${primaryKey}`, value: [`small-${primaryKey}`] }); + request.onerror = testCase.step_func(event => { + event.preventDefault(); + results.push([requestId, request.error]); + resolve(); + }); + request.onsuccess = testCase.step_func(() => { + reject(new Error('put with duplicate primary key succeded')); + }); + break; + case 'continue': // Tests returning a key, primary key, and value. + request = cursor; + cursor.result.continue(); + request.onsuccess = testCase.step_func(() => { + const result = request.result; + results.push( + [requestId, result.key, result.primaryKey, result.value]); + resolve(); + }); + request.onerror = null; + break; + case 'open': // Tests returning a cursor, key, primary key, and value. + request = index.openCursor(IDBKeyRange.lowerBound(`k${primaryKey}`)); + request.onsuccess = testCase.step_func(() => { + const result = request.result; + results.push( + [requestId, result.key, result.primaryKey, result.value]); + resolve(); + }); + break; + case 'continue-empty': // Tests returning a null result. + request = cursor; + cursor.result.continue(); + request.onsuccess = testCase.step_func(() => { + results.push([requestId, request.result]); + resolve(); + }); + request.onerror = null; + break; + case 'open-empty': // Tests returning a null cursor. + request = index.openCursor(IDBKeyRange.lowerBound(`k${primaryKey}`)); + request.onsuccess = testCase.step_func(() => { + const result = request.result; + results.push([requestId, request.result]); + resolve(); + }); + break; + case 'count': // Tests returning a numeric result. + request = index.count(); + request.onsuccess = testCase.step_func(() => { + results.push([requestId, request.result]); + resolve(); + }); + break; + }; + + if (!request.onsuccess) { + request.onsuccess = testCase.step_func(() => { + results.push([requestId, request.result]); + resolve(); + }); + } + if (!request.onerror) + request.onerror = testCase.step_func(event => { + event.preventDefault(); + reject(request.error); + }); + }); +} + +function checkOperationResult(operation, result, requestId) { + const opcode = operation[0]; + const primaryKey = operation[1]; + + const expectedValue = (primaryKey == 1 || primaryKey == 3) ? + largeValue(wrapThreshold, primaryKey) : [`small-${primaryKey}`]; + + const requestIndex = result[0]; + assert_equals( + requestIndex, requestId, 'result event order should match request order'); + switch (opcode) { + case 'put': + case 'put-with-id': + case 'add': + assert_equals( + result[1], primaryKey, + `${opcode} result should be the new object's primary key`); + break; + case 'get': + assert_equals( + result[1].id, primaryKey, + 'get result should match put value (primary key)'); + assert_equals( + result[1].key, `k${primaryKey}`, + 'get result should match put value (key)'); + assert_equals( + result[1].value.join(','), expectedValue.join(','), + 'get result should match put value (nested value)'); + break; + case 'getall': + assert_equals( + result[1].length, primaryKey, + 'getAll should return all the objects in the store'); + for (let i = 0; i < primaryKey; ++i) { + const object = result[1][i]; + assert_equals( + object.id, i + 1, + `getAll result ${i + 1} should match put value (primary key)`); + assert_equals( + object.key, `k${i + 1}`, + `get result ${i + 1} should match put value (key)`); + + const expectedValue = (i == 0 || i == 2) ? + largeValue(wrapThreshold, i + 1) : [`small-${i + 1}`]; + assert_equals( + object.value.join(','), object.value.join(','), + `get result ${i + 1} should match put value (nested value)`); + } + break; + case 'get-empty': + assert_equals( + result[1], undefined, 'get-empty result should be undefined'); + break; + case 'error': + assert_equals( + result[1].name, 'ConstraintError', + 'incorrect error from put with duplicate primary key'); + break; + case 'continue': + case 'open': + assert_equals( + result[1], `k${primaryKey}`, + `${opcode} key should match the key in the put value`); + assert_equals( + result[2], primaryKey, + `${opcode} primary key should match the put value's primary key`); + assert_equals( + result[3].id, primaryKey, + `${opcode} value should match put value (primary key)`); + assert_equals( + result[3].key, `k${primaryKey}`, + `${opcode} value should match put value (key)`); + assert_equals( + result[3].value.join(','), expectedValue.join(','), + `${opcode} value should match put value (nested value)`); + break; + case 'continue-empty': + case 'open-empty': + assert_equals(result[1], null, `${opcode} result should be null`); + break; + } +} + +function eventsTest(label, operations) { + promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + const store = database.createObjectStore( + 'test-store', { autoIncrement: true, keyPath: 'id' }); + store.createIndex('test-index', 'key', { unique: true }); + populateStore(store); + }).then(database => { + const transaction = database.transaction(['test-store'], 'readwrite'); + const store = transaction.objectStore('test-store'); + const index = store.index('test-index'); + return new Promise((resolve, reject) => { + openCursors(testCase, index, operations, reject, () => { + const results = []; + const promises = []; + for (let i = 0; i < operations.length; ++i) { + const promise = doOperation( + testCase, store, index, operations[i], i, results); + promises.push(promise); + }; + resolve(Promise.all(promises).then(() => results)); + }); + }); + }).then(results => { + assert_equals( + results.length, operations.length, + 'Promise.all should resolve after all sub-promises resolve'); + for (let i = 0; i < operations.length; ++i) + checkOperationResult(operations[i], results[i], i); + }); + }, label); +} + +eventsTest('small values', [ + ['get', 2], + ['count', 4], + ['continue-empty', null], + ['get-empty', 5], + ['add', 5], + ['open', 2], + ['continue', 2], + ['get', 4], + ['get-empty', 6], + ['count', 5], + ['put-with-id', 5], + ['put', 6], + ['error', 3], + ['continue', 4], + ['count', 6], + ['get-empty', 7], + ['open', 4], + ['open-empty', 7], + ['add', 7], +]); + +eventsTest('large values', [ + ['open', 1], + ['get', 1], + ['getall', 4], + ['get', 3], + ['continue', 3], + ['open', 3], +]); + +eventsTest('large value followed by small values', [ + ['get', 1], + ['getall', 4], + ['open', 2], + ['continue-empty', null], + ['get', 2], + ['get-empty', 5], + ['count', 4], + ['continue-empty', null], + ['open-empty', 5], + ['add', 5], + ['error', 1], + ['continue', 2], + ['get-empty', 6], + ['put-with-id', 5], + ['put', 6], +]); + +eventsTest('large values mixed with small values', [ + ['get', 1], + ['get', 2], + ['get-empty', 5], + ['count', 4], + ['continue-empty', null], + ['open', 1], + ['continue', 2], + ['open-empty', 5], + ['getall', 4], + ['open', 2], + ['continue-empty', null], + ['add', 5], + ['get', 3], + ['count', 5], + ['get-empty', 6], + ['put-with-id', 5], + ['getall', 5], + ['continue', 3], + ['open-empty', 6], + ['put', 6], + ['error', 1], + ['continue', 2], + ['open', 4], + ['get-empty', 7], + ['count', 6], + ['continue', 3], + ['add', 7], + ['getall', 7], + ['error', 3], + ['count', 7], +]); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/request_bubble-and-capture.htm b/testing/web-platform/tests/IndexedDB/request_bubble-and-capture.htm new file mode 100644 index 0000000000..8238e2c9ca --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/request_bubble-and-capture.htm @@ -0,0 +1,69 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Bubbling and capturing of request events</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var events = []; + + var open_rq = createdb(async_test()); + open_rq.onupgradeneeded = function(e) { + var db = e.target.result; + var txn = e.target.transaction; + var store = db.createObjectStore("s"); + var rq1 = store.add("", 1); + var rq2 = store.add("", 1); + db.onerror = function(){}; + + log_request(' db', db); + log_request('txn', txn); + log_request('rq1', rq1); + log_request('rq2', rq2); + + // Don't let it get to abort + db.addEventListener('error', function(e) { e.preventDefault() }, false); + } + + open_rq.onsuccess = function(e) { + log("open_rq.success")(e); + assert_array_equals(events, [ + "capture db.success", + "capture txn.success", + "capture rq1.success", + "bubble rq1.success", + + "capture db.error: ConstraintError", + "capture txn.error: ConstraintError", + "capture rq2.error: ConstraintError", + "bubble rq2.error: ConstraintError", + "bubble txn.error: ConstraintError", + "bubble db.error: ConstraintError", + + "open_rq.success" + ], + "events"); + this.done(); + } + + + function log_request(type, obj) { + obj.addEventListener('success', log('capture ' + type + '.success'), true); + obj.addEventListener('success', log('bubble ' + type + '.success'), false); + obj.addEventListener('error', log('capture ' + type + '.error'), true); + obj.addEventListener('error', log('bubble ' + type + '.error'), false); + } + + function log(msg) { + return function(e) { + if(e && e.target && e.target.error) + events.push(msg + ": " + e.target.error.name); + else + events.push(msg); + }; + } +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/resources/cross-origin-helper-frame.html b/testing/web-platform/tests/IndexedDB/resources/cross-origin-helper-frame.html new file mode 100644 index 0000000000..997c5a2b72 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/resources/cross-origin-helper-frame.html @@ -0,0 +1,37 @@ +<!doctype html> +<meta charset="utf8"> +<title>Performs IndexedDB tasks in response to postMessage</title> +<script> +'use strict'; + +self.addEventListener('message', async event => { + const action = event.data.action; + let response = null; + switch(action) { + case 'get-database-names': { + const dbInfos = await self.indexedDB.databases(); + response = dbInfos.map(dbInfo => dbInfo.name); + break; + } + + case 'delete-database': { + const dbName = event.data.name; + await new Promise((resolve, reject) => { + const request = indexedDB.deleteDatabase(dbName); + request.onsuccess = resolve; + request.onerror = reject; + }); + response = true; + break; + } + } + event.source.postMessage({ action, response }, event.origin); + window.close(); +}); + +// Make up for the fact that the opener of a cross-origin window has no way of +// knowing when the window finishes loading. +if (window.opener !== null) { + window.opener.postMessage({ action: null, response: 'ready' }, '*'); +} +</script> diff --git a/testing/web-platform/tests/IndexedDB/resources/file_to_save.txt b/testing/web-platform/tests/IndexedDB/resources/file_to_save.txt new file mode 100644 index 0000000000..3f5238e841 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/resources/file_to_save.txt @@ -0,0 +1 @@ +File to save to IndexedDB.
\ No newline at end of file diff --git a/testing/web-platform/tests/IndexedDB/resources/idb-partitioned-basic-iframe.tentative.html b/testing/web-platform/tests/IndexedDB/resources/idb-partitioned-basic-iframe.tentative.html new file mode 100644 index 0000000000..ed6bbf272f --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/resources/idb-partitioned-basic-iframe.tentative.html @@ -0,0 +1,80 @@ +<!doctype html> +<meta charset="utf-8"> +<script> +const dbName = "users"; + +// Create the database at v1 and detect success via `onsuccess`. +function createDatabase() { + return new Promise((resolve, reject) => { + var dbRequest = window.indexedDB.open(dbName, 1); + dbRequest.onblocked = () => reject(); + dbRequest.onerror = () => reject(); + dbRequest.onsuccess = (e) => { + e.target.result.close(); + resolve(); + } + }); +} + +// Open the database at v2 and detect existance via `onupgradeneeded`. +function doesDatabaseExist() { + let didExist = false; + return new Promise((resolve, reject) => { + var dbRequest = window.indexedDB.open(dbName, 2); + dbRequest.onblocked = () => reject(); + dbRequest.onerror = () => reject(); + dbRequest.onsuccess = (e) => { + e.target.result.close(); + deleteDatabase().then(() => resolve(didExist)); + }; + dbRequest.onupgradeneeded = (e) => { + didExist = e.oldVersion != 0; + }; + }); +} + +// Delete the database and detect success via `onsuccess`. +function deleteDatabase() { + return new Promise((resolve, reject) => { + var dbRequest = window.indexedDB.deleteDatabase(dbName); + dbRequest.onblocked = () => reject(); + dbRequest.onerror = () => reject(); + dbRequest.onsuccess = () => resolve(); + }); +} + +window.addEventListener("load", () => { + if (!parent.opener) { + // Step 2 + createDatabase().then(() => { + parent.postMessage( + {message: "same-site iframe loaded"}, + parent.origin, + ); + }); + } else { + // Step 4 + doesDatabaseExist().then((result) => { + parent.opener.postMessage( + { + message: "cross-site iframe loaded", + doesDatabaseExist: result, + }, + parent.opener.origin, + ); + }); + } +}); + +// Step 6 +window.addEventListener("message", (e) => { + if (e.data.message == "delete database") { + deleteDatabase().then(() => { + e.source.postMessage( + {message: "database deleted"}, + e.source.origin, + ); + }); + } +}); +</script> diff --git a/testing/web-platform/tests/IndexedDB/resources/idb-partitioned-coverage-iframe.tentative.html b/testing/web-platform/tests/IndexedDB/resources/idb-partitioned-coverage-iframe.tentative.html new file mode 100644 index 0000000000..a7be7e2cc9 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/resources/idb-partitioned-coverage-iframe.tentative.html @@ -0,0 +1,601 @@ +<!doctype html> +<meta charset=utf-8> +<script src="/resources/testharness.js"></script> +<script src="support.js"></script> +<!-- TODO(crbug.com/1218100): We should verify the full IDB surface area inside +an iframe, but for now a single test with an assortment of verifications is +sufficient to test third party storage partitioning didn't break anything. --> + +<!-- This block is from delete-request-queue.htm --> +<script> +let saw; +indexeddb_test( + (t, db) => { + saw = expect(t, ['delete1', 'delete2']); + let r = indexedDB.deleteDatabase(db.name); + r.onerror = t.unreached_func('delete should succeed'); + r.onsuccess = t.step_func(e => saw('delete1')); + }, + (t, db) => { + let r = indexedDB.deleteDatabase(db.name); + r.onerror = t.unreached_func('delete should succeed'); + r.onsuccess = t.step_func(e => saw('delete2')); + + db.close(); + }, + 'Deletes are processed in order'); +</script> + +<!-- This block is from idbcursor-advance-continue-async.htm --> +<script> +function upgrade_func(t, db, tx) { + var objStore = db.createObjectStore("test"); + objStore.createIndex("index", ""); + + objStore.add("data", 1); + objStore.add("data2", 2); +} + +indexeddb_test( + upgrade_func, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").openCursor(); + + rq.onsuccess = t.step_func(function(e) { + if (!e.target.result) { + assert_equals(count, 2, 'count'); + t.done(); + return; + } + var cursor = e.target.result; + + switch(count) { + case 0: + assert_equals(cursor.value, "data") + assert_equals(cursor.key, 1) + cursor.advance(1) + assert_equals(cursor.value, "data") + assert_equals(cursor.key, 1) + break + + case 1: + assert_equals(cursor.value, "data2") + assert_equals(cursor.key, 2) + cursor.advance(1) + assert_equals(cursor.value, "data2") + assert_equals(cursor.key, 2) + break + + default: + assert_unreached("Unexpected count: " + count) + } + + count++; + }); + rq.onerror = t.unreached_func("unexpected error") + }, + document.title + " - advance" +); + +indexeddb_test( + upgrade_func, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").index("index").openCursor(); + + rq.onsuccess = t.step_func(function(e) { + if (!e.target.result) { + assert_equals(count, 2, 'count'); + t.done(); + return; + } + var cursor = e.target.result; + + switch(count) { + case 0: + assert_equals(cursor.value, "data") + assert_equals(cursor.key, "data") + assert_equals(cursor.primaryKey, 1) + cursor.continue("data2") + assert_equals(cursor.value, "data") + assert_equals(cursor.key, "data") + assert_equals(cursor.primaryKey, 1) + break + + case 1: + assert_equals(cursor.value, "data2") + assert_equals(cursor.key, "data2") + assert_equals(cursor.primaryKey, 2) + cursor.continue() + assert_equals(cursor.value, "data2") + assert_equals(cursor.key, "data2") + assert_equals(cursor.primaryKey, 2) + break + + default: + assert_unreached("Unexpected count: " + count) + } + + count++; + }); + rq.onerror = t.unreached_func("unexpected error") + }, + document.title + " - continue" +); + +indexeddb_test( + upgrade_func, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").index("index").openCursor(); + + rq.onsuccess = t.step_func(function(e) { + if (!e.target.result) { + assert_equals(count, 2, 'count'); + t.done(); + return; + } + var cursor = e.target.result; + cursor.advance(1) + + switch(count) { + case 0: + assert_equals(cursor.value, "data") + assert_equals(cursor.key, "data") + assert_equals(cursor.primaryKey, 1) + break + + case 1: + assert_equals(cursor.value, "data2") + assert_equals(cursor.key, "data2") + assert_equals(cursor.primaryKey, 2) + break + + default: + assert_unreached("Unexpected count: " + count) + } + + count++; + }); + rq.onerror = t.unreached_func("unexpected error") + }, + document.title + " - fresh advance still async" +); + +indexeddb_test( + upgrade_func, + function(t, db) { + var count = 0; + var rq = db.transaction("test", "readonly", {durability: 'relaxed'}).objectStore("test").openCursor(); + + rq.onsuccess = t.step_func(function(e) { + if (!e.target.result) { + assert_equals(count, 2, 'count'); + t.done(); + return; + } + var cursor = e.target.result; + cursor.continue() + + switch(count) { + case 0: + assert_equals(cursor.value, "data") + assert_equals(cursor.key, 1) + break + + case 1: + assert_equals(cursor.value, "data2") + assert_equals(cursor.key, 2) + break + + default: + assert_unreached("Unexpected count: " + count) + } + + count++; + }); + rq.onerror = t.unreached_func("unexpected error") + }, + document.title + " - fresh continue still async" +); +</script> + +<!-- This block is from idbindex_getAll.htm --> +<script> +var alphabet = 'abcdefghijklmnopqrstuvwxyz'.split(''); +var ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); + +function getall_test(func, name) { + indexeddb_test( + function(t, connection, tx) { + var store = connection.createObjectStore('generated', + {autoIncrement: true, keyPath: 'id'}); + var index = store.createIndex('test_idx', 'upper'); + alphabet.forEach(function(letter) { + store.put({ch: letter, upper: letter.toUpperCase()}); + }); + + store = connection.createObjectStore('out-of-line', null); + index = store.createIndex('test_idx', 'upper'); + alphabet.forEach(function(letter) { + store.put({ch: letter, upper: letter.toUpperCase()}, letter); + }); + + store = connection.createObjectStore('out-of-line-not-unique', null); + index = store.createIndex('test_idx', 'half'); + alphabet.forEach(function(letter) { + if (letter <= 'm') + store.put({ch: letter, half: 'first'}, letter); + else + store.put({ch: letter, half: 'second'}, letter); + }); + + store = connection.createObjectStore('out-of-line-multi', null); + index = store.createIndex('test_idx', 'attribs', {multiEntry: true}); + alphabet.forEach(function(letter) { + attrs = []; + if (['a', 'e', 'i', 'o', 'u'].indexOf(letter) != -1) + attrs.push('vowel'); + else + attrs.push('consonant'); + if (letter == 'a') + attrs.push('first'); + if (letter == 'z') + attrs.push('last'); + store.put({ch: letter, attribs: attrs}, letter); + }); + + store = connection.createObjectStore('empty', null); + index = store.createIndex('test_idx', 'upper'); + }, + func, + name + ); +} + +function createGetAllRequest(t, storeName, connection, range, maxCount) { + var transaction = connection.transaction(storeName, 'readonly'); + var store = transaction.objectStore(storeName); + var index = store.index('test_idx'); + var req = index.getAll(range, maxCount); + req.onerror = t.unreached_func('getAll request should succeed'); + return req; +} + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'out-of-line', connection, 'C'); + req.onsuccess = t.step_func(function(evt) { + var data = evt.target.result; + assert_class_string(data, 'Array', 'result should be an array'); + assert_array_equals(data.map(function(e) { return e.ch; }), ['c']); + assert_array_equals(data.map(function(e) { return e.upper; }), ['C']); + t.done(); + }); + }, 'Single item get'); + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'empty', connection); + req.onsuccess = t.step_func(function(evt) { + assert_array_equals(evt.target.result, [], + 'getAll() on empty object store should return an empty array'); + t.done(); + }); + }, 'Empty object store'); + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'out-of-line', connection); + req.onsuccess = t.step_func(function(evt) { + var data = evt.target.result; + assert_class_string(data, 'Array', 'result should be an array'); + assert_array_equals(data.map(function(e) { return e.ch; }), alphabet); + assert_array_equals(data.map(function(e) { return e.upper; }), ALPHABET); + t.done(); + }); + }, 'Get all keys'); + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'out-of-line', connection, undefined, + 10); + req.onsuccess = t.step_func(function(evt) { + var data = evt.target.result; + assert_class_string(data, 'Array', 'result should be an array'); + assert_array_equals(data.map(function(e) { return e.ch; }), 'abcdefghij'.split('')); + assert_array_equals(data.map(function(e) { return e.upper; }), 'ABCDEFGHIJ'.split('')); + t.done(); + }); + }, 'maxCount=10'); + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'out-of-line', connection, + IDBKeyRange.bound('G', 'M')); + req.onsuccess = t.step_func(function(evt) { + var data = evt.target.result; + assert_array_equals(data.map(function(e) { return e.ch; }), 'ghijklm'.split('')); + assert_array_equals(data.map(function(e) { return e.upper; }), 'GHIJKLM'.split('')); + t.done(); + }); + }, 'Get bound range'); + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'out-of-line', connection, + IDBKeyRange.bound('G', 'M'), 3); + req.onsuccess = t.step_func(function(evt) { + var data = evt.target.result; + assert_class_string(data, 'Array', 'result should be an array'); + assert_array_equals(data.map(function(e) { return e.ch; }), 'ghi'.split('')); + assert_array_equals(data.map(function(e) { return e.upper; }), 'GHI'.split('')); + t.done(); + }); + }, 'Get bound range with maxCount'); + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'out-of-line', connection, + IDBKeyRange.bound('G', 'K', false, true)); + req.onsuccess = t.step_func(function(evt) { + var data = evt.target.result; + assert_class_string(data, 'Array', 'result should be an array'); + assert_array_equals(data.map(function(e) { return e.ch; }), 'ghij'.split('')); + assert_array_equals(data.map(function(e) { return e.upper; }), 'GHIJ'.split('')); + t.done(); + }); + }, 'Get upper excluded'); + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'out-of-line', connection, + IDBKeyRange.bound('G', 'K', true, false)); + req.onsuccess = t.step_func(function(evt) { + var data = evt.target.result; + assert_class_string(data, 'Array', 'result should be an array'); + assert_array_equals(data.map(function(e) { return e.ch; }), 'hijk'.split('')); + assert_array_equals(data.map(function(e) { return e.upper; }), 'HIJK'.split('')); + t.done(); + }); + }, 'Get lower excluded'); + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'generated', + connection, IDBKeyRange.bound(4, 15), 3); + req.onsuccess = t.step_func(function(evt) { + var data = evt.target.result; + assert_true(Array.isArray(data)); + assert_equals(data.length, 0); + t.done(); + }); + }, 'Get bound range (generated) with maxCount'); + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'out-of-line', + connection, "Doesn't exist"); + req.onsuccess = t.step_func(function(evt) { + assert_array_equals(evt.target.result, [], + 'getAll() using a nonexistent key should return an empty array'); + t.done(); + req.onerror = t.unreached_func('getAll request should succeed'); + }); + }, 'Non existent key'); + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'out-of-line', connection, + undefined, 0); + req.onsuccess = t.step_func(function(evt) { + var data = evt.target.result; + assert_class_string(data, 'Array', 'result should be an array'); + assert_array_equals(data.map(function(e) { return e.ch; }), alphabet); + assert_array_equals(data.map(function(e) { return e.upper; }), ALPHABET); + t.done(); + }); + }, 'maxCount=0'); + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'out-of-line-not-unique', connection, + 'first'); + req.onsuccess = t.step_func(function(evt) { + var data = evt.target.result; + assert_class_string(data, 'Array', 'result should be an array'); + assert_array_equals(data.map(function(e) { return e.ch; }), 'abcdefghijklm'.split('')); + assert_true(data.every(function(e) { return e.half === 'first'; })); + t.done(); + }); + }, 'Retrieve multiEntry key'); + +getall_test(function(t, connection) { + var req = createGetAllRequest(t, 'out-of-line-multi', connection, + 'vowel'); + req.onsuccess = t.step_func(function(evt) { + var data = evt.target.result; + assert_class_string(data, 'Array', 'result should be an array'); + assert_array_equals(data.map(function(e) { return e.ch; }), ['a', 'e', 'i', 'o', 'u']); + assert_array_equals(data[0].attribs, ['vowel', 'first']); + assert_true(data.every(function(e) { return e.attribs[0] === 'vowel'; })); + t.done(); + }); + }, 'Retrieve one key multiple values'); +</script> + +<!-- This block is from idbobjectstore_openKeyCursor.htm --> +<script> +function store_test(func, name) { + indexeddb_test( + function(t, db, tx) { + var store = db.createObjectStore("store"); + for (var i = 0; i < 10; ++i) { + store.put("value: " + i, i); + } + }, + function(t, db) { + var tx = db.transaction("store", "readonly", {durability: 'relaxed'}); + var store = tx.objectStore("store"); + func(t, db, tx, store); + }, name); +} + +store_test(function(t, db, tx, store) { + var expected = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + var actual = []; + var request = store.openKeyCursor(); + request.onsuccess = t.step_func(function() { + var cursor = request.result; + if (!cursor) + return; + assert_equals(cursor.direction, "next"); + assert_false("value" in cursor); + assert_equals(indexedDB.cmp(cursor.key, cursor.primaryKey), 0); + actual.push(cursor.key); + cursor.continue(); + }); + + tx.onabort = t.unreached_func("transaction aborted"); + tx.oncomplete = t.step_func(function() { + assert_array_equals(expected, actual, "keys should match"); + t.done(); + }); + +}, "IDBObjectStore.openKeyCursor() - forward iteration"); + +store_test(function(t, db, tx, store) { + var expected = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]; + var actual = []; + var request = store.openKeyCursor(null, "prev"); + request.onsuccess = t.step_func(function() { + var cursor = request.result; + if (!cursor) + return; + assert_equals(cursor.direction, "prev"); + assert_false("value" in cursor); + assert_equals(indexedDB.cmp(cursor.key, cursor.primaryKey), 0); + actual.push(cursor.key); + cursor.continue(); + }); + + tx.onabort = t.unreached_func("transaction aborted"); + tx.oncomplete = t.step_func(function() { + assert_array_equals(expected, actual, "keys should match"); + t.done(); + }); + +}, "IDBObjectStore.openKeyCursor() - reverse iteration"); + +store_test(function(t, db, tx, store) { + var expected = [4, 5, 6]; + var actual = []; + var request = store.openKeyCursor(IDBKeyRange.bound(4, 6)); + request.onsuccess = t.step_func(function() { + var cursor = request.result; + if (!cursor) + return; + assert_equals(cursor.direction, "next"); + assert_false("value" in cursor); + assert_equals(indexedDB.cmp(cursor.key, cursor.primaryKey), 0); + actual.push(cursor.key); + cursor.continue(); + }); + + tx.onabort = t.unreached_func("transaction aborted"); + tx.oncomplete = t.step_func(function() { + assert_array_equals(expected, actual, "keys should match"); + t.done(); + }); + +}, "IDBObjectStore.openKeyCursor() - forward iteration with range"); + +store_test(function(t, db, tx, store) { + var expected = [6, 5, 4]; + var actual = []; + var request = store.openKeyCursor(IDBKeyRange.bound(4, 6), "prev"); + request.onsuccess = t.step_func(function() { + var cursor = request.result; + if (!cursor) + return; + assert_equals(cursor.direction, "prev"); + assert_false("value" in cursor); + assert_equals(indexedDB.cmp(cursor.key, cursor.primaryKey), 0); + actual.push(cursor.key); + cursor.continue(); + }); + + tx.onabort = t.unreached_func("transaction aborted"); + tx.oncomplete = t.step_func(function() { + assert_array_equals(expected, actual, "keys should match"); + t.done(); + }); + +}, "IDBObjectStore.openKeyCursor() - reverse iteration with range"); + +store_test(function(t, db, tx, store) { + assert_throws_dom("DataError", function() { store.openKeyCursor(NaN); }, + "openKeyCursor should throw on invalid number key"); + assert_throws_dom("DataError", function() { store.openKeyCursor(new Date(NaN)); }, + "openKeyCursor should throw on invalid date key"); + assert_throws_dom("DataError", function() { + var cycle = []; + cycle.push(cycle); + store.openKeyCursor(cycle); + }, "openKeyCursor should throw on invalid array key"); + assert_throws_dom("DataError", function() { store.openKeyCursor({}); }, + "openKeyCursor should throw on invalid key type"); + setTimeout(t.step_func(function() { + assert_throws_dom("TransactionInactiveError", function() { store.openKeyCursor(); }, + "openKeyCursor should throw if transaction is inactive"); + t.done(); + }), 0); + +}, "IDBObjectStore.openKeyCursor() - invalid inputs"); +</script> + +<!-- This block is from idbtransaction.htm --> +<script> +async_test(function(t) { + var dbname = "idbtransaction-" + document.location + t.name; + indexedDB.deleteDatabase(dbname); + var open_rq = indexedDB.open(dbname); + + open_rq.onblocked = t.unreached_func('open_rq.onblocked'); + open_rq.onerror = t.unreached_func('open_rq.onerror'); + + open_rq.onupgradeneeded = t.step_func(function(e) { + t.add_cleanup(function() { + open_rq.onerror = function(e) { + e.preventDefault(); + }; + open_rq.result.close(); + indexedDB.deleteDatabase(open_rq.result.name); + }); + + assert_equals(e.target, open_rq, "e.target is reusing the same IDBOpenDBRequest"); + assert_equals(e.target.transaction, open_rq.transaction, "IDBOpenDBRequest.transaction"); + + assert_true(e.target.transaction instanceof IDBTransaction, "transaction instanceof IDBTransaction"); + t.done(); + }); + +}, document.title + " - request gotten by the handler"); + +async_test(function(t) { + var dbname = "idbtransaction-" + document.location + t.name; + indexedDB.deleteDatabase(dbname); + var open_rq = indexedDB.open(dbname); + + assert_equals(open_rq.transaction, null, "IDBOpenDBRequest.transaction"); + assert_equals(open_rq.source, null, "IDBOpenDBRequest.source"); + assert_equals(open_rq.readyState, "pending", "IDBOpenDBRequest.readyState"); + + assert_true(open_rq instanceof IDBOpenDBRequest, "open_rq instanceof IDBOpenDBRequest"); + assert_equals(open_rq + "", "[object IDBOpenDBRequest]", "IDBOpenDBRequest (open_rq)"); + + open_rq.onblocked = t.unreached_func('open_rq.onblocked'); + open_rq.onerror = t.unreached_func('open_rq.onerror'); + + open_rq.onupgradeneeded = t.step_func(function() { + t.add_cleanup(function() { + open_rq.onerror = function(e) { + e.preventDefault(); + }; + open_rq.result.close(); + indexedDB.deleteDatabase(open_rq.result.name); + }); + t.done(); + }); + +}, document.title + " - request returned by open()"); +</script> diff --git a/testing/web-platform/tests/IndexedDB/resources/idb-partitioned-persistence-iframe.tentative.html b/testing/web-platform/tests/IndexedDB/resources/idb-partitioned-persistence-iframe.tentative.html new file mode 100644 index 0000000000..ad6869f945 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/resources/idb-partitioned-persistence-iframe.tentative.html @@ -0,0 +1,76 @@ +<!doctype html> +<meta charset="utf-8"> +<script> +const dbName = "users"; + +// Create the database at v1 and detect success via `onsuccess`. +function createDatabase() { + return new Promise((resolve, reject) => { + var dbRequest = window.indexedDB.open(dbName, 1); + dbRequest.onblocked = () => reject(); + dbRequest.onerror = () => reject(); + dbRequest.onsuccess = (e) => { + e.target.result.close(); + resolve(); + } + }); +} + +// Open the database at v2 and detect existance via `onupgradeneeded`. +function doesDatabaseExist() { + let didExist = false; + return new Promise((resolve, reject) => { + var dbRequest = window.indexedDB.open(dbName, 2); + dbRequest.onblocked = () => reject(); + dbRequest.onerror = () => reject(); + dbRequest.onsuccess = (e) => { + e.target.result.close(); + deleteDatabase().then(() => resolve(didExist)); + }; + dbRequest.onupgradeneeded = (e) => { + didExist = e.oldVersion != 0; + }; + }); +} + +// Delete the database and detect success via `onsuccess`. +function deleteDatabase() { + return new Promise((resolve, reject) => { + var dbRequest = window.indexedDB.deleteDatabase(dbName); + dbRequest.onblocked = () => reject(); + dbRequest.onerror = () => reject(); + dbRequest.onsuccess = () => resolve(); + }); +} + +// Step 2 +window.addEventListener("load", () => { + parent.postMessage( + {message: "iframe loaded"}, + "*", + ); +}); + +window.addEventListener("message", (e) => { + if (e.data.message == "create database") { + // Step 4 + createDatabase().then(() => { + parent.postMessage( + {message: "database created"}, + "*", + ); + }); + } else if (e.data.message == "check database") { + // Step 6 + doesDatabaseExist().then((result) => { + parent.postMessage( + { + message: "database checked", + doesDatabaseExist: result, + }, + "*", + ); + }); + } +}); +</script> diff --git a/testing/web-platform/tests/IndexedDB/resources/idbfactory-origin-isolation-iframe.html b/testing/web-platform/tests/IndexedDB/resources/idbfactory-origin-isolation-iframe.html new file mode 100644 index 0000000000..0f16bcadaa --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/resources/idbfactory-origin-isolation-iframe.html @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<title>This iframe keeps a transaction on a database alive indefinitely to test</title> +<script> + +// Keeps the passed transaction alive indefinitely (by making requests +// against the named store). Returns a function that asserts that the +// transaction has not already completed and then ends the request loop so that +// the transaction may autocommit and complete. +function keep_alive(tx, store_name) { + let completed = false; + tx.addEventListener('complete', () => { completed = true; }); + + let keepSpinning = true; + + function spin() { + if (!keepSpinning) + return; + tx.objectStore(store_name).get(0).onsuccess = spin; + } + spin(); + + return () => { + assert_false(completed, 'Transaction completed while kept alive'); + keepSpinning = false; + }; +} + +async function run() { + const dbs_to_delete = await indexedDB.databases(); + for (const db_info of dbs_to_delete) { + let request = indexedDB.deleteDatabase(db_info.name); + await new Promise((resolve, reject) => { + request.onsuccess = resolve; + request.onerror = reject; + }); + } + + var openRequest = indexedDB.open('db-isolation-test'); + openRequest.onupgradeneeded = () => { + openRequest.result.createObjectStore('s'); + }; + openRequest.onsuccess = () => { + var tx = openRequest.result.transaction('s', 'readonly', {durability: 'relaxed'}); + keep_alive(tx, 's'); + window.parent.postMessage("keep_alive_started", "*"); + }; +} + +run(); +</script> diff --git a/testing/web-platform/tests/IndexedDB/resources/idbworker.js b/testing/web-platform/tests/IndexedDB/resources/idbworker.js new file mode 100644 index 0000000000..04a421fa38 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/resources/idbworker.js @@ -0,0 +1,34 @@ +var db + +self.addEventListener('message', MessageHandler, false) + +function MessageHandler(e) +{ + var open_rq, idb = self.indexedDB || self.msIndexedDB || self.webkitIndexedDB || self.mozIndexedDB + + if (!idb) + { + self.postMessage(false) + return + } + else + self.postMessage(true) + + open_rq = idb.open("webworker101", 1) + + open_rq.onupgradeneeded = function(e) { + db = e.target.result + db.createObjectStore("store") + .add("test", 1) + } + open_rq.onsuccess = function(e) { + db = e.target.result + db.onerror = function() { self.postMessage("db.error") } + db.transaction("store", "readonly", {durability: 'relaxed'}).objectStore("store").get(1).onsuccess = function(e) { + self.postMessage(e.target.result) + db.close() + } + } + open_rq.onerror = function() { self.postMessage("open.error") } + open_rq.onblocked = function() { self.postMessage("open.blocked") } +} diff --git a/testing/web-platform/tests/IndexedDB/resources/interleaved-cursors-common.js b/testing/web-platform/tests/IndexedDB/resources/interleaved-cursors-common.js new file mode 100644 index 0000000000..09ed078c1f --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/resources/interleaved-cursors-common.js @@ -0,0 +1,188 @@ +// Infrastructure shared by interleaved-cursors-{small,large}.html + +// Number of objects that each iterator goes over. +const itemCount = 10; + +// Ratio of small objects to large objects. +const largeObjectRatio = 5; + +// Size of large objects. This should exceed the size of a block in the storage +// method underlying the browser's IndexedDB implementation. For example, this +// needs to exceed the LevelDB block size on Chrome, and the SQLite block size +// on Firefox. +const largeObjectSize = 48 * 1024; + +function objectKey(cursorIndex, itemIndex) { + return `${cursorIndex}-key-${itemIndex}`; +} + +function objectValue(cursorIndex, itemIndex) { + if ((cursorIndex * itemCount + itemIndex) % largeObjectRatio === 0) { + // We use a typed array (as opposed to a string) because IndexedDB + // implementations may serialize strings using UTF-8 or UTF-16, yielding + // larger IndexedDB entries than we'd expect. It's very unlikely that an + // IndexedDB implementation would use anything other than the raw buffer to + // serialize a typed array. + const buffer = new Uint8Array(largeObjectSize); + + // Some IndexedDB implementations, like LevelDB, compress their data blocks + // before storing them to disk. We use a simple 32-bit xorshift PRNG, which + // should be sufficient to foil any fast generic-purpose compression scheme. + + // 32-bit xorshift - the seed can't be zero + let state = 1000 + (cursorIndex * itemCount + itemIndex); + + for (let i = 0; i < largeObjectSize; ++i) { + state ^= state << 13; + state ^= state >> 17; + state ^= state << 5; + buffer[i] = state & 0xff; + } + + return buffer; + } + return [cursorIndex, 'small', itemIndex]; +} + +// Writes the objects to be read by one cursor. Returns a promise that resolves +// when the write completes. +// +// We want to avoid creating a large transaction, because that is outside the +// test's scope, and it's a bad practice. So we break up the writes across +// multiple transactions. For simplicity, each transaction writes all the +// objects that will be read by a cursor. +function writeCursorObjects(database, cursorIndex) { + return new Promise((resolve, reject) => { + const transaction = database.transaction('cache', 'readwrite', {durability: 'relaxed'}); + transaction.onabort = () => { reject(transaction.error); }; + + const store = transaction.objectStore('cache'); + for (let i = 0; i < itemCount; ++i) { + store.put({ + key: objectKey(cursorIndex, i), value: objectValue(cursorIndex, i)}); + } + transaction.oncomplete = resolve; + }); +} + +// Returns a promise that resolves when the store has been populated. +function populateTestStore(testCase, database, cursorCount) { + let promiseChain = Promise.resolve(); + + for (let i = 0; i < cursorCount; ++i) + promiseChain = promiseChain.then(() => writeCursorObjects(database, i)); + + return promiseChain; +} + +// Reads cursors in an interleaved fashion, as shown below. +// +// Given N cursors, each of which points to the beginning of a K-item sequence, +// the following accesses will be made. +// +// OC(i) = open cursor i +// RD(i, j) = read result of cursor i, which should be at item j +// CC(i) = continue cursor i +// | = wait for onsuccess on the previous OC or CC +// +// OC(1) | RD(1, 1) OC(2) | RD(2, 1) OC(3) | ... | RD(n-1, 1) CC(n) | +// RD(n, 1) CC(1) | RD(1, 2) CC(2) | RD(2, 2) CC(3) | ... | RD(n-1, 2) CC(n) | +// RD(n, 2) CC(1) | RD(1, 3) CC(2) | RD(2, 3) CC(3) | ... | RD(n-1, 3) CC(n) | +// ... +// RD(n, k-1) CC(1) | RD(1, k) CC(2) | RD(2, k) CC(3) | ... | RD(n-1, k) CC(n) | +// RD(n, k) done +function interleaveCursors(testCase, store, cursorCount) { + return new Promise((resolve, reject) => { + // The cursors used for iteration are stored here so each cursor's onsuccess + // handler can call continue() on the next cursor. + const cursors = []; + + // The results of IDBObjectStore.openCursor() calls are stored here so we + // we can change the requests' onsuccess handler after every + // IDBCursor.continue() call. + const requests = []; + + const checkCursorState = (cursorIndex, itemIndex) => { + const cursor = cursors[cursorIndex]; + assert_equals(cursor.key, objectKey(cursorIndex, itemIndex)); + assert_equals(cursor.value.key, objectKey(cursorIndex, itemIndex)); + assert_equals( + cursor.value.value.join('-'), + objectValue(cursorIndex, itemIndex).join('-')); + }; + + const openCursor = (cursorIndex, callback) => { + const request = store.openCursor( + IDBKeyRange.lowerBound(objectKey(cursorIndex, 0))); + requests[cursorIndex] = request; + + request.onsuccess = testCase.step_func(() => { + const cursor = request.result; + cursors[cursorIndex] = cursor; + checkCursorState(cursorIndex, 0); + callback(); + }); + request.onerror = event => reject(request.error); + }; + + const readItemFromCursor = (cursorIndex, itemIndex, callback) => { + const request = requests[cursorIndex]; + request.onsuccess = testCase.step_func(() => { + const cursor = request.result; + cursors[cursorIndex] = cursor; + checkCursorState(cursorIndex, itemIndex); + callback(); + }); + + const cursor = cursors[cursorIndex]; + cursor.continue(); + }; + + // We open all the cursors one at a time, then cycle through the cursors and + // call continue() on each of them. This access pattern causes maximal + // trashing to an LRU cursor cache. Eviction scheme aside, any cache will + // have to evict some cursors, and this access pattern verifies that the + // cache correctly restores the state of evicted cursors. + const steps = []; + for (let cursorIndex = 0; cursorIndex < cursorCount; ++cursorIndex) + steps.push(openCursor.bind(null, cursorIndex)); + for (let itemIndex = 1; itemIndex < itemCount; ++itemIndex) { + for (let cursorIndex = 0; cursorIndex < cursorCount; ++cursorIndex) + steps.push(readItemFromCursor.bind(null, cursorIndex, itemIndex)); + } + + const runStep = (stepIndex) => { + if (stepIndex === steps.length) { + resolve(); + return; + } + steps[stepIndex](() => { runStep(stepIndex + 1); }); + }; + runStep(0); + }); +} + +function cursorTest(cursorCount) { + promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + const store = database.createObjectStore('cache', + { keyPath: 'key', autoIncrement: true }); + }).then(database => { + return populateTestStore(testCase, database, cursorCount).then( + () => database); + }).then(database => { + database.close(); + }).then(() => { + return openDatabase(testCase); + }).then(database => { + const transaction = database.transaction('cache', 'readonly', {durability: 'relaxed'}); + transaction.onabort = () => { reject(transaction.error); }; + + const store = transaction.objectStore('cache'); + return interleaveCursors(testCase, store, cursorCount).then( + () => database); + }).then(database => { + database.close(); + }); + }, `${cursorCount} cursors`); +} diff --git a/testing/web-platform/tests/IndexedDB/resources/nested-cloning-common.js b/testing/web-platform/tests/IndexedDB/resources/nested-cloning-common.js new file mode 100644 index 0000000000..db5f710ceb --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/resources/nested-cloning-common.js @@ -0,0 +1,211 @@ +'use strict'; + +// Should be large enough to trigger large value handling in the IndexedDB +// engines that have special code paths for large values. +const wrapThreshold = 128 * 1024; + +// Returns an IndexedDB value created from a descriptor. +// +// See the bottom of the file for descriptor samples. +function createValue(descriptor) { + if (typeof(descriptor) != 'object') + return descriptor; + + if (Array.isArray(descriptor)) + return descriptor.map((element) => createValue(element)); + + if (!descriptor.hasOwnProperty('type')) { + const value = {}; + for (let property of Object.getOwnPropertyNames(descriptor)) + value[property] = createValue(descriptor[property]); + return value; + } + + switch (descriptor.type) { + case 'blob': + return new Blob( + [largeValue(descriptor.size, descriptor.seed)], + { type: descriptor.mimeType }); + case 'buffer': + return largeValue(descriptor.size, descriptor.seed); + } +} + +// Checks an IndexedDB value against a descriptor. +// +// Returns a Promise that resolves if the value passes the check. +// +// See the bottom of the file for descriptor samples. +function checkValue(testCase, value, descriptor) { + if (typeof(descriptor) != 'object') { + assert_equals( + descriptor, value, + 'IndexedDB result should match put() argument'); + return Promise.resolve(); + } + + if (Array.isArray(descriptor)) { + assert_true( + Array.isArray(value), + 'IndexedDB result type should match put() argument'); + assert_equals( + descriptor.length, value.length, + 'IndexedDB result array size should match put() argument'); + + const subChecks = []; + for (let i = 0; i < descriptor.length; ++i) + subChecks.push(checkValue(testCase, value[i], descriptor[i])); + return Promise.all(subChecks); + } + + if (!descriptor.hasOwnProperty('type')) { + assert_array_equals( + Object.getOwnPropertyNames(value).sort(), + Object.getOwnPropertyNames(descriptor).sort(), + 'IndexedDB result object properties should match put() argument'); + const subChecks = []; + return Promise.all(Object.getOwnPropertyNames(descriptor).map(property => + checkValue(testCase, value[property], descriptor[property]))); + } + + switch (descriptor.type) { + case 'blob': + assert_class_string( + value, 'Blob', + 'IndexedDB result class should match put() argument'); + assert_equals( + descriptor.mimeType, value.type, + 'IndexedDB result Blob MIME type should match put() argument'); + assert_equals(descriptor.size, value.size, 'incorrect Blob size'); + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onloadend = testCase.step_func(() => { + if (reader.error) { + reject(reader.error); + return; + } + const view = new Uint8Array(reader.result); + assert_equals( + view.join(','), + largeValue(descriptor.size, descriptor.seed).join(','), + 'IndexedDB result Blob content should match put() argument'); + resolve(); + }); + reader.readAsArrayBuffer(value); + }); + + case 'buffer': + assert_class_string( + value, 'Uint8Array', + 'IndexedDB result type should match put() argument'); + assert_equals( + value.join(','), + largeValue(descriptor.size, descriptor.seed).join(','), + 'IndexedDB result typed array content should match put() argument'); + return Promise.resolve(); + } +} + +function cloningTestInternal(label, valueDescriptors, options) { + promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + let store; + if (options.useKeyGenerator) { + store = database.createObjectStore( + 'test-store', { keyPath: 'primaryKey', autoIncrement: true }); + } else { + store = database.createObjectStore('test-store'); + } + for (let i = 0; i < valueDescriptors.length; ++i) { + if (options.useKeyGenerator) { + store.put(createValue(valueDescriptors[i])); + } else { + store.put(createValue(valueDescriptors[i]), i + 1); + } + } + }).then(database => { + const transaction = database.transaction(['test-store'], 'readonly'); + const store = transaction.objectStore('test-store'); + const subChecks = []; + let resultIndex = 0; + for (let i = 0; i < valueDescriptors.length; ++i) { + subChecks.push(new Promise((resolve, reject) => { + const requestIndex = i; + const primaryKey = requestIndex + 1; + const request = store.get(primaryKey); + request.onerror = + testCase.step_func(() => { reject(request.error); }); + request.onsuccess = testCase.step_func(() => { + assert_equals( + resultIndex, requestIndex, + 'IDBRequest success events should be fired in request order'); + ++resultIndex; + + const result = request.result; + if (options.useKeyGenerator) { + assert_equals( + result.primaryKey, primaryKey, + 'IndexedDB result should have auto-incremented primary key'); + delete result.primaryKey; + } + resolve(checkValue( + testCase, result, valueDescriptors[requestIndex])); + }); + })); + } + + subChecks.push(new Promise((resolve, reject) => { + const requestIndex = valueDescriptors.length; + const request = store.getAll(); + request.onerror = + testCase.step_func(() => { reject(request.error); }); + request.onsuccess = testCase.step_func(() => { + assert_equals( + resultIndex, requestIndex, + 'IDBRequest success events should be fired in request order'); + ++resultIndex; + const result = request.result; + if (options.useKeyGenerator) { + for (let i = 0; i < valueDescriptors.length; ++i) { + const primaryKey = i + 1; + assert_equals( + result[i].primaryKey, primaryKey, + 'IndexedDB result should have auto-incremented primary key'); + delete result[i].primaryKey; + } + } + resolve(checkValue(testCase, result, valueDescriptors)); + }); + })); + + return Promise.all(subChecks); + }); + }, label); +} + +// Performs a series of put()s and verifies that get()s and getAll() match. +// +// Each element of the valueDescriptors array is fed into createValue(), and the +// resulting value is written to IndexedDB via a put() request. After the writes +// complete, the values are read in the same order in which they were written. +// Last, all the results are read one more time via a getAll(). +// +// The test verifies that the get() / getAll() results match the arguments to +// put() and that the order in which the get() result events are fired matches +// the order of the get() requests. +function cloningTest(label, valueDescriptors) { + cloningTestInternal(label, valueDescriptors, { useKeyGenerator: false }); +} + +// cloningTest, with coverage for key generators. +// +// This creates two tests. One test performs a series of put()s and verifies +// that get()s and getAll() match, exactly like cloningTestWithoutKeyGenerator. +// The other test performs the same put()s in an object store with a key +// generator, and checks that the key generator works properly. +function cloningTestWithKeyGenerator(label, valueDescriptors) { + cloningTestInternal(label, valueDescriptors, { useKeyGenerator: false }); + cloningTestInternal( + label + " with key generator", valueDescriptors, + { useKeyGenerator: true }); +} diff --git a/testing/web-platform/tests/IndexedDB/resources/reading-autoincrement-common.js b/testing/web-platform/tests/IndexedDB/resources/reading-autoincrement-common.js new file mode 100644 index 0000000000..45c8ffef92 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/resources/reading-autoincrement-common.js @@ -0,0 +1,93 @@ +// Returns the "name" property written to the object with the given ID. +function nameForId(id) { + return `Object ${id}`; +} + +// Initial database setup used by all the reading-autoincrement tests. +async function setupAutoincrementDatabase(testCase) { + const database = await createDatabase(testCase, database => { + const store = database.createObjectStore( + 'store', { autoIncrement: true, keyPath: 'id' }); + store.createIndex('by_name', 'name', { unique: true }); + store.createIndex('by_id', 'id', { unique: true }); + + // Cover writing from the initial upgrade transaction. + for (let i = 1; i <= 16; ++i) { + if (i % 2 == 0) { + store.put({name: nameForId(i), id: i}); + } else { + store.put({name: nameForId(i)}); + } + } + }); + + // Cover writing from a subsequent transaction. + const transaction = database.transaction(['store'], 'readwrite'); + const store = transaction.objectStore('store'); + for (let i = 17; i <= 32; ++i) { + if (i % 2 == 0) { + store.put({name: nameForId(i), id: i}); + } else { + store.put({name: nameForId(i)}); + } + } + await promiseForTransaction(testCase, transaction); + + return database; +} + +// Returns the IDs used by the object store, sorted as strings. +// +// This is used to determine the correct order of records when retrieved from an +// index that uses stringified IDs. +function idsSortedByStringCompare() { + const stringIds = []; + for (let i = 1; i <= 32; ++i) + stringIds.push(i); + stringIds.sort((a, b) => indexedDB.cmp(`${a}`, `${b}`)); + return stringIds; +} + +async function iterateCursor(testCase, cursorRequest, callback) { + // This uses requestWatcher() directly instead of using promiseForRequest() + // inside the loop to avoid creating multiple EventWatcher instances. In turn, + // this avoids ending up with O(N) listeners for the request and O(N^2) + // dispatched events. + const eventWatcher = requestWatcher(testCase, cursorRequest); + while (true) { + const event = await eventWatcher.wait_for('success'); + const cursor = event.target.result; + if (cursor === null) + return; + callback(cursor); + cursor.continue(); + } +} + +// Returns equivalent information to getAllKeys() by iterating a cursor. +// +// Returns an array with one dictionary per entry in the source. The dictionary +// has the properties "key" and "primaryKey". +async function getAllKeysViaCursor(testCase, cursorSource) { + const results = []; + await iterateCursor(testCase, cursorSource.openKeyCursor(), cursor => { + results.push({ key: cursor.key, primaryKey: cursor.primaryKey }); + }); + return results; +} + +// Returns equivalent information to getAll() by iterating a cursor. +// +// Returns an array with one dictionary per entry in the source. The dictionary +// has the properties "key", "primaryKey" and "value". +async function getAllViaCursor(testCase, cursorSource) { + const results = []; + await iterateCursor(testCase, cursorSource.openCursor(), cursor => { + results.push({ + key: cursor.key, + primaryKey: cursor.primaryKey, + value: cursor.value, + }); + }); + return results; +}
\ No newline at end of file diff --git a/testing/web-platform/tests/IndexedDB/resources/support-promises.js b/testing/web-platform/tests/IndexedDB/resources/support-promises.js new file mode 100644 index 0000000000..9128bfe151 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/resources/support-promises.js @@ -0,0 +1,355 @@ +'use strict'; + +// Returns an IndexedDB database name that is unique to the test case. +function databaseName(testCase) { + return 'db' + self.location.pathname + '-' + testCase.name; +} + +// EventWatcher covering all the events defined on IndexedDB requests. +// +// The events cover IDBRequest and IDBOpenDBRequest. +function requestWatcher(testCase, request) { + return new EventWatcher(testCase, request, + ['blocked', 'error', 'success', 'upgradeneeded']); +} + +// EventWatcher covering all the events defined on IndexedDB transactions. +// +// The events cover IDBTransaction. +function transactionWatcher(testCase, request) { + return new EventWatcher(testCase, request, ['abort', 'complete', 'error']); +} + +// Promise that resolves with an IDBRequest's result. +// +// The promise only resolves if IDBRequest receives the "success" event. Any +// other event causes the promise to reject with an error. This is correct in +// most cases, but insufficient for indexedDB.open(), which issues +// "upgradeneded" events under normal operation. +function promiseForRequest(testCase, request) { + const eventWatcher = requestWatcher(testCase, request); + return eventWatcher.wait_for('success').then(event => event.target.result); +} + +// Promise that resolves when an IDBTransaction completes. +// +// The promise resolves with undefined if IDBTransaction receives the "complete" +// event, and rejects with an error for any other event. +function promiseForTransaction(testCase, request) { + const eventWatcher = transactionWatcher(testCase, request); + return eventWatcher.wait_for('complete').then(() => {}); +} + +// Migrates an IndexedDB database whose name is unique for the test case. +// +// newVersion must be greater than the database's current version. +// +// migrationCallback will be called during a versionchange transaction and will +// given the created database, the versionchange transaction, and the database +// open request. +// +// Returns a promise. If the versionchange transaction goes through, the promise +// resolves to an IndexedDB database that should be closed by the caller. If the +// versionchange transaction is aborted, the promise resolves to an error. +function migrateDatabase(testCase, newVersion, migrationCallback) { + return migrateNamedDatabase( + testCase, databaseName(testCase), newVersion, migrationCallback); +} + +// Migrates an IndexedDB database. +// +// newVersion must be greater than the database's current version. +// +// migrationCallback will be called during a versionchange transaction and will +// given the created database, the versionchange transaction, and the database +// open request. +// +// Returns a promise. If the versionchange transaction goes through, the promise +// resolves to an IndexedDB database that should be closed by the caller. If the +// versionchange transaction is aborted, the promise resolves to an error. +function migrateNamedDatabase( + testCase, databaseName, newVersion, migrationCallback) { + // We cannot use eventWatcher.wait_for('upgradeneeded') here, because + // the versionchange transaction auto-commits before the Promise's then + // callback gets called. + return new Promise((resolve, reject) => { + const request = indexedDB.open(databaseName, newVersion); + request.onupgradeneeded = testCase.step_func(event => { + const database = event.target.result; + const transaction = event.target.transaction; + let shouldBeAborted = false; + let requestEventPromise = null; + + // We wrap IDBTransaction.abort so we can set up the correct event + // listeners and expectations if the test chooses to abort the + // versionchange transaction. + const transactionAbort = transaction.abort.bind(transaction); + transaction.abort = () => { + transaction._willBeAborted(); + transactionAbort(); + } + transaction._willBeAborted = () => { + requestEventPromise = new Promise((resolve, reject) => { + request.onerror = event => { + event.preventDefault(); + resolve(event.target.error); + }; + request.onsuccess = () => reject(new Error( + 'indexedDB.open should not succeed for an aborted ' + + 'versionchange transaction')); + }); + shouldBeAborted = true; + } + + // If migration callback returns a promise, we'll wait for it to resolve. + // This simplifies some tests. + const callbackResult = migrationCallback(database, transaction, request); + if (!shouldBeAborted) { + request.onerror = null; + request.onsuccess = null; + requestEventPromise = promiseForRequest(testCase, request); + } + + // requestEventPromise needs to be the last promise in the chain, because + // we want the event that it resolves to. + resolve(Promise.resolve(callbackResult).then(() => requestEventPromise)); + }); + request.onerror = event => reject(event.target.error); + request.onsuccess = () => { + const database = request.result; + testCase.add_cleanup(() => { database.close(); }); + reject(new Error( + 'indexedDB.open should not succeed without creating a ' + + 'versionchange transaction')); + }; + }).then(databaseOrError => { + if (databaseOrError instanceof IDBDatabase) + testCase.add_cleanup(() => { databaseOrError.close(); }); + return databaseOrError; + }); +} + +// Creates an IndexedDB database whose name is unique for the test case. +// +// setupCallback will be called during a versionchange transaction, and will be +// given the created database, the versionchange transaction, and the database +// open request. +// +// Returns a promise that resolves to an IndexedDB database. The caller should +// close the database. +function createDatabase(testCase, setupCallback) { + return createNamedDatabase(testCase, databaseName(testCase), setupCallback); +} + +// Creates an IndexedDB database. +// +// setupCallback will be called during a versionchange transaction, and will be +// given the created database, the versionchange transaction, and the database +// open request. +// +// Returns a promise that resolves to an IndexedDB database. The caller should +// close the database. +function createNamedDatabase(testCase, databaseName, setupCallback) { + const request = indexedDB.deleteDatabase(databaseName); + return promiseForRequest(testCase, request).then(() => { + testCase.add_cleanup(() => { indexedDB.deleteDatabase(databaseName); }); + return migrateNamedDatabase(testCase, databaseName, 1, setupCallback) + }); +} + +// Opens an IndexedDB database without performing schema changes. +// +// The given version number must match the database's current version. +// +// Returns a promise that resolves to an IndexedDB database. The caller should +// close the database. +function openDatabase(testCase, version) { + return openNamedDatabase(testCase, databaseName(testCase), version); +} + +// Opens an IndexedDB database without performing schema changes. +// +// The given version number must match the database's current version. +// +// Returns a promise that resolves to an IndexedDB database. The caller should +// close the database. +function openNamedDatabase(testCase, databaseName, version) { + const request = indexedDB.open(databaseName, version); + return promiseForRequest(testCase, request).then(database => { + testCase.add_cleanup(() => { database.close(); }); + return database; + }); +} + +// The data in the 'books' object store records in the first example of the +// IndexedDB specification. +const BOOKS_RECORD_DATA = [ + { title: 'Quarry Memories', author: 'Fred', isbn: 123456 }, + { title: 'Water Buffaloes', author: 'Fred', isbn: 234567 }, + { title: 'Bedrock Nights', author: 'Barney', isbn: 345678 }, +]; + +// Creates a 'books' object store whose contents closely resembles the first +// example in the IndexedDB specification. +const createBooksStore = (testCase, database) => { + const store = database.createObjectStore('books', + { keyPath: 'isbn', autoIncrement: true }); + store.createIndex('by_author', 'author'); + store.createIndex('by_title', 'title', { unique: true }); + for (const record of BOOKS_RECORD_DATA) + store.put(record); + return store; +} + +// Creates a 'books' object store whose contents closely resembles the first +// example in the IndexedDB specification, just without autoincrementing. +const createBooksStoreWithoutAutoIncrement = (testCase, database) => { + const store = database.createObjectStore('books', + { keyPath: 'isbn' }); + store.createIndex('by_author', 'author'); + store.createIndex('by_title', 'title', { unique: true }); + for (const record of BOOKS_RECORD_DATA) + store.put(record); + return store; +} + +// Creates a 'not_books' object store used to test renaming into existing or +// deleted store names. +function createNotBooksStore(testCase, database) { + const store = database.createObjectStore('not_books'); + store.createIndex('not_by_author', 'author'); + store.createIndex('not_by_title', 'title', { unique: true }); + return store; +} + +// Verifies that an object store's indexes match the indexes used to create the +// books store in the test database's version 1. +// +// The errorMessage is used if the assertions fail. It can state that the +// IndexedDB implementation being tested is incorrect, or that the testing code +// is using it incorrectly. +function checkStoreIndexes (testCase, store, errorMessage) { + assert_array_equals( + store.indexNames, ['by_author', 'by_title'], errorMessage); + const authorIndex = store.index('by_author'); + const titleIndex = store.index('by_title'); + return Promise.all([ + checkAuthorIndexContents(testCase, authorIndex, errorMessage), + checkTitleIndexContents(testCase, titleIndex, errorMessage), + ]); +} + +// Verifies that an object store's key generator is in the same state as the +// key generator created for the books store in the test database's version 1. +// +// The errorMessage is used if the assertions fail. It can state that the +// IndexedDB implementation being tested is incorrect, or that the testing code +// is using it incorrectly. +function checkStoreGenerator(testCase, store, expectedKey, errorMessage) { + const request = store.put( + { title: 'Bedrock Nights ' + expectedKey, author: 'Barney' }); + return promiseForRequest(testCase, request).then(result => { + assert_equals(result, expectedKey, errorMessage); + }); +} + +// Verifies that an object store's contents matches the contents used to create +// the books store in the test database's version 1. +// +// The errorMessage is used if the assertions fail. It can state that the +// IndexedDB implementation being tested is incorrect, or that the testing code +// is using it incorrectly. +function checkStoreContents(testCase, store, errorMessage) { + const request = store.get(123456); + return promiseForRequest(testCase, request).then(result => { + assert_equals(result.isbn, BOOKS_RECORD_DATA[0].isbn, errorMessage); + assert_equals(result.author, BOOKS_RECORD_DATA[0].author, errorMessage); + assert_equals(result.title, BOOKS_RECORD_DATA[0].title, errorMessage); + }); +} + +// Verifies that index matches the 'by_author' index used to create the +// by_author books store in the test database's version 1. +// +// The errorMessage is used if the assertions fail. It can state that the +// IndexedDB implementation being tested is incorrect, or that the testing code +// is using it incorrectly. +function checkAuthorIndexContents(testCase, index, errorMessage) { + const request = index.get(BOOKS_RECORD_DATA[2].author); + return promiseForRequest(testCase, request).then(result => { + assert_equals(result.isbn, BOOKS_RECORD_DATA[2].isbn, errorMessage); + assert_equals(result.title, BOOKS_RECORD_DATA[2].title, errorMessage); + }); +} + +// Verifies that an index matches the 'by_title' index used to create the books +// store in the test database's version 1. +// +// The errorMessage is used if the assertions fail. It can state that the +// IndexedDB implementation being tested is incorrect, or that the testing code +// is using it incorrectly. +function checkTitleIndexContents(testCase, index, errorMessage) { + const request = index.get(BOOKS_RECORD_DATA[2].title); + return promiseForRequest(testCase, request).then(result => { + assert_equals(result.isbn, BOOKS_RECORD_DATA[2].isbn, errorMessage); + assert_equals(result.author, BOOKS_RECORD_DATA[2].author, errorMessage); + }); +} + +// Returns an Uint8Array with pseudorandom data. +// +// The PRNG should be sufficient to defeat compression schemes, but it is not +// cryptographically strong. +function largeValue(size, seed) { + const buffer = new Uint8Array(size); + + // 32-bit xorshift - the seed can't be zero + let state = 1000 + seed; + + for (let i = 0; i < size; ++i) { + state ^= state << 13; + state ^= state >> 17; + state ^= state << 5; + buffer[i] = state & 0xff; + } + + return buffer; +} + +async function deleteAllDatabases(testCase) { + const dbs_to_delete = await indexedDB.databases(); + for( const db_info of dbs_to_delete) { + let request = indexedDB.deleteDatabase(db_info.name); + let eventWatcher = requestWatcher(testCase, request); + await eventWatcher.wait_for('success'); + } +} + +// Keeps the passed transaction alive indefinitely (by making requests +// against the named store). Returns a function that asserts that the +// transaction has not already completed and then ends the request loop so that +// the transaction may autocommit and complete. +function keepAlive(testCase, transaction, storeName) { + let completed = false; + transaction.addEventListener('complete', () => { completed = true; }); + + let keepSpinning = true; + + function spin() { + if (!keepSpinning) + return; + transaction.objectStore(storeName).get(0).onsuccess = spin; + } + spin(); + + return testCase.step_func(() => { + assert_false(completed, 'Transaction completed while kept alive'); + keepSpinning = false; + }); +} + +// Return a promise that resolves after a setTimeout finishes to break up the +// scope of a function's execution. +function timeoutPromise(ms) { + return new Promise(resolve => { setTimeout(resolve, ms); }); +} diff --git a/testing/web-platform/tests/IndexedDB/resources/support.js b/testing/web-platform/tests/IndexedDB/resources/support.js new file mode 100644 index 0000000000..18ed971ad7 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/resources/support.js @@ -0,0 +1,225 @@ +/* Delete created databases + * + * Go through each finished test, see if it has an associated database. Close + * that and delete the database. */ +add_completion_callback(function(tests) +{ + for (var i in tests) + { + if(tests[i].db) + { + tests[i].db.close(); + self.indexedDB.deleteDatabase(tests[i].db.name); + } + } +}); + +function fail(test, desc) { + return test.step_func(function(e) { + if (e && e.message && e.target.error) + assert_unreached(desc + " (" + e.target.error.name + ": " + e.message + ")"); + else if (e && e.message) + assert_unreached(desc + " (" + e.message + ")"); + else if (e && e.target.readyState === 'done' && e.target.error) + assert_unreached(desc + " (" + e.target.error.name + ")"); + else + assert_unreached(desc); + }); +} + +function createdb(test, dbname, version) +{ + var rq_open = createdb_for_multiple_tests(dbname, version); + return rq_open.setTest(test); +} + +function createdb_for_multiple_tests(dbname, version) { + var rq_open, + fake_open = {}, + test = null, + dbname = (dbname ? dbname : "testdb-" + new Date().getTime() + Math.random() ); + + if (version) + rq_open = self.indexedDB.open(dbname, version); + else + rq_open = self.indexedDB.open(dbname); + + function auto_fail(evt, current_test) { + /* Fail handlers, if we haven't set on/whatever/, don't + * expect to get event whatever. */ + rq_open.manually_handled = {}; + + rq_open.addEventListener(evt, function(e) { + if (current_test !== test) { + return; + } + + test.step(function() { + if (!rq_open.manually_handled[evt]) { + assert_unreached("unexpected open." + evt + " event"); + } + + if (e.target.result + '' == '[object IDBDatabase]' && + !this.db) { + this.db = e.target.result; + + this.db.onerror = fail(test, 'unexpected db.error'); + this.db.onabort = fail(test, 'unexpected db.abort'); + this.db.onversionchange = + fail(test, 'unexpected db.versionchange'); + } + }); + }); + rq_open.__defineSetter__("on" + evt, function(h) { + rq_open.manually_handled[evt] = true; + if (!h) + rq_open.addEventListener(evt, function() {}); + else + rq_open.addEventListener(evt, test.step_func(h)); + }); + } + + // add a .setTest method to the IDBOpenDBRequest object + Object.defineProperty(rq_open, 'setTest', { + enumerable: false, + value: function(t) { + test = t; + + auto_fail("upgradeneeded", test); + auto_fail("success", test); + auto_fail("blocked", test); + auto_fail("error", test); + + return this; + } + }); + + return rq_open; +} + +function assert_key_equals(actual, expected, description) { + assert_equals(indexedDB.cmp(actual, expected), 0, description); +} + +// Usage: +// indexeddb_test( +// (test_object, db_connection, upgrade_tx, open_request) => { +// // Database creation logic. +// }, +// (test_object, db_connection, open_request) => { +// // Test logic. +// test_object.done(); +// }, +// 'Test case description'); +function indexeddb_test(upgrade_func, open_func, description, options) { + async_test(function(t) { + options = Object.assign({upgrade_will_abort: false}, options); + var dbname = location + '-' + t.name; + var del = indexedDB.deleteDatabase(dbname); + del.onerror = t.unreached_func('deleteDatabase should succeed'); + var open = indexedDB.open(dbname, 1); + open.onupgradeneeded = t.step_func(function() { + var db = open.result; + t.add_cleanup(function() { + // If open didn't succeed already, ignore the error. + open.onerror = function(e) { + e.preventDefault(); + }; + db.close(); + indexedDB.deleteDatabase(db.name); + }); + var tx = open.transaction; + upgrade_func(t, db, tx, open); + }); + if (options.upgrade_will_abort) { + open.onsuccess = t.unreached_func('open should not succeed'); + } else { + open.onerror = t.unreached_func('open should succeed'); + open.onsuccess = t.step_func(function() { + var db = open.result; + if (open_func) + open_func(t, db, open); + }); + } + }, description); +} + +// Call with a Test and an array of expected results in order. Returns +// a function; call the function when a result arrives and when the +// expected number appear the order will be asserted and test +// completed. +function expect(t, expected) { + var results = []; + return result => { + results.push(result); + if (results.length === expected.length) { + assert_array_equals(results, expected); + t.done(); + } + }; +} + +// Checks to see if the passed transaction is active (by making +// requests against the named store). +function is_transaction_active(tx, store_name) { + try { + const request = tx.objectStore(store_name).get(0); + request.onerror = e => { + e.preventDefault(); + e.stopPropagation(); + }; + return true; + } catch (ex) { + assert_equals(ex.name, 'TransactionInactiveError', + 'Active check should either not throw anything, or throw ' + + 'TransactionInactiveError'); + return false; + } +} + +// Keeps the passed transaction alive indefinitely (by making requests +// against the named store). Returns a function that asserts that the +// transaction has not already completed and then ends the request loop so that +// the transaction may autocommit and complete. +function keep_alive(tx, store_name) { + let completed = false; + tx.addEventListener('complete', () => { completed = true; }); + + let keepSpinning = true; + + function spin() { + if (!keepSpinning) + return; + tx.objectStore(store_name).get(0).onsuccess = spin; + } + spin(); + + return () => { + assert_false(completed, 'Transaction completed while kept alive'); + keepSpinning = false; + }; +} + +// Returns a new function. After it is called |count| times, |func| +// will be called. +function barrier_func(count, func) { + let n = 0; + return () => { + if (++n === count) + func(); + }; +} + +// Create an IndexedDB by executing script on the given remote context +// with |dbName| and |version|. +async function createIndexedDBForTesting(rc, dbName, version) { + await rc.executeScript((dbName, version) => { + let request = indexedDB.open(dbName, version); + request.onupgradeneeded = () => { + request.result.createObjectStore('store'); + } + request.onversionchange = () => { + fail(t, 'unexpectedly received versionchange event.'); + } + }, [dbName, version]); +} diff --git a/testing/web-platform/tests/IndexedDB/serialize-sharedarraybuffer-throws.https.html b/testing/web-platform/tests/IndexedDB/serialize-sharedarraybuffer-throws.https.html new file mode 100644 index 0000000000..613ddfe99d --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/serialize-sharedarraybuffer-throws.https.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<title>IndexedDB: Attempting to serialize a SharedArrayBuffer should throw</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + async_test(function(t) { + + // The SAB constructor throws if the page is not cross-origin-isolated. + assert_true(self.crossOriginIsolated, + "The page is served with COOP and COEP, it should be cross-origin-isolated."); + + let open_rq = createdb(t); + open_rq.onupgradeneeded = function(e) { + let db = e.target.result; + let objStore = db.createObjectStore("test", { keyPath:"pKey" }); + + let sab = new SharedArrayBuffer(256); + + let rq; + assert_throws_dom("DataCloneError", () => { + rq = objStore.put({sab: sab}, 'key'); + }); + assert_equals(rq, undefined); + t.done(); + }; + }); +</script> diff --git a/testing/web-platform/tests/IndexedDB/serialize-sharedarraybuffer-throws.https.html.headers b/testing/web-platform/tests/IndexedDB/serialize-sharedarraybuffer-throws.https.html.headers new file mode 100644 index 0000000000..5f8621ef83 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/serialize-sharedarraybuffer-throws.https.html.headers @@ -0,0 +1,2 @@ +Cross-Origin-Embedder-Policy: require-corp +Cross-Origin-Opener-Policy: same-origin diff --git a/testing/web-platform/tests/IndexedDB/storage-buckets.https.any.js b/testing/web-platform/tests/IndexedDB/storage-buckets.https.any.js new file mode 100644 index 0000000000..24923ffcf8 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/storage-buckets.https.any.js @@ -0,0 +1,89 @@ +// META: title=Buckets API: Tests for indexedDB API. +// META: global=window,worker +// META: script=resources/support-promises.js + +promise_test(async testCase => { + const inboxBucket = await navigator.storageBuckets.open('inbox_bucket'); + const outboxBucket = await navigator.storageBuckets.open('outbox_bucket'); + testCase.add_cleanup(async () => { + await navigator.storageBuckets.delete('inbox_bucket'); + await navigator.storageBuckets.delete('outbox_bucket'); + }); + + // Set up similar databases in two buckets. + const inboxDb = await new Promise((resolve, reject) => { + const request = inboxBucket.indexedDB.open('messages'); + request.onupgradeneeded = (event) => { + const inboxStore = + event.target.result.createObjectStore('primary', {keyPath: 'id'}); + event.target.transaction.commit(); + }; + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); + + const txn = inboxDb.transaction(['primary'], 'readwrite'); + const inboxStore = txn.objectStore('primary'); + inboxStore.put({ subject: 'Bonjour', id: '42'}); + txn.commit(); + await promiseForTransaction(testCase, txn); + + const outboxDb = await new Promise((resolve, reject) => { + const request = outboxBucket.indexedDB.open('messages'); + request.onupgradeneeded = (event) => { + const outboxStore = + event.target.result.createObjectStore('primary', {keyPath: 'id'}); + event.target.transaction.commit(); + }; + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); + + const txn2 = outboxDb.transaction(['primary'], 'readwrite'); + const outboxStore = txn2.objectStore('primary'); + outboxStore.put({ subject: 're: Bonjour', id: '47'}); + txn2.commit(); + await promiseForTransaction(testCase, txn2); + + // Make sure it's possible to read from the bucket database. + const inboxMessage = await new Promise((resolve, reject) => { + const txn3 = inboxDb.transaction(['primary'], 'readonly'); + const inboxLookup = txn3.objectStore('primary').get('42'); + inboxLookup.onsuccess = (e) => resolve(inboxLookup.result); + inboxLookup.onerror = (e) => reject(inboxLookup.error); + }); + assert_equals(inboxMessage.subject, 'Bonjour'); + + // Make sure it's possible to read from the other bucket database. + const outboxMessage = await new Promise((resolve, reject) => { + const txn4 = outboxDb.transaction(['primary'], 'readonly'); + const outboxLookup = txn4.objectStore('primary').get('47'); + outboxLookup.onsuccess = (e) => resolve(outboxLookup.result); + outboxLookup.onerror = (e) => reject(outboxLookup.error); + }); + assert_equals(outboxMessage.subject, 're: Bonjour'); + + // Make sure they are different databases (looking up the data keyed on `47` + // fails in the first database). + const nonexistentInboxMessage = await new Promise((resolve, reject) => { + const txn5 = inboxDb.transaction(['primary'], 'readonly'); + const nonexistentInboxLookup = txn5.objectStore('primary').get('47'); + nonexistentInboxLookup.onsuccess = (e) => + resolve(nonexistentInboxLookup.result); + nonexistentInboxLookup.onerror = (e) => + reject(nonexistentInboxLookup.error); + }); + assert_equals(nonexistentInboxMessage, undefined); +}, 'Basic test that buckets create independent databases.'); + +promise_test(async testCase => { + const inboxBucket = await navigator.storageBuckets.open('inbox'); + await navigator.storageBuckets.delete('inbox'); + + return promise_rejects_dom( + testCase, 'UnknownError', new Promise((resolve, reject) => { + const request = inboxBucket.indexedDB.open('messages'); + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + })); +}, 'Tests trying to use indexedDB in a deleted bucket.'); diff --git a/testing/web-platform/tests/IndexedDB/string-list-ordering.htm b/testing/web-platform/tests/IndexedDB/string-list-ordering.htm new file mode 100644 index 0000000000..ddbbc3036f --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/string-list-ordering.htm @@ -0,0 +1,85 @@ +<!-- +Test converted from WebKit: +http://trac.webkit.org/browser/trunk/LayoutTests/storage/indexeddb/resources/list-ordering.js +--> + +<!DOCTYPE html> +<!-- Submitted from TestTWF Paris --> +<meta charset=utf-8> +<title>Test string list ordering in IndexedDB</title> +<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#widl-IDBDatabase-objectStoreNames"> +<link rel=assert title="The list must be sorted in ascending order using the algorithm defined by step 4 of section 11.8.5, The Abstract Relational Comparison Algorithm of the ECMAScript Language Specification [ECMA-262]."> +<link rel=author href="mailto:romain.huet@gmail.com" title="Romain Huet"> + +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + + var expectedOrder = [ + "", + "\x00", // 'NULL' (U+0000) + "0", + "1", + "A", + "B", + "a", + "b", + "\x7F", // 'DELETE' (U+007F) + "\xC0", // 'LATIN CAPITAL LETTER A WITH GRAVE' (U+00C0) + "\xC1", // 'LATIN CAPITAL LETTER A WITH ACUTE' (U+00C1) + "\xE0", // 'LATIN SMALL LETTER A WITH GRAVE' (U+00E0) + "\xE1", // 'LATIN SMALL LETTER A WITH ACUTE' (U+00E1) + "\xFF", // 'LATIN SMALL LETTER Y WITH DIAERESIS' (U+00FF) + "\u0100", // 'LATIN CAPITAL LETTER A WITH MACRON' (U+0100) + "\u1000", // 'MYANMAR LETTER KA' (U+1000) + "\uD834\uDD1E", // 'MUSICAL SYMBOL G-CLEF' (U+1D11E), UTF-16 surrogate pairs + "\uFFFD" // 'REPLACEMENT CHARACTER' (U+FFFD) + ]; + + var i, tmp, permutedOrder = expectedOrder.slice(); + permutedOrder.reverse(); + for (i = 0; i < permutedOrder.length - 2; i += 2) { + tmp = permutedOrder[i]; + permutedOrder[i] = permutedOrder[i + 1]; + permutedOrder[i + 1] = tmp; + } + + var objStore, db; + var t = async_test(); + + // Check that the expected order is the canonical JS sort order. + var sortedOrder = expectedOrder.slice(); + sortedOrder.sort(); + assert_array_equals(sortedOrder, expectedOrder); + + var request = createdb(t); + + request.onupgradeneeded = function(e) { + db = e.target.result; + + // Object stores. + for (var i = 0; i < permutedOrder.length; i++) { + objStore = db.createObjectStore(permutedOrder[i]); + } + assert_array_equals(db.objectStoreNames, expectedOrder); + + // Indexes. + for (var i = 0; i < permutedOrder.length; i++) { + objStore.createIndex(permutedOrder[i], "keyPath"); + } + assert_array_equals(objStore.indexNames, expectedOrder); + }; + + request.onsuccess = function(e) { + // Object stores. + assert_array_equals(db.objectStoreNames, expectedOrder); + // Indexes. + assert_array_equals(objStore.indexNames, expectedOrder); + t.done(); + }; + +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/structured-clone-transaction-state.any.js b/testing/web-platform/tests/IndexedDB/structured-clone-transaction-state.any.js new file mode 100644 index 0000000000..adf3be2f70 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/structured-clone-transaction-state.any.js @@ -0,0 +1,96 @@ +// META: script=resources/support-promises.js +// META: title=Indexed DB transaction state during Structured Serializing +// META: timeout=long +'use strict'; + +promise_test(async testCase => { + const db = await createDatabase(testCase, database => { + database.createObjectStore('store'); + }); + + const transaction = db.transaction(['store'], 'readwrite'); + const objectStore = transaction.objectStore('store'); + + let getterCalled = false; + const activeValue = {}; + Object.defineProperty(activeValue, 'propertyName', { + enumerable: true, + get: testCase.step_func(() => { + getterCalled = true; + assert_throws_dom('TransactionInactiveError', () => { + objectStore.get('key'); + }, 'transaction should not be active during structured clone'); + return 'value that should not be used'; + }), + }); + objectStore.add(activeValue, 'key'); + await promiseForTransaction(testCase, transaction); + db.close(); + + assert_true(getterCalled, + "activeValue's getter should be called during test"); +}, 'Transaction inactive during structured clone in IDBObjectStore.add()'); + +promise_test(async testCase => { + const db = await createDatabase(testCase, database => { + database.createObjectStore('store'); + }); + + const transaction = db.transaction(['store'], 'readwrite'); + const objectStore = transaction.objectStore('store'); + + let getterCalled = false; + const activeValue = {}; + Object.defineProperty(activeValue, 'propertyName', { + enumerable: true, + get: testCase.step_func(() => { + getterCalled = true; + assert_throws_dom('TransactionInactiveError', () => { + objectStore.get('key'); + }, 'transaction should not be active during structured clone'); + return 'value that should not be used'; + }), + }); + + objectStore.put(activeValue, 'key'); + await promiseForTransaction(testCase, transaction); + db.close(); + + assert_true(getterCalled, + "activeValue's getter should be called during test"); +}, 'Transaction inactive during structured clone in IDBObjectStore.put()'); + +promise_test(async testCase => { + const db = await createDatabase(testCase, database => { + const objectStore = database.createObjectStore('store'); + objectStore.put({}, 'key'); + }); + + const transaction = db.transaction(['store'], 'readwrite'); + const objectStore = transaction.objectStore('store'); + + let getterCalled = false; + const activeValue = {}; + Object.defineProperty(activeValue, 'propertyName', { + enumerable: true, + get: testCase.step_func(() => { + getterCalled = true; + assert_throws_dom('TransactionInactiveError', () => { + objectStore.get('key'); + }, 'transaction should not be active during structured clone'); + return 'value that should not be used'; + }), + }); + + const request = objectStore.openCursor(); + request.onsuccess = testCase.step_func(() => { + const cursor = request.result; + cursor.update(activeValue); + }); + + await promiseForTransaction(testCase, transaction); + db.close(); + + assert_true(getterCalled, + "activeValue's getter should be called during test"); +}, 'Transaction inactive during structured clone in IDBCursor.update()'); diff --git a/testing/web-platform/tests/IndexedDB/structured-clone.any.js b/testing/web-platform/tests/IndexedDB/structured-clone.any.js new file mode 100644 index 0000000000..15ab0359e2 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/structured-clone.any.js @@ -0,0 +1,324 @@ +// META: title=Indexed DB and Structured Serializing/Deserializing +// META: timeout=long +// META: script=resources/support-promises.js +// META: script=/common/subset-tests.js +// META: variant=?1-20 +// META: variant=?21-40 +// META: variant=?41-60 +// META: variant=?61-80 +// META: variant=?81-100 +// META: variant=?101-last + +// Tests Indexed DB coverage of HTML's Safe "passing of structured data" +// https://html.spec.whatwg.org/multipage/structured-data.html + +function describe(value) { + let type, str; + if (typeof value === 'object' && value) { + type = Object.getPrototypeOf(value).constructor.name; + // Handle Number(-0), etc. + str = Object.is(value.valueOf(), -0) ? '-0' : String(value); + } else { + type = typeof value; + // Handle primitive -0. + str = Object.is(value, -0) ? '-0' : String(value); + } + return `${type}: ${str}`; +} + +function cloneTest(value, verifyFunc) { + subsetTest(promise_test, async t => { + const db = await createDatabase(t, db => { + const store = db.createObjectStore('store'); + // This index is not used, but evaluating key path on each put() + // call will exercise (de)serialization. + store.createIndex('index', 'dummyKeyPath'); + }); + t.add_cleanup(() => { + if (db) { + db.close(); + indexedDB.deleteDatabase(db.name); + } + }); + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + await promiseForRequest(t, store.put(value, 'key')); + const result = await promiseForRequest(t, store.get('key')); + await verifyFunc(value, result); + await promiseForTransaction(t, tx); + }, describe(value)); +} + +// Specialization of cloneTest() for objects, with common asserts. +function cloneObjectTest(value, verifyFunc) { + cloneTest(value, async (orig, clone) => { + assert_not_equals(orig, clone); + assert_equals(typeof clone, 'object'); + assert_equals(Object.getPrototypeOf(orig), Object.getPrototypeOf(clone)); + await verifyFunc(orig, clone); + }); +} + +function cloneFailureTest(value) { + subsetTest(promise_test, async t => { + const db = await createDatabase(t, db => { + db.createObjectStore('store'); + }); + t.add_cleanup(() => { + if (db) { + db.close(); + indexedDB.deleteDatabase(db.name); + } + }); + const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + const store = tx.objectStore('store'); + assert_throws_dom('DataCloneError', () => store.put(value, 'key')); + }, 'Not serializable: ' + describe(value)); +} + +// +// ECMAScript types +// + +// Primitive values: Undefined, Null, Boolean, Number, BigInt, String +const booleans = [false, true]; +const numbers = [ + NaN, + -Infinity, + -Number.MAX_VALUE, + -0xffffffff, + -0x80000000, + -0x7fffffff, + -1, + -Number.MIN_VALUE, + -0, + 0, + 1, + Number.MIN_VALUE, + 0x7fffffff, + 0x80000000, + 0xffffffff, + Number.MAX_VALUE, + Infinity, +]; +const bigints = [ + -12345678901234567890n, + -1n, + 0n, + 1n, + 12345678901234567890n, +]; +const strings = [ + '', + 'this is a sample string', + 'null(\0)', +]; + +[undefined, null].concat(booleans, numbers, bigints, strings) + .forEach(value => cloneTest(value, (orig, clone) => { + assert_equals(orig, clone); + })); + +// "Primitive" Objects (Boolean, Number, BigInt, String) +[].concat(booleans, numbers, bigints, strings) + .forEach(value => cloneObjectTest(Object(value), (orig, clone) => { + assert_equals(orig.valueOf(), clone.valueOf()); + })); + +// Dates +[ + new Date(-1e13), + new Date(-1e12), + new Date(-1e9), + new Date(-1e6), + new Date(-1e3), + new Date(0), + new Date(1e3), + new Date(1e6), + new Date(1e9), + new Date(1e12), + new Date(1e13) +].forEach(value => cloneTest(value, (orig, clone) => { + assert_not_equals(orig, clone); + assert_equals(typeof clone, 'object'); + assert_equals(Object.getPrototypeOf(orig), Object.getPrototypeOf(clone)); + assert_equals(orig.valueOf(), clone.valueOf()); + })); + +// Regular Expressions +[ + new RegExp(), + /abc/, + /abc/g, + /abc/i, + /abc/gi, + /abc/m, + /abc/mg, + /abc/mi, + /abc/mgi, + /abc/gimsuy, +].forEach(value => cloneObjectTest(value, (orig, clone) => { + assert_equals(orig.toString(), clone.toString()); +})); + +// ArrayBuffer +cloneObjectTest(new Uint8Array([0, 1, 254, 255]).buffer, (orig, clone) => { + assert_array_equals(new Uint8Array(orig), new Uint8Array(clone)); +}); + +// TODO SharedArrayBuffer + +// Array Buffer Views +[ + new Uint8Array([]), + new Uint8Array([0, 1, 254, 255]), + new Uint16Array([0x0000, 0x0001, 0xFFFE, 0xFFFF]), + new Uint32Array([0x00000000, 0x00000001, 0xFFFFFFFE, 0xFFFFFFFF]), + new Int8Array([0, 1, 254, 255]), + new Int16Array([0x0000, 0x0001, 0xFFFE, 0xFFFF]), + new Int32Array([0x00000000, 0x00000001, 0xFFFFFFFE, 0xFFFFFFFF]), + new Uint8ClampedArray([0, 1, 254, 255]), + new Float32Array([-Infinity, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, Infinity, NaN]), + new Float64Array([-Infinity, -Number.MAX_VALUE, -Number.MIN_VALUE, 0, + Number.MIN_VALUE, Number.MAX_VALUE, Infinity, NaN]) +].forEach(value => cloneObjectTest(value, (orig, clone) => { + assert_array_equals(orig, clone); +})); + +// Map +cloneObjectTest(new Map([[1,2],[3,4]]), (orig, clone) => { + assert_array_equals([...orig.keys()], [...clone.keys()]); + assert_array_equals([...orig.values()], [...clone.values()]); +}); + +// Set +cloneObjectTest(new Set([1,2,3,4]), (orig, clone) => { + assert_array_equals([...orig.values()], [...clone.values()]); +}); + +// Error +[ + new Error(), + new Error('abc', 'def'), + new EvalError(), + new EvalError('ghi', 'jkl'), + new RangeError(), + new RangeError('ghi', 'jkl'), + new ReferenceError(), + new ReferenceError('ghi', 'jkl'), + new SyntaxError(), + new SyntaxError('ghi', 'jkl'), + new TypeError(), + new TypeError('ghi', 'jkl'), + new URIError(), + new URIError('ghi', 'jkl'), +].forEach(value => cloneObjectTest(value, (orig, clone) => { + assert_equals(orig.name, clone.name); + assert_equals(orig.message, clone.message); +})); + +// Arrays +[ + [], + [1,2,3], + Object.assign( + ['foo', 'bar'], + {10: true, 11: false, 20: 123, 21: 456, 30: null}), + Object.assign( + ['foo', 'bar'], + {a: true, b: false, foo: 123, bar: 456, '': null}), +].forEach(value => cloneObjectTest(value, (orig, clone) => { + assert_array_equals(orig, clone); + assert_array_equals(Object.keys(orig), Object.keys(clone)); + Object.keys(orig).forEach(key => { + assert_equals(orig[key], clone[key], `Property ${key}`); + }); +})); + +// Objects +cloneObjectTest({foo: true, bar: false}, (orig, clone) => { + assert_array_equals(Object.keys(orig), Object.keys(clone)); + Object.keys(orig).forEach(key => { + assert_equals(orig[key], clone[key], `Property ${key}`); + }); +}); + +// +// [Serializable] Platform objects +// + +// TODO: Test these additional interfaces: +// * DOMQuad +// * DOMException +// * RTCCertificate + +// Geometry types +[ + new DOMMatrix(), + new DOMMatrixReadOnly(), + new DOMPoint(), + new DOMPointReadOnly(), + new DOMRect, + new DOMRectReadOnly(), +].forEach(value => cloneObjectTest(value, (orig, clone) => { + Object.keys(Object.getPrototypeOf(orig)).forEach(key => { + assert_equals(orig[key], clone[key], `Property ${key}`); + }); +})); + +// ImageData +const image_data = new ImageData(8, 8); +for (let i = 0; i < 256; ++i) { + image_data.data[i] = i; +} +cloneObjectTest(image_data, (orig, clone) => { + assert_equals(orig.width, clone.width); + assert_equals(orig.height, clone.height); + assert_array_equals(orig.data, clone.data); +}); + +// Blob +cloneObjectTest( + new Blob(['This is a test.'], {type: 'a/b'}), + async (orig, clone) => { + assert_equals(orig.size, clone.size); + assert_equals(orig.type, clone.type); + assert_equals(await orig.text(), await clone.text()); + }); + +// File +cloneObjectTest( + new File(['This is a test.'], 'foo.txt', {type: 'c/d'}), + async (orig, clone) => { + assert_equals(orig.size, clone.size); + assert_equals(orig.type, clone.type); + assert_equals(orig.name, clone.name); + assert_equals(orig.lastModified, clone.lastModified); + assert_equals(await orig.text(), await clone.text()); + }); + + +// FileList - exposed in Workers, but not constructable. +if ('document' in self) { + // TODO: Test with populated list. + cloneObjectTest( + Object.assign(document.createElement('input'), + {type: 'file', multiple: true}).files, + async (orig, clone) => { + assert_equals(orig.length, clone.length); + }); +} + +// +// Non-serializable types +// +[ + // ECMAScript types + function() {}, + Symbol('desc'), + + // Non-[Serializable] platform objects + self, + new Event(''), + new MessageChannel() +].forEach(cloneFailureTest); diff --git a/testing/web-platform/tests/IndexedDB/transaction-abort-generator-revert.html b/testing/web-platform/tests/IndexedDB/transaction-abort-generator-revert.html new file mode 100644 index 0000000000..bbe0338c3a --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/transaction-abort-generator-revert.html @@ -0,0 +1,110 @@ +<!doctype html> +<meta charset="utf8"> +<title>IndexedDB: aborting transactions reverts an object store's key generator state</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#abort-transaction"> +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script> +'use strict'; + +promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => { + return new Promise((resolve, reject) => { + const request = indexedDB.open(databaseName(testCase), 2); + request.onupgradeneeded = testCase.step_func(event => { + const database = event.target.result; + const transaction = event.target.transaction; + const store = transaction.objectStore('books'); + const request2 = store.put( + { title: 'Bedrock Nights II', author: 'Barney' }); + request2.onerror = testCase.unreached_func( + 'IDBObjectStore.put() should not receive an error request'); + request2.onsuccess = testCase.step_func(event => { + assert_equals( + event.target.result, 345679, + "The key generator's current number should be set by " + + 'the last put operation in the database creation ' + + 'transaction'); + + request.onerror = event => { + event.preventDefault(); + resolve(event); + }; + request.onsuccess = () => reject(new Error( + 'indexedDB.open should not succeed after the ' + + 'versionchange transaction is aborted')); + + transaction.abort(); + }); + }); + request.onerror = event => reject(event.target.error); + request.onsuccess = () => reject(new Error( + 'indexedDB.open should not succeed without creating a ' + + 'versionchange transaction')); + }); + }).then(() => { + return openDatabase(testCase, 1); + }).then(database => { + const transaction = database.transaction(['books'], 'readwrite'); + const store = transaction.objectStore('books'); + + return checkStoreGenerator( + testCase, store, 345679, + "The key generator's current number should be reverted after the " + + 'transaction modifying it is aborted').then(() => database.close()); + }); +}, 'The current number of a key generator is reverted when a versionchange ' + + 'transaction aborts'); + +promise_test(testCase => { + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + return new Promise((resolve, reject) => { + const transaction = database.transaction(['books'], 'readwrite'); + const store = transaction.objectStore('books'); + const request = store.put( + { title: 'Bedrock Nights II', author: 'Barney' }); + request.onerror = testCase.unreached_func( + 'IDBObjectStore.put() should not receive an error request'); + request.onsuccess = testCase.step_func(event => { + assert_equals( + event.target.result, 345679, + "The key generator's current number should be set by the " + + 'last put operation in the database creation transaction'); + + transaction.onabort = event => { + event.preventDefault(); + resolve(event); + } + transaction.abort(); + }); + transaction.onabort = () => reject(new Error( + 'The aborted readwrite transaction should not receive an ' + + 'abort event before IDBTransaction.abort() is called')); + transaction.oncomplete = () => reject(new Error( + 'The aborted readwrite transaction should not receive a ' + + 'completed event')); + transaction.onerror = () => reject(new Error( + 'The aborted readwrite transaction should not receive an ' + + 'error event')); + }).then(() => database); + }).then(database => { + const transaction = database.transaction(['books'], 'readwrite'); + const store = transaction.objectStore('books'); + + return checkStoreGenerator( + testCase, store, 345679, + "The key generator's current number should be reverted after the " + + 'transaction modifying it is aborted').then(() => database.close()); + }); +}, 'The current number of a key generator is reverted when a readwrite ' + + 'transaction aborts'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/transaction-abort-index-metadata-revert.html b/testing/web-platform/tests/IndexedDB/transaction-abort-index-metadata-revert.html new file mode 100644 index 0000000000..54a873d875 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/transaction-abort-index-metadata-revert.html @@ -0,0 +1,278 @@ +<!doctype html> +<meta charset="utf8"> +<title>IndexedDB: aborting transactions reverts index metadata</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#abort-transaction"> +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script> +'use strict'; + +promise_test(testCase => { + let store = null, index = null; + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + store = createNotBooksStore(testCase, database); + index = store.index('not_by_author'); + assert_array_equals( + store.indexNames, ['not_by_author', 'not_by_title'], + 'IDBObjectStore.indexNames should include newly created indexes ' + + 'before the transaction is aborted'); + + transaction.abort(); + assert_throws_dom( + 'InvalidStateError', () => index.get('query'), + 'IDBIndex.get should throw InvalidStateError, indicating that ' + + 'the index is marked for deletion, immediately after ' + + 'IDBTransaction.abort() returns'); + assert_array_equals( + store.indexNames, [], + 'IDBObjectStore.indexNames should stop including the newly ' + + 'created indexes immediately after IDBTransaction.abort() returns'); + })).then(() => { + assert_throws_dom( + 'InvalidStateError', () => index.get('query'), + 'IDBIndex.get should throw InvalidStateError, indicating that ' + + 'the index is marked for deletion, after the transaction is ' + + 'aborted'); + assert_array_equals( + store.indexNames, [], + 'IDBObjectStore.indexNames should stop including the newly ' + + 'created indexes after the transaction is aborted'); + }); +}, 'Created stores get their indexes marked as deleted after the transaction ' + + 'that created them aborts'); + +promise_test(testCase => { + let store = null, index = null; + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + createNotBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + store = transaction.objectStore('not_books'); + index = store.index('not_by_author'); + + database.deleteObjectStore('not_books'); + assert_throws_dom( + 'InvalidStateError', () => index.get('query'), + 'IDBIndex.get should throw InvalidStateError, indicating that ' + + 'the index is marked for deletion, immediately after ' + + 'IDBDatabase.deleteObjectStore() returns'); + assert_array_equals( + store.indexNames, [], + 'IDBObjectStore.indexNames should be empty immediately after ' + + 'IDBDatabase.deleteObjectStore() returns'); + + transaction.abort(); + assert_throws_dom( + 'TransactionInactiveError', () => index.get('query'), + 'IDBIndex.get should throw TransactionInactiveError, indicating ' + + 'that the index is no longer marked for deletion, immediately ' + + 'after IDBTransaction.abort() returns'); + assert_array_equals( + store.indexNames, ['not_by_author', 'not_by_title'], + 'IDBObjectStore.indexNames should include the deleted indexes ' + + 'immediately after IDBTransaction.abort() returns'); + })).then(() => { + assert_throws_dom( + 'TransactionInactiveError', () => index.get('query'), + 'IDBIndex.get should throw TransactionInactiveError, indicating ' + + 'that the index is no longer marked for deletion, after the ' + + 'transaction is aborted'); + assert_array_equals( + store.indexNames, ['not_by_author', 'not_by_title'], + 'IDBObjectStore.indexNames should include the deleted indexes ' + + 'after the transaction is aborted'); + }); +}, 'Deleted stores get their indexes marked as not-deleted after the ' + + 'transaction that deleted them aborts'); + +promise_test(testCase => { + let store = null, index = null; + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + store = createNotBooksStore(testCase, database); + index = store.index('not_by_author'); + assert_array_equals( + store.indexNames, ['not_by_author', 'not_by_title'], + 'IDBObjectStore.indexNames should include newly created indexes ' + + 'before the transaction is aborted'); + + database.deleteObjectStore('not_books'); + assert_throws_dom( + 'InvalidStateError', () => index.get('query'), + 'IDBIndex.get should throw InvalidStateError, indicating that ' + + 'the index is marked for deletion, immediately after ' + + 'IDBDatabase.deleteObjectStore() returns'); + assert_array_equals( + store.indexNames, [], + 'IDBObjectStore.indexNames should be empty immediately after ' + + 'IDBDatabase.deleteObjectStore() returns'); + + transaction.abort(); + assert_throws_dom( + 'InvalidStateError', () => index.get('query'), + 'IDBIndex.get should throw InvalidStateError, indicating that ' + + 'the index is still marked for deletion, immediately after ' + + 'IDBTransaction.abort() returns'); + assert_array_equals( + store.indexNames, [], + 'IDBObjectStore.indexNames should not include the newly ' + + 'created indexes immediately after IDBTransaction.abort() returns'); + })).then(() => { + assert_throws_dom( + 'InvalidStateError', () => index.get('query'), + 'IDBIndex.get should throw InvalidStateError, indicating that ' + + 'the index is still marked for deletion, after the transaction ' + + 'is aborted'); + assert_array_equals( + store.indexNames, [], + 'IDBObjectStore.indexNames should not include the newly ' + + 'created indexes after the transaction is aborted'); + }); +}, 'Created+deleted stores still have their indexes marked as deleted after ' + + 'the transaction aborts'); + +promise_test(testCase => { + let store = null, index = null; + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + createNotBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + store = transaction.objectStore('not_books'); + index = store.createIndex('not_by_isbn', 'isbn'); + assert_array_equals( + store.indexNames, ['not_by_author', 'not_by_isbn', 'not_by_title'], + 'IDBObjectStore.indexNames should include newly created indexes ' + + 'before the transaction is aborted'); + + transaction.abort(); + assert_throws_dom( + 'InvalidStateError', () => index.get('query'), + 'IDBIndex.get should throw InvalidStateError, indicating that ' + + 'the index is marked for deletion, immediately after ' + + 'IDBTransaction.abort() returns'); + assert_array_equals( + store.indexNames, ['not_by_author', 'not_by_title'], + 'IDBObjectStore.indexNames should stop including the newly ' + + 'created index immediately after IDBTransaction.abort() returns'); + })).then(() => { + assert_throws_dom( + 'InvalidStateError', () => index.get('query'), + 'IDBIndex.get should throw InvalidStateError, indicating that ' + + 'the index is marked for deletion, after the transaction is ' + + 'aborted'); + assert_array_equals( + store.indexNames, ['not_by_author', 'not_by_title'], + 'IDBObjectStore.indexNames should stop including the newly ' + + 'created index after the transaction is aborted'); + }); +}, 'Created indexes get marked as deleted after their transaction aborts'); + +promise_test(testCase => { + let store = null, index = null; + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + createNotBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + store = transaction.objectStore('not_books'); + index = store.index('not_by_author'); + + store.deleteIndex('not_by_author'); + assert_throws_dom( + 'InvalidStateError', () => index.get('query'), + 'IDBIndex.get should throw InvalidStateError, indicating that ' + + 'the index is marked for deletion, immediately after ' + + 'IDBObjectStore.deleteIndex() returns'); + assert_array_equals( + store.indexNames, ['not_by_title'], + 'IDBObjectStore.indexNames should not include the deleted index ' + + 'immediately after IDBObjectStore.deleteIndex() returns'); + + transaction.abort(); + assert_throws_dom( + 'TransactionInactiveError', () => index.get('query'), + 'IDBIndex.get should throw TransactionInactiveError, indicating ' + + 'that the index is no longer marked for deletion, immediately ' + + 'after IDBTransaction.abort() returns'); + assert_array_equals( + store.indexNames, ['not_by_author', 'not_by_title'], + 'IDBObjectStore.indexNames should include the deleted indexes ' + + 'immediately after IDBTransaction.abort() returns'); + })).then(() => { + assert_throws_dom( + 'TransactionInactiveError', () => index.get('query'), + 'IDBIndex.get should throw TransactionInactiveError, indicating ' + + 'that the index is no longer marked for deletion, after the ' + + 'transaction is aborted'); + assert_array_equals( + store.indexNames, ['not_by_author', 'not_by_title'], + 'IDBObjectStore.indexNames should include the deleted indexes ' + + 'after the transaction is aborted'); + }); +}, 'Deleted indexes get marked as not-deleted after the transaction aborts'); + +promise_test(testCase => { + let store = null, index = null; + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + createNotBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + store = transaction.objectStore('not_books'); + index = store.createIndex('not_by_isbn', 'isbn'); + assert_array_equals( + store.indexNames, ['not_by_author', 'not_by_isbn', 'not_by_title'], + 'IDBObjectStore.indexNames should include newly created indexes ' + + 'before the transaction is aborted'); + + store.deleteIndex('not_by_isbn'); + assert_throws_dom( + 'InvalidStateError', () => index.get('query'), + 'IDBIndex.get should throw InvalidStateError, indicating that ' + + 'the index is marked for deletion, immediately after ' + + 'IDBObjectStore.deleteIndex() returns'); + assert_array_equals( + store.indexNames, ['not_by_author', 'not_by_title'], + 'IDBObjectStore.indexNames should not include the deleted index ' + + 'immediately after IDBObjectStore.deleteIndex() returns'); + + transaction.abort(); + assert_throws_dom( + 'InvalidStateError', () => index.get('query'), + 'IDBIndex.get should throw InvalidStateError, indicating that ' + + 'the index is still marked for deletion, immediately after ' + + 'IDBTransaction.abort() returns'); + assert_array_equals( + store.indexNames, ['not_by_author', 'not_by_title'], + 'IDBObjectStore.indexNames should stop including the newly ' + + 'created index immediately after IDBTransaction.abort() returns'); + })).then(() => { + assert_throws_dom( + 'InvalidStateError', () => index.get('query'), + 'IDBIndex.get should throw InvalidStateError, indicating that ' + + 'the index is marked for deletion, after the transaction is ' + + 'aborted'); + assert_array_equals( + store.indexNames, ['not_by_author', 'not_by_title'], + 'IDBObjectStore.indexNames should stop including the newly ' + + 'created index after the transaction is aborted'); + }); +}, 'Created+deleted indexes are still marked as deleted after their ' + + 'transaction aborts'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/transaction-abort-multiple-metadata-revert.html b/testing/web-platform/tests/IndexedDB/transaction-abort-multiple-metadata-revert.html new file mode 100644 index 0000000000..18abd0588c --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/transaction-abort-multiple-metadata-revert.html @@ -0,0 +1,293 @@ +<!doctype html> +<meta charset="utf8"> +<title>IndexedDB: aborting transactions reverts multiple operations on the same metadata</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#abort-transaction"> +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script> +'use strict'; + +promise_test(testCase => { + let store = null, index = null; + let migrationTransaction = null, migrationDatabase = null; + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + store = createNotBooksStore(testCase, database); + migrationDatabase = database; + migrationTransaction = transaction; + assert_array_equals( + database.objectStoreNames, ['books', 'not_books'], + 'IDBDatabase.objectStoreNames should include a newly created ' + + 'store before the transaction is aborted'); + assert_array_equals( + transaction.objectStoreNames, ['books', 'not_books'], + 'IDBTransaction.objectStoreNames should include a newly created ' + + 'store before the transaction is aborted'); + + index = store.index('not_by_author'); + store.deleteIndex('not_by_author'); + assert_throws_dom( + 'InvalidStateError', () => index.get('query'), + 'IDBIndex.get should throw InvalidStateError, indicating that ' + + 'the index is marked for deletion, immediately after ' + + 'IDBObjectStore.deleteIndex() returns'); + assert_array_equals( + store.indexNames, ['not_by_title'], + 'IDBObjectStore.indexNames should not include the deleted index ' + + 'immediately after IDBObjectStore.deleteIndex() returns'); + + transaction.abort(); + assert_throws_dom( + 'InvalidStateError', () => store.get('query'), + 'IDBObjectStore.get should throw InvalidStateError, indicating ' + + 'that the store is marked for deletion, immediately after ' + + 'IDBTransaction.abort() returns'); + assert_throws_dom( + 'InvalidStateError', () => index.get('query'), + 'IDBIndex.get should throw InvalidStateError, indicating that ' + + 'the index is still marked for deletion, immediately after ' + + 'IDBTransaction.abort() returns'); + assert_array_equals( + transaction.objectStoreNames, ['books'], + 'IDBTransaction.objectStoreNames should stop including the newly ' + + 'created store immediately after IDBTransaction.abort() returns'); + assert_array_equals( + database.objectStoreNames, ['books'], + 'IDBDatabase.objectStoreNames should stop including the newly ' + + 'created store immediately after IDBTransaction.abort() returns'); + assert_array_equals( + store.indexNames, [], + 'IDBObjectStore.indexNames for the newly created store should be ' + + 'empty immediately after IDBTransaction.abort() returns'); + })).then(() => { + assert_throws_dom( + 'InvalidStateError', () => store.get('query'), + 'IDBObjectStore.get should throw InvalidStateError, indicating ' + + 'that the store is marked for deletion, after the transaction is ' + + 'aborted'); + assert_throws_dom( + 'InvalidStateError', () => index.get('query'), + 'IDBIndex.get should throw InvalidStateError, indicating that ' + + 'the index is still marked for deletion, after the transaction ' + + 'is aborted'); + assert_array_equals( + migrationDatabase.objectStoreNames, ['books'], + 'IDBDatabase.objectStoreNames should stop including the newly ' + + 'created store after the transaction is aborted'); + assert_array_equals( + migrationTransaction.objectStoreNames, ['books'], + 'IDBTransaction.objectStoreNames should stop including the newly ' + + 'created store after the transaction is aborted'); + assert_array_equals( + store.indexNames, [], + 'IDBObjectStore.indexNames for the newly created store should be ' + + 'empty after the transaction is aborted'); + }); +}, 'Deleted indexes in newly created stores are still marked as deleted ' + + 'after the transaction aborts'); + +promise_test(testCase => { + let store = null, index = null; + let migrationTransaction = null, migrationDatabase = null; + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + createNotBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + migrationDatabase = database; + migrationTransaction = transaction; + store = transaction.objectStore('not_books'); + index = store.index('not_by_author'); + store.deleteIndex('not_by_author'); + assert_throws_dom( + 'InvalidStateError', () => index.get('query'), + 'IDBIndex.get should throw InvalidStateError, indicating that ' + + 'the index is marked for deletion, immediately after ' + + 'IDBObjectStore.deleteIndex() returns'); + assert_array_equals( + store.indexNames, ['not_by_title'], + 'IDBObjectStore.indexNames should not include the deleted index ' + + 'immediately after IDBObjectStore.deleteIndex() returns'); + + database.deleteObjectStore('not_books'); + assert_throws_dom( + 'InvalidStateError', () => store.get('query'), + 'IDBObjectStore.get should throw InvalidStateError, indicating ' + + 'that the store is marked for deletion, immediately after ' + + 'IDBDatabase.deleteObjectStore() returns'); + assert_throws_dom( + 'InvalidStateError', () => index.get('query'), + 'IDBIndex.get should throw InvalidStateError, indicating that ' + + 'the index is still marked for deletion, immediately after ' + + 'IDBObjectStore.deleteIndex() returns'); + assert_array_equals( + transaction.objectStoreNames, ['books'], + 'IDBTransaction.objectStoreNames should stop including the ' + + 'deleted store immediately after IDBDatabase.deleteObjectStore() ' + + 'returns'); + assert_array_equals( + database.objectStoreNames, ['books'], + 'IDBDatabase.objectStoreNames should stop including the newly ' + + 'created store immediately after IDBDatabase.deleteObjectStore() ' + + 'returns'); + assert_array_equals( + store.indexNames, [], + 'IDBObjectStore.indexNames for the deleted store should be empty ' + + 'immediately after IDBDatabase.deleteObjectStore() returns'); + + transaction.abort(); + assert_throws_dom( + 'TransactionInactiveError', () => store.get('query'), + 'IDBObjectStore.get should throw TransactionInactiveError, ' + + 'indicating that the store is no longer marked for deletion, ' + + 'immediately after IDBTransaction.abort() returns'); + assert_throws_dom( + 'TransactionInactiveError', () => index.get('query'), + 'IDBIndex.get should throw TransactionInactiveError, indicating ' + + 'that the index is no longer marked for deletion, immediately ' + + 'after IDBObjectStore.deleteIndex() returns'); + assert_array_equals( + database.objectStoreNames, ['books', 'not_books'], + 'IDBDatabase.objectStoreNames should include the deleted store ' + + 'store immediately after IDBTransaction.abort() returns'); + assert_array_equals( + transaction.objectStoreNames, ['books', 'not_books'], + 'IDBTransaction.objectStoreNames should include the deleted ' + + 'store immediately after IDBTransaction.abort() returns'); + assert_array_equals( + store.indexNames, ['not_by_author', 'not_by_title'], + 'IDBObjectStore.indexNames for the deleted store should not be ' + + 'empty any more immediately after IDBTransaction.abort() returns'); + })).then(() => { + assert_throws_dom( + 'TransactionInactiveError', () => store.get('query'), + 'IDBObjectStore.get should throw TransactionInactiveError, ' + + 'indicating that the store is no longer marked for deletion, ' + + 'after the transaction is aborted'); + assert_throws_dom( + 'TransactionInactiveError', () => index.get('query'), + 'IDBIndex.get should throw TransactionInactiveError, indicating ' + + 'that the index is no longer marked for deletion, after the ' + + 'transaction is aborted'); + assert_array_equals( + migrationDatabase.objectStoreNames, ['books', 'not_books'], + 'IDBDatabase.objectStoreNames should include the previously ' + + 'deleted store after the transaction is aborted'); + assert_array_equals( + migrationTransaction.objectStoreNames, ['books', 'not_books'], + 'IDBTransaction.objectStoreNames should include the previously ' + + 'deleted store after the transaction is aborted'); + assert_array_equals( + store.indexNames, ['not_by_author', 'not_by_title'], + 'IDBObjectStore.indexNames for the deleted store should not be ' + + 'empty after the transaction is aborted'); + }); +}, 'Deleted indexes in deleted stores are still marked as not-deleted after ' + + 'the transaction aborts'); + +promise_test(testCase => { + let store = null, index = null; + let migrationTransaction = null, migrationDatabase = null; + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + store = createNotBooksStore(testCase, database); + migrationDatabase = database; + migrationTransaction = transaction; + index = store.index('not_by_author'); + store.deleteIndex('not_by_author'); + assert_throws_dom( + 'InvalidStateError', () => index.get('query'), + 'IDBIndex.get should throw InvalidStateError, indicating that ' + + 'the index is marked for deletion, immediately after ' + + 'IDBObjectStore.deleteIndex() returns'); + assert_array_equals( + store.indexNames, ['not_by_title'], + 'IDBObjectStore.indexNames should not include the deleted index ' + + 'immediately after IDBObjectStore.deleteIndex() returns'); + + database.deleteObjectStore('not_books'); + assert_throws_dom( + 'InvalidStateError', () => store.get('query'), + 'IDBObjectStore.get should throw InvalidStateError, indicating ' + + 'that the store is marked for deletion, immediately after ' + + 'IDBDatabase.deleteObjectStore() returns'); + assert_throws_dom( + 'InvalidStateError', () => index.get('query'), + 'IDBIndex.get should throw InvalidStateError, indicating that ' + + 'the index is still marked for deletion, immediately after ' + + 'IDBDatabase.deleteObjectStore() returns'); + assert_array_equals( + transaction.objectStoreNames, ['books'], + 'IDBTransaction.objectStoreNames should stop including the ' + + 'deleted store immediately after IDBDatabase.deleteObjectStore() ' + + 'returns'); + assert_array_equals( + database.objectStoreNames, ['books'], + 'IDBDatabase.objectStoreNames should stop including the newly ' + + 'created store immediately after IDBDatabase.deleteObjectStore() ' + + 'returns'); + assert_array_equals( + store.indexNames, [], + 'IDBObjectStore.indexNames should be empty immediately after ' + + 'IDBDatabase.deleteObjectStore() returns'); + + transaction.abort(); + assert_throws_dom( + 'InvalidStateError', () => store.get('query'), + 'IDBObjectStore.get should throw InvalidStateError, indicating ' + + 'that the store is still marked for deletion, immediately after ' + + 'IDBTransaction.abort() returns'); + assert_throws_dom( + 'InvalidStateError', () => index.get('query'), + 'IDBIndex.get should throw InvalidStateError, indicating that ' + + 'the index is still marked for deletion, immediately after ' + + 'IDBTransaction.abort() returns'); + assert_array_equals( + transaction.objectStoreNames, ['books'], + 'IDBTransaction.objectStoreNames should not include the newly ' + + 'created store immediately after IDBTransaction.abort() returns'); + assert_array_equals( + database.objectStoreNames, ['books'], + 'IDBDatabase.objectStoreNames should not include the newly ' + + 'created store immediately after IDBTransaction.abort() returns'); + assert_array_equals( + store.indexNames, [], + 'IDBObjectStore.indexNames should be empty immediately after ' + + 'IDBTransaction.abort() returns'); + })).then(() => { + assert_throws_dom( + 'InvalidStateError', () => store.get('query'), + 'IDBObjectStore.get should throw InvalidStateError, indicating ' + + 'that the store is still marked for deletion, after the ' + + 'transaction is aborted'); + assert_throws_dom( + 'InvalidStateError', () => index.get('query'), + 'IDBIndex.get should throw InvalidStateError, indicating that ' + + 'the index is still marked for deletion, after the transaction ' + + 'is aborted'); + assert_array_equals( + migrationDatabase.objectStoreNames, ['books'], + 'IDBDatabase.objectStoreNames should not include the newly ' + + 'created store after the transaction is aborted'); + assert_array_equals( + migrationTransaction.objectStoreNames, ['books'], + 'IDBTransaction.objectStoreNames should not include the newly ' + + 'created store after the transaction is aborted'); + assert_array_equals( + store.indexNames, [], + 'IDBObjectStore.indexNames should be empty after the transaction ' + + 'is aborted'); + }); +}, 'Deleted indexes in created+deleted stores are still marked as deleted ' + + 'after their transaction aborts'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/transaction-abort-object-store-metadata-revert.html b/testing/web-platform/tests/IndexedDB/transaction-abort-object-store-metadata-revert.html new file mode 100644 index 0000000000..c31537bc5c --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/transaction-abort-object-store-metadata-revert.html @@ -0,0 +1,235 @@ +<!doctype html> +<meta charset="utf8"> +<title>IndexedDB: aborting transactions reverts object store metadata</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#abort-transaction"> +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script> +'use strict'; + +promise_test(testCase => { + let store = null, migrationTransaction = null, migrationDatabase = null; + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + store = createNotBooksStore(testCase, database); + migrationDatabase = database; + migrationTransaction = transaction; + assert_array_equals( + database.objectStoreNames, ['books', 'not_books'], + 'IDBDatabase.objectStoreNames should include a newly created ' + + 'store before the transaction is aborted'); + assert_array_equals( + transaction.objectStoreNames, ['books', 'not_books'], + 'IDBTransaction.objectStoreNames should include a newly created ' + + 'store before the transaction is aborted'); + + transaction.abort(); + assert_throws_dom( + 'InvalidStateError', () => store.get('query'), + 'IDBObjectStore.get should throw InvalidStateError, indicating ' + + 'that the store is marked for deletion, immediately after ' + + 'IDBTransaction.abort() returns'); + assert_array_equals( + transaction.objectStoreNames, ['books'], + 'IDBTransaction.objectStoreNames should stop including the newly ' + + 'created store immediately after IDBTransaction.abort() returns'); + assert_array_equals( + database.objectStoreNames, ['books'], + 'IDBDatabase.objectStoreNames should stop including the newly ' + + 'created store immediately after IDBTransaction.abort() returns'); + })).then(() => { + assert_throws_dom( + 'InvalidStateError', () => store.get('query'), + 'IDBObjectStore.get should throw InvalidStateError, indicating ' + + 'that the store is marked for deletion, after the transaction is ' + + 'aborted'); + assert_array_equals( + migrationDatabase.objectStoreNames, ['books'], + 'IDBDatabase.objectStoreNames should stop including the newly ' + + 'created store after the transaction is aborted'); + assert_array_equals( + migrationTransaction.objectStoreNames, ['books'], + 'IDBTransaction.objectStoreNames should stop including the newly ' + + 'created store after the transaction is aborted'); + }); +}, 'Created stores get marked as deleted after their transaction aborts'); + +promise_test(testCase => { + let store = null, migrationTransaction = null, migrationDatabase = null; + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + createNotBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + migrationDatabase = database; + migrationTransaction = transaction; + store = transaction.objectStore('not_books'); + + database.deleteObjectStore('not_books'); + assert_throws_dom( + 'InvalidStateError', () => store.get('query'), + 'IDBObjectStore.get should throw InvalidStateError, indicating ' + + 'that the store is marked for deletion, immediately after ' + + 'IDBDatabase.deleteObjectStore() returns'); + assert_array_equals( + transaction.objectStoreNames, ['books'], + 'IDBTransaction.objectStoreNames should stop including the ' + + 'deleted store immediately after IDBDatabase.deleteObjectStore() ' + + 'returns'); + assert_array_equals( + database.objectStoreNames, ['books'], + 'IDBDatabase.objectStoreNames should stop including the newly ' + + 'created store immediately after IDBDatabase.deleteObjectStore() ' + + 'returns'); + + transaction.abort(); + assert_throws_dom( + 'TransactionInactiveError', () => store.get('query'), + 'IDBObjectStore.get should throw TransactionInactiveError, ' + + 'indicating that the store is no longer marked for deletion, ' + + 'immediately after IDBTransaction.abort() returns'); + assert_array_equals( + database.objectStoreNames, ['books', 'not_books'], + 'IDBDatabase.objectStoreNames should include the deleted store ' + + 'store immediately after IDBTransaction.abort() returns'); + assert_array_equals( + transaction.objectStoreNames, ['books', 'not_books'], + 'IDBTransaction.objectStoreNames should include the deleted ' + + 'store immediately after IDBTransaction.abort() returns'); + })).then(() => { + assert_throws_dom( + 'TransactionInactiveError', () => store.get('query'), + 'IDBObjectStore.get should throw TransactionInactiveError, ' + + 'indicating that the store is no longer marked for deletion, ' + + 'after the transaction is aborted'); + assert_array_equals( + migrationDatabase.objectStoreNames, ['books', 'not_books'], + 'IDBDatabase.objectStoreNames should include the previously ' + + 'deleted store after the transaction is aborted'); + assert_array_equals( + migrationTransaction.objectStoreNames, ['books', 'not_books'], + 'IDBTransaction.objectStoreNames should include the previously ' + + 'deleted store after the transaction is aborted'); + }); +}, 'Deleted stores get marked as not-deleted after the transaction aborts'); + +promise_test(testCase => { + let store = null, migrationTransaction = null, migrationDatabase = null; + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + store = createNotBooksStore(testCase, database); + migrationDatabase = database; + migrationTransaction = transaction; + assert_array_equals( + database.objectStoreNames, ['books', 'not_books'], + 'IDBDatabase.objectStoreNames should include a newly created ' + + 'store before the transaction is aborted'); + assert_array_equals( + transaction.objectStoreNames, ['books', 'not_books'], + 'IDBTransaction.objectStoreNames should include a newly created ' + + 'store before the transaction is aborted'); + + database.deleteObjectStore('not_books'); + assert_throws_dom( + 'InvalidStateError', () => store.get('query'), + 'IDBObjectStore.get should throw InvalidStateError, indicating ' + + 'that the store is marked for deletion, immediately after ' + + 'IDBDatabase.deleteObjectStore() returns'); + assert_array_equals( + transaction.objectStoreNames, ['books'], + 'IDBTransaction.objectStoreNames should stop including the ' + + 'deleted store immediately after IDBDatabase.deleteObjectStore() ' + + 'returns'); + assert_array_equals( + database.objectStoreNames, ['books'], + 'IDBDatabase.objectStoreNames should stop including the newly ' + + 'created store immediately after IDBDatabase.deleteObjectStore() ' + + 'returns'); + + transaction.abort(); + assert_throws_dom( + 'InvalidStateError', () => store.get('query'), + 'IDBObjectStore.get should throw InvalidStateError, indicating ' + + 'that the store is still marked for deletion, immediately after ' + + 'IDBTransaction.abort() returns'); + assert_array_equals( + transaction.objectStoreNames, ['books'], + 'IDBTransaction.objectStoreNames should not include the newly ' + + 'created store immediately after IDBTransaction.abort() returns'); + assert_array_equals( + database.objectStoreNames, ['books'], + 'IDBDatabase.objectStoreNames should not include the newly ' + + 'created store immediately after IDBTransaction.abort() returns'); + })).then(() => { + assert_throws_dom( + 'InvalidStateError', () => store.get('query'), + 'IDBObjectStore.get should throw InvalidStateError, indicating ' + + 'that the store is still marked for deletion, after the ' + + 'transaction is aborted'); + assert_array_equals( + migrationDatabase.objectStoreNames, ['books'], + 'IDBDatabase.objectStoreNames should not include the newly ' + + 'created store after the transaction is aborted'); + assert_array_equals( + migrationTransaction.objectStoreNames, ['books'], + 'IDBTransaction.objectStoreNames should not include the newly ' + + 'created store after the transaction is aborted'); + }); +}, 'Created+deleted stores are still marked as deleted after their ' + + 'transaction aborts'); + +promise_test(testCase => { + let migrationTransaction = null, migrationDatabase = null; + return createDatabase(testCase, (database, transaction) => { + createBooksStore(testCase, database); + createNotBooksStore(testCase, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { + migrationDatabase = database; + migrationTransaction = transaction; + + database.deleteObjectStore('not_books'); + assert_array_equals( + transaction.objectStoreNames, ['books'], + 'IDBTransaction.objectStoreNames should stop including the ' + + 'deleted store immediately after IDBDatabase.deleteObjectStore() ' + + 'returns'); + assert_array_equals( + database.objectStoreNames, ['books'], + 'IDBDatabase.objectStoreNames should stop including the newly ' + + 'created store immediately after IDBDatabase.deleteObjectStore() ' + + 'returns'); + + transaction.abort(); + assert_array_equals( + database.objectStoreNames, ['books', 'not_books'], + 'IDBDatabase.objectStoreNames should include the deleted store ' + + 'store immediately after IDBTransaction.abort() returns'); + assert_array_equals( + transaction.objectStoreNames, ['books', 'not_books'], + 'IDBTransaction.objectStoreNames should include the deleted ' + + 'store immediately after IDBTransaction.abort() returns'); + })).then(() => { + assert_array_equals( + migrationDatabase.objectStoreNames, ['books', 'not_books'], + 'IDBDatabase.objectStoreNames should include the previously ' + + 'deleted store after the transaction is aborted'); + assert_array_equals( + migrationTransaction.objectStoreNames, ['books', 'not_books'], + 'IDBTransaction.objectStoreNames should include the previously ' + + 'deleted store after the transaction is aborted'); + }); +}, 'Un-instantiated deleted stores get marked as not-deleted after the ' + + 'transaction aborts'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/transaction-abort-request-error.html b/testing/web-platform/tests/IndexedDB/transaction-abort-request-error.html new file mode 100644 index 0000000000..285e1112c0 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/transaction-abort-request-error.html @@ -0,0 +1,68 @@ +<!doctype html> +<meta charset=utf-8> +<title>IndexedDB: Test error events fired at requests from aborted transaction</title> +<meta name=help href="https://w3c.github.io/IndexedDB/#abort-a-transaction"> +<meta name=help href="https://w3c.github.io/IndexedDB/#request-construct"> +<meta name=help href="https://w3c.github.io/IndexedDB/#transaction-construct"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +indexeddb_test( + (t, db) => { + db.createObjectStore('store'); + }, + (t, db) => { + const tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + const request = tx.objectStore('store').get(0); + tx.abort(); + request.onsuccess = t.unreached_func('request should not succeed'); + + let connection_saw_error = false; + let transaction_saw_error = false; + + request.onerror = t.step_func(e => { + assert_equals(request.readyState, 'done', + 'Request\'s done flag should be set'); + assert_equals(request.result, undefined, + 'Request\'s result should be undefined'); + assert_equals(request.error.name, 'AbortError', + 'Request\'s error should be AbortError'); + + assert_equals(e.target, request, 'event target should be request'); + assert_equals(e.type, 'error', 'Event type should be error'); + assert_true(e.bubbles, 'Event should bubble'); + assert_true(e.cancelable, 'Event should cancelable'); + + assert_true(connection_saw_error, + 'Event propagated through connection'); + assert_true(transaction_saw_error, + 'Event propagated through transaction'); + t.done(); + }); + + // Event propagates via "get the parent" on request and transaction. + + db.addEventListener('error', t.step_func(e => { + connection_saw_error = true; + assert_equals(e.target, request, 'event target should be request'); + assert_equals(e.type, 'error', 'Event type should be error'); + assert_true(e.bubbles, 'Event should bubble'); + assert_true(e.cancelable, 'Event should cancelable'); + }), true); + + tx.addEventListener('error', t.step_func(e => { + transaction_saw_error = true; + assert_equals(e.target, request, 'event target should be request'); + assert_equals(e.type, 'error', 'Event type should be error'); + assert_true(e.bubbles, 'Event should bubble'); + assert_true(e.cancelable, 'Event should cancelable'); + + assert_true(connection_saw_error, + 'Event propagated through connection'); + }), true); + }, + 'Properties of error events fired at requests when aborting a transaction'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/transaction-create_in_versionchange.htm b/testing/web-platform/tests/IndexedDB/transaction-create_in_versionchange.htm new file mode 100644 index 0000000000..d5bcfd45d8 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/transaction-create_in_versionchange.htm @@ -0,0 +1,82 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Attempt to create new transactions inside a versionchange transaction</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var db, events = [], + open_rq = createdb(async_test()) + + open_rq.onupgradeneeded = function(e) { + db = e.target.result + + db.createObjectStore("store") + .add("versionchange1", 1) + .addEventListener("success", log("versionchange_add.success")) + + assert_throws_dom('InvalidStateError', function() { db.transaction("store", "readonly", {durability: 'relaxed'}) }) + + e.target.transaction + .objectStore("store") + .count(2) + .addEventListener("success", log("versionchange_count.success")) + + assert_throws_dom('InvalidStateError', function() { db.transaction("store", "readwrite", {durability: 'relaxed'}) }) + + open_rq.transaction + .objectStore("store") + .add("versionchange2", 2) + .addEventListener("success", log("versionchange_add2.success")) + + open_rq.transaction.oncomplete = function(e) { + log("versionchange_txn.complete")(e) + + db.transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .count() + .addEventListener("success", log("complete_count.success")) + } + } + + open_rq.onsuccess = function(e) { + log("open_rq.success")(e) + + var txn = db.transaction("store", "readwrite", {durability: 'relaxed'}) + txn.objectStore("store") + .put("woo", 1) + .addEventListener("success", log("complete2_get.success")) + + txn.oncomplete = this.step_func(function(e) { + assert_array_equals(events, [ + "versionchange_add.success: 1", + "versionchange_count.success: 0", + "versionchange_add2.success: 2", + "versionchange_txn.complete", + + "open_rq.success: [object IDBDatabase]", + + "complete_count.success: 2", + "complete2_get.success: 1", + ], + "events") + this.done() + }) + } + + + function log(msg) { + return function(e) { + if(e && e.target && e.target.error) + events.push(msg + ": " + e.target.error.name) + else if(e && e.target && e.target.result !== undefined) + events.push(msg + ": " + e.target.result) + else + events.push(msg) + }; + } +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/transaction-deactivation-timing.html b/testing/web-platform/tests/IndexedDB/transaction-deactivation-timing.html new file mode 100644 index 0000000000..ae2e6f6463 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/transaction-deactivation-timing.html @@ -0,0 +1,143 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Transactions deactivation timing</title> +<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbdatabase-transaction"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> +<script> + +indexeddb_test( + (t, db, tx) => { + db.createObjectStore('store'); + }, + (t, db) => { + const tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + const release_tx = keep_alive(tx, 'store'); + assert_true(is_transaction_active(tx, 'store'), + 'Transaction should be active after creation'); + + setTimeout(t.step_func(() => { + assert_false(is_transaction_active(tx, 'store'), + 'Transaction should be inactive in next task'); + release_tx(); + t.done(); + }), 0); + }, + 'New transactions are deactivated before next task'); + +indexeddb_test( + (t, db, tx) => { + db.createObjectStore('store'); + }, + (t, db) => { + const tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + const release_tx = keep_alive(tx, 'store'); + assert_true(is_transaction_active(tx, 'store'), + 'Transaction should be active after creation'); + + Promise.resolve().then(t.step_func(() => { + assert_true(is_transaction_active(tx, 'store'), + 'Transaction should be active in microtask checkpoint'); + release_tx(); + t.done(); + })); + }, + 'New transactions are not deactivated until after the microtask checkpoint'); + +indexeddb_test( + (t, db, tx) => { + db.createObjectStore('store'); + }, + (t, db) => { + let tx, release_tx; + + Promise.resolve().then(t.step_func(() => { + tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + release_tx = keep_alive(tx, 'store'); + assert_true(is_transaction_active(tx, 'store'), + 'Transaction should be active after creation'); + })); + + setTimeout(t.step_func(() => { + assert_false(is_transaction_active(tx, 'store'), + 'Transaction should be inactive in next task'); + release_tx(); + t.done(); + }), 0); + }, + 'New transactions from microtask are deactivated before next task'); + +indexeddb_test( + (t, db, tx) => { + db.createObjectStore('store'); + }, + (t, db) => { + let tx, release_tx; + + Promise.resolve().then(t.step_func(() => { + tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + release_tx = keep_alive(tx, 'store'); + assert_true(is_transaction_active(tx, 'store'), + 'Transaction should be active after creation'); + })); + + Promise.resolve().then(t.step_func(() => { + assert_true(is_transaction_active(tx, 'store'), + 'Transaction should be active in microtask checkpoint'); + release_tx(); + t.done(); + })); + }, + 'New transactions from microtask are still active through the ' + + 'microtask checkpoint'); + + +indexeddb_test( + (t, db, tx) => { + db.createObjectStore('store'); + }, + (t, db) => { + // This transaction serves as the source of an event seen by multiple + // listeners. A DOM event with multiple listeners could be used instead, + // but not via dispatchEvent() because (drumroll...) that happens + // synchronously so microtasks don't run between steps. + const tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + assert_true(is_transaction_active(tx, 'store'), + 'Transaction should be active after creation'); + + const request = tx.objectStore('store').get(0); + let new_tx; + let first_listener_ran = false; + let microtasks_ran = false; + request.addEventListener('success', t.step_func(() => { + first_listener_ran = true; + assert_true(is_transaction_active(tx, 'store'), + 'Transaction should be active in callback'); + + // We check to see if this transaction is active across unrelated event + // dispatch steps. + new_tx = db.transaction('store', 'readonly', {durability: 'relaxed'}); + assert_true(is_transaction_active(new_tx, 'store'), + 'New transaction should be active after creation'); + + Promise.resolve().then(t.step_func(() => { + microtasks_ran = true; + assert_true(is_transaction_active(new_tx, 'store'), + 'New transaction is still active in microtask checkpoint'); + })); + + })); + request.addEventListener('success', t.step_func(() => { + assert_true(first_listener_ran, 'first listener ran first'); + assert_true(microtasks_ran, 'microtasks ran before second listener'); + assert_true(is_transaction_active(tx, 'store'), + 'Transaction should be active in callback'); + assert_false(is_transaction_active(new_tx, 'store'), + 'New transaction should be inactive in unrelated callback'); + t.done(); + })); + }, + 'Deactivation of new transactions happens at end of invocation'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/transaction-lifetime-blocked.htm b/testing/web-platform/tests/IndexedDB/transaction-lifetime-blocked.htm new file mode 100644 index 0000000000..760b6b9bdb --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/transaction-lifetime-blocked.htm @@ -0,0 +1,111 @@ +<!DOCTYPE html> +<title>Blocked event</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<div id="log"></div> + +<script> + + var db, db_got_versionchange, db2, + events = [], + t = async_test(), + dbname = location + '-' + t.name; + + t.step(function() { + indexedDB.deleteDatabase(dbname); + + var openrq = indexedDB.open(dbname, 3); + + // 1 + openrq.onupgradeneeded = t.step_func(function(e) { + events.push("open." + e.type); + e.target.result.createObjectStore('store'); + }); + + // 2 + openrq.onsuccess = t.step_func(function(e) { + db = e.target.result; + + events.push("open." + e.type); + + // 3 + db.onversionchange = t.step_func(function(e) { + events.push("db." + e.type); + + assert_equals(e.oldVersion, 3, "old version"); + assert_equals(e.newVersion, 4, "new version"); + // Do not close db here (as we should) + }); + + // Errors + db.onerror = fail(t, "db.error"); + db.abort = fail(t, "db.abort"); + + step_timeout(t.step_func(OpenSecond), 10); + }); + + // Errors + openrq.onerror = fail(t, "open.error"); + openrq.onblocked = fail(t, "open.blocked"); + + }); + + function OpenSecond (e) { + assert_equals(db2, undefined); + assert_equals(db + "", "[object IDBDatabase]"); + assert_array_equals(db.objectStoreNames, [ "store" ]); + + var openrq2 = indexedDB.open(dbname, 4); + + // 4 + openrq2.onblocked = t.step_func(function(e) { + events.push("open2." + e.type); + // We're closing connection from the other open() + db.close(); + }); + + // 5 + openrq2.onupgradeneeded = t.step_func(function(e) { + db2 = e.target.result; + + events.push("open2." + e.type); + + assert_equals(db2 + "", "[object IDBDatabase]"); + + // Errors + db2.onversionchange = fail(t, "db2.versionchange"); + db2.onerror = fail(t, "db2.error"); + db2.abort = fail(t, "db2.abort"); + }); + + // 6 + openrq2.onsuccess = t.step_func(function(e) { + events.push("open2." + e.type); + + assert_array_equals(events, + [ "open.upgradeneeded", + "open.success", + "db.versionchange", + "open2.blocked", + "open2.upgradeneeded", + "open2.success", + ]); + + step_timeout(function() { t.done(); }, 10); + }); + + // Errors + openrq2.onerror = fail(t, "open2.error"); + } + + + // Cleanup + add_completion_callback(function(tests) { + if (db2) db2.close(); + indexedDB.deleteDatabase(dbname); + }) + +</script> diff --git a/testing/web-platform/tests/IndexedDB/transaction-lifetime-empty.html b/testing/web-platform/tests/IndexedDB/transaction-lifetime-empty.html new file mode 100644 index 0000000000..86bd41ae62 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/transaction-lifetime-empty.html @@ -0,0 +1,89 @@ +<!DOCTYPE html> +<title>IndexedDB: Commit ordering of empty transactions</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> +<script> + +// Call with a test object and array of expected values. Returns a +// function to call with each actual value. Once the expected number +// of values is seen, asserts that the value orders match and completes +// the test. +function expect(t, expected) { + var results = []; + return result => { + results.push(result); + if (results.length === expected.length) { + assert_array_equals(results, expected); + t.done(); + } + }; +} + +indexeddb_test( + (t, db) => { + db.createObjectStore('store'); + }, + (t, db) => { + var saw = expect(t, ['rq1.onsuccess', + 'rq2.onsuccess', + 'tx1.oncomplete', + 'tx2.oncomplete']); + + var tx1 = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + tx1.onabort = t.unreached_func('transaction should commit'); + tx1.oncomplete = t.step_func(() => saw('tx1.oncomplete')); + + var store = tx1.objectStore('store'); + var rq1 = store.put('a', 1); + rq1.onerror = t.unreached_func('put should succeed'); + rq1.onsuccess = t.step_func(() => { + saw('rq1.onsuccess'); + + var tx2 = db.transaction('store', 'readonly', {durability: 'relaxed'}); + tx2.onabort = t.unreached_func('transaction should commit'); + tx2.oncomplete = t.step_func(() => saw('tx2.oncomplete')); + + var rq2 = store.put('b', 2); + rq2.onsuccess = t.step_func(() => saw('rq2.onsuccess')); + rq2.onerror = t.unreached_func('request should succeed'); + }); + + }, + 'Transactions without requests complete in the expected order'); + +indexeddb_test( + (t, db) => { + db.createObjectStore('store'); + }, + (t, db) => { + var saw = expect(t, ['rq1.onsuccess', + 'rq2.onsuccess', + 'tx1.oncomplete', + 'tx2.oncomplete', + 'tx3.oncomplete']); + var tx1 = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + tx1.onabort = t.unreached_func('transaction should commit'); + tx1.oncomplete = t.step_func(() => saw('tx1.oncomplete')); + + var store = tx1.objectStore('store'); + var rq1 = store.put('a', 1); + rq1.onerror = t.unreached_func('put should succeed'); + rq1.onsuccess = t.step_func(() => { + saw('rq1.onsuccess'); + + var tx2 = db.transaction('store', 'readonly', {durability: 'relaxed'}); + tx2.onabort = t.unreached_func('transaction should commit'); + tx2.oncomplete = t.step_func(() => saw('tx2.oncomplete')); + + var tx3 = db.transaction('store', 'readonly', {durability: 'relaxed'}); + tx3.onabort = t.unreached_func('transaction should commit'); + tx3.oncomplete = t.step_func(() => saw('tx3.oncomplete')); + + var rq2 = store.put('b', 2); + rq2.onsuccess = t.step_func(() => saw('rq2.onsuccess')); + rq2.onerror = t.unreached_func('request should succeed'); + }); + }, + 'Multiple transactions without requests complete in the expected order'); +</script> diff --git a/testing/web-platform/tests/IndexedDB/transaction-lifetime.htm b/testing/web-platform/tests/IndexedDB/transaction-lifetime.htm new file mode 100644 index 0000000000..996f62937f --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/transaction-lifetime.htm @@ -0,0 +1,104 @@ +<!DOCTYPE html> +<title>Test events opening a second database when one connection is open already</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<div id="log"></div> + +<script> + + var db, db_got_versionchange, db2, + events = [], + t = async_test(), + dbname = location + '-' + t.name; + + t.step(function() { + indexedDB.deleteDatabase(dbname); + + var openrq = indexedDB.open(dbname, 3); + + // 1 + openrq.onupgradeneeded = t.step_func(function(e) { + events.push("open." + e.type); + e.target.result.createObjectStore('store'); + }); + + // 2 + openrq.onsuccess = t.step_func(function(e) { + db = e.target.result; + + events.push("open." + e.type); + + // 3 + db.onversionchange = t.step_func(function(e) { + events.push("db." + e.type); + + assert_equals(e.oldVersion, 3, "old version"); + assert_equals(e.newVersion, 4, "new version"); + db.close(); + }); + + // Errors + db.onerror = fail(t, "db.error"); + db.abort = fail(t, "db.abort"); + + step_timeout(t.step_func(OpenSecond), 10); + }); + + // Errors + openrq.onerror = fail(t, "open.error"); + openrq.onblocked = fail(t, "open.blocked"); + + }); + + function OpenSecond (e) { + assert_equals(db2, undefined); + assert_equals(db + "", "[object IDBDatabase]"); + assert_array_equals(db.objectStoreNames, [ "store" ]); + + var openrq2 = indexedDB.open(dbname, 4); + + // 4 + openrq2.onupgradeneeded = t.step_func(function(e) { + db2 = e.target.result; + + events.push("open2." + e.type); + + assert_equals(db2 + "", "[object IDBDatabase]"); + + // Errors + db2.onversionchange = fail(t, "db2.versionchange"); + db2.onerror = fail(t, "db2.error"); + db2.abort = fail(t, "db2.abort"); + }); + + // 5 + openrq2.onsuccess = t.step_func(function(e) { + events.push("open2." + e.type); + + assert_array_equals(events, + [ "open.upgradeneeded", + "open.success", + "db.versionchange", + "open2.upgradeneeded", + "open2.success", + ]); + + step_timeout(function() { t.done(); }, 10); + }); + + // Errors + openrq2.onerror = fail(t, "open2.error"); + openrq2.onblocked = fail(t, "open2.blocked"); + } + + + // Cleanup + add_completion_callback(function(tests) { + if (db2) db2.close(); + indexedDB.deleteDatabase(dbname); + }) + +</script> diff --git a/testing/web-platform/tests/IndexedDB/transaction-relaxed-durability.tentative.any.js b/testing/web-platform/tests/IndexedDB/transaction-relaxed-durability.tentative.any.js new file mode 100644 index 0000000000..9197389a24 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/transaction-relaxed-durability.tentative.any.js @@ -0,0 +1,53 @@ +// META: script=resources/support-promises.js +// META: timeout=long + +/** + * This file contains the webplatform smoke tests for the optional + * durability parameter of the IndexedDB transaction API. + * + * @author enne@chromium.org + */ + +// Smoke test optional parameter on IndexedDB.transaction. +let cases = [ + { options: undefined, expected: 'default' }, + { options: {}, expected: 'default' }, + { options: { durability: 'default'}, expected: 'default' }, + { options: { durability: 'relaxed'}, expected: 'relaxed' }, + { options: { durability: 'strict'}, expected: 'strict' }, +]; + +for (let i = 0; i < cases.length; ++i) { + promise_test(async testCase => { + const db = await createDatabase(testCase, db => { + createBooksStore(testCase, db); + }); + const txn = db.transaction(['books'], 'readwrite', cases[i].options); + const objectStore = txn.objectStore('books'); + objectStore.put({isbn: 'one', title: 'title1'}); + await promiseForTransaction(testCase, txn); + + assert_equals(txn.durability, cases[i].expected); + + const txn2 = db.transaction(['books'], 'readonly'); + const objectStore2 = txn2.objectStore('books'); + const getTitle1 = objectStore2.get('one'); + await promiseForTransaction(testCase, txn2); + assert_array_equals( + [getTitle1.result.title], + ['title1'], + 'The title should match that which was put.'); + db.close(); + }, 'Committed data can be read back out: case ' + i); +} + +promise_test(async testCase => { + const db = await createDatabase(testCase, db => { + createBooksStore(testCase, db); + }); + + assert_throws_js(TypeError, function() { + db.transaction(['books'], 'readwrite', { durability: 'invalid' }); + }); + db.close(); +}, 'Invalid durability option throws a TypeError'); diff --git a/testing/web-platform/tests/IndexedDB/transaction-requestqueue.htm b/testing/web-platform/tests/IndexedDB/transaction-requestqueue.htm new file mode 100644 index 0000000000..cef8699df4 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/transaction-requestqueue.htm @@ -0,0 +1,89 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Transactions have a request queue</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + +var db, t = async_test(), + keys = { txn: [], txn2: [] }, + open_rq = createdb(t) + +open_rq.onupgradeneeded = function(e) { + var i, os; + db = e.target.result; + + for (i = 1; i < 6; i++) + { + os = db.createObjectStore("os" + i, { autoIncrement: true, keyPath: "k" }); + os.add({ os: "os" + i }); + os.put({ os: "os" + i, k: i}); + os.add({ os: "os" + i }); + } +} + +open_rq.onsuccess = function(e) { + var txn = db.transaction(["os2", "os1", "os3", "os5"]) + txn.objectStore("os1").openCursor().onsuccess = reg("txn") + txn.objectStore("os3").openCursor().onsuccess = reg("txn") + txn.objectStore("os1").get(2).onsuccess = reg("txn") + txn.objectStore("os2").get(3).onsuccess = reg("txn") + + var txn2 = db.transaction(["os4", "os3", "os1", "os5"]) + var os4 = txn2.objectStore("os4") + + for (var i=0; i < 3; i++) { + os4.openCursor().onsuccess = reg("txn2") + os4.get(5).onsuccess = reg("txn2") + os4.get(4).onsuccess = reg("txn2") + txn.objectStore("os2").get(1).onsuccess = reg("txn") + txn2.objectStore("os3").get(1).onsuccess = reg("txn2") + } + + txn2.objectStore("os1").get(2).onsuccess = reg("txn2") + txn.objectStore("os1").openCursor(null, "prev").onsuccess = reg("txn") + os4.openCursor(null, "prev").onsuccess = reg("txn2") + + txn.oncomplete = t.step_func(finish); + txn2.oncomplete = t.step_func(finish); +} + + +function reg(n) { + return t.step_func(function (e) { + var v = e.target.result; + if (v.value) v = v.value; + keys[n].push(v.os + ": " + v.k); + }); +} + +var finish_to_go = 2; +function finish() { + if (--finish_to_go) + return; + + assert_array_equals(keys['txn'], [ + "os1: 1", + "os3: 1", + "os1: 2", + "os2: 3", + "os2: 1", "os2: 1", "os2: 1", + "os1: 2", + ], 'transaction keys'); + + assert_array_equals(keys['txn2'], [ + "os4: 1", "os4: 5", "os4: 4", "os3: 1", + "os4: 1", "os4: 5", "os4: 4", "os3: 1", + "os4: 1", "os4: 5", "os4: 4", "os3: 1", + "os1: 2", + "os4: 5", + ], 'transaction 2 keys'); + + t.done(); +} +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/transaction-scheduling-across-connections.any.js b/testing/web-platform/tests/IndexedDB/transaction-scheduling-across-connections.any.js new file mode 100644 index 0000000000..06d886fe83 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/transaction-scheduling-across-connections.any.js @@ -0,0 +1,72 @@ +// META: script=resources/support.js + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('store'); + }, + + (t, db1) => { + // Open a second connection to the same database. + const open_request = indexedDB.open(db1.name); + open_request.onerror = t.unreached_func('open() should succeed'); + open_request.onupgradeneeded = + t.unreached_func('second connection should not upgrade'); + open_request.onsuccess = t.step_func(() => { + const db2 = open_request.result; + t.add_cleanup(() => { db2.close(); }); + + const transaction1 = db1.transaction('store', 'readwrite', {durability: 'relaxed'}); + transaction1.onabort = t.unreached_func('transaction1 should complete'); + + const transaction2 = db2.transaction('store', 'readwrite', {durability: 'relaxed'}); + transaction2.onabort = t.unreached_func('transaction2 should complete'); + + let transaction1PutSuccess = false; + let transaction1Complete = false; + let transaction2PutSuccess = false; + + // Keep transaction1 alive for a while and ensure transaction2 + // doesn't start. + + let count = 0; + (function doTransaction1Put() { + const request = transaction1.objectStore('store').put(1, count++); + request.onerror = t.unreached_func('request should succeed'); + request.onsuccess = t.step_func(evt => { + transaction1PutSuccess = true; + if (count < 5) { + doTransaction1Put(); + } + }); + }()); + + transaction1.oncomplete = t.step_func(evt => { + transaction1Complete = true; + assert_false( + transaction2PutSuccess, + 'transaction1 should complete before transaction2 put succeeds'); + }); + + const request = transaction2.objectStore('store').put(2, 0); + request.onerror = t.unreached_func('request should succeed'); + request.onsuccess = t.step_func(evt => { + transaction2PutSuccess = true; + assert_true( + transaction1Complete, + 'transaction2 put should not succeed before transaction1 completes'); + }); + + transaction2.oncomplete = t.step_func_done(evt => { + assert_true( + transaction1PutSuccess, + 'transaction1 put should succeed before transaction2 runs'); + assert_true( + transaction1Complete, + 'transaction1 should complete before transaction2 runs'); + assert_true( + transaction2PutSuccess, + 'transaction2 put should succeed before transaction2 completes'); + }); + }); + }, + "Check that readwrite transactions with overlapping scopes do not run in parallel."); diff --git a/testing/web-platform/tests/IndexedDB/transaction-scheduling-across-databases.any.js b/testing/web-platform/tests/IndexedDB/transaction-scheduling-across-databases.any.js new file mode 100644 index 0000000000..09d8ef20d1 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/transaction-scheduling-across-databases.any.js @@ -0,0 +1,72 @@ +// META: script=resources/support.js + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('store'); + }, + + (t, db1) => { + // Open a second database. + const db2name = db1.name + '-2'; + const delete_request = indexedDB.deleteDatabase(db2name); + delete_request.onerror = t.unreached_func('deleteDatabase() should succeed'); + const open_request = indexedDB.open(db2name, 1); + open_request.onerror = t.unreached_func('open() should succeed'); + open_request.onupgradeneeded = t.step_func(() => { + const db2 = open_request.result; + const store = db2.createObjectStore('store'); + }); + open_request.onsuccess = t.step_func(() => { + const db2 = open_request.result; + t.add_cleanup(() => { + db2.close(); + indexedDB.deleteDatabase(db2.name); + }); + + let transaction1PutSuccess = false; + let transaction2PutSuccess = false; + + const onTransactionComplete = barrier_func(2, t.step_func_done(() => { + assert_true(transaction1PutSuccess, + 'transaction1 should have executed at least one request'); + assert_true(transaction2PutSuccess, + 'transaction1 should have executed at least one request'); + })); + + + const transaction1 = db1.transaction('store', 'readwrite', {durability: 'relaxed'}); + transaction1.onabort = t.unreached_func('transaction1 should complete'); + transaction1.oncomplete = t.step_func(onTransactionComplete); + + const transaction2 = db2.transaction('store', 'readwrite', {durability: 'relaxed'}); + transaction2.onabort = t.unreached_func('transaction2 should complete'); + transaction2.oncomplete = t.step_func(onTransactionComplete); + + // Keep both transactions alive until each has reported at least one + // successful operation. + + function doTransaction1Put() { + const request = transaction1.objectStore('store').put(0, 0); + request.onerror = t.unreached_func('put request should succeed'); + request.onsuccess = t.step_func(() => { + transaction1PutSuccess = true; + if (!transaction2PutSuccess) + doTransaction1Put(); + }); + } + + function doTransaction2Put() { + const request = transaction2.objectStore('store').put(0, 0); + request.onerror = t.unreached_func('put request should succeed'); + request.onsuccess = t.step_func(() => { + transaction2PutSuccess = true; + if (!transaction1PutSuccess) + doTransaction2Put(); + }); + } + + doTransaction1Put(); + doTransaction2Put(); + }); + }, + "Check that transactions in different databases can run in parallel."); diff --git a/testing/web-platform/tests/IndexedDB/transaction-scheduling-mixed-scopes.any.js b/testing/web-platform/tests/IndexedDB/transaction-scheduling-mixed-scopes.any.js new file mode 100644 index 0000000000..6f550c58a5 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/transaction-scheduling-mixed-scopes.any.js @@ -0,0 +1,63 @@ +// META: script=resources/support.js + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('store'); + db.createObjectStore('a'); + db.createObjectStore('b'); + db.createObjectStore('c'); + }, + + (t, db) => { + let transaction1Started = false; + let transaction1Complete = false; + let transaction2Started = false; + let transaction2Complete = false; + let transaction3Started = false; + let transaction3Complete = false; + + const transaction1 = db.transaction(['a'], 'readonly'); + let request = transaction1.objectStore('a').get(0); + request.onerror = t.unreached_func('request should succeed'); + request.onsuccess = t.step_func(() => { + transaction1Started = true; + }); + transaction1.onabort = t.unreached_func('transaction1 should complete'); + transaction1.oncomplete = t.step_func(() => { + transaction1Complete = true; + assert_false(transaction2Started); + assert_false(transaction3Started); + }); + + + // transaction2 overlaps with transaction1, so must wait until transaction1 + // completes. + const transaction2 = db.transaction(['a', 'b'], 'readwrite'); + request = transaction2.objectStore('a').get(0); + request.onerror = t.unreached_func('request should succeed'); + request.onsuccess = t.step_func(() => { + assert_true(transaction1Complete); + transaction2Started = true; + }); + transaction2.onabort = t.unreached_func('transaction2 should complete'); + transaction2.oncomplete = t.step_func(() => { + transaction2Complete = true; + assert_false(transaction3Started); + }); + + // transaction3 overlaps with transaction2, so must wait until transaction2 + // completes even though it does not overlap with transaction1. + const transaction3 = db.transaction(['b', 'c'], 'readonly'); + request = transaction3.objectStore('b').get(0); + request.onerror = t.unreached_func('request should succeed'); + request.onsuccess = t.step_func(() => { + assert_true(transaction1Complete); + assert_true(transaction2Complete); + transaction3Started = true; + }); + transaction3.onabort = t.unreached_func('transaction3 should complete'); + transaction3.oncomplete = t.step_func_done(() => { + transaction3Complete = true; + }); + }, + "Check that scope restrictions on mixed transactions are enforced."); diff --git a/testing/web-platform/tests/IndexedDB/transaction-scheduling-ordering.any.js b/testing/web-platform/tests/IndexedDB/transaction-scheduling-ordering.any.js new file mode 100644 index 0000000000..42d056e9a1 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/transaction-scheduling-ordering.any.js @@ -0,0 +1,40 @@ +// META: script=resources/support.js + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('store'); + }, + + (t, db) => { + // Create in order tx1, tx2. + const tx1 = db.transaction('store', 'readwrite', { durability: 'relaxed' }); + const tx2 = db.transaction('store', 'readwrite', { durability: 'relaxed' }); + + // Use in order tx2, tx1. + tx2.objectStore('store').get(0); + tx1.objectStore('store').get(0); + + const order = []; + const done = barrier_func(2, t.step_func_done(() => { + // IndexedDB Spec: + // https://w3c.github.io/IndexedDB/#transaction-scheduling + // + // If multiple "readwrite" transactions are attempting to + // access the same object store (i.e. if they have overlapping + // scope), the transaction that was created first must be the + // transaction which gets access to the object store first. + // + assert_array_equals(order, [1, 2]); + })); + + tx1.oncomplete = t.step_func(e => { + order.push(1); + done(); + }); + + tx2.oncomplete = t.step_func(e => { + order.push(2); + done(); + }); + }, + "Verify Indexed DB transactions are ordered per spec"); diff --git a/testing/web-platform/tests/IndexedDB/transaction-scheduling-ro-waits-for-rw.any.js b/testing/web-platform/tests/IndexedDB/transaction-scheduling-ro-waits-for-rw.any.js new file mode 100644 index 0000000000..ae0d126808 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/transaction-scheduling-ro-waits-for-rw.any.js @@ -0,0 +1,26 @@ +// META: script=resources/support.js + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('store'); + store.put('value', 'key'); + }, + + (t, db) => { + const transaction1 = db.transaction('store', 'readwrite', {durability: 'relaxed'}); + transaction1.onabort = t.unreached_func('transaction1 should not abort'); + + const transaction2 = db.transaction('store', 'readonly', {durability: 'relaxed'}); + transaction2.onabort = t.unreached_func('transaction2 should not abort'); + + const request = transaction1.objectStore('store').put('new value', 'key'); + request.onerror = t.unreached_func('request should not fail'); + + const request2 = transaction2.objectStore('store').get('key'); + request2.onerror = t.unreached_func('request2 should not fail'); + request2.onsuccess = t.step_func_done(evt => { + assert_equals(request2.result, 'new value', + 'Request should see new value.'); + }); + }, + "readonly transaction should see the result of a previous readwrite transaction"); diff --git a/testing/web-platform/tests/IndexedDB/transaction-scheduling-rw-scopes.any.js b/testing/web-platform/tests/IndexedDB/transaction-scheduling-rw-scopes.any.js new file mode 100644 index 0000000000..7296fef3b0 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/transaction-scheduling-rw-scopes.any.js @@ -0,0 +1,63 @@ +// META: script=resources/support.js + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('store'); + db.createObjectStore('a'); + db.createObjectStore('b'); + db.createObjectStore('c'); + }, + + (t, db) => { + let transaction1Started = false; + let transaction1Complete = false; + let transaction2Started = false; + let transaction2Complete = false; + let transaction3Started = false; + let transaction3Complete = false; + + const transaction1 = db.transaction(['a'], 'readwrite'); + let request = transaction1.objectStore('a').get(0); + request.onerror = t.unreached_func('request should succeed'); + request.onsuccess = t.step_func(() => { + transaction1Started = true; + }); + transaction1.onabort = t.unreached_func('transaction1 should complete'); + transaction1.oncomplete = t.step_func(() => { + transaction1Complete = true; + assert_false(transaction2Started); + assert_false(transaction3Started); + }); + + + // transaction2 overlaps with transaction1, so must wait until transaction1 + // completes. + const transaction2 = db.transaction(['a', 'b'], 'readwrite'); + request = transaction2.objectStore('a').get(0); + request.onerror = t.unreached_func('request should succeed'); + request.onsuccess = t.step_func(() => { + assert_true(transaction1Complete); + transaction2Started = true; + }); + transaction2.onabort = t.unreached_func('transaction2 should complete'); + transaction2.oncomplete = t.step_func(() => { + transaction2Complete = true; + assert_false(transaction3Started); + }); + + // transaction3 overlaps with transaction2, so must wait until transaction2 + // completes even though it does not overlap with transaction1. + const transaction3 = db.transaction(['b', 'c'], 'readwrite'); + request = transaction3.objectStore('b').get(0); + request.onerror = t.unreached_func('request should succeed'); + request.onsuccess = t.step_func(() => { + assert_true(transaction1Complete); + assert_true(transaction2Complete); + transaction3Started = true; + }); + transaction3.onabort = t.unreached_func('transaction3 should complete'); + transaction3.oncomplete = t.step_func_done(() => { + transaction3Complete = true; + }); + }, + "Check that scope restrictions on read-write transactions are enforced."); diff --git a/testing/web-platform/tests/IndexedDB/transaction-scheduling-within-database.any.js b/testing/web-platform/tests/IndexedDB/transaction-scheduling-within-database.any.js new file mode 100644 index 0000000000..a9a20bae3a --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/transaction-scheduling-within-database.any.js @@ -0,0 +1,55 @@ +// META: script=resources/support.js + +indexeddb_test( + (t, db) => { + const store = db.createObjectStore('store'); + store.put('value', 'key'); + }, + + (t, db) => { + let transaction1GetSuccess = false; + let transaction2GetSuccess = false; + + const onTransactionComplete = barrier_func(2, t.step_func_done(() => { + assert_true(transaction1GetSuccess, + 'transaction1 should have executed at least one request'); + assert_true(transaction2GetSuccess, + 'transaction1 should have executed at least one request'); + })); + + const transaction1 = db.transaction('store', 'readonly', {durability: 'relaxed'}); + transaction1.onabort = t.unreached_func('transaction1 should not abort'); + transaction1.oncomplete = t.step_func(onTransactionComplete); + + const transaction2 = db.transaction('store', 'readonly', {durability: 'relaxed'}); + transaction2.onabort = t.unreached_func('transaction2 should not abort'); + transaction2.oncomplete = t.step_func(onTransactionComplete); + + // Keep both transactions alive until each has reported at least one + // successful operation + + function doTransaction1Get() { + const request = transaction1.objectStore('store').get('key'); + request.onerror = t.unreached_func('request should not fail'); + request.onsuccess = t.step_func(() => { + transaction1GetSuccess = true; + if (!transaction2GetSuccess) + doTransaction1Get(); + }); + } + + function doTransaction2Get() { + // NOTE: No logging since execution order is not deterministic. + const request = transaction2.objectStore('store').get('key'); + request.onerror = t.unreached_func('request should not fail'); + request.onsuccess = t.step_func(() => { + transaction2GetSuccess = true; + if (!transaction1GetSuccess) + doTransaction2Get(); + }); + } + + doTransaction1Get(); + doTransaction2Get(); + }, + 'Check that read-only transactions within a database can run in parallel.'); diff --git a/testing/web-platform/tests/IndexedDB/transaction_bubble-and-capture.htm b/testing/web-platform/tests/IndexedDB/transaction_bubble-and-capture.htm new file mode 100644 index 0000000000..9b6694b980 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/transaction_bubble-and-capture.htm @@ -0,0 +1,75 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Capture and bubble</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var events = []; + + var open_rq = createdb(async_test()); + open_rq.onupgradeneeded = function(e) { + var db = e.target.result; + var txn = e.target.transaction; + var store = db.createObjectStore("store"); + var rq1 = store.add("", 1); + var rq2 = store.add("", 1); + + db.onerror = undefined; // We will run db.error, but don't let that fail the test + + log_events('db', db, 'success'); + log_events('db', db, 'error'); + + log_events('txn', txn, 'success'); + log_events('txn', txn, 'error'); + + log_events('rq1', rq1, 'success'); + log_events('rq1', rq1, 'error'); + + log_events('rq2', rq2, 'success'); + log_events('rq2', rq2, 'error'); + + // Don't let it get to abort + db.addEventListener('error', function(e) { e.preventDefault(); }, false); + } + + open_rq.onsuccess = function(e) { + log("open_rq.success")(e); + assert_array_equals(events, [ + "capture db.success", + "capture txn.success", + "capture rq1.success", + "bubble rq1.success", + + "capture db.error: ConstraintError", + "capture txn.error: ConstraintError", + "capture rq2.error: ConstraintError", + "bubble rq2.error: ConstraintError", + "bubble txn.error: ConstraintError", + "bubble db.error: ConstraintError", + + "open_rq.success", + ], + "events"); + this.done(); + } + + + function log_events(type, obj, evt) { + obj.addEventListener(evt, log('capture ' + type + '.' + evt), true); + obj.addEventListener(evt, log('bubble ' + type + '.' + evt), false); + } + + function log(msg) { + return function(e) { + if(e && e.target && e.target.error) + events.push(msg + ": " + e.target.error.name); + else + events.push(msg); + }; + } +</script> + +<div id=log></div> diff --git a/testing/web-platform/tests/IndexedDB/upgrade-transaction-deactivation-timing.html b/testing/web-platform/tests/IndexedDB/upgrade-transaction-deactivation-timing.html new file mode 100644 index 0000000000..8119c9ab26 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/upgrade-transaction-deactivation-timing.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Upgrade transaction deactivation timing</title> +<link rel="help" href="http://localhost:4201/#upgrade-transaction-steps"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> +<script> + +indexeddb_test( + (t, db, tx) => { + db.createObjectStore('store'); + assert_true(is_transaction_active(tx, 'store'), + 'Transaction should be active in upgradeneeded callback'); + }, + (t, db) => { t.done(); }, + 'Upgrade transactions are active in upgradeneeded callback'); + +indexeddb_test( + (t, db, tx) => { + db.createObjectStore('store'); + assert_true(is_transaction_active(tx, 'store'), + 'Transaction should be active in upgradeneeded callback'); + + Promise.resolve().then(t.step_func(() => { + assert_true(is_transaction_active(tx, 'store'), + 'Transaction should be active in microtask checkpoint'); + })); + }, + (t, db) => { t.done(); }, + 'Upgrade transactions are active in upgradeneeded callback and microtasks'); + + +indexeddb_test( + (t, db, tx) => { + db.createObjectStore('store'); + const release_tx = keep_alive(tx, 'store'); + + setTimeout(t.step_func(() => { + assert_false(is_transaction_active(tx, 'store'), + 'Transaction should be inactive in next task'); + release_tx(); + }), 0); + }, + (t, db) => { t.done(); }, + 'Upgrade transactions are deactivated before next task'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/upgrade-transaction-lifecycle-backend-aborted.html b/testing/web-platform/tests/IndexedDB/upgrade-transaction-lifecycle-backend-aborted.html new file mode 100644 index 0000000000..862e85144d --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/upgrade-transaction-lifecycle-backend-aborted.html @@ -0,0 +1,84 @@ +<!doctype html> +<meta charset="utf8"> +<title>IndexedDB: backend-aborted versionchange transaction lifecycle</title> +<link rel="help" + href="https://w3c.github.io/IndexedDB/#upgrade-transaction-steps"> +<link rel="help" + href="https://w3c.github.io/IndexedDB/#dom-idbdatabase-createobjectstore"> +<link rel="help" + href="https://w3c.github.io/IndexedDB/#dom-idbdatabase-deleteobjectstore"> +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script> +'use strict'; + +promise_test(t => { + return createDatabase(t, database => { + createBooksStore(t, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(t, 2, (database, transaction, request) => { + return new Promise((resolve, reject) => { + transaction.addEventListener('abort', () => { + resolve(new Promise((resolve, reject) => { + assert_equals( + request.transaction, transaction, + "The open request's transaction should be reset after onabort"); + assert_throws_dom( + 'InvalidStateError', + () => { database.createObjectStore('books2'); }, + 'createObjectStore exception should reflect that the ' + + 'transaction is no longer running'); + assert_throws_dom( + 'InvalidStateError', + () => { database.deleteObjectStore('books'); }, + 'deleteObjectStore exception should reflect that the ' + + 'transaction is no longer running'); + resolve(); + })); + }, false); + transaction.objectStore('books').add(BOOKS_RECORD_DATA[0]); + transaction._willBeAborted(); + }); + })); +}, 'in the abort event handler for a transaction aborted due to an unhandled ' + + 'request error'); + +promise_test(t => { + return createDatabase(t, database => { + createBooksStore(t, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(t, 2, (database, transaction, request) => { + return new Promise((resolve, reject) => { + transaction.addEventListener('abort', () => { + setTimeout(() => { + resolve(new Promise((resolve, reject) => { + assert_equals( + request.transaction, null, + "The open request's transaction should be reset after " + + 'onabort microtasks'); + assert_throws_dom( + 'InvalidStateError', + () => { database.createObjectStore('books2'); }, + 'createObjectStore exception should reflect that the ' + + 'transaction is no longer running'); + assert_throws_dom( + 'InvalidStateError', + () => { database.deleteObjectStore('books'); }, + 'deleteObjectStore exception should reflect that the ' + + 'transaction is no longer running'); + resolve(); + })); + }, 0); + }, false); + transaction.objectStore('books').add(BOOKS_RECORD_DATA[0]); + transaction._willBeAborted(); + }); + })); +}, 'in a setTimeout(0) callback after the abort event is fired for a ' + + 'transaction aborted due to an unhandled request failure'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/upgrade-transaction-lifecycle-committed.html b/testing/web-platform/tests/IndexedDB/upgrade-transaction-lifecycle-committed.html new file mode 100644 index 0000000000..347d940aee --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/upgrade-transaction-lifecycle-committed.html @@ -0,0 +1,80 @@ +<!doctype html> +<meta charset="utf8"> +<title>IndexedDB: committed versionchange transaction lifecycle</title> +<link rel="help" + href="https://w3c.github.io/IndexedDB/#upgrade-transaction-steps"> +<link rel="help" + href="https://w3c.github.io/IndexedDB/#dom-idbdatabase-createobjectstore"> +<link rel="help" + href="https://w3c.github.io/IndexedDB/#dom-idbdatabase-deleteobjectstore"> +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script> +'use strict'; + +promise_test(t => { + return createDatabase(t, database => { + createBooksStore(t, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(t, 2, (database, transaction, request) => { + return new Promise((resolve, reject) => { + transaction.addEventListener('complete', () => { + resolve(new Promise((resolve, reject) => { + assert_equals( + request.transaction, transaction, + "The open request's transaction should be reset after " + + 'oncomplete'); + assert_throws_dom( + 'InvalidStateError', + () => { database.createObjectStore('books2'); }, + 'createObjectStore exception should reflect that the ' + + 'transaction is no longer running'); + assert_throws_dom( + 'InvalidStateError', + () => { database.deleteObjectStore('books'); }, + 'deleteObjectStore exception should reflect that the ' + + 'transaction is no longer running'); + resolve(); + })); + }, false); + }); + })).then(database => { database.close(); }); +}, 'in the complete event handler for a committed transaction'); + +promise_test(t => { + return createDatabase(t, database => { + createBooksStore(t, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(t, 2, (database, transaction, request) => { + return new Promise((resolve, reject) => { + transaction.addEventListener('complete', () => { + setTimeout(() => { + resolve(new Promise((resolve, reject) => { + assert_equals( + request.transaction, null, + "The open request's transaction should be reset after " + + 'oncomplete microtasks'); + assert_throws_dom( + 'InvalidStateError', + () => { database.createObjectStore('books2'); }, + 'createObjectStore exception should reflect that the ' + + 'transaction is no longer running'); + assert_throws_dom( + 'InvalidStateError', + () => { database.deleteObjectStore('books'); }, + 'deleteObjectStore exception should reflect that the ' + + 'transaction is no longer running'); + resolve(); + })); + }, 0); + }, false); + }); + })).then(database => { database.close(); }); +}, 'in a setTimeout(0) callback after the complete event is fired for a ' + + 'committed transaction'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/upgrade-transaction-lifecycle-user-aborted.html b/testing/web-platform/tests/IndexedDB/upgrade-transaction-lifecycle-user-aborted.html new file mode 100644 index 0000000000..4094ce34f3 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/upgrade-transaction-lifecycle-user-aborted.html @@ -0,0 +1,143 @@ +<!doctype html> +<meta charset="utf8"> +<title>IndexedDB: user-abort()ed versionchange transaction lifecycle</title> +<link rel="help" + href="https://w3c.github.io/IndexedDB/#upgrade-transaction-steps"> +<link rel="help" + href="https://w3c.github.io/IndexedDB/#dom-idbdatabase-createobjectstore"> +<link rel="help" + href="https://w3c.github.io/IndexedDB/#dom-idbdatabase-deleteobjectstore"> +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support-promises.js"></script> +<script> +'use strict'; + +promise_test(t => { + return createDatabase(t, database => { + createBooksStore(t, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(t, 2, (database, transaction, request) => { + transaction.abort(); + assert_equals( + request.transaction, transaction, + "The open request's transaction should be reset after onabort"); + + assert_throws_dom( + 'TransactionInactiveError', + () => { database.createObjectStore('books2'); }, + 'createObjectStore exception should reflect that the transaction is ' + + 'still running'); + assert_throws_dom( + 'TransactionInactiveError', + () => { database.deleteObjectStore('books'); }, + 'deleteObjectStore exception should reflect that the transaction is' + + 'still running'); + })); +}, 'synchronously after abort() is called'); + +promise_test(t => { + return createDatabase(t, database => { + createBooksStore(t, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(t, 2, (database, transaction, request) => { + let abortFired = false; + const abortPromise = new Promise((resolve, reject) => { + transaction.addEventListener('abort', () => { + abortFired = true; + resolve(); + }, false); + transaction.abort(); + }); + + return Promise.resolve().then(() => { + assert_false( + abortFired, + 'The abort event should fire after promises are resolved'); + assert_equals( + request.transaction, transaction, + "The open request's transaction should be reset after onabort"); + assert_throws_dom( + 'TransactionInactiveError', + () => { database.createObjectStore('books2'); }, + 'createObjectStore exception should reflect that the transaction ' + + 'is still running'); + assert_throws_dom( + 'TransactionInactiveError', + () => { database.deleteObjectStore('books'); }, + 'deleteObjectStore exception should reflect that the transaction ' + + 'is still running'); + }).then(() => abortPromise); + })); +}, 'in a promise microtask after abort() is called, before the transaction ' + + 'abort event is fired'); + +promise_test(t => { + return createDatabase(t, database => { + createBooksStore(t, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(t, 2, (database, transaction, request) => { + return new Promise((resolve, reject) => { + transaction.addEventListener('abort', () => { + resolve(new Promise((resolve, reject) => { + assert_equals( + request.transaction, transaction, + "The open request's transaction should be reset after onabort"); + assert_throws_dom( + 'InvalidStateError', + () => { database.createObjectStore('books2'); }, + 'createObjectStore exception should reflect that the ' + + 'transaction is no longer running'); + assert_throws_dom( + 'InvalidStateError', + () => { database.deleteObjectStore('books'); }, + 'deleteObjectStore exception should reflect that the ' + + 'transaction is no longer running'); + resolve(); + })); + }, false); + transaction.abort(); + }); + })); +}, 'in the abort event handler for a transaction aborted due to an abort() ' + + 'call'); + +promise_test(t => { + return createDatabase(t, database => { + createBooksStore(t, database); + }).then(database => { + database.close(); + }).then(() => migrateDatabase(t, 2, (database, transaction, request) => { + return new Promise((resolve, reject) => { + transaction.addEventListener('abort', () => { + setTimeout(() => { + resolve(new Promise((resolve, reject) => { + assert_equals( + request.transaction, null, + "The open request's transaction should be reset after " + + 'onabort microtasks'); + assert_throws_dom( + 'InvalidStateError', + () => { database.createObjectStore('books2'); }, + 'createObjectStore exception should reflect that the ' + + 'transaction is no longer running'); + assert_throws_dom( + 'InvalidStateError', + () => { database.deleteObjectStore('books'); }, + 'deleteObjectStore exception should reflect that the ' + + 'transaction is no longer running'); + resolve(); + })); + }, 0); + }, false); + transaction.abort(); + }); + })); +}, 'in a setTimeout(0) callback after the abort event is fired for a ' + + 'transaction aborted due to an abort() call'); + +</script> diff --git a/testing/web-platform/tests/IndexedDB/value.htm b/testing/web-platform/tests/IndexedDB/value.htm new file mode 100644 index 0000000000..32d7540ab3 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/value.htm @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Values</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> +function value(value, _instanceof) { + var t = async_test(document.title + " - " + _instanceof.name); + t.step(function() { + assert_true(value instanceof _instanceof, "TEST ERROR, instanceof"); + }); + + createdb(t).onupgradeneeded = function(e) { + e.target.result + .createObjectStore("store") + .add(value, 1); + + e.target.onsuccess = t.step_func(function(e) { + e.target.result + .transaction("store", "readonly", {durability: 'relaxed'}) + .objectStore("store") + .get(1) + .onsuccess = t.step_func(function(e) + { + assert_true(e.target.result instanceof _instanceof, "instanceof") + t.done(); + }); + }); + }; +} + +value(new Date(), Date); +value(new Array(), Array); + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/value_recursive.htm b/testing/web-platform/tests/IndexedDB/value_recursive.htm new file mode 100644 index 0000000000..e02c908a35 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/value_recursive.htm @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Recursive value</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/support.js"></script> + +<script> +function recursive_value(desc, value) { + var db, t = async_test(document.title + " - " + desc); + + createdb(t).onupgradeneeded = function(e) { + db = e.target.result + db.createObjectStore("store") + .add(value, 1); + + e.target.onsuccess = t.step_func(function(e) { + db.transaction('store', 'readonly', {durability: 'relaxed'}) + .objectStore('store') + .get(1) + .onsuccess = t.step_func(function(e) + { + + try + { + var fresh_value = JSON.stringify(value); + assert_unreached("Testcase is written wrongly, must supply something recursive (that JSON won't stringify)."); + } + catch (e) + { + if (e.name == 'TypeError') + { + try + { + JSON.stringify(e.target.result); + assert_unreached("Expected a non-JSON-serializable value back, didn't get that."); + } + catch (e) + { + t.done(); + return; + } + } + else + throw e; + } + }); + }); + }; +} + +var recursive = []; +recursive.push(recursive); +recursive_value('array directly contains self', recursive); + +var recursive2 = []; +recursive2.push([recursive2]); +recursive_value('array indirectly contains self', recursive2); + +var recursive3 = [recursive]; +recursive_value('array member contains self', recursive3); + +</script> + +<div id="log"></div> diff --git a/testing/web-platform/tests/IndexedDB/writer-starvation.htm b/testing/web-platform/tests/IndexedDB/writer-starvation.htm new file mode 100644 index 0000000000..df9c5dc757 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/writer-starvation.htm @@ -0,0 +1,105 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Writer starvation</title> +<link rel="author" href="mailto:odinho@opera.com" title="Odin Hørthe Omdal"> +<meta name=timeout content=long> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=resources/support.js></script> + +<script> + var db, read_request_count = 0, read_success_count = 0; + var write_request_count = 0, write_success_count = 0; + var RQ_COUNT = 25; + + var open_rq = createdb(async_test(undefined)); + open_rq.onupgradeneeded = function(e) { + db = e.target.result; + db.createObjectStore("s") + .add("1", 1); + } + + open_rq.onsuccess = function(e) { + var i = 0, continue_reading = true; + + /* Pre-fill some read requests */ + for (i = 0; i < RQ_COUNT; i++) + { + read_request_count++; + + db.transaction("s", "readonly", {durability: 'relaxed'}) + .objectStore("s") + .get(1) + .onsuccess = this.step_func(function(e) { + read_success_count++; + assert_equals(e.target.transaction.mode, "readonly"); + }); + } + + this.step(loop); + + function loop() { + read_request_count++; + + db.transaction("s", "readonly", {durability: 'relaxed'}) + .objectStore("s") + .get(1) + .onsuccess = this.step_func(function(e) + { + read_success_count++; + assert_equals(e.target.transaction.mode, "readonly"); + + if (read_success_count >= RQ_COUNT && write_request_count == 0) + { + write_request_count++; + + db.transaction("s", "readwrite", {durability: 'relaxed'}) + .objectStore("s") + .add("written", read_request_count) + .onsuccess = this.step_func(function(e) + { + write_success_count++; + assert_equals(e.target.transaction.mode, "readwrite"); + assert_equals(e.target.result, read_success_count, + "write cb came before later read cb's") + }); + + /* Reads done after the write */ + for (i = 0; i < 5; i++) + { + read_request_count++; + + db.transaction("s", "readonly", {durability: 'relaxed'}) + .objectStore("s") + .get(1) + .onsuccess = this.step_func(function(e) + { + read_success_count++; + }); + } + } + }); + + if (read_success_count < RQ_COUNT + 5) + step_timeout(this.step_func(loop), write_request_count ? 1000 : 100); + else + // This is merely a "nice" hack to run finish after the last request is done + db.transaction("s", "readonly", {durability: 'relaxed'}) + .objectStore("s") + .count() + .onsuccess = this.step_func(function() + { + step_timeout(this.step_func(finish), 100); + }); + } + } + + +function finish() { + assert_equals(read_request_count, read_success_count, "read counts"); + assert_equals(write_request_count, write_success_count, "write counts"); + this.done(); +} +</script> + +<div id=log></div> |