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
13 namespace content {
14
15 namespace {
16
17 static const int kMaxLogGetMessagesToSend = 16 * 1024;
18
19 } // namespace
20
DOMStorageCachedArea(int64 namespace_id,const GURL & origin,DOMStorageProxy * proxy)21 DOMStorageCachedArea::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
~DOMStorageCachedArea()31 DOMStorageCachedArea::~DOMStorageCachedArea() {}
32
GetLength(int connection_id)33 unsigned DOMStorageCachedArea::GetLength(int connection_id) {
34 PrimeIfNeeded(connection_id);
35 return map_->Length();
36 }
37
GetKey(int connection_id,unsigned index)38 base::NullableString16 DOMStorageCachedArea::GetKey(int connection_id,
39 unsigned index) {
40 PrimeIfNeeded(connection_id);
41 return map_->Key(index);
42 }
43
GetItem(int connection_id,const base::string16 & key)44 base::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
SetItem(int connection_id,const base::string16 & key,const base::string16 & value,const GURL & page_url)56 bool 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
RemoveItem(int connection_id,const base::string16 & key,const GURL & page_url)79 void 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
Clear(int connection_id,const GURL & page_url)95 void 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
ApplyMutation(const base::NullableString16 & key,const base::NullableString16 & new_value)108 void 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
MemoryBytesUsedByCache() const154 size_t DOMStorageCachedArea::MemoryBytesUsedByCache() const {
155 return map_.get() ? map_->bytes_used() : 0;
156 }
157
Prime(int connection_id)158 void 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
Reset()209 void DOMStorageCachedArea::Reset() {
210 map_ = NULL;
211 weak_factory_.InvalidateWeakPtrs();
212 ignore_key_mutations_.clear();
213 ignore_all_mutations_ = false;
214 }
215
OnLoadComplete(bool success)216 void DOMStorageCachedArea::OnLoadComplete(bool success) {
217 DCHECK(success);
218 DCHECK(ignore_all_mutations_);
219 ignore_all_mutations_ = false;
220 }
221
OnSetItemComplete(const base::string16 & key,bool success)222 void 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
OnRemoveItemComplete(const base::string16 & key,bool success)235 void 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
OnClearComplete(bool success)245 void DOMStorageCachedArea::OnClearComplete(bool success) {
246 DCHECK(success);
247 DCHECK(ignore_all_mutations_);
248 ignore_all_mutations_ = false;
249 }
250
251 } // namespace content
252