diff options
author | tfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-08-03 02:47:35 +0000 |
---|---|---|
committer | tfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-08-03 02:47:35 +0000 |
commit | a179081b086a0532d0250b73c66dac18af729693 (patch) | |
tree | f6f1539307da74f2a95c3ab08d0a7ee16d9a2dab /content/renderer/dom_storage | |
parent | 3f2ff115793c21854acf80b50532a6b03dd172a0 (diff) | |
download | chromium_src-a179081b086a0532d0250b73c66dac18af729693.zip chromium_src-a179081b086a0532d0250b73c66dac18af729693.tar.gz chromium_src-a179081b086a0532d0250b73c66dac18af729693.tar.bz2 |
Move dom_storage from webkit/renderer to content/renderer.
BUG=265753
R=jam@chromium.org, jamesr@chromium.org
Review URL: https://codereview.chromium.org/20922004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@215465 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/renderer/dom_storage')
9 files changed, 750 insertions, 18 deletions
diff --git a/content/renderer/dom_storage/OWNERS b/content/renderer/dom_storage/OWNERS index 3723f40..66ceac5 100644 --- a/content/renderer/dom_storage/OWNERS +++ b/content/renderer/dom_storage/OWNERS @@ -1 +1,2 @@ +marja@chromium.org michaeln@chromium.org diff --git a/content/renderer/dom_storage/dom_storage_cached_area.cc b/content/renderer/dom_storage/dom_storage_cached_area.cc new file mode 100644 index 0000000..a0203bb --- /dev/null +++ b/content/renderer/dom_storage/dom_storage_cached_area.cc @@ -0,0 +1,235 @@ +// 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/renderer/dom_storage/dom_storage_proxy.h" +#include "webkit/common/dom_storage/dom_storage_map.h" + +namespace content { + +DomStorageCachedArea::DomStorageCachedArea(int64 namespace_id, + const GURL& origin, + DomStorageProxy* proxy) + : ignore_all_mutations_(false), + namespace_id_(namespace_id), + origin_(origin), + proxy_(proxy), + 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); + return map_->GetItem(key); +} + +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() > dom_storage::kPerAreaQuota) + 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 dom_storage::DomStorageMap(dom_storage::kPerAreaQuota); + + // 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<dom_storage::DomStorageMap> old = map_; + map_ = new dom_storage::DomStorageMap(dom_storage::kPerAreaQuota); + + // 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(dom_storage::kPerAreaQuota); +} + +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; + dom_storage::ValuesMap values; + base::TimeTicks before = base::TimeTicks::Now(); + proxy_->LoadArea(connection_id, + &values, + 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 dom_storage::DomStorageMap(dom_storage::kPerAreaQuota); + map_->SwapValues(&values); + + 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 diff --git a/content/renderer/dom_storage/dom_storage_cached_area.h b/content/renderer/dom_storage/dom_storage_cached_area.h new file mode 100644 index 0000000..2aa4cd9 --- /dev/null +++ b/content/renderer/dom_storage/dom_storage_cached_area.h @@ -0,0 +1,97 @@ +// 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. + +#ifndef CONTENT_RENDERER_DOM_STORAGE_DOM_STORAGE_CACHED_AREA_H_ +#define CONTENT_RENDERER_DOM_STORAGE_DOM_STORAGE_CACHED_AREA_H_ + +#include <map> + +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/strings/nullable_string16.h" +#include "content/common/content_export.h" +#include "url/gurl.h" + +namespace dom_storage { +class DomStorageMap; +} + +namespace content { + +class DomStorageProxy; + +// Unlike the other classes in the dom_storage library, this one is intended +// for use in renderer processes. It maintains a complete cache of the +// origin's Map of key/value pairs for fast access. The cache is primed on +// first access and changes are written to the backend thru the |proxy|. +// Mutations originating in other processes are applied to the cache via +// the ApplyMutation method. +class CONTENT_EXPORT DomStorageCachedArea + : public base::RefCounted<DomStorageCachedArea> { + public: + DomStorageCachedArea(int64 namespace_id, + const GURL& origin, + DomStorageProxy* proxy); + + int64 namespace_id() const { return namespace_id_; } + const GURL& origin() const { return origin_; } + + unsigned GetLength(int connection_id); + base::NullableString16 GetKey(int connection_id, unsigned index); + base::NullableString16 GetItem(int connection_id, const base::string16& key); + bool SetItem(int connection_id, + const base::string16& key, + const base::string16& value, + const GURL& page_url); + void RemoveItem(int connection_id, + const base::string16& key, + const GURL& page_url); + void Clear(int connection_id, const GURL& page_url); + + void ApplyMutation(const base::NullableString16& key, + const base::NullableString16& new_value); + + size_t MemoryBytesUsedByCache() const; + + private: + friend class DomStorageCachedAreaTest; + friend class base::RefCounted<DomStorageCachedArea>; + ~DomStorageCachedArea(); + + // Primes the cache, loading all values for the area. + void Prime(int connection_id); + void PrimeIfNeeded(int connection_id) { + if (!map_.get()) + Prime(connection_id); + } + + // Resets the object back to its newly constructed state. + void Reset(); + + // Async completion callbacks for proxied operations. + // These are used to maintain cache consistency by preventing + // mutation events from other processes from overwriting local + // changes made after the mutation. + void OnLoadComplete(bool success); + void OnSetItemComplete(const base::string16& key, bool success); + void OnClearComplete(bool success); + void OnRemoveItemComplete(const base::string16& key, bool success); + + bool should_ignore_key_mutation(const base::string16& key) const { + return ignore_key_mutations_.find(key) != ignore_key_mutations_.end(); + } + + bool ignore_all_mutations_; + std::map<base::string16, int> ignore_key_mutations_; + + int64 namespace_id_; + GURL origin_; + scoped_refptr<dom_storage::DomStorageMap> map_; + scoped_refptr<DomStorageProxy> proxy_; + base::WeakPtrFactory<DomStorageCachedArea> weak_factory_; +}; + +} // namespace content + +#endif // CONTENT_RENDERER_DOM_STORAGE_DOM_STORAGE_CACHED_AREA_H_ diff --git a/content/renderer/dom_storage/dom_storage_cached_area_unittest.cc b/content/renderer/dom_storage/dom_storage_cached_area_unittest.cc new file mode 100644 index 0000000..9dcef95 --- /dev/null +++ b/content/renderer/dom_storage/dom_storage_cached_area_unittest.cc @@ -0,0 +1,356 @@ +// 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 <list> + +#include "base/bind.h" +#include "base/strings/utf_string_conversions.h" +#include "content/renderer/dom_storage/dom_storage_proxy.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace content { + +namespace { +// A mock implementation of the DomStorageProxy interface. +class MockProxy : public DomStorageProxy { + public: + MockProxy() { + ResetObservations(); + } + + // DomStorageProxy interface for use by DomStorageCachedArea. + + virtual void LoadArea(int connection_id, + dom_storage::ValuesMap* values, + const CompletionCallback& callback) OVERRIDE { + pending_callbacks_.push_back(callback); + observed_load_area_ = true; + observed_connection_id_ = connection_id; + *values = load_area_return_values_; + } + + virtual void SetItem(int connection_id, + const base::string16& key, + const base::string16& value, + const GURL& page_url, + const CompletionCallback& callback) OVERRIDE { + pending_callbacks_.push_back(callback); + observed_set_item_ = true; + observed_connection_id_ = connection_id; + observed_key_ = key; + observed_value_ = value; + observed_page_url_ = page_url; + } + + virtual void RemoveItem(int connection_id, + const base::string16& key, + const GURL& page_url, + const CompletionCallback& callback) OVERRIDE { + pending_callbacks_.push_back(callback); + observed_remove_item_ = true; + observed_connection_id_ = connection_id; + observed_key_ = key; + observed_page_url_ = page_url; + } + + virtual void ClearArea(int connection_id, + const GURL& page_url, + const CompletionCallback& callback) OVERRIDE { + pending_callbacks_.push_back(callback); + observed_clear_area_ = true; + observed_connection_id_ = connection_id; + observed_page_url_ = page_url; + } + + // Methods and members for use by test fixtures. + + void ResetObservations() { + observed_load_area_ = false; + observed_set_item_ = false; + observed_remove_item_ = false; + observed_clear_area_ = false; + observed_connection_id_ = 0; + observed_key_.clear(); + observed_value_.clear(); + observed_page_url_ = GURL(); + } + + void CompleteAllPendingCallbacks() { + while (!pending_callbacks_.empty()) + CompleteOnePendingCallback(true); + } + + void CompleteOnePendingCallback(bool success) { + ASSERT_TRUE(!pending_callbacks_.empty()); + pending_callbacks_.front().Run(success); + pending_callbacks_.pop_front(); + } + + typedef std::list<CompletionCallback> CallbackList; + + dom_storage::ValuesMap load_area_return_values_; + CallbackList pending_callbacks_; + bool observed_load_area_; + bool observed_set_item_; + bool observed_remove_item_; + bool observed_clear_area_; + int observed_connection_id_; + base::string16 observed_key_; + base::string16 observed_value_; + GURL observed_page_url_; + + private: + virtual ~MockProxy() {} +}; + +} // namespace + +class DomStorageCachedAreaTest : public testing::Test { + public: + DomStorageCachedAreaTest() + : kNamespaceId(10), + kOrigin("http://dom_storage/"), + kKey(ASCIIToUTF16("key")), + kValue(ASCIIToUTF16("value")), + kPageUrl("http://dom_storage/page") { + } + + const int64 kNamespaceId; + const GURL kOrigin; + const base::string16 kKey; + const base::string16 kValue; + const GURL kPageUrl; + + virtual void SetUp() { + mock_proxy_ = new MockProxy(); + } + + bool IsPrimed(DomStorageCachedArea* cached_area) { + return cached_area->map_.get(); + } + + bool IsIgnoringAllMutations(DomStorageCachedArea* cached_area) { + return cached_area->ignore_all_mutations_; + } + + bool IsIgnoringKeyMutations(DomStorageCachedArea* cached_area, + const base::string16& key) { + return cached_area->should_ignore_key_mutation(key); + } + + void ResetAll(DomStorageCachedArea* cached_area) { + cached_area->Reset(); + mock_proxy_->ResetObservations(); + mock_proxy_->pending_callbacks_.clear(); + } + + void ResetCacheOnly(DomStorageCachedArea* cached_area) { + cached_area->Reset(); + } + + protected: + scoped_refptr<MockProxy> mock_proxy_; +}; + +TEST_F(DomStorageCachedAreaTest, Basics) { + EXPECT_TRUE(mock_proxy_->HasOneRef()); + scoped_refptr<DomStorageCachedArea> cached_area = + new DomStorageCachedArea(kNamespaceId, kOrigin, mock_proxy_.get()); + EXPECT_EQ(kNamespaceId, cached_area->namespace_id()); + EXPECT_EQ(kOrigin, cached_area->origin()); + EXPECT_FALSE(mock_proxy_->HasOneRef()); + cached_area->ApplyMutation(base::NullableString16(kKey, false), + base::NullableString16(kValue, false)); + EXPECT_FALSE(IsPrimed(cached_area.get())); + + ResetAll(cached_area.get()); + EXPECT_EQ(kNamespaceId, cached_area->namespace_id()); + EXPECT_EQ(kOrigin, cached_area->origin()); + + const int kConnectionId = 1; + EXPECT_EQ(0u, cached_area->GetLength(kConnectionId)); + EXPECT_TRUE(cached_area->SetItem(kConnectionId, kKey, kValue, kPageUrl)); + EXPECT_EQ(1u, cached_area->GetLength(kConnectionId)); + EXPECT_EQ(kKey, cached_area->GetKey(kConnectionId, 0).string()); + EXPECT_EQ(kValue, cached_area->GetItem(kConnectionId, kKey).string()); + cached_area->RemoveItem(kConnectionId, kKey, kPageUrl); + EXPECT_EQ(0u, cached_area->GetLength(kConnectionId)); +} + +TEST_F(DomStorageCachedAreaTest, Getters) { + const int kConnectionId = 7; + scoped_refptr<DomStorageCachedArea> cached_area = + new DomStorageCachedArea(kNamespaceId, kOrigin, mock_proxy_.get()); + + // GetLength, we expect to see one call to load in the proxy. + EXPECT_FALSE(IsPrimed(cached_area.get())); + EXPECT_EQ(0u, cached_area->GetLength(kConnectionId)); + EXPECT_TRUE(IsPrimed(cached_area.get())); + EXPECT_TRUE(mock_proxy_->observed_load_area_); + EXPECT_EQ(kConnectionId, mock_proxy_->observed_connection_id_); + EXPECT_EQ(1u, mock_proxy_->pending_callbacks_.size()); + EXPECT_TRUE(IsIgnoringAllMutations(cached_area.get())); + mock_proxy_->CompleteAllPendingCallbacks(); + EXPECT_FALSE(IsIgnoringAllMutations(cached_area.get())); + + // GetKey, expect the one call to load. + ResetAll(cached_area.get()); + EXPECT_FALSE(IsPrimed(cached_area.get())); + EXPECT_TRUE(cached_area->GetKey(kConnectionId, 2).is_null()); + EXPECT_TRUE(IsPrimed(cached_area.get())); + EXPECT_TRUE(mock_proxy_->observed_load_area_); + EXPECT_EQ(kConnectionId, mock_proxy_->observed_connection_id_); + EXPECT_EQ(1u, mock_proxy_->pending_callbacks_.size()); + + // GetItem, ditto. + ResetAll(cached_area.get()); + EXPECT_FALSE(IsPrimed(cached_area.get())); + EXPECT_TRUE(cached_area->GetItem(kConnectionId, kKey).is_null()); + EXPECT_TRUE(IsPrimed(cached_area.get())); + EXPECT_TRUE(mock_proxy_->observed_load_area_); + EXPECT_EQ(kConnectionId, mock_proxy_->observed_connection_id_); + EXPECT_EQ(1u, mock_proxy_->pending_callbacks_.size()); +} + +TEST_F(DomStorageCachedAreaTest, Setters) { + const int kConnectionId = 7; + scoped_refptr<DomStorageCachedArea> cached_area = + new DomStorageCachedArea(kNamespaceId, kOrigin, mock_proxy_.get()); + + // SetItem, we expect a call to load followed by a call to set item + // in the proxy. + EXPECT_FALSE(IsPrimed(cached_area.get())); + EXPECT_TRUE(cached_area->SetItem(kConnectionId, kKey, kValue, kPageUrl)); + EXPECT_TRUE(IsPrimed(cached_area.get())); + EXPECT_TRUE(mock_proxy_->observed_load_area_); + EXPECT_TRUE(mock_proxy_->observed_set_item_); + EXPECT_EQ(kConnectionId, mock_proxy_->observed_connection_id_); + EXPECT_EQ(kPageUrl, mock_proxy_->observed_page_url_); + EXPECT_EQ(kKey, mock_proxy_->observed_key_); + EXPECT_EQ(kValue, mock_proxy_->observed_value_); + EXPECT_EQ(2u, mock_proxy_->pending_callbacks_.size()); + + // Clear, we expect a just the one call to clear in the proxy since + // there's no need to load the data prior to deleting it. + ResetAll(cached_area.get()); + EXPECT_FALSE(IsPrimed(cached_area.get())); + cached_area->Clear(kConnectionId, kPageUrl); + EXPECT_TRUE(IsPrimed(cached_area.get())); + EXPECT_TRUE(mock_proxy_->observed_clear_area_); + EXPECT_EQ(kConnectionId, mock_proxy_->observed_connection_id_); + EXPECT_EQ(kPageUrl, mock_proxy_->observed_page_url_); + EXPECT_EQ(1u, mock_proxy_->pending_callbacks_.size()); + + // RemoveItem with nothing to remove, expect just one call to load. + ResetAll(cached_area.get()); + EXPECT_FALSE(IsPrimed(cached_area.get())); + cached_area->RemoveItem(kConnectionId, kKey, kPageUrl); + EXPECT_TRUE(IsPrimed(cached_area.get())); + EXPECT_TRUE(mock_proxy_->observed_load_area_); + EXPECT_FALSE(mock_proxy_->observed_remove_item_); + EXPECT_EQ(kConnectionId, mock_proxy_->observed_connection_id_); + EXPECT_EQ(1u, mock_proxy_->pending_callbacks_.size()); + + // RemoveItem with something to remove, expect a call to load followed + // by a call to remove. + ResetAll(cached_area.get()); + mock_proxy_->load_area_return_values_[kKey] = + base::NullableString16(kValue, false); + EXPECT_FALSE(IsPrimed(cached_area.get())); + cached_area->RemoveItem(kConnectionId, kKey, kPageUrl); + EXPECT_TRUE(IsPrimed(cached_area.get())); + EXPECT_TRUE(mock_proxy_->observed_load_area_); + EXPECT_TRUE(mock_proxy_->observed_remove_item_); + EXPECT_EQ(kConnectionId, mock_proxy_->observed_connection_id_); + EXPECT_EQ(kPageUrl, mock_proxy_->observed_page_url_); + EXPECT_EQ(kKey, mock_proxy_->observed_key_); + EXPECT_EQ(2u, mock_proxy_->pending_callbacks_.size()); +} + +TEST_F(DomStorageCachedAreaTest, MutationsAreIgnoredUntilLoadCompletion) { + const int kConnectionId = 7; + scoped_refptr<DomStorageCachedArea> cached_area = + new DomStorageCachedArea(kNamespaceId, kOrigin, mock_proxy_.get()); + EXPECT_TRUE(cached_area->GetItem(kConnectionId, kKey).is_null()); + EXPECT_TRUE(IsPrimed(cached_area.get())); + EXPECT_TRUE(IsIgnoringAllMutations(cached_area.get())); + + // Before load completion, the mutation should be ignored. + cached_area->ApplyMutation(base::NullableString16(kKey, false), + base::NullableString16(kValue, false)); + EXPECT_TRUE(cached_area->GetItem(kConnectionId, kKey).is_null()); + + // Call the load completion callback. + mock_proxy_->CompleteOnePendingCallback(true); + EXPECT_FALSE(IsIgnoringAllMutations(cached_area.get())); + + // Verify that mutations are now applied. + cached_area->ApplyMutation(base::NullableString16(kKey, false), + base::NullableString16(kValue, false)); + EXPECT_EQ(kValue, cached_area->GetItem(kConnectionId, kKey).string()); +} + +TEST_F(DomStorageCachedAreaTest, MutationsAreIgnoredUntilClearCompletion) { + const int kConnectionId = 4; + scoped_refptr<DomStorageCachedArea> cached_area = + new DomStorageCachedArea(kNamespaceId, kOrigin, mock_proxy_.get()); + cached_area->Clear(kConnectionId, kPageUrl); + EXPECT_TRUE(IsIgnoringAllMutations(cached_area.get())); + mock_proxy_->CompleteOnePendingCallback(true); + EXPECT_FALSE(IsIgnoringAllMutations(cached_area.get())); + + // Verify that calling Clear twice works as expected, the first + // completion callback should have been cancelled. + ResetCacheOnly(cached_area.get()); + cached_area->Clear(kConnectionId, kPageUrl); + EXPECT_TRUE(IsIgnoringAllMutations(cached_area.get())); + cached_area->Clear(kConnectionId, kPageUrl); + EXPECT_TRUE(IsIgnoringAllMutations(cached_area.get())); + mock_proxy_->CompleteOnePendingCallback(true); + EXPECT_TRUE(IsIgnoringAllMutations(cached_area.get())); + mock_proxy_->CompleteOnePendingCallback(true); + EXPECT_FALSE(IsIgnoringAllMutations(cached_area.get())); +} + +TEST_F(DomStorageCachedAreaTest, KeyMutationsAreIgnoredUntilCompletion) { + const int kConnectionId = 8; + scoped_refptr<DomStorageCachedArea> cached_area = + new DomStorageCachedArea(kNamespaceId, kOrigin, mock_proxy_.get()); + + // SetItem + EXPECT_TRUE(cached_area->SetItem(kConnectionId, kKey, kValue, kPageUrl)); + mock_proxy_->CompleteOnePendingCallback(true); // load completion + EXPECT_FALSE(IsIgnoringAllMutations(cached_area.get())); + EXPECT_TRUE(IsIgnoringKeyMutations(cached_area.get(), kKey)); + cached_area->ApplyMutation(base::NullableString16(kKey, false), + base::NullableString16()); + EXPECT_EQ(kValue, cached_area->GetItem(kConnectionId, kKey).string()); + mock_proxy_->CompleteOnePendingCallback(true); // set completion + EXPECT_FALSE(IsIgnoringKeyMutations(cached_area.get(), kKey)); + + // RemoveItem + cached_area->RemoveItem(kConnectionId, kKey, kPageUrl); + EXPECT_TRUE(IsIgnoringKeyMutations(cached_area.get(), kKey)); + mock_proxy_->CompleteOnePendingCallback(true); // remove completion + EXPECT_FALSE(IsIgnoringKeyMutations(cached_area.get(), kKey)); + + // Multiple mutations to the same key. + EXPECT_TRUE(cached_area->SetItem(kConnectionId, kKey, kValue, kPageUrl)); + cached_area->RemoveItem(kConnectionId, kKey, kPageUrl); + EXPECT_TRUE(IsIgnoringKeyMutations(cached_area.get(), kKey)); + mock_proxy_->CompleteOnePendingCallback(true); // set completion + EXPECT_TRUE(IsIgnoringKeyMutations(cached_area.get(), kKey)); + mock_proxy_->CompleteOnePendingCallback(true); // remove completion + EXPECT_FALSE(IsIgnoringKeyMutations(cached_area.get(), kKey)); + + // A failed set item operation should Reset the cache. + EXPECT_TRUE(cached_area->SetItem(kConnectionId, kKey, kValue, kPageUrl)); + EXPECT_TRUE(IsIgnoringKeyMutations(cached_area.get(), kKey)); + mock_proxy_->CompleteOnePendingCallback(false); + EXPECT_FALSE(IsPrimed(cached_area.get())); +} + +} // namespace dom_storage diff --git a/content/renderer/dom_storage/dom_storage_dispatcher.cc b/content/renderer/dom_storage/dom_storage_dispatcher.cc index e36084c..3cab4fe 100644 --- a/content/renderer/dom_storage/dom_storage_dispatcher.cc +++ b/content/renderer/dom_storage/dom_storage_dispatcher.cc @@ -10,6 +10,8 @@ #include "base/strings/string_number_conversions.h" #include "base/synchronization/lock.h" #include "content/common/dom_storage_messages.h" +#include "content/renderer/dom_storage/dom_storage_cached_area.h" +#include "content/renderer/dom_storage/dom_storage_proxy.h" #include "content/renderer/dom_storage/webstoragearea_impl.h" #include "content/renderer/dom_storage/webstoragenamespace_impl.h" #include "content/renderer/render_thread_impl.h" @@ -17,11 +19,7 @@ #include "third_party/WebKit/public/web/WebKit.h" #include "third_party/WebKit/public/web/WebStorageEventDispatcher.h" #include "webkit/common/dom_storage/dom_storage_types.h" -#include "webkit/renderer/dom_storage/dom_storage_cached_area.h" -#include "webkit/renderer/dom_storage/dom_storage_proxy.h" -using dom_storage::DomStorageCachedArea; -using dom_storage::DomStorageProxy; using dom_storage::ValuesMap; namespace content { diff --git a/content/renderer/dom_storage/dom_storage_dispatcher.h b/content/renderer/dom_storage/dom_storage_dispatcher.h index f2498f7..f5d4004 100644 --- a/content/renderer/dom_storage/dom_storage_dispatcher.h +++ b/content/renderer/dom_storage/dom_storage_dispatcher.h @@ -9,15 +9,15 @@ class GURL; struct DOMStorageMsg_Event_Params; -namespace dom_storage { -class DomStorageCachedArea; -} + namespace IPC { class Message; } namespace content { +class DomStorageCachedArea; + // Dispatches DomStorage related messages sent to a renderer process from the // main browser process. There is one instance per child process. Messages // are dispatched on the main renderer thread. The RenderThreadImpl @@ -29,10 +29,10 @@ class DomStorageDispatcher { ~DomStorageDispatcher(); // Each call to open should be balanced with a call to close. - scoped_refptr<dom_storage::DomStorageCachedArea> OpenCachedArea( - int connection_id, int64 namespace_id, const GURL& origin); - void CloseCachedArea( - int connection_id, dom_storage::DomStorageCachedArea* area); + scoped_refptr<DomStorageCachedArea> OpenCachedArea(int connection_id, + int64 namespace_id, + const GURL& origin); + void CloseCachedArea(int connection_id, DomStorageCachedArea* area); bool OnMessageReceived(const IPC::Message& msg); diff --git a/content/renderer/dom_storage/dom_storage_proxy.h b/content/renderer/dom_storage/dom_storage_proxy.h new file mode 100644 index 0000000..57fcfaf --- /dev/null +++ b/content/renderer/dom_storage/dom_storage_proxy.h @@ -0,0 +1,48 @@ +// 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. + +#ifndef CONTENT_RENDERER_DOM_STORAGE_DOM_STORAGE_PROXY_H_ +#define CONTENT_RENDERER_DOM_STORAGE_DOM_STORAGE_PROXY_H_ + +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/strings/nullable_string16.h" +#include "base/strings/string16.h" +#include "url/gurl.h" +#include "webkit/common/dom_storage/dom_storage_types.h" + +namespace content { + +// Abstract interface for cached area, renderer to browser communications. +class DomStorageProxy : public base::RefCounted<DomStorageProxy> { + public: + typedef base::Callback<void(bool)> CompletionCallback; + + virtual void LoadArea(int connection_id, + dom_storage::ValuesMap* values, + const CompletionCallback& callback) = 0; + + virtual void SetItem(int connection_id, + const base::string16& key, + const base::string16& value, + const GURL& page_url, + const CompletionCallback& callback) = 0; + + virtual void RemoveItem(int connection_id, + const base::string16& key, + const GURL& page_url, + const CompletionCallback& callback) = 0; + + virtual void ClearArea(int connection_id, + const GURL& page_url, + const CompletionCallback& callback) = 0; + + protected: + friend class base::RefCounted<DomStorageProxy>; + virtual ~DomStorageProxy() {} +}; + +} // namespace content + +#endif // CONTENT_RENDERER_DOM_STORAGE_DOM_STORAGE_PROXY_H_ diff --git a/content/renderer/dom_storage/webstoragearea_impl.cc b/content/renderer/dom_storage/webstoragearea_impl.cc index 40df45a..72e9415 100644 --- a/content/renderer/dom_storage/webstoragearea_impl.cc +++ b/content/renderer/dom_storage/webstoragearea_impl.cc @@ -9,12 +9,11 @@ #include "base/strings/utf_string_conversions.h" #include "base/time/time.h" #include "content/common/dom_storage_messages.h" +#include "content/renderer/dom_storage/dom_storage_cached_area.h" #include "content/renderer/dom_storage/dom_storage_dispatcher.h" #include "content/renderer/render_thread_impl.h" #include "third_party/WebKit/public/platform/WebURL.h" -#include "webkit/renderer/dom_storage/dom_storage_cached_area.h" -using dom_storage::DomStorageCachedArea; using WebKit::WebString; using WebKit::WebURL; diff --git a/content/renderer/dom_storage/webstoragearea_impl.h b/content/renderer/dom_storage/webstoragearea_impl.h index a79f45f..47d0581 100644 --- a/content/renderer/dom_storage/webstoragearea_impl.h +++ b/content/renderer/dom_storage/webstoragearea_impl.h @@ -12,12 +12,10 @@ class GURL; -namespace dom_storage { -class DomStorageCachedArea; -} - namespace content { +class DomStorageCachedArea; + class WebStorageAreaImpl : public WebKit::WebStorageArea { public: static WebStorageAreaImpl* FromConnectionId(int id); @@ -39,7 +37,7 @@ class WebStorageAreaImpl : public WebKit::WebStorageArea { private: int connection_id_; - scoped_refptr<dom_storage::DomStorageCachedArea> cached_area_; + scoped_refptr<DomStorageCachedArea> cached_area_; }; } // namespace content |