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.
4
5 #include "content/browser/dom_storage/dom_storage_context_impl.h"
6
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/files/file_enumerator.h"
10 #include "base/files/file_util.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 "storage/browser/quota/special_storage_policy.h"
24
25 namespace content {
26
27 static const int kSessionStoraceScavengingSeconds = 60;
28
DOMStorageContextImpl(const base::FilePath & localstorage_directory,const base::FilePath & sessionstorage_directory,storage::SpecialStoragePolicy * special_storage_policy,DOMStorageTaskRunner * task_runner)29 DOMStorageContextImpl::DOMStorageContextImpl(
30 const base::FilePath& localstorage_directory,
31 const base::FilePath& sessionstorage_directory,
32 storage::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();
45 }
46
~DOMStorageContextImpl()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();
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
GetStorageNamespace(int64 namespace_id)63 DOMStorageNamespace* 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 (!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();
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();
85 }
86
GetLocalStorageUsage(std::vector<LocalStorageUsageInfo> * infos,bool include_file_info)87 void DOMStorageContextImpl::GetLocalStorageUsage(
88 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 }
107 }
108
GetSessionStorageUsage(std::vector<SessionStorageUsageInfo> * infos)109 void DOMStorageContextImpl::GetSessionStorageUsage(
110 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 }
127 }
128
DeleteLocalStorage(const GURL & origin)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);
136 if (area)
137 NotifyAreaCleared(area, origin);
138 }
139
DeleteSessionStorage(const SessionStorageUsageInfo & usage_info)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);
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);
161 }
162
Shutdown()163 void DOMStorageContextImpl::Shutdown() {
164 is_shutdown_ = true;
165 StorageNamespaceMap::const_iterator it = namespaces_.begin();
166 for (; it != namespaces_.end(); ++it)
167 it->second->Shutdown();
168
169 if (localstorage_directory_.empty() && !session_storage_database_.get())
170 return;
171
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.
176
177 bool has_session_only_origins =
178 special_storage_policy_.get() &&
179 special_storage_policy_->HasSessionOnlyOrigins();
180
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(
186 FROM_HERE,
187 DOMStorageTaskRunner::COMMIT_SEQUENCE,
188 base::Bind(&DOMStorageContextImpl::ClearSessionOnlyOrigins, this));
189 DCHECK(success);
190 }
191 }
192
AddEventObserver(EventObserver * observer)193 void DOMStorageContextImpl::AddEventObserver(EventObserver* observer) {
194 event_observers_.AddObserver(observer);
195 }
196
RemoveEventObserver(EventObserver * observer)197 void DOMStorageContextImpl::RemoveEventObserver(EventObserver* observer) {
198 event_observers_.RemoveObserver(observer);
199 }
200
NotifyItemSet(const DOMStorageArea * area,const base::string16 & key,const base::string16 & new_value,const base::NullableString16 & old_value,const GURL & page_url)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) {
207 FOR_EACH_OBSERVER(
208 EventObserver, event_observers_,
209 OnDOMStorageItemSet(area, key, new_value, old_value, page_url));
210 }
211
NotifyItemRemoved(const DOMStorageArea * area,const base::string16 & key,const base::string16 & old_value,const GURL & 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) {
217 FOR_EACH_OBSERVER(
218 EventObserver, event_observers_,
219 OnDOMStorageItemRemoved(area, key, old_value, page_url));
220 }
221
NotifyAreaCleared(const DOMStorageArea * area,const GURL & page_url)222 void DOMStorageContextImpl::NotifyAreaCleared(
223 const DOMStorageArea* area,
224 const GURL& page_url) {
225 FOR_EACH_OBSERVER(
226 EventObserver, event_observers_,
227 OnDOMStorageAreaCleared(area, page_url));
228 }
229
NotifyAliasSessionMerged(int64 namespace_id,DOMStorageNamespace * old_alias_master_namespace)230 void DOMStorageContextImpl::NotifyAliasSessionMerged(
231 int64 namespace_id,
232 DOMStorageNamespace* old_alias_master_namespace) {
233 FOR_EACH_OBSERVER(
234 EventObserver, event_observers_,
235 OnDOMSessionStorageReset(namespace_id));
236 if (old_alias_master_namespace)
237 MaybeShutdownSessionNamespace(old_alias_master_namespace);
238 }
239
AllocatePersistentSessionId()240 std::string DOMStorageContextImpl::AllocatePersistentSessionId() {
241 std::string guid = base::GenerateGUID();
242 std::replace(guid.begin(), guid.end(), '-', '_');
243 return guid;
244 }
245
CreateSessionNamespace(int64 namespace_id,const std::string & persistent_namespace_id)246 void DOMStorageContextImpl::CreateSessionNamespace(
247 int64 namespace_id,
248 const std::string& persistent_namespace_id) {
249 if (is_shutdown_)
250 return;
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(),
255 task_runner_.get());
256 persistent_namespace_id_to_namespace_id_[persistent_namespace_id] =
257 namespace_id;
258 }
259
DeleteSessionNamespace(int64 namespace_id,bool should_persist_data)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()) {
266 return;
267 }
268 it->second->set_ready_for_deletion_pending_aliases(true);
269 DOMStorageNamespace* alias_master = it->second->alias_master_namespace();
270 if (alias_master) {
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);
278 } else {
279 if (should_persist_data)
280 it->second->set_must_persist_at_shutdown(true);
281 MaybeShutdownSessionNamespace(it->second.get());
282 }
283 }
284
MaybeShutdownSessionNamespace(DOMStorageNamespace * ns)285 void DOMStorageContextImpl::MaybeShutdownSessionNamespace(
286 DOMStorageNamespace* ns) {
287 if (ns->num_aliases() > 0 || !ns->ready_for_deletion_pending_aliases())
288 return;
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(
295 FROM_HERE,
296 DOMStorageTaskRunner::COMMIT_SEQUENCE,
297 base::Bind(
298 base::IgnoreResult(&SessionStorageDatabase::DeleteNamespace),
299 session_storage_database_,
300 persistent_namespace_id));
301 } else {
302 // Ensure that the data gets committed before we shut down.
303 ns->Shutdown();
304 if (!scavenging_started_) {
305 // Protect the persistent namespace ID from scavenging.
306 protected_persistent_session_ids_.insert(persistent_namespace_id);
307 }
308 }
309 }
310 persistent_namespace_id_to_namespace_id_.erase(persistent_namespace_id);
311 namespaces_.erase(ns->namespace_id());
312 }
313
CloneSessionNamespace(int64 existing_id,int64 new_id,const std::string & new_persistent_id)314 void DOMStorageContextImpl::CloneSessionNamespace(
315 int64 existing_id, int64 new_id,
316 const std::string& new_persistent_id) {
317 if (is_shutdown_)
318 return;
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);
324 else
325 CreateSessionNamespace(new_id, new_persistent_id);
326 }
327
CreateAliasSessionNamespace(int64 existing_id,int64 new_id,const std::string & persistent_id)328 void DOMStorageContextImpl::CreateAliasSessionNamespace(
329 int64 existing_id, int64 new_id,
330 const std::string& persistent_id) {
331 if (is_shutdown_)
332 return;
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);
338 } else {
339 CreateSessionNamespace(new_id, persistent_id);
340 }
341 }
342
ClearSessionOnlyOrigins()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))
351 continue;
352 if (!special_storage_policy_->IsStorageSessionOnly(origin))
353 continue;
354
355 base::FilePath database_file_path = localstorage_directory_.Append(
356 DOMStorageArea::DatabaseFileNameFromOrigin(origin));
357 sql::Connection::Delete(database_file_path);
358 }
359 }
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))
366 continue;
367 if (!special_storage_policy_->IsStorageSessionOnly(origin))
368 continue;
369 session_storage_database_->DeleteArea(infos[i].persistent_namespace_id,
370 origin);
371 }
372 }
373 }
374
SetSaveSessionStorageOnDisk()375 void DOMStorageContextImpl::SetSaveSessionStorageOnDisk() {
376 DCHECK(namespaces_.empty());
377 if (!sessionstorage_directory_.empty()) {
378 session_storage_database_ = new SessionStorageDatabase(
379 sessionstorage_directory_);
380 }
381 }
382
StartScavengingUnusedSessionStorage()383 void DOMStorageContextImpl::StartScavengingUnusedSessionStorage() {
384 if (session_storage_database_.get()) {
385 task_runner_->PostDelayedTask(
386 FROM_HERE, base::Bind(&DOMStorageContextImpl::FindUnusedNamespaces,
387 this),
388 base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds));
389 }
390 }
391
FindUnusedNamespaces()392 void DOMStorageContextImpl::FindUnusedNamespaces() {
393 DCHECK(session_storage_database_.get());
394 if (scavenging_started_)
395 return;
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,
405 base::Bind(
406 &DOMStorageContextImpl::FindUnusedNamespacesInCommitSequence,
407 this, namespace_ids_in_use, protected_persistent_session_ids));
408 }
409
FindUnusedNamespacesInCommitSequence(const std::set<std::string> & namespace_ids_in_use,const std::set<std::string> & 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
415 // alive.
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);
425 }
426 }
427 if (!deletable_persistent_namespace_ids_.empty()) {
428 task_runner_->PostDelayedTask(
429 FROM_HERE, base::Bind(
430 &DOMStorageContextImpl::DeleteNextUnusedNamespace,
431 this),
432 base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds));
433 }
434 }
435
DeleteNextUnusedNamespace()436 void DOMStorageContextImpl::DeleteNextUnusedNamespace() {
437 if (is_shutdown_)
438 return;
439 task_runner_->PostShutdownBlockingTask(
440 FROM_HERE, DOMStorageTaskRunner::COMMIT_SEQUENCE,
441 base::Bind(
442 &DOMStorageContextImpl::DeleteNextUnusedNamespaceInCommitSequence,
443 this));
444 }
445
DeleteNextUnusedNamespaceInCommitSequence()446 void DOMStorageContextImpl::DeleteNextUnusedNamespaceInCommitSequence() {
447 if (deletable_persistent_namespace_ids_.empty())
448 return;
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,
456 this),
457 base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds));
458 }
459 }
460
AddTransactionLogProcessId(int64 namespace_id,int process_id)461 void DOMStorageContextImpl::AddTransactionLogProcessId(int64 namespace_id,
462 int process_id) {
463 DCHECK_NE(kLocalStorageNamespaceId, namespace_id);
464 StorageNamespaceMap::const_iterator it = namespaces_.find(namespace_id);
465 if (it == namespaces_.end())
466 return;
467 it->second->AddTransactionLogProcessId(process_id);
468 }
469
RemoveTransactionLogProcessId(int64 namespace_id,int process_id)470 void DOMStorageContextImpl::RemoveTransactionLogProcessId(int64 namespace_id,
471 int process_id) {
472 DCHECK_NE(kLocalStorageNamespaceId, namespace_id);
473 StorageNamespaceMap::const_iterator it = namespaces_.find(namespace_id);
474 if (it == namespaces_.end())
475 return;
476 it->second->RemoveTransactionLogProcessId(process_id);
477 }
478
479 SessionStorageNamespace::MergeResult
MergeSessionStorage(int64 namespace1_id,bool actually_merge,int process_id,int64 namespace2_id)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.get();
489 it = namespaces_.find(namespace2_id);
490 if (it == namespaces_.end())
491 return SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_FOUND;
492 DOMStorageNamespace* ns2 = it->second.get();
493 return ns1->Merge(actually_merge, process_id, ns2, this);
494 }
495
496 } // namespace content
497