Title: [271368] trunk
Revision
271368
Author
[email protected]
Date
2021-01-11 10:48:37 -0800 (Mon, 11 Jan 2021)

Log Message

Keep newly created IDBIndex objects in deleted map when IDBTransaction is aborted
https://bugs.webkit.org/show_bug.cgi?id=220489
<rdar://problem/70498831>

Patch by Sihui Liu <[email protected]> on 2021-01-11
Reviewed by Youenn Fablet.

Source/WebCore:

When an upgrade transaction is aborted, we move objects from m_deletedIndexes to m_referencedIndexes to revert
the index deletion operation. When updating m_referencedIndexes, we did not check whether key already exists.
Therefore, some indexes in m_referencedIndexes would be replaced and destroyed (since m_referencedIndexes holds
unique pointers) when the index is still referenced by JS.

Tests: storage/indexeddb/modern/abort-index-info-private.html
       storage/indexeddb/modern/abort-index-info.html

* Modules/indexeddb/IDBObjectStore.cpp:
(WebCore::IDBObjectStore::rollbackForVersionChangeAbort):

LayoutTests:

* storage/indexeddb/modern/abort-index-info-expected.txt: Added.
* storage/indexeddb/modern/abort-index-info-private-expected.txt: Added.
* storage/indexeddb/modern/abort-index-info-private.html: Added.
* storage/indexeddb/modern/abort-index-info.html: Added.
* storage/indexeddb/modern/resources/abort-index-info.js: Added.
(prepareDatabase):
(versionChangeSuccessCallback.secondRequest.onerror):
(versionChangeSuccessCallback):
(secondUpgradeNeeded):
(checkState):

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (271367 => 271368)


--- trunk/LayoutTests/ChangeLog	2021-01-11 18:12:19 UTC (rev 271367)
+++ trunk/LayoutTests/ChangeLog	2021-01-11 18:48:37 UTC (rev 271368)
@@ -1,3 +1,22 @@
+2021-01-11  Sihui Liu  <[email protected]>
+
+        Keep newly created IDBIndex objects in deleted map when IDBTransaction is aborted
+        https://bugs.webkit.org/show_bug.cgi?id=220489
+        <rdar://problem/70498831>
+
+        Reviewed by Youenn Fablet.
+
+        * storage/indexeddb/modern/abort-index-info-expected.txt: Added.
+        * storage/indexeddb/modern/abort-index-info-private-expected.txt: Added.
+        * storage/indexeddb/modern/abort-index-info-private.html: Added.
+        * storage/indexeddb/modern/abort-index-info.html: Added.
+        * storage/indexeddb/modern/resources/abort-index-info.js: Added.
+        (prepareDatabase):
+        (versionChangeSuccessCallback.secondRequest.onerror):
+        (versionChangeSuccessCallback):
+        (secondUpgradeNeeded):
+        (checkState):
+
 2021-01-11  Antti Koivisto  <[email protected]>
 
         REGRESSION (r258321): CSS rules using :first-of-type are applied to any/all siblings in a group under certain circumstances

Added: trunk/LayoutTests/storage/indexeddb/modern/abort-index-info-expected.txt (0 => 271368)


--- trunk/LayoutTests/storage/indexeddb/modern/abort-index-info-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/modern/abort-index-info-expected.txt	2021-01-11 18:48:37 UTC (rev 271368)
@@ -0,0 +1,51 @@
+Explores the edge cases of what IDBIndex objects look like after a version change transaction that changed them aborts.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+indexedDB = self.indexedDB || self.webkitIndexedDB || self.mozIndexedDB || self.msIndexedDB || self.OIndexedDB;
+
+indexedDB.deleteDatabase(dbname)
+indexedDB.open(dbname)
+connection1 = event.target.result;
+objectStore1 = connection1.createObjectStore('objectStore');
+index1_1 = objectStore1.createIndex('foo', 'foo');
+
+PASS connection1.version is 1
+PASS objectStore1.indexNames.length is 1
+PASS index1_1.name is "foo"
+connection1.close();
+secondRequest = indexedDB.open(dbname, 2);
+secondRequest._onupgradeneeded_ = secondUpgradeNeeded;
+
+connection2 = event.target.result;
+objectStore2 = secondRequest.transaction.objectStore('objectStore');
+index2_1 = objectStore2.index('foo');
+
+PASS connection2.version is 2
+PASS objectStore2.indexNames.length is 1
+PASS index2_1.name is "foo"
+
+objectStore2.deleteIndex('foo');
+new_index2_1 = objectStore2.createIndex('foo', 'foo');
+index2_2 = objectStore2.createIndex('bar', 'bar');
+
+PASS objectStore2.indexNames.length is 2
+PASS new_index2_1.name is "foo"
+PASS index2_2.name is "bar"
+
+secondRequest.transaction.abort();
+connection2.close()
+
+PASS connection1.version is 1
+PASS objectStore1.indexNames.length is 1
+PASS index1_1.name is "foo"
+PASS connection2.version is 1
+PASS objectStore2.indexNames.length is 1
+PASS index2_1.name is "foo"
+PASS new_index2_1.name is "foo"
+PASS index2_2.name is "bar"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/storage/indexeddb/modern/abort-index-info-private-expected.txt (0 => 271368)


