1 // Copyright 2013 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_namespace.h"
6
7 #include <set>
8 #include <utility>
9
10 #include "base/basictypes.h"
11 #include "base/bind.h"
12 #include "base/location.h"
13 #include "base/logging.h"
14 #include "base/stl_util.h"
15 #include "content/browser/dom_storage/dom_storage_area.h"
16 #include "content/browser/dom_storage/dom_storage_context_impl.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/common/child_process_host.h"
21
22 namespace content {
23
24 namespace {
25
26 static const unsigned int kMaxTransactionLogEntries = 8 * 1024;
27
28 } // namespace
29
DOMStorageNamespace(const base::FilePath & directory,DOMStorageTaskRunner * task_runner)30 DOMStorageNamespace::DOMStorageNamespace(
31 const base::FilePath& directory,
32 DOMStorageTaskRunner* task_runner)
33 : namespace_id_(kLocalStorageNamespaceId),
34 directory_(directory),
35 task_runner_(task_runner),
36 num_aliases_(0),
37 old_master_for_close_area_(NULL),
38 master_alias_count_decremented_(false),
39 ready_for_deletion_pending_aliases_(false),
40 must_persist_at_shutdown_(false) {
41 }
42
DOMStorageNamespace(int64 namespace_id,const std::string & persistent_namespace_id,SessionStorageDatabase * session_storage_database,DOMStorageTaskRunner * task_runner)43 DOMStorageNamespace::DOMStorageNamespace(
44 int64 namespace_id,
45 const std::string& persistent_namespace_id,
46 SessionStorageDatabase* session_storage_database,
47 DOMStorageTaskRunner* task_runner)
48 : namespace_id_(namespace_id),
49 persistent_namespace_id_(persistent_namespace_id),
50 task_runner_(task_runner),
51 session_storage_database_(session_storage_database),
52 num_aliases_(0),
53 old_master_for_close_area_(NULL),
54 master_alias_count_decremented_(false),
55 ready_for_deletion_pending_aliases_(false),
56 must_persist_at_shutdown_(false) {
57 DCHECK_NE(kLocalStorageNamespaceId, namespace_id);
58 }
59
~DOMStorageNamespace()60 DOMStorageNamespace::~DOMStorageNamespace() {
61 STLDeleteValues(&transactions_);
62 DecrementMasterAliasCount();
63 }
64
OpenStorageArea(const GURL & origin)65 DOMStorageArea* DOMStorageNamespace::OpenStorageArea(const GURL& origin) {
66 if (alias_master_namespace_.get())
67 return alias_master_namespace_->OpenStorageArea(origin);
68 if (AreaHolder* holder = GetAreaHolder(origin)) {
69 ++(holder->open_count_);
70 return holder->area_.get();
71 }
72 DOMStorageArea* area;
73 if (namespace_id_ == kLocalStorageNamespaceId) {
74 area = new DOMStorageArea(origin, directory_, task_runner_.get());
75 } else {
76 area = new DOMStorageArea(
77 namespace_id_, persistent_namespace_id_, origin,
78 session_storage_database_.get(), task_runner_.get());
79 }
80 areas_[origin] = AreaHolder(area, 1);
81 return area;
82 }
83
CloseStorageArea(DOMStorageArea * area)84 void DOMStorageNamespace::CloseStorageArea(DOMStorageArea* area) {
85 AreaHolder* holder = GetAreaHolder(area->origin());
86 if (alias_master_namespace_.get()) {
87 DCHECK(!holder);
88 if (old_master_for_close_area_)
89 old_master_for_close_area_->CloseStorageArea(area);
90 else
91 alias_master_namespace_->CloseStorageArea(area);
92 return;
93 }
94 DCHECK(holder);
95 DCHECK_EQ(holder->area_.get(), area);
96 --(holder->open_count_);
97 // TODO(michaeln): Clean up areas that aren't needed in memory anymore.
98 // The in-process-webkit based impl didn't do this either, but would be nice.
99 }
100
GetOpenStorageArea(const GURL & origin)101 DOMStorageArea* DOMStorageNamespace::GetOpenStorageArea(const GURL& origin) {
102 if (alias_master_namespace_.get())
103 return alias_master_namespace_->GetOpenStorageArea(origin);
104 AreaHolder* holder = GetAreaHolder(origin);
105 if (holder && holder->open_count_)
106 return holder->area_.get();
107 return NULL;
108 }
109
Clone(int64 clone_namespace_id,const std::string & clone_persistent_namespace_id)110 DOMStorageNamespace* DOMStorageNamespace::Clone(
111 int64 clone_namespace_id,
112 const std::string& clone_persistent_namespace_id) {
113 if (alias_master_namespace_.get()) {
114 return alias_master_namespace_->Clone(clone_namespace_id,
115 clone_persistent_namespace_id);
116 }
117 DCHECK_NE(kLocalStorageNamespaceId, namespace_id_);
118 DCHECK_NE(kLocalStorageNamespaceId, clone_namespace_id);
119 DOMStorageNamespace* clone = new DOMStorageNamespace(
120 clone_namespace_id, clone_persistent_namespace_id,
121 session_storage_database_.get(), task_runner_.get());
122 AreaMap::const_iterator it = areas_.begin();
123 // Clone the in-memory structures.
124 for (; it != areas_.end(); ++it) {
125 DOMStorageArea* area = it->second.area_->ShallowCopy(
126 clone_namespace_id, clone_persistent_namespace_id);
127 clone->areas_[it->first] = AreaHolder(area, 0);
128 }
129 // And clone the on-disk structures, too.
130 if (session_storage_database_.get()) {
131 task_runner_->PostShutdownBlockingTask(
132 FROM_HERE,
133 DOMStorageTaskRunner::COMMIT_SEQUENCE,
134 base::Bind(base::IgnoreResult(&SessionStorageDatabase::CloneNamespace),
135 session_storage_database_.get(), persistent_namespace_id_,
136 clone_persistent_namespace_id));
137 }
138 return clone;
139 }
140
CreateAlias(int64 alias_namespace_id)141 DOMStorageNamespace* DOMStorageNamespace::CreateAlias(
142 int64 alias_namespace_id) {
143 // Creates an alias of the current DOMStorageNamespace.
144 // The alias will have a reference to this namespace (called the master),
145 // and all operations will be redirected to the master (in particular,
146 // the alias will never open any areas of its own, but always redirect
147 // to the master). Accordingly, an alias will also never undergo the shutdown
148 // procedure which initiates persisting to disk, since there is never any data
149 // of its own to persist to disk. DOMStorageContextImpl is the place where
150 // shutdowns are initiated, but only for non-alias DOMStorageNamespaces.
151 DCHECK_NE(kLocalStorageNamespaceId, namespace_id_);
152 DCHECK_NE(kLocalStorageNamespaceId, alias_namespace_id);
153 DOMStorageNamespace* alias = new DOMStorageNamespace(
154 alias_namespace_id, persistent_namespace_id_,
155 session_storage_database_.get(), task_runner_.get());
156 if (alias_master_namespace_.get() != NULL) {
157 DCHECK(alias_master_namespace_->alias_master_namespace_.get() == NULL);
158 alias->alias_master_namespace_ = alias_master_namespace_;
159 } else {
160 alias->alias_master_namespace_ = this;
161 }
162 alias->alias_master_namespace_->num_aliases_++;
163 return alias;
164 }
165
DeleteLocalStorageOrigin(const GURL & origin)166 void DOMStorageNamespace::DeleteLocalStorageOrigin(const GURL& origin) {
167 DCHECK(!session_storage_database_.get());
168 DCHECK(!alias_master_namespace_.get());
169 AreaHolder* holder = GetAreaHolder(origin);
170 if (holder) {
171 holder->area_->DeleteOrigin();
172 return;
173 }
174 if (!directory_.empty()) {
175 scoped_refptr<DOMStorageArea> area =
176 new DOMStorageArea(origin, directory_, task_runner_.get());
177 area->DeleteOrigin();
178 }
179 }
180
DeleteSessionStorageOrigin(const GURL & origin)181 void DOMStorageNamespace::DeleteSessionStorageOrigin(const GURL& origin) {
182 if (alias_master_namespace_.get()) {
183 alias_master_namespace_->DeleteSessionStorageOrigin(origin);
184 return;
185 }
186 DOMStorageArea* area = OpenStorageArea(origin);
187 area->FastClear();
188 CloseStorageArea(area);
189 }
190
PurgeMemory(PurgeOption option)191 void DOMStorageNamespace::PurgeMemory(PurgeOption option) {
192 if (alias_master_namespace_.get()) {
193 alias_master_namespace_->PurgeMemory(option);
194 return;
195 }
196 if (directory_.empty())
197 return; // We can't purge w/o backing on disk.
198 AreaMap::iterator it = areas_.begin();
199 while (it != areas_.end()) {
200 // Leave it alone if changes are pending
201 if (it->second.area_->HasUncommittedChanges()) {
202 ++it;
203 continue;
204 }
205
206 // If not in use, we can shut it down and remove
207 // it from our collection entirely.
208 if (it->second.open_count_ == 0) {
209 it->second.area_->Shutdown();
210 areas_.erase(it++);
211 continue;
212 }
213
214 if (option == PURGE_AGGRESSIVE) {
215 // If aggressive is true, we clear caches and such
216 // for opened areas.
217 it->second.area_->PurgeMemory();
218 }
219
220 ++it;
221 }
222 }
223
Shutdown()224 void DOMStorageNamespace::Shutdown() {
225 AreaMap::const_iterator it = areas_.begin();
226 for (; it != areas_.end(); ++it)
227 it->second.area_->Shutdown();
228 }
229
CountInMemoryAreas() const230 unsigned int DOMStorageNamespace::CountInMemoryAreas() const {
231 if (alias_master_namespace_.get())
232 return alias_master_namespace_->CountInMemoryAreas();
233 unsigned int area_count = 0;
234 for (AreaMap::const_iterator it = areas_.begin(); it != areas_.end(); ++it) {
235 if (it->second.area_->IsLoadedInMemory())
236 ++area_count;
237 }
238 return area_count;
239 }
240
241 DOMStorageNamespace::AreaHolder*
GetAreaHolder(const GURL & origin)242 DOMStorageNamespace::GetAreaHolder(const GURL& origin) {
243 AreaMap::iterator found = areas_.find(origin);
244 if (found == areas_.end())
245 return NULL;
246 return &(found->second);
247 }
248
AddTransactionLogProcessId(int process_id)249 void DOMStorageNamespace::AddTransactionLogProcessId(int process_id) {
250 DCHECK(process_id != ChildProcessHost::kInvalidUniqueID);
251 DCHECK(transactions_.count(process_id) == 0);
252 TransactionData* transaction_data = new TransactionData;
253 transactions_[process_id] = transaction_data;
254 }
255
RemoveTransactionLogProcessId(int process_id)256 void DOMStorageNamespace::RemoveTransactionLogProcessId(int process_id) {
257 DCHECK(process_id != ChildProcessHost::kInvalidUniqueID);
258 DCHECK(transactions_.count(process_id) == 1);
259 delete transactions_[process_id];
260 transactions_.erase(process_id);
261 }
262
Merge(bool actually_merge,int process_id,DOMStorageNamespace * other,DOMStorageContextImpl * context)263 SessionStorageNamespace::MergeResult DOMStorageNamespace::Merge(
264 bool actually_merge,
265 int process_id,
266 DOMStorageNamespace* other,
267 DOMStorageContextImpl* context) {
268 if (!alias_master_namespace())
269 return SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_ALIAS;
270 if (transactions_.count(process_id) < 1)
271 return SessionStorageNamespace::MERGE_RESULT_NOT_LOGGING;
272 TransactionData* data = transactions_[process_id];
273 if (data->max_log_size_exceeded)
274 return SessionStorageNamespace::MERGE_RESULT_TOO_MANY_TRANSACTIONS;
275 if (data->log.size() < 1) {
276 if (actually_merge)
277 SwitchToNewAliasMaster(other, context);
278 return SessionStorageNamespace::MERGE_RESULT_NO_TRANSACTIONS;
279 }
280
281 // skip_areas and skip_keys store areas and (area, key) pairs, respectively,
282 // that have already been handled previously. Any further modifications to
283 // them will not change the result of the hypothetical merge.
284 std::set<GURL> skip_areas;
285 typedef std::pair<GURL, base::string16> OriginKey;
286 std::set<OriginKey> skip_keys;
287 // Indicates whether we could still merge the namespaces preserving all
288 // individual transactions.
289 for (unsigned int i = 0; i < data->log.size(); i++) {
290 TransactionRecord& transaction = data->log[i];
291 if (transaction.transaction_type == TRANSACTION_CLEAR) {
292 skip_areas.insert(transaction.origin);
293 continue;
294 }
295 if (skip_areas.find(transaction.origin) != skip_areas.end())
296 continue;
297 if (skip_keys.find(OriginKey(transaction.origin, transaction.key))
298 != skip_keys.end()) {
299 continue;
300 }
301 if (transaction.transaction_type == TRANSACTION_REMOVE ||
302 transaction.transaction_type == TRANSACTION_WRITE) {
303 skip_keys.insert(OriginKey(transaction.origin, transaction.key));
304 continue;
305 }
306 if (transaction.transaction_type == TRANSACTION_READ) {
307 DOMStorageArea* area = other->OpenStorageArea(transaction.origin);
308 base::NullableString16 other_value = area->GetItem(transaction.key);
309 other->CloseStorageArea(area);
310 if (transaction.value != other_value)
311 return SessionStorageNamespace::MERGE_RESULT_NOT_MERGEABLE;
312 continue;
313 }
314 NOTREACHED();
315 }
316 if (!actually_merge)
317 return SessionStorageNamespace::MERGE_RESULT_MERGEABLE;
318
319 // Actually perform the merge.
320
321 for (unsigned int i = 0; i < data->log.size(); i++) {
322 TransactionRecord& transaction = data->log[i];
323 if (transaction.transaction_type == TRANSACTION_READ)
324 continue;
325 DOMStorageArea* area = other->OpenStorageArea(transaction.origin);
326 if (transaction.transaction_type == TRANSACTION_CLEAR) {
327 area->Clear();
328 if (context)
329 context->NotifyAreaCleared(area, transaction.page_url);
330 }
331 if (transaction.transaction_type == TRANSACTION_REMOVE) {
332 base::string16 old_value;
333 area->RemoveItem(transaction.key, &old_value);
334 if (context) {
335 context->NotifyItemRemoved(area, transaction.key, old_value,
336 transaction.page_url);
337 }
338 }
339 if (transaction.transaction_type == TRANSACTION_WRITE) {
340 base::NullableString16 old_value;
341 area->SetItem(transaction.key, base::string16(transaction.value.string()),
342 &old_value);
343 if (context) {
344 context->NotifyItemSet(area, transaction.key,transaction.value.string(),
345 old_value, transaction.page_url);
346 }
347 }
348 other->CloseStorageArea(area);
349 }
350
351 SwitchToNewAliasMaster(other, context);
352 return SessionStorageNamespace::MERGE_RESULT_MERGEABLE;
353 }
354
IsLoggingRenderer(int process_id)355 bool DOMStorageNamespace::IsLoggingRenderer(int process_id) {
356 DCHECK(process_id != ChildProcessHost::kInvalidUniqueID);
357 if (transactions_.count(process_id) < 1)
358 return false;
359 return !transactions_[process_id]->max_log_size_exceeded;
360 }
361
AddTransaction(int process_id,const TransactionRecord & transaction)362 void DOMStorageNamespace::AddTransaction(
363 int process_id, const TransactionRecord& transaction) {
364 if (!IsLoggingRenderer(process_id))
365 return;
366 TransactionData* transaction_data = transactions_[process_id];
367 DCHECK(transaction_data);
368 if (transaction_data->max_log_size_exceeded)
369 return;
370 transaction_data->log.push_back(transaction);
371 if (transaction_data->log.size() > kMaxTransactionLogEntries) {
372 transaction_data->max_log_size_exceeded = true;
373 transaction_data->log.clear();
374 }
375 }
376
DecrementMasterAliasCount()377 bool DOMStorageNamespace::DecrementMasterAliasCount() {
378 if (!alias_master_namespace_.get() || master_alias_count_decremented_)
379 return false;
380 DCHECK_GT(alias_master_namespace_->num_aliases_, 0);
381 alias_master_namespace_->num_aliases_--;
382 master_alias_count_decremented_ = true;
383 return (alias_master_namespace_->num_aliases_ == 0);
384 }
385
SwitchToNewAliasMaster(DOMStorageNamespace * new_master,DOMStorageContextImpl * context)386 void DOMStorageNamespace::SwitchToNewAliasMaster(
387 DOMStorageNamespace* new_master,
388 DOMStorageContextImpl* context) {
389 DCHECK(alias_master_namespace());
390 scoped_refptr<DOMStorageNamespace> old_master = alias_master_namespace();
391 if (new_master->alias_master_namespace())
392 new_master = new_master->alias_master_namespace();
393 DCHECK(!new_master->alias_master_namespace());
394 DCHECK(old_master.get() != this);
395 DCHECK(old_master.get() != new_master);
396 DecrementMasterAliasCount();
397 alias_master_namespace_ = new_master;
398 alias_master_namespace_->num_aliases_++;
399 master_alias_count_decremented_ = false;
400 // There are three things that we need to clean up:
401 // -- the old master may ready for shutdown, if its last alias has disappeared
402 // -- The dom_storage hosts need to close and reopen their areas, so that
403 // they point to the correct new areas.
404 // -- The renderers will need to reset their local caches.
405 // All three of these things are accomplished with the following call below.
406 // |context| will be NULL in unit tests, which is when this will
407 // not apply, of course.
408 // During this call, open areas will be closed & reopened, so that they now
409 // come from the correct new master. Therefore, we must send close operations
410 // to the old master.
411 old_master_for_close_area_ = old_master.get();
412 if (context)
413 context->NotifyAliasSessionMerged(namespace_id(), old_master.get());
414 old_master_for_close_area_ = NULL;
415 }
416
TransactionData()417 DOMStorageNamespace::TransactionData::TransactionData()
418 : max_log_size_exceeded(false) {
419 }
420
~TransactionData()421 DOMStorageNamespace::TransactionData::~TransactionData() {
422 }
423
TransactionRecord()424 DOMStorageNamespace::TransactionRecord::TransactionRecord() {
425 }
426
~TransactionRecord()427 DOMStorageNamespace::TransactionRecord::~TransactionRecord() {
428 }
429
430 // AreaHolder
431
AreaHolder()432 DOMStorageNamespace::AreaHolder::AreaHolder()
433 : open_count_(0) {
434 }
435
AreaHolder(DOMStorageArea * area,int count)436 DOMStorageNamespace::AreaHolder::AreaHolder(
437 DOMStorageArea* area, int count)
438 : area_(area), open_count_(count) {
439 }
440
~AreaHolder()441 DOMStorageNamespace::AreaHolder::~AreaHolder() {
442 }
443
444 } // namespace content
445