diff options
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.cc | 532 |
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, |