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
22namespace content {
23
24namespace {
25
26static const unsigned int kMaxTransactionLogEntries = 8 * 1024;
27
28}  // namespace
29
30DOMStorageNamespace::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
43DOMStorageNamespace::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
60DOMStorageNamespace::~DOMStorageNamespace() {
61  STLDeleteValues(&transactions_);
62  DecrementMasterAliasCount();
63}
64
65DOMStorageArea* 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
84void 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
101DOMStorageArea* 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
110DOMStorageNamespace* 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
141DOMStorageNamespace* 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
166void 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
181void 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
191void 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
224void DOMStorageNamespace::Shutdown() {
225  AreaMap::const_iterator it = areas_.begin();
226  for (; it != areas_.end(); ++it)
227    it->second.area_->Shutdown();
228}
229
230unsigned 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
241DOMStorageNamespace::AreaHolder*
242DOMStorageNamespace::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
249void 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
256void 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
263SessionStorageNamespace::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
355bool 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
362void 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
377bool 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
386void 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
417DOMStorageNamespace::TransactionData::TransactionData()
418    : max_log_size_exceeded(false) {
419}
420
421DOMStorageNamespace::TransactionData::~TransactionData() {
422}
423
424DOMStorageNamespace::TransactionRecord::TransactionRecord() {
425}
426
427DOMStorageNamespace::TransactionRecord::~TransactionRecord() {
428}
429
430// AreaHolder
431
432DOMStorageNamespace::AreaHolder::AreaHolder()
433    : open_count_(0) {
434}
435
436DOMStorageNamespace::AreaHolder::AreaHolder(
437    DOMStorageArea* area, int count)
438    : area_(area), open_count_(count) {
439}
440
441DOMStorageNamespace::AreaHolder::~AreaHolder() {
442}
443
444}  // namespace content
445