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 "webkit/dom_storage/dom_storage_context.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/dom_storage/dom_storage_area.h"
14 #include "webkit/dom_storage/dom_storage_database.h"
15 #include "webkit/dom_storage/dom_storage_namespace.h"
16 #include "webkit/dom_storage/dom_storage_task_runner.h"
17 #include "webkit/dom_storage/dom_storage_types.h"
18 #include "webkit/dom_storage/session_storage_database.h"
19 #include "webkit/quota/special_storage_policy.h"
21 using file_util::FileEnumerator
;
23 namespace dom_storage
{
25 static const int kSessionStoraceScavengingSeconds
= 60;
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
),
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();
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();
52 session_storage_database_
= NULL
;
53 task_runner_
->PostShutdownBlockingTask(
55 DomStorageTaskRunner::COMMIT_SEQUENCE
,
56 base::Bind(&SessionStorageDatabase::Release
,
57 base::Unretained(to_release
)));
61 DomStorageNamespace
* DomStorageContext::GetStorageNamespace(
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();
75 DomStorageNamespace
* local
=
76 new DomStorageNamespace(localstorage_directory_
, task_runner_
);
77 namespaces_
[kLocalStorageNamespaceId
] = local
;
85 void DomStorageContext::GetLocalStorageUsage(
86 std::vector
<LocalStorageUsageInfo
>* infos
,
87 bool include_file_info
) {
88 if (localstorage_directory_
.empty())
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
);
103 infos
->push_back(info
);
108 void DomStorageContext::GetSessionStorageUsage(
109 std::vector
<SessionStorageUsageInfo
>* infos
) {
110 if (!session_storage_database_
.get())
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
);
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
);
136 NotifyAreaCleared(area
, origin
);
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
);
149 int64 namespace_id
= AllocateSessionId();
150 CreateSessionNamespace(namespace_id
, usage_info
.persistent_namespace_id
);
151 dom_storage_namespace
= GetStorageNamespace(namespace_id
);
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
);
159 NotifyAreaCleared(area
, usage_info
.origin
);
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
167 StorageNamespaceMap::iterator found
=
168 namespaces_
.find(kLocalStorageNamespaceId
);
169 if (found
!= namespaces_
.end())
170 found
->second
->PurgeMemory();
173 void DomStorageContext::Shutdown() {
175 StorageNamespaceMap::const_iterator it
= namespaces_
.begin();
176 for (; it
!= namespaces_
.end(); ++it
)
177 it
->second
->Shutdown();
179 if (localstorage_directory_
.empty() && !session_storage_database_
.get())
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.
187 bool has_session_only_origins
=
188 special_storage_policy_
.get() &&
189 special_storage_policy_
->HasSessionOnlyOrigins();
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(
197 DomStorageTaskRunner::COMMIT_SEQUENCE
,
198 base::Bind(&DomStorageContext::ClearSessionOnlyOrigins
, this));
203 void DomStorageContext::AddEventObserver(EventObserver
* observer
) {
204 event_observers_
.AddObserver(observer
);
207 void DomStorageContext::RemoveEventObserver(EventObserver
* observer
) {
208 event_observers_
.RemoveObserver(observer
);
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
) {
218 EventObserver
, event_observers_
,
219 OnDomStorageItemSet(area
, key
, new_value
, old_value
, page_url
));
222 void DomStorageContext::NotifyItemRemoved(
223 const DomStorageArea
* area
,
224 const base::string16
& key
,
225 const base::string16
& old_value
,
226 const GURL
& page_url
) {
228 EventObserver
, event_observers_
,
229 OnDomStorageItemRemoved(area
, key
, old_value
, page_url
));
232 void DomStorageContext::NotifyAreaCleared(
233 const DomStorageArea
* area
,
234 const GURL
& page_url
) {
236 EventObserver
, event_observers_
,
237 OnDomStorageAreaCleared(area
, page_url
));
240 std::string
DomStorageContext::AllocatePersistentSessionId() {
241 std::string guid
= base::GenerateGUID();
242 std::replace(guid
.begin(), guid
.end(), '-', '_');
246 void DomStorageContext::CreateSessionNamespace(
248 const std::string
& persistent_namespace_id
) {
251 DCHECK(namespace_id
!= kLocalStorageNamespaceId
);
252 DCHECK(namespaces_
.find(namespace_id
) == namespaces_
.end());
253 namespaces_
[namespace_id
] = new DomStorageNamespace(
254 namespace_id
, persistent_namespace_id
, session_storage_database_
.get(),
256 persistent_namespace_id_to_namespace_id_
[persistent_namespace_id
] =
260 void DomStorageContext::DeleteSessionNamespace(
261 int64 namespace_id
, bool should_persist_data
) {
262 DCHECK_NE(kLocalStorageNamespaceId
, namespace_id
);
263 StorageNamespaceMap::const_iterator it
= namespaces_
.find(namespace_id
);
264 if (it
== namespaces_
.end())
266 std::string persistent_namespace_id
= it
->second
->persistent_namespace_id();
267 if (session_storage_database_
.get()) {
268 if (!should_persist_data
) {
269 task_runner_
->PostShutdownBlockingTask(
271 DomStorageTaskRunner::COMMIT_SEQUENCE
,
273 base::IgnoreResult(&SessionStorageDatabase::DeleteNamespace
),
274 session_storage_database_
,
275 persistent_namespace_id
));
277 // Ensure that the data gets committed before we shut down.
278 it
->second
->Shutdown();
279 if (!scavenging_started_
) {
280 // Protect the persistent namespace ID from scavenging.
281 protected_persistent_session_ids_
.insert(persistent_namespace_id
);
285 persistent_namespace_id_to_namespace_id_
.erase(persistent_namespace_id
);
286 namespaces_
.erase(namespace_id
);
289 void DomStorageContext::CloneSessionNamespace(
290 int64 existing_id
, int64 new_id
,
291 const std::string
& new_persistent_id
) {
294 DCHECK_NE(kLocalStorageNamespaceId
, existing_id
);
295 DCHECK_NE(kLocalStorageNamespaceId
, new_id
);
296 StorageNamespaceMap::iterator found
= namespaces_
.find(existing_id
);
297 if (found
!= namespaces_
.end())
298 namespaces_
[new_id
] = found
->second
->Clone(new_id
, new_persistent_id
);
300 CreateSessionNamespace(new_id
, new_persistent_id
);
303 void DomStorageContext::ClearSessionOnlyOrigins() {
304 if (!localstorage_directory_
.empty()) {
305 std::vector
<LocalStorageUsageInfo
> infos
;
306 const bool kDontIncludeFileInfo
= false;
307 GetLocalStorageUsage(&infos
, kDontIncludeFileInfo
);
308 for (size_t i
= 0; i
< infos
.size(); ++i
) {
309 const GURL
& origin
= infos
[i
].origin
;
310 if (special_storage_policy_
->IsStorageProtected(origin
))
312 if (!special_storage_policy_
->IsStorageSessionOnly(origin
))
315 const bool kNotRecursive
= false;
316 base::FilePath database_file_path
= localstorage_directory_
.Append(
317 DomStorageArea::DatabaseFileNameFromOrigin(origin
));
318 file_util::Delete(database_file_path
, kNotRecursive
);
320 DomStorageDatabase::GetJournalFilePath(database_file_path
),
324 if (session_storage_database_
.get()) {
325 std::vector
<SessionStorageUsageInfo
> infos
;
326 GetSessionStorageUsage(&infos
);
327 for (size_t i
= 0; i
< infos
.size(); ++i
) {
328 const GURL
& origin
= infos
[i
].origin
;
329 if (special_storage_policy_
->IsStorageProtected(origin
))
331 if (!special_storage_policy_
->IsStorageSessionOnly(origin
))
333 session_storage_database_
->DeleteArea(infos
[i
].persistent_namespace_id
,
339 void DomStorageContext::SetSaveSessionStorageOnDisk() {
340 DCHECK(namespaces_
.empty());
341 if (!sessionstorage_directory_
.empty()) {
342 session_storage_database_
= new SessionStorageDatabase(
343 sessionstorage_directory_
);
347 void DomStorageContext::StartScavengingUnusedSessionStorage() {
348 if (session_storage_database_
.get()) {
349 task_runner_
->PostDelayedTask(
350 FROM_HERE
, base::Bind(&DomStorageContext::FindUnusedNamespaces
, this),
351 base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds
));
355 void DomStorageContext::FindUnusedNamespaces() {
356 DCHECK(session_storage_database_
.get());
357 if (scavenging_started_
)
359 scavenging_started_
= true;
360 std::set
<std::string
> namespace_ids_in_use
;
361 for (StorageNamespaceMap::const_iterator it
= namespaces_
.begin();
362 it
!= namespaces_
.end(); ++it
)
363 namespace_ids_in_use
.insert(it
->second
->persistent_namespace_id());
364 std::set
<std::string
> protected_persistent_session_ids
;
365 protected_persistent_session_ids
.swap(protected_persistent_session_ids_
);
366 task_runner_
->PostShutdownBlockingTask(
367 FROM_HERE
, DomStorageTaskRunner::COMMIT_SEQUENCE
,
369 &DomStorageContext::FindUnusedNamespacesInCommitSequence
,
370 this, namespace_ids_in_use
, protected_persistent_session_ids
));
373 void DomStorageContext::FindUnusedNamespacesInCommitSequence(
374 const std::set
<std::string
>& namespace_ids_in_use
,
375 const std::set
<std::string
>& protected_persistent_session_ids
) {
376 DCHECK(session_storage_database_
.get());
377 // Delete all namespaces which don't have an associated DomStorageNamespace
379 std::map
<std::string
, std::vector
<GURL
> > namespaces_and_origins
;
380 session_storage_database_
->ReadNamespacesAndOrigins(&namespaces_and_origins
);
381 for (std::map
<std::string
, std::vector
<GURL
> >::const_iterator it
=
382 namespaces_and_origins
.begin();
383 it
!= namespaces_and_origins
.end(); ++it
) {
384 if (namespace_ids_in_use
.find(it
->first
) == namespace_ids_in_use
.end() &&
385 protected_persistent_session_ids
.find(it
->first
) ==
386 protected_persistent_session_ids
.end()) {
387 deletable_persistent_namespace_ids_
.push_back(it
->first
);
390 if (!deletable_persistent_namespace_ids_
.empty()) {
391 task_runner_
->PostDelayedTask(
392 FROM_HERE
, base::Bind(
393 &DomStorageContext::DeleteNextUnusedNamespace
,
395 base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds
));
399 void DomStorageContext::DeleteNextUnusedNamespace() {
402 task_runner_
->PostShutdownBlockingTask(
403 FROM_HERE
, DomStorageTaskRunner::COMMIT_SEQUENCE
,
405 &DomStorageContext::DeleteNextUnusedNamespaceInCommitSequence
,
409 void DomStorageContext::DeleteNextUnusedNamespaceInCommitSequence() {
410 if (deletable_persistent_namespace_ids_
.empty())
412 const std::string
& persistent_id
= deletable_persistent_namespace_ids_
.back();
413 session_storage_database_
->DeleteNamespace(persistent_id
);
414 deletable_persistent_namespace_ids_
.pop_back();
415 if (!deletable_persistent_namespace_ids_
.empty()) {
416 task_runner_
->PostDelayedTask(
417 FROM_HERE
, base::Bind(
418 &DomStorageContext::DeleteNextUnusedNamespace
,
420 base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds
));
424 } // namespace dom_storage