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 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 31 DOMStorageCachedArea::~DOMStorageCachedArea() {} 32 33 unsigned DOMStorageCachedArea::GetLength(int connection_id) { 34 PrimeIfNeeded(connection_id); 35 return map_->Length(); 36 } 37 38 base::NullableString16 DOMStorageCachedArea::GetKey(int connection_id, 39 unsigned index) { 40 PrimeIfNeeded(connection_id); 41 return map_->Key(index); 42 } 43 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 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 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 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 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 154 size_t DOMStorageCachedArea::MemoryBytesUsedByCache() const { 155 return map_.get() ? map_->bytes_used() : 0; 156 } 157 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 209 void DOMStorageCachedArea::Reset() { 210 map_ = NULL; 211 weak_factory_.InvalidateWeakPtrs(); 212 ignore_key_mutations_.clear(); 213 ignore_all_mutations_ = false; 214 } 215 216 void DOMStorageCachedArea::OnLoadComplete(bool success) { 217 DCHECK(success); 218 DCHECK(ignore_all_mutations_); 219 ignore_all_mutations_ = false; 220 } 221 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 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 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