--- trunk/LayoutTests/storage/indexeddb/modern/abort-index-info-private-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/modern/abort-index-info-private-expected.txt	2021-01-11 18:48:37 UTC (rev 271368)
@@ -0,0 +1,51 @@
+Explores the edge cases of what IDBIndex objects look like after a version change transaction that changed them aborts.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+indexedDB = self.indexedDB || self.webkitIndexedDB || self.mozIndexedDB || self.msIndexedDB || self.OIndexedDB;
+
+indexedDB.deleteDatabase(dbname)
+indexedDB.open(dbname)
+connection1 = event.target.result;
+objectStore1 = connection1.createObjectStore('objectStore');
+index1_1 = objectStore1.createIndex('foo', 'foo');
+
+PASS connection1.version is 1
+PASS objectStore1.indexNames.length is 1
+PASS index1_1.name is "foo"
+connection1.close();
+secondRequest = indexedDB.open(dbname, 2);
+secondRequest._onupgradeneeded_ = secondUpgradeNeeded;
+
+connection2 = event.target.result;
+objectStore2 = secondRequest.transaction.objectStore('objectStore');
+index2_1 = objectStore2.index('foo');
+
+PASS connection2.version is 2
+PASS objectStore2.indexNames.length is 1
+PASS index2_1.name is "foo"
+
+objectStore2.deleteIndex('foo');
+new_index2_1 = objectStore2.createIndex('foo', 'foo');
+index2_2 = objectStore2.createIndex('bar', 'bar');
+
+PASS objectStore2.indexNames.length is 2
+PASS new_index2_1.name is "foo"
+PASS index2_2.name is "bar"
+
+secondRequest.transaction.abort();
+connection2.close()
+
+PASS connection1.version is 1
+PASS objectStore1.indexNames.length is 1
+PASS index1_1.name is "foo"
+PASS connection2.version is 1
+PASS objectStore2.indexNames.length is 1
+PASS index2_1.name is "foo"
+PASS new_index2_1.name is "foo"
+PASS index2_2.name is "bar"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/storage/indexeddb/modern/abort-index-info-private.html (0 => 271368)


