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/bind.h"
11 #include "base/command_line.h"
12 #include "base/files/file_enumerator.h"
13 #include "base/files/file_util.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_impl.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 "storage/browser/database/database_util.h"
34 #include "storage/browser/quota/quota_manager_proxy.h"
35 #include "storage/browser/quota/special_storage_policy.h"
36 #include "storage/common/database/database_identifier.h"
37 #include "ui/base/text/bytes_formatting.h"
39 using base::DictionaryValue
;
40 using base::ListValue
;
41 using storage::DatabaseUtil
;
44 const base::FilePath::CharType
IndexedDBContextImpl::kIndexedDBDirectory
[] =
45 FILE_PATH_LITERAL("IndexedDB");
47 static const base::FilePath::CharType kBlobExtension
[] =
48 FILE_PATH_LITERAL(".blob");
50 static const base::FilePath::CharType kIndexedDBExtension
[] =
51 FILE_PATH_LITERAL(".indexeddb");
53 static const base::FilePath::CharType kLevelDBExtension
[] =
54 FILE_PATH_LITERAL(".leveldb");
58 // This may be called after the IndexedDBContext is destroyed.
59 void GetAllOriginsAndPaths(const base::FilePath
& indexeddb_path
,
60 std::vector
<GURL
>* origins
,
61 std::vector
<base::FilePath
>* file_paths
) {
62 // TODO(jsbell): DCHECK that this is running on an IndexedDB thread,
63 // if a global handle to it is ever available.
64 if (indexeddb_path
.empty())
66 base::FileEnumerator
file_enumerator(
67 indexeddb_path
, false, base::FileEnumerator::DIRECTORIES
);
68 for (base::FilePath file_path
= file_enumerator
.Next(); !file_path
.empty();
69 file_path
= file_enumerator
.Next()) {
70 if (file_path
.Extension() == kLevelDBExtension
&&
71 file_path
.RemoveExtension().Extension() == kIndexedDBExtension
) {
72 std::string origin_id
= file_path
.BaseName().RemoveExtension()
73 .RemoveExtension().MaybeAsASCII();
74 origins
->push_back(storage::GetOriginFromIdentifier(origin_id
));
76 file_paths
->push_back(file_path
);
81 // This will be called after the IndexedDBContext is destroyed.
82 void ClearSessionOnlyOrigins(
83 const base::FilePath
& indexeddb_path
,
84 scoped_refptr
<storage::SpecialStoragePolicy
> special_storage_policy
) {
85 // TODO(jsbell): DCHECK that this is running on an IndexedDB thread,
86 // if a global handle to it is ever available.
87 std::vector
<GURL
> origins
;
88 std::vector
<base::FilePath
> file_paths
;
89 GetAllOriginsAndPaths(indexeddb_path
, &origins
, &file_paths
);
90 DCHECK_EQ(origins
.size(), file_paths
.size());
91 std::vector
<base::FilePath
>::const_iterator file_path_iter
=
93 for (std::vector
<GURL
>::const_iterator iter
= origins
.begin();
94 iter
!= origins
.end();
95 ++iter
, ++file_path_iter
) {
96 if (!special_storage_policy
->IsStorageSessionOnly(*iter
))
98 if (special_storage_policy
->IsStorageProtected(*iter
))
100 base::DeleteFile(*file_path_iter
, true);
106 IndexedDBContextImpl::IndexedDBContextImpl(
107 const base::FilePath
& data_path
,
108 storage::SpecialStoragePolicy
* special_storage_policy
,
109 storage::QuotaManagerProxy
* quota_manager_proxy
,
110 base::SequencedTaskRunner
* task_runner
)
111 : force_keep_session_state_(false),
112 special_storage_policy_(special_storage_policy
),
113 quota_manager_proxy_(quota_manager_proxy
),
114 task_runner_(task_runner
) {
116 if (!data_path
.empty())
117 data_path_
= data_path
.Append(kIndexedDBDirectory
);
118 if (quota_manager_proxy
) {
119 quota_manager_proxy
->RegisterClient(new IndexedDBQuotaClient(this));
123 IndexedDBFactory
* IndexedDBContextImpl::GetIDBFactory() {
124 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
125 if (!factory_
.get()) {
126 // Prime our cache of origins with existing databases so we can
127 // detect when dbs are newly created.
129 factory_
= new IndexedDBFactoryImpl(this);
131 return factory_
.get();
134 std::vector
<GURL
> IndexedDBContextImpl::GetAllOrigins() {
135 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
136 std::set
<GURL
>* origins_set
= GetOriginSet();
137 return std::vector
<GURL
>(origins_set
->begin(), origins_set
->end());
140 std::vector
<IndexedDBInfo
> IndexedDBContextImpl::GetAllOriginsInfo() {
141 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
142 std::vector
<GURL
> origins
= GetAllOrigins();
143 std::vector
<IndexedDBInfo
> result
;
144 for (const auto& origin_url
: origins
) {
145 size_t connection_count
= GetConnectionCount(origin_url
);
146 result
.push_back(IndexedDBInfo(origin_url
,
147 GetOriginDiskUsage(origin_url
),
148 GetOriginLastModified(origin_url
),
154 static bool HostNameComparator(const GURL
& i
, const GURL
& j
) {
155 return i
.host() < j
.host();
158 base::ListValue
* IndexedDBContextImpl::GetAllOriginsDetails() {
159 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
160 std::vector
<GURL
> origins
= GetAllOrigins();
162 std::sort(origins
.begin(), origins
.end(), HostNameComparator
);
164 scoped_ptr
<base::ListValue
> list(new base::ListValue());
165 for (const auto& origin_url
: origins
) {
166 scoped_ptr
<base::DictionaryValue
> info(new base::DictionaryValue());
167 info
->SetString("url", origin_url
.spec());
168 info
->SetString("size", ui::FormatBytes(GetOriginDiskUsage(origin_url
)));
169 info
->SetDouble("last_modified",
170 GetOriginLastModified(origin_url
).ToJsTime());
171 if (!is_incognito()) {
172 scoped_ptr
<base::ListValue
> paths(new base::ListValue());
173 for (const base::FilePath
& path
: GetStoragePaths(origin_url
))
174 paths
->AppendString(path
.value());
175 info
->Set("paths", paths
.release());
177 info
->SetDouble("connection_count", GetConnectionCount(origin_url
));
179 // This ends up being O(n^2) since we iterate over all open databases
180 // to extract just those in the origin, and we're iterating over all
181 // origins in the outer loop.
183 if (factory_
.get()) {
184 std::pair
<IndexedDBFactory::OriginDBMapIterator
,
185 IndexedDBFactory::OriginDBMapIterator
> range
=
186 factory_
->GetOpenDatabasesForOrigin(origin_url
);
187 // TODO(jsbell): Sort by name?
188 scoped_ptr
<base::ListValue
> database_list(new base::ListValue());
190 for (IndexedDBFactory::OriginDBMapIterator it
= range
.first
;
193 const IndexedDBDatabase
* db
= it
->second
;
194 scoped_ptr
<base::DictionaryValue
> db_info(new base::DictionaryValue());
196 db_info
->SetString("name", db
->name());
197 db_info
->SetDouble("pending_opens", db
->PendingOpenCount());
198 db_info
->SetDouble("pending_upgrades", db
->PendingUpgradeCount());
199 db_info
->SetDouble("running_upgrades", db
->RunningUpgradeCount());
200 db_info
->SetDouble("pending_deletes", db
->PendingDeleteCount());
201 db_info
->SetDouble("connection_count",
202 db
->ConnectionCount() - db
->PendingUpgradeCount() -
203 db
->RunningUpgradeCount());
205 scoped_ptr
<base::ListValue
> transaction_list(new base::ListValue());
206 std::vector
<const IndexedDBTransaction
*> transactions
=
207 db
->transaction_coordinator().GetTransactions();
208 for (const auto* transaction
: transactions
) {
209 scoped_ptr
<base::DictionaryValue
> transaction_info(
210 new base::DictionaryValue());
212 const char* kModes
[] = { "readonly", "readwrite", "versionchange" };
213 transaction_info
->SetString("mode", kModes
[transaction
->mode()]);
214 switch (transaction
->state()) {
215 case IndexedDBTransaction::CREATED
:
216 transaction_info
->SetString("status", "blocked");
218 case IndexedDBTransaction::STARTED
:
219 if (transaction
->diagnostics().tasks_scheduled
> 0)
220 transaction_info
->SetString("status", "running");
222 transaction_info
->SetString("status", "started");
224 case IndexedDBTransaction::COMMITTING
:
225 transaction_info
->SetString("status", "committing");
227 case IndexedDBTransaction::FINISHED
:
228 transaction_info
->SetString("status", "finished");
232 transaction_info
->SetDouble(
234 IndexedDBDispatcherHost::TransactionIdToProcessId(
236 transaction_info
->SetDouble(
238 IndexedDBDispatcherHost::TransactionIdToRendererTransactionId(
240 transaction_info
->SetDouble(
242 (base::Time::Now() - transaction
->diagnostics().creation_time
)
244 transaction_info
->SetDouble(
246 (base::Time::Now() - transaction
->diagnostics().start_time
)
248 transaction_info
->SetDouble(
249 "tasks_scheduled", transaction
->diagnostics().tasks_scheduled
);
250 transaction_info
->SetDouble(
251 "tasks_completed", transaction
->diagnostics().tasks_completed
);
253 scoped_ptr
<base::ListValue
> scope(new base::ListValue());
254 for (const auto& id
: transaction
->scope()) {
255 IndexedDBDatabaseMetadata::ObjectStoreMap::const_iterator it
=
256 db
->metadata().object_stores
.find(id
);
257 if (it
!= db
->metadata().object_stores
.end())
258 scope
->AppendString(it
->second
.name
);
261 transaction_info
->Set("scope", scope
.release());
262 transaction_list
->Append(transaction_info
.release());
264 db_info
->Set("transactions", transaction_list
.release());
266 database_list
->Append(db_info
.release());
268 info
->Set("databases", database_list
.release());
271 list
->Append(info
.release());
273 return list
.release();
276 int64
IndexedDBContextImpl::GetOriginDiskUsage(const GURL
& origin_url
) {
277 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
278 if (data_path_
.empty() || !IsInOriginSet(origin_url
))
280 EnsureDiskUsageCacheInitialized(origin_url
);
281 return origin_size_map_
[origin_url
];
284 base::Time
IndexedDBContextImpl::GetOriginLastModified(const GURL
& origin_url
) {
285 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
286 if (data_path_
.empty() || !IsInOriginSet(origin_url
))
288 base::FilePath idb_directory
= GetLevelDBPath(origin_url
);
289 base::File::Info file_info
;
290 if (!base::GetFileInfo(idb_directory
, &file_info
))
292 return file_info
.last_modified
;
295 void IndexedDBContextImpl::DeleteForOrigin(const GURL
& origin_url
) {
296 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
297 ForceClose(origin_url
, FORCE_CLOSE_DELETE_ORIGIN
);
298 if (data_path_
.empty() || !IsInOriginSet(origin_url
))
301 base::FilePath idb_directory
= GetLevelDBPath(origin_url
);
302 EnsureDiskUsageCacheInitialized(origin_url
);
303 leveldb::Status s
= LevelDBDatabase::Destroy(idb_directory
);
305 LOG(WARNING
) << "Failed to delete LevelDB database: "
306 << idb_directory
.AsUTF8Unsafe();
308 // LevelDB does not delete empty directories; work around this.
309 // TODO(jsbell): Remove when upstream bug is fixed.
310 // https://code.google.com/p/leveldb/issues/detail?id=209
311 const bool kNonRecursive
= false;
312 base::DeleteFile(idb_directory
, kNonRecursive
);
314 base::DeleteFile(GetBlobPath(storage::GetIdentifierFromOrigin(origin_url
)),
315 true /* recursive */);
316 QueryDiskAndUpdateQuotaUsage(origin_url
);
318 RemoveFromOriginSet(origin_url
);
319 origin_size_map_
.erase(origin_url
);
320 space_available_map_
.erase(origin_url
);
324 void IndexedDBContextImpl::CopyOriginData(const GURL
& origin_url
,
325 IndexedDBContext
* dest_context
) {
326 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
328 if (data_path_
.empty() || !IsInOriginSet(origin_url
))
331 IndexedDBContextImpl
* dest_context_impl
=
332 static_cast<IndexedDBContextImpl
*>(dest_context
);
334 ForceClose(origin_url
, FORCE_CLOSE_COPY_ORIGIN
);
335 std::string origin_id
= storage::GetIdentifierFromOrigin(origin_url
);
337 // Make sure we're not about to delete our own database.
338 CHECK_NE(dest_context_impl
->data_path().value(), data_path().value());
340 // Delete any existing storage paths in the destination context.
341 // A previously failed migration may have left behind partially copied
343 for (const base::FilePath
& dest_path
:
344 dest_context_impl
->GetStoragePaths(origin_url
))
345 base::DeleteFile(dest_path
, true);
347 base::FilePath dest_data_path
= dest_context_impl
->data_path();
348 base::CreateDirectory(dest_data_path
);
350 for (const base::FilePath
& src_data_path
: GetStoragePaths(origin_url
)) {
351 if (base::PathExists(src_data_path
)) {
352 base::CopyDirectory(src_data_path
, dest_data_path
, true);
357 void IndexedDBContextImpl::ForceClose(const GURL origin_url
,
358 ForceCloseReason reason
) {
359 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
360 UMA_HISTOGRAM_ENUMERATION("WebCore.IndexedDB.Context.ForceCloseReason",
362 FORCE_CLOSE_REASON_MAX
);
364 if (data_path_
.empty() || !IsInOriginSet(origin_url
))
368 factory_
->ForceClose(origin_url
);
369 DCHECK_EQ(0UL, GetConnectionCount(origin_url
));
372 size_t IndexedDBContextImpl::GetConnectionCount(const GURL
& origin_url
) {
373 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
374 if (data_path_
.empty() || !IsInOriginSet(origin_url
))
380 return factory_
->GetConnectionCount(origin_url
);
383 base::FilePath
IndexedDBContextImpl::GetLevelDBPath(
384 const GURL
& origin_url
) const {
385 std::string origin_id
= storage::GetIdentifierFromOrigin(origin_url
);
386 return GetLevelDBPath(origin_id
);
389 std::vector
<base::FilePath
> IndexedDBContextImpl::GetStoragePaths(
390 const GURL
& origin_url
) const {
391 std::string origin_id
= storage::GetIdentifierFromOrigin(origin_url
);
392 std::vector
<base::FilePath
> paths
;
393 paths
.push_back(GetLevelDBPath(origin_id
));
394 paths
.push_back(GetBlobPath(origin_id
));
398 base::FilePath
IndexedDBContextImpl::GetFilePathForTesting(
399 const std::string
& origin_id
) const {
400 return GetLevelDBPath(origin_id
);
403 void IndexedDBContextImpl::SetTaskRunnerForTesting(
404 base::SequencedTaskRunner
* task_runner
) {
405 DCHECK(!task_runner_
.get());
406 task_runner_
= task_runner
;
409 void IndexedDBContextImpl::ConnectionOpened(const GURL
& origin_url
,
410 IndexedDBConnection
* connection
) {
411 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
412 if (quota_manager_proxy()) {
413 quota_manager_proxy()->NotifyStorageAccessed(
414 storage::QuotaClient::kIndexedDatabase
,
416 storage::kStorageTypeTemporary
);
418 if (AddToOriginSet(origin_url
)) {
419 // A newly created db, notify the quota system.
420 QueryDiskAndUpdateQuotaUsage(origin_url
);
422 EnsureDiskUsageCacheInitialized(origin_url
);
424 QueryAvailableQuota(origin_url
);
427 void IndexedDBContextImpl::ConnectionClosed(const GURL
& origin_url
,
428 IndexedDBConnection
* connection
) {
429 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
430 if (quota_manager_proxy()) {
431 quota_manager_proxy()->NotifyStorageAccessed(
432 storage::QuotaClient::kIndexedDatabase
,
434 storage::kStorageTypeTemporary
);
436 if (factory_
.get() && factory_
->GetConnectionCount(origin_url
) == 0)
437 QueryDiskAndUpdateQuotaUsage(origin_url
);
440 void IndexedDBContextImpl::TransactionComplete(const GURL
& origin_url
) {
441 DCHECK(!factory_
.get() || factory_
->GetConnectionCount(origin_url
) > 0);
442 QueryDiskAndUpdateQuotaUsage(origin_url
);
443 QueryAvailableQuota(origin_url
);
446 void IndexedDBContextImpl::DatabaseDeleted(const GURL
& origin_url
) {
447 AddToOriginSet(origin_url
);
448 QueryDiskAndUpdateQuotaUsage(origin_url
);
449 QueryAvailableQuota(origin_url
);
452 bool IndexedDBContextImpl::WouldBeOverQuota(const GURL
& origin_url
,
453 int64 additional_bytes
) {
454 if (space_available_map_
.find(origin_url
) == space_available_map_
.end()) {
455 // We haven't heard back from the QuotaManager yet, just let it through.
458 bool over_quota
= additional_bytes
> space_available_map_
[origin_url
];
462 bool IndexedDBContextImpl::IsOverQuota(const GURL
& origin_url
) {
463 const int kOneAdditionalByte
= 1;
464 return WouldBeOverQuota(origin_url
, kOneAdditionalByte
);
467 storage::QuotaManagerProxy
* IndexedDBContextImpl::quota_manager_proxy() {
468 return quota_manager_proxy_
.get();
471 IndexedDBContextImpl::~IndexedDBContextImpl() {
472 if (factory_
.get()) {
473 TaskRunner()->PostTask(
474 FROM_HERE
, base::Bind(&IndexedDBFactory::ContextDestroyed
, factory_
));
478 if (data_path_
.empty())
481 if (force_keep_session_state_
)
484 bool has_session_only_databases
=
485 special_storage_policy_
.get() &&
486 special_storage_policy_
->HasSessionOnlyOrigins();
488 // Clearing only session-only databases, and there are none.
489 if (!has_session_only_databases
)
492 TaskRunner()->PostTask(
495 &ClearSessionOnlyOrigins
, data_path_
, special_storage_policy_
));
498 base::FilePath
IndexedDBContextImpl::GetBlobPath(
499 const std::string
& origin_id
) const {
500 DCHECK(!data_path_
.empty());
501 return data_path_
.AppendASCII(origin_id
).AddExtension(kIndexedDBExtension
)
502 .AddExtension(kBlobExtension
);
505 base::FilePath
IndexedDBContextImpl::GetLevelDBPath(
506 const std::string
& origin_id
) const {
507 DCHECK(!data_path_
.empty());
508 return data_path_
.AppendASCII(origin_id
).AddExtension(kIndexedDBExtension
)
509 .AddExtension(kLevelDBExtension
);
512 int64
IndexedDBContextImpl::ReadUsageFromDisk(const GURL
& origin_url
) const {
513 if (data_path_
.empty())
515 int64 total_size
= 0;
516 for (const base::FilePath
& path
: GetStoragePaths(origin_url
))
517 total_size
+= base::ComputeDirectorySize(path
);
521 void IndexedDBContextImpl::EnsureDiskUsageCacheInitialized(
522 const GURL
& origin_url
) {
523 if (origin_size_map_
.find(origin_url
) == origin_size_map_
.end())
524 origin_size_map_
[origin_url
] = ReadUsageFromDisk(origin_url
);
527 void IndexedDBContextImpl::QueryDiskAndUpdateQuotaUsage(
528 const GURL
& origin_url
) {
529 int64 former_disk_usage
= origin_size_map_
[origin_url
];
530 int64 current_disk_usage
= ReadUsageFromDisk(origin_url
);
531 int64 difference
= current_disk_usage
- former_disk_usage
;
533 origin_size_map_
[origin_url
] = current_disk_usage
;
534 // quota_manager_proxy() is NULL in unit tests.
535 if (quota_manager_proxy()) {
536 quota_manager_proxy()->NotifyStorageModified(
537 storage::QuotaClient::kIndexedDatabase
,
539 storage::kStorageTypeTemporary
,
545 void IndexedDBContextImpl::GotUsageAndQuota(const GURL
& origin_url
,
546 storage::QuotaStatusCode status
,
549 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
550 DCHECK(status
== storage::kQuotaStatusOk
||
551 status
== storage::kQuotaErrorAbort
)
552 << "status was " << status
;
553 if (status
== storage::kQuotaErrorAbort
) {
554 // We seem to no longer care to wait around for the answer.
557 TaskRunner()->PostTask(FROM_HERE
,
558 base::Bind(&IndexedDBContextImpl::GotUpdatedQuota
,
565 void IndexedDBContextImpl::GotUpdatedQuota(const GURL
& origin_url
,
568 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
569 space_available_map_
[origin_url
] = quota
- usage
;
572 void IndexedDBContextImpl::QueryAvailableQuota(const GURL
& origin_url
) {
573 if (!BrowserThread::CurrentlyOn(BrowserThread::IO
)) {
574 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
575 if (quota_manager_proxy()) {
576 BrowserThread::PostTask(
580 &IndexedDBContextImpl::QueryAvailableQuota
, this, origin_url
));
584 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
585 if (!quota_manager_proxy() || !quota_manager_proxy()->quota_manager())
587 quota_manager_proxy()->quota_manager()->GetUsageAndQuota(
589 storage::kStorageTypeTemporary
,
590 base::Bind(&IndexedDBContextImpl::GotUsageAndQuota
, this, origin_url
));
593 std::set
<GURL
>* IndexedDBContextImpl::GetOriginSet() {
595 std::vector
<GURL
> origins
;
596 GetAllOriginsAndPaths(data_path_
, &origins
, NULL
);
597 origin_set_
.reset(new std::set
<GURL
>(origins
.begin(), origins
.end()));
599 return origin_set_
.get();
602 void IndexedDBContextImpl::ResetCaches() {
604 origin_size_map_
.clear();
605 space_available_map_
.clear();
608 base::SequencedTaskRunner
* IndexedDBContextImpl::TaskRunner() const {
609 return task_runner_
.get();
612 } // namespace content