| // Copyright 2020 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. |
| |
| #import "ios/web/navigation/session_storage_builder.h" |
| |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "ios/web/common/features.h" |
| #import "ios/web/navigation/wk_navigation_util.h" |
| #import "ios/web/public/session/crw_navigation_item_storage.h" |
| #import "ios/web/public/session/crw_session_storage.h" |
| #include "ios/web/public/test/web_test.h" |
| #import "ios/web/test/fakes/crw_fake_back_forward_list.h" |
| #import "ios/web/web_state/ui/crw_web_view_navigation_proxy.h" |
| #import "ios/web/web_state/web_state_impl.h" |
| #import "third_party/ocmock/OCMock/OCMock.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| namespace web { |
| |
| using wk_navigation_util::kMaxSessionSize; |
| |
| // WebState that provides Mock CRWWebViewNavigationProxy object. |
| class WebStateWithMockProxy : public WebStateImpl { |
| public: |
| explicit WebStateWithMockProxy(const CreateParams& params) |
| : WebStateImpl(params), |
| mock_web_view_(OCMProtocolMock(@protocol(CRWWebViewNavigationProxy))), |
| fake_wk_list_([[CRWFakeBackForwardList alloc] init]) { |
| OCMStub([mock_web_view_ backForwardList]).andReturn(fake_wk_list_); |
| } |
| |
| id<CRWWebViewNavigationProxy> mock_web_view() { return mock_web_view_; } |
| |
| CRWFakeBackForwardList* fake_wk_list() { return fake_wk_list_; } |
| |
| private: |
| id<CRWWebViewNavigationProxy> GetWebViewNavigationProxy() const override { |
| return mock_web_view_; |
| } |
| |
| id mock_web_view_ = nil; |
| CRWFakeBackForwardList* fake_wk_list_ = nil; |
| }; |
| |
| using SessionStorageBuilderTest = WebTest; |
| |
| void SetNavigationItemSizedStrings(WebStateWithMockProxy& web_state, |
| int index, |
| int url_length, |
| int virtual_url_length, |
| int title_length, |
| int referrer_url_length) { |
| NavigationItemImpl* item = |
| web_state.GetNavigationManagerImpl().GetNavigationItemImplAtIndex(index); |
| if (url_length) { |
| NSString* url = [@"https://" stringByPaddingToLength:url_length |
| withString:@"a" |
| startingAtIndex:0]; |
| item->SetURL(GURL(base::SysNSStringToUTF8(url))); |
| } else { |
| item->SetURL(GURL()); |
| } |
| if (virtual_url_length) { |
| NSString* virtual_url = |
| [@"https://" stringByPaddingToLength:virtual_url_length |
| withString:@"b" |
| startingAtIndex:0]; |
| item->SetVirtualURL(GURL(base::SysNSStringToUTF8(virtual_url))); |
| } else { |
| item->SetVirtualURL(GURL()); |
| } |
| if (title_length) { |
| NSString* title = [@"" stringByPaddingToLength:title_length |
| withString:@"c" |
| startingAtIndex:0]; |
| item->SetTitle(base::SysNSStringToUTF16(title)); |
| } else { |
| item->SetTitle(base::SysNSStringToUTF16(@"")); |
| } |
| |
| Referrer referrer; |
| if (referrer_url_length) { |
| NSString* referrer_url = |
| [@"https://" stringByPaddingToLength:referrer_url_length |
| withString:@"d" |
| startingAtIndex:0]; |
| referrer.url = GURL(base::SysNSStringToUTF8(referrer_url)); |
| } |
| item->SetReferrer(referrer); |
| } |
| |
| // Tests building storage for session that is longer than kMaxSessionSize with |
| // last committed item at the end of the session. |
| TEST_F(SessionStorageBuilderTest, BuildStorageForExtraLongSession) { |
| // Create WebState with navigation item count that exceeds kMaxSessionSize. |
| WebState::CreateParams params(GetBrowserState()); |
| WebStateWithMockProxy web_state(params); |
| |
| NSMutableArray* back_urls = [NSMutableArray array]; |
| for (int i = 0; i < kMaxSessionSize; i++) { |
| [back_urls addObject:[NSString stringWithFormat:@"http://%d.test", i]]; |
| } |
| NSString* const current_url = @"http://current.test"; |
| [web_state.fake_wk_list() setCurrentURL:current_url |
| backListURLs:back_urls |
| forwardListURLs:nil]; |
| OCMStub([web_state.mock_web_view() URL]) |
| .andReturn([NSURL URLWithString:current_url]); |
| NavigationManager* navigation_manager = web_state.GetNavigationManager(); |
| int original_item_count = navigation_manager->GetItemCount(); |
| ASSERT_EQ(kMaxSessionSize + 1, original_item_count); |
| |
| // Verify that storage item count does not exceed kMaxSessionSize. |
| SessionStorageBuilder builder; |
| CRWSessionStorage* storage = builder.BuildStorage(&web_state); |
| ASSERT_TRUE(storage); |
| int stored_item_count = storage.itemStorages.count; |
| ASSERT_EQ(kMaxSessionSize, stored_item_count); |
| |
| // Walk backwards and verify that URLs in the storage match original URLs. |
| for (int i = 0; i < kMaxSessionSize; i++) { |
| NavigationItem* item = |
| navigation_manager->GetItemAtIndex(original_item_count - i - 1); |
| CRWNavigationItemStorage* item_storage = |
| [storage.itemStorages objectAtIndex:stored_item_count - i - 1]; |
| EXPECT_EQ(item->GetURL(), item_storage.URL) << "index: " << i; |
| } |
| } |
| |
| // Tests building storage for session that has items with |
| // ShouldSkipSerialization flag. The session length after skipping the items is |
| // not longer than kMaxSessionSize. |
| TEST_F(SessionStorageBuilderTest, ShouldSkipSerializationItems) { |
| // Create WebState with navigation item count that exceeds kMaxSessionSize. |
| WebState::CreateParams params(GetBrowserState()); |
| WebStateWithMockProxy web_state(params); |
| |
| NSMutableArray* back_urls = [NSMutableArray array]; |
| for (int i = 0; i < kMaxSessionSize; i++) { |
| [back_urls addObject:[NSString stringWithFormat:@"http://%d.test", i]]; |
| } |
| NSString* const current_url = @"http://current.test"; |
| [web_state.fake_wk_list() setCurrentURL:current_url |
| backListURLs:back_urls |
| forwardListURLs:nil]; |
| OCMStub([web_state.mock_web_view() URL]) |
| .andReturn([NSURL URLWithString:current_url]); |
| NavigationManager* navigation_manager = web_state.GetNavigationManager(); |
| int original_item_count = navigation_manager->GetItemCount(); |
| ASSERT_EQ(kMaxSessionSize + 1, original_item_count); |
| |
| const int kSkippedItemIndex = kMaxSessionSize - 1; |
| web_state.GetNavigationManagerImpl() |
| .GetNavigationItemImplAtIndex(kSkippedItemIndex) |
| ->SetShouldSkipSerialization(true); |
| |
| // Verify that storage item count does not exceed kMaxSessionSize. |
| SessionStorageBuilder builder; |
| CRWSessionStorage* storage = builder.BuildStorage(&web_state); |
| ASSERT_TRUE(storage); |
| int stored_item_count = storage.itemStorages.count; |
| ASSERT_EQ(kMaxSessionSize, stored_item_count); |
| |
| // Verify that URLs in the storage match original URLs without skipped item. |
| for (int storage_index = 0, item_index = 0; |
| storage_index < kMaxSessionSize && |
| item_index < web_state.GetNavigationManagerImpl().GetItemCount(); |
| storage_index++, item_index++) { |
| if (item_index == kSkippedItemIndex) { |
| item_index++; |
| } |
| NavigationItem* item = navigation_manager->GetItemAtIndex(item_index); |
| |
| CRWNavigationItemStorage* item_storage = |
| [storage.itemStorages objectAtIndex:storage_index]; |
| EXPECT_EQ(item->GetURL(), item_storage.URL) << "item_index: " << item_index; |
| } |
| } |
| |
| // Tests building storage for session that has URL longer than |
| // url::kMaxURLChars. |
| TEST_F(SessionStorageBuilderTest, SkipLongUrls) { |
| // Create WebState with navigation item count that exceeds kMaxSessionSize. |
| WebState::CreateParams params(GetBrowserState()); |
| WebStateWithMockProxy web_state(params); |
| |
| NSString* long_url = |
| [@"https://" stringByPaddingToLength:url::kMaxURLChars + 1 |
| withString:@"a" |
| startingAtIndex:0]; |
| NSString* normal_url = @"https://foo.test"; |
| NSString* const current_url = normal_url; |
| [web_state.fake_wk_list() setCurrentURL:normal_url |
| backListURLs:@[ long_url ] |
| forwardListURLs:nil]; |
| OCMStub([web_state.mock_web_view() URL]) |
| .andReturn([NSURL URLWithString:current_url]); |
| NavigationManager* navigation_manager = web_state.GetNavigationManager(); |
| ASSERT_EQ(2, navigation_manager->GetItemCount()); |
| |
| web_state.GetNavigationManagerImpl() |
| .GetNavigationItemImplAtIndex(1) |
| ->SetReferrer(web::Referrer(GURL(base::SysNSStringToUTF8(long_url)), |
| web::ReferrerPolicy::ReferrerPolicyDefault)); |
| |
| // Verify that storage has single item and that item does not have a referrer. |
| SessionStorageBuilder builder; |
| CRWSessionStorage* storage = builder.BuildStorage(&web_state); |
| ASSERT_TRUE(storage); |
| ASSERT_EQ(1U, storage.itemStorages.count); |
| |
| EXPECT_EQ(GURL::EmptyGURL(), [storage.itemStorages.firstObject referrer].url); |
| } |
| |
| // Tests building storage for session that has items longer than |
| // web::kMaxNavigationItemSize. |
| TEST_F(SessionStorageBuilderTest, SkipItemOverMaxSize) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature(features::kReduceSessionSize); |
| |
| // Create WebState with navigation item count that are below maximum size. |
| WebState::CreateParams params(GetBrowserState()); |
| WebStateWithMockProxy web_state(params); |
| |
| NSString* normal_url = @"https://foo.test"; |
| NSString* const current_url = normal_url; |
| [web_state.fake_wk_list() setCurrentURL:normal_url |
| backListURLs:@[ normal_url ] |
| forwardListURLs:nil]; |
| OCMStub([web_state.mock_web_view() URL]) |
| .andReturn([NSURL URLWithString:current_url]); |
| NavigationManager* navigation_manager = web_state.GetNavigationManager(); |
| ASSERT_EQ(2, navigation_manager->GetItemCount()); |
| |
| SessionStorageBuilder builder; |
| CRWSessionStorage* storage = builder.BuildStorage(&web_state); |
| ASSERT_TRUE(storage); |
| ASSERT_EQ(2U, storage.itemStorages.count); |
| |
| SetNavigationItemSizedStrings(web_state, 0, web::kMaxNavigationItemSize - 1, |
| 0, 0, 0); |
| storage = builder.BuildStorage(&web_state); |
| ASSERT_TRUE(storage); |
| ASSERT_EQ(2U, storage.itemStorages.count); |
| |
| SetNavigationItemSizedStrings(web_state, 0, web::kMaxNavigationItemSize, 0, 0, |
| 0); |
| |
| storage = builder.BuildStorage(&web_state); |
| ASSERT_TRUE(storage); |
| ASSERT_EQ(1U, storage.itemStorages.count); |
| |
| SetNavigationItemSizedStrings( |
| web_state, 0, web::kMaxNavigationItemSize - 1000 - 1, 1000 - 1, 0, 0); |
| |
| storage = builder.BuildStorage(&web_state); |
| ASSERT_TRUE(storage); |
| ASSERT_EQ(2U, storage.itemStorages.count); |
| |
| SetNavigationItemSizedStrings(web_state, 0, |
| web::kMaxNavigationItemSize - 1000, 1000, 0, 0); |
| |
| storage = builder.BuildStorage(&web_state); |
| ASSERT_TRUE(storage); |
| ASSERT_EQ(1U, storage.itemStorages.count); |
| |
| SetNavigationItemSizedStrings(web_state, 0, |
| web::kMaxNavigationItemSize - 1040 - 1, |
| 1000 - 1, 40 - 1, 0); |
| |
| storage = builder.BuildStorage(&web_state); |
| ASSERT_TRUE(storage); |
| ASSERT_EQ(2U, storage.itemStorages.count); |
| |
| SetNavigationItemSizedStrings( |
| web_state, 0, web::kMaxNavigationItemSize - 1040, 1000, 40, 0); |
| |
| storage = builder.BuildStorage(&web_state); |
| ASSERT_TRUE(storage); |
| ASSERT_EQ(1U, storage.itemStorages.count); |
| |
| SetNavigationItemSizedStrings(web_state, 0, |
| web::kMaxNavigationItemSize - 2040 - 1, |
| 1000 - 1, 40 - 1, 1000 - 1); |
| |
| storage = builder.BuildStorage(&web_state); |
| ASSERT_TRUE(storage); |
| ASSERT_EQ(2U, storage.itemStorages.count); |
| |
| SetNavigationItemSizedStrings( |
| web_state, 0, web::kMaxNavigationItemSize - 2040, 1000, 40, 1000); |
| |
| storage = builder.BuildStorage(&web_state); |
| ASSERT_TRUE(storage); |
| ASSERT_EQ(1U, storage.itemStorages.count); |
| } |
| |
| } // namespace web |