OLD | NEW |
| (Empty) |
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 "webkit/browser/dom_storage/dom_storage_context.h" | |
6 | |
7 #include "base/bind.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 "webkit/browser/dom_storage/dom_storage_area.h" | |
15 #include "webkit/browser/dom_storage/dom_storage_database.h" | |
16 #include "webkit/browser/dom_storage/dom_storage_namespace.h" | |
17 #include "webkit/browser/dom_storage/dom_storage_task_runner.h" | |
18 #include "webkit/browser/dom_storage/session_storage_database.h" | |
19 #include "webkit/browser/quota/special_storage_policy.h" | |
20 #include "webkit/common/dom_storage/dom_storage_types.h" | |
21 | |
22 namespace dom_storage { | |
23 | |
24 static const int kSessionStoraceScavengingSeconds = 60; | |
25 | |
26 DomStorageContext::DomStorageContext( | |
27 const base::FilePath& localstorage_directory, | |
28 const base::FilePath& sessionstorage_directory, | |
29 quota::SpecialStoragePolicy* special_storage_policy, | |
30 DomStorageTaskRunner* task_runner) | |
31 : localstorage_directory_(localstorage_directory), | |
32 sessionstorage_directory_(sessionstorage_directory), | |
33 task_runner_(task_runner), | |
34 is_shutdown_(false), | |
35 force_keep_session_state_(false), | |
36 special_storage_policy_(special_storage_policy), | |
37 scavenging_started_(false) { | |
38 // AtomicSequenceNum starts at 0 but we want to start session | |
39 // namespace ids at one since zero is reserved for the | |
40 // kLocalStorageNamespaceId. | |
41 session_id_sequence_.GetNext(); | |
42 } | |
43 | |
44 DomStorageContext::~DomStorageContext() { | |
45 if (session_storage_database_.get()) { | |
46 // SessionStorageDatabase shouldn't be deleted right away: deleting it will | |
47 // potentially involve waiting in leveldb::DBImpl::~DBImpl, and waiting | |
48 // shouldn't happen on this thread. | |
49 SessionStorageDatabase* to_release = session_storage_database_.get(); | |
50 to_release->AddRef(); | |
51 session_storage_database_ = NULL; | |
52 task_runner_->PostShutdownBlockingTask( | |
53 FROM_HERE, | |
54 DomStorageTaskRunner::COMMIT_SEQUENCE, | |
55 base::Bind(&SessionStorageDatabase::Release, | |
56 base::Unretained(to_release))); | |
57 } | |
58 } | |
59 | |
60 DomStorageNamespace* DomStorageContext::GetStorageNamespace( | |
61 int64 namespace_id) { | |
62 if (is_shutdown_) | |
63 return NULL; | |
64 StorageNamespaceMap::iterator found = namespaces_.find(namespace_id); | |
65 if (found == namespaces_.end()) { | |
66 if (namespace_id == kLocalStorageNamespaceId) { | |
67 if (!localstorage_directory_.empty()) { | |
68 if (!file_util::CreateDirectory(localstorage_directory_)) { | |
69 LOG(ERROR) << "Failed to create 'Local Storage' directory," | |
70 " falling back to in-memory only."; | |
71 localstorage_directory_ = base::FilePath(); | |
72 } | |
73 } | |
74 DomStorageNamespace* local = | |
75 new DomStorageNamespace(localstorage_directory_, task_runner_.get()); | |
76 namespaces_[kLocalStorageNamespaceId] = local; | |
77 return local; | |
78 } | |
79 return NULL; | |
80 } | |
81 return found->second.get(); | |
82 } | |
83 | |
84 void DomStorageContext::GetLocalStorageUsage( | |
85 std::vector<LocalStorageUsageInfo>* infos, | |
86 bool include_file_info) { | |
87 if (localstorage_directory_.empty()) | |
88 return; | |
89 base::FileEnumerator enumerator(localstorage_directory_, false, | |
90 base::FileEnumerator::FILES); | |
91 for (base::FilePath path = enumerator.Next(); !path.empty(); | |
92 path = enumerator.Next()) { | |
93 if (path.MatchesExtension(DomStorageArea::kDatabaseFileExtension)) { | |
94 LocalStorageUsageInfo info; | |
95 info.origin = DomStorageArea::OriginFromDatabaseFileName(path); | |
96 if (include_file_info) { | |
97 base::FileEnumerator::FileInfo find_info = enumerator.GetInfo(); | |
98 info.data_size = find_info.GetSize(); | |
99 info.last_modified = find_info.GetLastModifiedTime(); | |
100 } | |
101 infos->push_back(info); | |
102 } | |
103 } | |
104 } | |
105 | |
106 void DomStorageContext::GetSessionStorageUsage( | |
107 std::vector<SessionStorageUsageInfo>* infos) { | |
108 if (!session_storage_database_.get()) | |
109 return; | |
110 std::map<std::string, std::vector<GURL> > namespaces_and_origins; | |
111 session_storage_database_->ReadNamespacesAndOrigins( | |
112 &namespaces_and_origins); | |
113 for (std::map<std::string, std::vector<GURL> >::const_iterator it = | |
114 namespaces_and_origins.begin(); | |
115 it != namespaces_and_origins.end(); ++it) { | |
116 for (std::vector<GURL>::const_iterator origin_it = it->second.begin(); | |
117 origin_it != it->second.end(); ++origin_it) { | |
118 SessionStorageUsageInfo info; | |
119 info.persistent_namespace_id = it->first; | |
120 info.origin = *origin_it; | |
121 infos->push_back(info); | |
122 } | |
123 } | |
124 } | |
125 | |
126 void DomStorageContext::DeleteLocalStorage(const GURL& origin) { | |
127 DCHECK(!is_shutdown_); | |
128 DomStorageNamespace* local = GetStorageNamespace(kLocalStorageNamespaceId); | |
129 local->DeleteLocalStorageOrigin(origin); | |
130 // Synthesize a 'cleared' event if the area is open so CachedAreas in | |
131 // renderers get emptied out too. | |
132 DomStorageArea* area = local->GetOpenStorageArea(origin); | |
133 if (area) | |
134 NotifyAreaCleared(area, origin); | |
135 } | |
136 | |
137 void DomStorageContext::DeleteSessionStorage( | |
138 const SessionStorageUsageInfo& usage_info) { | |
139 DCHECK(!is_shutdown_); | |
140 DomStorageNamespace* dom_storage_namespace = NULL; | |
141 std::map<std::string, int64>::const_iterator it = | |
142 persistent_namespace_id_to_namespace_id_.find( | |
143 usage_info.persistent_namespace_id); | |
144 if (it != persistent_namespace_id_to_namespace_id_.end()) { | |
145 dom_storage_namespace = GetStorageNamespace(it->second); | |
146 } else { | |
147 int64 namespace_id = AllocateSessionId(); | |
148 CreateSessionNamespace(namespace_id, usage_info.persistent_namespace_id); | |
149 dom_storage_namespace = GetStorageNamespace(namespace_id); | |
150 } | |
151 dom_storage_namespace->DeleteSessionStorageOrigin(usage_info.origin); | |
152 // Synthesize a 'cleared' event if the area is open so CachedAreas in | |
153 // renderers get emptied out too. | |
154 DomStorageArea* area = | |
155 dom_storage_namespace->GetOpenStorageArea(usage_info.origin); | |
156 if (area) | |
157 NotifyAreaCleared(area, usage_info.origin); | |
158 } | |
159 | |
160 void DomStorageContext::PurgeMemory() { | |
161 // We can only purge memory from the local storage namespace | |
162 // which is backed by disk. | |
163 // TODO(marja): Purge sessionStorage, too. (Requires changes to the FastClear | |
164 // functionality.) | |
165 StorageNamespaceMap::iterator found = | |
166 namespaces_.find(kLocalStorageNamespaceId); | |
167 if (found != namespaces_.end()) | |
168 found->second->PurgeMemory(DomStorageNamespace::PURGE_AGGRESSIVE); | |
169 } | |
170 | |
171 void DomStorageContext::Shutdown() { | |
172 is_shutdown_ = true; | |
173 StorageNamespaceMap::const_iterator it = namespaces_.begin(); | |
174 for (; it != namespaces_.end(); ++it) | |
175 it->second->Shutdown(); | |
176 | |
177 if (localstorage_directory_.empty() && !session_storage_database_.get()) | |
178 return; | |
179 | |
180 // Respect the content policy settings about what to | |
181 // keep and what to discard. | |
182 if (force_keep_session_state_) | |
183 return; // Keep everything. | |
184 | |
185 bool has_session_only_origins = | |
186 special_storage_policy_.get() && | |
187 special_storage_policy_->HasSessionOnlyOrigins(); | |
188 | |
189 if (has_session_only_origins) { | |
190 // We may have to delete something. We continue on the | |
191 // commit sequence after area shutdown tasks have cycled | |
192 // thru that sequence (and closed their database files). | |
193 bool success = task_runner_->PostShutdownBlockingTask( | |
194 FROM_HERE, | |
195 DomStorageTaskRunner::COMMIT_SEQUENCE, | |
196 base::Bind(&DomStorageContext::ClearSessionOnlyOrigins, this)); | |
197 DCHECK(success); | |
198 } | |
199 } | |
200 | |
201 void DomStorageContext::AddEventObserver(EventObserver* observer) { | |
202 event_observers_.AddObserver(observer); | |
203 } | |
204 | |
205 void DomStorageContext::RemoveEventObserver(EventObserver* observer) { | |
206 event_observers_.RemoveObserver(observer); | |
207 } | |
208 | |
209 void DomStorageContext::NotifyItemSet( | |
210 const DomStorageArea* area, | |
211 const base::string16& key, | |
212 const base::string16& new_value, | |
213 const base::NullableString16& old_value, | |
214 const GURL& page_url) { | |
215 FOR_EACH_OBSERVER( | |
216 EventObserver, event_observers_, | |
217 OnDomStorageItemSet(area, key, new_value, old_value, page_url)); | |
218 } | |
219 | |
220 void DomStorageContext::NotifyItemRemoved( | |
221 const DomStorageArea* area, | |
222 const base::string16& key, | |
223 const base::string16& old_value, | |
224 const GURL& page_url) { | |
225 FOR_EACH_OBSERVER( | |
226 EventObserver, event_observers_, | |
227 OnDomStorageItemRemoved(area, key, old_value, page_url)); | |
228 } | |
229 | |
230 void DomStorageContext::NotifyAreaCleared( | |
231 const DomStorageArea* area, | |
232 const GURL& page_url) { | |
233 FOR_EACH_OBSERVER( | |
234 EventObserver, event_observers_, | |
235 OnDomStorageAreaCleared(area, page_url)); | |
236 } | |
237 | |
238 std::string DomStorageContext::AllocatePersistentSessionId() { | |
239 std::string guid = base::GenerateGUID(); | |
240 std::replace(guid.begin(), guid.end(), '-', '_'); | |
241 return guid; | |
242 } | |
243 | |
244 void DomStorageContext::CreateSessionNamespace( | |
245 int64 namespace_id, | |
246 const std::string& persistent_namespace_id) { | |
247 if (is_shutdown_) | |
248 return; | |
249 DCHECK(namespace_id != kLocalStorageNamespaceId); | |
250 DCHECK(namespaces_.find(namespace_id) == namespaces_.end()); | |
251 namespaces_[namespace_id] = new DomStorageNamespace( | |
252 namespace_id, persistent_namespace_id, session_storage_database_.get(), | |
253 task_runner_.get()); | |
254 persistent_namespace_id_to_namespace_id_[persistent_namespace_id] = | |
255 namespace_id; | |
256 } | |
257 | |
258 void DomStorageContext::DeleteSessionNamespace( | |
259 int64 namespace_id, bool should_persist_data) { | |
260 DCHECK_NE(kLocalStorageNamespaceId, namespace_id); | |
261 StorageNamespaceMap::const_iterator it = namespaces_.find(namespace_id); | |
262 if (it == namespaces_.end()) | |
263 return; | |
264 std::string persistent_namespace_id = it->second->persistent_namespace_id(); | |
265 if (session_storage_database_.get()) { | |
266 if (!should_persist_data) { | |
267 task_runner_->PostShutdownBlockingTask( | |
268 FROM_HERE, | |
269 DomStorageTaskRunner::COMMIT_SEQUENCE, | |
270 base::Bind( | |
271 base::IgnoreResult(&SessionStorageDatabase::DeleteNamespace), | |
272 session_storage_database_, | |
273 persistent_namespace_id)); | |
274 } else { | |
275 // Ensure that the data gets committed before we shut down. | |
276 it->second->Shutdown(); | |
277 if (!scavenging_started_) { | |
278 // Protect the persistent namespace ID from scavenging. | |
279 protected_persistent_session_ids_.insert(persistent_namespace_id); | |
280 } | |
281 } | |
282 } | |
283 persistent_namespace_id_to_namespace_id_.erase(persistent_namespace_id); | |
284 namespaces_.erase(namespace_id); | |
285 } | |
286 | |
287 void DomStorageContext::CloneSessionNamespace( | |
288 int64 existing_id, int64 new_id, | |
289 const std::string& new_persistent_id) { | |
290 if (is_shutdown_) | |
291 return; | |
292 DCHECK_NE(kLocalStorageNamespaceId, existing_id); | |
293 DCHECK_NE(kLocalStorageNamespaceId, new_id); | |
294 StorageNamespaceMap::iterator found = namespaces_.find(existing_id); | |
295 if (found != namespaces_.end()) | |
296 namespaces_[new_id] = found->second->Clone(new_id, new_persistent_id); | |
297 else | |
298 CreateSessionNamespace(new_id, new_persistent_id); | |
299 } | |
300 | |
301 void DomStorageContext::ClearSessionOnlyOrigins() { | |
302 if (!localstorage_directory_.empty()) { | |
303 std::vector<LocalStorageUsageInfo> infos; | |
304 const bool kDontIncludeFileInfo = false; | |
305 GetLocalStorageUsage(&infos, kDontIncludeFileInfo); | |
306 for (size_t i = 0; i < infos.size(); ++i) { | |
307 const GURL& origin = infos[i].origin; | |
308 if (special_storage_policy_->IsStorageProtected(origin)) | |
309 continue; | |
310 if (!special_storage_policy_->IsStorageSessionOnly(origin)) | |
311 continue; | |
312 | |
313 base::FilePath database_file_path = localstorage_directory_.Append( | |
314 DomStorageArea::DatabaseFileNameFromOrigin(origin)); | |
315 sql::Connection::Delete(database_file_path); | |
316 } | |
317 } | |
318 if (session_storage_database_.get()) { | |
319 std::vector<SessionStorageUsageInfo> infos; | |
320 GetSessionStorageUsage(&infos); | |
321 for (size_t i = 0; i < infos.size(); ++i) { | |
322 const GURL& origin = infos[i].origin; | |
323 if (special_storage_policy_->IsStorageProtected(origin)) | |
324 continue; | |
325 if (!special_storage_policy_->IsStorageSessionOnly(origin)) | |
326 continue; | |
327 session_storage_database_->DeleteArea(infos[i].persistent_namespace_id, | |
328 origin); | |
329 } | |
330 } | |
331 } | |
332 | |
333 void DomStorageContext::SetSaveSessionStorageOnDisk() { | |
334 DCHECK(namespaces_.empty()); | |
335 if (!sessionstorage_directory_.empty()) { | |
336 session_storage_database_ = new SessionStorageDatabase( | |
337 sessionstorage_directory_); | |
338 } | |
339 } | |
340 | |
341 void DomStorageContext::StartScavengingUnusedSessionStorage() { | |
342 if (session_storage_database_.get()) { | |
343 task_runner_->PostDelayedTask( | |
344 FROM_HERE, base::Bind(&DomStorageContext::FindUnusedNamespaces, this), | |
345 base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds)); | |
346 } | |
347 } | |
348 | |
349 void DomStorageContext::FindUnusedNamespaces() { | |
350 DCHECK(session_storage_database_.get()); | |
351 if (scavenging_started_) | |
352 return; | |
353 scavenging_started_ = true; | |
354 std::set<std::string> namespace_ids_in_use; | |
355 for (StorageNamespaceMap::const_iterator it = namespaces_.begin(); | |
356 it != namespaces_.end(); ++it) | |
357 namespace_ids_in_use.insert(it->second->persistent_namespace_id()); | |
358 std::set<std::string> protected_persistent_session_ids; | |
359 protected_persistent_session_ids.swap(protected_persistent_session_ids_); | |
360 task_runner_->PostShutdownBlockingTask( | |
361 FROM_HERE, DomStorageTaskRunner::COMMIT_SEQUENCE, | |
362 base::Bind( | |
363 &DomStorageContext::FindUnusedNamespacesInCommitSequence, | |
364 this, namespace_ids_in_use, protected_persistent_session_ids)); | |
365 } | |
366 | |
367 void DomStorageContext::FindUnusedNamespacesInCommitSequence( | |
368 const std::set<std::string>& namespace_ids_in_use, | |
369 const std::set<std::string>& protected_persistent_session_ids) { | |
370 DCHECK(session_storage_database_.get()); | |
371 // Delete all namespaces which don't have an associated DomStorageNamespace | |
372 // alive. | |
373 std::map<std::string, std::vector<GURL> > namespaces_and_origins; | |
374 session_storage_database_->ReadNamespacesAndOrigins(&namespaces_and_origins); | |
375 for (std::map<std::string, std::vector<GURL> >::const_iterator it = | |
376 namespaces_and_origins.begin(); | |
377 it != namespaces_and_origins.end(); ++it) { | |
378 if (namespace_ids_in_use.find(it->first) == namespace_ids_in_use.end() && | |
379 protected_persistent_session_ids.find(it->first) == | |
380 protected_persistent_session_ids.end()) { | |
381 deletable_persistent_namespace_ids_.push_back(it->first); | |
382 } | |
383 } | |
384 if (!deletable_persistent_namespace_ids_.empty()) { | |
385 task_runner_->PostDelayedTask( | |
386 FROM_HERE, base::Bind( | |
387 &DomStorageContext::DeleteNextUnusedNamespace, | |
388 this), | |
389 base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds)); | |
390 } | |
391 } | |
392 | |
393 void DomStorageContext::DeleteNextUnusedNamespace() { | |
394 if (is_shutdown_) | |
395 return; | |
396 task_runner_->PostShutdownBlockingTask( | |
397 FROM_HERE, DomStorageTaskRunner::COMMIT_SEQUENCE, | |
398 base::Bind( | |
399 &DomStorageContext::DeleteNextUnusedNamespaceInCommitSequence, | |
400 this)); | |
401 } | |
402 | |
403 void DomStorageContext::DeleteNextUnusedNamespaceInCommitSequence() { | |
404 if (deletable_persistent_namespace_ids_.empty()) | |
405 return; | |
406 const std::string& persistent_id = deletable_persistent_namespace_ids_.back(); | |
407 session_storage_database_->DeleteNamespace(persistent_id); | |
408 deletable_persistent_namespace_ids_.pop_back(); | |
409 if (!deletable_persistent_namespace_ids_.empty()) { | |
410 task_runner_->PostDelayedTask( | |
411 FROM_HERE, base::Bind( | |
412 &DomStorageContext::DeleteNextUnusedNamespace, | |
413 this), | |
414 base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds)); | |
415 } | |
416 } | |
417 | |
418 } // namespace dom_storage | |
OLD | NEW |