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