--- trunk/LayoutTests/storage/indexeddb/modern/abort-index-info-private.html	                        (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/modern/abort-index-info-private.html	2021-01-11 18:48:37 UTC (rev 271368)
@@ -0,0 +1,10 @@
+<!-- webkit-test-runner [ useEphemeralSession=true ] -->
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+<script src=""
+</body>
+</html>
\ No newline at end of file

Added: trunk/LayoutTests/storage/indexeddb/modern/abort-index-info.html (0 => 271368)


--- trunk/LayoutTests/storage/indexeddb/modern/abort-index-info.html	                        (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/modern/abort-index-info.html	2021-01-11 18:48:37 UTC (rev 271368)
@@ -0,0 +1,9 @@
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+<script src=""
+</body>
+</html>
\ No newline at end of file

Added: trunk/LayoutTests/storage/indexeddb/modern/resources/abort-index-info.js (0 => 271368)


--- trunk/LayoutTests/storage/indexeddb/modern/resources/abort-index-info.js	                        (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/modern/resources/abort-index-info.js	2021-01-11 18:48:37 UTC (rev 271368)
@@ -0,0 +1,68 @@
+description("Explores the edge cases of what IDBIndex objects look like after a version change transaction that changed them aborts.");
+
+indexedDBTest(prepareDatabase, versionChangeSuccessCallback);
+
+function prepareDatabase()
+{
+    evalAndLog("connection1 = event.target.result;");
+    evalAndLog("objectStore1 = connection1.createObjectStore('objectStore');");
+    evalAndLog("index1_1 = objectStore1.createIndex('foo', 'foo');");
+
+    debug("");
+    shouldBe("connection1.version", "1");
+    shouldBe("objectStore1.indexNames.length", "1");
+    shouldBeEqualToString("index1_1.name", "foo");
+}
+
+function versionChangeSuccessCallback()
+{
+    evalAndLog("connection1.close();");
+    evalAndLog("secondRequest = indexedDB.open(dbname, 2);");
+    evalAndLog("secondRequest._onupgradeneeded_ = secondUpgradeNeeded;");
+    secondRequest._onsuccess_ = unexpectedSuccessCallback;
+    secondRequest._onerror_ = function() {
+        evalAndLog("connection2.close()");
+        checkState();
+    };
+}
+
+function secondUpgradeNeeded()
+{
+    debug("");
+    evalAndLog("connection2 = event.target.result;");
+    evalAndLog("objectStore2 = secondRequest.transaction.objectStore('objectStore');");
+    evalAndLog("index2_1 = objectStore2.index('foo');");
+
+    debug("");
+    shouldBe("connection2.version", "2");
+    shouldBe("objectStore2.indexNames.length", "1");
+    shouldBeEqualToString("index2_1.name", "foo");
+    
+    debug("");
+    evalAndLog("objectStore2.deleteIndex('foo');");
+    evalAndLog("new_index2_1 = objectStore2.createIndex('foo', 'foo');");
+    evalAndLog("index2_2 = objectStore2.createIndex('bar', 'bar');");
+
+    debug("");
+    shouldBe("objectStore2.indexNames.length", "2");
+    shouldBeEqualToString("new_index2_1.name", "foo");
+    shouldBeEqualToString("index2_2.name", "bar");
+
+    debug("");
+    evalAndLog("secondRequest.transaction.abort();");
+}
+
+function checkState()
+{
+    debug("");
+    shouldBe("connection1.version", "1");
+    shouldBe("objectStore1.indexNames.length", "1");
+    shouldBeEqualToString("index1_1.name", "foo");
+    shouldBe("connection2.version", "1");
+    shouldBe("objectStore2.indexNames.length", "1");
+    shouldBeEqualToString("index2_1.name", "foo");
+    shouldBeEqualToString("new_index2_1.name", "foo");
+    shouldBeEqualToString("index2_2.name", "bar");
+
+    finishJSTest();
+}

Modified: trunk/Source/WebCore/ChangeLog (271367 => 271368)


--- trunk/Source/WebCore/ChangeLog	2021-01-11 18:12:19 UTC (rev 271367)
+++ trunk/Source/WebCore/ChangeLog	2021-01-11 18:48:37 UTC (rev 271368)
@@ -1,3 +1,22 @@
+2021-01-11  Sihui Liu  <[email protected]>
+
+        Keep newly created IDBIndex objects in deleted map when IDBTransaction is aborted
+        https://bugs.webkit.org/show_bug.cgi?id=220489
+        <rdar://problem/70498831>
+
+        Reviewed by Youenn Fablet.
+
+        When an upgrade transaction is aborted, we move objects from m_deletedIndexes to m_referencedIndexes to revert 
+        the index deletion operation. When updating m_referencedIndexes, we did not check whether key already exists. 
+        Therefore, some indexes in m_referencedIndexes would be replaced and destroyed (since m_referencedIndexes holds 
+        unique pointers) when the index is still referenced by JS.
+
+        Tests: storage/indexeddb/modern/abort-index-info-private.html
+               storage/indexeddb/modern/abort-index-info.html
+
+        * Modules/indexeddb/IDBObjectStore.cpp:
+        (WebCore::IDBObjectStore::rollbackForVersionChangeAbort):
+
 2021-01-11  Antti Koivisto  <[email protected]>
 
         REGRESSION (r258321): CSS rules using :first-of-type are applied to any/all siblings in a group under certain circumstances

Modified: trunk/Source/WebCore/Modules/indexeddb/IDBObjectStore.cpp (271367 => 271368)


--- trunk/Source/WebCore/Modules/indexeddb/IDBObjectStore.cpp	2021-01-11 18:12:19 UTC (rev 271367)
+++ trunk/Source/WebCore/Modules/indexeddb/IDBObjectStore.cpp	2021-01-11 18:48:37 UTC (rev 271368)
@@ -703,10 +703,14 @@
     Locker<Lock> locker(m_referencedIndexLock);
 
     Vector<uint64_t> identifiersToRemove;
+    Vector<std::unique_ptr<IDBIndex>> indexesToDelete;
     for (auto& iterator : m_deletedIndexes) {
         if (m_info.hasIndex(iterator.key)) {
             auto name = iterator.value->info().name();
-            m_referencedIndexes.set(name, WTFMove(iterator.value));
+            auto result = m_referencedIndexes.add(name, nullptr);
+            if (!result.isNewEntry)
+                indexesToDelete.append(std::exchange(result.iterator->value, nullptr));
+            result.iterator->value = std::exchange(iterator.value, nullptr);
             identifiersToRemove.append(iterator.key);
         }
     }
@@ -716,6 +720,12 @@
 
     for (auto& index : m_referencedIndexes.values())
         index->rollbackInfoForVersionChangeAbort();
+
+    for (auto& index : indexesToDelete) {
+        index->rollbackInfoForVersionChangeAbort();
+        auto indexIdentifier = index->info().identifier();
+        m_deletedIndexes.set(indexIdentifier, std::exchange(index, nullptr));
+    }
 }
 
 void IDBObjectStore::visitReferencedIndexes(SlotVisitor& visitor) const
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to