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