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