diff --git a/Libraries/LibWeb/IndexedDB/IDBFactory.cpp b/Libraries/LibWeb/IndexedDB/IDBFactory.cpp index 8e7a949fda5..b4584c90b35 100644 --- a/Libraries/LibWeb/IndexedDB/IDBFactory.cpp +++ b/Libraries/LibWeb/IndexedDB/IDBFactory.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -93,4 +94,25 @@ WebIDL::ExceptionOr> IDBFactory::open(String const& na return request; } +// https://w3c.github.io/IndexedDB/#dom-idbfactory-cmp +WebIDL::ExceptionOr IDBFactory::cmp(JS::Value first, JS::Value second) +{ + // 1. Let a be the result of converting a value to a key with first. Rethrow any exceptions. + auto a = convert_a_value_to_a_key(realm(), first); + + // 2. If a is invalid, throw a "DataError" DOMException. + if (a.is_error()) + return WebIDL::DataError::create(realm(), "Failed to convert a value to a key"_string); + + // 3. Let b be the result of converting a value to a key with second. Rethrow any exceptions. + auto b = convert_a_value_to_a_key(realm(), second); + + // 4. If b is invalid, throw a "DataError" DOMException. + if (b.is_error()) + return WebIDL::DataError::create(realm(), "Failed to convert a value to a key"_string); + + // 5. Return the results of comparing two keys with a and b. + return Key::compare_two_keys(a.release_value(), b.release_value()); +} + } diff --git a/Libraries/LibWeb/IndexedDB/IDBFactory.h b/Libraries/LibWeb/IndexedDB/IDBFactory.h index 1bb8649345e..b913672a1e2 100644 --- a/Libraries/LibWeb/IndexedDB/IDBFactory.h +++ b/Libraries/LibWeb/IndexedDB/IDBFactory.h @@ -21,6 +21,7 @@ public: virtual ~IDBFactory() override; WebIDL::ExceptionOr> open(String const& name, Optional version); + WebIDL::ExceptionOr cmp(JS::Value first, JS::Value second); protected: explicit IDBFactory(JS::Realm&); diff --git a/Libraries/LibWeb/IndexedDB/IDBFactory.idl b/Libraries/LibWeb/IndexedDB/IDBFactory.idl index e6b457c1457..5f57f2afb35 100644 --- a/Libraries/LibWeb/IndexedDB/IDBFactory.idl +++ b/Libraries/LibWeb/IndexedDB/IDBFactory.idl @@ -6,7 +6,7 @@ interface IDBFactory { [FIXME] Promise> databases(); - [FIXME] short cmp(any first, any second); + short cmp(any first, any second); }; dictionary IDBDatabaseInfo { diff --git a/Tests/LibWeb/Text/expected/wpt-import/IndexedDB/idbfactory_cmp.txt b/Tests/LibWeb/Text/expected/wpt-import/IndexedDB/idbfactory_cmp.txt new file mode 100644 index 00000000000..6b944c46442 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/IndexedDB/idbfactory_cmp.txt @@ -0,0 +1,11 @@ +Summary + +Harness status: OK + +Rerun + +Found 1 tests + +1 Pass +Details +Result Test Name MessagePass IDBFactory.cmp() \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/IndexedDB/idbfactory_cmp2.txt b/Tests/LibWeb/Text/expected/wpt-import/IndexedDB/idbfactory_cmp2.txt new file mode 100644 index 00000000000..1c4119973e5 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/IndexedDB/idbfactory_cmp2.txt @@ -0,0 +1,13 @@ +Summary + +Harness status: OK + +Rerun + +Found 3 tests + +3 Pass +Details +Result Test Name MessagePass IDBFactory.cmp() - no argument +Pass IDBFactory.cmp() - null +Pass IDBFactory.cmp() - NaN \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/IndexedDB/idbfactory_cmp3.txt b/Tests/LibWeb/Text/expected/wpt-import/IndexedDB/idbfactory_cmp3.txt new file mode 100644 index 00000000000..090a70a5d4b --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/IndexedDB/idbfactory_cmp3.txt @@ -0,0 +1,14 @@ +Summary + +Harness status: OK + +Rerun + +Found 4 tests + +4 Pass +Details +Result Test Name MessagePass Array v.s. Binary +Pass Binary v.s. String +Pass String v.s. Date +Pass Date v.s. Number \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/IndexedDB/idbfactory_cmp4.txt b/Tests/LibWeb/Text/expected/wpt-import/IndexedDB/idbfactory_cmp4.txt new file mode 100644 index 00000000000..0d488d65bd3 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/IndexedDB/idbfactory_cmp4.txt @@ -0,0 +1,14 @@ +Summary + +Harness status: OK + +Rerun + +Found 4 tests + +4 Pass +Details +Result Test Name MessagePass Compare in unsigned octet values (in the range [0, 255]) +Pass Compare values in then same length +Pass Compare values in different lengths +Pass Compare when the values in the range of their minimal length are the same \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/IndexedDB/keyorder.txt b/Tests/LibWeb/Text/expected/wpt-import/IndexedDB/keyorder.txt new file mode 100644 index 00000000000..fe2e9c44692 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/IndexedDB/keyorder.txt @@ -0,0 +1,35 @@ +Summary + +Harness status: OK + +Rerun + +Found 24 tests + +12 Pass +12 Fail +Details +Result Test Name MessageFail Database readback sort - String < Array Cannot access property "transaction" on undefined object "db" +Pass IDBKey.cmp sorted - String < Array +Fail Database readback sort - float < String Cannot access property "transaction" on undefined object "db" +Pass IDBKey.cmp sorted - float < String +Fail Database readback sort - float < Date Cannot access property "transaction" on undefined object "db" +Pass IDBKey.cmp sorted - float < Date +Fail Database readback sort - float < Date < String < Array Cannot access property "transaction" on undefined object "db" +Pass IDBKey.cmp sorted - float < Date < String < Array +Fail Database readback sort - Date(1 sec ago) < Date(now) < Date(1 minute in future) Cannot access property "transaction" on undefined object "db" +Pass IDBKey.cmp sorted - Date(1 sec ago) < Date(now) < Date(1 minute in future) +Fail Database readback sort - -1.1 < 1 < 1.01337 < 1.013373 < 2 Cannot access property "transaction" on undefined object "db" +Pass IDBKey.cmp sorted - -1.1 < 1 < 1.01337 < 1.013373 < 2 +Fail Database readback sort - -Infinity < -0.01 < 0 < Infinity Cannot access property "transaction" on undefined object "db" +Pass IDBKey.cmp sorted - -Infinity < -0.01 < 0 < Infinity +Fail Database readback sort - "" < "a" < "ab" < "b" < "ba" Cannot access property "transaction" on undefined object "db" +Pass IDBKey.cmp sorted - "" < "a" < "ab" < "b" < "ba" +Fail Database readback sort - Arrays Cannot access property "transaction" on undefined object "db" +Pass IDBKey.cmp sorted - Arrays +Fail Database readback sort - Array.length: 10,000 < Array.length: 10,001 Cannot access property "transaction" on undefined object "db" +Pass IDBKey.cmp sorted - Array.length: 10,000 < Array.length: 10,001 +Fail Database readback sort - Infinity inside arrays Cannot access property "transaction" on undefined object "db" +Pass IDBKey.cmp sorted - Infinity inside arrays +Fail Database readback sort - Test different stuff at once Cannot access property "transaction" on undefined object "db" +Pass IDBKey.cmp sorted - Test different stuff at once \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/IndexedDB/idbfactory_cmp.htm b/Tests/LibWeb/Text/input/wpt-import/IndexedDB/idbfactory_cmp.htm new file mode 100644 index 00000000000..8dc6bf23e0b --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/IndexedDB/idbfactory_cmp.htm @@ -0,0 +1,21 @@ + + +IDBFactory.cmp() - compared keys return correct value + + + + + + + +
diff --git a/Tests/LibWeb/Text/input/wpt-import/IndexedDB/idbfactory_cmp2.htm b/Tests/LibWeb/Text/input/wpt-import/IndexedDB/idbfactory_cmp2.htm new file mode 100644 index 00000000000..95ceddbef02 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/IndexedDB/idbfactory_cmp2.htm @@ -0,0 +1,41 @@ + + +IDBFactory.cmp() - invalid key + + + + + +
+ + diff --git a/Tests/LibWeb/Text/input/wpt-import/IndexedDB/idbfactory_cmp3.htm b/Tests/LibWeb/Text/input/wpt-import/IndexedDB/idbfactory_cmp3.htm new file mode 100644 index 00000000000..5b30fb1a221 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/IndexedDB/idbfactory_cmp3.htm @@ -0,0 +1,27 @@ + + +IDBFactory.cmp() - compared keys in different types + + + + + + + +
diff --git a/Tests/LibWeb/Text/input/wpt-import/IndexedDB/idbfactory_cmp4.htm b/Tests/LibWeb/Text/input/wpt-import/IndexedDB/idbfactory_cmp4.htm new file mode 100644 index 00000000000..2a4d549f343 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/IndexedDB/idbfactory_cmp4.htm @@ -0,0 +1,40 @@ + + +IDBFactory.cmp() - comparison of binary keys + + + + + + + +
diff --git a/Tests/LibWeb/Text/input/wpt-import/IndexedDB/keyorder.htm b/Tests/LibWeb/Text/input/wpt-import/IndexedDB/keyorder.htm new file mode 100644 index 00000000000..660facbb69f --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/IndexedDB/keyorder.htm @@ -0,0 +1,175 @@ + + + +Key sort order + + + + + + + + +
diff --git a/Tests/LibWeb/Text/input/wpt-import/IndexedDB/resources/support.js b/Tests/LibWeb/Text/input/wpt-import/IndexedDB/resources/support.js new file mode 100644 index 00000000000..1d5d8df5ca3 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/IndexedDB/resources/support.js @@ -0,0 +1,240 @@ +/* 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 = () => { + if (version == 1) { + // Only create the object store once. + request.result.createObjectStore('store'); + } + } + request.onversionchange = () => { + fail(t, 'unexpectedly received versionchange event.'); + } + }, [dbName, version]); +} + +// Create an IndexedDB by executing script on the given remote context +// with |dbName| and |version|, and wait for the reuslt. +async function waitUntilIndexedDBOpenForTesting(rc, dbName, version) { + await rc.executeScript(async (dbName, version) => { + await new Promise((resolve, reject) => { + let request = indexedDB.open(dbName, version); + request.onsuccess = resolve; + request.onerror = reject; + }); + }, [dbName, version]); +}