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