summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/modules/storage/cached_storage_area_test.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/blink/renderer/modules/storage/cached_storage_area_test.cc')
-rw-r--r--chromium/third_party/blink/renderer/modules/storage/cached_storage_area_test.cc532
1 files changed, 393 insertions, 139 deletions
diff --git a/chromium/third_party/blink/renderer/modules/storage/cached_storage_area_test.cc b/chromium/third_party/blink/renderer/modules/storage/cached_storage_area_test.cc
index 89cb97f28cb..e40dc9ff2a7 100644
--- a/chromium/third_party/blink/renderer/modules/storage/cached_storage_area_test.cc
+++ b/chromium/third_party/blink/renderer/modules/storage/cached_storage_area_test.cc
@@ -4,6 +4,8 @@
#include "third_party/blink/renderer/modules/storage/cached_storage_area.h"
+#include "base/memory/scoped_refptr.h"
+#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
#include "third_party/blink/renderer/modules/storage/testing/fake_area_source.h"
@@ -15,9 +17,10 @@
namespace blink {
using FormatOption = CachedStorageArea::FormatOption;
+using ::testing::ElementsAre;
+using ::testing::UnorderedElementsAre;
-class CachedStorageAreaTest : public testing::Test,
- public CachedStorageArea::InspectorEventListener {
+class CachedStorageAreaTest : public testing::Test {
public:
const scoped_refptr<SecurityOrigin> kOrigin =
SecurityOrigin::CreateFromString("http://dom_storage/");
@@ -30,15 +33,14 @@ class CachedStorageAreaTest : public testing::Test,
const String kRemoteSource = kPageUrl2.GetString() + "\n" + kRemoteSourceId;
void SetUp() override {
- if (IsSessionStorage()) {
- cached_area_ = CachedStorageArea::CreateForSessionStorage(
- kOrigin, mock_storage_area_.GetAssociatedInterfaceRemote(),
- scheduler::GetSingleThreadTaskRunnerForTesting(), this);
- } else {
- cached_area_ = CachedStorageArea::CreateForLocalStorage(
- kOrigin, mock_storage_area_.GetInterfaceRemote(),
- scheduler::GetSingleThreadTaskRunnerForTesting(), this);
- }
+ const CachedStorageArea::AreaType area_type =
+ IsSessionStorage() ? CachedStorageArea::AreaType::kSessionStorage
+ : CachedStorageArea::AreaType::kLocalStorage;
+ cached_area_ = base::MakeRefCounted<CachedStorageArea>(
+ area_type, kOrigin, scheduler::GetSingleThreadTaskRunnerForTesting(),
+ nullptr);
+ cached_area_->SetRemoteAreaForTesting(
+ mock_storage_area_.GetInterfaceRemote());
source_area_ = MakeGarbageCollected<FakeAreaSource>(kPageUrl);
source_area_id_ = cached_area_->RegisterSource(source_area_);
source_ = kPageUrl.GetString() + "\n" + source_area_id_;
@@ -46,22 +48,12 @@ class CachedStorageAreaTest : public testing::Test,
cached_area_->RegisterSource(source_area2_);
}
- void DidDispatchStorageEvent(const SecurityOrigin* origin,
- const String& key,
- const String& old_value,
- const String& new_value) override {}
-
virtual bool IsSessionStorage() { return false; }
bool IsCacheLoaded() { return cached_area_->map_.get(); }
- bool IsIgnoringAllMutations() { return cached_area_->ignore_all_mutations_; }
-
- void ResetCache() { cached_area_->Reset(); }
-
bool IsIgnoringKeyMutations(const String& key) {
- return cached_area_->ignore_key_mutations_.find(key) !=
- cached_area_->ignore_key_mutations_.end();
+ return cached_area_->pending_mutations_by_key_.Contains(key);
}
static Vector<uint8_t> StringToUint8Vector(const String& input,
@@ -98,6 +90,29 @@ class CachedStorageAreaTest : public testing::Test,
: FormatOption::kLocalStorageDetectFormat);
}
+ MockStorageArea::ObservedPut ObservedPut(const String& key,
+ const String& value,
+ const String& source) {
+ return MockStorageArea::ObservedPut{KeyToUint8Vector(key),
+ ValueToUint8Vector(value), source};
+ }
+
+ MockStorageArea::ObservedDelete ObservedDelete(const String& key,
+ const String& source) {
+ return MockStorageArea::ObservedDelete{KeyToUint8Vector(key), source};
+ }
+
+ FakeAreaSource::Event Event(const String& key,
+ const String& old_value,
+ const String& new_value) {
+ return FakeAreaSource::Event{key, old_value, new_value, ""};
+ }
+
+ void InjectKeyValue(const String& key, const String& value) {
+ mock_storage_area_.InjectKeyValue(KeyToUint8Vector(key),
+ ValueToUint8Vector(value));
+ }
+
protected:
MockStorageArea mock_storage_area_;
Persistent<FakeAreaSource> source_area_;
@@ -134,48 +149,39 @@ TEST_P(CachedStorageAreaTestWithParam, Basics) {
}
TEST_P(CachedStorageAreaTestWithParam, GetLength) {
- // GetLength, we expect to see one call to load in the db.
+ // Expect GetLength to load the cache.
EXPECT_FALSE(IsCacheLoaded());
EXPECT_EQ(0u, cached_area_->GetLength());
EXPECT_TRUE(IsCacheLoaded());
- EXPECT_TRUE(mock_storage_area_.observed_get_all());
- EXPECT_EQ(1u, mock_storage_area_.pending_callbacks_count());
- EXPECT_TRUE(IsIgnoringAllMutations());
- mock_storage_area_.CompleteAllPendingCallbacks();
- mock_storage_area_.Flush();
- EXPECT_FALSE(IsIgnoringAllMutations());
+ EXPECT_EQ(1, mock_storage_area_.observed_get_alls());
}
TEST_P(CachedStorageAreaTestWithParam, GetKey) {
- // GetKey, expect the one call to load.
+ // Expect GetKey to load the cache.
EXPECT_FALSE(IsCacheLoaded());
EXPECT_TRUE(cached_area_->GetKey(2).IsNull());
EXPECT_TRUE(IsCacheLoaded());
- EXPECT_TRUE(mock_storage_area_.observed_get_all());
- EXPECT_EQ(1u, mock_storage_area_.pending_callbacks_count());
+ EXPECT_EQ(1, mock_storage_area_.observed_get_alls());
}
TEST_P(CachedStorageAreaTestWithParam, GetItem) {
- // GetItem, ditto.
+ // Expect GetItem to load the cache.
EXPECT_FALSE(IsCacheLoaded());
EXPECT_TRUE(cached_area_->GetItem(kKey).IsNull());
EXPECT_TRUE(IsCacheLoaded());
- EXPECT_TRUE(mock_storage_area_.observed_get_all());
- EXPECT_EQ(1u, mock_storage_area_.pending_callbacks_count());
+ EXPECT_EQ(1, mock_storage_area_.observed_get_alls());
}
TEST_P(CachedStorageAreaTestWithParam, SetItem) {
- // SetItem, we expect a call to load followed by a call to put in the db.
+ // Expect SetItem to load the cache and then generate a change event.
EXPECT_FALSE(IsCacheLoaded());
EXPECT_TRUE(cached_area_->SetItem(kKey, kValue, source_area_));
- mock_storage_area_.Flush();
EXPECT_TRUE(IsCacheLoaded());
- EXPECT_TRUE(mock_storage_area_.observed_get_all());
- EXPECT_TRUE(mock_storage_area_.observed_put());
- EXPECT_EQ(source_, mock_storage_area_.observed_source());
- EXPECT_EQ(KeyToUint8Vector(kKey), mock_storage_area_.observed_key());
- EXPECT_EQ(ValueToUint8Vector(kValue), mock_storage_area_.observed_value());
- EXPECT_EQ(2u, mock_storage_area_.pending_callbacks_count());
+ EXPECT_EQ(1, mock_storage_area_.observed_get_alls());
+
+ mock_storage_area_.Flush();
+ EXPECT_THAT(mock_storage_area_.observed_puts(),
+ ElementsAre(ObservedPut(kKey, kValue, source_)));
EXPECT_TRUE(source_area_->events.IsEmpty());
if (IsSessionStorage()) {
@@ -198,14 +204,11 @@ TEST_P(CachedStorageAreaTestWithParam, Clear_AlreadyEmpty) {
cached_area_->Clear(source_area_);
mock_storage_area_.Flush();
EXPECT_TRUE(IsCacheLoaded());
- EXPECT_TRUE(mock_storage_area_.observed_delete_all());
- EXPECT_EQ(source_, mock_storage_area_.observed_source());
+ EXPECT_THAT(mock_storage_area_.observed_delete_alls(), ElementsAre(source_));
if (IsSessionStorage()) {
- EXPECT_TRUE(mock_storage_area_.observed_get_all());
- EXPECT_EQ(2u, mock_storage_area_.pending_callbacks_count());
+ EXPECT_EQ(1, mock_storage_area_.observed_get_alls());
} else {
- EXPECT_FALSE(mock_storage_area_.observed_get_all());
- EXPECT_EQ(1u, mock_storage_area_.pending_callbacks_count());
+ EXPECT_EQ(0, mock_storage_area_.observed_get_alls());
}
// Neither should have events since area was already empty.
@@ -214,22 +217,17 @@ TEST_P(CachedStorageAreaTestWithParam, Clear_AlreadyEmpty) {
}
TEST_P(CachedStorageAreaTestWithParam, Clear_WithData) {
- mock_storage_area_.mutable_get_all_return_values().push_back(
- mojom::blink::KeyValue::New(KeyToUint8Vector(kKey),
- ValueToUint8Vector(kValue)));
+ InjectKeyValue(kKey, kValue);
EXPECT_FALSE(IsCacheLoaded());
cached_area_->Clear(source_area_);
mock_storage_area_.Flush();
EXPECT_TRUE(IsCacheLoaded());
- EXPECT_TRUE(mock_storage_area_.observed_delete_all());
- EXPECT_EQ(source_, mock_storage_area_.observed_source());
+ EXPECT_THAT(mock_storage_area_.observed_delete_alls(), ElementsAre(source_));
if (IsSessionStorage()) {
- EXPECT_TRUE(mock_storage_area_.observed_get_all());
- EXPECT_EQ(2u, mock_storage_area_.pending_callbacks_count());
+ EXPECT_EQ(1, mock_storage_area_.observed_get_alls());
} else {
- EXPECT_FALSE(mock_storage_area_.observed_get_all());
- EXPECT_EQ(1u, mock_storage_area_.pending_callbacks_count());
+ EXPECT_EQ(0, mock_storage_area_.observed_get_alls());
}
EXPECT_TRUE(source_area_->events.IsEmpty());
@@ -250,9 +248,8 @@ TEST_P(CachedStorageAreaTestWithParam, RemoveItem_NothingToRemove) {
cached_area_->RemoveItem(kKey, source_area_);
mock_storage_area_.Flush();
EXPECT_TRUE(IsCacheLoaded());
- EXPECT_TRUE(mock_storage_area_.observed_get_all());
- EXPECT_FALSE(mock_storage_area_.observed_delete());
- EXPECT_EQ(1u, mock_storage_area_.pending_callbacks_count());
+ EXPECT_EQ(1, mock_storage_area_.observed_get_alls());
+ EXPECT_TRUE(mock_storage_area_.observed_deletes().IsEmpty());
// Neither should have events since area was already empty.
EXPECT_TRUE(source_area_->events.IsEmpty());
@@ -262,18 +259,15 @@ TEST_P(CachedStorageAreaTestWithParam, RemoveItem_NothingToRemove) {
TEST_P(CachedStorageAreaTestWithParam, RemoveItem) {
// RemoveItem with something to remove, expect a call to load followed
// by a call to remove.
- mock_storage_area_.mutable_get_all_return_values().push_back(
- mojom::blink::KeyValue::New(KeyToUint8Vector(kKey),
- ValueToUint8Vector(kValue)));
+ InjectKeyValue(kKey, kValue);
+
EXPECT_FALSE(IsCacheLoaded());
cached_area_->RemoveItem(kKey, source_area_);
mock_storage_area_.Flush();
EXPECT_TRUE(IsCacheLoaded());
- EXPECT_TRUE(mock_storage_area_.observed_get_all());
- EXPECT_TRUE(mock_storage_area_.observed_delete());
- EXPECT_EQ(source_, mock_storage_area_.observed_source());
- EXPECT_EQ(KeyToUint8Vector(kKey), mock_storage_area_.observed_key());
- EXPECT_EQ(2u, mock_storage_area_.pending_callbacks_count());
+ EXPECT_EQ(1, mock_storage_area_.observed_get_alls());
+ EXPECT_THAT(mock_storage_area_.observed_deletes(),
+ ElementsAre(ObservedDelete(kKey, source_)));
EXPECT_TRUE(source_area_->events.IsEmpty());
if (IsSessionStorage()) {
@@ -288,13 +282,11 @@ TEST_P(CachedStorageAreaTestWithParam, RemoveItem) {
}
TEST_P(CachedStorageAreaTestWithParam, BrowserDisconnect) {
+ InjectKeyValue(kKey, kValue);
+
// GetLength to prime the cache.
- mock_storage_area_.mutable_get_all_return_values().push_back(
- mojom::blink::KeyValue::New(KeyToUint8Vector(kKey),
- ValueToUint8Vector(kValue)));
EXPECT_EQ(1u, cached_area_->GetLength());
EXPECT_TRUE(IsCacheLoaded());
- mock_storage_area_.CompleteAllPendingCallbacks();
mock_storage_area_.ResetObservations();
// Now disconnect the pipe from the browser, simulating situations where the
@@ -309,65 +301,242 @@ TEST_P(CachedStorageAreaTestWithParam, BrowserDisconnect) {
cached_area_->RemoveItem(kKey, source_area_);
EXPECT_EQ(0u, cached_area_->GetLength());
EXPECT_TRUE(cached_area_->GetItem(kKey).IsNull());
-
- // TODO(mek): This should work for session storage too, but for some reason
- // instead just hangs.
- if (!IsSessionStorage()) {
- // Even resetting the cache should still allow class to function properly.
- ResetCache();
- EXPECT_TRUE(cached_area_->GetItem(kKey).IsNull());
- EXPECT_TRUE(cached_area_->SetItem(kKey, kValue, source_area_));
- EXPECT_EQ(1u, cached_area_->GetLength());
- EXPECT_EQ(kValue, cached_area_->GetItem(kKey));
- }
}
-TEST_F(CachedStorageAreaTest, MutationsAreIgnoredUntilLoadCompletion) {
- mojom::blink::StorageAreaObserver* observer = cached_area_.get();
+TEST_P(CachedStorageAreaTestWithParam, ResetConnectionWithNoDelta) {
+ const String kKey1 = "key1";
+ const String kValue1 = "value1";
+ const String kKey2 = "key2";
+ const String kValue2 = "value2";
+ InjectKeyValue(kKey1, kValue1);
+ InjectKeyValue(kKey2, kValue2);
- EXPECT_TRUE(cached_area_->GetItem(kKey).IsNull());
+ // Prime the cache.
+ EXPECT_EQ(2u, cached_area_->GetLength());
EXPECT_TRUE(IsCacheLoaded());
- EXPECT_TRUE(IsIgnoringAllMutations());
+ EXPECT_EQ(1, mock_storage_area_.observed_get_alls());
- // Before load completion, the mutation should be ignored.
- observer->KeyAdded(KeyToUint8Vector(kKey), ValueToUint8Vector(kValue),
- kRemoteSource);
- EXPECT_TRUE(cached_area_->GetItem(kKey).IsNull());
+ // Simulate a connection reset, which should always re-initialize the local
+ // cache.
+ cached_area_->ResetConnection(mock_storage_area_.GetInterfaceRemote());
+ EXPECT_TRUE(IsCacheLoaded());
+ EXPECT_EQ(2, mock_storage_area_.observed_get_alls());
+ EXPECT_EQ(2u, cached_area_->GetLength());
+
+ // Cached values should be unchanged for both Session and Local Storage.
+ EXPECT_EQ(kValue1, cached_area_->GetItem(kKey1));
+ EXPECT_EQ(kValue2, cached_area_->GetItem(kKey2));
- // Call the load completion callback.
- mock_storage_area_.CompleteOnePendingCallback(true);
+ // There should be no observed operations on the backend.
mock_storage_area_.Flush();
- EXPECT_FALSE(IsIgnoringAllMutations());
+ EXPECT_TRUE(mock_storage_area_.observed_puts().IsEmpty());
+ EXPECT_TRUE(mock_storage_area_.observed_deletes().IsEmpty());
- // Verify that mutations are now applied.
- observer->KeyAdded(KeyToUint8Vector(kKey), ValueToUint8Vector(kValue),
- kRemoteSource);
- EXPECT_EQ(kValue, cached_area_->GetItem(kKey));
+ // There should also be no generated storage events.
+ EXPECT_TRUE(source_area_->events.IsEmpty());
}
-TEST_F(CachedStorageAreaTest, MutationsAreIgnoredUntilClearCompletion) {
- cached_area_->Clear(source_area_);
- mock_storage_area_.Flush();
- EXPECT_TRUE(IsIgnoringAllMutations());
- mock_storage_area_.CompleteOnePendingCallback(true);
- mock_storage_area_.Flush();
- EXPECT_FALSE(IsIgnoringAllMutations());
+TEST_P(CachedStorageAreaTestWithParam, ResetConnectionWithKeyDiff) {
+ const String kKey1 = "key1";
+ const String kValue1 = "value1";
+ const String kKey2 = "key2";
+ const String kCachedValue2 = "cached_value2";
+ const String kPersistedValue2 = "persisted_value2";
+ InjectKeyValue(kKey1, kValue1);
+ InjectKeyValue(kKey2, kCachedValue2);
+
+ // Prime the cache.
+ EXPECT_EQ(2u, cached_area_->GetLength());
+ EXPECT_TRUE(IsCacheLoaded());
+ EXPECT_EQ(1, mock_storage_area_.observed_get_alls());
- // Verify that calling Clear twice works as expected, the first
- // completion callback should have been cancelled.
- ResetCache();
- cached_area_->Clear(source_area_);
+ // Now modify the backend so it's out of sync with the cache. Namely, the
+ // value of |kKey2| is no different.
+ mock_storage_area_.Clear();
+ InjectKeyValue(kKey1, kValue1);
+ InjectKeyValue(kKey2, kPersistedValue2);
+
+ // Resetting the connection should re-initialize the local cache, with
+ // different outcomes for Local and Session Storage.
+ cached_area_->ResetConnection(mock_storage_area_.GetInterfaceRemote());
+ EXPECT_TRUE(IsCacheLoaded());
+ EXPECT_EQ(2, mock_storage_area_.observed_get_alls());
+ EXPECT_EQ(2u, cached_area_->GetLength());
+ EXPECT_EQ(kValue1, cached_area_->GetItem(kKey1));
mock_storage_area_.Flush();
- EXPECT_TRUE(IsIgnoringAllMutations());
- cached_area_->Clear(source_area_);
+
+ if (IsSessionStorage()) {
+ // For Session Storage, we expect the local cache to push changes to the
+ // backend, as the local cache is the source of truth.
+ EXPECT_EQ(kCachedValue2, cached_area_->GetItem(kKey2));
+ EXPECT_THAT(mock_storage_area_.observed_puts(),
+ ElementsAre(ObservedPut(kKey2, kCachedValue2, "\n")));
+ EXPECT_TRUE(mock_storage_area_.observed_deletes().IsEmpty());
+ EXPECT_TRUE(source_area_->events.IsEmpty());
+ } else {
+ // For Local Storage, we expect no mutations to the backend but instead a
+ // storage event to be broadcast for the diff.
+ EXPECT_EQ(kPersistedValue2, cached_area_->GetItem(kKey2));
+ EXPECT_TRUE(mock_storage_area_.observed_puts().IsEmpty());
+ EXPECT_TRUE(mock_storage_area_.observed_deletes().IsEmpty());
+ EXPECT_THAT(source_area_->events,
+ ElementsAre(Event(kKey2, kCachedValue2, kPersistedValue2)));
+ }
+}
+
+TEST_P(CachedStorageAreaTestWithParam, ResetConnectionWithMissingBackendKey) {
+ const String kKey1 = "key1";
+ const String kValue1 = "value1";
+ const String kKey2 = "key2";
+ const String kValue2 = "value2";
+ InjectKeyValue(kKey1, kValue1);
+ InjectKeyValue(kKey2, kValue2);
+
+ // Prime the cache.
+ EXPECT_EQ(2u, cached_area_->GetLength());
+ EXPECT_TRUE(IsCacheLoaded());
+ EXPECT_EQ(1, mock_storage_area_.observed_get_alls());
+
+ // Now modify the backend so it's out of sync with the cache. Namely, |kKey2|
+ // is no longer present in the backend.
+ mock_storage_area_.Clear();
+ InjectKeyValue(kKey1, kValue1);
+
+ // Resetting the connection should re-initialize the local cache, with
+ // different outcomes for Local and Session Storage.
+ cached_area_->ResetConnection(mock_storage_area_.GetInterfaceRemote());
+ EXPECT_TRUE(IsCacheLoaded());
+ EXPECT_EQ(2, mock_storage_area_.observed_get_alls());
+ EXPECT_EQ(kValue1, cached_area_->GetItem(kKey1));
mock_storage_area_.Flush();
- EXPECT_TRUE(IsIgnoringAllMutations());
- mock_storage_area_.CompleteOnePendingCallback(true);
+
+ if (IsSessionStorage()) {
+ // For Session Storage, we expect the local cache to push changes to the
+ // backend, as the local cache is the source of truth.
+ EXPECT_EQ(2u, cached_area_->GetLength());
+ EXPECT_EQ(kValue2, cached_area_->GetItem(kKey2));
+ EXPECT_THAT(mock_storage_area_.observed_puts(),
+ ElementsAre(ObservedPut(kKey2, kValue2, "\n")));
+ EXPECT_TRUE(mock_storage_area_.observed_deletes().IsEmpty());
+ EXPECT_TRUE(source_area_->events.IsEmpty());
+ } else {
+ // For Local Storage, we expect no mutations to the backend but instead a
+ // storage event to be broadcast for the diff.
+ EXPECT_EQ(1u, cached_area_->GetLength());
+ EXPECT_TRUE(cached_area_->GetItem(kKey2).IsNull());
+ EXPECT_TRUE(mock_storage_area_.observed_puts().IsEmpty());
+ EXPECT_TRUE(mock_storage_area_.observed_deletes().IsEmpty());
+ EXPECT_THAT(source_area_->events,
+ ElementsAre(Event(kKey2, kValue2, String())));
+ }
+}
+
+TEST_P(CachedStorageAreaTestWithParam, ResetConnectionWithMissingLocalKey) {
+ const String kKey1 = "key1";
+ const String kValue1 = "value1";
+ const String kKey2 = "key2";
+ const String kValue2 = "value2";
+ InjectKeyValue(kKey1, kValue1);
+
+ // Prime the cache.
+ EXPECT_EQ(1u, cached_area_->GetLength());
+ EXPECT_TRUE(IsCacheLoaded());
+ EXPECT_EQ(1, mock_storage_area_.observed_get_alls());
+
+ // Now modify the backend so it's out of sync with the cache. Namely, |kKey2|
+ // is present in the backend despite never being cached locally.
+ InjectKeyValue(kKey2, kValue2);
+
+ // Resetting the connection should re-initialize the local cache, with
+ // different outcomes for Local and Session Storage.
+ cached_area_->ResetConnection(mock_storage_area_.GetInterfaceRemote());
+ EXPECT_TRUE(IsCacheLoaded());
+ EXPECT_EQ(2, mock_storage_area_.observed_get_alls());
+ EXPECT_EQ(kValue1, cached_area_->GetItem(kKey1));
mock_storage_area_.Flush();
- EXPECT_TRUE(IsIgnoringAllMutations());
- mock_storage_area_.CompleteOnePendingCallback(true);
+
+ if (IsSessionStorage()) {
+ // For Session Storage, we expect the local cache to push changes to the
+ // backend, as the local cache is the source of truth.
+ EXPECT_EQ(1u, cached_area_->GetLength());
+ EXPECT_TRUE(cached_area_->GetItem(kKey2).IsNull());
+ EXPECT_THAT(mock_storage_area_.observed_deletes(),
+ ElementsAre(ObservedDelete(kKey2, "\n")));
+ EXPECT_TRUE(mock_storage_area_.observed_puts().IsEmpty());
+ EXPECT_TRUE(source_area_->events.IsEmpty());
+ } else {
+ // For Local Storage, we expect no mutations to the backend but instead a
+ // storage event to be broadcast for the diff.
+ EXPECT_EQ(2u, cached_area_->GetLength());
+ EXPECT_EQ(kValue2, cached_area_->GetItem(kKey2));
+ EXPECT_TRUE(mock_storage_area_.observed_puts().IsEmpty());
+ EXPECT_TRUE(mock_storage_area_.observed_deletes().IsEmpty());
+ EXPECT_THAT(source_area_->events,
+ ElementsAre(Event(kKey2, String(), kValue2)));
+ }
+}
+
+TEST_P(CachedStorageAreaTestWithParam, ResetConnectionWithComplexDiff) {
+ const String kKey1 = "key1";
+ const String kValue1 = "value1";
+ const String kKey2 = "key2";
+ const String kValue2 = "value2";
+ const String kAltValue2 = "alt_value2";
+ const String kKey3 = "key3";
+ const String kValue3 = "value3";
+ const String kKey4 = "key4";
+ const String kValue4 = "value4";
+ InjectKeyValue(kKey1, kValue1);
+ InjectKeyValue(kKey2, kValue2);
+ InjectKeyValue(kKey3, kValue3);
+
+ // Prime the cache.
+ EXPECT_EQ(3u, cached_area_->GetLength());
+ EXPECT_TRUE(IsCacheLoaded());
+ EXPECT_EQ(1, mock_storage_area_.observed_get_alls());
+
+ // Now modify the backend so it's out of sync with the cache. Namely, the
+ // value of |kKey2| differs, |kKey3| is no longer present in the backend, and
+ // |kKey4| is now present where it wasn't before.
+ mock_storage_area_.Clear();
+ InjectKeyValue(kKey1, kValue1);
+ InjectKeyValue(kKey2, kAltValue2);
+ InjectKeyValue(kKey4, kValue4);
+
+ // Resetting the connection should re-initialize the local cache, with
+ // different outcomes for Local and Session Storage.
+ cached_area_->ResetConnection(mock_storage_area_.GetInterfaceRemote());
+ EXPECT_TRUE(IsCacheLoaded());
+ EXPECT_EQ(2, mock_storage_area_.observed_get_alls());
+ EXPECT_EQ(3u, cached_area_->GetLength());
+ EXPECT_EQ(kValue1, cached_area_->GetItem(kKey1));
mock_storage_area_.Flush();
- EXPECT_FALSE(IsIgnoringAllMutations());
+
+ if (IsSessionStorage()) {
+ // For Session Storage, we expect the local cache to push changes to the
+ // backend, as the local cache is the source of truth.
+ EXPECT_EQ(kValue2, cached_area_->GetItem(kKey2));
+ EXPECT_EQ(kValue3, cached_area_->GetItem(kKey3));
+ EXPECT_TRUE(cached_area_->GetItem(kKey4).IsNull());
+ EXPECT_THAT(mock_storage_area_.observed_puts(),
+ UnorderedElementsAre(ObservedPut(kKey2, kValue2, "\n"),
+ ObservedPut(kKey3, kValue3, "\n")));
+ EXPECT_THAT(mock_storage_area_.observed_deletes(),
+ ElementsAre(ObservedDelete(kKey4, "\n")));
+ EXPECT_TRUE(source_area_->events.IsEmpty());
+ } else {
+ // For Local Storage, we expect no mutations to the backend but instead a
+ // storage event to be broadcast for the diff.
+ EXPECT_EQ(kAltValue2, cached_area_->GetItem(kKey2));
+ EXPECT_TRUE(cached_area_->GetItem(kKey3).IsNull());
+ EXPECT_EQ(kValue4, cached_area_->GetItem(kKey4));
+ EXPECT_TRUE(mock_storage_area_.observed_puts().IsEmpty());
+ EXPECT_TRUE(mock_storage_area_.observed_deletes().IsEmpty());
+ EXPECT_THAT(source_area_->events,
+ UnorderedElementsAre(Event(kKey2, kValue2, kAltValue2),
+ Event(kKey3, kValue3, String()),
+ Event(kKey4, String(), kValue4)));
+ }
}
TEST_F(CachedStorageAreaTest, KeyMutationsAreIgnoredUntilCompletion) {
@@ -375,59 +544,58 @@ TEST_F(CachedStorageAreaTest, KeyMutationsAreIgnoredUntilCompletion) {
// SetItem
EXPECT_TRUE(cached_area_->SetItem(kKey, kValue, source_area_));
- mock_storage_area_.CompleteOnePendingCallback(true); // load completion
mock_storage_area_.Flush();
- EXPECT_FALSE(IsIgnoringAllMutations());
EXPECT_TRUE(IsIgnoringKeyMutations(kKey));
- observer->KeyDeleted(KeyToUint8Vector(kKey), {0}, kRemoteSource);
- mock_storage_area_.Flush();
+ observer->KeyDeleted(KeyToUint8Vector(kKey), base::nullopt, kRemoteSource);
+ EXPECT_TRUE(IsIgnoringKeyMutations(kKey));
EXPECT_EQ(kValue, cached_area_->GetItem(kKey));
- mock_storage_area_.CompleteOnePendingCallback(true); // set completion
- mock_storage_area_.Flush();
+ observer->KeyChanged(KeyToUint8Vector(kKey), ValueToUint8Vector(kValue),
+ base::nullopt, source_);
EXPECT_FALSE(IsIgnoringKeyMutations(kKey));
// RemoveItem
cached_area_->RemoveItem(kKey, source_area_);
mock_storage_area_.Flush();
EXPECT_TRUE(IsIgnoringKeyMutations(kKey));
- mock_storage_area_.CompleteOnePendingCallback(true); // remove completion
- mock_storage_area_.Flush();
+ observer->KeyDeleted(KeyToUint8Vector(kKey), ValueToUint8Vector(kValue),
+ source_);
EXPECT_FALSE(IsIgnoringKeyMutations(kKey));
// Multiple mutations to the same key.
EXPECT_TRUE(cached_area_->SetItem(kKey, kValue, source_area_));
cached_area_->RemoveItem(kKey, source_area_);
- mock_storage_area_.Flush();
- EXPECT_TRUE(IsIgnoringKeyMutations(kKey));
- mock_storage_area_.CompleteOnePendingCallback(true); // set completion
- mock_storage_area_.Flush();
EXPECT_TRUE(IsIgnoringKeyMutations(kKey));
- mock_storage_area_.CompleteOnePendingCallback(true); // remove completion
mock_storage_area_.Flush();
+ observer->KeyChanged(KeyToUint8Vector(kKey), ValueToUint8Vector(kValue),
+ base::nullopt, source_);
+ observer->KeyDeleted(KeyToUint8Vector(kKey), ValueToUint8Vector(kValue),
+ source_);
EXPECT_FALSE(IsIgnoringKeyMutations(kKey));
- // A failed set item operation should Reset the cache.
+ // A failed set item operation should reset the key's cached value.
EXPECT_TRUE(cached_area_->SetItem(kKey, kValue, source_area_));
mock_storage_area_.Flush();
EXPECT_TRUE(IsIgnoringKeyMutations(kKey));
- mock_storage_area_.CompleteOnePendingCallback(false);
- mock_storage_area_.Flush();
- EXPECT_FALSE(IsCacheLoaded());
+ observer->KeyChangeFailed(KeyToUint8Vector(kKey), source_);
+ EXPECT_TRUE(cached_area_->GetItem(kKey).IsNull());
}
TEST_F(CachedStorageAreaTest, ChangeEvents) {
mojom::blink::StorageAreaObserver* observer = cached_area_.get();
- observer->KeyAdded(KeyToUint8Vector(kKey), ValueToUint8Vector(kValue),
- source_);
+ cached_area_->SetItem(kKey, kValue, source_area_);
+ cached_area_->SetItem(kKey, kValue2, source_area_);
+ cached_area_->RemoveItem(kKey, source_area_);
+ observer->KeyChanged(KeyToUint8Vector(kKey), ValueToUint8Vector(kValue),
+ base::nullopt, source_);
observer->KeyChanged(KeyToUint8Vector(kKey), ValueToUint8Vector(kValue2),
ValueToUint8Vector(kValue), source_);
observer->KeyDeleted(KeyToUint8Vector(kKey), ValueToUint8Vector(kValue2),
source_);
- observer->KeyAdded(KeyToUint8Vector(kKey), ValueToUint8Vector(kValue),
- kRemoteSource);
- observer->AllDeleted(kRemoteSource);
+ observer->KeyChanged(KeyToUint8Vector(kKey), ValueToUint8Vector(kValue),
+ base::nullopt, kRemoteSource);
+ observer->AllDeleted(/*was_nonempty=*/true, kRemoteSource);
// Source area should have ignored all but the last two events.
ASSERT_EQ(2u, source_area_->events.size());
@@ -471,6 +639,92 @@ TEST_F(CachedStorageAreaTest, ChangeEvents) {
EXPECT_EQ(kPageUrl2, source_area2_->events[4].url);
}
+TEST_F(CachedStorageAreaTest, RevertOnChangeFailed) {
+ // Verifies that when local key changes fail, the cache is restored to an
+ // appropriate state.
+ mojom::blink::StorageAreaObserver* observer = cached_area_.get();
+ cached_area_->SetItem(kKey, kValue, source_area_);
+ EXPECT_EQ(kValue, cached_area_->GetItem(kKey));
+ observer->KeyChangeFailed(KeyToUint8Vector(kKey), source_);
+ EXPECT_TRUE(cached_area_->GetItem(kKey).IsNull());
+}
+
+TEST_F(CachedStorageAreaTest, RevertOnChangeFailedWithSubsequentChanges) {
+ // Failure of an operation observed while another subsequent operation is
+ // still queued. In this case, no revert should happen because the change that
+ // would be reverted has already been overwritten.
+ mojom::blink::StorageAreaObserver* observer = cached_area_.get();
+ cached_area_->SetItem(kKey, kValue, source_area_);
+ EXPECT_EQ(kValue, cached_area_->GetItem(kKey));
+ cached_area_->SetItem(kKey, kValue2, source_area_);
+ EXPECT_EQ(kValue2, cached_area_->GetItem(kKey));
+ observer->KeyChangeFailed(KeyToUint8Vector(kKey), source_);
+ EXPECT_EQ(kValue2, cached_area_->GetItem(kKey));
+ observer->KeyChanged(KeyToUint8Vector(kKey), ValueToUint8Vector(kValue2),
+ base::nullopt, source_);
+ EXPECT_EQ(kValue2, cached_area_->GetItem(kKey));
+}
+
+TEST_F(CachedStorageAreaTest, RevertOnConsecutiveChangeFailures) {
+ mojom::blink::StorageAreaObserver* observer = cached_area_.get();
+ // If two operations fail in a row, the cache should revert to the original
+ // state before either |SetItem()|.
+ cached_area_->SetItem(kKey, kValue, source_area_);
+ cached_area_->SetItem(kKey, kValue2, source_area_);
+ EXPECT_EQ(kValue2, cached_area_->GetItem(kKey));
+ observer->KeyChangeFailed(KeyToUint8Vector(kKey), source_);
+ // Still caching |kValue2| because that operation is still pending.
+ EXPECT_EQ(kValue2, cached_area_->GetItem(kKey));
+ observer->KeyChangeFailed(KeyToUint8Vector(kKey), source_);
+ // Now that the second operation also failed, the cache should revert to the
+ // value from before the first |SetItem()|, i.e. no value.
+ EXPECT_TRUE(cached_area_->GetItem(kKey).IsNull());
+}
+
+TEST_F(CachedStorageAreaTest, RevertOnChangeFailedWithNonLocalChanges) {
+ // If a non-local mutation is observed while a local mutation is pending
+ // acknowledgement, and that local mutation ends up getting rejected, the
+ // cache should revert to a state reflecting the non-local change that was
+ // temporarily ignored.
+ mojom::blink::StorageAreaObserver* observer = cached_area_.get();
+ cached_area_->SetItem(kKey, kValue, source_area_);
+ EXPECT_EQ(kValue, cached_area_->GetItem(kKey));
+ // Should be ignored.
+ observer->KeyChanged(KeyToUint8Vector(kKey), ValueToUint8Vector(kValue2),
+ base::nullopt, kRemoteSource);
+ EXPECT_EQ(kValue, cached_area_->GetItem(kKey));
+ // Now that we fail the pending |SetItem()|, the above remote change should be
+ // reflected.
+ observer->KeyChangeFailed(KeyToUint8Vector(kKey), source_);
+ EXPECT_EQ(kValue2, cached_area_->GetItem(kKey));
+}
+
+TEST_F(CachedStorageAreaTest, RevertOnChangeFailedAfterNonLocalClear) {
+ // If a non-local clear is observed while a local mutation is pending
+ // acknowledgement and that local mutation ends up getting rejected, the cache
+ // should revert the key to have no value, even if it had a value during the
+ // corresponding |SetItem()| call.
+ mojom::blink::StorageAreaObserver* observer = cached_area_.get();
+ cached_area_->SetItem(kKey, kValue, source_area_);
+ EXPECT_EQ(kValue, cached_area_->GetItem(kKey));
+ cached_area_->SetItem(kKey, kValue2, source_area_);
+ EXPECT_EQ(kValue2, cached_area_->GetItem(kKey));
+ observer->KeyChanged(KeyToUint8Vector(kKey), ValueToUint8Vector(kValue),
+ base::nullopt, source_);
+ // We still have |kValue2| cached since its mutation is still pending.
+ EXPECT_EQ(kValue2, cached_area_->GetItem(kKey));
+
+ // Even after a non-local clear is observed, |kValue2| remains cached because
+ // pending local mutations are replayed over a non-local clear.
+ observer->AllDeleted(true, kRemoteSource);
+ EXPECT_EQ(kValue2, cached_area_->GetItem(kKey));
+
+ // But if that pending mutation fails, we should "revert" to the cleared
+ // value, as that's what the backend would have.
+ observer->KeyChangeFailed(KeyToUint8Vector(kKey), source_);
+ EXPECT_TRUE(cached_area_->GetItem(kKey).IsNull());
+}
+
namespace {
class StringEncoding : public CachedStorageAreaTest,