| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/dom_storage/dom_storage_context_impl.h" |
| |
| #include <inttypes.h> |
| #include <stddef.h> |
| #include <stdlib.h> |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/files/file_enumerator.h" |
| #include "base/files/file_util.h" |
| #include "base/guid.h" |
| #include "base/location.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/stl_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/sys_info.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/memory_dump_manager.h" |
| #include "base/trace_event/process_memory_dump.h" |
| #include "content/browser/dom_storage/dom_storage_area.h" |
| #include "content/browser/dom_storage/dom_storage_database.h" |
| #include "content/browser/dom_storage/dom_storage_namespace.h" |
| #include "content/browser/dom_storage/dom_storage_task_runner.h" |
| #include "content/browser/dom_storage/session_storage_database.h" |
| #include "content/common/dom_storage/dom_storage_types.h" |
| #include "content/public/browser/dom_storage_context.h" |
| #include "content/public/browser/local_storage_usage_info.h" |
| #include "content/public/browser/session_storage_usage_info.h" |
| #include "storage/browser/quota/special_storage_policy.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| namespace content { |
| namespace { |
| |
| // Limits on the cache size and number of areas in memory, over which the areas |
| // are purged. |
| #if defined(OS_ANDROID) |
| const unsigned kMaxStorageAreaCount = 20; |
| const size_t kMaxCacheSize = 2 * 1024 * 1024; |
| #else |
| const unsigned kMaxStorageAreaCount = 100; |
| const size_t kMaxCacheSize = 20 * 1024 * 1024; |
| #endif |
| |
| const int kSessionStoraceScavengingSeconds = 60; |
| |
| // Offset the session storage namespace ids generated by different contexts |
| // to help identify when an id from one is mistakenly used in another. |
| int g_session_id_offset_sequence = 1; |
| const int kSessionIdOffsetAmount = 10000; |
| |
| // Aggregates statistics from all the namespaces. |
| DOMStorageNamespace::UsageStatistics GetTotalNamespaceStatistics( |
| const DOMStorageContextImpl::StorageNamespaceMap& namespace_map) { |
| DOMStorageNamespace::UsageStatistics total_stats = {0}; |
| for (const auto& it : namespace_map) { |
| DOMStorageNamespace::UsageStatistics stats = |
| it.second->GetUsageStatistics(); |
| total_stats.total_cache_size += stats.total_cache_size; |
| total_stats.total_area_count += stats.total_area_count; |
| total_stats.inactive_area_count += stats.inactive_area_count; |
| } |
| return total_stats; |
| } |
| |
| } // namespace |
| |
| DOMStorageContextImpl::DOMStorageContextImpl( |
| const base::FilePath& localstorage_directory, |
| const base::FilePath& sessionstorage_directory, |
| storage::SpecialStoragePolicy* special_storage_policy, |
| scoped_refptr<DOMStorageTaskRunner> task_runner) |
| : localstorage_directory_(localstorage_directory), |
| sessionstorage_directory_(sessionstorage_directory), |
| task_runner_(std::move(task_runner)), |
| session_id_offset_(abs((g_session_id_offset_sequence++ % 10)) * |
| kSessionIdOffsetAmount), |
| session_id_sequence_(session_id_offset_), |
| is_shutdown_(false), |
| force_keep_session_state_(false), |
| special_storage_policy_(special_storage_policy), |
| scavenging_started_(false), |
| is_low_end_device_(base::SysInfo::IsLowEndDevice()) { |
| // Tests may run without task runners. |
| if (task_runner_) { |
| // Registering dump provider is safe even outside the task runner. |
| base::trace_event::MemoryDumpManager::GetInstance() |
| ->RegisterDumpProviderWithSequencedTaskRunner( |
| this, "DOMStorage", task_runner_->GetSequencedTaskRunner( |
| DOMStorageTaskRunner::PRIMARY_SEQUENCE), |
| base::trace_event::MemoryDumpProvider::Options()); |
| } |
| } |
| |
| DOMStorageContextImpl::~DOMStorageContextImpl() { |
| DCHECK(is_shutdown_); |
| if (session_storage_database_.get()) { |
| // SessionStorageDatabase shouldn't be deleted right away: deleting it will |
| // potentially involve waiting in leveldb::DBImpl::~DBImpl, and waiting |
| // shouldn't happen on this thread. |
| SessionStorageDatabase* to_release = session_storage_database_.get(); |
| to_release->AddRef(); |
| session_storage_database_ = NULL; |
| task_runner_->PostShutdownBlockingTask( |
| FROM_HERE, |
| DOMStorageTaskRunner::COMMIT_SEQUENCE, |
| base::Bind(&SessionStorageDatabase::Release, |
| base::Unretained(to_release))); |
| } |
| } |
| |
| DOMStorageNamespace* DOMStorageContextImpl::GetStorageNamespace( |
| int64_t namespace_id) { |
| if (is_shutdown_) |
| return NULL; |
| StorageNamespaceMap::iterator found = namespaces_.find(namespace_id); |
| if (found == namespaces_.end()) { |
| if (namespace_id == kLocalStorageNamespaceId) { |
| if (!localstorage_directory_.empty()) { |
| if (!base::CreateDirectory(localstorage_directory_)) { |
| LOG(ERROR) << "Failed to create 'Local Storage' directory," |
| " falling back to in-memory only."; |
| localstorage_directory_ = base::FilePath(); |
| } |
| } |
| DOMStorageNamespace* local = |
| new DOMStorageNamespace(localstorage_directory_, task_runner_.get()); |
| namespaces_[kLocalStorageNamespaceId] = local; |
| return local; |
| } |
| return NULL; |
| } |
| return found->second.get(); |
| } |
| |
| void DOMStorageContextImpl::GetLocalStorageUsage( |
| std::vector<LocalStorageUsageInfo>* infos, |
| bool include_file_info) { |
| if (localstorage_directory_.empty()) { |
| DOMStorageNamespace* local = GetStorageNamespace(kLocalStorageNamespaceId); |
| std::vector<GURL> origins; |
| local->GetOriginsWithAreas(&origins); |
| for (const GURL& origin : origins) { |
| LocalStorageUsageInfo info; |
| info.origin = origin; |
| infos->push_back(info); |
| } |
| return; |
| } |
| |
| base::FileEnumerator enumerator(localstorage_directory_, false, |
| base::FileEnumerator::FILES); |
| for (base::FilePath path = enumerator.Next(); !path.empty(); |
| path = enumerator.Next()) { |
| if (path.MatchesExtension(DOMStorageArea::kDatabaseFileExtension)) { |
| LocalStorageUsageInfo info; |
| info.origin = DOMStorageArea::OriginFromDatabaseFileName(path); |
| if (include_file_info) { |
| base::FileEnumerator::FileInfo find_info = enumerator.GetInfo(); |
| info.data_size = find_info.GetSize(); |
| info.last_modified = find_info.GetLastModifiedTime(); |
| } |
| infos->push_back(info); |
| } |
| } |
| } |
| |
| void DOMStorageContextImpl::GetSessionStorageUsage( |
| std::vector<SessionStorageUsageInfo>* infos) { |
| if (!session_storage_database_.get()) { |
| for (const auto& entry : namespaces_) { |
| std::vector<GURL> origins; |
| entry.second->GetOriginsWithAreas(&origins); |
| for (const GURL& origin : origins) { |
| SessionStorageUsageInfo info; |
| info.persistent_namespace_id = entry.second->persistent_namespace_id(); |
| info.origin = origin; |
| infos->push_back(info); |
| } |
| } |
| return; |
| } |
| |
| std::map<std::string, std::vector<GURL> > namespaces_and_origins; |
| session_storage_database_->ReadNamespacesAndOrigins( |
| &namespaces_and_origins); |
| for (std::map<std::string, std::vector<GURL> >::const_iterator it = |
| namespaces_and_origins.begin(); |
| it != namespaces_and_origins.end(); ++it) { |
| for (std::vector<GURL>::const_iterator origin_it = it->second.begin(); |
| origin_it != it->second.end(); ++origin_it) { |
| SessionStorageUsageInfo info; |
| info.persistent_namespace_id = it->first; |
| info.origin = *origin_it; |
| infos->push_back(info); |
| } |
| } |
| } |
| |
| void DOMStorageContextImpl::DeleteLocalStorageForPhysicalOrigin( |
| const GURL& origin_url) { |
| DCHECK(!is_shutdown_); |
| url::Origin origin(origin_url); |
| DOMStorageNamespace* local = GetStorageNamespace(kLocalStorageNamespaceId); |
| std::vector<GURL> origins; |
| local->GetOriginsWithAreas(&origins); |
| // Suborigin at the physical origin of |origin_url| should have their storage |
| // deleted as well. |
| // https://w3c.github.io/webappsec-suborigins/ |
| for (const auto& origin_candidate_url : origins) { |
| url::Origin origin_candidate(origin_candidate_url); |
| // |origin| is guaranteed to be deleted below, so don't delete it until |
| // then. That is, only suborigins at the same physical origin as |origin| |
| // should be deleted at this point. |
| if (!origin_candidate.IsSameOriginWith(origin) && |
| origin_candidate.IsSamePhysicalOriginWith(origin)) { |
| DeleteLocalStorage(origin_candidate_url); |
| } |
| } |
| // It is important to always explicitly delete |origin|. If it does not appear |
| // it |origins| above, there still may be a directory open in the namespace in |
| // preparation for this storage, and this call will make sure that the |
| // directory is deleted. |
| DeleteLocalStorage(origin_url); |
| } |
| |
| void DOMStorageContextImpl::DeleteLocalStorage(const GURL& origin_url) { |
| DOMStorageNamespace* local = GetStorageNamespace(kLocalStorageNamespaceId); |
| local->DeleteLocalStorageOrigin(origin_url); |
| // Synthesize a 'cleared' event if the area is open so CachedAreas in |
| // renderers get emptied out too. |
| DOMStorageArea* area = local->GetOpenStorageArea(origin_url); |
| if (area) |
| NotifyAreaCleared(area, origin_url); |
| } |
| |
| void DOMStorageContextImpl::DeleteSessionStorage( |
| const SessionStorageUsageInfo& usage_info) { |
| DCHECK(!is_shutdown_); |
| DOMStorageNamespace* dom_storage_namespace = NULL; |
| std::map<std::string, int64_t>::const_iterator it = |
| persistent_namespace_id_to_namespace_id_.find( |
| usage_info.persistent_namespace_id); |
| if (it != persistent_namespace_id_to_namespace_id_.end()) { |
| dom_storage_namespace = GetStorageNamespace(it->second); |
| } else { |
| int64_t namespace_id = AllocateSessionId(); |
| CreateSessionNamespace(namespace_id, usage_info.persistent_namespace_id); |
| dom_storage_namespace = GetStorageNamespace(namespace_id); |
| } |
| dom_storage_namespace->DeleteSessionStorageOrigin(usage_info.origin); |
| // Synthesize a 'cleared' event if the area is open so CachedAreas in |
| // renderers get emptied out too. |
| DOMStorageArea* area = |
| dom_storage_namespace->GetOpenStorageArea(usage_info.origin); |
| if (area) |
| NotifyAreaCleared(area, usage_info.origin); |
| } |
| |
| void DOMStorageContextImpl::Flush() { |
| for (auto& entry : namespaces_) |
| entry.second->Flush(); |
| } |
| |
| void DOMStorageContextImpl::Shutdown() { |
| if (task_runner_) |
| task_runner_->AssertIsRunningOnPrimarySequence(); |
| is_shutdown_ = true; |
| StorageNamespaceMap::const_iterator it = namespaces_.begin(); |
| for (; it != namespaces_.end(); ++it) |
| it->second->Shutdown(); |
| |
| base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider( |
| this); |
| |
| if (localstorage_directory_.empty() && !session_storage_database_.get()) |
| return; |
| |
| // Respect the content policy settings about what to |
| // keep and what to discard. |
| if (force_keep_session_state_) |
| return; // Keep everything. |
| |
| bool has_session_only_origins = |
| special_storage_policy_.get() && |
| special_storage_policy_->HasSessionOnlyOrigins(); |
| |
| if (has_session_only_origins) { |
| // We may have to delete something. We continue on the |
| // commit sequence after area shutdown tasks have cycled |
| // thru that sequence (and closed their database files). |
| bool success = task_runner_->PostShutdownBlockingTask( |
| FROM_HERE, |
| DOMStorageTaskRunner::COMMIT_SEQUENCE, |
| base::Bind(&DOMStorageContextImpl::ClearSessionOnlyOrigins, this)); |
| DCHECK(success); |
| } |
| } |
| |
| void DOMStorageContextImpl::AddEventObserver(EventObserver* observer) { |
| event_observers_.AddObserver(observer); |
| } |
| |
| void DOMStorageContextImpl::RemoveEventObserver(EventObserver* observer) { |
| event_observers_.RemoveObserver(observer); |
| } |
| |
| void DOMStorageContextImpl::NotifyItemSet( |
| const DOMStorageArea* area, |
| const base::string16& key, |
| const base::string16& new_value, |
| const base::NullableString16& old_value, |
| const GURL& page_url) { |
| for (auto& observer : event_observers_) |
| observer.OnDOMStorageItemSet(area, key, new_value, old_value, page_url); |
| } |
| |
| void DOMStorageContextImpl::NotifyItemRemoved( |
| const DOMStorageArea* area, |
| const base::string16& key, |
| const base::string16& old_value, |
| const GURL& page_url) { |
| for (auto& observer : event_observers_) |
| observer.OnDOMStorageItemRemoved(area, key, old_value, page_url); |
| } |
| |
| void DOMStorageContextImpl::NotifyAreaCleared( |
| const DOMStorageArea* area, |
| const GURL& page_url) { |
| for (auto& observer : event_observers_) |
| observer.OnDOMStorageAreaCleared(area, page_url); |
| } |
| |
| int64_t DOMStorageContextImpl::AllocateSessionId() { |
| return base::subtle::Barrier_AtomicIncrement(&session_id_sequence_, 1); |
| } |
| |
| std::string DOMStorageContextImpl::AllocatePersistentSessionId() { |
| std::string guid = base::GenerateGUID(); |
| std::replace(guid.begin(), guid.end(), '-', '_'); |
| return guid; |
| } |
| |
| // Used to diagnose unknown namespace_ids given to the ipc message filter. |
| base::Optional<bad_message::BadMessageReason> |
| DOMStorageContextImpl::DiagnoseSessionNamespaceId(int64_t namespace_id) { |
| if (namespace_id < session_id_offset_ || |
| namespace_id - session_id_offset_ > kSessionIdOffsetAmount) { |
| return bad_message::DSH_WRONG_STORAGE_PARTITION; |
| } |
| |
| if (base::ContainsValue(recently_deleted_session_ids_, namespace_id)) |
| return bad_message::DSH_DELETED_SESSION_ID; |
| |
| int last_allocated_session_id = |
| base::subtle::Barrier_AtomicIncrement(&session_id_sequence_, 0); |
| if (namespace_id <= last_allocated_session_id) |
| return bad_message::DSH_NOT_CREATED_SESSION_ID; |
| return bad_message::DSH_NOT_ALLOCATED_SESSION_ID; |
| } |
| |
| void DOMStorageContextImpl::CreateSessionNamespace( |
| int64_t namespace_id, |
| const std::string& persistent_namespace_id) { |
| if (is_shutdown_) |
| return; |
| DCHECK(namespace_id != kLocalStorageNamespaceId); |
| DCHECK(namespaces_.find(namespace_id) == namespaces_.end()); |
| namespaces_[namespace_id] = new DOMStorageNamespace( |
| namespace_id, persistent_namespace_id, session_storage_database_.get(), |
| task_runner_.get()); |
| persistent_namespace_id_to_namespace_id_[persistent_namespace_id] = |
| namespace_id; |
| } |
| |
| void DOMStorageContextImpl::DeleteSessionNamespace(int64_t namespace_id, |
| bool should_persist_data) { |
| DCHECK_NE(kLocalStorageNamespaceId, namespace_id); |
| StorageNamespaceMap::const_iterator it = namespaces_.find(namespace_id); |
| if (it == namespaces_.end()) |
| return; |
| std::string persistent_namespace_id = it->second->persistent_namespace_id(); |
| if (session_storage_database_.get()) { |
| if (!should_persist_data) { |
| task_runner_->PostShutdownBlockingTask( |
| FROM_HERE, |
| DOMStorageTaskRunner::COMMIT_SEQUENCE, |
| base::Bind( |
| base::IgnoreResult(&SessionStorageDatabase::DeleteNamespace), |
| session_storage_database_, |
| persistent_namespace_id)); |
| } else { |
| // Ensure that the data gets committed before we shut down. |
| it->second->Shutdown(); |
| if (!scavenging_started_) { |
| // Protect the persistent namespace ID from scavenging. |
| protected_persistent_session_ids_.insert(persistent_namespace_id); |
| } |
| } |
| } |
| persistent_namespace_id_to_namespace_id_.erase(persistent_namespace_id); |
| namespaces_.erase(namespace_id); |
| |
| recently_deleted_session_ids_.push_back(namespace_id); |
| if (recently_deleted_session_ids_.size() > 10) |
| recently_deleted_session_ids_.pop_front(); |
| } |
| |
| void DOMStorageContextImpl::CloneSessionNamespace( |
| int64_t existing_id, |
| int64_t new_id, |
| const std::string& new_persistent_id) { |
| if (is_shutdown_) |
| return; |
| DCHECK_NE(kLocalStorageNamespaceId, existing_id); |
| DCHECK_NE(kLocalStorageNamespaceId, new_id); |
| StorageNamespaceMap::iterator found = namespaces_.find(existing_id); |
| if (found != namespaces_.end()) { |
| namespaces_[new_id] = found->second->Clone(new_id, new_persistent_id); |
| return; |
| } |
| CreateSessionNamespace(new_id, new_persistent_id); |
| } |
| |
| void DOMStorageContextImpl::ClearSessionOnlyOrigins() { |
| if (!localstorage_directory_.empty()) { |
| std::vector<LocalStorageUsageInfo> infos; |
| const bool kDontIncludeFileInfo = false; |
| GetLocalStorageUsage(&infos, kDontIncludeFileInfo); |
| for (size_t i = 0; i < infos.size(); ++i) { |
| const GURL& origin = infos[i].origin; |
| if (special_storage_policy_->IsStorageProtected(origin)) |
| continue; |
| if (!special_storage_policy_->IsStorageSessionOnly(origin)) |
| continue; |
| |
| base::FilePath database_file_path = localstorage_directory_.Append( |
| DOMStorageArea::DatabaseFileNameFromOrigin(origin)); |
| sql::Connection::Delete(database_file_path); |
| } |
| } |
| if (session_storage_database_.get()) { |
| std::vector<SessionStorageUsageInfo> infos; |
| GetSessionStorageUsage(&infos); |
| for (size_t i = 0; i < infos.size(); ++i) { |
| const GURL& origin = infos[i].origin; |
| if (special_storage_policy_->IsStorageProtected(origin)) |
| continue; |
| if (!special_storage_policy_->IsStorageSessionOnly(origin)) |
| continue; |
| session_storage_database_->DeleteArea(infos[i].persistent_namespace_id, |
| origin); |
| } |
| } |
| } |
| |
| void DOMStorageContextImpl::SetSaveSessionStorageOnDisk() { |
| DCHECK(namespaces_.empty()); |
| if (!sessionstorage_directory_.empty()) { |
| session_storage_database_ = new SessionStorageDatabase( |
| sessionstorage_directory_); |
| } |
| } |
| |
| void DOMStorageContextImpl::StartScavengingUnusedSessionStorage() { |
| if (session_storage_database_.get()) { |
| task_runner_->PostDelayedTask( |
| FROM_HERE, base::Bind(&DOMStorageContextImpl::FindUnusedNamespaces, |
| this), |
| base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds)); |
| } |
| } |
| |
| void DOMStorageContextImpl::PurgeMemory(PurgeOption purge_option) { |
| if (is_shutdown_) |
| return; |
| |
| DOMStorageNamespace::UsageStatistics initial_stats = |
| GetTotalNamespaceStatistics(namespaces_); |
| if (!initial_stats.total_area_count) |
| return; |
| |
| // Track the total localStorage cache size. |
| UMA_HISTOGRAM_CUSTOM_COUNTS("LocalStorage.BrowserLocalStorageCacheSizeInKB", |
| initial_stats.total_cache_size / 1024, 1, 100000, |
| 50); |
| |
| const char* purge_reason = nullptr; |
| if (purge_option == PURGE_IF_NEEDED) { |
| // Purging is done based on the cache sizes without including the database |
| // size since it can be expensive trying to estimate the sqlite usage for |
| // all databases. For low end devices purge all inactive areas. |
| if (initial_stats.total_cache_size > kMaxCacheSize) |
| purge_reason = "SizeLimitExceeded"; |
| else if (initial_stats.total_area_count > kMaxStorageAreaCount) |
| purge_reason = "AreaCountLimitExceeded"; |
| else if (is_low_end_device_) |
| purge_reason = "InactiveOnLowEndDevice"; |
| if (!purge_reason) |
| return; |
| |
| purge_option = PURGE_UNOPENED; |
| } else { |
| if (purge_option == PURGE_AGGRESSIVE) |
| purge_reason = "AggressivePurgeTriggered"; |
| else |
| purge_reason = "ModeratePurgeTriggered"; |
| } |
| |
| // Return if no areas can be purged with the given option. |
| bool aggressively = purge_option == PURGE_AGGRESSIVE; |
| if (!aggressively && !initial_stats.inactive_area_count) { |
| return; |
| } |
| for (const auto& it : namespaces_) |
| it.second->PurgeMemory(aggressively); |
| |
| // Track the size of cache purged. |
| size_t purged_size_kib = |
| (initial_stats.total_cache_size - |
| GetTotalNamespaceStatistics(namespaces_).total_cache_size) / |
| 1024; |
| std::string full_histogram_name = |
| std::string("LocalStorage.BrowserLocalStorageCachePurgedInKB.") + |
| purge_reason; |
| base::HistogramBase* histogram = base::Histogram::FactoryGet( |
| full_histogram_name, 1, 100000, 50, |
| base::HistogramBase::kUmaTargetedHistogramFlag); |
| if (histogram) |
| histogram->Add(purged_size_kib); |
| UMA_HISTOGRAM_CUSTOM_COUNTS("LocalStorage.BrowserLocalStorageCachePurgedInKB", |
| purged_size_kib, 1, 100000, 50); |
| } |
| |
| bool DOMStorageContextImpl::OnMemoryDump( |
| const base::trace_event::MemoryDumpArgs& args, |
| base::trace_event::ProcessMemoryDump* pmd) { |
| if (session_storage_database_) |
| session_storage_database_->OnMemoryDump(pmd); |
| if (args.level_of_detail == |
| base::trace_event::MemoryDumpLevelOfDetail::BACKGROUND) { |
| DOMStorageNamespace::UsageStatistics total_stats = |
| GetTotalNamespaceStatistics(namespaces_); |
| auto* mad = pmd->CreateAllocatorDump( |
| base::StringPrintf("dom_storage/0x%" PRIXPTR "/cache_size", |
| reinterpret_cast<uintptr_t>(this))); |
| mad->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize, |
| base::trace_event::MemoryAllocatorDump::kUnitsBytes, |
| total_stats.total_cache_size); |
| mad->AddScalar("inactive_areas", |
| base::trace_event::MemoryAllocatorDump::kUnitsObjects, |
| total_stats.inactive_area_count); |
| mad->AddScalar("total_areas", |
| base::trace_event::MemoryAllocatorDump::kUnitsObjects, |
| total_stats.total_area_count); |
| return true; |
| } |
| for (const auto& it : namespaces_) { |
| it.second->OnMemoryDump(pmd); |
| } |
| return true; |
| } |
| |
| void DOMStorageContextImpl::FindUnusedNamespaces() { |
| DCHECK(session_storage_database_.get()); |
| if (scavenging_started_) |
| return; |
| scavenging_started_ = true; |
| std::set<std::string> namespace_ids_in_use; |
| for (StorageNamespaceMap::const_iterator it = namespaces_.begin(); |
| it != namespaces_.end(); ++it) |
| namespace_ids_in_use.insert(it->second->persistent_namespace_id()); |
| std::set<std::string> protected_persistent_session_ids; |
| protected_persistent_session_ids.swap(protected_persistent_session_ids_); |
| task_runner_->PostShutdownBlockingTask( |
| FROM_HERE, DOMStorageTaskRunner::COMMIT_SEQUENCE, |
| base::Bind( |
| &DOMStorageContextImpl::FindUnusedNamespacesInCommitSequence, |
| this, namespace_ids_in_use, protected_persistent_session_ids)); |
| } |
| |
| void DOMStorageContextImpl::FindUnusedNamespacesInCommitSequence( |
| const std::set<std::string>& namespace_ids_in_use, |
| const std::set<std::string>& protected_persistent_session_ids) { |
| DCHECK(session_storage_database_.get()); |
| // Delete all namespaces which don't have an associated DOMStorageNamespace |
| // alive. |
| std::map<std::string, std::vector<GURL> > namespaces_and_origins; |
| session_storage_database_->ReadNamespacesAndOrigins(&namespaces_and_origins); |
| for (std::map<std::string, std::vector<GURL> >::const_iterator it = |
| namespaces_and_origins.begin(); |
| it != namespaces_and_origins.end(); ++it) { |
| if (namespace_ids_in_use.find(it->first) == namespace_ids_in_use.end() && |
| protected_persistent_session_ids.find(it->first) == |
| protected_persistent_session_ids.end()) { |
| deletable_persistent_namespace_ids_.push_back(it->first); |
| } |
| } |
| if (!deletable_persistent_namespace_ids_.empty()) { |
| task_runner_->PostDelayedTask( |
| FROM_HERE, base::Bind( |
| &DOMStorageContextImpl::DeleteNextUnusedNamespace, |
| this), |
| base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds)); |
| } |
| } |
| |
| void DOMStorageContextImpl::DeleteNextUnusedNamespace() { |
| if (is_shutdown_) |
| return; |
| task_runner_->PostShutdownBlockingTask( |
| FROM_HERE, DOMStorageTaskRunner::COMMIT_SEQUENCE, |
| base::Bind( |
| &DOMStorageContextImpl::DeleteNextUnusedNamespaceInCommitSequence, |
| this)); |
| } |
| |
| void DOMStorageContextImpl::DeleteNextUnusedNamespaceInCommitSequence() { |
| if (deletable_persistent_namespace_ids_.empty()) |
| return; |
| const std::string& persistent_id = deletable_persistent_namespace_ids_.back(); |
| session_storage_database_->DeleteNamespace(persistent_id); |
| deletable_persistent_namespace_ids_.pop_back(); |
| if (!deletable_persistent_namespace_ids_.empty()) { |
| task_runner_->PostDelayedTask( |
| FROM_HERE, base::Bind( |
| &DOMStorageContextImpl::DeleteNextUnusedNamespace, |
| this), |
| base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds)); |
| } |
| } |
| |
| } // namespace content |