// Copyright (c) 2006-2008 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 "base/file_util.h" #include "base/path_service.h" #include "base/string_util.h" #include "chrome/browser/profile_manager.h" #include "chrome/browser/history/history.h" #include "chrome/browser/sessions/session_service.h" #include "chrome/browser/sessions/session_service_test_helper.h" #include "chrome/browser/sessions/session_types.h" #include "chrome/browser/tab_contents/navigation_controller.h" #include "chrome/browser/tab_contents/navigation_entry.h" #include "chrome/browser/tab_contents/tab_contents_delegate.h" #include "chrome/browser/tab_contents/tab_contents_factory.h" #include "chrome/common/notification_registrar.h" #include "chrome/common/notification_service.h" #include "chrome/common/render_messages.h" #include "chrome/common/stl_util-inl.h" #include "chrome/test/test_notification_tracker.h" #include "chrome/test/test_tab_contents.h" #include "chrome/test/testing_profile.h" #include "net/base/net_util.h" #include "testing/gtest/include/gtest/gtest.h" // TODO(port): get rid of this section, finish porting. #if defined(OS_WIN) #include "chrome/browser/tab_contents/tab_contents.h" #else #include "chrome/common/temp_scaffolding_stubs.h" #endif using base::Time; namespace { // NavigationControllerTest ---------------------------------------------------- class NavigationControllerTest : public testing::Test, public TabContentsDelegate { public: NavigationControllerTest() : contents(NULL), profile(NULL), factory1_(TestTabContentsFactory::CreateAndRegisterFactory()), factory2_(TestTabContentsFactory::CreateAndRegisterFactory()) { } ~NavigationControllerTest() { delete profile; } // testing::Test methods: virtual void SetUp() { if (!profile) profile = new TestingProfile(); contents = new TestTabContents(type1()); contents->set_delegate(this); contents->CreateView(); contents->SetupController(profile); } virtual void TearDown() { TestTabContents::set_site_instance(NULL); // Make sure contents is valid. NavigationControllerHistoryTest ends up // resetting this before TearDown is invoked. if (contents) ClearContents(); } void ClearContents() { contents->set_delegate(NULL); contents->CloseContents(); contents = NULL; } // TabContentsDelegate methods (only care about ReplaceContents): virtual void OpenURLFromTab(TabContents*, const GURL&, const GURL&, WindowOpenDisposition, PageTransition::Type) {} virtual void NavigationStateChanged(const TabContents*, unsigned flags) {} virtual void ReplaceContents(TabContents* source, TabContents* new_contents) { contents->set_delegate(NULL); contents = static_cast(new_contents); contents->set_delegate(this); } virtual void AddNewContents(TabContents*, TabContents*, WindowOpenDisposition, const gfx::Rect&, bool user_gesture) {} virtual void ActivateContents(TabContents*) {} virtual void LoadingStateChanged(TabContents*) {} virtual void NavigateToPage(TabContents*, const GURL&, PageTransition::Type) {} virtual void CloseContents(TabContents*) {} virtual void MoveContents(TabContents*, const gfx::Rect&) {} virtual bool IsPopup(TabContents*) { return false; } virtual void ToolbarSizeChanged(TabContents* source, bool is_animating) {} virtual void URLStarredChanged(TabContents* source, bool starred) {} virtual void UpdateTargetURL(TabContents* source, const GURL& url) {}; TabContentsType type1() const { return factory1_->type(); } TabContentsType type2() const { return factory2_->type(); } const std::string& scheme1() const { return factory1_->scheme(); } const std::string& scheme2() const { return factory2_->scheme(); } TestTabContents* contents; Profile* profile; private: MessageLoopForUI message_loop_; scoped_ptr factory1_; scoped_ptr factory2_; }; // NavigationControllerHistoryTest --------------------------------------------- class NavigationControllerHistoryTest : public NavigationControllerTest { public: NavigationControllerHistoryTest() : url0(scheme1() + ":foo1"), url1(scheme1() + ":foo1"), url2(scheme1() + ":foo1"), profile_manager_(NULL) { } virtual ~NavigationControllerHistoryTest() { // Prevent our base class from deleting the profile since profile's // lifetime is managed by profile_manager_. profile = NULL; STLDeleteElements(&windows_); } virtual void SetUp() { // Calculate the path for a scratch profile, and make sure it's empty. ASSERT_TRUE(PathService::Get(base::DIR_TEMP, &test_dir_)); file_util::AppendToPath(&test_dir_, L"NavigationControllerTest"); profile_path_ = test_dir_; file_util::AppendToPath(&profile_path_, L"New Profile"); file_util::Delete(test_dir_, true); file_util::CreateDirectory(test_dir_); // Create a profile. profile_manager_ = new ProfileManager(); profile = ProfileManager::CreateProfile( FilePath::FromWStringHack(profile_path_), L"New Profile", L"new-profile", L""); ASSERT_TRUE(profile); profile_manager_->AddProfile(profile); // Do the super thing. Notice that the above code sets profile, profile is // used in NavigationControllerTest::SetUp(), hence it now. NavigationControllerTest::SetUp(); // Force the session service to be created. SessionService* service = profile->GetSessionService(); service->SetWindowType(window_id, Browser::TYPE_NORMAL); service->SetWindowBounds(window_id, gfx::Rect(0, 1, 2, 3), false); service->SetTabIndexInWindow(window_id, contents->controller()->session_id(), 0); contents->controller()->SetWindowID(window_id); } virtual void TearDown() { NavigationControllerTest::TearDown(); helper_.set_service(NULL); // Make sure we wait for history to shut down before continuing. The task // we add will cause our message loop to quit once it is destroyed. HistoryService* history = profile->GetHistoryService(Profile::IMPLICIT_ACCESS); history->SetOnBackendDestroyTask(new MessageLoop::QuitTask); delete profile_manager_; MessageLoop::current()->Run(); ASSERT_TRUE(file_util::Delete(test_dir_, true)); ASSERT_FALSE(file_util::PathExists(test_dir_)); } // Deletes the current profile manager and creates a new one. Indirectly this // shuts down the history database and reopens it. void ReopenDatabase() { ClearContents(); helper_.set_service(NULL); delete profile_manager_; profile_manager_ = new ProfileManager(); profile_manager_->AddProfileByPath( FilePath::FromWStringHack(profile_path_)); profile = profile_manager_->GetProfileByPath( FilePath::FromWStringHack(profile_path_)); helper_.set_service(profile->GetSessionService()); } void GetLastSession() { Profile* profile = contents->profile(); profile->GetSessionService()->TabClosed( contents->controller()->window_id(), contents->controller()->session_id()); ReopenDatabase(); Time close_time; helper_.ReadWindows(&windows_); } CancelableRequestConsumer consumer; // URLs for testing. const GURL url0; const GURL url1; const GURL url2; std::vector windows_; SessionID window_id; SessionServiceTestHelper helper_; private: ProfileManager* profile_manager_; std::wstring test_dir_; std::wstring profile_path_; }; void RegisterForAllNavNotifications(TestNotificationTracker* tracker, NavigationController* controller) { tracker->ListenFor(NotificationType::NAV_ENTRY_COMMITTED, Source(controller)); tracker->ListenFor(NotificationType::NAV_LIST_PRUNED, Source(controller)); tracker->ListenFor(NotificationType::NAV_ENTRY_CHANGED, Source(controller)); } } // namespace // ----------------------------------------------------------------------------- TEST_F(NavigationControllerTest, Defaults) { EXPECT_TRUE(contents->is_active()); EXPECT_TRUE(contents->controller()); EXPECT_FALSE(contents->controller()->GetPendingEntry()); EXPECT_FALSE(contents->controller()->GetLastCommittedEntry()); EXPECT_EQ(contents->controller()->GetPendingEntryIndex(), -1); EXPECT_EQ(contents->controller()->GetLastCommittedEntryIndex(), -1); EXPECT_EQ(contents->controller()->GetEntryCount(), 0); EXPECT_FALSE(contents->controller()->CanGoBack()); EXPECT_FALSE(contents->controller()->CanGoForward()); } TEST_F(NavigationControllerTest, LoadURL) { TestNotificationTracker notifications; RegisterForAllNavNotifications(¬ifications, contents->controller()); const GURL url1(scheme1() + ":foo1"); const GURL url2(scheme1() + ":foo2"); contents->controller()->LoadURL(url1, GURL(), PageTransition::TYPED); // Creating a pending notification should not have issued any of the // notifications we're listening for. EXPECT_EQ(0U, notifications.size()); // The load should now be pending. EXPECT_EQ(contents->controller()->GetEntryCount(), 0); EXPECT_EQ(contents->controller()->GetLastCommittedEntryIndex(), -1); EXPECT_EQ(contents->controller()->GetPendingEntryIndex(), -1); EXPECT_FALSE(contents->controller()->GetLastCommittedEntry()); EXPECT_TRUE(contents->controller()->GetPendingEntry()); EXPECT_FALSE(contents->controller()->CanGoBack()); EXPECT_FALSE(contents->controller()->CanGoForward()); EXPECT_EQ(contents->GetMaxPageID(), -1); // We should have gotten no notifications from the preceeding checks. EXPECT_EQ(0U, notifications.size()); contents->CompleteNavigationAsRenderer(0, url1); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); // The load should now be committed. EXPECT_EQ(contents->controller()->GetEntryCount(), 1); EXPECT_EQ(contents->controller()->GetLastCommittedEntryIndex(), 0); EXPECT_EQ(contents->controller()->GetPendingEntryIndex(), -1); EXPECT_TRUE(contents->controller()->GetLastCommittedEntry()); EXPECT_FALSE(contents->controller()->GetPendingEntry()); EXPECT_FALSE(contents->controller()->CanGoBack()); EXPECT_FALSE(contents->controller()->CanGoForward()); EXPECT_EQ(contents->GetMaxPageID(), 0); // Load another... contents->controller()->LoadURL(url2, GURL(), PageTransition::TYPED); // The load should now be pending. EXPECT_EQ(contents->controller()->GetEntryCount(), 1); EXPECT_EQ(contents->controller()->GetLastCommittedEntryIndex(), 0); EXPECT_EQ(contents->controller()->GetPendingEntryIndex(), -1); EXPECT_TRUE(contents->controller()->GetLastCommittedEntry()); EXPECT_TRUE(contents->controller()->GetPendingEntry()); // TODO(darin): maybe this should really be true? EXPECT_FALSE(contents->controller()->CanGoBack()); EXPECT_FALSE(contents->controller()->CanGoForward()); EXPECT_EQ(contents->GetMaxPageID(), 0); contents->CompleteNavigationAsRenderer(1, url2); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); // The load should now be committed. EXPECT_EQ(contents->controller()->GetEntryCount(), 2); EXPECT_EQ(contents->controller()->GetLastCommittedEntryIndex(), 1); EXPECT_EQ(contents->controller()->GetPendingEntryIndex(), -1); EXPECT_TRUE(contents->controller()->GetLastCommittedEntry()); EXPECT_FALSE(contents->controller()->GetPendingEntry()); EXPECT_TRUE(contents->controller()->CanGoBack()); EXPECT_FALSE(contents->controller()->CanGoForward()); EXPECT_EQ(contents->GetMaxPageID(), 1); } // Tests what happens when the same page is loaded again. Should not create a // new session history entry. This is what happens when you press enter in the // URL bar to reload: a pending entry is created and then it is discarded when // the load commits (because WebCore didn't actually make a new entry). TEST_F(NavigationControllerTest, LoadURL_SamePage) { TestNotificationTracker notifications; RegisterForAllNavNotifications(¬ifications, contents->controller()); const GURL url1(scheme1() + ":foo1"); contents->controller()->LoadURL(url1, GURL(), PageTransition::TYPED); EXPECT_EQ(0U, notifications.size()); contents->CompleteNavigationAsRenderer(0, url1); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); contents->controller()->LoadURL(url1, GURL(), PageTransition::TYPED); EXPECT_EQ(0U, notifications.size()); contents->CompleteNavigationAsRenderer(0, url1); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); // We should not have produced a new session history entry. EXPECT_EQ(contents->controller()->GetEntryCount(), 1); EXPECT_EQ(contents->controller()->GetLastCommittedEntryIndex(), 0); EXPECT_EQ(contents->controller()->GetPendingEntryIndex(), -1); EXPECT_TRUE(contents->controller()->GetLastCommittedEntry()); EXPECT_FALSE(contents->controller()->GetPendingEntry()); EXPECT_FALSE(contents->controller()->CanGoBack()); EXPECT_FALSE(contents->controller()->CanGoForward()); } // Tests loading a URL but discarding it before the load commits. TEST_F(NavigationControllerTest, LoadURL_Discarded) { TestNotificationTracker notifications; RegisterForAllNavNotifications(¬ifications, contents->controller()); const GURL url1(scheme1() + ":foo1"); const GURL url2(scheme1() + ":foo2"); contents->controller()->LoadURL(url1, GURL(), PageTransition::TYPED); EXPECT_EQ(0U, notifications.size()); contents->CompleteNavigationAsRenderer(0, url1); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); contents->controller()->LoadURL(url2, GURL(), PageTransition::TYPED); contents->controller()->DiscardNonCommittedEntries(); EXPECT_EQ(0U, notifications.size()); // Should not have produced a new session history entry. EXPECT_EQ(contents->controller()->GetEntryCount(), 1); EXPECT_EQ(contents->controller()->GetLastCommittedEntryIndex(), 0); EXPECT_EQ(contents->controller()->GetPendingEntryIndex(), -1); EXPECT_TRUE(contents->controller()->GetLastCommittedEntry()); EXPECT_FALSE(contents->controller()->GetPendingEntry()); EXPECT_FALSE(contents->controller()->CanGoBack()); EXPECT_FALSE(contents->controller()->CanGoForward()); } // Tests navigations that come in unrequested. This happens when the user // navigates from the web page, and here we test that there is no pending entry. TEST_F(NavigationControllerTest, LoadURL_NoPending) { TestNotificationTracker notifications; RegisterForAllNavNotifications(¬ifications, contents->controller()); // First make an existing committed entry. const GURL kExistingURL1(scheme1() + ":eh"); contents->controller()->LoadURL(kExistingURL1, GURL(), PageTransition::TYPED); contents->CompleteNavigationAsRenderer(0, kExistingURL1); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); // Do a new navigation without making a pending one. const GURL kNewURL(scheme1() + ":see"); contents->CompleteNavigationAsRenderer(99, kNewURL); // There should no longer be any pending entry, and the third navigation we // just made should be committed. EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); EXPECT_EQ(-1, contents->controller()->GetPendingEntryIndex()); EXPECT_EQ(1, contents->controller()->GetLastCommittedEntryIndex()); EXPECT_EQ(kNewURL, contents->controller()->GetActiveEntry()->url()); } // Tests navigating to a new URL when there is a new pending navigation that is // not the one that just loaded. This will happen if the user types in a URL to // somewhere slow, and then navigates the current page before the typed URL // commits. TEST_F(NavigationControllerTest, LoadURL_NewPending) { TestNotificationTracker notifications; RegisterForAllNavNotifications(¬ifications, contents->controller()); // First make an existing committed entry. const GURL kExistingURL1(scheme1() + ":eh"); contents->controller()->LoadURL(kExistingURL1, GURL(), PageTransition::TYPED); contents->CompleteNavigationAsRenderer(0, kExistingURL1); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); // Make a pending entry to somewhere new. const GURL kExistingURL2(scheme1() + ":bee"); contents->controller()->LoadURL(kExistingURL2, GURL(), PageTransition::TYPED); EXPECT_EQ(0U, notifications.size()); // Before that commits, do a new navigation. const GURL kNewURL(scheme1() + ":see"); contents->CompleteNavigationAsRenderer(3, kNewURL); // There should no longer be any pending entry, and the third navigation we // just made should be committed. EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); EXPECT_EQ(-1, contents->controller()->GetPendingEntryIndex()); EXPECT_EQ(1, contents->controller()->GetLastCommittedEntryIndex()); EXPECT_EQ(kNewURL, contents->controller()->GetActiveEntry()->url()); } // Tests navigating to a new URL when there is a pending back/forward // navigation. This will happen if the user hits back, but before that commits, // they navigate somewhere new. TEST_F(NavigationControllerTest, LoadURL_ExistingPending) { TestNotificationTracker notifications; RegisterForAllNavNotifications(¬ifications, contents->controller()); // First make some history. const GURL kExistingURL1(scheme1() + ":eh"); contents->controller()->LoadURL(kExistingURL1, GURL(), PageTransition::TYPED); contents->CompleteNavigationAsRenderer(0, kExistingURL1); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); const GURL kExistingURL2(scheme1() + ":bee"); contents->controller()->LoadURL(kExistingURL2, GURL(), PageTransition::TYPED); contents->CompleteNavigationAsRenderer(1, kExistingURL2); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); // Now make a pending back/forward navigation. The zeroth entry should be // pending. contents->controller()->GoBack(); EXPECT_EQ(0U, notifications.size()); EXPECT_EQ(0, contents->controller()->GetPendingEntryIndex()); EXPECT_EQ(1, contents->controller()->GetLastCommittedEntryIndex()); // Before that commits, do a new navigation. const GURL kNewURL(scheme1() + ":see"); NavigationController::LoadCommittedDetails details; contents->CompleteNavigationAsRenderer(3, kNewURL); // There should no longer be any pending entry, and the third navigation we // just made should be committed. EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); EXPECT_EQ(-1, contents->controller()->GetPendingEntryIndex()); EXPECT_EQ(2, contents->controller()->GetLastCommittedEntryIndex()); EXPECT_EQ(kNewURL, contents->controller()->GetActiveEntry()->url()); } TEST_F(NavigationControllerTest, Reload) { TestNotificationTracker notifications; RegisterForAllNavNotifications(¬ifications, contents->controller()); const GURL url1(scheme1() + ":foo1"); contents->controller()->LoadURL(url1, GURL(), PageTransition::TYPED); EXPECT_EQ(0U, notifications.size()); contents->CompleteNavigationAsRenderer(0, url1); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); contents->controller()->Reload(true); EXPECT_EQ(0U, notifications.size()); // The reload is pending. EXPECT_EQ(contents->controller()->GetEntryCount(), 1); EXPECT_EQ(contents->controller()->GetLastCommittedEntryIndex(), 0); EXPECT_EQ(contents->controller()->GetPendingEntryIndex(), 0); EXPECT_TRUE(contents->controller()->GetLastCommittedEntry()); EXPECT_TRUE(contents->controller()->GetPendingEntry()); EXPECT_FALSE(contents->controller()->CanGoBack()); EXPECT_FALSE(contents->controller()->CanGoForward()); contents->CompleteNavigationAsRenderer(0, url1); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); // Now the reload is committed. EXPECT_EQ(contents->controller()->GetEntryCount(), 1); EXPECT_EQ(contents->controller()->GetLastCommittedEntryIndex(), 0); EXPECT_EQ(contents->controller()->GetPendingEntryIndex(), -1); EXPECT_TRUE(contents->controller()->GetLastCommittedEntry()); EXPECT_FALSE(contents->controller()->GetPendingEntry()); EXPECT_FALSE(contents->controller()->CanGoBack()); EXPECT_FALSE(contents->controller()->CanGoForward()); } // Tests what happens when a reload navigation produces a new page. TEST_F(NavigationControllerTest, Reload_GeneratesNewPage) { TestNotificationTracker notifications; RegisterForAllNavNotifications(¬ifications, contents->controller()); const GURL url1(scheme1() + ":foo1"); const GURL url2(scheme1() + ":foo2"); contents->controller()->LoadURL(url1, GURL(), PageTransition::TYPED); contents->CompleteNavigationAsRenderer(0, url1); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); contents->controller()->Reload(true); EXPECT_EQ(0U, notifications.size()); contents->CompleteNavigationAsRenderer(1, url2); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); // Now the reload is committed. EXPECT_EQ(contents->controller()->GetEntryCount(), 2); EXPECT_EQ(contents->controller()->GetLastCommittedEntryIndex(), 1); EXPECT_EQ(contents->controller()->GetPendingEntryIndex(), -1); EXPECT_TRUE(contents->controller()->GetLastCommittedEntry()); EXPECT_FALSE(contents->controller()->GetPendingEntry()); EXPECT_TRUE(contents->controller()->CanGoBack()); EXPECT_FALSE(contents->controller()->CanGoForward()); } // Tests what happens when we navigate back successfully TEST_F(NavigationControllerTest, Back) { TestNotificationTracker notifications; RegisterForAllNavNotifications(¬ifications, contents->controller()); const GURL url1(scheme1() + ":foo1"); contents->CompleteNavigationAsRenderer(0, url1); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); const GURL url2(scheme1() + ":foo2"); contents->CompleteNavigationAsRenderer(1, url2); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); contents->controller()->GoBack(); EXPECT_EQ(0U, notifications.size()); // We should now have a pending navigation to go back. EXPECT_EQ(contents->controller()->GetEntryCount(), 2); EXPECT_EQ(contents->controller()->GetLastCommittedEntryIndex(), 1); EXPECT_EQ(contents->controller()->GetPendingEntryIndex(), 0); EXPECT_TRUE(contents->controller()->GetLastCommittedEntry()); EXPECT_TRUE(contents->controller()->GetPendingEntry()); EXPECT_FALSE(contents->controller()->CanGoBack()); EXPECT_TRUE(contents->controller()->CanGoForward()); contents->CompleteNavigationAsRenderer(0, url2); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); // The back navigation completed successfully. EXPECT_EQ(contents->controller()->GetEntryCount(), 2); EXPECT_EQ(contents->controller()->GetLastCommittedEntryIndex(), 0); EXPECT_EQ(contents->controller()->GetPendingEntryIndex(), -1); EXPECT_TRUE(contents->controller()->GetLastCommittedEntry()); EXPECT_FALSE(contents->controller()->GetPendingEntry()); EXPECT_FALSE(contents->controller()->CanGoBack()); EXPECT_TRUE(contents->controller()->CanGoForward()); } // Tests what happens when a back navigation produces a new page. TEST_F(NavigationControllerTest, Back_GeneratesNewPage) { TestNotificationTracker notifications; RegisterForAllNavNotifications(¬ifications, contents->controller()); const GURL url1(scheme1() + ":foo1"); const GURL url2(scheme1() + ":foo2"); const GURL url3(scheme1() + ":foo3"); contents->controller()->LoadURL(url1, GURL(), PageTransition::TYPED); contents->CompleteNavigationAsRenderer(0, url1); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); contents->controller()->LoadURL(url2, GURL(), PageTransition::TYPED); contents->CompleteNavigationAsRenderer(1, url2); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); contents->controller()->GoBack(); EXPECT_EQ(0U, notifications.size()); // We should now have a pending navigation to go back. EXPECT_EQ(contents->controller()->GetEntryCount(), 2); EXPECT_EQ(contents->controller()->GetLastCommittedEntryIndex(), 1); EXPECT_EQ(contents->controller()->GetPendingEntryIndex(), 0); EXPECT_TRUE(contents->controller()->GetLastCommittedEntry()); EXPECT_TRUE(contents->controller()->GetPendingEntry()); EXPECT_FALSE(contents->controller()->CanGoBack()); EXPECT_TRUE(contents->controller()->CanGoForward()); contents->CompleteNavigationAsRenderer(2, url3); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); // The back navigation resulted in a completely new navigation. // TODO(darin): perhaps this behavior will be confusing to users? EXPECT_EQ(contents->controller()->GetEntryCount(), 3); EXPECT_EQ(contents->controller()->GetLastCommittedEntryIndex(), 2); EXPECT_EQ(contents->controller()->GetPendingEntryIndex(), -1); EXPECT_TRUE(contents->controller()->GetLastCommittedEntry()); EXPECT_FALSE(contents->controller()->GetPendingEntry()); EXPECT_TRUE(contents->controller()->CanGoBack()); EXPECT_FALSE(contents->controller()->CanGoForward()); } // Receives a back message when there is a new pending navigation entry. TEST_F(NavigationControllerTest, Back_NewPending) { TestNotificationTracker notifications; RegisterForAllNavNotifications(¬ifications, contents->controller()); const GURL kUrl1(scheme1() + ":foo1"); const GURL kUrl2(scheme1() + ":foo2"); const GURL kUrl3(scheme1() + ":foo3"); // First navigate two places so we have some back history. contents->CompleteNavigationAsRenderer(0, kUrl1); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); //contents->controller()->LoadURL(kUrl2, PageTransition::TYPED); contents->CompleteNavigationAsRenderer(1, kUrl2); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); // Now start a new pending navigation and go back before it commits. contents->controller()->LoadURL(kUrl3, GURL(), PageTransition::TYPED); EXPECT_EQ(-1, contents->controller()->GetPendingEntryIndex()); EXPECT_EQ(kUrl3, contents->controller()->GetPendingEntry()->url()); contents->controller()->GoBack(); // The pending navigation should now be the "back" item and the new one // should be gone. EXPECT_EQ(0, contents->controller()->GetPendingEntryIndex()); EXPECT_EQ(kUrl1, contents->controller()->GetPendingEntry()->url()); } // Receives a back message when there is a different renavigation already // pending. TEST_F(NavigationControllerTest, Back_OtherBackPending) { const GURL kUrl1(scheme1() + ":foo1"); const GURL kUrl2(scheme1() + ":foo2"); const GURL kUrl3(scheme1() + ":foo3"); // First navigate three places so we have some back history. contents->CompleteNavigationAsRenderer(0, kUrl1); contents->CompleteNavigationAsRenderer(1, kUrl2); contents->CompleteNavigationAsRenderer(2, kUrl3); // With nothing pending, say we get a navigation to the second entry. contents->CompleteNavigationAsRenderer(1, kUrl2); // That second URL should be the last committed and it should have gotten the // new title. EXPECT_EQ(kUrl2, contents->controller()->GetEntryWithPageID( type1(), NULL, 1)->url()); EXPECT_EQ(1, contents->controller()->GetLastCommittedEntryIndex()); EXPECT_EQ(-1, contents->controller()->GetPendingEntryIndex()); // Now go forward to the last item again and say it was committed. contents->controller()->GoForward(); contents->CompleteNavigationAsRenderer(2, kUrl3); // Now start going back one to the second page. It will be pending. contents->controller()->GoBack(); EXPECT_EQ(1, contents->controller()->GetPendingEntryIndex()); EXPECT_EQ(2, contents->controller()->GetLastCommittedEntryIndex()); // Not synthesize a totally new back event to the first page. This will not // match the pending one. contents->CompleteNavigationAsRenderer(0, kUrl1); // The navigation should not have affected the pending entry. EXPECT_EQ(1, contents->controller()->GetPendingEntryIndex()); // But the navigated entry should be the last committed. EXPECT_EQ(0, contents->controller()->GetLastCommittedEntryIndex()); EXPECT_EQ(kUrl1, contents->controller()->GetLastCommittedEntry()->url()); } // Tests what happens when we navigate forward successfully. TEST_F(NavigationControllerTest, Forward) { TestNotificationTracker notifications; RegisterForAllNavNotifications(¬ifications, contents->controller()); const GURL url1(scheme1() + ":foo1"); const GURL url2(scheme1() + ":foo2"); contents->CompleteNavigationAsRenderer(0, url1); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); contents->CompleteNavigationAsRenderer(1, url2); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); contents->controller()->GoBack(); contents->CompleteNavigationAsRenderer(0, url1); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); contents->controller()->GoForward(); // We should now have a pending navigation to go forward. EXPECT_EQ(contents->controller()->GetEntryCount(), 2); EXPECT_EQ(contents->controller()->GetLastCommittedEntryIndex(), 0); EXPECT_EQ(contents->controller()->GetPendingEntryIndex(), 1); EXPECT_TRUE(contents->controller()->GetLastCommittedEntry()); EXPECT_TRUE(contents->controller()->GetPendingEntry()); EXPECT_TRUE(contents->controller()->CanGoBack()); EXPECT_FALSE(contents->controller()->CanGoForward()); contents->CompleteNavigationAsRenderer(1, url2); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); // The forward navigation completed successfully. EXPECT_EQ(contents->controller()->GetEntryCount(), 2); EXPECT_EQ(contents->controller()->GetLastCommittedEntryIndex(), 1); EXPECT_EQ(contents->controller()->GetPendingEntryIndex(), -1); EXPECT_TRUE(contents->controller()->GetLastCommittedEntry()); EXPECT_FALSE(contents->controller()->GetPendingEntry()); EXPECT_TRUE(contents->controller()->CanGoBack()); EXPECT_FALSE(contents->controller()->CanGoForward()); } // Tests what happens when a forward navigation produces a new page. TEST_F(NavigationControllerTest, Forward_GeneratesNewPage) { TestNotificationTracker notifications; RegisterForAllNavNotifications(¬ifications, contents->controller()); const GURL url1(scheme1() + ":foo1"); const GURL url2(scheme1() + ":foo2"); const GURL url3(scheme1() + ":foo3"); contents->CompleteNavigationAsRenderer(0, url1); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); contents->CompleteNavigationAsRenderer(1, url2); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); contents->controller()->GoBack(); contents->CompleteNavigationAsRenderer(0, url1); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); contents->controller()->GoForward(); EXPECT_EQ(0U, notifications.size()); // Should now have a pending navigation to go forward. EXPECT_EQ(contents->controller()->GetEntryCount(), 2); EXPECT_EQ(contents->controller()->GetLastCommittedEntryIndex(), 0); EXPECT_EQ(contents->controller()->GetPendingEntryIndex(), 1); EXPECT_TRUE(contents->controller()->GetLastCommittedEntry()); EXPECT_TRUE(contents->controller()->GetPendingEntry()); EXPECT_TRUE(contents->controller()->CanGoBack()); EXPECT_FALSE(contents->controller()->CanGoForward()); contents->CompleteNavigationAsRenderer(2, url3); EXPECT_TRUE(notifications.Check2AndReset( NotificationType::NAV_LIST_PRUNED, NotificationType::NAV_ENTRY_COMMITTED)); EXPECT_EQ(contents->controller()->GetEntryCount(), 2); EXPECT_EQ(contents->controller()->GetLastCommittedEntryIndex(), 1); EXPECT_EQ(contents->controller()->GetPendingEntryIndex(), -1); EXPECT_TRUE(contents->controller()->GetLastCommittedEntry()); EXPECT_FALSE(contents->controller()->GetPendingEntry()); EXPECT_TRUE(contents->controller()->CanGoBack()); EXPECT_FALSE(contents->controller()->CanGoForward()); } // Tests navigation via link click within a subframe. A new navigation entry // should be created. TEST_F(NavigationControllerTest, NewSubframe) { TestNotificationTracker notifications; RegisterForAllNavNotifications(¬ifications, contents->controller()); const GURL url1(scheme1() + ":foo1"); contents->CompleteNavigationAsRenderer(0, url1); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); const GURL url2(scheme1() + ":foo2"); ViewHostMsg_FrameNavigate_Params params; params.page_id = 1; params.url = url2; params.transition = PageTransition::MANUAL_SUBFRAME; params.should_update_history = false; params.gesture = NavigationGestureUser; params.is_post = false; NavigationController::LoadCommittedDetails details; EXPECT_TRUE(contents->controller()->RendererDidNavigate(params, &details)); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); EXPECT_EQ(url1, details.previous_url); EXPECT_FALSE(details.is_auto); EXPECT_FALSE(details.is_in_page); EXPECT_FALSE(details.is_main_frame); // The new entry should be appended. EXPECT_EQ(2, contents->controller()->GetEntryCount()); // New entry should refer to the new page, but the old URL (entries only // reflect the toplevel URL). EXPECT_EQ(url1, details.entry->url()); EXPECT_EQ(params.page_id, details.entry->page_id()); } // Some pages create a popup, then write an iframe into it. This causes a // subframe navigation without having any committed entry. Such navigations // just get thrown on the ground, but we shouldn't crash. TEST_F(NavigationControllerTest, SubframeOnEmptyPage) { TestNotificationTracker notifications; RegisterForAllNavNotifications(¬ifications, contents->controller()); // Navigation controller currently has no entries. const GURL url(scheme1() + ":foo2"); ViewHostMsg_FrameNavigate_Params params; params.page_id = 1; params.url = url; params.transition = PageTransition::AUTO_SUBFRAME; params.should_update_history = false; params.gesture = NavigationGestureAuto; params.is_post = false; NavigationController::LoadCommittedDetails details; EXPECT_FALSE(contents->controller()->RendererDidNavigate(params, &details)); EXPECT_EQ(0U, notifications.size()); } // Auto subframes are ones the page loads automatically like ads. They should // not create new navigation entries. TEST_F(NavigationControllerTest, AutoSubframe) { TestNotificationTracker notifications; RegisterForAllNavNotifications(¬ifications, contents->controller()); const GURL url1(scheme1() + ":foo1"); contents->CompleteNavigationAsRenderer(0, url1); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); const GURL url2(scheme1() + ":foo2"); ViewHostMsg_FrameNavigate_Params params; params.page_id = 0; params.url = url2; params.transition = PageTransition::AUTO_SUBFRAME; params.should_update_history = false; params.gesture = NavigationGestureUser; params.is_post = false; // Navigating should do nothing. NavigationController::LoadCommittedDetails details; EXPECT_FALSE(contents->controller()->RendererDidNavigate(params, &details)); EXPECT_EQ(0U, notifications.size()); // There should still be only one entry. EXPECT_EQ(1, contents->controller()->GetEntryCount()); } // Tests navigation and then going back to a subframe navigation. TEST_F(NavigationControllerTest, BackSubframe) { TestNotificationTracker notifications; RegisterForAllNavNotifications(¬ifications, contents->controller()); // Main page. const GURL url1(scheme1() + ":foo1"); contents->CompleteNavigationAsRenderer(0, url1); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); // First manual subframe navigation. const GURL url2(scheme1() + ":foo2"); ViewHostMsg_FrameNavigate_Params params; params.page_id = 1; params.url = url2; params.transition = PageTransition::MANUAL_SUBFRAME; params.should_update_history = false; params.gesture = NavigationGestureUser; params.is_post = false; // This should generate a new entry. NavigationController::LoadCommittedDetails details; EXPECT_TRUE(contents->controller()->RendererDidNavigate(params, &details)); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); EXPECT_EQ(2, contents->controller()->GetEntryCount()); // Second manual subframe navigation should also make a new entry. const GURL url3(scheme1() + ":foo3"); params.page_id = 2; params.url = url3; EXPECT_TRUE(contents->controller()->RendererDidNavigate(params, &details)); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); EXPECT_EQ(3, contents->controller()->GetEntryCount()); EXPECT_EQ(2, contents->controller()->GetCurrentEntryIndex()); // Go back one. contents->controller()->GoBack(); params.url = url2; params.page_id = 1; EXPECT_TRUE(contents->controller()->RendererDidNavigate(params, &details)); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); EXPECT_EQ(3, contents->controller()->GetEntryCount()); EXPECT_EQ(1, contents->controller()->GetCurrentEntryIndex()); // Go back one more. contents->controller()->GoBack(); params.url = url1; params.page_id = 0; EXPECT_TRUE(contents->controller()->RendererDidNavigate(params, &details)); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); EXPECT_EQ(3, contents->controller()->GetEntryCount()); EXPECT_EQ(0, contents->controller()->GetCurrentEntryIndex()); } TEST_F(NavigationControllerTest, LinkClick) { TestNotificationTracker notifications; RegisterForAllNavNotifications(¬ifications, contents->controller()); const GURL url1(scheme1() + ":foo1"); const GURL url2(scheme1() + ":foo2"); contents->CompleteNavigationAsRenderer(0, url1); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); contents->CompleteNavigationAsRenderer(1, url2); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); // Should not have produced a new session history entry. EXPECT_EQ(contents->controller()->GetEntryCount(), 2); EXPECT_EQ(contents->controller()->GetLastCommittedEntryIndex(), 1); EXPECT_EQ(contents->controller()->GetPendingEntryIndex(), -1); EXPECT_TRUE(contents->controller()->GetLastCommittedEntry()); EXPECT_FALSE(contents->controller()->GetPendingEntry()); EXPECT_TRUE(contents->controller()->CanGoBack()); EXPECT_FALSE(contents->controller()->CanGoForward()); } TEST_F(NavigationControllerTest, InPage) { TestNotificationTracker notifications; RegisterForAllNavNotifications(¬ifications, contents->controller()); // Main page. Note that we need "://" so this URL is treated as "standard" // which are the only ones that can have a ref. const GURL url1(scheme1() + "://foo"); contents->CompleteNavigationAsRenderer(0, url1); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); // First navigation. const GURL url2(scheme1() + "://foo#a"); ViewHostMsg_FrameNavigate_Params params; params.page_id = 1; params.url = url2; params.transition = PageTransition::LINK; params.should_update_history = false; params.gesture = NavigationGestureUser; params.is_post = false; // This should generate a new entry. NavigationController::LoadCommittedDetails details; EXPECT_TRUE(contents->controller()->RendererDidNavigate(params, &details)); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); EXPECT_EQ(2, contents->controller()->GetEntryCount()); // Go back one. ViewHostMsg_FrameNavigate_Params back_params(params); contents->controller()->GoBack(); back_params.url = url1; back_params.page_id = 0; EXPECT_TRUE(contents->controller()->RendererDidNavigate(back_params, &details)); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); EXPECT_EQ(2, contents->controller()->GetEntryCount()); EXPECT_EQ(0, contents->controller()->GetCurrentEntryIndex()); EXPECT_EQ(back_params.url, contents->controller()->GetActiveEntry()->url()); // Go forward ViewHostMsg_FrameNavigate_Params forward_params(params); contents->controller()->GoForward(); forward_params.url = url2; forward_params.page_id = 1; EXPECT_TRUE(contents->controller()->RendererDidNavigate(forward_params, &details)); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); EXPECT_EQ(2, contents->controller()->GetEntryCount()); EXPECT_EQ(1, contents->controller()->GetCurrentEntryIndex()); EXPECT_EQ(forward_params.url, contents->controller()->GetActiveEntry()->url()); // Now go back and forward again. This is to work around a bug where we would // compare the incoming URL with the last committed entry rather than the // one identified by an existing page ID. This would result in the second URL // losing the reference fragment when you navigate away from it and then back. contents->controller()->GoBack(); EXPECT_TRUE(contents->controller()->RendererDidNavigate(back_params, &details)); contents->controller()->GoForward(); EXPECT_TRUE(contents->controller()->RendererDidNavigate(forward_params, &details)); EXPECT_EQ(forward_params.url, contents->controller()->GetActiveEntry()->url()); } TEST_F(NavigationControllerTest, SwitchTypes) { TestNotificationTracker notifications; RegisterForAllNavNotifications(¬ifications, contents->controller()); const GURL url1(scheme1() + ":foo"); const GURL url2(scheme2() + ":foo"); contents->CompleteNavigationAsRenderer(0, url1); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); TestTabContents* initial_contents = contents; contents->controller()->LoadURL(url2, GURL(), PageTransition::TYPED); // The tab contents should have been replaced ASSERT_TRUE(initial_contents != contents); contents->CompleteNavigationAsRenderer(1, url2); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); // A second navigation entry should have been committed even though the // PageIDs are the same. PageIDs are scoped to the tab contents type. EXPECT_EQ(contents->controller()->GetEntryCount(), 2); EXPECT_EQ(contents->controller()->GetLastCommittedEntryIndex(), 1); EXPECT_EQ(contents->controller()->GetPendingEntryIndex(), -1); EXPECT_TRUE(contents->controller()->GetLastCommittedEntry()); EXPECT_FALSE(contents->controller()->GetPendingEntry()); EXPECT_TRUE(contents->controller()->CanGoBack()); EXPECT_FALSE(contents->controller()->CanGoForward()); // Navigate back... contents->controller()->GoBack(); ASSERT_TRUE(initial_contents == contents); // switched again! contents->CompleteNavigationAsRenderer(0, url1); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); EXPECT_EQ(contents->controller()->GetEntryCount(), 2); EXPECT_EQ(contents->controller()->GetLastCommittedEntryIndex(), 0); EXPECT_EQ(contents->controller()->GetPendingEntryIndex(), -1); EXPECT_TRUE(contents->controller()->GetLastCommittedEntry()); EXPECT_FALSE(contents->controller()->GetPendingEntry()); EXPECT_FALSE(contents->controller()->CanGoBack()); EXPECT_TRUE(contents->controller()->CanGoForward()); // There may be TabContentsCollector tasks pending, so flush them from queue. MessageLoop::current()->RunAllPending(); } // Tests what happens when we begin to navigate to a new contents type, but // then that navigation gets discarded instead. TEST_F(NavigationControllerTest, SwitchTypes_Discard) { TestNotificationTracker notifications; RegisterForAllNavNotifications(¬ifications, contents->controller()); const GURL url1(scheme1() + ":foo"); const GURL url2(scheme2() + ":foo"); contents->CompleteNavigationAsRenderer(0, url1); EXPECT_TRUE(notifications.Check1AndReset( NotificationType::NAV_ENTRY_COMMITTED)); TestTabContents* initial_contents = contents; contents->controller()->LoadURL(url2, GURL(), PageTransition::TYPED); EXPECT_EQ(0U, notifications.size()); // The tab contents should have been replaced ASSERT_TRUE(initial_contents != contents); contents->controller()->DiscardNonCommittedEntries(); EXPECT_EQ(0U, notifications.size()); // The tab contents should have been replaced back ASSERT_TRUE(initial_contents == contents); EXPECT_EQ(contents->controller()->GetEntryCount(), 1); EXPECT_EQ(contents->controller()->GetLastCommittedEntryIndex(), 0); EXPECT_EQ(contents->controller()->GetPendingEntryIndex(), -1); EXPECT_TRUE(contents->controller()->GetLastCommittedEntry()); EXPECT_FALSE(contents->controller()->GetPendingEntry()); EXPECT_FALSE(contents->controller()->CanGoBack()); EXPECT_FALSE(contents->controller()->CanGoForward()); // There may be TabContentsCollector tasks pending, so flush them from queue. MessageLoop::current()->RunAllPending(); } // Tests that TabContentsTypes that are not in use are deleted (via a // TabContentsCollector task). Prevents regression of bug 1296773. TEST_F(NavigationControllerTest, SwitchTypesCleanup) { const GURL url1(scheme1() + ":foo"); const GURL url2(scheme2() + ":foo"); const GURL url3(scheme2() + ":bar"); // Note that we need the LoadURL calls so that pending entries and the // different tab contents types are created. "Renderer" navigations won't // actually cross tab contents boundaries without these. contents->controller()->LoadURL(url1, GURL(), PageTransition::TYPED); contents->CompleteNavigationAsRenderer(0, url1); contents->controller()->LoadURL(url2, GURL(), PageTransition::TYPED); contents->CompleteNavigationAsRenderer(1, url2); contents->controller()->LoadURL(url3, GURL(), PageTransition::TYPED); contents->CompleteNavigationAsRenderer(2, url3); // Navigate back to the start. contents->controller()->GoToIndex(0); contents->CompleteNavigationAsRenderer(0, url1); // Now jump to the end. contents->controller()->GoToIndex(2); contents->CompleteNavigationAsRenderer(2, url3); // There may be TabContentsCollector tasks pending, so flush them from queue. MessageLoop::current()->RunAllPending(); // Now that the tasks have been flushed, the first tab type should be gone. ASSERT_TRUE( contents->controller()->GetTabContents(type1()) == NULL); ASSERT_EQ(contents, contents->controller()->GetTabContents(type2())); } namespace { // NotificationObserver implementation used in verifying we've received the // NotificationType::NAV_LIST_PRUNED method. class PrunedListener : public NotificationObserver { public: explicit PrunedListener(NavigationController* controller) : notification_count_(0) { registrar_.Add(this, NotificationType::NAV_LIST_PRUNED, Source(controller)); } virtual void Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { if (type == NotificationType::NAV_LIST_PRUNED) { notification_count_++; details_ = *(Details(details).ptr()); } } // Number of times NAV_LIST_PRUNED has been observed. int notification_count_; // Details from the last NAV_LIST_PRUNED. NavigationController::PrunedDetails details_; private: NotificationRegistrar registrar_; DISALLOW_COPY_AND_ASSIGN(PrunedListener); }; } // Tests that we limit the number of navigation entries created correctly. TEST_F(NavigationControllerTest, EnforceMaxNavigationCount) { size_t original_count = NavigationController::max_entry_count(); const int kMaxEntryCount = 5; NavigationController::set_max_entry_count(kMaxEntryCount); int url_index; char buffer[128]; // Load up to the max count, all entries should be there. for (url_index = 0; url_index < kMaxEntryCount; url_index++) { base::snprintf(buffer, 128, (scheme1() + "://www.a.com/%d").c_str(), url_index); GURL url(buffer); contents->controller()->LoadURL(url, GURL(), PageTransition::TYPED); contents->CompleteNavigationAsRenderer(url_index, url); } EXPECT_EQ(contents->controller()->GetEntryCount(), kMaxEntryCount); // Created a PrunedListener to observe prune notifications. PrunedListener listener(contents->controller()); // Navigate some more. base::snprintf(buffer, 128, (scheme1() + "://www.a.com/%d").c_str(), url_index); GURL url(buffer); contents->controller()->LoadURL(url, GURL(), PageTransition::TYPED); contents->CompleteNavigationAsRenderer(url_index, url); url_index++; // We should have got a pruned navigation. EXPECT_EQ(1, listener.notification_count_); EXPECT_TRUE(listener.details_.from_front); EXPECT_EQ(1, listener.details_.count); // We expect http://www.a.com/0 to be gone. EXPECT_EQ(contents->controller()->GetEntryCount(), kMaxEntryCount); EXPECT_EQ(contents->controller()->GetEntryAtIndex(0)->url(), GURL(scheme1() + "://www.a.com/1")); // More navigations. for (int i = 0; i < 3; i++) { base::snprintf(buffer, 128, (scheme1() + "://www.a.com/%d").c_str(), url_index); url = GURL(buffer); contents->controller()->LoadURL(url, GURL(), PageTransition::TYPED); contents->CompleteNavigationAsRenderer(url_index, url); url_index++; } EXPECT_EQ(contents->controller()->GetEntryCount(), kMaxEntryCount); EXPECT_EQ(contents->controller()->GetEntryAtIndex(0)->url(), GURL(scheme1() + "://www.a.com/4")); NavigationController::set_max_entry_count(original_count); } // Tests that we can do a restore and navigate to the restored entries and // everything is updated properly. This can be tricky since there is no // SiteInstance for the entries created initially. TEST_F(NavigationControllerTest, RestoreNavigate) { SiteInstance* site_instance = SiteInstance::CreateSiteInstance(profile); TestTabContents::set_site_instance(site_instance); site_instance->AddRef(); // Create a NavigationController with a restored set of tabs. GURL url(scheme1() + ":foo"); std::vector navigations; navigations.push_back(TabNavigation(0, url, GURL(), ASCIIToUTF16("Title"), "state", PageTransition::LINK)); NavigationController* controller = new NavigationController(profile, navigations, 0); controller->GoToIndex(0); // We should now have one entry, and it should be "pending". EXPECT_EQ(1, controller->GetEntryCount()); EXPECT_EQ(controller->GetEntryAtIndex(0), controller->GetPendingEntry()); EXPECT_EQ(0, controller->GetEntryAtIndex(0)->page_id()); // Say we navigated to that entry. ViewHostMsg_FrameNavigate_Params params; params.page_id = 0; params.url = url; params.transition = PageTransition::LINK; params.should_update_history = false; params.gesture = NavigationGestureUser; params.is_post = false; NavigationController::LoadCommittedDetails details; controller->RendererDidNavigate(params, &details); // There should be no longer any pending entry and one committed one. This // means that we were able to locate the entry, assign its site instance, and // commit it properly. EXPECT_EQ(1, controller->GetEntryCount()); EXPECT_EQ(0, controller->GetLastCommittedEntryIndex()); EXPECT_FALSE(controller->GetPendingEntry()); EXPECT_EQ(site_instance, controller->GetLastCommittedEntry()->site_instance()); // Clean up the navigation controller. ClearContents(); controller->Destroy(); TestTabContents::set_site_instance(NULL); site_instance->Release(); } // Make sure that the page type and stuff is correct after an interstitial. TEST_F(NavigationControllerTest, Interstitial) { // First navigate somewhere normal. const GURL url1(scheme1() + ":foo"); contents->controller()->LoadURL(url1, GURL(), PageTransition::TYPED); contents->CompleteNavigationAsRenderer(0, url1); // Now navigate somewhere with an interstitial. const GURL url2(scheme1() + ":bar"); contents->controller()->LoadURL(url1, GURL(), PageTransition::TYPED); contents->controller()->GetPendingEntry()->set_page_type( NavigationEntry::INTERSTITIAL_PAGE); // At this point the interstitial will be displayed and the load will still // be pending. If the user continues, the load will commit. contents->CompleteNavigationAsRenderer(1, url2); // The page should be a normal page again. EXPECT_EQ(url2, contents->controller()->GetLastCommittedEntry()->url()); EXPECT_EQ(NavigationEntry::NORMAL_PAGE, contents->controller()->GetLastCommittedEntry()->page_type()); } TEST_F(NavigationControllerTest, RemoveEntry) { const GURL url1(scheme1() + ":foo1"); const GURL url2(scheme1() + ":foo2"); const GURL url3(scheme1() + ":foo3"); const GURL url4(scheme1() + ":foo4"); const GURL url5(scheme1() + ":foo5"); const GURL pending_url(scheme1() + ":pending"); const GURL default_url(scheme1() + ":default"); contents->controller()->LoadURL(url1, GURL(), PageTransition::TYPED); contents->CompleteNavigationAsRenderer(0, url1); contents->controller()->LoadURL(url2, GURL(), PageTransition::TYPED); contents->CompleteNavigationAsRenderer(1, url2); contents->controller()->LoadURL(url3, GURL(), PageTransition::TYPED); contents->CompleteNavigationAsRenderer(2, url3); contents->controller()->LoadURL(url4, GURL(), PageTransition::TYPED); contents->CompleteNavigationAsRenderer(3, url4); contents->controller()->LoadURL(url5, GURL(), PageTransition::TYPED); contents->CompleteNavigationAsRenderer(4, url5); // Remove the last entry. contents->controller()->RemoveEntryAtIndex( contents->controller()->GetEntryCount() - 1, default_url); EXPECT_EQ(4, contents->controller()->GetEntryCount()); EXPECT_EQ(3, contents->controller()->GetLastCommittedEntryIndex()); NavigationEntry* pending_entry = contents->controller()->GetPendingEntry(); EXPECT_TRUE(pending_entry && pending_entry->url() == url4); // Add a pending entry. contents->controller()->LoadURL(pending_url, GURL(), PageTransition::TYPED); // Now remove the last entry. contents->controller()->RemoveEntryAtIndex( contents->controller()->GetEntryCount() - 1, default_url); // The pending entry should have been discarded and the last committed entry // removed. EXPECT_EQ(3, contents->controller()->GetEntryCount()); EXPECT_EQ(2, contents->controller()->GetLastCommittedEntryIndex()); pending_entry = contents->controller()->GetPendingEntry(); EXPECT_TRUE(pending_entry && pending_entry->url() == url3); // Remove an entry which is not the last committed one. contents->controller()->RemoveEntryAtIndex(0, default_url); EXPECT_EQ(2, contents->controller()->GetEntryCount()); EXPECT_EQ(1, contents->controller()->GetLastCommittedEntryIndex()); // No navigation should have been initiated since we did not remove the // current entry. EXPECT_FALSE(contents->controller()->GetPendingEntry()); // Remove the 2 remaining entries. contents->controller()->RemoveEntryAtIndex(1, default_url); contents->controller()->RemoveEntryAtIndex(0, default_url); // This should have created a pending default entry. EXPECT_EQ(0, contents->controller()->GetEntryCount()); EXPECT_EQ(-1, contents->controller()->GetLastCommittedEntryIndex()); pending_entry = contents->controller()->GetPendingEntry(); EXPECT_TRUE(pending_entry && pending_entry->url() == default_url); } // Tests the transient entry, making sure it goes away with all navigations. TEST_F(NavigationControllerTest, TransientEntry) { TestNotificationTracker notifications; RegisterForAllNavNotifications(¬ifications, contents->controller()); const GURL url0(scheme1() + ":foo0"); const GURL url1(scheme1() + ":foo1"); const GURL url2(scheme1() + ":foo2"); const GURL url3(scheme1() + ":foo3"); const GURL url4(scheme1() + ":foo4"); const GURL transient_url(scheme1() + ":transient"); contents->controller()->LoadURL(url0, GURL(), PageTransition::TYPED); contents->CompleteNavigationAsRenderer(0, url0); contents->controller()->LoadURL(url1, GURL(), PageTransition::TYPED); contents->CompleteNavigationAsRenderer(1, url1); notifications.Reset(); // Adding a transient with no pending entry. NavigationEntry* transient_entry = new NavigationEntry(TAB_CONTENTS_WEB); transient_entry->set_url(transient_url); contents->controller()->AddTransientEntry(transient_entry); // We should not have received any notifications. EXPECT_EQ(0U, notifications.size()); // Check our state. EXPECT_EQ(transient_url, contents->controller()->GetActiveEntry()->url()); EXPECT_EQ(contents->controller()->GetEntryCount(), 3); EXPECT_EQ(contents->controller()->GetLastCommittedEntryIndex(), 1); EXPECT_EQ(contents->controller()->GetPendingEntryIndex(), -1); EXPECT_TRUE(contents->controller()->GetLastCommittedEntry()); EXPECT_FALSE(contents->controller()->GetPendingEntry()); EXPECT_TRUE(contents->controller()->CanGoBack()); EXPECT_FALSE(contents->controller()->CanGoForward()); EXPECT_EQ(contents->GetMaxPageID(), 1); // Navigate. contents->controller()->LoadURL(url2, GURL(), PageTransition::TYPED); contents->CompleteNavigationAsRenderer(2, url2); // We should have navigated, transient entry should be gone. EXPECT_EQ(url2, contents->controller()->GetActiveEntry()->url()); EXPECT_EQ(contents->controller()->GetEntryCount(), 3); // Add a transient again, then navigate with no pending entry this time. transient_entry = new NavigationEntry(TAB_CONTENTS_WEB); transient_entry->set_url(transient_url); contents->controller()->AddTransientEntry(transient_entry); EXPECT_EQ(transient_url, contents->controller()->GetActiveEntry()->url()); contents->CompleteNavigationAsRenderer(3, url3); // Transient entry should be gone. EXPECT_EQ(url3, contents->controller()->GetActiveEntry()->url()); EXPECT_EQ(contents->controller()->GetEntryCount(), 4); // Initiate a navigation, add a transient then commit navigation. contents->controller()->LoadURL(url4, GURL(), PageTransition::TYPED); transient_entry = new NavigationEntry(TAB_CONTENTS_WEB); transient_entry->set_url(transient_url); contents->controller()->AddTransientEntry(transient_entry); EXPECT_EQ(transient_url, contents->controller()->GetActiveEntry()->url()); contents->CompleteNavigationAsRenderer(4, url4); EXPECT_EQ(url4, contents->controller()->GetActiveEntry()->url()); EXPECT_EQ(contents->controller()->GetEntryCount(), 5); // Add a transient and go back. This should simply remove the transient. transient_entry = new NavigationEntry(TAB_CONTENTS_WEB); transient_entry->set_url(transient_url); contents->controller()->AddTransientEntry(transient_entry); EXPECT_EQ(transient_url, contents->controller()->GetActiveEntry()->url()); EXPECT_TRUE(contents->controller()->CanGoBack()); EXPECT_FALSE(contents->controller()->CanGoForward()); contents->controller()->GoBack(); // Transient entry should be gone. EXPECT_EQ(url4, contents->controller()->GetActiveEntry()->url()); EXPECT_EQ(contents->controller()->GetEntryCount(), 5); contents->CompleteNavigationAsRenderer(3, url3); // Add a transient and go to an entry before the current one. transient_entry = new NavigationEntry(TAB_CONTENTS_WEB); transient_entry->set_url(transient_url); contents->controller()->AddTransientEntry(transient_entry); EXPECT_EQ(transient_url, contents->controller()->GetActiveEntry()->url()); contents->controller()->GoToIndex(1); // The navigation should have been initiated, transient entry should be gone. EXPECT_EQ(url1, contents->controller()->GetActiveEntry()->url()); contents->CompleteNavigationAsRenderer(1, url1); // Add a transient and go to an entry after the current one. transient_entry = new NavigationEntry(TAB_CONTENTS_WEB); transient_entry->set_url(transient_url); contents->controller()->AddTransientEntry(transient_entry); EXPECT_EQ(transient_url, contents->controller()->GetActiveEntry()->url()); contents->controller()->GoToIndex(3); // The navigation should have been initiated, transient entry should be gone. // Because of the transient entry that is removed, going to index 3 makes us // land on url2. EXPECT_EQ(url2, contents->controller()->GetActiveEntry()->url()); contents->CompleteNavigationAsRenderer(2, url2); // Add a transient and go forward. transient_entry = new NavigationEntry(TAB_CONTENTS_WEB); transient_entry->set_url(transient_url); contents->controller()->AddTransientEntry(transient_entry); EXPECT_EQ(transient_url, contents->controller()->GetActiveEntry()->url()); EXPECT_TRUE(contents->controller()->CanGoForward()); contents->controller()->GoForward(); // We should have navigated, transient entry should be gone. EXPECT_EQ(url3, contents->controller()->GetActiveEntry()->url()); contents->CompleteNavigationAsRenderer(3, url3); // Ensure the URLS are correct. EXPECT_EQ(contents->controller()->GetEntryCount(), 5); EXPECT_EQ(contents->controller()->GetEntryAtIndex(0)->url(), url0); EXPECT_EQ(contents->controller()->GetEntryAtIndex(1)->url(), url1); EXPECT_EQ(contents->controller()->GetEntryAtIndex(2)->url(), url2); EXPECT_EQ(contents->controller()->GetEntryAtIndex(3)->url(), url3); EXPECT_EQ(contents->controller()->GetEntryAtIndex(4)->url(), url4); } // Tests that IsInPageNavigation returns appropriate results. Prevents // regression for bug 1126349. TEST_F(NavigationControllerTest, IsInPageNavigation) { // Navigate to URL with no refs. const GURL url("http://www.google.com/home.html"); contents->CompleteNavigationAsRenderer(0, url); // Reloading the page is not an in-page navigation. EXPECT_FALSE(contents->controller()->IsURLInPageNavigation(url)); const GURL other_url("http://www.google.com/add.html"); EXPECT_FALSE(contents->controller()->IsURLInPageNavigation(other_url)); const GURL url_with_ref("http://www.google.com/home.html#my_ref"); EXPECT_TRUE(contents->controller()->IsURLInPageNavigation(url_with_ref)); // Navigate to URL with refs. contents->CompleteNavigationAsRenderer(1, url_with_ref); // Reloading the page is not an in-page navigation. EXPECT_FALSE(contents->controller()->IsURLInPageNavigation(url_with_ref)); EXPECT_FALSE(contents->controller()->IsURLInPageNavigation(url)); EXPECT_FALSE(contents->controller()->IsURLInPageNavigation(other_url)); const GURL other_url_with_ref("http://www.google.com/home.html#my_other_ref"); EXPECT_TRUE(contents->controller()->IsURLInPageNavigation( other_url_with_ref)); } // Some pages can have subframes with the same base URL (minus the reference) as // the main page. Even though this is hard, it can happen, and we don't want // these subframe navigations to affect the toplevel document. They should // instead be ignored. http://crbug.com/5585 TEST_F(NavigationControllerTest, SameSubframe) { // Navigate the main frame. const GURL url("http://www.google.com/"); contents->CompleteNavigationAsRenderer(0, url); // We should be at the first navigation entry. EXPECT_EQ(contents->controller()->GetEntryCount(), 1); EXPECT_EQ(contents->controller()->GetLastCommittedEntryIndex(), 0); // Navigate a subframe that would normally count as in-page. const GURL subframe("http://www.google.com/#"); ViewHostMsg_FrameNavigate_Params params; params.page_id = 0; params.url = subframe; params.transition = PageTransition::AUTO_SUBFRAME; params.should_update_history = false; params.gesture = NavigationGestureAuto; params.is_post = false; NavigationController::LoadCommittedDetails details; EXPECT_FALSE(contents->controller()->RendererDidNavigate(params, &details)); // Nothing should have changed. EXPECT_EQ(contents->controller()->GetEntryCount(), 1); EXPECT_EQ(contents->controller()->GetLastCommittedEntryIndex(), 0); } // A basic test case. Navigates to a single url, and make sure the history // db matches. TEST_F(NavigationControllerHistoryTest, Basic) { contents->controller()->LoadURL(url0, GURL(), PageTransition::LINK); contents->CompleteNavigationAsRenderer(0, url0); GetLastSession(); helper_.AssertSingleWindowWithSingleTab(windows_, 1); helper_.AssertTabEquals(0, 0, 1, *(windows_[0]->tabs[0])); TabNavigation nav1(0, url0, GURL(), string16(), std::string(), PageTransition::LINK); helper_.AssertNavigationEquals(nav1, windows_[0]->tabs[0]->navigations[0]); } // Navigates to three urls, then goes back and make sure the history database // is in sync. TEST_F(NavigationControllerHistoryTest, NavigationThenBack) { contents->CompleteNavigationAsRenderer(0, url0); contents->CompleteNavigationAsRenderer(1, url1); contents->CompleteNavigationAsRenderer(2, url2); contents->controller()->GoBack(); contents->CompleteNavigationAsRenderer(1, url1); GetLastSession(); helper_.AssertSingleWindowWithSingleTab(windows_, 3); helper_.AssertTabEquals(0, 1, 3, *(windows_[0]->tabs[0])); TabNavigation nav(0, url0, GURL(), string16(), std::string(), PageTransition::LINK); helper_.AssertNavigationEquals(nav, windows_[0]->tabs[0]->navigations[0]); nav.set_url(url1); helper_.AssertNavigationEquals(nav, windows_[0]->tabs[0]->navigations[1]); nav.set_url(url2); helper_.AssertNavigationEquals(nav, windows_[0]->tabs[0]->navigations[2]); } // Navigates to three urls, then goes back twice, then loads a new url. TEST_F(NavigationControllerHistoryTest, NavigationPruning) { contents->CompleteNavigationAsRenderer(0, url0); contents->CompleteNavigationAsRenderer(1, url1); contents->CompleteNavigationAsRenderer(2, url2); contents->controller()->GoBack(); contents->CompleteNavigationAsRenderer(1, url1); contents->controller()->GoBack(); contents->CompleteNavigationAsRenderer(0, url0); contents->CompleteNavigationAsRenderer(3, url2); // Now have url0, and url2. GetLastSession(); helper_.AssertSingleWindowWithSingleTab(windows_, 2); helper_.AssertTabEquals(0, 1, 2, *(windows_[0]->tabs[0])); TabNavigation nav(0, url0, GURL(), string16(), std::string(), PageTransition::LINK); helper_.AssertNavigationEquals(nav, windows_[0]->tabs[0]->navigations[0]); nav.set_url(url2); helper_.AssertNavigationEquals(nav, windows_[0]->tabs[0]->navigations[1]); }