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 #include <utility> 9 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" 38 39 using base::DictionaryValue; 40 using base::ListValue; 41 using storage::DatabaseUtil; 42 43 namespace content { 44 const base::FilePath::CharType IndexedDBContextImpl::kIndexedDBDirectory[] = 45 FILE_PATH_LITERAL("IndexedDB"); 46 47 static const base::FilePath::CharType kIndexedDBExtension[] = 48 FILE_PATH_LITERAL(".indexeddb"); 49 50 static const base::FilePath::CharType kLevelDBExtension[] = 51 FILE_PATH_LITERAL(".leveldb"); 52 53 namespace { 54 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); 74 } 75 } 76 } 77 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); 98 } 99 } 100 101 } // namespace 102 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)); 117 } 118 } 119 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); 127 } 128 return factory_.get(); 129 } 130 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(); 137 ++iter) { 138 origins.push_back(*iter); 139 } 140 return origins; 141 } 142 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(); 149 ++iter) { 150 const GURL& origin_url = *iter; 151 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), 157 idb_directory, 158 connection_count)); 159 } 160 return result; 161 } 162 163 static bool HostNameComparator(const GURL& i, const GURL& j) { 164 return i.host() < j.host(); 165 } 166 167 base::ListValue* IndexedDBContextImpl::GetAllOriginsDetails() { 168 DCHECK(TaskRunner()->RunsTasksOnCurrentThread()); 169 std::vector<GURL> origins = GetAllOrigins(); 170 171 std::sort(origins.begin(), origins.end(), HostNameComparator); 172 173 scoped_ptr<base::ListValue> list(new base::ListValue()); 174 for (std::vector<GURL>::const_iterator iter = origins.begin(); 175 iter != origins.end(); 176 ++iter) { 177 const GURL& origin_url = *iter; 178 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()); 184 if (!is_incognito()) 185 info->SetString("path", GetFilePath(origin_url).value()); 186 info->SetDouble("connection_count", GetConnectionCount(origin_url)); 187 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. 191 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()); 198 199 for (IndexedDBFactory::OriginDBMapIterator it = range.first; 200 it != range.second; 201 ++it) { 202 const IndexedDBDatabase* db = it->second; 203 scoped_ptr<base::DictionaryValue> db_info(new base::DictionaryValue()); 204 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()); 213 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(); 220 ++trans_it) { 221 const IndexedDBTransaction* transaction = *trans_it; 222 scoped_ptr<base::DictionaryValue> transaction_info( 223 new base::DictionaryValue()); 224 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"); 230 break; 231 case IndexedDBTransaction::STARTED: 232 if (transaction->diagnostics().tasks_scheduled > 0) 233 transaction_info->SetString("status", "running"); 234 else 235 transaction_info->SetString("status", "started"); 236 break; 237 case IndexedDBTransaction::COMMITTING: 238 transaction_info->SetString("status", "committing"); 239 break; 240 case IndexedDBTransaction::FINISHED: 241 transaction_info->SetString("status", "finished"); 242 break; 243 } 244 245 transaction_info->SetDouble( 246 "pid", 247 IndexedDBDispatcherHost::TransactionIdToProcessId( 248 transaction->id())); 249 transaction_info->SetDouble( 250 "tid", 251 IndexedDBDispatcherHost::TransactionIdToRendererTransactionId( 252 transaction->id())); 253 transaction_info->SetDouble( 254 "age", 255 (base::Time::Now() - transaction->diagnostics().creation_time) 256 .InMillisecondsF()); 257 transaction_info->SetDouble( 258 "runtime", 259 (base::Time::Now() - transaction->diagnostics().start_time) 260 .InMillisecondsF()); 261 transaction_info->SetDouble( 262 "tasks_scheduled", transaction->diagnostics().tasks_scheduled); 263 transaction_info->SetDouble( 264 "tasks_completed", transaction->diagnostics().tasks_completed); 265 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(); 270 ++scope_it) { 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); 275 } 276 277 transaction_info->Set("scope", scope.release()); 278 transaction_list->Append(transaction_info.release()); 279 } 280 db_info->Set("transactions", transaction_list.release()); 281 282 database_list->Append(db_info.release()); 283 } 284 info->Set("databases", database_list.release()); 285 } 286 287 list->Append(info.release()); 288 } 289 return list.release(); 290 } 291 292 int64 IndexedDBContextImpl::GetOriginDiskUsage(const GURL& origin_url) { 293 DCHECK(TaskRunner()->RunsTasksOnCurrentThread()); 294 if (data_path_.empty() || !IsInOriginSet(origin_url)) 295 return 0; 296 EnsureDiskUsageCacheInitialized(origin_url); 297 return origin_size_map_[origin_url]; 298 } 299 300 base::Time IndexedDBContextImpl::GetOriginLastModified(const GURL& origin_url) { 301 DCHECK(TaskRunner()->RunsTasksOnCurrentThread()); 302 if (data_path_.empty() || !IsInOriginSet(origin_url)) 303 return base::Time(); 304 base::FilePath idb_directory = GetFilePath(origin_url); 305 base::File::Info file_info; 306 if (!base::GetFileInfo(idb_directory, &file_info)) 307 return base::Time(); 308 return file_info.last_modified; 309 } 310 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)) 315 return; 316 317 base::FilePath idb_directory = GetFilePath(origin_url); 318 EnsureDiskUsageCacheInitialized(origin_url); 319 leveldb::Status s = LevelDBDatabase::Destroy(idb_directory); 320 if (!s.ok()) { 321 LOG(WARNING) << "Failed to delete LevelDB database: " 322 << idb_directory.AsUTF8Unsafe(); 323 } else { 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); 329 } 330 331 QueryDiskAndUpdateQuotaUsage(origin_url); 332 if (s.ok()) { 333 RemoveFromOriginSet(origin_url); 334 origin_size_map_.erase(origin_url); 335 space_available_map_.erase(origin_url); 336 } 337 } 338 339 void IndexedDBContextImpl::ForceClose(const GURL origin_url, 340 ForceCloseReason reason) { 341 DCHECK(TaskRunner()->RunsTasksOnCurrentThread()); 342 UMA_HISTOGRAM_ENUMERATION("WebCore.IndexedDB.Context.ForceCloseReason", 343 reason, 344 FORCE_CLOSE_REASON_MAX); 345 346 if (data_path_.empty() || !IsInOriginSet(origin_url)) 347 return; 348 349 if (factory_.get()) 350 factory_->ForceClose(origin_url); 351 DCHECK_EQ(0UL, GetConnectionCount(origin_url)); 352 } 353 354 size_t IndexedDBContextImpl::GetConnectionCount(const GURL& origin_url) { 355 DCHECK(TaskRunner()->RunsTasksOnCurrentThread()); 356 if (data_path_.empty() || !IsInOriginSet(origin_url)) 357 return 0; 358 359 if (!factory_.get()) 360 return 0; 361 362 return factory_->GetConnectionCount(origin_url); 363 } 364 365 base::FilePath IndexedDBContextImpl::GetFilePath(const GURL& origin_url) const { 366 std::string origin_id = storage::GetIdentifierFromOrigin(origin_url); 367 return GetIndexedDBFilePath(origin_id); 368 } 369 370 base::FilePath IndexedDBContextImpl::GetFilePathForTesting( 371 const std::string& origin_id) const { 372 return GetIndexedDBFilePath(origin_id); 373 } 374 375 void IndexedDBContextImpl::SetTaskRunnerForTesting( 376 base::SequencedTaskRunner* task_runner) { 377 DCHECK(!task_runner_.get()); 378 task_runner_ = task_runner; 379 } 380 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, 387 origin_url, 388 storage::kStorageTypeTemporary); 389 } 390 if (AddToOriginSet(origin_url)) { 391 // A newly created db, notify the quota system. 392 QueryDiskAndUpdateQuotaUsage(origin_url); 393 } else { 394 EnsureDiskUsageCacheInitialized(origin_url); 395 } 396 QueryAvailableQuota(origin_url); 397 } 398 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, 405 origin_url, 406 storage::kStorageTypeTemporary); 407 } 408 if (factory_.get() && factory_->GetConnectionCount(origin_url) == 0) 409 QueryDiskAndUpdateQuotaUsage(origin_url); 410 } 411 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); 416 } 417 418 void IndexedDBContextImpl::DatabaseDeleted(const GURL& origin_url) { 419 AddToOriginSet(origin_url); 420 QueryDiskAndUpdateQuotaUsage(origin_url); 421 QueryAvailableQuota(origin_url); 422 } 423 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. 428 return false; 429 } 430 bool over_quota = additional_bytes > space_available_map_[origin_url]; 431 return over_quota; 432 } 433 434 bool IndexedDBContextImpl::IsOverQuota(const GURL& origin_url) { 435 const int kOneAdditionalByte = 1; 436 return WouldBeOverQuota(origin_url, kOneAdditionalByte); 437 } 438 439 storage::QuotaManagerProxy* IndexedDBContextImpl::quota_manager_proxy() { 440 return quota_manager_proxy_.get(); 441 } 442 443 IndexedDBContextImpl::~IndexedDBContextImpl() { 444 if (factory_.get()) { 445 TaskRunner()->PostTask( 446 FROM_HERE, base::Bind(&IndexedDBFactory::ContextDestroyed, factory_)); 447 factory_ = NULL; 448 } 449 450 if (data_path_.empty()) 451 return; 452 453 if (force_keep_session_state_) 454 return; 455 456 bool has_session_only_databases = 457 special_storage_policy_.get() && 458 special_storage_policy_->HasSessionOnlyOrigins(); 459 460 // Clearing only session-only databases, and there are none. 461 if (!has_session_only_databases) 462 return; 463 464 TaskRunner()->PostTask( 465 FROM_HERE, 466 base::Bind( 467 &ClearSessionOnlyOrigins, data_path_, special_storage_policy_)); 468 } 469 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); 475 } 476 477 int64 IndexedDBContextImpl::ReadUsageFromDisk(const GURL& origin_url) const { 478 if (data_path_.empty()) 479 return 0; 480 base::FilePath file_path = GetFilePath(origin_url); 481 return base::ComputeDirectorySize(file_path); 482 } 483 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); 488 } 489 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; 495 if (difference) { 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, 501 origin_url, 502 storage::kStorageTypeTemporary, 503 difference); 504 } 505 } 506 } 507 508 void IndexedDBContextImpl::GotUsageAndQuota(const GURL& origin_url, 509 storage::QuotaStatusCode status, 510 int64 usage, 511 int64 quota) { 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. 518 return; 519 } 520 TaskRunner()->PostTask(FROM_HERE, 521 base::Bind(&IndexedDBContextImpl::GotUpdatedQuota, 522 this, 523 origin_url, 524 usage, 525 quota)); 526 } 527 528 void 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 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( 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 storage::kStorageTypeTemporary, 553 base::Bind(&IndexedDBContextImpl::GotUsageAndQuota, this, origin_url)); 554 } 555 556 std::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 570 void IndexedDBContextImpl::ResetCaches() { 571 origin_set_.reset(); 572 origin_size_map_.clear(); 573 space_available_map_.clear(); 574 } 575 576 base::SequencedTaskRunner* IndexedDBContextImpl::TaskRunner() const { 577 return task_runner_.get(); 578 } 579 580 } // namespace content 581