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"
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"
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),
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();
61 session_storage_database_
= NULL
;
62 task_runner_
->PostShutdownBlockingTask(
64 DOMStorageTaskRunner::COMMIT_SEQUENCE
,
65 base::Bind(&SessionStorageDatabase::Release
,
66 base::Unretained(to_release
)));
70 DOMStorageNamespace
* DOMStorageContextImpl::GetStorageNamespace(
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
;
91 return found
->second
.get();
94 void DOMStorageContextImpl::GetLocalStorageUsage(
95 std::vector
<LocalStorageUsageInfo
>* infos
,
96 bool include_file_info
) {
97 if (localstorage_directory_
.empty())
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())
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
);
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
);
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
);
167 NotifyAreaCleared(area
, usage_info
.origin
);
170 void DOMStorageContextImpl::Flush() {
171 for (auto& entry
: namespaces_
)
172 entry
.second
->Flush();
175 void DOMStorageContextImpl::Shutdown() {
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())
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(
199 DOMStorageTaskRunner::COMMIT_SEQUENCE
,
200 base::Bind(&DOMStorageContextImpl::ClearSessionOnlyOrigins
, this));
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
) {
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
) {
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
) {
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(), '-', '_');
252 void DOMStorageContextImpl::CreateSessionNamespace(
254 const std::string
& persistent_namespace_id
) {
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(),
262 persistent_namespace_id_to_namespace_id_
[persistent_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())
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(
277 DOMStorageTaskRunner::COMMIT_SEQUENCE
,
279 base::IgnoreResult(&SessionStorageDatabase::DeleteNamespace
),
280 session_storage_database_
,
281 persistent_namespace_id
));
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
) {
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
);
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
))
318 if (!special_storage_policy_
->IsStorageSessionOnly(origin
))
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
))
333 if (!special_storage_policy_
->IsStorageSessionOnly(origin
))
335 session_storage_database_
->DeleteArea(infos
[i
].persistent_namespace_id
,
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
,
354 base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds
));
358 void DOMStorageContextImpl::FindUnusedNamespaces() {
359 DCHECK(session_storage_database_
.get());
360 if (scavenging_started_
)
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
,
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
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
,
398 base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds
));
402 void DOMStorageContextImpl::DeleteNextUnusedNamespace() {
405 task_runner_
->PostShutdownBlockingTask(
406 FROM_HERE
, DOMStorageTaskRunner::COMMIT_SEQUENCE
,
408 &DOMStorageContextImpl::DeleteNextUnusedNamespaceInCommitSequence
,
412 void DOMStorageContextImpl::DeleteNextUnusedNamespaceInCommitSequence() {
413 if (deletable_persistent_namespace_ids_
.empty())
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
,
423 base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds
));
427 } // namespace content