Update web-platform-tests to revision e8bfc205e36ad699601212cd50083870bad9a75d

This commit is contained in:
Ms2ger 2016-11-14 11:07:09 +01:00
parent 65dd6d4340
commit ccdb0a3458
1428 changed files with 118036 additions and 9786 deletions

View file

@ -0,0 +1,382 @@
<!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="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 = function() {
assert_throws("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("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("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("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("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("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("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("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("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("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("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("DataError", function() {
cursor.continuePrimaryKey("A", 6);
}, "DataError is expected if key is lower then current one.");
assert_throws("DataError", function() {
cursor.continuePrimaryKey("B", 5);
}, "DataError is expected if primary key is equal to current one.");
assert_throws("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("DataError", function() {
cursor.continuePrimaryKey("C", 6);
}, "DataError is expected if key is larger then current one.");
assert_throws("DataError", function() {
cursor.continuePrimaryKey("B", 7);
}, "DataError is expected if primary key is equal to current one.");
assert_throws("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>
<div id="log"></div>

View file

@ -0,0 +1,110 @@
<!DOCTYPE html>
<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="support-promises.js"></script>
<script>
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 requestWatcher(testCase, request).wait_for('success');
}).then(event => {
const database = event.target.result;
const transaction = database.transaction('books', 'readonly');
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 requestWatcher(testCase, request).wait_for('success');
}).then(event => {
const database = event.target.result;
const transaction = database.transaction('not_books', 'readonly');
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>

View file

@ -0,0 +1,130 @@
<!DOCTYPE html>
<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="support-promises.js"></script>
<script>
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(
'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');
const store = transaction.objectStore('books');
const index = store.index('by_author');
assert_throws(
'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');
const store = transaction.objectStore('books');
const index = store.index('by_author');
assert_throws(
'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(
'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('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');
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');
assert_throws(
{ name: 'Custom stringifying error' },
() => {
index.name = {
toString: () => { throw { name: 'Custom stringifying error'}; }
};
}, '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');
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>

View file

@ -0,0 +1,298 @@
<!DOCTYPE html>
<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="support-promises.js"></script>
<script>
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');
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(
'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');
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(
'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');
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');
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');
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');
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');
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');
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>

View file

@ -0,0 +1,120 @@
<!DOCTYPE html>
<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="support-promises.js"></script>
<script>
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 requestWatcher(testCase, request).wait_for('success');
}).then(event => {
const database = event.target.result;
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');
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 requestWatcher(testCase, request).wait_for('success');
}).then(event => {
const database = event.target.result;
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>

View file

@ -0,0 +1,118 @@
<!DOCTYPE html>
<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="support-promises.js"></script>
<script>
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('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');
const store = transaction.objectStore('books');
assert_throws('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');
const store = transaction.objectStore('books');
assert_throws('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('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('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');
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');
assert_throws(
{ name: 'Custom stringifying error' },
() => {
store.name = {
toString: () => { throw { name: 'Custom stringifying error'}; }
};
}, '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');
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>

View file

@ -0,0 +1,366 @@
<!DOCTYPE html>
<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="support-promises.js"></script>
<script>
// 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');
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(
'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');
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(
'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');
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');
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');
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');
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');
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');
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');
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');
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');
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>

View file

@ -21,8 +21,8 @@ setup(function() {
var idls = request.responseText;
idlArray.add_untested_idls("[PrimaryGlobal] interface Window { };");
idlArray.add_untested_idls("interface Event { };");
idlArray.add_untested_idls("interface EventTarget { };");
idlArray.add_untested_idls("[Exposed=(Window,Worker)] interface Event { };");
idlArray.add_untested_idls("[Exposed=(Window,Worker)] interface EventTarget { };");
// From Indexed DB:
idlArray.add_idls(idls);

View file

@ -9,6 +9,7 @@ enum IDBRequestReadyState {
"done"
};
[Exposed=(Window,Worker)]
interface IDBKeyRange {
readonly attribute any lower;
readonly attribute any upper;
@ -42,6 +43,7 @@ dictionary IDBVersionChangeEventInit : EventInit {
unsigned long long? newVersion = null;
};
[Exposed=(Window,Worker)]
interface IDBRequest : EventTarget {
readonly attribute any result;
readonly attribute DOMError error;
@ -52,12 +54,14 @@ interface IDBRequest : EventTarget {
attribute EventHandler onerror;
};
[Exposed=(Window,Worker)]
interface IDBOpenDBRequest : IDBRequest {
attribute EventHandler onblocked;
attribute EventHandler onupgradeneeded;
};
[Constructor(DOMString type, optional IDBVersionChangeEventInit eventInitDict)]
[Exposed=(Window,Worker),
Constructor(DOMString type, optional IDBVersionChangeEventInit eventInitDict)]
interface IDBVersionChangeEvent : Event {
readonly attribute unsigned long long oldVersion;
readonly attribute unsigned long long? newVersion;
@ -68,12 +72,14 @@ interface IDBEnvironment {
readonly attribute IDBFactory indexedDB;
};
[Exposed=(Window,Worker)]
interface IDBFactory {
IDBOpenDBRequest open (DOMString name, [EnforceRange] optional unsigned long long version);
IDBOpenDBRequest deleteDatabase (DOMString name);
short cmp (any first, any second);
};
[Exposed=(Window,Worker)]
interface IDBDatabase : EventTarget {
readonly attribute DOMString name;
readonly attribute unsigned long long version;
@ -88,6 +94,7 @@ interface IDBDatabase : EventTarget {
attribute EventHandler onversionchange;
};
[Exposed=(Window,Worker)]
interface IDBObjectStore {
attribute DOMString name;
readonly attribute any keyPath;
@ -106,6 +113,7 @@ interface IDBObjectStore {
IDBRequest count (optional any key);
};
[Exposed=(Window,Worker)]
interface IDBIndex {
attribute DOMString name;
readonly attribute IDBObjectStore objectStore;
@ -119,6 +127,7 @@ interface IDBIndex {
IDBRequest count (optional any key);
};
[Exposed=(Window,Worker)]
interface IDBCursor {
readonly attribute (IDBObjectStore or IDBIndex) source;
readonly attribute IDBCursorDirection direction;
@ -130,10 +139,12 @@ interface IDBCursor {
IDBRequest delete ();
};
[Exposed=(Window,Worker)]
interface IDBCursorWithValue : IDBCursor {
readonly attribute any value;
};
[Exposed=(Window,Worker)]
interface IDBTransaction : EventTarget {
readonly attribute IDBTransactionMode mode;
readonly attribute IDBDatabase db;

View file

@ -10,9 +10,9 @@ request.onload = function() {
var idlArray = new IdlArray();
var idls = request.responseText;
idlArray.add_untested_idls("interface WorkerGlobalScope {};");
idlArray.add_untested_idls("interface Event { };");
idlArray.add_untested_idls("interface EventTarget { };");
idlArray.add_untested_idls("[Exposed=Worker] interface WorkerGlobalScope {};");
idlArray.add_untested_idls("[Exposed=(Window,Worker)] interface Event { };");
idlArray.add_untested_idls("[Exposed=(Window,Worker)] interface EventTarget { };");
// From Indexed DB:
idlArray.add_idls("WorkerGlobalScope implements IDBEnvironment;");

View file

@ -0,0 +1,200 @@
// Returns an IndexedDB database name likely to be unique to the test case.
const databaseName = (testCase) => {
return 'db' + self.location.pathname + '-' + testCase.name;
};
// Creates an EventWatcher covering all the events that can be issued by
// IndexedDB requests and transactions.
const requestWatcher = (testCase, request) => {
return new EventWatcher(testCase, request,
['error', 'success', 'upgradeneeded']);
};
// 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
// be given the created database and the versionchange transaction.
//
// Returns a promise. If the versionchange transaction goes through, the promise
// resolves to an IndexedDB database that must be closed by the caller. If the
// versionchange transaction is aborted, the promise resolves to an error.
const migrateDatabase = (testCase, 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(testCase), newVersion);
request.onupgradeneeded = testCase.step_func(event => {
const database = event.target.result;
const transaction = event.target.transaction;
let abortCalled = false;
// 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 = () => {
request.onerror = event => {
event.preventDefault();
resolve(event);
};
request.onsuccess = () => reject(new Error(
'indexedDB.open should not succeed after the ' +
'versionchange transaction is aborted'));
transactionAbort();
abortCalled = true;
}
migrationCallback(database, transaction);
if (!abortCalled) {
request.onsuccess = null;
resolve(requestWatcher(testCase, request).wait_for('success'));
}
});
request.onerror = event => reject(event.target.error);
request.onsuccess = () => reject(new Error(
'indexedDB.open should not succeed without creating a ' +
'versionchange transaction'));
}).then(event => event.target.result || event.target.error);
};
// 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 and the versionchange transaction.
//
// Returns a promise that resolves to an IndexedDB database. The caller must
// close the database.
const createDatabase = (testCase, setupCallback) => {
const request = indexedDB.deleteDatabase(databaseName(testCase));
const eventWatcher = requestWatcher(testCase, request);
return eventWatcher.wait_for('success').then(event =>
migrateDatabase(testCase, 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 must
// close the database.
const openDatabase = (testCase, version) => {
const request = indexedDB.open(databaseName(testCase), version);
const eventWatcher = requestWatcher(testCase, request);
return eventWatcher.wait_for('success').then(
event => event.target.result);
}
// 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 (let 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.
const 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.
const 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.
const checkStoreGenerator = (testCase, store, expectedKey, errorMessage) => {
const request = store.put(
{ title: 'Bedrock Nights ' + expectedKey, author: 'Barney' });
const eventWatcher = requestWatcher(testCase, request);
return eventWatcher.wait_for('success').then(() => {
const result = request.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.
const checkStoreContents = (testCase, store, errorMessage) => {
const request = store.get(123456);
const eventWatcher = requestWatcher(testCase, request);
return eventWatcher.wait_for('success').then(() => {
const result = request.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.
const checkAuthorIndexContents = (testCase, index, errorMessage) => {
const request = index.get(BOOKS_RECORD_DATA[2].author);
const eventWatcher = requestWatcher(testCase, request);
return eventWatcher.wait_for('success').then(() => {
const result = request.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.
const checkTitleIndexContents = (testCase, index, errorMessage) => {
const request = index.get(BOOKS_RECORD_DATA[2].title);
const eventWatcher = requestWatcher(testCase, request);
return eventWatcher.wait_for('success').then(() => {
const result = request.result;
assert_equals(result.isbn, BOOKS_RECORD_DATA[2].isbn, errorMessage);
assert_equals(result.author, BOOKS_RECORD_DATA[2].author, errorMessage);
});
};

View file

@ -0,0 +1,108 @@
<!DOCTYPE html>
<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="support-promises.js"></script>
<script>
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>

View file

@ -0,0 +1,276 @@
<!DOCTYPE html>
<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="support-promises.js"></script>
<script>
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(
'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(
'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(
'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(
'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(
'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(
'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(
'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(
'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(
'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(
'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(
'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(
'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(
'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(
'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(
'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(
'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>

View file

@ -0,0 +1,291 @@
<!DOCTYPE html>
<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="support-promises.js"></script>
<script>
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(
'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(
'InvalidStateError', () => store.get('query'),
'IDBObjectStore.get should throw InvalidStateError, indicating ' +
'that the store is marked for deletion, immediately after ' +
'IDBTransaction.abort() returns');
assert_throws(
'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(
'InvalidStateError', () => store.get('query'),
'IDBObjectStore.get should throw InvalidStateError, indicating ' +
'that the store is marked for deletion, after the transaction is ' +
'aborted');
assert_throws(
'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(
'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(
'InvalidStateError', () => store.get('query'),
'IDBObjectStore.get should throw InvalidStateError, indicating ' +
'that the store is marked for deletion, immediately after ' +
'IDBDatabase.deleteObjectStore() returns');
assert_throws(
'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(
'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(
'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(
'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(
'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(
'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(
'InvalidStateError', () => store.get('query'),
'IDBObjectStore.get should throw InvalidStateError, indicating ' +
'that the store is marked for deletion, immediately after ' +
'IDBDatabase.deleteObjectStore() returns');
assert_throws(
'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(
'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(
'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(
'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(
'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>

View file

@ -0,0 +1,233 @@
<!DOCTYPE html>
<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="support-promises.js"></script>
<script>
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(
'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(
'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(
'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(
'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(
'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(
'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(
'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(
'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>