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/renderer/dom_storage/dom_storage_cached_area.h"
6
7#include "base/basictypes.h"
8#include "base/metrics/histogram.h"
9#include "base/time/time.h"
10#include "content/common/dom_storage/dom_storage_map.h"
11#include "content/renderer/dom_storage/dom_storage_proxy.h"
12
13namespace content {
14
15namespace {
16
17static const int kMaxLogGetMessagesToSend = 16 * 1024;
18
19}  // namespace
20
21DOMStorageCachedArea::DOMStorageCachedArea(int64 namespace_id,
22                                           const GURL& origin,
23                                           DOMStorageProxy* proxy)
24    : ignore_all_mutations_(false),
25      namespace_id_(namespace_id),
26      origin_(origin),
27      proxy_(proxy),
28      remaining_log_get_messages_(0),
29      weak_factory_(this) {}
30
31DOMStorageCachedArea::~DOMStorageCachedArea() {}
32
33unsigned DOMStorageCachedArea::GetLength(int connection_id) {
34  PrimeIfNeeded(connection_id);
35  return map_->Length();
36}
37
38base::NullableString16 DOMStorageCachedArea::GetKey(int connection_id,
39                                                    unsigned index) {
40  PrimeIfNeeded(connection_id);
41  return map_->Key(index);
42}
43
44base::NullableString16 DOMStorageCachedArea::GetItem(
45    int connection_id,
46    const base::string16& key) {
47  PrimeIfNeeded(connection_id);
48  base::NullableString16 result = map_->GetItem(key);
49  if (remaining_log_get_messages_ > 0) {
50    remaining_log_get_messages_--;
51    proxy_->LogGetItem(connection_id, key, result);
52  }
53  return result;
54}
55
56bool DOMStorageCachedArea::SetItem(int connection_id,
57                                   const base::string16& key,
58                                   const base::string16& value,
59                                   const GURL& page_url) {
60  // A quick check to reject obviously overbudget items to avoid
61  // the priming the cache.
62  if (key.length() + value.length() > kPerStorageAreaQuota)
63    return false;
64
65  PrimeIfNeeded(connection_id);
66  base::NullableString16 unused;
67  if (!map_->SetItem(key, value, &unused))
68    return false;
69
70  // Ignore mutations to 'key' until OnSetItemComplete.
71  ignore_key_mutations_[key]++;
72  proxy_->SetItem(
73      connection_id, key, value, page_url,
74      base::Bind(&DOMStorageCachedArea::OnSetItemComplete,
75                 weak_factory_.GetWeakPtr(), key));
76  return true;
77}
78
79void DOMStorageCachedArea::RemoveItem(int connection_id,
80                                      const base::string16& key,
81                                      const GURL& page_url) {
82  PrimeIfNeeded(connection_id);
83  base::string16 unused;
84  if (!map_->RemoveItem(key, &unused))
85    return;
86
87  // Ignore mutations to 'key' until OnRemoveItemComplete.
88  ignore_key_mutations_[key]++;
89  proxy_->RemoveItem(
90      connection_id, key, page_url,
91      base::Bind(&DOMStorageCachedArea::OnRemoveItemComplete,
92                 weak_factory_.GetWeakPtr(), key));
93}
94
95void DOMStorageCachedArea::Clear(int connection_id, const GURL& page_url) {
96  // No need to prime the cache in this case.
97  Reset();
98  map_ = new DOMStorageMap(kPerStorageAreaQuota);
99
100  // Ignore all mutations until OnClearComplete time.
101  ignore_all_mutations_ = true;
102  proxy_->ClearArea(connection_id,
103                    page_url,
104                    base::Bind(&DOMStorageCachedArea::OnClearComplete,
105                               weak_factory_.GetWeakPtr()));
106}
107
108void DOMStorageCachedArea::ApplyMutation(
109    const base::NullableString16& key,
110    const base::NullableString16& new_value) {
111  if (!map_.get() || ignore_all_mutations_)
112    return;
113
114  if (key.is_null()) {
115    // It's a clear event.
116    scoped_refptr<DOMStorageMap> old = map_;
117    map_ = new DOMStorageMap(kPerStorageAreaQuota);
118
119    // We have to retain local additions which happened after this
120    // clear operation from another process.
121    std::map<base::string16, int>::iterator iter =
122        ignore_key_mutations_.begin();
123    while (iter != ignore_key_mutations_.end()) {
124      base::NullableString16 value = old->GetItem(iter->first);
125      if (!value.is_null()) {
126        base::NullableString16 unused;
127        map_->SetItem(iter->first, value.string(), &unused);
128      }
129      ++iter;
130    }
131    return;
132  }
133
134  // We have to retain local changes.
135  if (should_ignore_key_mutation(key.string()))
136    return;
137
138  if (new_value.is_null()) {
139    // It's a remove item event.
140    base::string16 unused;
141    map_->RemoveItem(key.string(), &unused);
142    return;
143  }
144
145  // It's a set item event.
146  // We turn off quota checking here to accomodate the over budget
147  // allowance that's provided in the browser process.
148  base::NullableString16 unused;
149  map_->set_quota(kint32max);
150  map_->SetItem(key.string(), new_value.string(), &unused);
151  map_->set_quota(kPerStorageAreaQuota);
152}
153
154size_t DOMStorageCachedArea::MemoryBytesUsedByCache() const {
155  return map_.get() ? map_->bytes_used() : 0;
156}
157
158void DOMStorageCachedArea::Prime(int connection_id) {
159  DCHECK(!map_.get());
160
161  // The LoadArea method is actually synchronous, but we have to
162  // wait for an asyncly delivered message to know when incoming
163  // mutation events should be applied. Our valuemap is plucked
164  // from ipc stream out of order, mutations in front if it need
165  // to be ignored.
166
167  // Ignore all mutations until OnLoadComplete time.
168  ignore_all_mutations_ = true;
169  DOMStorageValuesMap values;
170  bool send_log_get_messages = false;
171  base::TimeTicks before = base::TimeTicks::Now();
172  proxy_->LoadArea(connection_id,
173                   &values,
174                   &send_log_get_messages,
175                   base::Bind(&DOMStorageCachedArea::OnLoadComplete,
176                              weak_factory_.GetWeakPtr()));
177  base::TimeDelta time_to_prime = base::TimeTicks::Now() - before;
178  // Keeping this histogram named the same (without the ForRenderer suffix)
179  // to maintain histogram continuity.
180  UMA_HISTOGRAM_TIMES("LocalStorage.TimeToPrimeLocalStorage",
181                      time_to_prime);
182  map_ = new DOMStorageMap(kPerStorageAreaQuota);
183  map_->SwapValues(&values);
184  if (send_log_get_messages)
185    remaining_log_get_messages_ = kMaxLogGetMessagesToSend;
186
187  size_t local_storage_size_kb = map_->bytes_used() / 1024;
188  // Track localStorage size, from 0-6MB. Note that the maximum size should be
189  // 5MB, but we add some slop since we want to make sure the max size is always
190  // above what we see in practice, since histograms can't change.
191  UMA_HISTOGRAM_CUSTOM_COUNTS("LocalStorage.RendererLocalStorageSizeInKB",
192                              local_storage_size_kb,
193                              0, 6 * 1024, 50);
194  if (local_storage_size_kb < 100) {
195    UMA_HISTOGRAM_TIMES(
196        "LocalStorage.RendererTimeToPrimeLocalStorageUnder100KB",
197        time_to_prime);
198  } else if (local_storage_size_kb < 1000) {
199    UMA_HISTOGRAM_TIMES(
200        "LocalStorage.RendererTimeToPrimeLocalStorage100KBTo1MB",
201        time_to_prime);
202  } else {
203    UMA_HISTOGRAM_TIMES(
204        "LocalStorage.RendererTimeToPrimeLocalStorage1MBTo5MB",
205        time_to_prime);
206  }
207}
208
209void DOMStorageCachedArea::Reset() {
210  map_ = NULL;
211  weak_factory_.InvalidateWeakPtrs();
212  ignore_key_mutations_.clear();
213  ignore_all_mutations_ = false;
214}
215
216void DOMStorageCachedArea::OnLoadComplete(bool success) {
217  DCHECK(success);
218  DCHECK(ignore_all_mutations_);
219  ignore_all_mutations_ = false;
220}
221
222void DOMStorageCachedArea::OnSetItemComplete(const base::string16& key,
223                                             bool success) {
224  if (!success) {
225    Reset();
226    return;
227  }
228  std::map<base::string16, int>::iterator found =
229      ignore_key_mutations_.find(key);
230  DCHECK(found != ignore_key_mutations_.end());
231  if (--found->second == 0)
232    ignore_key_mutations_.erase(found);
233}
234
235void DOMStorageCachedArea::OnRemoveItemComplete(const base::string16& key,
236                                                bool success) {
237  DCHECK(success);
238  std::map<base::string16, int>::iterator found =
239      ignore_key_mutations_.find(key);
240  DCHECK(found != ignore_key_mutations_.end());
241  if (--found->second == 0)
242    ignore_key_mutations_.erase(found);
243}
244
245void DOMStorageCachedArea::OnClearComplete(bool success) {
246  DCHECK(success);
247  DCHECK(ignore_all_mutations_);
248  ignore_all_mutations_ = false;
249}
250
251}  // namespace content
252