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/file_util.h" 10 #include "base/files/file_enumerator.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 "webkit/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 quota::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 (!file_util::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::PurgeMemory() { 164 // We can only purge memory from the local storage namespace 165 // which is backed by disk. 166 // TODO(marja): Purge sessionStorage, too. (Requires changes to the FastClear 167 // functionality.) 168 StorageNamespaceMap::iterator found = 169 namespaces_.find(kLocalStorageNamespaceId); 170 if (found != namespaces_.end()) 171 found->second->PurgeMemory(DOMStorageNamespace::PURGE_AGGRESSIVE); 172 } 173 174 void DOMStorageContextImpl::Shutdown() { 175 is_shutdown_ = true; 176 StorageNamespaceMap::const_iterator it = namespaces_.begin(); 177 for (; it != namespaces_.end(); ++it) 178 it->second->Shutdown(); 179 180 if (localstorage_directory_.empty() && !session_storage_database_.get()) 181 return; 182 183 // Respect the content policy settings about what to 184 // keep and what to discard. 185 if (force_keep_session_state_) 186 return; // Keep everything. 187 188 bool has_session_only_origins = 189 special_storage_policy_.get() && 190 special_storage_policy_->HasSessionOnlyOrigins(); 191 192 if (has_session_only_origins) { 193 // We may have to delete something. We continue on the 194 // commit sequence after area shutdown tasks have cycled 195 // thru that sequence (and closed their database files). 196 bool success = task_runner_->PostShutdownBlockingTask( 197 FROM_HERE, 198 DOMStorageTaskRunner::COMMIT_SEQUENCE, 199 base::Bind(&DOMStorageContextImpl::ClearSessionOnlyOrigins, this)); 200 DCHECK(success); 201 } 202 } 203 204 void DOMStorageContextImpl::AddEventObserver(EventObserver* observer) { 205 event_observers_.AddObserver(observer); 206 } 207 208 void DOMStorageContextImpl::RemoveEventObserver(EventObserver* observer) { 209 event_observers_.RemoveObserver(observer); 210 } 211 212 void DOMStorageContextImpl::NotifyItemSet( 213 const DOMStorageArea* area, 214 const base::string16& key, 215 const base::string16& new_value, 216 const base::NullableString16& old_value, 217 const GURL& page_url) { 218 FOR_EACH_OBSERVER( 219 EventObserver, event_observers_, 220 OnDOMStorageItemSet(area, key, new_value, old_value, page_url)); 221 } 222 223 void DOMStorageContextImpl::NotifyItemRemoved( 224 const DOMStorageArea* area, 225 const base::string16& key, 226 const base::string16& old_value, 227 const GURL& page_url) { 228 FOR_EACH_OBSERVER( 229 EventObserver, event_observers_, 230 OnDOMStorageItemRemoved(area, key, old_value, page_url)); 231 } 232 233 void DOMStorageContextImpl::NotifyAreaCleared( 234 const DOMStorageArea* area, 235 const GURL& page_url) { 236 FOR_EACH_OBSERVER( 237 EventObserver, event_observers_, 238 OnDOMStorageAreaCleared(area, page_url)); 239 } 240 241 std::string DOMStorageContextImpl::AllocatePersistentSessionId() { 242 std::string guid = base::GenerateGUID(); 243 std::replace(guid.begin(), guid.end(), '-', '_'); 244 return guid; 245 } 246 247 void DOMStorageContextImpl::CreateSessionNamespace( 248 int64 namespace_id, 249 const std::string& persistent_namespace_id) { 250 if (is_shutdown_) 251 return; 252 DCHECK(namespace_id != kLocalStorageNamespaceId); 253 DCHECK(namespaces_.find(namespace_id) == namespaces_.end()); 254 namespaces_[namespace_id] = new DOMStorageNamespace( 255 namespace_id, persistent_namespace_id, session_storage_database_.get(), 256 task_runner_.get()); 257 persistent_namespace_id_to_namespace_id_[persistent_namespace_id] = 258 namespace_id; 259 } 260 261 void DOMStorageContextImpl::DeleteSessionNamespace( 262 int64 namespace_id, bool should_persist_data) { 263 DCHECK_NE(kLocalStorageNamespaceId, namespace_id); 264 StorageNamespaceMap::const_iterator it = namespaces_.find(namespace_id); 265 if (it == namespaces_.end()) 266 return; 267 std::string persistent_namespace_id = it->second->persistent_namespace_id(); 268 if (session_storage_database_.get()) { 269 if (!should_persist_data) { 270 task_runner_->PostShutdownBlockingTask( 271 FROM_HERE, 272 DOMStorageTaskRunner::COMMIT_SEQUENCE, 273 base::Bind( 274 base::IgnoreResult(&SessionStorageDatabase::DeleteNamespace), 275 session_storage_database_, 276 persistent_namespace_id)); 277 } else { 278 // Ensure that the data gets committed before we shut down. 279 it->second->Shutdown(); 280 if (!scavenging_started_) { 281 // Protect the persistent namespace ID from scavenging. 282 protected_persistent_session_ids_.insert(persistent_namespace_id); 283 } 284 } 285 } 286 persistent_namespace_id_to_namespace_id_.erase(persistent_namespace_id); 287 namespaces_.erase(namespace_id); 288 } 289 290 void DOMStorageContextImpl::CloneSessionNamespace( 291 int64 existing_id, int64 new_id, 292 const std::string& new_persistent_id) { 293 if (is_shutdown_) 294 return; 295 DCHECK_NE(kLocalStorageNamespaceId, existing_id); 296 DCHECK_NE(kLocalStorageNamespaceId, new_id); 297 StorageNamespaceMap::iterator found = namespaces_.find(existing_id); 298 if (found != namespaces_.end()) 299 namespaces_[new_id] = found->second->Clone(new_id, new_persistent_id); 300 else 301 CreateSessionNamespace(new_id, new_persistent_id); 302 } 303 304 void DOMStorageContextImpl::ClearSessionOnlyOrigins() { 305 if (!localstorage_directory_.empty()) { 306 std::vector<LocalStorageUsageInfo> infos; 307 const bool kDontIncludeFileInfo = false; 308 GetLocalStorageUsage(&infos, kDontIncludeFileInfo); 309 for (size_t i = 0; i < infos.size(); ++i) { 310 const GURL& origin = infos[i].origin; 311 if (special_storage_policy_->IsStorageProtected(origin)) 312 continue; 313 if (!special_storage_policy_->IsStorageSessionOnly(origin)) 314 continue; 315 316 base::FilePath database_file_path = localstorage_directory_.Append( 317 DOMStorageArea::DatabaseFileNameFromOrigin(origin)); 318 sql::Connection::Delete(database_file_path); 319 } 320 } 321 if (session_storage_database_.get()) { 322 std::vector<SessionStorageUsageInfo> infos; 323 GetSessionStorageUsage(&infos); 324 for (size_t i = 0; i < infos.size(); ++i) { 325 const GURL& origin = infos[i].origin; 326 if (special_storage_policy_->IsStorageProtected(origin)) 327 continue; 328 if (!special_storage_policy_->IsStorageSessionOnly(origin)) 329 continue; 330 session_storage_database_->DeleteArea(infos[i].persistent_namespace_id, 331 origin); 332 } 333 } 334 } 335 336 void DOMStorageContextImpl::SetSaveSessionStorageOnDisk() { 337 DCHECK(namespaces_.empty()); 338 if (!sessionstorage_directory_.empty()) { 339 session_storage_database_ = new SessionStorageDatabase( 340 sessionstorage_directory_); 341 } 342 } 343 344 void DOMStorageContextImpl::StartScavengingUnusedSessionStorage() { 345 if (session_storage_database_.get()) { 346 task_runner_->PostDelayedTask( 347 FROM_HERE, base::Bind(&DOMStorageContextImpl::FindUnusedNamespaces, 348 this), 349 base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds)); 350 } 351 } 352 353 void DOMStorageContextImpl::FindUnusedNamespaces() { 354 DCHECK(session_storage_database_.get()); 355 if (scavenging_started_) 356 return; 357 scavenging_started_ = true; 358 std::set<std::string> namespace_ids_in_use; 359 for (StorageNamespaceMap::const_iterator it = namespaces_.begin(); 360 it != namespaces_.end(); ++it) 361 namespace_ids_in_use.insert(it->second->persistent_namespace_id()); 362 std::set<std::string> protected_persistent_session_ids; 363 protected_persistent_session_ids.swap(protected_persistent_session_ids_); 364 task_runner_->PostShutdownBlockingTask( 365 FROM_HERE, DOMStorageTaskRunner::COMMIT_SEQUENCE, 366 base::Bind( 367 &DOMStorageContextImpl::FindUnusedNamespacesInCommitSequence, 368 this, namespace_ids_in_use, protected_persistent_session_ids)); 369 } 370 371 void DOMStorageContextImpl::FindUnusedNamespacesInCommitSequence( 372 const std::set<std::string>& namespace_ids_in_use, 373 const std::set<std::string>& protected_persistent_session_ids) { 374 DCHECK(session_storage_database_.get()); 375 // Delete all namespaces which don't have an associated DOMStorageNamespace 376 // alive. 377 std::map<std::string, std::vector<GURL> > namespaces_and_origins; 378 session_storage_database_->ReadNamespacesAndOrigins(&namespaces_and_origins); 379 for (std::map<std::string, std::vector<GURL> >::const_iterator it = 380 namespaces_and_origins.begin(); 381 it != namespaces_and_origins.end(); ++it) { 382 if (namespace_ids_in_use.find(it->first) == namespace_ids_in_use.end() && 383 protected_persistent_session_ids.find(it->first) == 384 protected_persistent_session_ids.end()) { 385 deletable_persistent_namespace_ids_.push_back(it->first); 386 } 387 } 388 if (!deletable_persistent_namespace_ids_.empty()) { 389 task_runner_->PostDelayedTask( 390 FROM_HERE, base::Bind( 391 &DOMStorageContextImpl::DeleteNextUnusedNamespace, 392 this), 393 base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds)); 394 } 395 } 396 397 void DOMStorageContextImpl::DeleteNextUnusedNamespace() { 398 if (is_shutdown_) 399 return; 400 task_runner_->PostShutdownBlockingTask( 401 FROM_HERE, DOMStorageTaskRunner::COMMIT_SEQUENCE, 402 base::Bind( 403 &DOMStorageContextImpl::DeleteNextUnusedNamespaceInCommitSequence, 404 this)); 405 } 406 407 void DOMStorageContextImpl::DeleteNextUnusedNamespaceInCommitSequence() { 408 if (deletable_persistent_namespace_ids_.empty()) 409 return; 410 const std::string& persistent_id = deletable_persistent_namespace_ids_.back(); 411 session_storage_database_->DeleteNamespace(persistent_id); 412 deletable_persistent_namespace_ids_.pop_back(); 413 if (!deletable_persistent_namespace_ids_.empty()) { 414 task_runner_->PostDelayedTask( 415 FROM_HERE, base::Bind( 416 &DOMStorageContextImpl::DeleteNextUnusedNamespace, 417 this), 418 base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds)); 419 } 420 } 421 422 } // namespace content 423