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