blob: dd1f33e847fb13702496061685d6f85b7863ad3c [file] [log] [blame]
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001// 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"
Ben Murdochbb1529c2013-08-08 10:24:53 +01009#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"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000024
25namespace content {
Torne (Richard Coles)58218062012-11-14 11:43:16 +000026
Ben Murdochbb1529c2013-08-08 10:24:53 +010027static const int kSessionStoraceScavengingSeconds = 60;
Torne (Richard Coles)58218062012-11-14 11:43:16 +000028
29DOMStorageContextImpl::DOMStorageContextImpl(
Ben Murdochbb1529c2013-08-08 10:24:53 +010030 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();
Torne (Richard Coles)58218062012-11-14 11:43:16 +000045}
46
47DOMStorageContextImpl::~DOMStorageContextImpl() {
Ben Murdochbb1529c2013-08-08 10:24:53 +010048 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
63DOMStorageNamespace* 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();
Torne (Richard Coles)58218062012-11-14 11:43:16 +000085}
86
87void DOMStorageContextImpl::GetLocalStorageUsage(
Ben Murdochbb1529c2013-08-08 10:24:53 +010088 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 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000107}
108
109void DOMStorageContextImpl::GetSessionStorageUsage(
Ben Murdochbb1529c2013-08-08 10:24:53 +0100110 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 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000127}
128
129void DOMStorageContextImpl::DeleteLocalStorage(const GURL& origin) {
Ben Murdochbb1529c2013-08-08 10:24:53 +0100130 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);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000138}
139
140void DOMStorageContextImpl::DeleteSessionStorage(
Ben Murdochbb1529c2013-08-08 10:24:53 +0100141 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);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000161}
162
163void DOMStorageContextImpl::PurgeMemory() {
Ben Murdochbb1529c2013-08-08 10:24:53 +0100164 // 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);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000172}
173
174void DOMStorageContextImpl::Shutdown() {
Ben Murdochbb1529c2013-08-08 10:24:53 +0100175 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
204void DOMStorageContextImpl::AddEventObserver(EventObserver* observer) {
205 event_observers_.AddObserver(observer);
206}
207
208void DOMStorageContextImpl::RemoveEventObserver(EventObserver* observer) {
209 event_observers_.RemoveObserver(observer);
210}
211
212void 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
223void 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
233void 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
241std::string DOMStorageContextImpl::AllocatePersistentSessionId() {
242 std::string guid = base::GenerateGUID();
243 std::replace(guid.begin(), guid.end(), '-', '_');
244 return guid;
245}
246
247void 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
261void 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
290void 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
304void 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
336void DOMStorageContextImpl::SetSaveSessionStorageOnDisk() {
337 DCHECK(namespaces_.empty());
338 if (!sessionstorage_directory_.empty()) {
339 session_storage_database_ = new SessionStorageDatabase(
340 sessionstorage_directory_);
341 }
342}
343
344void 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
353void 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
371void 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
397void 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
407void 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 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000420}
421
422} // namespace content