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