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.
5 #include "content/browser/indexed_db/indexed_db_context_impl.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"
36 using base::DictionaryValue;
37 using base::ListValue;
38 using webkit_database::DatabaseUtil;
41 const base::FilePath::CharType IndexedDBContextImpl::kIndexedDBDirectory[] =
42 FILE_PATH_LITERAL("IndexedDB");
44 static const base::FilePath::CharType kIndexedDBExtension[] =
45 FILE_PATH_LITERAL(".indexeddb");
47 static const base::FilePath::CharType kLevelDBExtension[] =
48 FILE_PATH_LITERAL(".leveldb");
52 // This may be called after the IndexedDBContext is destroyed.
53 void 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())
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));
70 file_paths->push_back(file_path);
75 // This will be called after the IndexedDBContext is destroyed.
76 void 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 =
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))
92 if (special_storage_policy->IsStorageProtected(*iter))
94 base::DeleteFile(*file_path_iter, true);
100 IndexedDBContextImpl::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));
116 IndexedDBFactory* IndexedDBContextImpl::GetIDBFactory() {
117 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
119 // Prime our cache of origins with existing databases so we can
120 // detect when dbs are newly created.
122 factory_ = new IndexedDBFactory(this);
127 std::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();
134 origins.push_back(*iter);
139 std::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();
146 const GURL& origin_url = *iter;
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),
159 static bool HostNameComparator(const GURL& i, const GURL& j) {
160 return i.host() < j.host();
163 ListValue* IndexedDBContextImpl::GetAllOriginsDetails() {
164 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
165 std::vector<GURL> origins = GetAllOrigins();
167 std::sort(origins.begin(), origins.end(), HostNameComparator);
169 scoped_ptr<ListValue> list(new ListValue());
170 for (std::vector<GURL>::const_iterator iter = origins.begin();
171 iter != origins.end();
173 const GURL& origin_url = *iter;
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));
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.
188 std::vector<IndexedDBDatabase*> databases =
189 factory_->GetOpenDatabasesForOrigin(origin_url);
190 // TODO(jsbell): Sort by name?
191 scoped_ptr<ListValue> database_list(new ListValue());
193 for (std::vector<IndexedDBDatabase*>::iterator it = databases.begin();
194 it != databases.end();
197 const IndexedDBDatabase* db = *it;
198 scoped_ptr<DictionaryValue> db_info(new DictionaryValue());
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());
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();
217 const IndexedDBTransaction* transaction = *trans_it;
218 scoped_ptr<DictionaryValue> transaction_info(new DictionaryValue());
220 const char* kModes[] = { "readonly", "readwrite", "versionchange" };
221 transaction_info->SetString("mode", kModes[transaction->mode()]);
222 switch (transaction->queue_status()) {
223 case IndexedDBTransaction::CREATED:
224 transaction_info->SetString("status", "created");
226 case IndexedDBTransaction::BLOCKED:
227 transaction_info->SetString("status", "blocked");
229 case IndexedDBTransaction::UNBLOCKED:
230 if (transaction->IsRunning())
231 transaction_info->SetString("status", "running");
233 transaction_info->SetString("status", "started");
237 transaction_info->SetDouble(
239 IndexedDBDispatcherHost::TransactionIdToProcessId(
241 transaction_info->SetDouble(
243 IndexedDBDispatcherHost::TransactionIdToRendererTransactionId(
245 transaction_info->SetDouble(
247 (base::Time::Now() - transaction->creation_time())
249 transaction_info->SetDouble(
251 (base::Time::Now() - transaction->start_time())
253 transaction_info->SetDouble("tasks_scheduled",
254 transaction->tasks_scheduled());
255 transaction_info->SetDouble("tasks_completed",
256 transaction->tasks_completed());
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();
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);
269 transaction_info->Set("scope", scope.release());
270 transaction_list->Append(transaction_info.release());
272 db_info->Set("transactions", transaction_list.release());
274 database_list->Append(db_info.release());
276 info->Set("databases", database_list.release());
279 list->Append(info.release());
281 return list.release();
284 int64 IndexedDBContextImpl::GetOriginDiskUsage(const GURL& origin_url) {
285 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
286 if (data_path_.empty() || !IsInOriginSet(origin_url))
288 EnsureDiskUsageCacheInitialized(origin_url);
289 return origin_size_map_[origin_url];
292 base::Time IndexedDBContextImpl::GetOriginLastModified(const GURL& origin_url) {
293 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
294 if (data_path_.empty() || !IsInOriginSet(origin_url))
296 base::FilePath idb_directory = GetFilePath(origin_url);
297 base::PlatformFileInfo file_info;
298 if (!file_util::GetFileInfo(idb_directory, &file_info))
300 return file_info.last_modified;
303 void IndexedDBContextImpl::DeleteForOrigin(const GURL& origin_url) {
304 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
305 ForceClose(origin_url);
306 if (data_path_.empty() || !IsInOriginSet(origin_url))
309 base::FilePath idb_directory = GetFilePath(origin_url);
310 EnsureDiskUsageCacheInitialized(origin_url);
311 bool deleted = LevelDBDatabase::Destroy(idb_directory);
313 LOG(WARNING) << "Failed to delete LevelDB database: "
314 << idb_directory.AsUTF8Unsafe();
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);
323 QueryDiskAndUpdateQuotaUsage(origin_url);
325 RemoveFromOriginSet(origin_url);
326 origin_size_map_.erase(origin_url);
327 space_available_map_.erase(origin_url);
331 void IndexedDBContextImpl::ForceClose(const GURL origin_url) {
332 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
333 if (data_path_.empty() || !IsInOriginSet(origin_url))
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();
346 DCHECK_EQ(connections_[origin_url].size(), 0UL);
347 connections_.erase(origin_url);
351 size_t IndexedDBContextImpl::GetConnectionCount(const GURL& origin_url) {
352 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
353 if (data_path_.empty() || !IsInOriginSet(origin_url))
356 if (connections_.find(origin_url) == connections_.end())
359 return connections_[origin_url].size();
362 base::FilePath IndexedDBContextImpl::GetFilePath(const GURL& origin_url) const {
363 std::string origin_id = webkit_database::GetIdentifierFromOrigin(origin_url);
364 return GetIndexedDBFilePath(origin_id);
367 base::FilePath IndexedDBContextImpl::GetFilePathForTesting(
368 const std::string& origin_id) const {
369 return GetIndexedDBFilePath(origin_id);
372 void IndexedDBContextImpl::SetTaskRunnerForTesting(
373 base::SequencedTaskRunner* task_runner) {
374 DCHECK(!task_runner_);
375 task_runner_ = task_runner;
378 void 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,
386 quota::kStorageTypeTemporary);
388 connections_[origin_url].insert(connection);
389 if (AddToOriginSet(origin_url)) {
390 // A newly created db, notify the quota system.
391 QueryDiskAndUpdateQuotaUsage(origin_url);
393 EnsureDiskUsageCacheInitialized(origin_url);
395 QueryAvailableQuota(origin_url);
398 void 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)
405 if (quota_manager_proxy()) {
406 quota_manager_proxy()->NotifyStorageAccessed(
407 quota::QuotaClient::kIndexedDatabase,
409 quota::kStorageTypeTemporary);
411 connections_[origin_url].erase(connection);
412 if (connections_[origin_url].size() == 0) {
413 QueryDiskAndUpdateQuotaUsage(origin_url);
414 connections_.erase(origin_url);
418 void 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);
425 bool 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.
431 bool over_quota = additional_bytes > space_available_map_[origin_url];
435 bool IndexedDBContextImpl::IsOverQuota(const GURL& origin_url) {
436 const int kOneAdditionalByte = 1;
437 return WouldBeOverQuota(origin_url, kOneAdditionalByte);
440 quota::QuotaManagerProxy* IndexedDBContextImpl::quota_manager_proxy() {
441 return quota_manager_proxy_;
444 IndexedDBContextImpl::~IndexedDBContextImpl() {
446 TaskRunner()->PostTask(
447 FROM_HERE, base::Bind(&IndexedDBFactory::ContextDestroyed, factory_));
451 if (data_path_.empty())
454 if (force_keep_session_state_)
457 bool has_session_only_databases =
458 special_storage_policy_ &&
459 special_storage_policy_->HasSessionOnlyOrigins();
461 // Clearning only session-only databases, and there are none.
462 if (!has_session_only_databases)
465 TaskRunner()->PostTask(
468 &ClearSessionOnlyOrigins, data_path_, special_storage_policy_));
471 base::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);
478 int64 IndexedDBContextImpl::ReadUsageFromDisk(const GURL& origin_url) const {
479 if (data_path_.empty())
481 base::FilePath file_path = GetFilePath(origin_url);
482 return base::ComputeDirectorySize(file_path);
485 void 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);
491 void 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;
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,
503 quota::kStorageTypeTemporary,
509 void IndexedDBContextImpl::GotUsageAndQuota(const GURL& origin_url,
510 quota::QuotaStatusCode status,
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.
520 TaskRunner()->PostTask(FROM_HERE,
521 base::Bind(&IndexedDBContextImpl::GotUpdatedQuota,
528 void IndexedDBContextImpl::GotUpdatedQuota(const GURL& origin_url,
531 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
532 space_available_map_[origin_url] = quota - usage;
535 void IndexedDBContextImpl::QueryAvailableQuota(const GURL& origin_url) {
536 if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
537 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
538 if (quota_manager_proxy()) {
539 BrowserThread::PostTask(
543 &IndexedDBContextImpl::QueryAvailableQuota, this, origin_url));
547 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
548 if (!quota_manager_proxy() || !quota_manager_proxy()->quota_manager())
550 quota_manager_proxy()->quota_manager()->GetUsageAndQuota(
552 quota::kStorageTypeTemporary,
553 base::Bind(&IndexedDBContextImpl::GotUsageAndQuota, this, origin_url));
556 std::set<GURL>* IndexedDBContextImpl::GetOriginSet() {
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();
564 origin_set_->insert(*iter);
567 return origin_set_.get();
570 void IndexedDBContextImpl::ResetCaches() {
572 origin_size_map_.clear();
573 space_available_map_.clear();
576 base::TaskRunner* IndexedDBContextImpl::TaskRunner() const {
580 } // namespace content