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