indexed_db_context_impl.cc revision 116680a4aac90f2aa7413d9095a592090648e557
1// Copyright (c) 2012 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_context_impl.h"
6
7#include <algorithm>
8#include <utility>
9
10#include "base/bind.h"
11#include "base/command_line.h"
12#include "base/file_util.h"
13#include "base/files/file_enumerator.h"
14#include "base/logging.h"
15#include "base/metrics/histogram.h"
16#include "base/sequenced_task_runner.h"
17#include "base/strings/string_util.h"
18#include "base/strings/utf_string_conversions.h"
19#include "base/threading/thread_restrictions.h"
20#include "base/time/time.h"
21#include "base/values.h"
22#include "content/browser/browser_main_loop.h"
23#include "content/browser/indexed_db/indexed_db_connection.h"
24#include "content/browser/indexed_db/indexed_db_database.h"
25#include "content/browser/indexed_db/indexed_db_dispatcher_host.h"
26#include "content/browser/indexed_db/indexed_db_factory.h"
27#include "content/browser/indexed_db/indexed_db_quota_client.h"
28#include "content/browser/indexed_db/indexed_db_tracing.h"
29#include "content/browser/indexed_db/indexed_db_transaction.h"
30#include "content/public/browser/browser_thread.h"
31#include "content/public/browser/indexed_db_info.h"
32#include "content/public/common/content_switches.h"
33#include "ui/base/text/bytes_formatting.h"
34#include "webkit/browser/database/database_util.h"
35#include "webkit/browser/quota/quota_manager_proxy.h"
36#include "webkit/browser/quota/special_storage_policy.h"
37#include "webkit/common/database/database_identifier.h"
38
39using base::DictionaryValue;
40using base::ListValue;
41using webkit_database::DatabaseUtil;
42
43namespace content {
44const base::FilePath::CharType IndexedDBContextImpl::kIndexedDBDirectory[] =
45    FILE_PATH_LITERAL("IndexedDB");
46
47static const base::FilePath::CharType kIndexedDBExtension[] =
48    FILE_PATH_LITERAL(".indexeddb");
49
50static const base::FilePath::CharType kLevelDBExtension[] =
51    FILE_PATH_LITERAL(".leveldb");
52
53namespace {
54
55// This may be called after the IndexedDBContext is destroyed.
56void GetAllOriginsAndPaths(const base::FilePath& indexeddb_path,
57                           std::vector<GURL>* origins,
58                           std::vector<base::FilePath>* file_paths) {
59  // TODO(jsbell): DCHECK that this is running on an IndexedDB thread,
60  // if a global handle to it is ever available.
61  if (indexeddb_path.empty())
62    return;
63  base::FileEnumerator file_enumerator(
64      indexeddb_path, false, base::FileEnumerator::DIRECTORIES);
65  for (base::FilePath file_path = file_enumerator.Next(); !file_path.empty();
66       file_path = file_enumerator.Next()) {
67    if (file_path.Extension() == kLevelDBExtension &&
68        file_path.RemoveExtension().Extension() == kIndexedDBExtension) {
69      std::string origin_id = file_path.BaseName().RemoveExtension()
70          .RemoveExtension().MaybeAsASCII();
71      origins->push_back(webkit_database::GetOriginFromIdentifier(origin_id));
72      if (file_paths)
73        file_paths->push_back(file_path);
74    }
75  }
76}
77
78// This will be called after the IndexedDBContext is destroyed.
79void ClearSessionOnlyOrigins(
80    const base::FilePath& indexeddb_path,
81    scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy) {
82  // TODO(jsbell): DCHECK that this is running on an IndexedDB thread,
83  // if a global handle to it is ever available.
84  std::vector<GURL> origins;
85  std::vector<base::FilePath> file_paths;
86  GetAllOriginsAndPaths(indexeddb_path, &origins, &file_paths);
87  DCHECK_EQ(origins.size(), file_paths.size());
88  std::vector<base::FilePath>::const_iterator file_path_iter =
89      file_paths.begin();
90  for (std::vector<GURL>::const_iterator iter = origins.begin();
91       iter != origins.end();
92       ++iter, ++file_path_iter) {
93    if (!special_storage_policy->IsStorageSessionOnly(*iter))
94      continue;
95    if (special_storage_policy->IsStorageProtected(*iter))
96      continue;
97    base::DeleteFile(*file_path_iter, true);
98  }
99}
100
101}  // namespace
102
103IndexedDBContextImpl::IndexedDBContextImpl(
104    const base::FilePath& data_path,
105    quota::SpecialStoragePolicy* special_storage_policy,
106    quota::QuotaManagerProxy* quota_manager_proxy,
107    base::SequencedTaskRunner* task_runner)
108    : force_keep_session_state_(false),
109      special_storage_policy_(special_storage_policy),
110      quota_manager_proxy_(quota_manager_proxy),
111      task_runner_(task_runner) {
112  IDB_TRACE("init");
113  if (!data_path.empty())
114    data_path_ = data_path.Append(kIndexedDBDirectory);
115  if (quota_manager_proxy) {
116    quota_manager_proxy->RegisterClient(new IndexedDBQuotaClient(this));
117  }
118}
119
120IndexedDBFactory* IndexedDBContextImpl::GetIDBFactory() {
121  DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
122  if (!factory_) {
123    // Prime our cache of origins with existing databases so we can
124    // detect when dbs are newly created.
125    GetOriginSet();
126    factory_ = new IndexedDBFactory(this);
127  }
128  return factory_;
129}
130
131std::vector<GURL> IndexedDBContextImpl::GetAllOrigins() {
132  DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
133  std::vector<GURL> origins;
134  std::set<GURL>* origins_set = GetOriginSet();
135  for (std::set<GURL>::const_iterator iter = origins_set->begin();
136       iter != origins_set->end();
137       ++iter) {
138    origins.push_back(*iter);
139  }
140  return origins;
141}
142
143std::vector<IndexedDBInfo> IndexedDBContextImpl::GetAllOriginsInfo() {
144  DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
145  std::vector<GURL> origins = GetAllOrigins();
146  std::vector<IndexedDBInfo> result;
147  for (std::vector<GURL>::const_iterator iter = origins.begin();
148       iter != origins.end();
149       ++iter) {
150    const GURL& origin_url = *iter;
151
152    base::FilePath idb_directory = GetFilePath(origin_url);
153    size_t connection_count = GetConnectionCount(origin_url);
154    result.push_back(IndexedDBInfo(origin_url,
155                                   GetOriginDiskUsage(origin_url),
156                                   GetOriginLastModified(origin_url),
157                                   idb_directory,
158                                   connection_count));
159  }
160  return result;
161}
162
163static bool HostNameComparator(const GURL& i, const GURL& j) {
164  return i.host() < j.host();
165}
166
167base::ListValue* IndexedDBContextImpl::GetAllOriginsDetails() {
168  DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
169  std::vector<GURL> origins = GetAllOrigins();
170
171  std::sort(origins.begin(), origins.end(), HostNameComparator);
172
173  scoped_ptr<base::ListValue> list(new base::ListValue());
174  for (std::vector<GURL>::const_iterator iter = origins.begin();
175       iter != origins.end();
176       ++iter) {
177    const GURL& origin_url = *iter;
178
179    scoped_ptr<base::DictionaryValue> info(new base::DictionaryValue());
180    info->SetString("url", origin_url.spec());
181    info->SetString("size", ui::FormatBytes(GetOriginDiskUsage(origin_url)));
182    info->SetDouble("last_modified",
183                    GetOriginLastModified(origin_url).ToJsTime());
184    if (!is_incognito())
185      info->SetString("path", GetFilePath(origin_url).value());
186    info->SetDouble("connection_count", GetConnectionCount(origin_url));
187
188    // This ends up being O(n^2) since we iterate over all open databases
189    // to extract just those in the origin, and we're iterating over all
190    // origins in the outer loop.
191
192    if (factory_) {
193      std::pair<IndexedDBFactory::OriginDBMapIterator,
194                IndexedDBFactory::OriginDBMapIterator> range =
195          factory_->GetOpenDatabasesForOrigin(origin_url);
196      // TODO(jsbell): Sort by name?
197      scoped_ptr<base::ListValue> database_list(new base::ListValue());
198
199      for (IndexedDBFactory::OriginDBMapIterator it = range.first;
200           it != range.second;
201           ++it) {
202        const IndexedDBDatabase* db = it->second;
203        scoped_ptr<base::DictionaryValue> db_info(new base::DictionaryValue());
204
205        db_info->SetString("name", db->name());
206        db_info->SetDouble("pending_opens", db->PendingOpenCount());
207        db_info->SetDouble("pending_upgrades", db->PendingUpgradeCount());
208        db_info->SetDouble("running_upgrades", db->RunningUpgradeCount());
209        db_info->SetDouble("pending_deletes", db->PendingDeleteCount());
210        db_info->SetDouble("connection_count",
211                           db->ConnectionCount() - db->PendingUpgradeCount() -
212                               db->RunningUpgradeCount());
213
214        scoped_ptr<base::ListValue> transaction_list(new base::ListValue());
215        std::vector<const IndexedDBTransaction*> transactions =
216            db->transaction_coordinator().GetTransactions();
217        for (std::vector<const IndexedDBTransaction*>::iterator trans_it =
218                 transactions.begin();
219             trans_it != transactions.end();
220             ++trans_it) {
221          const IndexedDBTransaction* transaction = *trans_it;
222          scoped_ptr<base::DictionaryValue> transaction_info(
223              new base::DictionaryValue());
224
225          const char* kModes[] = { "readonly", "readwrite", "versionchange" };
226          transaction_info->SetString("mode", kModes[transaction->mode()]);
227          switch (transaction->state()) {
228            case IndexedDBTransaction::CREATED:
229              transaction_info->SetString("status", "blocked");
230              break;
231            case IndexedDBTransaction::STARTED:
232              if (transaction->diagnostics().tasks_scheduled > 0)
233                transaction_info->SetString("status", "running");
234              else
235                transaction_info->SetString("status", "started");
236              break;
237            case IndexedDBTransaction::COMMITTING:
238              transaction_info->SetString("status", "committing");
239              break;
240            case IndexedDBTransaction::FINISHED:
241              transaction_info->SetString("status", "finished");
242              break;
243          }
244
245          transaction_info->SetDouble(
246              "pid",
247              IndexedDBDispatcherHost::TransactionIdToProcessId(
248                  transaction->id()));
249          transaction_info->SetDouble(
250              "tid",
251              IndexedDBDispatcherHost::TransactionIdToRendererTransactionId(
252                  transaction->id()));
253          transaction_info->SetDouble(
254              "age",
255              (base::Time::Now() - transaction->diagnostics().creation_time)
256                  .InMillisecondsF());
257          transaction_info->SetDouble(
258              "runtime",
259              (base::Time::Now() - transaction->diagnostics().start_time)
260                  .InMillisecondsF());
261          transaction_info->SetDouble(
262              "tasks_scheduled", transaction->diagnostics().tasks_scheduled);
263          transaction_info->SetDouble(
264              "tasks_completed", transaction->diagnostics().tasks_completed);
265
266          scoped_ptr<base::ListValue> scope(new base::ListValue());
267          for (std::set<int64>::const_iterator scope_it =
268                   transaction->scope().begin();
269               scope_it != transaction->scope().end();
270               ++scope_it) {
271            IndexedDBDatabaseMetadata::ObjectStoreMap::const_iterator it =
272                db->metadata().object_stores.find(*scope_it);
273            if (it != db->metadata().object_stores.end())
274              scope->AppendString(it->second.name);
275          }
276
277          transaction_info->Set("scope", scope.release());
278          transaction_list->Append(transaction_info.release());
279        }
280        db_info->Set("transactions", transaction_list.release());
281
282        database_list->Append(db_info.release());
283      }
284      info->Set("databases", database_list.release());
285    }
286
287    list->Append(info.release());
288  }
289  return list.release();
290}
291
292int64 IndexedDBContextImpl::GetOriginDiskUsage(const GURL& origin_url) {
293  DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
294  if (data_path_.empty() || !IsInOriginSet(origin_url))
295    return 0;
296  EnsureDiskUsageCacheInitialized(origin_url);
297  return origin_size_map_[origin_url];
298}
299
300base::Time IndexedDBContextImpl::GetOriginLastModified(const GURL& origin_url) {
301  DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
302  if (data_path_.empty() || !IsInOriginSet(origin_url))
303    return base::Time();
304  base::FilePath idb_directory = GetFilePath(origin_url);
305  base::File::Info file_info;
306  if (!base::GetFileInfo(idb_directory, &file_info))
307    return base::Time();
308  return file_info.last_modified;
309}
310
311void IndexedDBContextImpl::DeleteForOrigin(const GURL& origin_url) {
312  DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
313  ForceClose(origin_url, FORCE_CLOSE_DELETE_ORIGIN);
314  if (data_path_.empty() || !IsInOriginSet(origin_url))
315    return;
316
317  base::FilePath idb_directory = GetFilePath(origin_url);
318  EnsureDiskUsageCacheInitialized(origin_url);
319  leveldb::Status s = LevelDBDatabase::Destroy(idb_directory);
320  if (!s.ok()) {
321    LOG(WARNING) << "Failed to delete LevelDB database: "
322                 << idb_directory.AsUTF8Unsafe();
323  } else {
324    // LevelDB does not delete empty directories; work around this.
325    // TODO(jsbell): Remove when upstream bug is fixed.
326    // https://code.google.com/p/leveldb/issues/detail?id=209
327    const bool kNonRecursive = false;
328    base::DeleteFile(idb_directory, kNonRecursive);
329  }
330
331  QueryDiskAndUpdateQuotaUsage(origin_url);
332  if (s.ok()) {
333    RemoveFromOriginSet(origin_url);
334    origin_size_map_.erase(origin_url);
335    space_available_map_.erase(origin_url);
336  }
337}
338
339void IndexedDBContextImpl::ForceClose(const GURL origin_url,
340                                      ForceCloseReason reason) {
341  DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
342  UMA_HISTOGRAM_ENUMERATION("WebCore.IndexedDB.Context.ForceCloseReason",
343                            reason,
344                            FORCE_CLOSE_REASON_MAX);
345
346  if (data_path_.empty() || !IsInOriginSet(origin_url))
347    return;
348
349  if (factory_)
350    factory_->ForceClose(origin_url);
351  DCHECK_EQ(0UL, GetConnectionCount(origin_url));
352}
353
354size_t IndexedDBContextImpl::GetConnectionCount(const GURL& origin_url) {
355  DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
356  if (data_path_.empty() || !IsInOriginSet(origin_url))
357    return 0;
358
359  if (!factory_)
360    return 0;
361
362  return factory_->GetConnectionCount(origin_url);
363}
364
365base::FilePath IndexedDBContextImpl::GetFilePath(const GURL& origin_url) const {
366  std::string origin_id = webkit_database::GetIdentifierFromOrigin(origin_url);
367  return GetIndexedDBFilePath(origin_id);
368}
369
370base::FilePath IndexedDBContextImpl::GetFilePathForTesting(
371    const std::string& origin_id) const {
372  return GetIndexedDBFilePath(origin_id);
373}
374
375void IndexedDBContextImpl::SetTaskRunnerForTesting(
376    base::SequencedTaskRunner* task_runner) {
377  DCHECK(!task_runner_);
378  task_runner_ = task_runner;
379}
380
381void IndexedDBContextImpl::ConnectionOpened(const GURL& origin_url,
382                                            IndexedDBConnection* connection) {
383  DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
384  if (quota_manager_proxy()) {
385    quota_manager_proxy()->NotifyStorageAccessed(
386        quota::QuotaClient::kIndexedDatabase,
387        origin_url,
388        quota::kStorageTypeTemporary);
389  }
390  if (AddToOriginSet(origin_url)) {
391    // A newly created db, notify the quota system.
392    QueryDiskAndUpdateQuotaUsage(origin_url);
393  } else {
394    EnsureDiskUsageCacheInitialized(origin_url);
395  }
396  QueryAvailableQuota(origin_url);
397}
398
399void IndexedDBContextImpl::ConnectionClosed(const GURL& origin_url,
400                                            IndexedDBConnection* connection) {
401  DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
402  if (quota_manager_proxy()) {
403    quota_manager_proxy()->NotifyStorageAccessed(
404        quota::QuotaClient::kIndexedDatabase,
405        origin_url,
406        quota::kStorageTypeTemporary);
407  }
408  if (factory_ && factory_->GetConnectionCount(origin_url) == 0)
409    QueryDiskAndUpdateQuotaUsage(origin_url);
410}
411
412void IndexedDBContextImpl::TransactionComplete(const GURL& origin_url) {
413  DCHECK(!factory_ || factory_->GetConnectionCount(origin_url) > 0);
414  QueryDiskAndUpdateQuotaUsage(origin_url);
415  QueryAvailableQuota(origin_url);
416}
417
418void IndexedDBContextImpl::DatabaseDeleted(const GURL& origin_url) {
419  AddToOriginSet(origin_url);
420  QueryDiskAndUpdateQuotaUsage(origin_url);
421  QueryAvailableQuota(origin_url);
422}
423
424bool IndexedDBContextImpl::WouldBeOverQuota(const GURL& origin_url,
425                                            int64 additional_bytes) {
426  if (space_available_map_.find(origin_url) == space_available_map_.end()) {
427    // We haven't heard back from the QuotaManager yet, just let it through.
428    return false;
429  }
430  bool over_quota = additional_bytes > space_available_map_[origin_url];
431  return over_quota;
432}
433
434bool IndexedDBContextImpl::IsOverQuota(const GURL& origin_url) {
435  const int kOneAdditionalByte = 1;
436  return WouldBeOverQuota(origin_url, kOneAdditionalByte);
437}
438
439quota::QuotaManagerProxy* IndexedDBContextImpl::quota_manager_proxy() {
440  return quota_manager_proxy_;
441}
442
443IndexedDBContextImpl::~IndexedDBContextImpl() {
444  if (factory_) {
445    TaskRunner()->PostTask(
446        FROM_HERE, base::Bind(&IndexedDBFactory::ContextDestroyed, factory_));
447    factory_ = NULL;
448  }
449
450  if (data_path_.empty())
451    return;
452
453  if (force_keep_session_state_)
454    return;
455
456  bool has_session_only_databases =
457      special_storage_policy_ &&
458      special_storage_policy_->HasSessionOnlyOrigins();
459
460  // Clearing only session-only databases, and there are none.
461  if (!has_session_only_databases)
462    return;
463
464  TaskRunner()->PostTask(
465      FROM_HERE,
466      base::Bind(
467          &ClearSessionOnlyOrigins, data_path_, special_storage_policy_));
468}
469
470base::FilePath IndexedDBContextImpl::GetIndexedDBFilePath(
471    const std::string& origin_id) const {
472  DCHECK(!data_path_.empty());
473  return data_path_.AppendASCII(origin_id).AddExtension(kIndexedDBExtension)
474      .AddExtension(kLevelDBExtension);
475}
476
477int64 IndexedDBContextImpl::ReadUsageFromDisk(const GURL& origin_url) const {
478  if (data_path_.empty())
479    return 0;
480  base::FilePath file_path = GetFilePath(origin_url);
481  return base::ComputeDirectorySize(file_path);
482}
483
484void IndexedDBContextImpl::EnsureDiskUsageCacheInitialized(
485    const GURL& origin_url) {
486  if (origin_size_map_.find(origin_url) == origin_size_map_.end())
487    origin_size_map_[origin_url] = ReadUsageFromDisk(origin_url);
488}
489
490void IndexedDBContextImpl::QueryDiskAndUpdateQuotaUsage(
491    const GURL& origin_url) {
492  int64 former_disk_usage = origin_size_map_[origin_url];
493  int64 current_disk_usage = ReadUsageFromDisk(origin_url);
494  int64 difference = current_disk_usage - former_disk_usage;
495  if (difference) {
496    origin_size_map_[origin_url] = current_disk_usage;
497    // quota_manager_proxy() is NULL in unit tests.
498    if (quota_manager_proxy()) {
499      quota_manager_proxy()->NotifyStorageModified(
500          quota::QuotaClient::kIndexedDatabase,
501          origin_url,
502          quota::kStorageTypeTemporary,
503          difference);
504    }
505  }
506}
507
508void IndexedDBContextImpl::GotUsageAndQuota(const GURL& origin_url,
509                                            quota::QuotaStatusCode status,
510                                            int64 usage,
511                                            int64 quota) {
512  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
513  DCHECK(status == quota::kQuotaStatusOk || status == quota::kQuotaErrorAbort)
514      << "status was " << status;
515  if (status == quota::kQuotaErrorAbort) {
516    // We seem to no longer care to wait around for the answer.
517    return;
518  }
519  TaskRunner()->PostTask(FROM_HERE,
520                         base::Bind(&IndexedDBContextImpl::GotUpdatedQuota,
521                                    this,
522                                    origin_url,
523                                    usage,
524                                    quota));
525}
526
527void IndexedDBContextImpl::GotUpdatedQuota(const GURL& origin_url,
528                                           int64 usage,
529                                           int64 quota) {
530  DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
531  space_available_map_[origin_url] = quota - usage;
532}
533
534void IndexedDBContextImpl::QueryAvailableQuota(const GURL& origin_url) {
535  if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
536    DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
537    if (quota_manager_proxy()) {
538      BrowserThread::PostTask(
539          BrowserThread::IO,
540          FROM_HERE,
541          base::Bind(
542              &IndexedDBContextImpl::QueryAvailableQuota, this, origin_url));
543    }
544    return;
545  }
546  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
547  if (!quota_manager_proxy() || !quota_manager_proxy()->quota_manager())
548    return;
549  quota_manager_proxy()->quota_manager()->GetUsageAndQuota(
550      origin_url,
551      quota::kStorageTypeTemporary,
552      base::Bind(&IndexedDBContextImpl::GotUsageAndQuota, this, origin_url));
553}
554
555std::set<GURL>* IndexedDBContextImpl::GetOriginSet() {
556  if (!origin_set_) {
557    origin_set_.reset(new std::set<GURL>);
558    std::vector<GURL> origins;
559    GetAllOriginsAndPaths(data_path_, &origins, NULL);
560    for (std::vector<GURL>::const_iterator iter = origins.begin();
561         iter != origins.end();
562         ++iter) {
563      origin_set_->insert(*iter);
564    }
565  }
566  return origin_set_.get();
567}
568
569void IndexedDBContextImpl::ResetCaches() {
570  origin_set_.reset();
571  origin_size_map_.clear();
572  space_available_map_.clear();
573}
574
575base::TaskRunner* IndexedDBContextImpl::TaskRunner() const {
576  return task_runner_;
577}
578
579}  // namespace content
580