Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / content / browser / dom_storage / dom_storage_context_impl.cc
blobcef7b573dd040a659bae948a3d0f294e280bd6ec
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.
5 #include "content/browser/dom_storage/dom_storage_context_impl.h"
7 #include <stdlib.h>
9 #include "base/bind.h"
10 #include "base/bind_helpers.h"
11 #include "base/files/file_enumerator.h"
12 #include "base/files/file_util.h"
13 #include "base/guid.h"
14 #include "base/location.h"
15 #include "base/time/time.h"
16 #include "content/browser/dom_storage/dom_storage_area.h"
17 #include "content/browser/dom_storage/dom_storage_database.h"
18 #include "content/browser/dom_storage/dom_storage_namespace.h"
19 #include "content/browser/dom_storage/dom_storage_task_runner.h"
20 #include "content/browser/dom_storage/session_storage_database.h"
21 #include "content/common/dom_storage/dom_storage_types.h"
22 #include "content/public/browser/dom_storage_context.h"
23 #include "content/public/browser/local_storage_usage_info.h"
24 #include "content/public/browser/session_storage_usage_info.h"
25 #include "storage/browser/quota/special_storage_policy.h"
27 namespace content {
29 static const int kSessionStoraceScavengingSeconds = 60;
31 // Offset the session storage namespace ids generated by different contexts
32 // to help identify when an id from one is mistakenly used in another.
33 static int g_session_id_offset_sequence = 1;
35 DOMStorageContextImpl::DOMStorageContextImpl(
36 const base::FilePath& localstorage_directory,
37 const base::FilePath& sessionstorage_directory,
38 storage::SpecialStoragePolicy* special_storage_policy,
39 DOMStorageTaskRunner* task_runner)
40 : localstorage_directory_(localstorage_directory),
41 sessionstorage_directory_(sessionstorage_directory),
42 task_runner_(task_runner),
43 session_id_offset_(abs((g_session_id_offset_sequence++ % 10)) * 1000),
44 is_shutdown_(false),
45 force_keep_session_state_(false),
46 special_storage_policy_(special_storage_policy),
47 scavenging_started_(false) {
48 // AtomicSequenceNum starts at 0 but we want to start session
49 // namespace ids at one since zero is reserved for the
50 // kLocalStorageNamespaceId.
51 session_id_sequence_.GetNext();
54 DOMStorageContextImpl::~DOMStorageContextImpl() {
55 if (session_storage_database_.get()) {
56 // SessionStorageDatabase shouldn't be deleted right away: deleting it will
57 // potentially involve waiting in leveldb::DBImpl::~DBImpl, and waiting
58 // shouldn't happen on this thread.
59 SessionStorageDatabase* to_release = session_storage_database_.get();
60 to_release->AddRef();
61 session_storage_database_ = NULL;
62 task_runner_->PostShutdownBlockingTask(
63 FROM_HERE,
64 DOMStorageTaskRunner::COMMIT_SEQUENCE,
65 base::Bind(&SessionStorageDatabase::Release,
66 base::Unretained(to_release)));
70 DOMStorageNamespace* DOMStorageContextImpl::GetStorageNamespace(
71 int64 namespace_id) {
72 if (is_shutdown_)
73 return NULL;
74 StorageNamespaceMap::iterator found = namespaces_.find(namespace_id);
75 if (found == namespaces_.end()) {
76 if (namespace_id == kLocalStorageNamespaceId) {
77 if (!localstorage_directory_.empty()) {
78 if (!base::CreateDirectory(localstorage_directory_)) {
79 LOG(ERROR) << "Failed to create 'Local Storage' directory,"
80 " falling back to in-memory only.";
81 localstorage_directory_ = base::FilePath();
84 DOMStorageNamespace* local =
85 new DOMStorageNamespace(localstorage_directory_, task_runner_.get());
86 namespaces_[kLocalStorageNamespaceId] = local;
87 return local;
89 return NULL;
91 return found->second.get();
94 void DOMStorageContextImpl::GetLocalStorageUsage(
95 std::vector<LocalStorageUsageInfo>* infos,
96 bool include_file_info) {
97 if (localstorage_directory_.empty())
98 return;
99 base::FileEnumerator enumerator(localstorage_directory_, false,
100 base::FileEnumerator::FILES);
101 for (base::FilePath path = enumerator.Next(); !path.empty();
102 path = enumerator.Next()) {
103 if (path.MatchesExtension(DOMStorageArea::kDatabaseFileExtension)) {
104 LocalStorageUsageInfo info;
105 info.origin = DOMStorageArea::OriginFromDatabaseFileName(path);
106 if (include_file_info) {
107 base::FileEnumerator::FileInfo find_info = enumerator.GetInfo();
108 info.data_size = find_info.GetSize();
109 info.last_modified = find_info.GetLastModifiedTime();
111 infos->push_back(info);
116 void DOMStorageContextImpl::GetSessionStorageUsage(
117 std::vector<SessionStorageUsageInfo>* infos) {
118 if (!session_storage_database_.get())
119 return;
120 std::map<std::string, std::vector<GURL> > namespaces_and_origins;
121 session_storage_database_->ReadNamespacesAndOrigins(
122 &namespaces_and_origins);
123 for (std::map<std::string, std::vector<GURL> >::const_iterator it =
124 namespaces_and_origins.begin();
125 it != namespaces_and_origins.end(); ++it) {
126 for (std::vector<GURL>::const_iterator origin_it = it->second.begin();
127 origin_it != it->second.end(); ++origin_it) {
128 SessionStorageUsageInfo info;
129 info.persistent_namespace_id = it->first;
130 info.origin = *origin_it;
131 infos->push_back(info);
136 void DOMStorageContextImpl::DeleteLocalStorage(const GURL& origin) {
137 DCHECK(!is_shutdown_);
138 DOMStorageNamespace* local = GetStorageNamespace(kLocalStorageNamespaceId);
139 local->DeleteLocalStorageOrigin(origin);
140 // Synthesize a 'cleared' event if the area is open so CachedAreas in
141 // renderers get emptied out too.
142 DOMStorageArea* area = local->GetOpenStorageArea(origin);
143 if (area)
144 NotifyAreaCleared(area, origin);
147 void DOMStorageContextImpl::DeleteSessionStorage(
148 const SessionStorageUsageInfo& usage_info) {
149 DCHECK(!is_shutdown_);
150 DOMStorageNamespace* dom_storage_namespace = NULL;
151 std::map<std::string, int64>::const_iterator it =
152 persistent_namespace_id_to_namespace_id_.find(
153 usage_info.persistent_namespace_id);
154 if (it != persistent_namespace_id_to_namespace_id_.end()) {
155 dom_storage_namespace = GetStorageNamespace(it->second);
156 } else {
157 int64 namespace_id = AllocateSessionId();
158 CreateSessionNamespace(namespace_id, usage_info.persistent_namespace_id);
159 dom_storage_namespace = GetStorageNamespace(namespace_id);
161 dom_storage_namespace->DeleteSessionStorageOrigin(usage_info.origin);
162 // Synthesize a 'cleared' event if the area is open so CachedAreas in
163 // renderers get emptied out too.
164 DOMStorageArea* area =
165 dom_storage_namespace->GetOpenStorageArea(usage_info.origin);
166 if (area)
167 NotifyAreaCleared(area, usage_info.origin);
170 void DOMStorageContextImpl::Flush() {
171 for (auto& entry : namespaces_)
172 entry.second->Flush();
175 void DOMStorageContextImpl::Shutdown() {
176 is_shutdown_ = true;
177 StorageNamespaceMap::const_iterator it = namespaces_.begin();
178 for (; it != namespaces_.end(); ++it)
179 it->second->Shutdown();
181 if (localstorage_directory_.empty() && !session_storage_database_.get())
182 return;
184 // Respect the content policy settings about what to
185 // keep and what to discard.
186 if (force_keep_session_state_)
187 return; // Keep everything.
189 bool has_session_only_origins =
190 special_storage_policy_.get() &&
191 special_storage_policy_->HasSessionOnlyOrigins();
193 if (has_session_only_origins) {
194 // We may have to delete something. We continue on the
195 // commit sequence after area shutdown tasks have cycled
196 // thru that sequence (and closed their database files).
197 bool success = task_runner_->PostShutdownBlockingTask(
198 FROM_HERE,
199 DOMStorageTaskRunner::COMMIT_SEQUENCE,
200 base::Bind(&DOMStorageContextImpl::ClearSessionOnlyOrigins, this));
201 DCHECK(success);
205 void DOMStorageContextImpl::AddEventObserver(EventObserver* observer) {
206 event_observers_.AddObserver(observer);
209 void DOMStorageContextImpl::RemoveEventObserver(EventObserver* observer) {
210 event_observers_.RemoveObserver(observer);
213 void DOMStorageContextImpl::NotifyItemSet(
214 const DOMStorageArea* area,
215 const base::string16& key,
216 const base::string16& new_value,
217 const base::NullableString16& old_value,
218 const GURL& page_url) {
219 FOR_EACH_OBSERVER(
220 EventObserver, event_observers_,
221 OnDOMStorageItemSet(area, key, new_value, old_value, page_url));
224 void DOMStorageContextImpl::NotifyItemRemoved(
225 const DOMStorageArea* area,
226 const base::string16& key,
227 const base::string16& old_value,
228 const GURL& page_url) {
229 FOR_EACH_OBSERVER(
230 EventObserver, event_observers_,
231 OnDOMStorageItemRemoved(area, key, old_value, page_url));
234 void DOMStorageContextImpl::NotifyAreaCleared(
235 const DOMStorageArea* area,
236 const GURL& page_url) {
237 FOR_EACH_OBSERVER(
238 EventObserver, event_observers_,
239 OnDOMStorageAreaCleared(area, page_url));
242 int64 DOMStorageContextImpl::AllocateSessionId() {
243 return session_id_sequence_.GetNext() + session_id_offset_;
246 std::string DOMStorageContextImpl::AllocatePersistentSessionId() {
247 std::string guid = base::GenerateGUID();
248 std::replace(guid.begin(), guid.end(), '-', '_');
249 return guid;
252 void DOMStorageContextImpl::CreateSessionNamespace(
253 int64 namespace_id,
254 const std::string& persistent_namespace_id) {
255 if (is_shutdown_)
256 return;
257 DCHECK(namespace_id != kLocalStorageNamespaceId);
258 DCHECK(namespaces_.find(namespace_id) == namespaces_.end());
259 namespaces_[namespace_id] = new DOMStorageNamespace(
260 namespace_id, persistent_namespace_id, session_storage_database_.get(),
261 task_runner_.get());
262 persistent_namespace_id_to_namespace_id_[persistent_namespace_id] =
263 namespace_id;
266 void DOMStorageContextImpl::DeleteSessionNamespace(
267 int64 namespace_id, bool should_persist_data) {
268 DCHECK_NE(kLocalStorageNamespaceId, namespace_id);
269 StorageNamespaceMap::const_iterator it = namespaces_.find(namespace_id);
270 if (it == namespaces_.end())
271 return;
272 std::string persistent_namespace_id = it->second->persistent_namespace_id();
273 if (session_storage_database_.get()) {
274 if (!should_persist_data) {
275 task_runner_->PostShutdownBlockingTask(
276 FROM_HERE,
277 DOMStorageTaskRunner::COMMIT_SEQUENCE,
278 base::Bind(
279 base::IgnoreResult(&SessionStorageDatabase::DeleteNamespace),
280 session_storage_database_,
281 persistent_namespace_id));
282 } else {
283 // Ensure that the data gets committed before we shut down.
284 it->second->Shutdown();
285 if (!scavenging_started_) {
286 // Protect the persistent namespace ID from scavenging.
287 protected_persistent_session_ids_.insert(persistent_namespace_id);
291 persistent_namespace_id_to_namespace_id_.erase(persistent_namespace_id);
292 namespaces_.erase(namespace_id);
295 void DOMStorageContextImpl::CloneSessionNamespace(
296 int64 existing_id, int64 new_id,
297 const std::string& new_persistent_id) {
298 if (is_shutdown_)
299 return;
300 DCHECK_NE(kLocalStorageNamespaceId, existing_id);
301 DCHECK_NE(kLocalStorageNamespaceId, new_id);
302 StorageNamespaceMap::iterator found = namespaces_.find(existing_id);
303 if (found != namespaces_.end())
304 namespaces_[new_id] = found->second->Clone(new_id, new_persistent_id);
305 else
306 CreateSessionNamespace(new_id, new_persistent_id);
309 void DOMStorageContextImpl::ClearSessionOnlyOrigins() {
310 if (!localstorage_directory_.empty()) {
311 std::vector<LocalStorageUsageInfo> infos;
312 const bool kDontIncludeFileInfo = false;
313 GetLocalStorageUsage(&infos, kDontIncludeFileInfo);
314 for (size_t i = 0; i < infos.size(); ++i) {
315 const GURL& origin = infos[i].origin;
316 if (special_storage_policy_->IsStorageProtected(origin))
317 continue;
318 if (!special_storage_policy_->IsStorageSessionOnly(origin))
319 continue;
321 base::FilePath database_file_path = localstorage_directory_.Append(
322 DOMStorageArea::DatabaseFileNameFromOrigin(origin));
323 sql::Connection::Delete(database_file_path);
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);
341 void DOMStorageContextImpl::SetSaveSessionStorageOnDisk() {
342 DCHECK(namespaces_.empty());
343 if (!sessionstorage_directory_.empty()) {
344 session_storage_database_ = new SessionStorageDatabase(
345 sessionstorage_directory_);
349 void DOMStorageContextImpl::StartScavengingUnusedSessionStorage() {
350 if (session_storage_database_.get()) {
351 task_runner_->PostDelayedTask(
352 FROM_HERE, base::Bind(&DOMStorageContextImpl::FindUnusedNamespaces,
353 this),
354 base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds));
358 void DOMStorageContextImpl::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 &DOMStorageContextImpl::FindUnusedNamespacesInCommitSequence,
373 this, namespace_ids_in_use, protected_persistent_session_ids));
376 void DOMStorageContextImpl::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);
393 if (!deletable_persistent_namespace_ids_.empty()) {
394 task_runner_->PostDelayedTask(
395 FROM_HERE, base::Bind(
396 &DOMStorageContextImpl::DeleteNextUnusedNamespace,
397 this),
398 base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds));
402 void DOMStorageContextImpl::DeleteNextUnusedNamespace() {
403 if (is_shutdown_)
404 return;
405 task_runner_->PostShutdownBlockingTask(
406 FROM_HERE, DOMStorageTaskRunner::COMMIT_SEQUENCE,
407 base::Bind(
408 &DOMStorageContextImpl::DeleteNextUnusedNamespaceInCommitSequence,
409 this));
412 void DOMStorageContextImpl::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 &DOMStorageContextImpl::DeleteNextUnusedNamespace,
422 this),
423 base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds));
427 } // namespace content