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/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_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 "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"
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 kIndexedDBExtension
[] =
48 FILE_PATH_LITERAL(".indexeddb");
50 static const base::FilePath::CharType kLevelDBExtension
[] =
51 FILE_PATH_LITERAL(".leveldb");
55 // This may be called after the IndexedDBContext is destroyed.
56 void 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())
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(storage::GetOriginFromIdentifier(origin_id
));
73 file_paths
->push_back(file_path
);
78 // This will be called after the IndexedDBContext is destroyed.
79 void ClearSessionOnlyOrigins(
80 const base::FilePath
& indexeddb_path
,
81 scoped_refptr
<storage::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
=
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
))
95 if (special_storage_policy
->IsStorageProtected(*iter
))
97 base::DeleteFile(*file_path_iter
, true);
103 IndexedDBContextImpl::IndexedDBContextImpl(
104 const base::FilePath
& data_path
,
105 storage::SpecialStoragePolicy
* special_storage_policy
,
106 storage::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
) {
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));
120 IndexedDBFactory
* IndexedDBContextImpl::GetIDBFactory() {
121 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
122 if (!factory_
.get()) {
123 // Prime our cache of origins with existing databases so we can
124 // detect when dbs are newly created.
126 factory_
= new IndexedDBFactoryImpl(this);
128 return factory_
.get();
131 std::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();
138 origins
.push_back(*iter
);
143 std::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();
150 const GURL
& origin_url
= *iter
;
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
),
163 static bool HostNameComparator(const GURL
& i
, const GURL
& j
) {
164 return i
.host() < j
.host();
167 base::ListValue
* IndexedDBContextImpl::GetAllOriginsDetails() {
168 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
169 std::vector
<GURL
> origins
= GetAllOrigins();
171 std::sort(origins
.begin(), origins
.end(), HostNameComparator
);
173 scoped_ptr
<base::ListValue
> list(new base::ListValue());
174 for (std::vector
<GURL
>::const_iterator iter
= origins
.begin();
175 iter
!= origins
.end();
177 const GURL
& origin_url
= *iter
;
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());
185 info
->SetString("path", GetFilePath(origin_url
).value());
186 info
->SetDouble("connection_count", GetConnectionCount(origin_url
));
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.
192 if (factory_
.get()) {
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());
199 for (IndexedDBFactory::OriginDBMapIterator it
= range
.first
;
202 const IndexedDBDatabase
* db
= it
->second
;
203 scoped_ptr
<base::DictionaryValue
> db_info(new base::DictionaryValue());
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());
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();
221 const IndexedDBTransaction
* transaction
= *trans_it
;
222 scoped_ptr
<base::DictionaryValue
> transaction_info(
223 new base::DictionaryValue());
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");
231 case IndexedDBTransaction::STARTED
:
232 if (transaction
->diagnostics().tasks_scheduled
> 0)
233 transaction_info
->SetString("status", "running");
235 transaction_info
->SetString("status", "started");
237 case IndexedDBTransaction::COMMITTING
:
238 transaction_info
->SetString("status", "committing");
240 case IndexedDBTransaction::FINISHED
:
241 transaction_info
->SetString("status", "finished");
245 transaction_info
->SetDouble(
247 IndexedDBDispatcherHost::TransactionIdToProcessId(
249 transaction_info
->SetDouble(
251 IndexedDBDispatcherHost::TransactionIdToRendererTransactionId(
253 transaction_info
->SetDouble(
255 (base::Time::Now() - transaction
->diagnostics().creation_time
)
257 transaction_info
->SetDouble(
259 (base::Time::Now() - transaction
->diagnostics().start_time
)
261 transaction_info
->SetDouble(
262 "tasks_scheduled", transaction
->diagnostics().tasks_scheduled
);
263 transaction_info
->SetDouble(
264 "tasks_completed", transaction
->diagnostics().tasks_completed
);
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();
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
);
277 transaction_info
->Set("scope", scope
.release());
278 transaction_list
->Append(transaction_info
.release());
280 db_info
->Set("transactions", transaction_list
.release());
282 database_list
->Append(db_info
.release());
284 info
->Set("databases", database_list
.release());
287 list
->Append(info
.release());
289 return list
.release();
292 int64
IndexedDBContextImpl::GetOriginDiskUsage(const GURL
& origin_url
) {
293 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
294 if (data_path_
.empty() || !IsInOriginSet(origin_url
))
296 EnsureDiskUsageCacheInitialized(origin_url
);
297 return origin_size_map_
[origin_url
];
300 base::Time
IndexedDBContextImpl::GetOriginLastModified(const GURL
& origin_url
) {
301 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
302 if (data_path_
.empty() || !IsInOriginSet(origin_url
))
304 base::FilePath idb_directory
= GetFilePath(origin_url
);
305 base::File::Info file_info
;
306 if (!base::GetFileInfo(idb_directory
, &file_info
))
308 return file_info
.last_modified
;
311 void 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
))
317 base::FilePath idb_directory
= GetFilePath(origin_url
);
318 EnsureDiskUsageCacheInitialized(origin_url
);
319 leveldb::Status s
= LevelDBDatabase::Destroy(idb_directory
);
321 LOG(WARNING
) << "Failed to delete LevelDB database: "
322 << idb_directory
.AsUTF8Unsafe();
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
);
331 QueryDiskAndUpdateQuotaUsage(origin_url
);
333 RemoveFromOriginSet(origin_url
);
334 origin_size_map_
.erase(origin_url
);
335 space_available_map_
.erase(origin_url
);
339 void IndexedDBContextImpl::ForceClose(const GURL origin_url
,
340 ForceCloseReason reason
) {
341 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
342 UMA_HISTOGRAM_ENUMERATION("WebCore.IndexedDB.Context.ForceCloseReason",
344 FORCE_CLOSE_REASON_MAX
);
346 if (data_path_
.empty() || !IsInOriginSet(origin_url
))
350 factory_
->ForceClose(origin_url
);
351 DCHECK_EQ(0UL, GetConnectionCount(origin_url
));
354 size_t IndexedDBContextImpl::GetConnectionCount(const GURL
& origin_url
) {
355 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
356 if (data_path_
.empty() || !IsInOriginSet(origin_url
))
362 return factory_
->GetConnectionCount(origin_url
);
365 base::FilePath
IndexedDBContextImpl::GetFilePath(const GURL
& origin_url
) const {
366 std::string origin_id
= storage::GetIdentifierFromOrigin(origin_url
);
367 return GetIndexedDBFilePath(origin_id
);
370 base::FilePath
IndexedDBContextImpl::GetFilePathForTesting(
371 const std::string
& origin_id
) const {
372 return GetIndexedDBFilePath(origin_id
);
375 void IndexedDBContextImpl::SetTaskRunnerForTesting(
376 base::SequencedTaskRunner
* task_runner
) {
377 DCHECK(!task_runner_
.get());
378 task_runner_
= task_runner
;
381 void IndexedDBContextImpl::ConnectionOpened(const GURL
& origin_url
,
382 IndexedDBConnection
* connection
) {
383 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
384 if (quota_manager_proxy()) {
385 quota_manager_proxy()->NotifyStorageAccessed(
386 storage::QuotaClient::kIndexedDatabase
,
388 storage::kStorageTypeTemporary
);
390 if (AddToOriginSet(origin_url
)) {
391 // A newly created db, notify the quota system.
392 QueryDiskAndUpdateQuotaUsage(origin_url
);
394 EnsureDiskUsageCacheInitialized(origin_url
);
396 QueryAvailableQuota(origin_url
);
399 void IndexedDBContextImpl::ConnectionClosed(const GURL
& origin_url
,
400 IndexedDBConnection
* connection
) {
401 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
402 if (quota_manager_proxy()) {
403 quota_manager_proxy()->NotifyStorageAccessed(
404 storage::QuotaClient::kIndexedDatabase
,
406 storage::kStorageTypeTemporary
);
408 if (factory_
.get() && factory_
->GetConnectionCount(origin_url
) == 0)
409 QueryDiskAndUpdateQuotaUsage(origin_url
);
412 void IndexedDBContextImpl::TransactionComplete(const GURL
& origin_url
) {
413 DCHECK(!factory_
.get() || factory_
->GetConnectionCount(origin_url
) > 0);
414 QueryDiskAndUpdateQuotaUsage(origin_url
);
415 QueryAvailableQuota(origin_url
);
418 void IndexedDBContextImpl::DatabaseDeleted(const GURL
& origin_url
) {
419 AddToOriginSet(origin_url
);
420 QueryDiskAndUpdateQuotaUsage(origin_url
);
421 QueryAvailableQuota(origin_url
);
424 bool 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.
430 bool over_quota
= additional_bytes
> space_available_map_
[origin_url
];
434 bool IndexedDBContextImpl::IsOverQuota(const GURL
& origin_url
) {
435 const int kOneAdditionalByte
= 1;
436 return WouldBeOverQuota(origin_url
, kOneAdditionalByte
);
439 storage::QuotaManagerProxy
* IndexedDBContextImpl::quota_manager_proxy() {
440 return quota_manager_proxy_
.get();
443 IndexedDBContextImpl::~IndexedDBContextImpl() {
444 if (factory_
.get()) {
445 TaskRunner()->PostTask(
446 FROM_HERE
, base::Bind(&IndexedDBFactory::ContextDestroyed
, factory_
));
450 if (data_path_
.empty())
453 if (force_keep_session_state_
)
456 bool has_session_only_databases
=
457 special_storage_policy_
.get() &&
458 special_storage_policy_
->HasSessionOnlyOrigins();
460 // Clearing only session-only databases, and there are none.
461 if (!has_session_only_databases
)
464 TaskRunner()->PostTask(
467 &ClearSessionOnlyOrigins
, data_path_
, special_storage_policy_
));
470 base::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
);
477 int64
IndexedDBContextImpl::ReadUsageFromDisk(const GURL
& origin_url
) const {
478 if (data_path_
.empty())
480 base::FilePath file_path
= GetFilePath(origin_url
);
481 return base::ComputeDirectorySize(file_path
);
484 void 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
);
490 void 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
;
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 storage::QuotaClient::kIndexedDatabase
,
502 storage::kStorageTypeTemporary
,
508 void IndexedDBContextImpl::GotUsageAndQuota(const GURL
& origin_url
,
509 storage::QuotaStatusCode status
,
512 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
513 DCHECK(status
== storage::kQuotaStatusOk
||
514 status
== storage::kQuotaErrorAbort
)
515 << "status was " << status
;
516 if (status
== storage::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 storage::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::SequencedTaskRunner
* IndexedDBContextImpl::TaskRunner() const {
577 return task_runner_
.get();
580 } // namespace content