OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2013 Google Inc. All rights reserved. | 2 * Copyright (C) 2013 Google Inc. All rights reserved. |
3 * | 3 * |
4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
5 * modification, are permitted provided that the following conditions are | 5 * modification, are permitted provided that the following conditions are |
6 * met: | 6 * met: |
7 * | 7 * |
8 * * Redistributions of source code must retain the above copyright | 8 * * Redistributions of source code must retain the above copyright |
9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
10 * * Redistributions in binary form must reproduce the above | 10 * * Redistributions in binary form must reproduce the above |
(...skipping 18 matching lines...) Expand all Loading... |
29 */ | 29 */ |
30 | 30 |
31 #include "modules/indexeddb/IDBTransaction.h" | 31 #include "modules/indexeddb/IDBTransaction.h" |
32 | 32 |
33 #include <memory> | 33 #include <memory> |
34 | 34 |
35 #include "bindings/core/v8/V8BindingForTesting.h" | 35 #include "bindings/core/v8/V8BindingForTesting.h" |
36 #include "core/dom/DOMException.h" | 36 #include "core/dom/DOMException.h" |
37 #include "core/dom/Document.h" | 37 #include "core/dom/Document.h" |
38 #include "core/dom/ExceptionCode.h" | 38 #include "core/dom/ExceptionCode.h" |
| 39 #include "core/events/EventQueue.h" |
39 #include "modules/indexeddb/IDBDatabase.h" | 40 #include "modules/indexeddb/IDBDatabase.h" |
40 #include "modules/indexeddb/IDBDatabaseCallbacks.h" | 41 #include "modules/indexeddb/IDBDatabaseCallbacks.h" |
| 42 #include "modules/indexeddb/IDBValue.h" |
| 43 #include "modules/indexeddb/IDBValueWrapping.h" |
41 #include "modules/indexeddb/MockWebIDBDatabase.h" | 44 #include "modules/indexeddb/MockWebIDBDatabase.h" |
42 #include "platform/SharedBuffer.h" | 45 #include "platform/SharedBuffer.h" |
| 46 #include "platform/wtf/PtrUtil.h" |
| 47 #include "platform/wtf/RefPtr.h" |
| 48 #include "platform/wtf/Vector.h" |
| 49 #include "public/platform/Platform.h" |
| 50 #include "public/platform/WebURLLoaderMockFactory.h" |
| 51 #include "public/platform/WebURLResponse.h" |
43 #include "testing/gtest/include/gtest/gtest.h" | 52 #include "testing/gtest/include/gtest/gtest.h" |
44 #include "v8/include/v8.h" | 53 #include "v8/include/v8.h" |
45 | 54 |
46 namespace blink { | 55 namespace blink { |
47 namespace { | 56 namespace { |
48 | 57 |
49 void DeactivateNewTransactions(v8::Isolate* isolate) { | 58 void DeactivateNewTransactions(v8::Isolate* isolate) { |
50 V8PerIsolateData::From(isolate)->RunEndOfScopeTasks(); | 59 V8PerIsolateData::From(isolate)->RunEndOfScopeTasks(); |
51 } | 60 } |
52 | 61 |
53 class FakeIDBDatabaseCallbacks final : public IDBDatabaseCallbacks { | 62 class FakeIDBDatabaseCallbacks final : public IDBDatabaseCallbacks { |
54 public: | 63 public: |
55 static FakeIDBDatabaseCallbacks* Create() { | 64 static FakeIDBDatabaseCallbacks* Create() { |
56 return new FakeIDBDatabaseCallbacks(); | 65 return new FakeIDBDatabaseCallbacks(); |
57 } | 66 } |
58 void OnVersionChange(int64_t old_version, int64_t new_version) override {} | 67 void OnVersionChange(int64_t old_version, int64_t new_version) override {} |
59 void OnForcedClose() override {} | 68 void OnForcedClose() override {} |
60 void OnAbort(int64_t transaction_id, DOMException* error) override {} | 69 void OnAbort(int64_t transaction_id, DOMException* error) override {} |
61 void OnComplete(int64_t transaction_id) override {} | 70 void OnComplete(int64_t transaction_id) override {} |
62 | 71 |
63 private: | 72 private: |
64 FakeIDBDatabaseCallbacks() {} | 73 FakeIDBDatabaseCallbacks() {} |
65 }; | 74 }; |
66 | 75 |
67 TEST(IDBTransactionTest, EnsureLifetime) { | 76 class IDBTransactionTest : public ::testing::Test { |
| 77 protected: |
| 78 void SetUp() override { |
| 79 url_loader_mock_factory_ = Platform::Current()->GetURLLoaderMockFactory(); |
| 80 WebURLResponse response; |
| 81 response.SetURL(KURL(KURL(), "blob:")); |
| 82 url_loader_mock_factory_->RegisterURLProtocol(WebString("blob"), response, |
| 83 ""); |
| 84 } |
| 85 |
| 86 void TearDown() override { |
| 87 url_loader_mock_factory_->UnregisterAllURLsAndClearMemoryCache(); |
| 88 } |
| 89 |
| 90 void BuildTransaction(V8TestingScope& scope, |
| 91 std::unique_ptr<MockWebIDBDatabase> backend) { |
| 92 db_ = IDBDatabase::Create(scope.GetExecutionContext(), std::move(backend), |
| 93 FakeIDBDatabaseCallbacks::Create(), |
| 94 scope.GetIsolate()); |
| 95 |
| 96 HashSet<String> transaction_scope = {"store"}; |
| 97 transaction_ = IDBTransaction::CreateNonVersionChange( |
| 98 scope.GetScriptState(), kTransactionId, transaction_scope, |
| 99 kWebIDBTransactionModeReadOnly, db_.Get()); |
| 100 } |
| 101 |
| 102 WebURLLoaderMockFactory* url_loader_mock_factory_; |
| 103 Persistent<IDBDatabase> db_; |
| 104 Persistent<IDBTransaction> transaction_; |
| 105 |
| 106 static constexpr int64_t kTransactionId = 1234; |
| 107 }; |
| 108 |
| 109 // The created value is an array of true. If create_wrapped_value is true, the |
| 110 // IDBValue's byte array will be wrapped in a Blob, otherwise it will not be. |
| 111 RefPtr<IDBValue> CreateIDBValue(v8::Isolate* isolate, |
| 112 bool create_wrapped_value) { |
| 113 size_t element_count = create_wrapped_value ? 16 : 2; |
| 114 v8::Local<v8::Array> v8_array = v8::Array::New(isolate, element_count); |
| 115 for (size_t i = 0; i < element_count; ++i) |
| 116 v8_array->Set(i, v8::True(isolate)); |
| 117 |
| 118 NonThrowableExceptionState non_throwable_exception_state; |
| 119 IDBValueWrapper wrapper(isolate, v8_array, |
| 120 SerializedScriptValue::SerializeOptions::kSerialize, |
| 121 non_throwable_exception_state); |
| 122 wrapper.WrapIfBiggerThan(create_wrapped_value ? 0 : 1024 * element_count); |
| 123 |
| 124 std::unique_ptr<Vector<RefPtr<BlobDataHandle>>> blob_data_handles = |
| 125 WTF::MakeUnique<Vector<RefPtr<BlobDataHandle>>>(); |
| 126 wrapper.ExtractBlobDataHandles(blob_data_handles.get()); |
| 127 Vector<WebBlobInfo>& blob_infos = wrapper.WrappedBlobInfo(); |
| 128 RefPtr<SharedBuffer> wrapped_marker_buffer = wrapper.ExtractWireBytes(); |
| 129 |
| 130 RefPtr<IDBValue> idb_value = |
| 131 IDBValue::Create(wrapped_marker_buffer, std::move(blob_data_handles), |
| 132 WTF::MakeUnique<Vector<WebBlobInfo>>(blob_infos)); |
| 133 |
| 134 DCHECK_EQ(create_wrapped_value, |
| 135 IDBValueUnwrapper::IsWrapped(idb_value.Get())); |
| 136 return idb_value; |
| 137 } |
| 138 |
| 139 TEST_F(IDBTransactionTest, ContextDestroyedEarlyDeath) { |
68 V8TestingScope scope; | 140 V8TestingScope scope; |
69 std::unique_ptr<MockWebIDBDatabase> backend = MockWebIDBDatabase::Create(); | 141 std::unique_ptr<MockWebIDBDatabase> backend = MockWebIDBDatabase::Create(); |
70 EXPECT_CALL(*backend, Close()).Times(1); | 142 EXPECT_CALL(*backend, Close()).Times(1); |
71 Persistent<IDBDatabase> db = IDBDatabase::Create( | 143 BuildTransaction(scope, std::move(backend)); |
72 scope.GetExecutionContext(), std::move(backend), | |
73 FakeIDBDatabaseCallbacks::Create(), scope.GetIsolate()); | |
74 | 144 |
75 const int64_t kTransactionId = 1234; | |
76 HashSet<String> transaction_scope = HashSet<String>(); | |
77 transaction_scope.insert("test-store-name"); | |
78 Persistent<IDBTransaction> transaction = | |
79 IDBTransaction::CreateNonVersionChange( | |
80 scope.GetScriptState(), kTransactionId, transaction_scope, | |
81 kWebIDBTransactionModeReadOnly, db.Get()); | |
82 PersistentHeapHashSet<WeakMember<IDBTransaction>> live_transactions; | 145 PersistentHeapHashSet<WeakMember<IDBTransaction>> live_transactions; |
83 live_transactions.insert(transaction); | 146 live_transactions.insert(transaction_); |
84 | 147 |
85 ThreadState::Current()->CollectAllGarbage(); | 148 ThreadState::Current()->CollectAllGarbage(); |
86 EXPECT_EQ(1u, live_transactions.size()); | 149 EXPECT_EQ(1u, live_transactions.size()); |
87 | 150 |
88 Persistent<IDBRequest> request = IDBRequest::Create( | 151 Persistent<IDBRequest> request = IDBRequest::Create( |
89 scope.GetScriptState(), IDBAny::CreateUndefined(), transaction.Get()); | 152 scope.GetScriptState(), IDBAny::CreateUndefined(), transaction_.Get()); |
90 DeactivateNewTransactions(scope.GetIsolate()); | 153 DeactivateNewTransactions(scope.GetIsolate()); |
91 | 154 |
92 request.Clear(); // The transaction is holding onto the request. | 155 request.Clear(); // The transaction is holding onto the request. |
93 ThreadState::Current()->CollectAllGarbage(); | 156 ThreadState::Current()->CollectAllGarbage(); |
94 EXPECT_EQ(1u, live_transactions.size()); | 157 EXPECT_EQ(1u, live_transactions.size()); |
95 | 158 |
96 // This will generate an Abort() call to the back end which is dropped by the | 159 // This will generate an Abort() call to the back end which is dropped by the |
97 // fake proxy, so an explicit OnAbort call is made. | 160 // fake proxy, so an explicit OnAbort call is made. |
98 scope.GetExecutionContext()->NotifyContextDestroyed(); | 161 scope.GetExecutionContext()->NotifyContextDestroyed(); |
99 transaction->OnAbort(DOMException::Create(kAbortError, "Aborted")); | 162 transaction_->OnAbort(DOMException::Create(kAbortError, "Aborted")); |
100 transaction.Clear(); | 163 transaction_.Clear(); |
101 | 164 |
102 ThreadState::Current()->CollectAllGarbage(); | 165 ThreadState::Current()->CollectAllGarbage(); |
103 EXPECT_EQ(0u, live_transactions.size()); | 166 EXPECT_EQ(0U, live_transactions.size()); |
104 } | 167 } |
105 | 168 |
106 TEST(IDBTransactionTest, TransactionFinish) { | 169 TEST_F(IDBTransactionTest, ContextDestroyedAfterDone) { |
107 V8TestingScope scope; | 170 V8TestingScope scope; |
108 const int64_t kTransactionId = 1234; | 171 std::unique_ptr<MockWebIDBDatabase> backend = MockWebIDBDatabase::Create(); |
| 172 EXPECT_CALL(*backend, Close()).Times(1); |
| 173 BuildTransaction(scope, std::move(backend)); |
109 | 174 |
| 175 PersistentHeapHashSet<WeakMember<IDBTransaction>> live_transactions; |
| 176 live_transactions.insert(transaction_); |
| 177 |
| 178 ThreadState::Current()->CollectAllGarbage(); |
| 179 EXPECT_EQ(1U, live_transactions.size()); |
| 180 |
| 181 Persistent<IDBRequest> request = IDBRequest::Create( |
| 182 scope.GetScriptState(), IDBAny::CreateUndefined(), transaction_.Get()); |
| 183 DeactivateNewTransactions(scope.GetIsolate()); |
| 184 |
| 185 // This response should result in an event being enqueued immediately. |
| 186 request->HandleResponse(CreateIDBValue(scope.GetIsolate(), false)); |
| 187 |
| 188 request.Clear(); // The transaction is holding onto the request. |
| 189 ThreadState::Current()->CollectAllGarbage(); |
| 190 EXPECT_EQ(1U, live_transactions.size()); |
| 191 |
| 192 // This will generate an Abort() call to the back end which is dropped by the |
| 193 // fake proxy, so an explicit OnAbort call is made. |
| 194 scope.GetExecutionContext()->NotifyContextDestroyed(); |
| 195 transaction_->OnAbort(DOMException::Create(kAbortError, "Aborted")); |
| 196 transaction_.Clear(); |
| 197 |
| 198 // The request completed, so it has enqueued a success event. Discard the |
| 199 // event, so that the transaction can go away. |
| 200 EXPECT_EQ(1U, live_transactions.size()); |
| 201 scope.GetExecutionContext()->GetEventQueue()->Close(); |
| 202 |
| 203 ThreadState::Current()->CollectAllGarbage(); |
| 204 EXPECT_EQ(0U, live_transactions.size()); |
| 205 } |
| 206 |
| 207 TEST_F(IDBTransactionTest, ContextDestroyedWithQueuedResult) { |
| 208 V8TestingScope scope; |
| 209 std::unique_ptr<MockWebIDBDatabase> backend = MockWebIDBDatabase::Create(); |
| 210 EXPECT_CALL(*backend, Close()).Times(1); |
| 211 BuildTransaction(scope, std::move(backend)); |
| 212 |
| 213 PersistentHeapHashSet<WeakMember<IDBTransaction>> live_transactions; |
| 214 live_transactions.insert(transaction_); |
| 215 |
| 216 ThreadState::Current()->CollectAllGarbage(); |
| 217 EXPECT_EQ(1U, live_transactions.size()); |
| 218 |
| 219 Persistent<IDBRequest> request = IDBRequest::Create( |
| 220 scope.GetScriptState(), IDBAny::CreateUndefined(), transaction_.Get()); |
| 221 DeactivateNewTransactions(scope.GetIsolate()); |
| 222 |
| 223 request->HandleResponse(CreateIDBValue(scope.GetIsolate(), true)); |
| 224 |
| 225 request.Clear(); // The transaction is holding onto the request. |
| 226 ThreadState::Current()->CollectAllGarbage(); |
| 227 EXPECT_EQ(1U, live_transactions.size()); |
| 228 |
| 229 // This will generate an Abort() call to the back end which is dropped by the |
| 230 // fake proxy, so an explicit OnAbort call is made. |
| 231 scope.GetExecutionContext()->NotifyContextDestroyed(); |
| 232 transaction_->OnAbort(DOMException::Create(kAbortError, "Aborted")); |
| 233 transaction_.Clear(); |
| 234 |
| 235 url_loader_mock_factory_->ServeAsynchronousRequests(); |
| 236 |
| 237 ThreadState::Current()->CollectAllGarbage(); |
| 238 EXPECT_EQ(0U, live_transactions.size()); |
| 239 } |
| 240 |
| 241 TEST_F(IDBTransactionTest, ContextDestroyedWithTwoQueuedResults) { |
| 242 V8TestingScope scope; |
| 243 std::unique_ptr<MockWebIDBDatabase> backend = MockWebIDBDatabase::Create(); |
| 244 EXPECT_CALL(*backend, Close()).Times(1); |
| 245 BuildTransaction(scope, std::move(backend)); |
| 246 |
| 247 PersistentHeapHashSet<WeakMember<IDBTransaction>> live_transactions; |
| 248 live_transactions.insert(transaction_); |
| 249 |
| 250 ThreadState::Current()->CollectAllGarbage(); |
| 251 EXPECT_EQ(1U, live_transactions.size()); |
| 252 |
| 253 Persistent<IDBRequest> request1 = IDBRequest::Create( |
| 254 scope.GetScriptState(), IDBAny::CreateUndefined(), transaction_.Get()); |
| 255 Persistent<IDBRequest> request2 = IDBRequest::Create( |
| 256 scope.GetScriptState(), IDBAny::CreateUndefined(), transaction_.Get()); |
| 257 DeactivateNewTransactions(scope.GetIsolate()); |
| 258 |
| 259 request1->HandleResponse(CreateIDBValue(scope.GetIsolate(), true)); |
| 260 request2->HandleResponse(CreateIDBValue(scope.GetIsolate(), true)); |
| 261 |
| 262 request1.Clear(); // The transaction is holding onto the requests. |
| 263 request2.Clear(); |
| 264 ThreadState::Current()->CollectAllGarbage(); |
| 265 EXPECT_EQ(1U, live_transactions.size()); |
| 266 |
| 267 // This will generate an Abort() call to the back end which is dropped by the |
| 268 // fake proxy, so an explicit OnAbort call is made. |
| 269 scope.GetExecutionContext()->NotifyContextDestroyed(); |
| 270 transaction_->OnAbort(DOMException::Create(kAbortError, "Aborted")); |
| 271 transaction_.Clear(); |
| 272 |
| 273 url_loader_mock_factory_->ServeAsynchronousRequests(); |
| 274 |
| 275 ThreadState::Current()->CollectAllGarbage(); |
| 276 EXPECT_EQ(0U, live_transactions.size()); |
| 277 } |
| 278 |
| 279 TEST_F(IDBTransactionTest, TransactionFinish) { |
| 280 V8TestingScope scope; |
110 std::unique_ptr<MockWebIDBDatabase> backend = MockWebIDBDatabase::Create(); | 281 std::unique_ptr<MockWebIDBDatabase> backend = MockWebIDBDatabase::Create(); |
111 EXPECT_CALL(*backend, Commit(kTransactionId)).Times(1); | 282 EXPECT_CALL(*backend, Commit(kTransactionId)).Times(1); |
112 EXPECT_CALL(*backend, Close()).Times(1); | 283 EXPECT_CALL(*backend, Close()).Times(1); |
113 Persistent<IDBDatabase> db = IDBDatabase::Create( | 284 BuildTransaction(scope, std::move(backend)); |
114 scope.GetExecutionContext(), std::move(backend), | |
115 FakeIDBDatabaseCallbacks::Create(), scope.GetIsolate()); | |
116 | 285 |
117 HashSet<String> transaction_scope = HashSet<String>(); | |
118 transaction_scope.insert("test-store-name"); | |
119 Persistent<IDBTransaction> transaction = | |
120 IDBTransaction::CreateNonVersionChange( | |
121 scope.GetScriptState(), kTransactionId, transaction_scope, | |
122 kWebIDBTransactionModeReadOnly, db.Get()); | |
123 PersistentHeapHashSet<WeakMember<IDBTransaction>> live_transactions; | 286 PersistentHeapHashSet<WeakMember<IDBTransaction>> live_transactions; |
124 live_transactions.insert(transaction); | 287 live_transactions.insert(transaction_); |
125 | 288 |
126 ThreadState::Current()->CollectAllGarbage(); | 289 ThreadState::Current()->CollectAllGarbage(); |
127 EXPECT_EQ(1u, live_transactions.size()); | 290 EXPECT_EQ(1U, live_transactions.size()); |
128 | 291 |
129 DeactivateNewTransactions(scope.GetIsolate()); | 292 DeactivateNewTransactions(scope.GetIsolate()); |
130 | 293 |
131 ThreadState::Current()->CollectAllGarbage(); | 294 ThreadState::Current()->CollectAllGarbage(); |
132 EXPECT_EQ(1u, live_transactions.size()); | 295 EXPECT_EQ(1U, live_transactions.size()); |
133 | 296 |
134 transaction.Clear(); | 297 transaction_.Clear(); |
135 | 298 |
136 ThreadState::Current()->CollectAllGarbage(); | 299 ThreadState::Current()->CollectAllGarbage(); |
137 EXPECT_EQ(1u, live_transactions.size()); | 300 EXPECT_EQ(1U, live_transactions.size()); |
138 | 301 |
139 // Stop the context, so events don't get queued (which would keep the | 302 // Stop the context, so events don't get queued (which would keep the |
140 // transaction alive). | 303 // transaction alive). |
141 scope.GetExecutionContext()->NotifyContextDestroyed(); | 304 scope.GetExecutionContext()->NotifyContextDestroyed(); |
142 | 305 |
143 // Fire an abort to make sure this doesn't free the transaction during use. | 306 // Fire an abort to make sure this doesn't free the transaction during use. |
144 // The test will not fail if it is, but ASAN would notice the error. | 307 // The test will not fail if it is, but ASAN would notice the error. |
145 db->OnAbort(kTransactionId, DOMException::Create(kAbortError, "Aborted")); | 308 db_->OnAbort(kTransactionId, DOMException::Create(kAbortError, "Aborted")); |
146 | 309 |
147 // OnAbort() should have cleared the transaction's reference to the database. | 310 // OnAbort() should have cleared the transaction's reference to the database. |
148 ThreadState::Current()->CollectAllGarbage(); | 311 ThreadState::Current()->CollectAllGarbage(); |
149 EXPECT_EQ(0u, live_transactions.size()); | 312 EXPECT_EQ(0U, live_transactions.size()); |
150 } | 313 } |
151 | 314 |
152 } // namespace | 315 } // namespace |
153 } // namespace blink | 316 } // namespace blink |
OLD | NEW |