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