// 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 old = map_; map_ = new DOMStorageMap(kPerStorageAreaQuota); // We have to retain local additions which happened after this // clear operation from another process. std::map::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::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::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