| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/renderer/dom_storage/dom_storage_cached_area.h" |
| |
| #include "base/basictypes.h" |
| #include "base/metrics/histogram.h" |
| #include "base/time/time.h" |
| #include "content/common/dom_storage/dom_storage_map.h" |
| #include "content/renderer/dom_storage/dom_storage_proxy.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| static const int kMaxLogGetMessagesToSend = 16 * 1024; |
| |
| } // namespace |
| |
| DOMStorageCachedArea::DOMStorageCachedArea(int64 namespace_id, |
| const GURL& origin, |
| DOMStorageProxy* proxy) |
| : ignore_all_mutations_(false), |
| namespace_id_(namespace_id), |
| origin_(origin), |
| proxy_(proxy), |
| remaining_log_get_messages_(0), |
| weak_factory_(this) {} |
| |
| DOMStorageCachedArea::~DOMStorageCachedArea() {} |
| |
| unsigned DOMStorageCachedArea::GetLength(int connection_id) { |
| PrimeIfNeeded(connection_id); |
| return map_->Length(); |
| } |
| |
| base::NullableString16 DOMStorageCachedArea::GetKey(int connection_id, |
| unsigned index) { |
| PrimeIfNeeded(connection_id); |
| return map_->Key(index); |
| } |
| |
| base::NullableString16 DOMStorageCachedArea::GetItem( |
| int connection_id, |
| const base::string16& key) { |
| PrimeIfNeeded(connection_id); |
| base::NullableString16 result = map_->GetItem(key); |
| if (remaining_log_get_messages_ > 0) { |
| remaining_log_get_messages_--; |
| proxy_->LogGetItem(connection_id, key, result); |
| } |
| return result; |
| } |
| |
| bool DOMStorageCachedArea::SetItem(int connection_id, |
| const base::string16& key, |
| const base::string16& value, |
| const GURL& page_url) { |
| // A quick check to reject obviously overbudget items to avoid |
| // the priming the cache. |
| if (key.length() + value.length() > kPerStorageAreaQuota) |
| return false; |
| |
| PrimeIfNeeded(connection_id); |
| base::NullableString16 unused; |
| if (!map_->SetItem(key, value, &unused)) |
| return false; |
| |
| // Ignore mutations to 'key' until OnSetItemComplete. |
| ignore_key_mutations_[key]++; |
| proxy_->SetItem( |
| connection_id, key, value, page_url, |
| base::Bind(&DOMStorageCachedArea::OnSetItemComplete, |
| weak_factory_.GetWeakPtr(), key)); |
| return true; |
| } |
| |
| void DOMStorageCachedArea::RemoveItem(int connection_id, |
| const base::string16& key, |
| const GURL& page_url) { |
| PrimeIfNeeded(connection_id); |
| base::string16 unused; |
| if (!map_->RemoveItem(key, &unused)) |
| return; |
| |
| // Ignore mutations to 'key' until OnRemoveItemComplete. |
| ignore_key_mutations_[key]++; |
| proxy_->RemoveItem( |
| connection_id, key, page_url, |
| base::Bind(&DOMStorageCachedArea::OnRemoveItemComplete, |
| weak_factory_.GetWeakPtr(), key)); |
| } |
| |
| void DOMStorageCachedArea::Clear(int connection_id, const GURL& page_url) { |
| // No need to prime the cache in this case. |
| Reset(); |
| map_ = new DOMStorageMap(kPerStorageAreaQuota); |
| |
| // Ignore all mutations until OnClearComplete time. |
| ignore_all_mutations_ = true; |
| proxy_->ClearArea(connection_id, |
| page_url, |
| base::Bind(&DOMStorageCachedArea::OnClearComplete, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void DOMStorageCachedArea::ApplyMutation( |
| const base::NullableString16& key, |
| const base::NullableString16& new_value) { |
| if (!map_.get() || ignore_all_mutations_) |
| return; |
| |
| if (key.is_null()) { |
| // It's a clear event. |
| scoped_refptr<DOMStorageMap> old = map_; |
| map_ = new DOMStorageMap(kPerStorageAreaQuota); |
| |
| // We have to retain local additions which happened after this |
| // clear operation from another process. |
| std::map<base::string16, int>::iterator iter = |
| ignore_key_mutations_.begin(); |
| while (iter != ignore_key_mutations_.end()) { |
| base::NullableString16 value = old->GetItem(iter->first); |
| if (!value.is_null()) { |
| base::NullableString16 unused; |
| map_->SetItem(iter->first, value.string(), &unused); |
| } |
| ++iter; |
| } |
| return; |
| } |
| |
| // We have to retain local changes. |
| if (should_ignore_key_mutation(key.string())) |
| return; |
| |
| if (new_value.is_null()) { |
| // It's a remove item event. |
| base::string16 unused; |
| map_->RemoveItem(key.string(), &unused); |
| return; |
| } |
| |
| // It's a set item event. |
| // We turn off quota checking here to accomodate the over budget |
| // allowance that's provided in the browser process. |
| base::NullableString16 unused; |
| map_->set_quota(kint32max); |
| map_->SetItem(key.string(), new_value.string(), &unused); |
| map_->set_quota(kPerStorageAreaQuota); |
| } |
| |
| size_t DOMStorageCachedArea::MemoryBytesUsedByCache() const { |
| return map_.get() ? map_->bytes_used() : 0; |
| } |
| |
| void DOMStorageCachedArea::Prime(int connection_id) { |
| DCHECK(!map_.get()); |
| |
| // The LoadArea method is actually synchronous, but we have to |
| // wait for an asyncly delivered message to know when incoming |
| // mutation events should be applied. Our valuemap is plucked |
| // from ipc stream out of order, mutations in front if it need |
| // to be ignored. |
| |
| // Ignore all mutations until OnLoadComplete time. |
| ignore_all_mutations_ = true; |
| DOMStorageValuesMap values; |
| bool send_log_get_messages = false; |
| base::TimeTicks before = base::TimeTicks::Now(); |
| proxy_->LoadArea(connection_id, |
| &values, |
| &send_log_get_messages, |
| base::Bind(&DOMStorageCachedArea::OnLoadComplete, |
| weak_factory_.GetWeakPtr())); |
| base::TimeDelta time_to_prime = base::TimeTicks::Now() - before; |
| // Keeping this histogram named the same (without the ForRenderer suffix) |
| // to maintain histogram continuity. |
| UMA_HISTOGRAM_TIMES("LocalStorage.TimeToPrimeLocalStorage", |
| time_to_prime); |
| map_ = new DOMStorageMap(kPerStorageAreaQuota); |
| map_->SwapValues(&values); |
| if (send_log_get_messages) |
| remaining_log_get_messages_ = kMaxLogGetMessagesToSend; |
| |
| size_t local_storage_size_kb = map_->bytes_used() / 1024; |
| // Track localStorage size, from 0-6MB. Note that the maximum size should be |
| // 5MB, but we add some slop since we want to make sure the max size is always |
| // above what we see in practice, since histograms can't change. |
| UMA_HISTOGRAM_CUSTOM_COUNTS("LocalStorage.RendererLocalStorageSizeInKB", |
| local_storage_size_kb, |
| 0, 6 * 1024, 50); |
| if (local_storage_size_kb < 100) { |
| UMA_HISTOGRAM_TIMES( |
| "LocalStorage.RendererTimeToPrimeLocalStorageUnder100KB", |
| time_to_prime); |
| } else if (local_storage_size_kb < 1000) { |
| UMA_HISTOGRAM_TIMES( |
| "LocalStorage.RendererTimeToPrimeLocalStorage100KBTo1MB", |
| time_to_prime); |
| } else { |
| UMA_HISTOGRAM_TIMES( |
| "LocalStorage.RendererTimeToPrimeLocalStorage1MBTo5MB", |
| time_to_prime); |
| } |
| } |
| |
| void DOMStorageCachedArea::Reset() { |
| map_ = NULL; |
| weak_factory_.InvalidateWeakPtrs(); |
| ignore_key_mutations_.clear(); |
| ignore_all_mutations_ = false; |
| } |
| |
| void DOMStorageCachedArea::OnLoadComplete(bool success) { |
| DCHECK(success); |
| DCHECK(ignore_all_mutations_); |
| ignore_all_mutations_ = false; |
| } |
| |
| void DOMStorageCachedArea::OnSetItemComplete(const base::string16& key, |
| bool success) { |
| if (!success) { |
| Reset(); |
| return; |
| } |
| std::map<base::string16, int>::iterator found = |
| ignore_key_mutations_.find(key); |
| DCHECK(found != ignore_key_mutations_.end()); |
| if (--found->second == 0) |
| ignore_key_mutations_.erase(found); |
| } |
| |
| void DOMStorageCachedArea::OnRemoveItemComplete(const base::string16& key, |
| bool success) { |
| DCHECK(success); |
| std::map<base::string16, int>::iterator found = |
| ignore_key_mutations_.find(key); |
| DCHECK(found != ignore_key_mutations_.end()); |
| if (--found->second == 0) |
| ignore_key_mutations_.erase(found); |
| } |
| |
| void DOMStorageCachedArea::OnClearComplete(bool success) { |
| DCHECK(success); |
| DCHECK(ignore_all_mutations_); |
| ignore_all_mutations_ = false; |
| } |
| |
| } // namespace content |