Diff
Modified: trunk/LayoutTests/ChangeLog (97416 => 97417)
--- trunk/LayoutTests/ChangeLog 2011-10-13 22:54:02 UTC (rev 97416)
+++ trunk/LayoutTests/ChangeLog 2011-10-13 23:08:13 UTC (rev 97417)
@@ -1,3 +1,19 @@
+2011-10-13 Joshua Bell <jsb...@chromium.org>
+
+ make IDBFactory.open wait for pending setVersion transactions to complete
+ https://bugs.webkit.org/show_bug.cgi?id=69307
+
+ Reviewed by Tony Chang.
+
+ Some tests marked FAIL due to crbug.com/100123
+
+ * storage/indexeddb/open-close-version-expected.txt: Added.
+ * storage/indexeddb/open-close-version.html: Added.
+ * storage/indexeddb/two-version-changes-expected.txt: Added.
+ * storage/indexeddb/two-version-changes.html: Added.
+ * storage/indexeddb/version-change-exclusive-expected.txt: Added.
+ * storage/indexeddb/version-change-exclusive.html: Added.
+
2011-10-13 Dimitri Glazkov <dglaz...@chromium.org>
https://bugs.webkit.org/show_bug.cgi?id=70069
Added: trunk/LayoutTests/storage/indexeddb/open-close-version-expected.txt (0 => 97417)
--- trunk/LayoutTests/storage/indexeddb/open-close-version-expected.txt (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/open-close-version-expected.txt 2011-10-13 23:08:13 UTC (rev 97417)
@@ -0,0 +1,103 @@
+Test interleaved open/close/setVersion calls in various permutations
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
+PASS indexedDB == null is false
+
+TEST: setVersion blocked on open handles
+window.dbname = 'test1'; window.ver = 1; window.steps = []
+'h1.open'
+'h1.open.onsuccess'
+'h2.open'
+'h2.open.onsuccess'
+'h1.setVersion'
+'h2.onversionchange'
+ h2 closing, but not immediately
+'h1.setVersion.onblocked'
+'h2.close'
+'h1.setVersion.onsuccess'
+'h1.setVersion.transaction-complete'
+PASS window.steps.toString() is "h1.open,h1.open.onsuccess,h2.open,h2.open.onsuccess,h1.setVersion,h2.onversionchange,h1.setVersion.onblocked,h2.close,h1.setVersion.onsuccess,h1.setVersion.transaction-complete"
+
+TEST: setVersion not blocked if handle closed immediately
+window.dbname = 'test2'; window.ver = 1; window.steps = []
+'h1.open'
+'h1.open.onsuccess'
+'h2.open'
+'h2.open.onsuccess'
+'h1.setVersion'
+'h2.onversionchange'
+ h2 closing immediately
+'h2.close'
+'h1.setVersion.onblocked'
+'h1.setVersion.onsuccess'
+'h1.setVersion.transaction-complete'
+NOTE: Will FAIL with extra bogus h1.setVersion.onblocked step; crbug.com/100123
+FAIL window.steps.toString() should be h1.open,h1.open.onsuccess,h2.open,h2.open.onsuccess,h1.setVersion,h2.onversionchange,h2.close,h1.setVersion.onsuccess,h1.setVersion.transaction-complete. Was h1.open,h1.open.onsuccess,h2.open,h2.open.onsuccess,h1.setVersion,h2.onversionchange,h2.close,h1.setVersion.onblocked,h1.setVersion.onsuccess,h1.setVersion.transaction-complete.
+
+TEST: open and setVersion blocked if a VERSION_CHANGE transaction is running - close when blocked
+window.dbname = 'test3'; window.ver = 1; window.steps = []
+'h1.open'
+'h1.open.onsuccess'
+'h2.open'
+'h2.open.onsuccess'
+'h1.setVersion'
+'h2.setVersion'
+'h2.onversionchange'
+'h1.setVersion.onblocked'
+'h1.onversionchange'
+'h2.setVersion.onblocked'
+ h2 blocked so closing
+'h2.close'
+'h3.open'
+'h2.setVersion.onerror'
+'h1.setVersion.onsuccess'
+'h1.setVersion.transaction-complete'
+'h3.open.onsuccess'
+PASS window.steps.toString() is "h1.open,h1.open.onsuccess,h2.open,h2.open.onsuccess,h1.setVersion,h2.setVersion,h2.onversionchange,h1.setVersion.onblocked,h1.onversionchange,h2.setVersion.onblocked,h2.close,h3.open,h2.setVersion.onerror,h1.setVersion.onsuccess,h1.setVersion.transaction-complete,h3.open.onsuccess"
+
+TEST: open and setVersion blocked if a VERSION_CHANGE transaction is running - just close
+window.dbname = 'test4'; window.ver = 1; window.steps = []
+'h1.open'
+'h1.open.onsuccess'
+'h2.open'
+'h2.open.onsuccess'
+'h1.setVersion'
+'h3.open'
+'h2.close'
+'h1.setVersion.onblocked'
+'h1.setVersion.onsuccess'
+'h1.setVersion.transaction-complete'
+'h3.open.onsuccess'
+NOTE: Will FAIL with extra bogus h1.setVersion.onblocked step; crbug.com/100123
+FAIL window.steps.toString() should be h1.open,h1.open.onsuccess,h2.open,h2.open.onsuccess,h1.setVersion,h3.open,h2.close,h1.setVersion.onsuccess,h1.setVersion.transaction-complete,h3.open.onsuccess. Was h1.open,h1.open.onsuccess,h2.open,h2.open.onsuccess,h1.setVersion,h3.open,h2.close,h1.setVersion.onblocked,h1.setVersion.onsuccess,h1.setVersion.transaction-complete,h3.open.onsuccess.
+
+TEST: open blocked if a VERSION_CHANGE transaction is running
+window.dbname = 'test5'; window.ver = 1; window.steps = []
+'h1.open'
+'h1.open.onsuccess'
+'h1.setVersion'
+'h2.open'
+'h1.setVersion.onsuccess'
+'h1.setVersion.transaction-complete'
+'h2.open.onsuccess'
+PASS window.steps.toString() is "h1.open,h1.open.onsuccess,h1.setVersion,h2.open,h1.setVersion.onsuccess,h1.setVersion.transaction-complete,h2.open.onsuccess"
+
+TEST: two setVersions from the same connection
+window.dbname = 'test6'; window.ver = 1; window.steps = []
+'h1.open'
+'h1.open.onsuccess'
+'h1.setVersion'
+'h1.setVersion'
+'h1.setVersion.onsuccess'
+'h1.setVersion.transaction-complete'
+half done
+'h1.setVersion.onsuccess'
+'h1.setVersion.transaction-complete'
+PASS window.steps.toString() is "h1.open,h1.open.onsuccess,h1.setVersion,h1.setVersion,h1.setVersion.onsuccess,h1.setVersion.transaction-complete,h1.setVersion.onsuccess,h1.setVersion.transaction-complete"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/storage/indexeddb/open-close-version.html (0 => 97417)
--- trunk/LayoutTests/storage/indexeddb/open-close-version.html (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/open-close-version.html 2011-10-13 23:08:13 UTC (rev 97417)
@@ -0,0 +1,300 @@
+<html>
+<head>
+<link rel="stylesheet" href=""
+<script src=""
+<script src=""
+<script src=""
+</head>
+<body>
+<p id="description"></p>
+<div id="console"></div>
+<script>
+
+description("Test interleaved open/close/setVersion calls in various permutations");
+if (window.layoutTestController)
+ layoutTestController.waitUntilDone();
+
+function Connection(id) {
+ id = String(id);
+ var self = this;
+ this.open = function(opts) {
+ window.steps.push(evalAndLog("'" + id + ".open'"));
+ var req = indexedDB.open(window.dbname);
+ req._onerror_ = unexpectedErrorCallback;
+ req._onsuccess_ = function (e) {
+ self.handle = e.target.result;
+ window.steps.push(evalAndLog("'" + id + ".open.onsuccess'"));
+ self.handle._onversionchange_ = function(e) {
+ window.steps.push(evalAndLog("'" + id + ".onversionchange'"));
+ if (opts && opts.onversion) { opts.onversion.call(self); }
+ };
+ if (opts && opts.onsuccess) { opts.onsuccess.call(self); }
+ };
+ };
+
+ this.close = function() {
+ window.steps.push(evalAndLog("'" + id + ".close'"));
+ this.handle.close();
+ };
+
+ this.setVersion = function(opts) {
+ window.steps.push(evalAndLog("'" + id + ".setVersion'"));
+ var req = this.handle.setVersion(String(window.ver++));
+ req._onabort_ = function (e) {
+ window.steps.push(evalAndLog("'" + id + ".setVersion.onabort'"));
+ if (opts && opts.onabort) { opts.onabort.call(self); }
+ };
+ req._onblocked_ = function (e) {
+ window.steps.push(evalAndLog("'" + id + ".setVersion.onblocked'"));
+ if (opts && opts.onblocked) { opts.onblocked.call(self); }
+ };
+ req._onsuccess_ = function (e) {
+ window.steps.push(evalAndLog("'" + id + ".setVersion.onsuccess'"));
+ if (self.handle.objectStoreNames.contains("test-store" + window.ver)) {
+ self.handle.deleteObjectStore("test-store" + window.ver);
+ }
+ var store = self.handle.createObjectStore("test-store" + window.ver);
+ var count = 0;
+ do_async_puts(); // Keep this transaction running for a while
+ function do_async_puts() {
+ var req = store.put(count, count);
+ req._onerror_ = unexpectedErrorCallback;
+ req._onsuccess_ = function (e) {
+ if (++count < 10) {
+ do_async_puts();
+ } else {
+ window.steps.push(evalAndLog("'" + id + ".setVersion.transaction-complete'"));
+ if (opts && opts.onsuccess) { opts.onsuccess.call(self); }
+ }
+ };
+ }
+ };
+ req._onerror_ = function (e) {
+ window.steps.push(evalAndLog("'" + id + ".setVersion.onerror'"));
+ if (opts && opts.onerror) { opts.onerror.call(self); }
+ };
+ };
+}
+
+// run a series of steps that take a continuation function
+function runSteps(commands) {
+ if (commands.length) {
+ var command = commands.shift();
+ command(function() { runSteps(commands); });
+ }
+}
+
+function test() {
+ indexedDB = evalAndLog("indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;");
+ shouldBeFalse("indexedDB == null");
+ test1();
+}
+
+function test1() {
+ debug("");
+ debug("TEST: setVersion blocked on open handles");
+ evalAndLog("window.dbname = 'test1'; window.ver = 1; window.steps = []");
+ var h1 = new Connection("h1");
+ var h2 = new Connection("h2");
+ runSteps([function(doNext) { h1.open({onsuccess: doNext}); },
+ function(doNext) { h2.open({onsuccess: doNext,
+ onversion: function() {
+ debug(" h2 closing, but not immediately");
+ setTimeout(function() { h2.close(); }, 0);
+ }}); },
+ function(doNext) { h1.setVersion({onsuccess: finishTest}); },
+ ]);
+ function finishTest() {
+ shouldBeEqualToString("window.steps.toString()",
+ ["h1.open",
+ "h1.open.onsuccess",
+ "h2.open",
+ "h2.open.onsuccess",
+ "h1.setVersion",
+ "h2.onversionchange",
+ "h1.setVersion.onblocked",
+ "h2.close",
+ "h1.setVersion.onsuccess",
+ "h1.setVersion.transaction-complete"
+ ].toString());
+ test2();
+ }
+}
+
+function test2() {
+ debug("");
+ debug("TEST: setVersion not blocked if handle closed immediately");
+ evalAndLog("window.dbname = 'test2'; window.ver = 1; window.steps = []");
+ var h1 = new Connection("h1");
+ var h2 = new Connection("h2");
+ runSteps([function(doNext) { h1.open({onsuccess: doNext}); },
+ function(doNext) { h2.open({onsuccess: doNext,
+ onversion: function() {
+ debug(" h2 closing immediately");
+ h2.close();
+ }}); },
+ function(doNext) { h1.setVersion({onsuccess: doNext}); },
+ finishTest
+ ]);
+
+ function finishTest() {
+ debug("NOTE: Will FAIL with extra bogus h1.setVersion.onblocked step; crbug.com/100123");
+ shouldBeEqualToString("window.steps.toString()",
+ ["h1.open",
+ "h1.open.onsuccess",
+ "h2.open",
+ "h2.open.onsuccess",
+ "h1.setVersion",
+ "h2.onversionchange",
+ "h2.close",
+ "h1.setVersion.onsuccess",
+ "h1.setVersion.transaction-complete"
+ ].toString());
+ test3();
+ }
+}
+
+function test3() {
+ debug("");
+ debug("TEST: open and setVersion blocked if a VERSION_CHANGE transaction is running - close when blocked");
+ evalAndLog("window.dbname = 'test3'; window.ver = 1; window.steps = []");
+ var h1 = new Connection("h1");
+ var h2 = new Connection("h2");
+ var h3 = new Connection("h3");
+ runSteps([function(doNext) { h1.open({onsuccess: doNext}); },
+ function(doNext) { h2.open({onsuccess: doNext}); },
+ function(doNext) { h1.setVersion(); doNext(); },
+ function(doNext) { h2.setVersion({
+ onblocked: function() {
+ debug(" h2 blocked so closing");
+ h2.close();
+ doNext();
+ }}); },
+ function() { h3.open({onsuccess: finishTest});}
+ ]);
+
+ function finishTest() {
+ shouldBeEqualToString("window.steps.toString()",
+ ["h1.open",
+ "h1.open.onsuccess",
+ "h2.open",
+ "h2.open.onsuccess",
+ "h1.setVersion",
+ "h2.setVersion",
+ "h2.onversionchange",
+ "h1.setVersion.onblocked",
+ "h1.onversionchange",
+ "h2.setVersion.onblocked",
+ "h2.close",
+ "h3.open",
+ "h2.setVersion.onerror",
+ "h1.setVersion.onsuccess",
+ "h1.setVersion.transaction-complete",
+ "h3.open.onsuccess"
+ ].toString());
+ test4();
+ }
+}
+
+function test4() {
+ debug("");
+ debug("TEST: open and setVersion blocked if a VERSION_CHANGE transaction is running - just close");
+ evalAndLog("window.dbname = 'test4'; window.ver = 1; window.steps = []");
+ var h1 = new Connection("h1");
+ var h2 = new Connection("h2");
+ var h3 = new Connection("h3");
+ runSteps([function(doNext) { h1.open({onsuccess: doNext}); },
+ function(doNext) { h2.open({onsuccess: doNext}); },
+ function(doNext) { h1.setVersion(); doNext(); },
+ function(doNext) { h3.open({onsuccess: finishTest}); doNext(); },
+ function(doNext) { h2.close(); },
+ ]);
+
+ function finishTest() {
+ debug("NOTE: Will FAIL with extra bogus h1.setVersion.onblocked step; crbug.com/100123");
+ shouldBeEqualToString("window.steps.toString()",
+ ["h1.open",
+ "h1.open.onsuccess",
+ "h2.open",
+ "h2.open.onsuccess",
+ "h1.setVersion",
+ "h3.open",
+ "h2.close",
+ "h1.setVersion.onsuccess",
+ "h1.setVersion.transaction-complete",
+ "h3.open.onsuccess"
+ ].toString());
+ test5();
+ }
+}
+
+function test5() {
+ debug("");
+ debug("TEST: open blocked if a VERSION_CHANGE transaction is running");
+ evalAndLog("window.dbname = 'test5'; window.ver = 1; window.steps = []");
+ var h1 = new Connection("h1");
+ var h2 = new Connection("h2");
+
+ runSteps([function(doNext) { h1.open({onsuccess: doNext}); },
+ function(doNext) { h1.setVersion(); doNext(); },
+ function(doNext) { h2.open({onsuccess: finishTest}); }
+ ]);
+
+ function finishTest() {
+ shouldBeEqualToString("window.steps.toString()",
+ ["h1.open",
+ "h1.open.onsuccess",
+ "h1.setVersion",
+ "h2.open",
+ "h1.setVersion.onsuccess",
+ "h1.setVersion.transaction-complete",
+ "h2.open.onsuccess"
+ ].toString());
+ test6();
+ }
+}
+
+
+function test6() {
+ debug("");
+ debug("TEST: two setVersions from the same connection");
+ evalAndLog("window.dbname = 'test6'; window.ver = 1; window.steps = []");
+ var h1 = new Connection("h1");
+
+ runSteps([function(doNext) { h1.open({onsuccess: doNext}); },
+ function(doNext) { h1.setVersion({onsuccess: halfDone});
+ h1.setVersion({onsuccess: halfDone}); }
+ ]);
+
+ var counter = 0;
+ function halfDone() {
+ counter++;
+ if (counter < 2) {
+ debug('half done');
+ } else {
+ finishTest();
+ }
+ }
+
+ function finishTest() {
+ shouldBeEqualToString("window.steps.toString()",
+ ["h1.open",
+ "h1.open.onsuccess",
+ "h1.setVersion",
+ "h1.setVersion",
+ "h1.setVersion.onsuccess",
+ "h1.setVersion.transaction-complete",
+ "h1.setVersion.onsuccess",
+ "h1.setVersion.transaction-complete"
+ ].toString());
+ done();
+ }
+}
+
+var successfullyParsed = true;
+
+test();
+
+</script>
+</body>
+</html>
Added: trunk/LayoutTests/storage/indexeddb/two-version-changes-expected.txt (0 => 97417)
--- trunk/LayoutTests/storage/indexeddb/two-version-changes-expected.txt (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/two-version-changes-expected.txt 2011-10-13 23:08:13 UTC (rev 97417)
@@ -0,0 +1,40 @@
+Test behavior when the same connection calls setVersion twice
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
+IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.mozIDBTransaction;
+PASS indexedDB == null is false
+PASS IDBTransaction == null is false
+window.state = 'starting'
+indexedDB.open('two-versions-one-connection')
+db = event.target.result
+window.state = 0
+db.setVersion('version 1')
+db.setVersion('version 2')
+setVersion() #1 callback
+window.store1 = db.createObjectStore('test-store1')
+PASS ++window.state is 1
+window.store1.put('aaa', 111)
+PASS ++window.state is 2
+PASS ++window.state is 3
+halfway there...
+setVersion() #2 callback
+PASS ++window.state is 4
+window.store2 = db.createObjectStore('test-store2')
+window.store2.put('bbb', 222)
+PASS ++window.state is 5
+PASS ++window.state is 6
+PASS window.db.version is "version 2"
+window.trans = db.transaction(['test-store1', 'test-store2'])
+window.store = window.trans.objectStore('test-store1')
+window.req = window.store.get(111)
+PASS event.target.result is "aaa"
+window.store = window.trans.objectStore('test-store2')
+window.req = window.store.get(222)
+PASS event.target.result is "bbb"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/storage/indexeddb/two-version-changes.html (0 => 97417)
--- trunk/LayoutTests/storage/indexeddb/two-version-changes.html (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/two-version-changes.html 2011-10-13 23:08:13 UTC (rev 97417)
@@ -0,0 +1,116 @@
+<html>
+<head>
+<link rel="stylesheet" href=""
+<script src=""
+<script src=""
+<script src=""
+</head>
+<body>
+<p id="description"></p>
+<div id="console"></div>
+<script>
+
+description("Test behavior when the same connection calls setVersion twice");
+if (window.layoutTestController)
+ layoutTestController.waitUntilDone();
+
+
+function test() {
+ indexedDB = evalAndLog("indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;");
+ IDBTransaction = evalAndLog("IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.mozIDBTransaction;");
+
+ shouldBeFalse("indexedDB == null");
+ shouldBeFalse("IDBTransaction == null");
+ openDBConnection();
+}
+
+function openDBConnection()
+{
+ evalAndLog("window.state = 'starting'");
+ var request = evalAndLog("indexedDB.open('two-versions-one-connection')");
+ request._onsuccess_ = openSuccess;
+ request._onerror_ = unexpectedErrorCallback;
+}
+
+function openSuccess()
+{
+ window.db = evalAndLog("db = event.target.result");
+ evalAndLog("window.state = 0");
+
+ var versionChangeRequest1 = evalAndLog("db.setVersion('version 1')");
+ versionChangeRequest1._onerror_ = unexpectedErrorCallback;
+ versionChangeRequest1._onsuccess_ = inSetVersion1;
+
+ // and concurrently...
+
+ var versionChangeRequest2 = evalAndLog("db.setVersion('version 2')");
+ versionChangeRequest2._onerror_ = unexpectedErrorCallback;
+ versionChangeRequest2._onsuccess_ = inSetVersion2;
+}
+
+function inSetVersion1()
+{
+ debug("setVersion() #1 callback");
+ evalAndLog("window.store1 = db.createObjectStore('test-store1')");
+ shouldBe("++window.state", "1");
+ var req = evalAndLog("window.store1.put('aaa', 111)");
+ req._onerror_ = unexpectedErrorCallback;
+ req._onsuccess_ = function (e) {
+ shouldBe("++window.state", "2");
+ halfwayDone();
+ };
+}
+
+function inSetVersion2()
+{
+ debug("setVersion() #2 callback");
+ shouldBe("++window.state", "4");
+ evalAndLog("window.store2 = db.createObjectStore('test-store2')");
+
+ var req = evalAndLog("window.store2.put('bbb', 222)");
+ req._onerror_ = unexpectedErrorCallback;
+ req._onsuccess_ = function (e) {
+ shouldBe("++window.state", "5");
+ halfwayDone();
+ };
+}
+
+var counter = 0;
+function halfwayDone()
+{
+ counter += 1;
+ if (counter < 2) {
+ shouldBe("++window.state", "3");
+ debug("halfway there..." );
+ } else {
+ shouldBe("++window.state", "6");
+ checkResults();
+ }
+}
+
+function checkResults() {
+ shouldBeEqualToString("window.db.version", "version 2");
+ trans = evalAndLog("window.trans = db.transaction(['test-store1', 'test-store2'])");
+ store = evalAndLog("window.store = window.trans.objectStore('test-store1')");
+ req = evalAndLog("window.req = window.store.get(111)");
+ req._onerror_ = unexpectedErrorCallback;
+ req._onsuccess_ = function (e) {
+ shouldBeEqualToString("event.target.result", "aaa");
+
+ store = evalAndLog("window.store = window.trans.objectStore('test-store2')");
+ req = evalAndLog("window.req = window.store.get(222)");
+ req._onerror_ = unexpectedErrorCallback;
+ req._onsuccess_ = function (e) {
+ shouldBeEqualToString("event.target.result", "bbb");
+ done();
+ };
+ };
+}
+
+var successfullyParsed = true;
+
+test();
+
+</script>
+</body>
+</html>
Added: trunk/LayoutTests/storage/indexeddb/version-change-exclusive-expected.txt (0 => 97417)
--- trunk/LayoutTests/storage/indexeddb/version-change-exclusive-expected.txt (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/version-change-exclusive-expected.txt 2011-10-13 23:08:13 UTC (rev 97417)
@@ -0,0 +1,46 @@
+Ensure VERSION_CHANGE transaction doesn't run concurrently with other transactions
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
+PASS indexedDB == null is false
+window.state = 'starting'
+indexedDB.open('version-change-exclusive')
+db = event.target.result
+calling setVersion() - callback should run immediately
+db.setVersion('version 1')
+calling open() - callback should wait until VERSION_CHANGE transaction is complete
+indexedDB.open('version-change-exclusive')
+setVersion() callback
+starting work in VERSION_CHANGE transaction
+window.state = 'VERSION_CHANGE started'
+store = db.createObjectStore('test-store')
+store.put(0, 0)
+in put's onsuccess
+store.put(1, 1)
+in put's onsuccess
+store.put(2, 2)
+in put's onsuccess
+store.put(3, 3)
+in put's onsuccess
+store.put(4, 4)
+in put's onsuccess
+store.put(5, 5)
+in put's onsuccess
+store.put(6, 6)
+in put's onsuccess
+store.put(7, 7)
+in put's onsuccess
+store.put(8, 8)
+in put's onsuccess
+store.put(9, 9)
+in put's onsuccess
+ending work in VERSION_CHANGE transaction
+window.state = 'VERSION_CHANGE finished'
+open() callback - this should appear after VERSION_CHANGE transaction ends
+PASS window.state is "VERSION_CHANGE finished"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/storage/indexeddb/version-change-exclusive.html (0 => 97417)
--- trunk/LayoutTests/storage/indexeddb/version-change-exclusive.html (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/version-change-exclusive.html 2011-10-13 23:08:13 UTC (rev 97417)
@@ -0,0 +1,87 @@
+<html>
+<head>
+<link rel="stylesheet" href=""
+<script src=""
+<script src=""
+<script src=""
+</head>
+<body>
+<p id="description"></p>
+<div id="console"></div>
+<script>
+
+description("Ensure VERSION_CHANGE transaction doesn't run concurrently with other transactions");
+if (window.layoutTestController)
+ layoutTestController.waitUntilDone();
+
+
+function test() {
+ indexedDB = evalAndLog("indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;");
+ shouldBeFalse("indexedDB == null");
+ openDBConnection();
+}
+
+function openDBConnection()
+{
+ evalAndLog("window.state = 'starting'");
+ request = evalAndLog("indexedDB.open('version-change-exclusive')");
+ request._onsuccess_ = openSuccess;
+ request._onerror_ = unexpectedErrorCallback;
+}
+
+function openSuccess()
+{
+ window.db = evalAndLog("db = event.target.result");
+
+ debug("calling setVersion() - callback should run immediately");
+ var versionChangeRequest = evalAndLog("db.setVersion('version 1')");
+ versionChangeRequest._onerror_ = unexpectedErrorCallback;
+ versionChangeRequest._onsuccess_ = inSetVersion;
+
+ // and concurrently...
+
+ debug("calling open() - callback should wait until VERSION_CHANGE transaction is complete");
+ var openRequest = evalAndLog("indexedDB.open('version-change-exclusive')");
+ openRequest._onsuccess_ = openAgainSuccess;
+ openRequest._onerror_ = unexpectedErrorCallback;
+}
+
+function inSetVersion()
+{
+ debug("setVersion() callback");
+ debug("starting work in VERSION_CHANGE transaction");
+ evalAndLog("window.state = 'VERSION_CHANGE started'");
+
+ window.store = evalAndLog("store = db.createObjectStore('test-store')");
+ window.count = 0;
+ do_async_puts();
+
+ function do_async_puts() {
+ var req = evalAndLog("store.put(" + count + ", " + count + ")");
+ req._onerror_ = unexpectedErrorCallback;
+ req._onsuccess_ = function (e) {
+ debug("in put's onsuccess");
+ if (++window.count < 10) {
+ do_async_puts();
+ } else {
+ debug("ending work in VERSION_CHANGE transaction");
+ evalAndLog("window.state = 'VERSION_CHANGE finished'");
+ }
+ };
+ }
+}
+
+function openAgainSuccess()
+{
+ debug("open() callback - this should appear after VERSION_CHANGE transaction ends");
+ shouldBeEqualToString("window.state", "VERSION_CHANGE finished");
+ done();
+}
+
+var successfullyParsed = true;
+
+test();
+
+</script>
+</body>
+</html>
Modified: trunk/Source/WebCore/ChangeLog (97416 => 97417)
--- trunk/Source/WebCore/ChangeLog 2011-10-13 22:54:02 UTC (rev 97416)
+++ trunk/Source/WebCore/ChangeLog 2011-10-13 23:08:13 UTC (rev 97417)
@@ -1,3 +1,36 @@
+2011-10-13 Joshua Bell <jsb...@chromium.org>
+
+ make IDBFactory.open wait for pending setVersion transactions to complete
+ https://bugs.webkit.org/show_bug.cgi?id=69307
+
+ Reviewed by Tony Chang.
+
+ Added a queue of pending open calls, similar to the queue of pending
+ setVersion calls. Ensure pending calls are processed in the correct
+ order when transactions complete.
+
+ Tests: storage/indexeddb/open-close-version.html
+ storage/indexeddb/two-version-changes.html
+ storage/indexeddb/version-change-exclusive.html
+
+ * storage/IDBDatabaseBackendImpl.cpp:
+ (WebCore::IDBDatabaseBackendImpl::PendingOpenCall::create):
+ (WebCore::IDBDatabaseBackendImpl::PendingOpenCall::callbacks):
+ (WebCore::IDBDatabaseBackendImpl::PendingOpenCall::PendingOpenCall):
+ (WebCore::IDBDatabaseBackendImpl::setVersion):
+ (WebCore::IDBDatabaseBackendImpl::transactionStarted):
+ (WebCore::IDBDatabaseBackendImpl::transactionFinished):
+ (WebCore::IDBDatabaseBackendImpl::processPendingCalls):
+ (WebCore::IDBDatabaseBackendImpl::openConnection):
+ (WebCore::IDBDatabaseBackendImpl::close):
+ * storage/IDBDatabaseBackendImpl.h:
+ * storage/IDBFactoryBackendImpl.cpp:
+ (WebCore::IDBFactoryBackendImpl::open):
+ * storage/IDBTransactionBackendImpl.cpp:
+ (WebCore::IDBTransactionBackendImpl::abort):
+ (WebCore::IDBTransactionBackendImpl::start):
+ (WebCore::IDBTransactionBackendImpl::commit):
+
2011-10-13 Chris Marrin <cmar...@apple.com>
Fix Leopard build
Modified: trunk/Source/WebCore/storage/IDBDatabaseBackendImpl.cpp (97416 => 97417)
--- trunk/Source/WebCore/storage/IDBDatabaseBackendImpl.cpp 2011-10-13 22:54:02 UTC (rev 97416)
+++ trunk/Source/WebCore/storage/IDBDatabaseBackendImpl.cpp 2011-10-13 23:08:13 UTC (rev 97417)
@@ -39,6 +39,22 @@
namespace WebCore {
+class IDBDatabaseBackendImpl::PendingOpenCall : public RefCounted<PendingOpenCall> {
+public:
+ static PassRefPtr<PendingOpenCall> create(PassRefPtr<IDBCallbacks> callbacks)
+ {
+ return adoptRef(new PendingOpenCall(callbacks));
+ }
+ PassRefPtr<IDBCallbacks> callbacks() { return m_callbacks; }
+
+private:
+ PendingOpenCall(PassRefPtr<IDBCallbacks> callbacks)
+ : m_callbacks(callbacks)
+ {
+ }
+ RefPtr<IDBCallbacks> m_callbacks;
+};
+
class IDBDatabaseBackendImpl::PendingSetVersionCall : public RefCounted<PendingSetVersionCall> {
public:
static PassRefPtr<PendingSetVersionCall> create(const String& version, PassRefPtr<IDBCallbacks> callbacks, PassRefPtr<IDBDatabaseCallbacks> databaseCallbacks)
@@ -174,12 +190,20 @@
if (*it != databaseCallbacks)
(*it)->onVersionChange(version);
}
+ // FIXME: Only fire onBlocked if there are open connections after the
+ // VersionChangeEvents are received, not just set up to fire.
+ // http://crbug.com/100123
if (m_databaseCallbacksSet.size() > 1) {
callbacks->onBlocked();
RefPtr<PendingSetVersionCall> pendingSetVersionCall = PendingSetVersionCall::create(version, callbacks, databaseCallbacks);
m_pendingSetVersionCalls.append(pendingSetVersionCall);
return;
}
+ if (m_runningVersionChangeTransaction) {
+ RefPtr<PendingSetVersionCall> pendingSetVersionCall = PendingSetVersionCall::create(version, callbacks, databaseCallbacks);
+ m_pendingSetVersionCalls.append(pendingSetVersionCall);
+ return;
+ }
RefPtr<DOMStringList> objectStoreNames = DOMStringList::create();
RefPtr<IDBDatabaseBackendImpl> database = this;
@@ -203,6 +227,43 @@
callbacks->onSuccess(transaction);
}
+void IDBDatabaseBackendImpl::transactionStarted(PassRefPtr<IDBTransactionBackendInterface> prpTransaction)
+{
+ RefPtr<IDBTransactionBackendInterface> transaction = prpTransaction;
+ if (transaction->mode() == IDBTransaction::VERSION_CHANGE) {
+ ASSERT(!m_runningVersionChangeTransaction);
+ m_runningVersionChangeTransaction = transaction;
+ }
+}
+
+void IDBDatabaseBackendImpl::transactionFinished(PassRefPtr<IDBTransactionBackendInterface> prpTransaction)
+{
+ RefPtr<IDBTransactionBackendInterface> transaction = prpTransaction;
+ if (transaction->mode() == IDBTransaction::VERSION_CHANGE) {
+ ASSERT(transaction.get() == m_runningVersionChangeTransaction.get());
+ m_runningVersionChangeTransaction.clear();
+ processPendingCalls();
+ }
+}
+
+void IDBDatabaseBackendImpl::processPendingCalls()
+{
+ // Pending calls may be requeued or aborted
+ Deque<RefPtr<PendingSetVersionCall> > pendingSetVersionCalls;
+ m_pendingSetVersionCalls.swap(pendingSetVersionCalls);
+ while (!pendingSetVersionCalls.isEmpty()) {
+ ExceptionCode ec = 0;
+ RefPtr<PendingSetVersionCall> pendingSetVersionCall = pendingSetVersionCalls.takeFirst();
+ setVersion(pendingSetVersionCall->version(), pendingSetVersionCall->callbacks(), pendingSetVersionCall->databaseCallbacks(), ec);
+ ASSERT(!ec);
+ }
+
+ while (!m_runningVersionChangeTransaction && m_pendingSetVersionCalls.isEmpty() && !m_pendingOpenCalls.isEmpty()) {
+ RefPtr<PendingOpenCall> pendingOpenCall = m_pendingOpenCalls.takeFirst();
+ openConnection(pendingOpenCall->callbacks());
+ }
+}
+
PassRefPtr<IDBTransactionBackendInterface> IDBDatabaseBackendImpl::transaction(DOMStringList* objectStoreNames, unsigned short mode, ExceptionCode& ec)
{
for (size_t i = 0; i < objectStoreNames->length(); ++i) {
@@ -221,6 +282,14 @@
m_databaseCallbacksSet.add(RefPtr<IDBDatabaseCallbacks>(callbacks));
}
+void IDBDatabaseBackendImpl::openConnection(PassRefPtr<IDBCallbacks> callbacks)
+{
+ if (m_runningVersionChangeTransaction || !m_pendingSetVersionCalls.isEmpty())
+ m_pendingOpenCalls.append(PendingOpenCall::create(callbacks));
+ else
+ callbacks->onSuccess(this);
+}
+
void IDBDatabaseBackendImpl::close(PassRefPtr<IDBDatabaseCallbacks> prpCallbacks)
{
RefPtr<IDBDatabaseCallbacks> callbacks = prpCallbacks;
@@ -229,12 +298,7 @@
if (m_databaseCallbacksSet.size() > 1)
return;
- while (!m_pendingSetVersionCalls.isEmpty()) {
- ExceptionCode ec = 0;
- RefPtr<PendingSetVersionCall> pendingSetVersionCall = m_pendingSetVersionCalls.takeFirst();
- setVersion(pendingSetVersionCall->version(), pendingSetVersionCall->callbacks(), pendingSetVersionCall->databaseCallbacks(), ec);
- ASSERT(!ec);
- }
+ processPendingCalls();
}
void IDBDatabaseBackendImpl::loadObjectStores()
Modified: trunk/Source/WebCore/storage/IDBDatabaseBackendImpl.h (97416 => 97417)
--- trunk/Source/WebCore/storage/IDBDatabaseBackendImpl.h 2011-10-13 22:54:02 UTC (rev 97416)
+++ trunk/Source/WebCore/storage/IDBDatabaseBackendImpl.h 2011-10-13 23:08:13 UTC (rev 97417)
@@ -54,7 +54,10 @@
static const int64_t InvalidId = 0;
int64_t id() const { return m_id; }
+
+ // FIXME: Rename "open" to something more descriptive, like registerFrontEndCallbacks.
void open(PassRefPtr<IDBDatabaseCallbacks>);
+ void openConnection(PassRefPtr<IDBCallbacks>);
virtual String name() const { return m_name; }
virtual String version() const { return m_version; }
@@ -68,11 +71,14 @@
PassRefPtr<IDBObjectStoreBackendInterface> objectStore(const String& name);
IDBTransactionCoordinator* transactionCoordinator() const { return m_transactionCoordinator.get(); }
+ void transactionStarted(PassRefPtr<IDBTransactionBackendInterface>);
+ void transactionFinished(PassRefPtr<IDBTransactionBackendInterface>);
private:
IDBDatabaseBackendImpl(const String& name, IDBBackingStore* database, IDBTransactionCoordinator*, IDBFactoryBackendImpl*, const String& uniqueIdentifier);
void loadObjectStores();
+ void processPendingCalls();
static void createObjectStoreInternal(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl>, PassRefPtr<IDBObjectStoreBackendImpl>, PassRefPtr<IDBTransactionBackendInterface>);
static void deleteObjectStoreInternal(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl>, PassRefPtr<IDBObjectStoreBackendImpl>, PassRefPtr<IDBTransactionBackendInterface>);
@@ -96,10 +102,14 @@
ObjectStoreMap m_objectStores;
RefPtr<IDBTransactionCoordinator> m_transactionCoordinator;
+ RefPtr<IDBTransactionBackendInterface> m_runningVersionChangeTransaction;
class PendingSetVersionCall;
Deque<RefPtr<PendingSetVersionCall> > m_pendingSetVersionCalls;
+ class PendingOpenCall;
+ Deque<RefPtr<PendingOpenCall> > m_pendingOpenCalls;
+
typedef ListHashSet<RefPtr<IDBDatabaseCallbacks> > DatabaseCallbacksSet;
DatabaseCallbacksSet m_databaseCallbacksSet;
};
Modified: trunk/Source/WebCore/storage/IDBFactoryBackendImpl.cpp (97416 => 97417)
--- trunk/Source/WebCore/storage/IDBFactoryBackendImpl.cpp 2011-10-13 22:54:02 UTC (rev 97416)
+++ trunk/Source/WebCore/storage/IDBFactoryBackendImpl.cpp 2011-10-13 23:08:13 UTC (rev 97417)
@@ -109,7 +109,9 @@
IDBDatabaseBackendMap::iterator it = m_databaseBackendMap.find(uniqueIdentifier);
if (it != m_databaseBackendMap.end()) {
- callbacks->onSuccess(it->second);
+ // If it's already been opened, we have to wait for any pending
+ // setVersion calls to complete.
+ it->second->openConnection(callbacks);
return;
}
Modified: trunk/Source/WebCore/storage/IDBTransactionBackendImpl.cpp (97416 => 97417)
--- trunk/Source/WebCore/storage/IDBTransactionBackendImpl.cpp 2011-10-13 22:54:02 UTC (rev 97416)
+++ trunk/Source/WebCore/storage/IDBTransactionBackendImpl.cpp 2011-10-13 23:08:13 UTC (rev 97417)
@@ -131,6 +131,7 @@
m_callbacks->onAbort();
m_database->transactionCoordinator()->didFinishTransaction(this);
ASSERT(!m_database->transactionCoordinator()->isActive(this));
+ m_database->transactionFinished(this);
m_database = 0;
}
@@ -171,6 +172,7 @@
m_state = StartPending;
m_database->transactionCoordinator()->didStartTransaction(this);
+ m_database->transactionStarted(this);
}
void IDBTransactionBackendImpl::commit()
@@ -186,6 +188,7 @@
m_transaction->commit();
m_callbacks->onComplete();
m_database->transactionCoordinator()->didFinishTransaction(this);
+ m_database->transactionFinished(this);
m_database = 0;
}