Skip to content

Commit

Permalink
IndexedDB: Add tests for HTML monkey patches
Browse files Browse the repository at this point in the history
The IDB spec implicitly "monkey patches" HTML with imprecise prose
when describing newly created transactions:

"When control is returned to the event loop, the implementation must
unset the active flag."

The plan is replace that with a proper hook. Adding tests first to ensure the
expected behavior is captured appropriately.

w3c/IndexedDB#87
  • Loading branch information
inexorabletash authored Mar 7, 2017
1 parent 19564bf commit 57aa2ac
Show file tree
Hide file tree
Showing 4 changed files with 368 additions and 0 deletions.
154 changes: 154 additions & 0 deletions IndexedDB/event-dispatch-active-flag.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<!DOCTYPE html>
<meta charset=utf-8>
<title>Transaction active flag is set during event dispatch</title>
<link rel="help" href="https://w3c.github.io/IndexedDB/#fire-success-event">
<link rel="help" href="https://w3c.github.io/IndexedDB/#fire-error-event">
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script src=support.js></script>
<script>

indexeddb_test(
(t, db, tx) => {
db.createObjectStore('store');
},
(t, db) => {
const tx = db.transaction('store');
const release_tx = keep_alive(tx, 'store');

assert_true(is_transaction_active(tx, 'store'),
'Transaction should be active after creation');

const request = tx.objectStore('store').get(0);
request.onerror = t.unreached_func('request should succeed');
request.onsuccess = () => {
assert_true(is_transaction_active(tx, 'store'),
'Transaction should be active during success handler');

let saw_handler_promise = false;
Promise.resolve().then(t.step_func(() => {
saw_handler_promise = true;
assert_true(is_transaction_active(tx, 'store'),
'Transaction should be active in handler\'s microtasks');
}));

setTimeout(t.step_func(() => {
assert_true(saw_handler_promise);
assert_false(is_transaction_active(tx, 'store'),
'Transaction should be inactive in next task');
release_tx();
t.done();
}), 0);
};
},
'Transactions are active during success handlers');

indexeddb_test(
(t, db, tx) => {
db.createObjectStore('store');
},
(t, db) => {
const tx = db.transaction('store');
const release_tx = keep_alive(tx, 'store');
assert_true(is_transaction_active(tx, 'store'),
'Transaction should be active after creation');

const request = tx.objectStore('store').get(0);
request.onerror = t.unreached_func('request should succeed');
request.addEventListener('success', () => {
assert_true(is_transaction_active(tx, 'store'),
'Transaction should be active during success listener');

let saw_listener_promise = false;
Promise.resolve().then(t.step_func(() => {
saw_listener_promise = true;
assert_true(is_transaction_active(tx, 'store'),
'Transaction should be active in listener\'s microtasks');
}));

setTimeout(t.step_func(() => {
assert_true(saw_listener_promise);
assert_false(is_transaction_active(tx, 'store'),
'Transaction should be inactive in next task');
release_tx();
t.done();
}), 0);
});
},
'Transactions are active during success listeners');

indexeddb_test(
(t, db, tx) => {
db.createObjectStore('store');
},
(t, db) => {
const tx = db.transaction('store', 'readwrite');
const release_tx = keep_alive(tx, 'store');
assert_true(is_transaction_active(tx, 'store'),
'Transaction should be active after creation');

tx.objectStore('store').put(0, 0);
const request = tx.objectStore('store').add(0, 0);
request.onsuccess = t.unreached_func('request should fail');
request.onerror = e => {
e.preventDefault();

assert_true(is_transaction_active(tx, 'store'),
'Transaction should be active during error handler');

let saw_handler_promise = false;
Promise.resolve().then(t.step_func(() => {
saw_handler_promise = true;
assert_true(is_transaction_active(tx, 'store'),
'Transaction should be active in handler\'s microtasks');
}));

setTimeout(t.step_func(() => {
assert_true(saw_handler_promise);
assert_false(is_transaction_active(tx, 'store'),
'Transaction should be inactive in next task');
release_tx();
t.done();
}), 0);
};
},
'Transactions are active during error handlers');

indexeddb_test(
(t, db, tx) => {
db.createObjectStore('store');
},
(t, db) => {
const tx = db.transaction('store', 'readwrite');
const release_tx = keep_alive(tx, 'store');
assert_true(is_transaction_active(tx, 'store'),
'Transaction should be active after creation');

tx.objectStore('store').put(0, 0);
const request = tx.objectStore('store').add(0, 0);
request.onsuccess = t.unreached_func('request should fail');
request.addEventListener('error', e => {
e.preventDefault();

assert_true(is_transaction_active(tx, 'store'),
'Transaction should be active during error listener');

let saw_listener_promise = false;
Promise.resolve().then(t.step_func(() => {
saw_listener_promise = true;
assert_true(is_transaction_active(tx, 'store'),
'Transaction should be active in listener\'s microtasks');
}));

setTimeout(t.step_func(() => {
assert_true(saw_listener_promise);
assert_false(is_transaction_active(tx, 'store'),
'Transaction should be inactive in next task');
release_tx();
t.done();
}), 0);
});
},
'Transactions are active during error listeners');

</script>
23 changes: 23 additions & 0 deletions IndexedDB/support.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,26 @@ function is_transaction_active(tx, store_name) {
return false;
}
}

// Keep the passed transaction alive indefinitely (by making requests
// against the named store). Returns a function to to let the
// transaction finish, and asserts that the transaction is not yet
// finished.
function keep_alive(tx, store_name) {
let completed = false;
tx.addEventListener('complete', () => { completed = true; });

let pin = true;

function spin() {
if (!pin)
return;
tx.objectStore(store_name).get(0).onsuccess = spin;
}
spin();

return () => {
assert_false(completed, 'Transaction completed while kept alive');
pin = false;
};
}
143 changes: 143 additions & 0 deletions IndexedDB/transaction-deactivation-timing.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<!DOCTYPE html>
<meta charset=utf-8>
<title>Transactions deactivation timing</title>
<link rel="help" href="https://w3c.github.io/IndexedDB/#dom-idbdatabase-transaction">
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script src=support.js></script>
<script>

