Blink roll 25b6bd3a7a131ffe68d809546ad1a20707915cdc:3a503f41ae42e5b79cfcd2ff10e65afde...
[chromium-blink-merge.git] / content / browser / indexed_db / indexed_db_context_impl.cc
blobb54d59a46ffbdd6ca3434e954bc327f6ea84be1b
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"
7 #include <algorithm>
8 #include <utility>
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;
43 namespace content {
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");
53 namespace {
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())
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(storage::GetOriginFromIdentifier(origin_id));
72 if (file_paths)
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 =
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);
101 } // namespace
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) {
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));
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.
125 GetOriginSet();
126 factory_ = new IndexedDBFactoryImpl(this);
128 return factory_.get();
131 std::vector<GURL> IndexedDBContextImpl::GetAllOrigins() {
132 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
133 std::set<GURL>* origins_set = GetOriginSet();
134 return std::vector<GURL>(origins_set->begin(), origins_set->end());
137 std::vector<IndexedDBInfo> IndexedDBContextImpl::GetAllOriginsInfo() {
138 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
139 std::vector<GURL> origins = GetAllOrigins();
140 std::vector<IndexedDBInfo> result;
141 for (const auto& origin_url : origins) {
142 base::FilePath idb_directory = GetFilePath(origin_url);
143 size_t connection_count = GetConnectionCount(origin_url);
144 result.push_back(IndexedDBInfo(origin_url,
145 GetOriginDiskUsage(origin_url),
146 GetOriginLastModified(origin_url),
147 idb_directory,
148 connection_count));
150 return result;
153 static bool HostNameComparator(const GURL& i, const GURL& j) {
154 return i.host() < j.host();
157 base::ListValue* IndexedDBContextImpl::GetAllOriginsDetails() {
158 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
159 std::vector<GURL> origins = GetAllOrigins();
161 std::sort(origins.begin(), origins.end(), HostNameComparator);
163 scoped_ptr<base::ListValue> list(new base::ListValue());
164 for (const auto& origin_url : origins) {
165 scoped_ptr<base::DictionaryValue> info(new base::DictionaryValue());
166 info->SetString("url", origin_url.spec());
167 info->SetString("size", ui::FormatBytes(GetOriginDiskUsage(origin_url)));
168 info->SetDouble("last_modified",
169 GetOriginLastModified(origin_url).ToJsTime());
170 if (!is_incognito())
171 info->SetString("path", GetFilePath(origin_url).value());
172 info->SetDouble("connection_count", GetConnectionCount(origin_url));
174 // This ends up being O(n^2) since we iterate over all open databases
175 // to extract just those in the origin, and we're iterating over all
176 // origins in the outer loop.
178 if (factory_.get()) {
179 std::pair<IndexedDBFactory::OriginDBMapIterator,
180 IndexedDBFactory::OriginDBMapIterator> range =
181 factory_->GetOpenDatabasesForOrigin(origin_url);
182 // TODO(jsbell): Sort by name?
183 scoped_ptr<base::ListValue> database_list(new base::ListValue());
185 for (IndexedDBFactory::OriginDBMapIterator it = range.first;
186 it != range.second;
187 ++it) {
188 const IndexedDBDatabase* db = it->second;
189 scoped_ptr<base::DictionaryValue> db_info(new base::DictionaryValue());
191 db_info->SetString("name", db->name());
192 db_info->SetDouble("pending_opens", db->PendingOpenCount());
193 db_info->SetDouble("pending_upgrades", db->PendingUpgradeCount());
194 db_info->SetDouble("running_upgrades", db->RunningUpgradeCount());
195 db_info->SetDouble("pending_deletes", db->PendingDeleteCount());
196 db_info->SetDouble("connection_count",
197 db->ConnectionCount() - db->PendingUpgradeCount() -
198 db->RunningUpgradeCount());
200 scoped_ptr<base::ListValue> transaction_list(new base::ListValue());
201 std::vector<const IndexedDBTransaction*> transactions =
202 db->transaction_coordinator().GetTransactions();
203 for (const auto* transaction : transactions) {
204 scoped_ptr<base::DictionaryValue> transaction_info(
205 new base::DictionaryValue());
207 const char* kModes[] = { "readonly", "readwrite", "versionchange" };
208 transaction_info->SetString("mode", kModes[transaction->mode()]);
209 switch (transaction->state()) {
210 case IndexedDBTransaction::CREATED:
211 transaction_info->SetString("status", "blocked");
212 break;
213 case IndexedDBTransaction::STARTED:
214 if (transaction->diagnostics().tasks_scheduled > 0)
215 transaction_info->SetString("status", "running");
216 else
217 transaction_info->SetString("status", "started");
218 break;
219 case IndexedDBTransaction::COMMITTING:
220 transaction_info->SetString("status", "committing");
221 break;
222 case IndexedDBTransaction::FINISHED:
223 transaction_info->SetString("status", "finished");
224 break;
227 transaction_info->SetDouble(
228 "pid",
229 IndexedDBDispatcherHost::TransactionIdToProcessId(
230 transaction->id()));
231 transaction_info->SetDouble(
232 "tid",
233 IndexedDBDispatcherHost::TransactionIdToRendererTransactionId(
234 transaction->id()));
235 transaction_info->SetDouble(
236 "age",
237 (base::Time::Now() - transaction->diagnostics().creation_time)
238 .InMillisecondsF());
239 transaction_info->SetDouble(
240 "runtime",
241 (base::Time::Now() - transaction->diagnostics().start_time)
242 .InMillisecondsF());
243 transaction_info->SetDouble(
244 "tasks_scheduled", transaction->diagnostics().tasks_scheduled);
245 transaction_info->SetDouble(
246 "tasks_completed", transaction->diagnostics().tasks_completed);
248 scoped_ptr<base::ListValue> scope(new base::ListValue());
249 for (const auto& id : transaction->scope()) {
250 IndexedDBDatabaseMetadata::ObjectStoreMap::const_iterator it =
251 db->metadata().object_stores.find(id);
252 if (it != db->metadata().object_stores.end())
253 scope->AppendString(it->second.name);
256 transaction_info->Set("scope", scope.release());
257 transaction_list->Append(transaction_info.release());
259 db_info->Set("transactions", transaction_list.release());
261 database_list->Append(db_info.release());
263 info->Set("databases", database_list.release());
266 list->Append(info.release());
268 return list.release();
271 int64 IndexedDBContextImpl::GetOriginDiskUsage(const GURL& origin_url) {
272 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
273 if (data_path_.empty() || !IsInOriginSet(origin_url))
274 return 0;
275 EnsureDiskUsageCacheInitialized(origin_url);
276 return origin_size_map_[origin_url];
279 base::Time IndexedDBContextImpl::GetOriginLastModified(const GURL& origin_url) {
280 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
281 if (data_path_.empty() || !IsInOriginSet(origin_url))
282 return base::Time();
283 base::FilePath idb_directory = GetFilePath(origin_url);
284 base::File::Info file_info;
285 if (!base::GetFileInfo(idb_directory, &file_info))
286 return base::Time();
287 return file_info.last_modified;
290 void IndexedDBContextImpl::DeleteForOrigin(const GURL& origin_url) {
291 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
292 ForceClose(origin_url, FORCE_CLOSE_DELETE_ORIGIN);
293 if (data_path_.empty() || !IsInOriginSet(origin_url))
294 return;
296 base::FilePath idb_directory = GetFilePath(origin_url);
297 EnsureDiskUsageCacheInitialized(origin_url);
298 leveldb::Status s = LevelDBDatabase::Destroy(idb_directory);
299 if (!s.ok()) {
300 LOG(WARNING) << "Failed to delete LevelDB database: "
301 << idb_directory.AsUTF8Unsafe();
302 } else {
303 // LevelDB does not delete empty directories; work around this.
304 // TODO(jsbell): Remove when upstream bug is fixed.
305 // https://code.google.com/p/leveldb/issues/detail?id=209
306 const bool kNonRecursive = false;
307 base::DeleteFile(idb_directory, kNonRecursive);
310 QueryDiskAndUpdateQuotaUsage(origin_url);
311 if (s.ok()) {
312 RemoveFromOriginSet(origin_url);
313 origin_size_map_.erase(origin_url);
314 space_available_map_.erase(origin_url);
318 void IndexedDBContextImpl::ForceClose(const GURL origin_url,
319 ForceCloseReason reason) {
320 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
321 UMA_HISTOGRAM_ENUMERATION("WebCore.IndexedDB.Context.ForceCloseReason",
322 reason,
323 FORCE_CLOSE_REASON_MAX);
325 if (data_path_.empty() || !IsInOriginSet(origin_url))
326 return;
328 if (factory_.get())
329 factory_->ForceClose(origin_url);
330 DCHECK_EQ(0UL, GetConnectionCount(origin_url));
333 size_t IndexedDBContextImpl::GetConnectionCount(const GURL& origin_url) {
334 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
335 if (data_path_.empty() || !IsInOriginSet(origin_url))
336 return 0;
338 if (!factory_.get())
339 return 0;
341 return factory_->GetConnectionCount(origin_url);
344 base::FilePath IndexedDBContextImpl::GetFilePath(const GURL& origin_url) const {
345 std::string origin_id = storage::GetIdentifierFromOrigin(origin_url);
346 return GetIndexedDBFilePath(origin_id);
349 base::FilePath IndexedDBContextImpl::GetFilePathForTesting(
350 const std::string& origin_id) const {
351 return GetIndexedDBFilePath(origin_id);
354 void IndexedDBContextImpl::SetTaskRunnerForTesting(
355 base::SequencedTaskRunner* task_runner) {
356 DCHECK(!task_runner_.get());
357 task_runner_ = task_runner;
360 void IndexedDBContextImpl::ConnectionOpened(const GURL& origin_url,
361 IndexedDBConnection* connection) {
362 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
363 if (quota_manager_proxy()) {
364 quota_manager_proxy()->NotifyStorageAccessed(
365 storage::QuotaClient::kIndexedDatabase,
366 origin_url,
367 storage::kStorageTypeTemporary);
369 if (AddToOriginSet(origin_url)) {
370 // A newly created db, notify the quota system.
371 QueryDiskAndUpdateQuotaUsage(origin_url);
372 } else {
373 EnsureDiskUsageCacheInitialized(origin_url);
375 QueryAvailableQuota(origin_url);
378 void IndexedDBContextImpl::ConnectionClosed(const GURL& origin_url,
379 IndexedDBConnection* connection) {
380 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
381 if (quota_manager_proxy()) {
382 quota_manager_proxy()->NotifyStorageAccessed(
383 storage::QuotaClient::kIndexedDatabase,
384 origin_url,
385 storage::kStorageTypeTemporary);
387 if (factory_.get() && factory_->GetConnectionCount(origin_url) == 0)
388 QueryDiskAndUpdateQuotaUsage(origin_url);
391 void IndexedDBContextImpl::TransactionComplete(const GURL& origin_url) {
392 DCHECK(!factory_.get() || factory_->GetConnectionCount(origin_url) > 0);
393 QueryDiskAndUpdateQuotaUsage(origin_url);
394 QueryAvailableQuota(origin_url);
397 void IndexedDBContextImpl::DatabaseDeleted(const GURL& origin_url) {
398 AddToOriginSet(origin_url);
399 QueryDiskAndUpdateQuotaUsage(origin_url);
400 QueryAvailableQuota(origin_url);
403 bool IndexedDBContextImpl::WouldBeOverQuota(const GURL& origin_url,
404 int64 additional_bytes) {
405 if (space_available_map_.find(origin_url) == space_available_map_.end()) {
406 // We haven't heard back from the QuotaManager yet, just let it through.
407 return false;
409 bool over_quota = additional_bytes > space_available_map_[origin_url];
410 return over_quota;
413 bool IndexedDBContextImpl::IsOverQuota(const GURL& origin_url) {
414 const int kOneAdditionalByte = 1;
415 return WouldBeOverQuota(origin_url, kOneAdditionalByte);
418 storage::QuotaManagerProxy* IndexedDBContextImpl::quota_manager_proxy() {
419 return quota_manager_proxy_.get();
422 IndexedDBContextImpl::~IndexedDBContextImpl() {
423 if (factory_.get()) {
424 TaskRunner()->PostTask(
425 FROM_HERE, base::Bind(&IndexedDBFactory::ContextDestroyed, factory_));
426 factory_ = NULL;
429 if (data_path_.empty())
430 return;
432 if (force_keep_session_state_)
433 return;
435 bool has_session_only_databases =
436 special_storage_policy_.get() &&
437 special_storage_policy_->HasSessionOnlyOrigins();
439 // Clearing only session-only databases, and there are none.
440 if (!has_session_only_databases)
441 return;
443 TaskRunner()->PostTask(
444 FROM_HERE,
445 base::Bind(
446 &ClearSessionOnlyOrigins, data_path_, special_storage_policy_));
449 base::FilePath IndexedDBContextImpl::GetIndexedDBFilePath(
450 const std::string& origin_id) const {
451 DCHECK(!data_path_.empty());
452 return data_path_.AppendASCII(origin_id).AddExtension(kIndexedDBExtension)
453 .AddExtension(kLevelDBExtension);
456 int64 IndexedDBContextImpl::ReadUsageFromDisk(const GURL& origin_url) const {
457 if (data_path_.empty())
458 return 0;
459 base::FilePath file_path = GetFilePath(origin_url);
460 return base::ComputeDirectorySize(file_path);
463 void IndexedDBContextImpl::EnsureDiskUsageCacheInitialized(
464 const GURL& origin_url) {
465 if (origin_size_map_.find(origin_url) == origin_size_map_.end())
466 origin_size_map_[origin_url] = ReadUsageFromDisk(origin_url);
469 void IndexedDBContextImpl::QueryDiskAndUpdateQuotaUsage(
470 const GURL& origin_url) {
471 int64 former_disk_usage = origin_size_map_[origin_url];
472 int64 current_disk_usage = ReadUsageFromDisk(origin_url);
473 int64 difference = current_disk_usage - former_disk_usage;
474 if (difference) {
475 origin_size_map_[origin_url] = current_disk_usage;
476 // quota_manager_proxy() is NULL in unit tests.
477 if (quota_manager_proxy()) {
478 quota_manager_proxy()->NotifyStorageModified(
479 storage::QuotaClient::kIndexedDatabase,
480 origin_url,
481 storage::kStorageTypeTemporary,
482 difference);
487 void IndexedDBContextImpl::GotUsageAndQuota(const GURL& origin_url,
488 storage::QuotaStatusCode status,
489 int64 usage,
490 int64 quota) {
491 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
492 DCHECK(status == storage::kQuotaStatusOk ||
493 status == storage::kQuotaErrorAbort)
494 << "status was " << status;
495 if (status == storage::kQuotaErrorAbort) {
496 // We seem to no longer care to wait around for the answer.
497 return;
499 TaskRunner()->PostTask(FROM_HERE,
500 base::Bind(&IndexedDBContextImpl::GotUpdatedQuota,
501 this,
502 origin_url,
503 usage,
504 quota));
507 void IndexedDBContextImpl::GotUpdatedQuota(const GURL& origin_url,
508 int64 usage,
509 int64 quota) {
510 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
511 space_available_map_[origin_url] = quota - usage;
514 void IndexedDBContextImpl::QueryAvailableQuota(const GURL& origin_url) {
515 if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
516 DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
517 if (quota_manager_proxy()) {
518 BrowserThread::PostTask(
519 BrowserThread::IO,
520 FROM_HERE,
521 base::Bind(
522 &IndexedDBContextImpl::QueryAvailableQuota, this, origin_url));
524 return;
526 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
527 if (!quota_manager_proxy() || !quota_manager_proxy()->quota_manager())
528 return;
529 quota_manager_proxy()->quota_manager()->GetUsageAndQuota(
530 origin_url,
531 storage::kStorageTypeTemporary,
532 base::Bind(&IndexedDBContextImpl::GotUsageAndQuota, this, origin_url));
535 std::set<GURL>* IndexedDBContextImpl::GetOriginSet() {
536 if (!origin_set_) {
537 std::vector<GURL> origins;
538 GetAllOriginsAndPaths(data_path_, &origins, NULL);
539 origin_set_.reset(new std::set<GURL>(origins.begin(), origins.end()));
541 return origin_set_.get();
544 void IndexedDBContextImpl::ResetCaches() {
545 origin_set_.reset();
546 origin_size_map_.clear();
547 space_available_map_.clear();
550 base::SequencedTaskRunner* IndexedDBContextImpl::TaskRunner() const {
551 return task_runner_.get();
554 } // namespace content