// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/web/navigation/navigation_manager_impl.h"
#import "base/test/metrics/histogram_tester.h"
#import "base/test/scoped_feature_list.h"
#import "ios/web/common/features.h"
#import "ios/web/navigation/navigation_manager_impl.h"
#import "ios/web/public/test/navigation_test_util.h"
#import "ios/web/public/test/web_state_test_util.h"
#import "ios/web/public/test/web_view_content_test_util.h"
#import "ios/web/test/web_int_test.h"
#import "ios/web/web_state/ui/crw_web_controller.h"
#import "ios/web/web_state/web_state_impl.h"
#import "net/test/embedded_test_server/default_handlers.h"
#import "net/test/embedded_test_server/embedded_test_server.h"
namespace web {
// Test fixture for navigation manager tests requiring a real webview (for
// example, those involving session restoration).
class NavigationManagerImplTest : public WebIntTest {
protected:
void SetUp() override {
WebIntTest::SetUp();
test_server_ = std::make_unique<net::test_server::EmbeddedTestServer>();
net::test_server::RegisterDefaultHandlers(test_server_.get());
ASSERT_TRUE(test_server_->Start());
}
NavigationManager* navigation_manager() {
return web_state()->GetNavigationManager();
}
NavigationManagerImpl& navigation_manager_impl() {
return web::WebStateImpl::FromWebState(web_state())
->GetNavigationManagerImpl();
}
base::HistogramTester histogram_tester_;
protected:
// Embedded test server which hosts sample pages.
std::unique_ptr<net::EmbeddedTestServer> test_server_;
};
// Tests state of an empty navigation manager.
TEST_F(NavigationManagerImplTest, EmptyManager) {
ASSERT_EQ(0, navigation_manager()->GetItemCount());
}
// Tests state of a single navigation.
TEST_F(NavigationManagerImplTest, SingleNavigation) {
ASSERT_EQ(0, navigation_manager()->GetItemCount());
GURL url = test_server_->GetURL("/echo");
ASSERT_TRUE(LoadUrl(url));
EXPECT_EQ(1, navigation_manager()->GetItemCount());
}
// Tests state after restoration of a single item.
TEST_F(NavigationManagerImplTest, SingleItemRestore) {
ASSERT_EQ(0, navigation_manager()->GetItemCount());
GURL url = test_server_->GetURL("/echo");
auto item = std::make_unique<NavigationItemImpl>();
item->SetURL(url);
item->SetTitle(u"Test Website");
std::vector<std::unique_ptr<NavigationItem>> items;
items.push_back(std::move(item));
navigation_manager()->Restore(/*last_committed_item_index=*/0,
std::move(items));
EXPECT_EQ(1, navigation_manager()->GetItemCount());
EXPECT_EQ(0, navigation_manager()->GetLastCommittedItemIndex());
NavigationItem* visible_item = navigation_manager()->GetVisibleItem();
ASSERT_TRUE(visible_item);
NavigationItem* last_committed_item =
navigation_manager()->GetLastCommittedItem();
ASSERT_TRUE(last_committed_item);
EXPECT_EQ(visible_item, last_committed_item);
ASSERT_FALSE(navigation_manager()->GetPendingItem());
}
// Tests state after restoration of multiple items.
TEST_F(NavigationManagerImplTest, MultipleItemRestore) {
ASSERT_EQ(0, web_state()->GetNavigationManager()->GetItemCount());
auto item0 = std::make_unique<NavigationItemImpl>();
item0->SetURL(test_server_->GetURL("/echo?0"));
item0->SetTitle(u"Test Website 0");
auto item1 = std::make_unique<NavigationItemImpl>();
item1->SetURL(test_server_->GetURL("/echo?1"));
std::vector<std::unique_ptr<NavigationItem>> items;
items.push_back(std::move(item0));
items.push_back(std::move(item1));
navigation_manager()->Restore(1 /* last_committed_item_index */,
std::move(items));
ASSERT_EQ(2, web_state()->GetNavigationManager()->GetItemCount());
EXPECT_EQ(1, navigation_manager()->GetLastCommittedItemIndex());
ASSERT_FALSE(navigation_manager()->GetPendingItem());
}
// Tests that restoring session replaces existing history in navigation manager.
TEST_F(NavigationManagerImplTest, RestoreSessionResetsHistory) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
/*enabled_features=*/{features::kRemoveOldWebStateRestoration},
/*disabled_features=*/{});
EXPECT_EQ(-1, navigation_manager()->GetPendingItemIndex());
EXPECT_EQ(-1, navigation_manager()->GetLastCommittedItemIndex());
ASSERT_TRUE(LoadUrl(test_server_->GetURL("/echo?0")));
ASSERT_TRUE(LoadUrl(test_server_->GetURL("/echo?1")));
navigation_manager_impl().AddPendingItem(
test_server_->GetURL("/echo?0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
web::NavigationInitiationType::BROWSER_INITIATED,
/*is_post_navigation=*/false, /*is_error_navigation=*/false,
web::HttpsUpgradeType::kNone);
EXPECT_EQ(1, navigation_manager()->GetLastCommittedItemIndex());
EXPECT_TRUE(navigation_manager()->GetPendingItem() != nullptr);
// Sets up each test case with a session history of 3 items. The middle item
// is the current item.
auto item0 = std::make_unique<NavigationItemImpl>();
GURL url0 = test_server_->GetURL("/echo?restored0");
item0->SetURL(url0);
auto item1 = std::make_unique<NavigationItemImpl>();
GURL url1 = test_server_->GetURL("/echo?restored1");
item1->SetURL(url1);
auto item2 = std::make_unique<NavigationItemImpl>();
GURL url2 = test_server_->GetURL("/echo?restored2");
item2->SetURL(url2);
std::vector<std::unique_ptr<NavigationItem>> items;
items.push_back(std::move(item0));
items.push_back(std::move(item1));
items.push_back(std::move(item2));
navigation_manager()->Restore(2 /* last_committed_item_index */,
std::move(items));
ASSERT_EQ(3, web_state()->GetNavigationManager()->GetItemCount());
EXPECT_EQ(2, navigation_manager()->GetLastCommittedItemIndex());
EXPECT_TRUE(navigation_manager()->GetPendingItem() == nullptr);
// // Check that cached visible item is returned.
EXPECT_EQ(url2, navigation_manager()->GetVisibleItem()->GetURL());
}
// Tests that Reload from detached mode restores cached history.
TEST_F(NavigationManagerImplTest, DetachedModeReload) {
GURL url0 = test_server_->GetURL("/echo?0");
ASSERT_TRUE(LoadUrl(url0));
GURL url1 = test_server_->GetURL("/echo?1");
ASSERT_TRUE(LoadUrl(url1));
GURL url2 = test_server_->GetURL("/echo?2");
ASSERT_TRUE(LoadUrl(url2));
navigation_manager()->GoBack();
ASSERT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
base::test::ios::kWaitForPageLoadTimeout, ^{
base::RunLoop().RunUntilIdle();
return url1 == navigation_manager()->GetVisibleItem()->GetURL();
}));
ASSERT_EQ(3, web_state()->GetNavigationManager()->GetItemCount());
// Clear the webview.
navigation_manager_impl().DetachFromWebView();
[web::test::GetWebController(web_state()) removeWebView];
// Reloading should restore history.
navigation_manager()->Reload(web::ReloadType::NORMAL,
false /* check_for_repost */);
EXPECT_EQ(url1, navigation_manager()->GetVisibleItem()->GetURL());
std::vector<NavigationItem*> back_items =
navigation_manager()->GetBackwardItems();
ASSERT_EQ(1ul, back_items.size());
EXPECT_EQ(url0, back_items[0]->GetURL().spec());
std::vector<NavigationItem*> forward_items =
navigation_manager()->GetForwardItems();
ASSERT_EQ(1ul, forward_items.size());
EXPECT_EQ(url2, forward_items[0]->GetURL().spec());
histogram_tester_.ExpectTotalCount(kRestoreNavigationItemCount, 1);
histogram_tester_.ExpectBucketCount(kRestoreNavigationItemCount, 3, 1);
}
// Tests that GoToIndex from detached mode restores cached history with
// updated / current item offset.
TEST_F(NavigationManagerImplTest, DetachedModeGoToIndex) {
GURL url0 = test_server_->GetURL("/echo?0");
ASSERT_TRUE(LoadUrl(url0));
ASSERT_TRUE(LoadUrl(test_server_->GetURL("/echo?1")));
ASSERT_TRUE(LoadUrl(test_server_->GetURL("/echo?2")));
navigation_manager_impl().DetachFromWebView();
[web::test::GetWebController(web_state()) removeWebView];
navigation_manager()->GoToIndex(0);
EXPECT_EQ(nullptr, navigation_manager()->GetPendingItem());
EXPECT_EQ(url0, navigation_manager()->GetVisibleItem()->GetURL());
histogram_tester_.ExpectTotalCount(kRestoreNavigationItemCount, 1);
histogram_tester_.ExpectBucketCount(kRestoreNavigationItemCount, 3, 1);
}
// Tests that LoadIfNecessary from detached mode restores cached history.
TEST_F(NavigationManagerImplTest, DetachedModeLoadIfNecessary) {
ASSERT_TRUE(LoadUrl(test_server_->GetURL("/echo?0")));
ASSERT_TRUE(LoadUrl(test_server_->GetURL("/echo?1")));
GURL url2 = test_server_->GetURL("/echo?2");
ASSERT_TRUE(LoadUrl(url2));
navigation_manager_impl().DetachFromWebView();
[web::test::GetWebController(web_state()) removeWebView];
navigation_manager()->LoadIfNecessary();
EXPECT_EQ(nullptr, navigation_manager()->GetPendingItem());
EXPECT_EQ(url2, navigation_manager()->GetVisibleItem()->GetURL());
histogram_tester_.ExpectTotalCount(kRestoreNavigationItemCount, 1);
histogram_tester_.ExpectBucketCount(kRestoreNavigationItemCount, 3, 1);
}
// Tests that LoadURLWithParams from detached mode restores backward history and
// adds the new item at the end.
TEST_F(NavigationManagerImplTest, DetachedModeLoadURLWithParams) {
ASSERT_TRUE(LoadUrl(test_server_->GetURL("/echo?0")));
ASSERT_TRUE(LoadUrl(test_server_->GetURL("/echo?1")));
navigation_manager_impl().DetachFromWebView();
[web::test::GetWebController(web_state()) removeWebView];
GURL url = test_server_->GetURL("/echo?2");
NavigationManager::WebLoadParams params(url);
navigation_manager()->LoadURLWithParams(params);
EXPECT_EQ(nullptr, navigation_manager()->GetPendingItem());
EXPECT_EQ(url, navigation_manager()->GetVisibleItem()->GetURL());
histogram_tester_.ExpectTotalCount(kRestoreNavigationItemCount, 1);
histogram_tester_.ExpectBucketCount(kRestoreNavigationItemCount, 3, 1);
}
} // namespace web