indexeddb_test(
(t, db, tx) => {
db.createObjectStore('store');
},
(t, db) => {
const tx = db.transaction('store');
const release_tx = keep_alive(tx, 'store');
assert_true(is_transaction_active(tx, 'store'),
'Transaction should be active after creation');

setTimeout(t.step_func(() => {
assert_false(is_transaction_active(tx, 'store'),
'Transaction should be inactive in next task');
release_tx();
t.done();
}), 0);
},
'New transactions are deactivated before next task');

indexeddb_test(
(t, db, tx) => {
db.createObjectStore('store');
},
(t, db) => {
const tx = db.transaction('store');
const release_tx = keep_alive(tx, 'store');
assert_true(is_transaction_active(tx, 'store'),
'Transaction should be active after creation');

Promise.resolve().then(t.step_func(() => {
assert_true(is_transaction_active(tx, 'store'),
'Transaction should be active in microtask checkpoint');
release_tx();
t.done();
}));
},
'New transactions are not deactivated until after the microtask checkpoint');

indexeddb_test(
(t, db, tx) => {
db.createObjectStore('store');
},
(t, db) => {
let tx, release_tx;

Promise.resolve().then(t.step_func(() => {
tx = db.transaction('store');
release_tx = keep_alive(tx, 'store');
assert_true(is_transaction_active(tx, 'store'),
'Transaction should be active after creation');
}));

setTimeout(t.step_func(() => {
assert_false(is_transaction_active(tx, 'store'),
'Transaction should be inactive in next task');
release_tx();
t.done();
}), 0);
},
'New transactions from microtask are deactivated before next task');

indexeddb_test(
(t, db, tx) => {
db.createObjectStore('store');
},
(t, db) => {
let tx, release_tx;

Promise.resolve().then(t.step_func(() => {
tx = db.transaction('store');
release_tx = keep_alive(tx, 'store');
assert_true(is_transaction_active(tx, 'store'),
'Transaction should be active after creation');
}));

Promise.resolve().then(t.step_func(() => {
assert_true(is_transaction_active(tx, 'store'),
'Transaction should be active in microtask checkpoint');
release_tx();
t.done();
}));
},
'New transactions from microtask are still active through the ' +
'microtask checkpoint');


indexeddb_test(
(t, db, tx) => {
db.createObjectStore('store');
},
(t, db) => {
// This transaction serves as the source of an event seen by multiple
// listeners. A DOM event with multiple listeners could be used instead,
// but not via dispatchEvent() because (drumroll...) that happens
// synchronously so microtasks don't run between steps.
const tx = db.transaction('store');
assert_true(is_transaction_active(tx, 'store'),
'Transaction should be active after creation');

const request = tx.objectStore('store').get(0);
let new_tx;
let first_listener_ran = false;
let microtasks_ran = false;
request.addEventListener('success', t.step_func(() => {
first_listener_ran = true;
assert_true(is_transaction_active(tx, 'store'),
'Transaction should be active in callback');

// We check to see if this transaction is active across unrelated event
// dispatch steps.
new_tx = db.transaction('store');
assert_true(is_transaction_active(new_tx, 'store'),
'New transaction should be active after creation');

Promise.resolve().then(t.step_func(() => {
microtasks_ran = true;
assert_true(is_transaction_active(new_tx, 'store'),
'New transaction is still active in microtask checkpoint');
}));

}));
request.addEventListener('success', t.step_func(() => {
assert_true(first_listener_ran, 'first listener ran first');
assert_true(microtasks_ran, 'microtasks ran before second listener');
assert_true(is_transaction_active(tx, 'store'),
'Transaction should be active in callback');
assert_false(is_transaction_active(new_tx, 'store'),
'New transaction should be inactive in unrelated callback');
t.done();
}));
},
'Deactivation of new transactions happens at end of invocation');

</script>
48 changes: 48 additions & 0 deletions IndexedDB/upgrade-transaction-deactivation-timing.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<!DOCTYPE html>
<meta charset=utf-8>
<title>Upgrade transaction deactivation timing</title>
<link rel="help" href="http://localhost:4201/#upgrade-transaction-steps">
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script src=support.js></script>
<script>

indexeddb_test(
(t, db, tx) => {
db.createObjectStore('store');
assert_true(is_transaction_active(tx, 'store'),
'Transaction should be active in upgradeneeded callback');
},
(t, db) => { t.done(); },
'Upgrade transactions are active in upgradeneeded callback');

indexeddb_test(
(t, db, tx) => {
db.createObjectStore('store');
assert_true(is_transaction_active(tx, 'store'),
'Transaction should be active in upgradeneeded callback');

Promise.resolve().then(t.step_func(() => {
assert_true(is_transaction_active(tx, 'store'),
'Transaction should be active in microtask checkpoint');
}));
},
(t, db) => { t.done(); },
'Upgrade transactions are active in upgradeneeded callback and microtasks');


indexeddb_test(
(t, db, tx) => {
db.createObjectStore('store');
const release_tx = keep_alive(tx, 'store');

setTimeout(t.step_func(() => {
assert_false(is_transaction_active(tx, 'store'),
'Transaction should be inactive in next task');
release_tx();
}), 0);
},
(t, db) => { t.done(); },
'Upgrade transactions are deactivated before next task');

</script>

0 comments on commit 57aa2ac

Please sign in to comment.