• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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