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"
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"
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"
27 static const int kSessionStoraceScavengingSeconds
= 60;
29 DOMStorageContextImpl::DOMStorageContextImpl(
30 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
),
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();
47 DOMStorageContextImpl::~DOMStorageContextImpl() {
48 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();
54 session_storage_database_
= NULL
;
55 task_runner_
->PostShutdownBlockingTask(
57 DOMStorageTaskRunner::COMMIT_SEQUENCE
,
58 base::Bind(&SessionStorageDatabase::Release
,
59 base::Unretained(to_release
)));
63 DOMStorageNamespace
* DOMStorageContextImpl::GetStorageNamespace(
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 (!base::CreateDirectory(localstorage_directory_
)) {
72 LOG(ERROR
) << "Failed to create 'Local Storage' directory,"
73 " falling back to in-memory only.";
74 localstorage_directory_
= base::FilePath();
77 DOMStorageNamespace
* local
=
78 new DOMStorageNamespace(localstorage_directory_
, task_runner_
.get());
79 namespaces_
[kLocalStorageNamespaceId
] = local
;
84 return found
->second
.get();
87 void DOMStorageContextImpl::GetLocalStorageUsage(
88 std::vector
<LocalStorageUsageInfo
>* infos
,
89 bool include_file_info
) {
90 if (localstorage_directory_
.empty())
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();
104 infos
->push_back(info
);
109 void DOMStorageContextImpl::GetSessionStorageUsage(
110 std::vector
<SessionStorageUsageInfo
>* infos
) {
111 if (!session_storage_database_
.get())
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
);
129 void DOMStorageContextImpl::DeleteLocalStorage(const GURL
& origin
) {
130 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
);
137 NotifyAreaCleared(area
, origin
);
140 void DOMStorageContextImpl::DeleteSessionStorage(
141 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
);
150 int64 namespace_id
= AllocateSessionId();
151 CreateSessionNamespace(namespace_id
, usage_info
.persistent_namespace_id
);
152 dom_storage_namespace
= GetStorageNamespace(namespace_id
);
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
);
160 NotifyAreaCleared(area
, usage_info
.origin
);
163 void DOMStorageContextImpl::Shutdown() {
165 StorageNamespaceMap::const_iterator it
= namespaces_
.begin();
166 for (; it
!= namespaces_
.end(); ++it
)
167 it
->second
->Shutdown();
169 if (localstorage_directory_
.empty() && !session_storage_database_
.get())
172 // Respect the content policy settings about what to
173 // keep and what to discard.
174 if (force_keep_session_state_
)
175 return; // Keep everything.
177 bool has_session_only_origins
=
178 special_storage_policy_
.get() &&
179 special_storage_policy_
->HasSessionOnlyOrigins();
181 if (has_session_only_origins
) {
182 // We may have to delete something. We continue on the
183 // commit sequence after area shutdown tasks have cycled
184 // thru that sequence (and closed their database files).
185 bool success
= task_runner_
->PostShutdownBlockingTask(
187 DOMStorageTaskRunner::COMMIT_SEQUENCE
,
188 base::Bind(&DOMStorageContextImpl::ClearSessionOnlyOrigins
, this));
193 void DOMStorageContextImpl::AddEventObserver(EventObserver
* observer
) {
194 event_observers_
.AddObserver(observer
);
197 void DOMStorageContextImpl::RemoveEventObserver(EventObserver
* observer
) {
198 event_observers_
.RemoveObserver(observer
);
201 void DOMStorageContextImpl::NotifyItemSet(
202 const DOMStorageArea
* area
,
203 const base::string16
& key
,
204 const base::string16
& new_value
,
205 const base::NullableString16
& old_value
,
206 const GURL
& page_url
) {
208 EventObserver
, event_observers_
,
209 OnDOMStorageItemSet(area
, key
, new_value
, old_value
, page_url
));
212 void DOMStorageContextImpl::NotifyItemRemoved(
213 const DOMStorageArea
* area
,
214 const base::string16
& key
,
215 const base::string16
& old_value
,
216 const GURL
& page_url
) {
218 EventObserver
, event_observers_
,
219 OnDOMStorageItemRemoved(area
, key
, old_value
, page_url
));
222 void DOMStorageContextImpl::NotifyAreaCleared(
223 const DOMStorageArea
* area
,
224 const GURL
& page_url
) {
226 EventObserver
, event_observers_
,
227 OnDOMStorageAreaCleared(area
, page_url
));
230 void DOMStorageContextImpl::NotifyAliasSessionMerged(
232 DOMStorageNamespace
* old_alias_master_namespace
) {
234 EventObserver
, event_observers_
,
235 OnDOMSessionStorageReset(namespace_id
));
236 if (old_alias_master_namespace
)
237 MaybeShutdownSessionNamespace(old_alias_master_namespace
);
240 std::string
DOMStorageContextImpl::AllocatePersistentSessionId() {
241 std::string guid
= base::GenerateGUID();
242 std::replace(guid
.begin(), guid
.end(), '-', '_');
246 void DOMStorageContextImpl::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 DOMStorageContextImpl::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() ||
265 it
->second
->ready_for_deletion_pending_aliases()) {
268 it
->second
->set_ready_for_deletion_pending_aliases(true);
269 DOMStorageNamespace
* alias_master
= it
->second
->alias_master_namespace();
271 DCHECK(it
->second
->num_aliases() == 0);
272 DCHECK(alias_master
->alias_master_namespace() == NULL
);
273 if (should_persist_data
)
274 alias_master
->set_must_persist_at_shutdown(true);
275 if (it
->second
->DecrementMasterAliasCount())
276 MaybeShutdownSessionNamespace(alias_master
);
277 namespaces_
.erase(namespace_id
);
279 if (should_persist_data
)
280 it
->second
->set_must_persist_at_shutdown(true);
281 MaybeShutdownSessionNamespace(it
->second
);
285 void DOMStorageContextImpl::MaybeShutdownSessionNamespace(
286 DOMStorageNamespace
* ns
) {
287 if (ns
->num_aliases() > 0 || !ns
->ready_for_deletion_pending_aliases())
289 DCHECK_EQ(ns
->num_aliases(), 0);
290 DCHECK(ns
->alias_master_namespace() == NULL
);
291 std::string persistent_namespace_id
= ns
->persistent_namespace_id();
292 if (session_storage_database_
.get()) {
293 if (!ns
->must_persist_at_shutdown()) {
294 task_runner_
->PostShutdownBlockingTask(
296 DOMStorageTaskRunner::COMMIT_SEQUENCE
,
298 base::IgnoreResult(&SessionStorageDatabase::DeleteNamespace
),
299 session_storage_database_
,
300 persistent_namespace_id
));
302 // Ensure that the data gets committed before we shut down.
304 if (!scavenging_started_
) {
305 // Protect the persistent namespace ID from scavenging.
306 protected_persistent_session_ids_
.insert(persistent_namespace_id
);
310 persistent_namespace_id_to_namespace_id_
.erase(persistent_namespace_id
);
311 namespaces_
.erase(ns
->namespace_id());
314 void DOMStorageContextImpl::CloneSessionNamespace(
315 int64 existing_id
, int64 new_id
,
316 const std::string
& new_persistent_id
) {
319 DCHECK_NE(kLocalStorageNamespaceId
, existing_id
);
320 DCHECK_NE(kLocalStorageNamespaceId
, new_id
);
321 StorageNamespaceMap::iterator found
= namespaces_
.find(existing_id
);
322 if (found
!= namespaces_
.end())
323 namespaces_
[new_id
] = found
->second
->Clone(new_id
, new_persistent_id
);
325 CreateSessionNamespace(new_id
, new_persistent_id
);
328 void DOMStorageContextImpl::CreateAliasSessionNamespace(
329 int64 existing_id
, int64 new_id
,
330 const std::string
& persistent_id
) {
333 DCHECK_NE(kLocalStorageNamespaceId
, existing_id
);
334 DCHECK_NE(kLocalStorageNamespaceId
, new_id
);
335 StorageNamespaceMap::iterator found
= namespaces_
.find(existing_id
);
336 if (found
!= namespaces_
.end()) {
337 namespaces_
[new_id
] = found
->second
->CreateAlias(new_id
);
339 CreateSessionNamespace(new_id
, persistent_id
);
343 void DOMStorageContextImpl::ClearSessionOnlyOrigins() {
344 if (!localstorage_directory_
.empty()) {
345 std::vector
<LocalStorageUsageInfo
> infos
;
346 const bool kDontIncludeFileInfo
= false;
347 GetLocalStorageUsage(&infos
, kDontIncludeFileInfo
);
348 for (size_t i
= 0; i
< infos
.size(); ++i
) {
349 const GURL
& origin
= infos
[i
].origin
;
350 if (special_storage_policy_
->IsStorageProtected(origin
))
352 if (!special_storage_policy_
->IsStorageSessionOnly(origin
))
355 base::FilePath database_file_path
= localstorage_directory_
.Append(
356 DOMStorageArea::DatabaseFileNameFromOrigin(origin
));
357 sql::Connection::Delete(database_file_path
);
360 if (session_storage_database_
.get()) {
361 std::vector
<SessionStorageUsageInfo
> infos
;
362 GetSessionStorageUsage(&infos
);
363 for (size_t i
= 0; i
< infos
.size(); ++i
) {
364 const GURL
& origin
= infos
[i
].origin
;
365 if (special_storage_policy_
->IsStorageProtected(origin
))
367 if (!special_storage_policy_
->IsStorageSessionOnly(origin
))
369 session_storage_database_
->DeleteArea(infos
[i
].persistent_namespace_id
,
375 void DOMStorageContextImpl::SetSaveSessionStorageOnDisk() {
376 DCHECK(namespaces_
.empty());
377 if (!sessionstorage_directory_
.empty()) {
378 session_storage_database_
= new SessionStorageDatabase(
379 sessionstorage_directory_
);
383 void DOMStorageContextImpl::StartScavengingUnusedSessionStorage() {
384 if (session_storage_database_
.get()) {
385 task_runner_
->PostDelayedTask(
386 FROM_HERE
, base::Bind(&DOMStorageContextImpl::FindUnusedNamespaces
,
388 base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds
));
392 void DOMStorageContextImpl::FindUnusedNamespaces() {
393 DCHECK(session_storage_database_
.get());
394 if (scavenging_started_
)
396 scavenging_started_
= true;
397 std::set
<std::string
> namespace_ids_in_use
;
398 for (StorageNamespaceMap::const_iterator it
= namespaces_
.begin();
399 it
!= namespaces_
.end(); ++it
)
400 namespace_ids_in_use
.insert(it
->second
->persistent_namespace_id());
401 std::set
<std::string
> protected_persistent_session_ids
;
402 protected_persistent_session_ids
.swap(protected_persistent_session_ids_
);
403 task_runner_
->PostShutdownBlockingTask(
404 FROM_HERE
, DOMStorageTaskRunner::COMMIT_SEQUENCE
,
406 &DOMStorageContextImpl::FindUnusedNamespacesInCommitSequence
,
407 this, namespace_ids_in_use
, protected_persistent_session_ids
));
410 void DOMStorageContextImpl::FindUnusedNamespacesInCommitSequence(
411 const std::set
<std::string
>& namespace_ids_in_use
,
412 const std::set
<std::string
>& protected_persistent_session_ids
) {
413 DCHECK(session_storage_database_
.get());
414 // Delete all namespaces which don't have an associated DOMStorageNamespace
416 std::map
<std::string
, std::vector
<GURL
> > namespaces_and_origins
;
417 session_storage_database_
->ReadNamespacesAndOrigins(&namespaces_and_origins
);
418 for (std::map
<std::string
, std::vector
<GURL
> >::const_iterator it
=
419 namespaces_and_origins
.begin();
420 it
!= namespaces_and_origins
.end(); ++it
) {
421 if (namespace_ids_in_use
.find(it
->first
) == namespace_ids_in_use
.end() &&
422 protected_persistent_session_ids
.find(it
->first
) ==
423 protected_persistent_session_ids
.end()) {
424 deletable_persistent_namespace_ids_
.push_back(it
->first
);
427 if (!deletable_persistent_namespace_ids_
.empty()) {
428 task_runner_
->PostDelayedTask(
429 FROM_HERE
, base::Bind(
430 &DOMStorageContextImpl::DeleteNextUnusedNamespace
,
432 base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds
));
436 void DOMStorageContextImpl::DeleteNextUnusedNamespace() {
439 task_runner_
->PostShutdownBlockingTask(
440 FROM_HERE
, DOMStorageTaskRunner::COMMIT_SEQUENCE
,
442 &DOMStorageContextImpl::DeleteNextUnusedNamespaceInCommitSequence
,
446 void DOMStorageContextImpl::DeleteNextUnusedNamespaceInCommitSequence() {
447 if (deletable_persistent_namespace_ids_
.empty())
449 const std::string
& persistent_id
= deletable_persistent_namespace_ids_
.back();
450 session_storage_database_
->DeleteNamespace(persistent_id
);
451 deletable_persistent_namespace_ids_
.pop_back();
452 if (!deletable_persistent_namespace_ids_
.empty()) {
453 task_runner_
->PostDelayedTask(
454 FROM_HERE
, base::Bind(
455 &DOMStorageContextImpl::DeleteNextUnusedNamespace
,
457 base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds
));
461 void DOMStorageContextImpl::AddTransactionLogProcessId(int64 namespace_id
,
463 DCHECK_NE(kLocalStorageNamespaceId
, namespace_id
);
464 StorageNamespaceMap::const_iterator it
= namespaces_
.find(namespace_id
);
465 if (it
== namespaces_
.end())
467 it
->second
->AddTransactionLogProcessId(process_id
);
470 void DOMStorageContextImpl::RemoveTransactionLogProcessId(int64 namespace_id
,
472 DCHECK_NE(kLocalStorageNamespaceId
, namespace_id
);
473 StorageNamespaceMap::const_iterator it
= namespaces_
.find(namespace_id
);
474 if (it
== namespaces_
.end())
476 it
->second
->RemoveTransactionLogProcessId(process_id
);
479 SessionStorageNamespace::MergeResult
480 DOMStorageContextImpl::MergeSessionStorage(
481 int64 namespace1_id
, bool actually_merge
, int process_id
,
482 int64 namespace2_id
) {
483 DCHECK_NE(kLocalStorageNamespaceId
, namespace1_id
);
484 DCHECK_NE(kLocalStorageNamespaceId
, namespace2_id
);
485 StorageNamespaceMap::const_iterator it
= namespaces_
.find(namespace1_id
);
486 if (it
== namespaces_
.end())
487 return SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_FOUND
;
488 DOMStorageNamespace
* ns1
= it
->second
;
489 it
= namespaces_
.find(namespace2_id
);
490 if (it
== namespaces_
.end())
491 return SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_FOUND
;
492 DOMStorageNamespace
* ns2
= it
->second
;
493 return ns1
->Merge(actually_merge
, process_id
, ns2
, this);
496 } // namespace content