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/dom_storage/dom_storage_context_impl.h" 6 7 #include "base/bind.h" 8 #include "base/bind_helpers.h" 9 #include "base/files/file_enumerator.h" 10 #include "base/files/file_util.h" 11 #include "base/guid.h" 12 #include "base/location.h" 13 #include "base/time/time.h" 14 #include "content/browser/dom_storage/dom_storage_area.h" 15 #include "content/browser/dom_storage/dom_storage_database.h" 16 #include "content/browser/dom_storage/dom_storage_namespace.h" 17 #include "content/browser/dom_storage/dom_storage_task_runner.h" 18 #include "content/browser/dom_storage/session_storage_database.h" 19 #include "content/common/dom_storage/dom_storage_types.h" 20 #include "content/public/browser/dom_storage_context.h" 21 #include "content/public/browser/local_storage_usage_info.h" 22 #include "content/public/browser/session_storage_usage_info.h" 23 #include "storage/browser/quota/special_storage_policy.h" 24 25 namespace content { 26 27 static const int kSessionStoraceScavengingSeconds = 60; 28 29 DOMStorageContextImpl::DOMStorageContextImpl( 30 const base::FilePath& localstorage_directory, 31 const base::FilePath& sessionstorage_directory, 32 storage::SpecialStoragePolicy* special_storage_policy, 33 DOMStorageTaskRunner* task_runner) 34 : localstorage_directory_(localstorage_directory), 35 sessionstorage_directory_(sessionstorage_directory), 36 task_runner_(task_runner), 37 is_shutdown_(false), 38 force_keep_session_state_(false), 39 special_storage_policy_(special_storage_policy), 40 scavenging_started_(false) { 41 // AtomicSequenceNum starts at 0 but we want to start session 42 // namespace ids at one since zero is reserved for the 43 // kLocalStorageNamespaceId. 44 session_id_sequence_.GetNext(); 45 } 46 47 DOMStorageContextImpl::~DOMStorageContextImpl() { 48 if (session_storage_database_.get()) { 49 // SessionStorageDatabase shouldn't be deleted right away: deleting it will 50 // potentially involve waiting in leveldb::DBImpl::~DBImpl, and waiting 51 // shouldn't happen on this thread. 52 SessionStorageDatabase* to_release = session_storage_database_.get(); 53 to_release->AddRef(); 54 session_storage_database_ = NULL; 55 task_runner_->PostShutdownBlockingTask( 56 FROM_HERE, 57 DOMStorageTaskRunner::COMMIT_SEQUENCE, 58 base::Bind(&SessionStorageDatabase::Release, 59 base::Unretained(to_release))); 60 } 61 } 62 63 DOMStorageNamespace* DOMStorageContextImpl::GetStorageNamespace( 64 int64 namespace_id) { 65 if (is_shutdown_) 66 return NULL; 67 StorageNamespaceMap::iterator found = namespaces_.find(namespace_id); 68 if (found == namespaces_.end()) { 69 if (namespace_id == kLocalStorageNamespaceId) { 70 if (!localstorage_directory_.empty()) { 71 if (!base::CreateDirectory(localstorage_directory_)) { 72 LOG(ERROR) << "Failed to create 'Local Storage' directory," 73 " falling back to in-memory only."; 74 localstorage_directory_ = base::FilePath(); 75 } 76 } 77 DOMStorageNamespace* local = 78 new DOMStorageNamespace(localstorage_directory_, task_runner_.get()); 79 namespaces_[kLocalStorageNamespaceId] = local; 80 return local; 81 } 82 return NULL; 83 } 84 return found->second.get(); 85 } 86 87 void DOMStorageContextImpl::GetLocalStorageUsage( 88 std::vector<LocalStorageUsageInfo>* infos, 89 bool include_file_info) { 90 if (localstorage_directory_.empty()) 91 return; 92 base::FileEnumerator enumerator(localstorage_directory_, false, 93 base::FileEnumerator::FILES); 94 for (base::FilePath path = enumerator.Next(); !path.empty(); 95 path = enumerator.Next()) { 96 if (path.MatchesExtension(DOMStorageArea::kDatabaseFileExtension)) { 97 LocalStorageUsageInfo info; 98 info.origin = DOMStorageArea::OriginFromDatabaseFileName(path); 99 if (include_file_info) { 100 base::FileEnumerator::FileInfo find_info = enumerator.GetInfo(); 101 info.data_size = find_info.GetSize(); 102 info.last_modified = find_info.GetLastModifiedTime(); 103 } 104 infos->push_back(info); 105 } 106 } 107 } 108 109 void DOMStorageContextImpl::GetSessionStorageUsage( 110 std::vector<SessionStorageUsageInfo>* infos) { 111 if (!session_storage_database_.get()) 112 return; 113 std::map<std::string, std::vector<GURL> > namespaces_and_origins; 114 session_storage_database_->ReadNamespacesAndOrigins( 115 &namespaces_and_origins); 116 for (std::map<std::string, std::vector<GURL> >::const_iterator it = 117 namespaces_and_origins.begin(); 118 it != namespaces_and_origins.end(); ++it) { 119 for (std::vector<GURL>::const_iterator origin_it = it->second.begin(); 120 origin_it != it->second.end(); ++origin_it) { 121 SessionStorageUsageInfo info; 122 info.persistent_namespace_id = it->first; 123 info.origin = *origin_it; 124 infos->push_back(info); 125 } 126 } 127 } 128 129 void DOMStorageContextImpl::DeleteLocalStorage(const GURL& origin) { 130 DCHECK(!is_shutdown_); 131 DOMStorageNamespace* local = GetStorageNamespace(kLocalStorageNamespaceId); 132 local->DeleteLocalStorageOrigin(origin); 133 // Synthesize a 'cleared' event if the area is open so CachedAreas in 134 // renderers get emptied out too. 135 DOMStorageArea* area = local->GetOpenStorageArea(origin); 136 if (area) 137 NotifyAreaCleared(area, origin); 138 } 139 140 void DOMStorageContextImpl::DeleteSessionStorage( 141 const SessionStorageUsageInfo& usage_info) { 142 DCHECK(!is_shutdown_); 143 DOMStorageNamespace* dom_storage_namespace = NULL; 144 std::map<std::string, int64>::const_iterator it = 145 persistent_namespace_id_to_namespace_id_.find( 146 usage_info.persistent_namespace_id); 147 if (it != persistent_namespace_id_to_namespace_id_.end()) { 148 dom_storage_namespace = GetStorageNamespace(it->second); 149 } else { 150 int64 namespace_id = AllocateSessionId(); 151 CreateSessionNamespace(namespace_id, usage_info.persistent_namespace_id); 152 dom_storage_namespace = GetStorageNamespace(namespace_id); 153 } 154 dom_storage_namespace->DeleteSessionStorageOrigin(usage_info.origin); 155 // Synthesize a 'cleared' event if the area is open so CachedAreas in 156 // renderers get emptied out too. 157 DOMStorageArea* area = 158 dom_storage_namespace->GetOpenStorageArea(usage_info.origin); 159 if (area) 160 NotifyAreaCleared(area, usage_info.origin); 161 } 162 163 void DOMStorageContextImpl::Shutdown() { 164 is_shutdown_ = true; 165 StorageNamespaceMap::const_iterator it = namespaces_.begin(); 166 for (; it != namespaces_.end(); ++it) 167 it->second->Shutdown(); 168 169 if (localstorage_directory_.empty() && !session_storage_database_.get()) 170 return; 171 172 // Respect the content policy settings about what to 173 // keep and what to discard. 174 if (force_keep_session_state_) 175 return; // Keep everything. 176 177 bool has_session_only_origins = 178 special_storage_policy_.get() && 179 special_storage_policy_->HasSessionOnlyOrigins(); 180 181 if (has_session_only_origins) { 182 // We may have to delete something. We continue on the 183 // commit sequence after area shutdown tasks have cycled 184 // thru that sequence (and closed their database files). 185 bool success = task_runner_->PostShutdownBlockingTask( 186 FROM_HERE, 187 DOMStorageTaskRunner::COMMIT_SEQUENCE, 188 base::Bind(&DOMStorageContextImpl::ClearSessionOnlyOrigins, this)); 189 DCHECK(success); 190 } 191 } 192 193 void DOMStorageContextImpl::AddEventObserver(EventObserver* observer) { 194 event_observers_.AddObserver(observer); 195 } 196 197 void DOMStorageContextImpl::RemoveEventObserver(EventObserver* observer) { 198 event_observers_.RemoveObserver(observer); 199 } 200 201 void DOMStorageContextImpl::NotifyItemSet( 202 const DOMStorageArea* area, 203 const base::string16& key, 204 const base::string16& new_value, 205 const base::NullableString16& old_value, 206 const GURL& page_url) { 207 FOR_EACH_OBSERVER( 208 EventObserver, event_observers_, 209 OnDOMStorageItemSet(area, key, new_value, old_value, page_url)); 210 } 211 212 void DOMStorageContextImpl::NotifyItemRemoved( 213 const DOMStorageArea* area, 214 const base::string16& key, 215 const base::string16& old_value, 216 const GURL& page_url) { 217 FOR_EACH_OBSERVER( 218 EventObserver, event_observers_, 219 OnDOMStorageItemRemoved(area, key, old_value, page_url)); 220 } 221 222 void DOMStorageContextImpl::NotifyAreaCleared( 223 const DOMStorageArea* area, 224 const GURL& page_url) { 225 FOR_EACH_OBSERVER( 226 EventObserver, event_observers_, 227 OnDOMStorageAreaCleared(area, page_url)); 228 } 229 230 void DOMStorageContextImpl::NotifyAliasSessionMerged( 231 int64 namespace_id, 232 DOMStorageNamespace* old_alias_master_namespace) { 233 FOR_EACH_OBSERVER( 234 EventObserver, event_observers_, 235 OnDOMSessionStorageReset(namespace_id)); 236 if (old_alias_master_namespace) 237 MaybeShutdownSessionNamespace(old_alias_master_namespace); 238 } 239 240 std::string DOMStorageContextImpl::AllocatePersistentSessionId() { 241 std::string guid = base::GenerateGUID(); 242 std::replace(guid.begin(), guid.end(), '-', '_'); 243 return guid; 244 } 245 246 void DOMStorageContextImpl::CreateSessionNamespace( 247 int64 namespace_id, 248 const std::string& persistent_namespace_id) { 249 if (is_shutdown_) 250 return; 251 DCHECK(namespace_id != kLocalStorageNamespaceId); 252 DCHECK(namespaces_.find(namespace_id) == namespaces_.end()); 253 namespaces_[namespace_id] = new DOMStorageNamespace( 254 namespace_id, persistent_namespace_id, session_storage_database_.get(), 255 task_runner_.get()); 256 persistent_namespace_id_to_namespace_id_[persistent_namespace_id] = 257 namespace_id; 258 } 259 260 void DOMStorageContextImpl::DeleteSessionNamespace( 261 int64 namespace_id, bool should_persist_data) { 262 DCHECK_NE(kLocalStorageNamespaceId, namespace_id); 263 StorageNamespaceMap::const_iterator it = namespaces_.find(namespace_id); 264 if (it == namespaces_.end() || 265 it->second->ready_for_deletion_pending_aliases()) { 266 return; 267 } 268 it->second->set_ready_for_deletion_pending_aliases(true); 269 DOMStorageNamespace* alias_master = it->second->alias_master_namespace(); 270 if (alias_master) { 271 DCHECK(it->second->num_aliases() == 0); 272 DCHECK(alias_master->alias_master_namespace() == NULL); 273 if (should_persist_data) 274 alias_master->set_must_persist_at_shutdown(true); 275 if (it->second->DecrementMasterAliasCount()) 276 MaybeShutdownSessionNamespace(alias_master); 277 namespaces_.erase(namespace_id); 278 } else { 279 if (should_persist_data) 280 it->second->set_must_persist_at_shutdown(true); 281 MaybeShutdownSessionNamespace(it->second.get()); 282 } 283 } 284 285 void DOMStorageContextImpl::MaybeShutdownSessionNamespace( 286 DOMStorageNamespace* ns) { 287 if (ns->num_aliases() > 0 || !ns->ready_for_deletion_pending_aliases()) 288 return; 289 DCHECK_EQ(ns->num_aliases(), 0); 290 DCHECK(ns->alias_master_namespace() == NULL); 291 std::string persistent_namespace_id = ns->persistent_namespace_id(); 292 if (session_storage_database_.get()) { 293 if (!ns->must_persist_at_shutdown()) { 294 task_runner_->PostShutdownBlockingTask( 295 FROM_HERE, 296 DOMStorageTaskRunner::COMMIT_SEQUENCE, 297 base::Bind( 298 base::IgnoreResult(&SessionStorageDatabase::DeleteNamespace), 299 session_storage_database_, 300 persistent_namespace_id)); 301 } else { 302 // Ensure that the data gets committed before we shut down. 303 ns->Shutdown(); 304 if (!scavenging_started_) { 305 // Protect the persistent namespace ID from scavenging. 306 protected_persistent_session_ids_.insert(persistent_namespace_id); 307 } 308 } 309 } 310 persistent_namespace_id_to_namespace_id_.erase(persistent_namespace_id); 311 namespaces_.erase(ns->namespace_id()); 312 } 313 314 void DOMStorageContextImpl::CloneSessionNamespace( 315 int64 existing_id, int64 new_id, 316 const std::string& new_persistent_id) { 317 if (is_shutdown_) 318 return; 319 DCHECK_NE(kLocalStorageNamespaceId, existing_id); 320 DCHECK_NE(kLocalStorageNamespaceId, new_id); 321 StorageNamespaceMap::iterator found = namespaces_.find(existing_id); 322 if (found != namespaces_.end()) 323 namespaces_[new_id] = found->second->Clone(new_id, new_persistent_id); 324 else 325 CreateSessionNamespace(new_id, new_persistent_id); 326 } 327 328 void DOMStorageContextImpl::CreateAliasSessionNamespace( 329 int64 existing_id, int64 new_id, 330 const std::string& persistent_id) { 331 if (is_shutdown_) 332 return; 333 DCHECK_NE(kLocalStorageNamespaceId, existing_id); 334 DCHECK_NE(kLocalStorageNamespaceId, new_id); 335 StorageNamespaceMap::iterator found = namespaces_.find(existing_id); 336 if (found != namespaces_.end()) { 337 namespaces_[new_id] = found->second->CreateAlias(new_id); 338 } else { 339 CreateSessionNamespace(new_id, persistent_id); 340 } 341 } 342 343 void DOMStorageContextImpl::ClearSessionOnlyOrigins() { 344 if (!localstorage_directory_.empty()) { 345 std::vector<LocalStorageUsageInfo> infos; 346 const bool kDontIncludeFileInfo = false; 347 GetLocalStorageUsage(&infos, kDontIncludeFileInfo); 348 for (size_t i = 0; i < infos.size(); ++i) { 349 const GURL& origin = infos[i].origin; 350 if (special_storage_policy_->IsStorageProtected(origin)) 351 continue; 352 if (!special_storage_policy_->IsStorageSessionOnly(origin)) 353 continue; 354 355 base::FilePath database_file_path = localstorage_directory_.Append( 356 DOMStorageArea::DatabaseFileNameFromOrigin(origin)); 357 sql::Connection::Delete(database_file_path); 358 } 359 } 360 if (session_storage_database_.get()) { 361 std::vector<SessionStorageUsageInfo> infos; 362 GetSessionStorageUsage(&infos); 363 for (size_t i = 0; i < infos.size(); ++i) { 364 const GURL& origin = infos[i].origin; 365 if (special_storage_policy_->IsStorageProtected(origin)) 366 continue; 367 if (!special_storage_policy_->IsStorageSessionOnly(origin)) 368 continue; 369 session_storage_database_->DeleteArea(infos[i].persistent_namespace_id, 370 origin); 371 } 372 } 373 } 374 375 void DOMStorageContextImpl::SetSaveSessionStorageOnDisk() { 376 DCHECK(namespaces_.empty()); 377 if (!sessionstorage_directory_.empty()) { 378 session_storage_database_ = new SessionStorageDatabase( 379 sessionstorage_directory_); 380 } 381 } 382 383 void DOMStorageContextImpl::StartScavengingUnusedSessionStorage() { 384 if (session_storage_database_.get()) { 385 task_runner_->PostDelayedTask( 386 FROM_HERE, base::Bind(&DOMStorageContextImpl::FindUnusedNamespaces, 387 this), 388 base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds)); 389 } 390 } 391 392 void DOMStorageContextImpl::FindUnusedNamespaces() { 393 DCHECK(session_storage_database_.get()); 394 if (scavenging_started_) 395 return; 396 scavenging_started_ = true; 397 std::set<std::string> namespace_ids_in_use; 398 for (StorageNamespaceMap::const_iterator it = namespaces_.begin(); 399 it != namespaces_.end(); ++it) 400 namespace_ids_in_use.insert(it->second->persistent_namespace_id()); 401 std::set<std::string> protected_persistent_session_ids; 402 protected_persistent_session_ids.swap(protected_persistent_session_ids_); 403 task_runner_->PostShutdownBlockingTask( 404 FROM_HERE, DOMStorageTaskRunner::COMMIT_SEQUENCE, 405 base::Bind( 406 &DOMStorageContextImpl::FindUnusedNamespacesInCommitSequence, 407 this, namespace_ids_in_use, protected_persistent_session_ids)); 408 } 409 410 void DOMStorageContextImpl::FindUnusedNamespacesInCommitSequence( 411 const std::set<std::string>& namespace_ids_in_use, 412 const std::set<std::string>& protected_persistent_session_ids) { 413 DCHECK(session_storage_database_.get()); 414 // Delete all namespaces which don't have an associated DOMStorageNamespace 415 // alive. 416 std::map<std::string, std::vector<GURL> > namespaces_and_origins; 417 session_storage_database_->ReadNamespacesAndOrigins(&namespaces_and_origins); 418 for (std::map<std::string, std::vector<GURL> >::const_iterator it = 419 namespaces_and_origins.begin(); 420 it != namespaces_and_origins.end(); ++it) { 421 if (namespace_ids_in_use.find(it->first) == namespace_ids_in_use.end() && 422 protected_persistent_session_ids.find(it->first) == 423 protected_persistent_session_ids.end()) { 424 deletable_persistent_namespace_ids_.push_back(it->first); 425 } 426 } 427 if (!deletable_persistent_namespace_ids_.empty()) { 428 task_runner_->PostDelayedTask( 429 FROM_HERE, base::Bind( 430 &DOMStorageContextImpl::DeleteNextUnusedNamespace, 431 this), 432 base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds)); 433 } 434 } 435 436 void DOMStorageContextImpl::DeleteNextUnusedNamespace() { 437 if (is_shutdown_) 438 return; 439 task_runner_->PostShutdownBlockingTask( 440 FROM_HERE, DOMStorageTaskRunner::COMMIT_SEQUENCE, 441 base::Bind( 442 &DOMStorageContextImpl::DeleteNextUnusedNamespaceInCommitSequence, 443 this)); 444 } 445 446 void DOMStorageContextImpl::DeleteNextUnusedNamespaceInCommitSequence() { 447 if (deletable_persistent_namespace_ids_.empty()) 448 return; 449 const std::string& persistent_id = deletable_persistent_namespace_ids_.back(); 450 session_storage_database_->DeleteNamespace(persistent_id); 451 deletable_persistent_namespace_ids_.pop_back(); 452 if (!deletable_persistent_namespace_ids_.empty()) { 453 task_runner_->PostDelayedTask( 454 FROM_HERE, base::Bind( 455 &DOMStorageContextImpl::DeleteNextUnusedNamespace, 456 this), 457 base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds)); 458 } 459 } 460 461 void DOMStorageContextImpl::AddTransactionLogProcessId(int64 namespace_id, 462 int process_id) { 463 DCHECK_NE(kLocalStorageNamespaceId, namespace_id); 464 StorageNamespaceMap::const_iterator it = namespaces_.find(namespace_id); 465 if (it == namespaces_.end()) 466 return; 467 it->second->AddTransactionLogProcessId(process_id); 468 } 469 470 void DOMStorageContextImpl::RemoveTransactionLogProcessId(int64 namespace_id, 471 int process_id) { 472 DCHECK_NE(kLocalStorageNamespaceId, namespace_id); 473 StorageNamespaceMap::const_iterator it = namespaces_.find(namespace_id); 474 if (it == namespaces_.end()) 475 return; 476 it->second->RemoveTransactionLogProcessId(process_id); 477 } 478 479 SessionStorageNamespace::MergeResult 480 DOMStorageContextImpl::MergeSessionStorage( 481 int64 namespace1_id, bool actually_merge, int process_id, 482 int64 namespace2_id) { 483 DCHECK_NE(kLocalStorageNamespaceId, namespace1_id); 484 DCHECK_NE(kLocalStorageNamespaceId, namespace2_id); 485 StorageNamespaceMap::const_iterator it = namespaces_.find(namespace1_id); 486 if (it == namespaces_.end()) 487 return SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_FOUND; 488 DOMStorageNamespace* ns1 = it->second.get(); 489 it = namespaces_.find(namespace2_id); 490 if (it == namespaces_.end()) 491 return SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_FOUND; 492 DOMStorageNamespace* ns2 = it->second.get(); 493 return ns1->Merge(actually_merge, process_id, ns2, this); 494 } 495 496 } // namespace content 497