summaryrefslogtreecommitdiffstats
path: root/content/renderer/dom_storage
diff options
context:
space:
mode:
authortfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-08-03 02:47:35 +0000
committertfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-08-03 02:47:35 +0000
commita179081b086a0532d0250b73c66dac18af729693 (patch)
treef6f1539307da74f2a95c3ab08d0a7ee16d9a2dab /content/renderer/dom_storage
parent3f2ff115793c21854acf80b50532a6b03dd172a0 (diff)
downloadchromium_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')
-rw-r--r--content/renderer/dom_storage/OWNERS1
-rw-r--r--content/renderer/dom_storage/dom_storage_cached_area.cc235
-rw-r--r--content/renderer/dom_storage/dom_storage_cached_area.h97
-rw-r--r--content/renderer/dom_storage/dom_storage_cached_area_unittest.cc356
-rw-r--r--content/renderer/dom_storage/dom_storage_dispatcher.cc6
-rw-r--r--content/renderer/dom_storage/dom_storage_dispatcher.h14
-rw-r--r--content/renderer/dom_storage/dom_storage_proxy.h48
-rw-r--r--content/renderer/dom_storage/webstoragearea_impl.cc3
-rw-r--r--content/renderer/dom_storage/webstoragearea_impl.h8
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