1 // Copyright (c) 2013 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_factory.h" 6 7 #include <vector> 8 9 #include "base/logging.h" 10 #include "base/strings/utf_string_conversions.h" 11 #include "base/time/time.h" 12 #include "content/browser/indexed_db/indexed_db_backing_store.h" 13 #include "content/browser/indexed_db/indexed_db_context_impl.h" 14 #include "content/browser/indexed_db/indexed_db_database_error.h" 15 #include "content/browser/indexed_db/indexed_db_tracing.h" 16 #include "content/browser/indexed_db/indexed_db_transaction_coordinator.h" 17 #include "third_party/WebKit/public/platform/WebIDBDatabaseException.h" 18 #include "third_party/leveldatabase/env_chromium.h" 19 #include "webkit/common/database/database_identifier.h" 20 21 using base::ASCIIToUTF16; 22 23 namespace content { 24 25 const int64 kBackingStoreGracePeriodMs = 2000; 26 27 IndexedDBFactory::IndexedDBFactory(IndexedDBContextImpl* context) 28 : context_(context) {} 29 30 IndexedDBFactory::~IndexedDBFactory() {} 31 32 void IndexedDBFactory::RemoveDatabaseFromMaps( 33 const IndexedDBDatabase::Identifier& identifier) { 34 IndexedDBDatabaseMap::iterator it = database_map_.find(identifier); 35 DCHECK(it != database_map_.end()); 36 IndexedDBDatabase* database = it->second; 37 database_map_.erase(it); 38 39 std::pair<OriginDBMap::iterator, OriginDBMap::iterator> range = 40 origin_dbs_.equal_range(database->identifier().first); 41 DCHECK(range.first != range.second); 42 for (OriginDBMap::iterator it2 = range.first; it2 != range.second; ++it2) { 43 if (it2->second == database) { 44 origin_dbs_.erase(it2); 45 break; 46 } 47 } 48 } 49 50 void IndexedDBFactory::ReleaseDatabase( 51 const IndexedDBDatabase::Identifier& identifier, 52 bool forcedClose) { 53 54 DCHECK(!database_map_.find(identifier)->second->backing_store()); 55 56 RemoveDatabaseFromMaps(identifier); 57 58 // No grace period on a forced-close, as the initiator is 59 // assuming the backing store will be released once all 60 // connections are closed. 61 ReleaseBackingStore(identifier.first, forcedClose); 62 } 63 64 void IndexedDBFactory::ReleaseBackingStore(const GURL& origin_url, 65 bool immediate) { 66 if (immediate) { 67 IndexedDBBackingStoreMap::iterator it = 68 backing_stores_with_active_blobs_.find(origin_url); 69 if (it != backing_stores_with_active_blobs_.end()) { 70 it->second->active_blob_registry()->ForceShutdown(); 71 backing_stores_with_active_blobs_.erase(it); 72 } 73 } 74 75 // Only close if this is the last reference. 76 if (!HasLastBackingStoreReference(origin_url)) 77 return; 78 79 // If this factory does hold the last reference to the backing store, it can 80 // be closed - but unless requested to close it immediately, keep it around 81 // for a short period so that a re-open is fast. 82 if (immediate) { 83 CloseBackingStore(origin_url); 84 return; 85 } 86 87 // Start a timer to close the backing store, unless something else opens it 88 // in the mean time. 89 DCHECK(!backing_store_map_[origin_url]->close_timer()->IsRunning()); 90 backing_store_map_[origin_url]->close_timer()->Start( 91 FROM_HERE, 92 base::TimeDelta::FromMilliseconds(kBackingStoreGracePeriodMs), 93 base::Bind(&IndexedDBFactory::MaybeCloseBackingStore, this, origin_url)); 94 } 95 96 void IndexedDBFactory::MaybeCloseBackingStore(const GURL& origin_url) { 97 // Another reference may have opened since the maybe-close was posted, so it 98 // is necessary to check again. 99 if (HasLastBackingStoreReference(origin_url)) 100 CloseBackingStore(origin_url); 101 } 102 103 void IndexedDBFactory::CloseBackingStore(const GURL& origin_url) { 104 IndexedDBBackingStoreMap::iterator it = backing_store_map_.find(origin_url); 105 DCHECK(it != backing_store_map_.end()); 106 // Stop the timer (if it's running) - this may happen if the timer was started 107 // and then a forced close occurs. 108 it->second->close_timer()->Stop(); 109 backing_store_map_.erase(it); 110 } 111 112 bool IndexedDBFactory::HasLastBackingStoreReference(const GURL& origin_url) 113 const { 114 IndexedDBBackingStore* ptr; 115 { 116 // Scope so that the implicit scoped_refptr<> is freed. 117 IndexedDBBackingStoreMap::const_iterator it = 118 backing_store_map_.find(origin_url); 119 DCHECK(it != backing_store_map_.end()); 120 ptr = it->second.get(); 121 } 122 return ptr->HasOneRef(); 123 } 124 125 void IndexedDBFactory::ForceClose(const GURL& origin_url) { 126 std::pair<OriginDBMapIterator, OriginDBMapIterator> range = 127 GetOpenDatabasesForOrigin(origin_url); 128 129 while (range.first != range.second) { 130 IndexedDBDatabase* db = range.first->second; 131 ++range.first; 132 db->ForceClose(); 133 } 134 135 if (backing_store_map_.find(origin_url) != backing_store_map_.end()) 136 ReleaseBackingStore(origin_url, true /* immediate */); 137 } 138 139 void IndexedDBFactory::ContextDestroyed() { 140 // Timers on backing stores hold a reference to this factory. When the 141 // context (which nominally owns this factory) is destroyed during thread 142 // termination the timers must be stopped so that this factory and the 143 // stores can be disposed of. 144 for (IndexedDBBackingStoreMap::iterator it = backing_store_map_.begin(); 145 it != backing_store_map_.end(); 146 ++it) 147 it->second->close_timer()->Stop(); 148 backing_store_map_.clear(); 149 backing_stores_with_active_blobs_.clear(); 150 context_ = NULL; 151 } 152 153 void IndexedDBFactory::ReportOutstandingBlobs(const GURL& origin_url, 154 bool blobs_outstanding) { 155 if (!context_) 156 return; 157 if (blobs_outstanding) { 158 DCHECK(!backing_stores_with_active_blobs_.count(origin_url)); 159 IndexedDBBackingStoreMap::iterator it = backing_store_map_.find(origin_url); 160 if (it != backing_store_map_.end()) 161 backing_stores_with_active_blobs_.insert(*it); 162 else 163 DCHECK(false); 164 } else { 165 IndexedDBBackingStoreMap::iterator it = 166 backing_stores_with_active_blobs_.find(origin_url); 167 if (it != backing_stores_with_active_blobs_.end()) { 168 backing_stores_with_active_blobs_.erase(it); 169 ReleaseBackingStore(origin_url, false /* immediate */); 170 } 171 } 172 } 173 174 void IndexedDBFactory::GetDatabaseNames( 175 scoped_refptr<IndexedDBCallbacks> callbacks, 176 const GURL& origin_url, 177 const base::FilePath& data_directory, 178 net::URLRequestContext* request_context) { 179 IDB_TRACE("IndexedDBFactory::GetDatabaseNames"); 180 // TODO(dgrogan): Plumb data_loss back to script eventually? 181 blink::WebIDBDataLoss data_loss; 182 std::string data_loss_message; 183 bool disk_full; 184 scoped_refptr<IndexedDBBackingStore> backing_store = 185 OpenBackingStore(origin_url, 186 data_directory, 187 request_context, 188 &data_loss, 189 &data_loss_message, 190 &disk_full); 191 if (!backing_store) { 192 callbacks->OnError( 193 IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError, 194 "Internal error opening backing store for " 195 "indexedDB.webkitGetDatabaseNames.")); 196 return; 197 } 198 199 leveldb::Status s; 200 std::vector<base::string16> names = backing_store->GetDatabaseNames(&s); 201 if (!s.ok()) { 202 // TODO(cmumford): Handle this error 203 DLOG(ERROR) << "Internal error getting database names"; 204 } 205 callbacks->OnSuccess(names); 206 backing_store = NULL; 207 ReleaseBackingStore(origin_url, false /* immediate */); 208 } 209 210 void IndexedDBFactory::DeleteDatabase( 211 const base::string16& name, 212 net::URLRequestContext* request_context, 213 scoped_refptr<IndexedDBCallbacks> callbacks, 214 const GURL& origin_url, 215 const base::FilePath& data_directory) { 216 IDB_TRACE("IndexedDBFactory::DeleteDatabase"); 217 IndexedDBDatabase::Identifier unique_identifier(origin_url, name); 218 IndexedDBDatabaseMap::iterator it = database_map_.find(unique_identifier); 219 if (it != database_map_.end()) { 220 // If there are any connections to the database, directly delete the 221 // database. 222 it->second->DeleteDatabase(callbacks); 223 return; 224 } 225 226 // TODO(dgrogan): Plumb data_loss back to script eventually? 227 blink::WebIDBDataLoss data_loss; 228 std::string data_loss_message; 229 bool disk_full = false; 230 scoped_refptr<IndexedDBBackingStore> backing_store = 231 OpenBackingStore(origin_url, 232 data_directory, 233 request_context, 234 &data_loss, 235 &data_loss_message, 236 &disk_full); 237 if (!backing_store) { 238 callbacks->OnError( 239 IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError, 240 ASCIIToUTF16( 241 "Internal error opening backing store " 242 "for indexedDB.deleteDatabase."))); 243 return; 244 } 245 246 leveldb::Status s; 247 scoped_refptr<IndexedDBDatabase> database = IndexedDBDatabase::Create( 248 name, backing_store, this, unique_identifier, &s); 249 if (!database) { 250 IndexedDBDatabaseError error( 251 blink::WebIDBDatabaseExceptionUnknownError, 252 ASCIIToUTF16( 253 "Internal error creating database backend for " 254 "indexedDB.deleteDatabase.")); 255 callbacks->OnError(error); 256 if (leveldb_env::IsCorruption(s)) 257 HandleBackingStoreCorruption(origin_url, error); 258 return; 259 } 260 261 database_map_[unique_identifier] = database; 262 origin_dbs_.insert(std::make_pair(origin_url, database)); 263 database->DeleteDatabase(callbacks); 264 RemoveDatabaseFromMaps(unique_identifier); 265 database = NULL; 266 backing_store = NULL; 267 ReleaseBackingStore(origin_url, false /* immediate */); 268 } 269 270 void IndexedDBFactory::DatabaseDeleted( 271 const IndexedDBDatabase::Identifier& identifier) { 272 // NULL after ContextDestroyed() called, and in some unit tests. 273 if (!context_) 274 return; 275 context_->DatabaseDeleted(identifier.first); 276 } 277 278 void IndexedDBFactory::HandleBackingStoreFailure(const GURL& origin_url) { 279 // NULL after ContextDestroyed() called, and in some unit tests. 280 if (!context_) 281 return; 282 context_->ForceClose(origin_url, 283 IndexedDBContextImpl::FORCE_CLOSE_BACKING_STORE_FAILURE); 284 } 285 286 void IndexedDBFactory::HandleBackingStoreCorruption( 287 const GURL& origin_url, 288 const IndexedDBDatabaseError& error) { 289 // Make a copy of origin_url as this is likely a reference to a member of a 290 // backing store which this function will be deleting. 291 GURL saved_origin_url(origin_url); 292 DCHECK(context_); 293 base::FilePath path_base = context_->data_path(); 294 IndexedDBBackingStore::RecordCorruptionInfo( 295 path_base, saved_origin_url, base::UTF16ToUTF8(error.message())); 296 HandleBackingStoreFailure(saved_origin_url); 297 // Note: DestroyBackingStore only deletes LevelDB files, leaving all others, 298 // so our corruption info file will remain. 299 leveldb::Status s = 300 IndexedDBBackingStore::DestroyBackingStore(path_base, saved_origin_url); 301 if (!s.ok()) 302 DLOG(ERROR) << "Unable to delete backing store: " << s.ToString(); 303 } 304 305 bool IndexedDBFactory::IsDatabaseOpen(const GURL& origin_url, 306 const base::string16& name) const { 307 return !!database_map_.count(IndexedDBDatabase::Identifier(origin_url, name)); 308 } 309 310 bool IndexedDBFactory::IsBackingStoreOpen(const GURL& origin_url) const { 311 return backing_store_map_.find(origin_url) != backing_store_map_.end(); 312 } 313 314 bool IndexedDBFactory::IsBackingStorePendingClose(const GURL& origin_url) 315 const { 316 IndexedDBBackingStoreMap::const_iterator it = 317 backing_store_map_.find(origin_url); 318 if (it == backing_store_map_.end()) 319 return false; 320 return it->second->close_timer()->IsRunning(); 321 } 322 323 scoped_refptr<IndexedDBBackingStore> IndexedDBFactory::OpenBackingStoreHelper( 324 const GURL& origin_url, 325 const base::FilePath& data_directory, 326 net::URLRequestContext* request_context, 327 blink::WebIDBDataLoss* data_loss, 328 std::string* data_loss_message, 329 bool* disk_full, 330 bool first_time) { 331 return IndexedDBBackingStore::Open(this, 332 origin_url, 333 data_directory, 334 request_context, 335 data_loss, 336 data_loss_message, 337 disk_full, 338 context_->TaskRunner(), 339 first_time); 340 } 341 342 scoped_refptr<IndexedDBBackingStore> IndexedDBFactory::OpenBackingStore( 343 const GURL& origin_url, 344 const base::FilePath& data_directory, 345 net::URLRequestContext* request_context, 346 blink::WebIDBDataLoss* data_loss, 347 std::string* data_loss_message, 348 bool* disk_full) { 349 const bool open_in_memory = data_directory.empty(); 350 351 IndexedDBBackingStoreMap::iterator it2 = backing_store_map_.find(origin_url); 352 if (it2 != backing_store_map_.end()) { 353 it2->second->close_timer()->Stop(); 354 return it2->second; 355 } 356 357 scoped_refptr<IndexedDBBackingStore> backing_store; 358 bool first_time = false; 359 if (open_in_memory) { 360 backing_store = 361 IndexedDBBackingStore::OpenInMemory(origin_url, context_->TaskRunner()); 362 } else { 363 first_time = !backends_opened_since_boot_.count(origin_url); 364 365 backing_store = OpenBackingStoreHelper(origin_url, 366 data_directory, 367 request_context, 368 data_loss, 369 data_loss_message, 370 disk_full, 371 first_time); 372 } 373 374 if (backing_store.get()) { 375 if (first_time) 376 backends_opened_since_boot_.insert(origin_url); 377 backing_store_map_[origin_url] = backing_store; 378 // If an in-memory database, bind lifetime to this factory instance. 379 if (open_in_memory) 380 session_only_backing_stores_.insert(backing_store); 381 382 // All backing stores associated with this factory should be of the same 383 // type. 384 DCHECK_NE(session_only_backing_stores_.empty(), open_in_memory); 385 386 return backing_store; 387 } 388 389 return 0; 390 } 391 392 void IndexedDBFactory::Open(const base::string16& name, 393 const IndexedDBPendingConnection& connection, 394 net::URLRequestContext* request_context, 395 const GURL& origin_url, 396 const base::FilePath& data_directory) { 397 IDB_TRACE("IndexedDBFactory::Open"); 398 scoped_refptr<IndexedDBDatabase> database; 399 IndexedDBDatabase::Identifier unique_identifier(origin_url, name); 400 IndexedDBDatabaseMap::iterator it = database_map_.find(unique_identifier); 401 blink::WebIDBDataLoss data_loss = 402 blink::WebIDBDataLossNone; 403 std::string data_loss_message; 404 bool disk_full = false; 405 bool was_open = (it != database_map_.end()); 406 if (!was_open) { 407 scoped_refptr<IndexedDBBackingStore> backing_store = 408 OpenBackingStore(origin_url, 409 data_directory, 410 request_context, 411 &data_loss, 412 &data_loss_message, 413 &disk_full); 414 if (!backing_store) { 415 if (disk_full) { 416 connection.callbacks->OnError( 417 IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionQuotaError, 418 ASCIIToUTF16( 419 "Encountered full disk while opening " 420 "backing store for indexedDB.open."))); 421 return; 422 } 423 connection.callbacks->OnError(IndexedDBDatabaseError( 424 blink::WebIDBDatabaseExceptionUnknownError, 425 ASCIIToUTF16( 426 "Internal error opening backing store for indexedDB.open."))); 427 return; 428 } 429 430 leveldb::Status s; 431 database = IndexedDBDatabase::Create( 432 name, backing_store, this, unique_identifier, &s); 433 if (!database) { 434 DLOG(ERROR) << "Unable to create the database"; 435 IndexedDBDatabaseError error(blink::WebIDBDatabaseExceptionUnknownError, 436 ASCIIToUTF16( 437 "Internal error creating " 438 "database backend for " 439 "indexedDB.open.")); 440 connection.callbacks->OnError(error); 441 if (leveldb_env::IsCorruption(s)) { 442 backing_store = NULL; // Closes the LevelDB so that it can be deleted 443 HandleBackingStoreCorruption(origin_url, error); 444 } 445 return; 446 } 447 } else { 448 database = it->second; 449 } 450 451 if (data_loss != blink::WebIDBDataLossNone) 452 connection.callbacks->OnDataLoss(data_loss, data_loss_message); 453 454 database->OpenConnection(connection); 455 456 if (!was_open && database->ConnectionCount() > 0) { 457 database_map_[unique_identifier] = database; 458 origin_dbs_.insert(std::make_pair(origin_url, database)); 459 } 460 } 461 462 std::pair<IndexedDBFactory::OriginDBMapIterator, 463 IndexedDBFactory::OriginDBMapIterator> 464 IndexedDBFactory::GetOpenDatabasesForOrigin(const GURL& origin_url) const { 465 return origin_dbs_.equal_range(origin_url); 466 } 467 468 size_t IndexedDBFactory::GetConnectionCount(const GURL& origin_url) const { 469 size_t count(0); 470 471 std::pair<OriginDBMapIterator, OriginDBMapIterator> range = 472 GetOpenDatabasesForOrigin(origin_url); 473 for (OriginDBMapIterator it = range.first; it != range.second; ++it) 474 count += it->second->ConnectionCount(); 475 476 return count; 477 } 478 479 } // namespace content 480