1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "content/browser/indexed_db/indexed_db_factory.h"
6
7 #include "base/file_util.h"
8 #include "base/files/scoped_temp_dir.h"
9 #include "base/logging.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/test/test_simple_task_runner.h"
13 #include "content/browser/indexed_db/indexed_db_connection.h"
14 #include "content/browser/indexed_db/indexed_db_context_impl.h"
15 #include "content/browser/indexed_db/mock_indexed_db_callbacks.h"
16 #include "content/browser/indexed_db/mock_indexed_db_database_callbacks.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18 #include "third_party/WebKit/public/platform/WebIDBDatabaseException.h"
19 #include "third_party/WebKit/public/platform/WebIDBTypes.h"
20 #include "url/gurl.h"
21 #include "webkit/common/database/database_identifier.h"
22
23 using base::ASCIIToUTF16;
24
25 namespace content {
26
27 namespace {
28
29 class MockIDBFactory : public IndexedDBFactory {
30 public:
MockIDBFactory(IndexedDBContextImpl * context)31 explicit MockIDBFactory(IndexedDBContextImpl* context)
32 : IndexedDBFactory(context) {}
TestOpenBackingStore(const GURL & origin,const base::FilePath & data_directory)33 scoped_refptr<IndexedDBBackingStore> TestOpenBackingStore(
34 const GURL& origin,
35 const base::FilePath& data_directory) {
36 blink::WebIDBDataLoss data_loss =
37 blink::WebIDBDataLossNone;
38 std::string data_loss_message;
39 bool disk_full;
40 scoped_refptr<IndexedDBBackingStore> backing_store =
41 OpenBackingStore(origin,
42 data_directory,
43 NULL /* request_context */,
44 &data_loss,
45 &data_loss_message,
46 &disk_full);
47 EXPECT_EQ(blink::WebIDBDataLossNone, data_loss);
48 return backing_store;
49 }
50
TestCloseBackingStore(IndexedDBBackingStore * backing_store)51 void TestCloseBackingStore(IndexedDBBackingStore* backing_store) {
52 CloseBackingStore(backing_store->origin_url());
53 }
54
TestReleaseBackingStore(IndexedDBBackingStore * backing_store,bool immediate)55 void TestReleaseBackingStore(IndexedDBBackingStore* backing_store,
56 bool immediate) {
57 ReleaseBackingStore(backing_store->origin_url(), immediate);
58 }
59
60 private:
~MockIDBFactory()61 virtual ~MockIDBFactory() {}
62
63 DISALLOW_COPY_AND_ASSIGN(MockIDBFactory);
64 };
65
66 } // namespace
67
68 class IndexedDBFactoryTest : public testing::Test {
69 public:
IndexedDBFactoryTest()70 IndexedDBFactoryTest() {
71 task_runner_ = new base::TestSimpleTaskRunner();
72 context_ = new IndexedDBContextImpl(base::FilePath(),
73 NULL /* special_storage_policy */,
74 NULL /* quota_manager_proxy */,
75 task_runner_.get());
76 idb_factory_ = new MockIDBFactory(context_.get());
77 }
78
79 protected:
80 // For timers to post events.
81 base::MessageLoop loop_;
82
factory() const83 MockIDBFactory* factory() const { return idb_factory_.get(); }
clear_factory()84 void clear_factory() { idb_factory_ = NULL; }
context() const85 IndexedDBContextImpl* context() const { return context_.get(); }
86
87 private:
88 scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
89 scoped_refptr<IndexedDBContextImpl> context_;
90 scoped_refptr<MockIDBFactory> idb_factory_;
91
92 DISALLOW_COPY_AND_ASSIGN(IndexedDBFactoryTest);
93 };
94
TEST_F(IndexedDBFactoryTest,BackingStoreLifetime)95 TEST_F(IndexedDBFactoryTest, BackingStoreLifetime) {
96 GURL origin1("http://localhost:81");
97 GURL origin2("http://localhost:82");
98
99 base::ScopedTempDir temp_directory;
100 ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
101 scoped_refptr<IndexedDBBackingStore> disk_store1 =
102 factory()->TestOpenBackingStore(origin1, temp_directory.path());
103
104 scoped_refptr<IndexedDBBackingStore> disk_store2 =
105 factory()->TestOpenBackingStore(origin1, temp_directory.path());
106 EXPECT_EQ(disk_store1.get(), disk_store2.get());
107
108 scoped_refptr<IndexedDBBackingStore> disk_store3 =
109 factory()->TestOpenBackingStore(origin2, temp_directory.path());
110
111 factory()->TestCloseBackingStore(disk_store1);
112 factory()->TestCloseBackingStore(disk_store3);
113
114 EXPECT_FALSE(disk_store1->HasOneRef());
115 EXPECT_FALSE(disk_store2->HasOneRef());
116 EXPECT_TRUE(disk_store3->HasOneRef());
117
118 disk_store2 = NULL;
119 EXPECT_TRUE(disk_store1->HasOneRef());
120 }
121
TEST_F(IndexedDBFactoryTest,BackingStoreLazyClose)122 TEST_F(IndexedDBFactoryTest, BackingStoreLazyClose) {
123 GURL origin("http://localhost:81");
124
125 base::ScopedTempDir temp_directory;
126 ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
127 scoped_refptr<IndexedDBBackingStore> store =
128 factory()->TestOpenBackingStore(origin, temp_directory.path());
129
130 // Give up the local refptr so that the factory has the only
131 // outstanding reference.
132 IndexedDBBackingStore* store_ptr = store.get();
133 store = NULL;
134 EXPECT_FALSE(store_ptr->close_timer()->IsRunning());
135 factory()->TestReleaseBackingStore(store_ptr, false);
136 EXPECT_TRUE(store_ptr->close_timer()->IsRunning());
137
138 factory()->TestOpenBackingStore(origin, temp_directory.path());
139 EXPECT_FALSE(store_ptr->close_timer()->IsRunning());
140 factory()->TestReleaseBackingStore(store_ptr, false);
141 EXPECT_TRUE(store_ptr->close_timer()->IsRunning());
142
143 // Take back a ref ptr and ensure that the actual close
144 // stops a running timer.
145 store = store_ptr;
146 factory()->TestCloseBackingStore(store_ptr);
147 EXPECT_FALSE(store_ptr->close_timer()->IsRunning());
148 }
149
TEST_F(IndexedDBFactoryTest,MemoryBackingStoreLifetime)150 TEST_F(IndexedDBFactoryTest, MemoryBackingStoreLifetime) {
151 GURL origin1("http://localhost:81");
152 GURL origin2("http://localhost:82");
153
154 scoped_refptr<IndexedDBBackingStore> mem_store1 =
155 factory()->TestOpenBackingStore(origin1, base::FilePath());
156
157 scoped_refptr<IndexedDBBackingStore> mem_store2 =
158 factory()->TestOpenBackingStore(origin1, base::FilePath());
159 EXPECT_EQ(mem_store1.get(), mem_store2.get());
160
161 scoped_refptr<IndexedDBBackingStore> mem_store3 =
162 factory()->TestOpenBackingStore(origin2, base::FilePath());
163
164 factory()->TestCloseBackingStore(mem_store1);
165 factory()->TestCloseBackingStore(mem_store3);
166
167 EXPECT_FALSE(mem_store1->HasOneRef());
168 EXPECT_FALSE(mem_store2->HasOneRef());
169 EXPECT_FALSE(mem_store3->HasOneRef());
170
171 clear_factory();
172 EXPECT_FALSE(mem_store1->HasOneRef()); // mem_store1 and 2
173 EXPECT_FALSE(mem_store2->HasOneRef()); // mem_store1 and 2
174 EXPECT_TRUE(mem_store3->HasOneRef());
175
176 mem_store2 = NULL;
177 EXPECT_TRUE(mem_store1->HasOneRef());
178 }
179
TEST_F(IndexedDBFactoryTest,RejectLongOrigins)180 TEST_F(IndexedDBFactoryTest, RejectLongOrigins) {
181 base::ScopedTempDir temp_directory;
182 ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
183 const base::FilePath base_path = temp_directory.path();
184
185 int limit = base::GetMaximumPathComponentLength(base_path);
186 EXPECT_GT(limit, 0);
187
188 std::string origin(limit + 1, 'x');
189 GURL too_long_origin("http://" + origin + ":81/");
190 scoped_refptr<IndexedDBBackingStore> diskStore1 =
191 factory()->TestOpenBackingStore(too_long_origin, base_path);
192 EXPECT_FALSE(diskStore1);
193
194 GURL ok_origin("http://someorigin.com:82/");
195 scoped_refptr<IndexedDBBackingStore> diskStore2 =
196 factory()->TestOpenBackingStore(ok_origin, base_path);
197 EXPECT_TRUE(diskStore2);
198 }
199
200 class DiskFullFactory : public IndexedDBFactory {
201 public:
DiskFullFactory(IndexedDBContextImpl * context)202 explicit DiskFullFactory(IndexedDBContextImpl* context)
203 : IndexedDBFactory(context) {}
204
205 private:
~DiskFullFactory()206 virtual ~DiskFullFactory() {}
OpenBackingStore(const GURL & origin_url,const base::FilePath & data_directory,net::URLRequestContext * request_context,blink::WebIDBDataLoss * data_loss,std::string * data_loss_message,bool * disk_full)207 virtual scoped_refptr<IndexedDBBackingStore> OpenBackingStore(
208 const GURL& origin_url,
209 const base::FilePath& data_directory,
210 net::URLRequestContext* request_context,
211 blink::WebIDBDataLoss* data_loss,
212 std::string* data_loss_message,
213 bool* disk_full) OVERRIDE {
214 *disk_full = true;
215 return scoped_refptr<IndexedDBBackingStore>();
216 }
217
218 DISALLOW_COPY_AND_ASSIGN(DiskFullFactory);
219 };
220
221 class LookingForQuotaErrorMockCallbacks : public IndexedDBCallbacks {
222 public:
LookingForQuotaErrorMockCallbacks()223 LookingForQuotaErrorMockCallbacks()
224 : IndexedDBCallbacks(NULL, 0, 0), error_called_(false) {}
OnError(const IndexedDBDatabaseError & error)225 virtual void OnError(const IndexedDBDatabaseError& error) OVERRIDE {
226 error_called_ = true;
227 EXPECT_EQ(blink::WebIDBDatabaseExceptionQuotaError, error.code());
228 }
error_called() const229 bool error_called() const { return error_called_; }
230
231 private:
~LookingForQuotaErrorMockCallbacks()232 virtual ~LookingForQuotaErrorMockCallbacks() {}
233 bool error_called_;
234
235 DISALLOW_COPY_AND_ASSIGN(LookingForQuotaErrorMockCallbacks);
236 };
237
TEST_F(IndexedDBFactoryTest,QuotaErrorOnDiskFull)238 TEST_F(IndexedDBFactoryTest, QuotaErrorOnDiskFull) {
239 const GURL origin("http://localhost:81");
240 base::ScopedTempDir temp_directory;
241 ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
242
243 scoped_refptr<DiskFullFactory> factory = new DiskFullFactory(context());
244 scoped_refptr<LookingForQuotaErrorMockCallbacks> callbacks =
245 new LookingForQuotaErrorMockCallbacks;
246 scoped_refptr<IndexedDBDatabaseCallbacks> dummy_database_callbacks =
247 new IndexedDBDatabaseCallbacks(NULL, 0, 0);
248 const base::string16 name(ASCIIToUTF16("name"));
249 IndexedDBPendingConnection connection(callbacks,
250 dummy_database_callbacks,
251 0, /* child_process_id */
252 2, /* transaction_id */
253 1 /* version */);
254 factory->Open(name,
255 connection,
256 NULL /* request_context */,
257 origin,
258 temp_directory.path());
259 EXPECT_TRUE(callbacks->error_called());
260 }
261
TEST_F(IndexedDBFactoryTest,BackingStoreReleasedOnForcedClose)262 TEST_F(IndexedDBFactoryTest, BackingStoreReleasedOnForcedClose) {
263 GURL origin("http://localhost:81");
264
265 base::ScopedTempDir temp_directory;
266 ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
267
268 scoped_refptr<MockIndexedDBCallbacks> callbacks(new MockIndexedDBCallbacks());
269 scoped_refptr<MockIndexedDBDatabaseCallbacks> db_callbacks(
270 new MockIndexedDBDatabaseCallbacks());
271 const int64 transaction_id = 1;
272 IndexedDBPendingConnection connection(
273 callbacks,
274 db_callbacks,
275 0, /* child_process_id */
276 transaction_id,
277 IndexedDBDatabaseMetadata::DEFAULT_INT_VERSION);
278 factory()->Open(ASCIIToUTF16("db"),
279 connection,
280 NULL /* request_context */,
281 origin,
282 temp_directory.path());
283
284 EXPECT_TRUE(callbacks->connection());
285
286 EXPECT_TRUE(factory()->IsBackingStoreOpen(origin));
287 EXPECT_FALSE(factory()->IsBackingStorePendingClose(origin));
288
289 callbacks->connection()->ForceClose();
290
291 EXPECT_FALSE(factory()->IsBackingStoreOpen(origin));
292 EXPECT_FALSE(factory()->IsBackingStorePendingClose(origin));
293 }
294
TEST_F(IndexedDBFactoryTest,BackingStoreReleaseDelayedOnClose)295 TEST_F(IndexedDBFactoryTest, BackingStoreReleaseDelayedOnClose) {
296 GURL origin("http://localhost:81");
297
298 base::ScopedTempDir temp_directory;
299 ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
300
301 scoped_refptr<MockIndexedDBCallbacks> callbacks(new MockIndexedDBCallbacks());
302 scoped_refptr<MockIndexedDBDatabaseCallbacks> db_callbacks(
303 new MockIndexedDBDatabaseCallbacks());
304 const int64 transaction_id = 1;
305 IndexedDBPendingConnection connection(
306 callbacks,
307 db_callbacks,
308 0, /* child_process_id */
309 transaction_id,
310 IndexedDBDatabaseMetadata::DEFAULT_INT_VERSION);
311 factory()->Open(ASCIIToUTF16("db"),
312 connection,
313 NULL /* request_context */,
314 origin,
315 temp_directory.path());
316
317 EXPECT_TRUE(callbacks->connection());
318 IndexedDBBackingStore* store =
319 callbacks->connection()->database()->backing_store();
320 EXPECT_FALSE(store->HasOneRef()); // Factory and database.
321
322 EXPECT_TRUE(factory()->IsBackingStoreOpen(origin));
323 callbacks->connection()->Close();
324 EXPECT_TRUE(store->HasOneRef()); // Factory.
325 EXPECT_TRUE(factory()->IsBackingStoreOpen(origin));
326 EXPECT_TRUE(factory()->IsBackingStorePendingClose(origin));
327 EXPECT_TRUE(store->close_timer()->IsRunning());
328
329 // Take a ref so it won't be destroyed out from under the test.
330 scoped_refptr<IndexedDBBackingStore> store_ref = store;
331 // Now simulate shutdown, which should stop the timer.
332 factory()->ContextDestroyed();
333 EXPECT_TRUE(store->HasOneRef()); // Local.
334 EXPECT_FALSE(store->close_timer()->IsRunning());
335 EXPECT_FALSE(factory()->IsBackingStoreOpen(origin));
336 EXPECT_FALSE(factory()->IsBackingStorePendingClose(origin));
337 }
338
TEST_F(IndexedDBFactoryTest,DeleteDatabaseClosesBackingStore)339 TEST_F(IndexedDBFactoryTest, DeleteDatabaseClosesBackingStore) {
340 GURL origin("http://localhost:81");
341
342 base::ScopedTempDir temp_directory;
343 ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
344
345 EXPECT_FALSE(factory()->IsBackingStoreOpen(origin));
346
347 const bool expect_connection = false;
348 scoped_refptr<MockIndexedDBCallbacks> callbacks(
349 new MockIndexedDBCallbacks(expect_connection));
350 factory()->DeleteDatabase(ASCIIToUTF16("db"),
351 NULL /* request_context */,
352 callbacks,
353 origin,
354 temp_directory.path());
355
356 EXPECT_TRUE(factory()->IsBackingStoreOpen(origin));
357 EXPECT_TRUE(factory()->IsBackingStorePendingClose(origin));
358
359 // Now simulate shutdown, which should stop the timer.
360 factory()->ContextDestroyed();
361
362 EXPECT_FALSE(factory()->IsBackingStoreOpen(origin));
363 EXPECT_FALSE(factory()->IsBackingStorePendingClose(origin));
364 }
365
TEST_F(IndexedDBFactoryTest,GetDatabaseNamesClosesBackingStore)366 TEST_F(IndexedDBFactoryTest, GetDatabaseNamesClosesBackingStore) {
367 GURL origin("http://localhost:81");
368
369 base::ScopedTempDir temp_directory;
370 ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
371
372 EXPECT_FALSE(factory()->IsBackingStoreOpen(origin));
373
374 const bool expect_connection = false;
375 scoped_refptr<MockIndexedDBCallbacks> callbacks(
376 new MockIndexedDBCallbacks(expect_connection));
377 factory()->GetDatabaseNames(
378 callbacks, origin, temp_directory.path(), NULL /* request_context */);
379
380 EXPECT_TRUE(factory()->IsBackingStoreOpen(origin));
381 EXPECT_TRUE(factory()->IsBackingStorePendingClose(origin));
382
383 // Now simulate shutdown, which should stop the timer.
384 factory()->ContextDestroyed();
385
386 EXPECT_FALSE(factory()->IsBackingStoreOpen(origin));
387 EXPECT_FALSE(factory()->IsBackingStorePendingClose(origin));
388 }
389
TEST_F(IndexedDBFactoryTest,ForceCloseReleasesBackingStore)390 TEST_F(IndexedDBFactoryTest, ForceCloseReleasesBackingStore) {
391 GURL origin("http://localhost:81");
392
393 base::ScopedTempDir temp_directory;
394 ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
395
396 scoped_refptr<MockIndexedDBCallbacks> callbacks(new MockIndexedDBCallbacks());
397 scoped_refptr<MockIndexedDBDatabaseCallbacks> db_callbacks(
398 new MockIndexedDBDatabaseCallbacks());
399 const int64 transaction_id = 1;
400 IndexedDBPendingConnection connection(
401 callbacks,
402 db_callbacks,
403 0, /* child_process_id */
404 transaction_id,
405 IndexedDBDatabaseMetadata::DEFAULT_INT_VERSION);
406 factory()->Open(ASCIIToUTF16("db"),
407 connection,
408 NULL /* request_context */,
409 origin,
410 temp_directory.path());
411
412 EXPECT_TRUE(callbacks->connection());
413 EXPECT_TRUE(factory()->IsBackingStoreOpen(origin));
414 EXPECT_FALSE(factory()->IsBackingStorePendingClose(origin));
415
416 callbacks->connection()->Close();
417
418 EXPECT_TRUE(factory()->IsBackingStoreOpen(origin));
419 EXPECT_TRUE(factory()->IsBackingStorePendingClose(origin));
420
421 factory()->ForceClose(origin);
422
423 EXPECT_FALSE(factory()->IsBackingStoreOpen(origin));
424 EXPECT_FALSE(factory()->IsBackingStorePendingClose(origin));
425
426 // Ensure it is safe if the store is not open.
427 factory()->ForceClose(origin);
428 }
429
430 class UpgradeNeededCallbacks : public MockIndexedDBCallbacks {
431 public:
UpgradeNeededCallbacks()432 UpgradeNeededCallbacks() {}
433
OnSuccess(scoped_ptr<IndexedDBConnection> connection,const IndexedDBDatabaseMetadata & metadata)434 virtual void OnSuccess(scoped_ptr<IndexedDBConnection> connection,
435 const IndexedDBDatabaseMetadata& metadata) OVERRIDE {
436 EXPECT_TRUE(connection_.get());
437 EXPECT_FALSE(connection.get());
438 }
439
OnUpgradeNeeded(int64 old_version,scoped_ptr<IndexedDBConnection> connection,const content::IndexedDBDatabaseMetadata & metadata)440 virtual void OnUpgradeNeeded(
441 int64 old_version,
442 scoped_ptr<IndexedDBConnection> connection,
443 const content::IndexedDBDatabaseMetadata& metadata) OVERRIDE {
444 connection_ = connection.Pass();
445 }
446
447 protected:
~UpgradeNeededCallbacks()448 virtual ~UpgradeNeededCallbacks() {}
449
450 private:
451 DISALLOW_COPY_AND_ASSIGN(UpgradeNeededCallbacks);
452 };
453
454 class ErrorCallbacks : public MockIndexedDBCallbacks {
455 public:
ErrorCallbacks()456 ErrorCallbacks() : MockIndexedDBCallbacks(false), saw_error_(false) {}
457
OnError(const IndexedDBDatabaseError & error)458 virtual void OnError(const IndexedDBDatabaseError& error) OVERRIDE {
459 saw_error_= true;
460 }
saw_error() const461 bool saw_error() const { return saw_error_; }
462
463 private:
~ErrorCallbacks()464 virtual ~ErrorCallbacks() {}
465 bool saw_error_;
466
467 DISALLOW_COPY_AND_ASSIGN(ErrorCallbacks);
468 };
469
TEST_F(IndexedDBFactoryTest,DatabaseFailedOpen)470 TEST_F(IndexedDBFactoryTest, DatabaseFailedOpen) {
471 GURL origin("http://localhost:81");
472
473 base::ScopedTempDir temp_directory;
474 ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
475
476 const base::string16 db_name(ASCIIToUTF16("db"));
477 const int64 db_version = 2;
478 const int64 transaction_id = 1;
479 scoped_refptr<IndexedDBDatabaseCallbacks> db_callbacks(
480 new MockIndexedDBDatabaseCallbacks());
481
482 // Open at version 2, then close.
483 {
484 scoped_refptr<MockIndexedDBCallbacks> callbacks(
485 new UpgradeNeededCallbacks());
486 IndexedDBPendingConnection connection(callbacks,
487 db_callbacks,
488 0, /* child_process_id */
489 transaction_id,
490 db_version);
491 factory()->Open(db_name,
492 connection,
493 NULL /* request_context */,
494 origin,
495 temp_directory.path());
496 EXPECT_TRUE(factory()->IsDatabaseOpen(origin, db_name));
497
498 // Pump the message loop so the upgrade transaction can run.
499 base::MessageLoop::current()->RunUntilIdle();
500 EXPECT_TRUE(callbacks->connection());
501 callbacks->connection()->database()->Commit(transaction_id);
502
503 callbacks->connection()->Close();
504 EXPECT_FALSE(factory()->IsDatabaseOpen(origin, db_name));
505 }
506
507 // Open at version < 2, which will fail; ensure factory doesn't retain
508 // the database object.
509 {
510 scoped_refptr<ErrorCallbacks> callbacks(new ErrorCallbacks());
511 IndexedDBPendingConnection connection(callbacks,
512 db_callbacks,
513 0, /* child_process_id */
514 transaction_id,
515 db_version - 1);
516 factory()->Open(db_name,
517 connection,
518 NULL /* request_context */,
519 origin,
520 temp_directory.path());
521 EXPECT_TRUE(callbacks->saw_error());
522 EXPECT_FALSE(factory()->IsDatabaseOpen(origin, db_name));
523 }
524
525 // Terminate all pending-close timers.
526 factory()->ForceClose(origin);
527 }
528
529 } // namespace content
530