// 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. #include "weblayer/browser/persistence/browser_persister.h" #include "base/callback_helpers.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/guid.h" #include "base/path_service.h" #include "base/run_loop.h" #include "base/test/bind.h" #include "base/threading/thread_restrictions.h" #include "build/build_config.h" #include "components/sessions/core/command_storage_manager_test_helper.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/url_loader_interceptor.h" #include "net/base/filename_util.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "testing/gmock/include/gmock/gmock.h" #include "weblayer/browser/browser_impl.h" #include "weblayer/browser/persistence/browser_persister_file_utils.h" #include "weblayer/browser/profile_impl.h" #include "weblayer/browser/tab_impl.h" #include "weblayer/common/weblayer_paths.h" #include "weblayer/public/browser_restore_observer.h" #include "weblayer/public/navigation.h" #include "weblayer/public/navigation_controller.h" #include "weblayer/public/navigation_observer.h" #include "weblayer/public/tab.h" #include "weblayer/shell/browser/shell.h" #include "weblayer/test/interstitial_utils.h" #include "weblayer/test/test_navigation_observer.h" #include "weblayer/test/weblayer_browser_test.h" #include "weblayer/test/weblayer_browser_test_utils.h" namespace weblayer { class BrowserPersisterTestHelper { public: static sessions::CommandStorageManager* GetCommandStorageManager( BrowserPersister* persister) { return persister->command_storage_manager_.get(); } }; namespace { using testing::UnorderedElementsAre; class BrowserNavigationObserverImpl : public BrowserRestoreObserver, public NavigationObserver { public: static void WaitForNewTabToCompleteNavigation(Browser* browser, const GURL& url, size_t tab_to_wait_for = 0) { BrowserNavigationObserverImpl observer(browser, url, tab_to_wait_for); observer.Wait(); } private: BrowserNavigationObserverImpl(Browser* browser, const GURL& url, size_t tab_to_wait_for) : browser_(browser), url_(url), tab_to_wait_for_(tab_to_wait_for) { browser_->AddBrowserRestoreObserver(this); } ~BrowserNavigationObserverImpl() override { tab_->GetNavigationController()->RemoveObserver(this); } void Wait() { run_loop_.Run(); } // NavigationObserver; void NavigationCompleted(Navigation* navigation) override { if (navigation->GetURL() == url_) run_loop_.Quit(); } // BrowserRestoreObserver: void OnRestoreCompleted() override { browser_->RemoveBrowserRestoreObserver(this); ASSERT_LT(tab_to_wait_for_, browser_->GetTabs().size()); ASSERT_EQ(nullptr, tab_); tab_ = browser_->GetTabs()[tab_to_wait_for_]; tab_->GetNavigationController()->AddObserver(this); } Browser* browser_; const GURL& url_; Tab* tab_ = nullptr; const size_t tab_to_wait_for_; std::unique_ptr navigation_observer_; base::RunLoop run_loop_; }; void ShutdownBrowserPersisterAndWait(BrowserImpl* browser) { auto task_runner = sessions::CommandStorageManagerTestHelper( BrowserPersisterTestHelper::GetCommandStorageManager( browser->browser_persister())) .GetBackendTaskRunner(); browser->PrepareForShutdown(); base::RunLoop run_loop; task_runner->PostTaskAndReply(FROM_HERE, base::DoNothing(), run_loop.QuitClosure()); run_loop.Run(); } std::unique_ptr CreateBrowser(ProfileImpl* profile, const std::string& persistence_id) { Browser::PersistenceInfo info; info.id = persistence_id; auto browser = Browser::Create(profile, &info); return std::unique_ptr( static_cast(browser.release())); } } // namespace using BrowserPersisterTest = WebLayerBrowserTest; IN_PROC_BROWSER_TEST_F(BrowserPersisterTest, SingleTab) { ASSERT_TRUE(embedded_test_server()->Start()); std::unique_ptr browser = CreateBrowser(GetProfile(), "x"); Tab* tab = browser->CreateTab(); EXPECT_TRUE(browser->IsRestoringPreviousState()); const GURL url = embedded_test_server()->GetURL("/simple_page.html"); NavigateAndWaitForCompletion(url, tab); ShutdownBrowserPersisterAndWait(browser.get()); tab = nullptr; browser.reset(); browser = CreateBrowser(GetProfile(), "x"); // Should be no tabs while waiting for restore. EXPECT_TRUE(browser->GetTabs().empty()); EXPECT_TRUE(browser->IsRestoringPreviousState()); // Wait for the restore and navigation to complete. BrowserNavigationObserverImpl::WaitForNewTabToCompleteNavigation( browser.get(), url); ASSERT_EQ(1u, browser->GetTabs().size()); EXPECT_EQ(browser->GetTabs()[0], browser->GetActiveTab()); EXPECT_EQ(1, browser->GetTabs()[0] ->GetNavigationController() ->GetNavigationListSize()); EXPECT_FALSE(browser->IsRestoringPreviousState()); } IN_PROC_BROWSER_TEST_F(BrowserPersisterTest, RestoresGuid) { ASSERT_TRUE(embedded_test_server()->Start()); std::unique_ptr browser = CreateBrowser(GetProfile(), "x"); Tab* tab = browser->CreateTab(); const std::string original_guid = tab->GetGuid(); EXPECT_FALSE(original_guid.empty()); EXPECT_TRUE(base::IsValidGUID(original_guid)); const GURL url = embedded_test_server()->GetURL("/simple_page.html"); NavigateAndWaitForCompletion(url, tab); ShutdownBrowserPersisterAndWait(browser.get()); tab = nullptr; browser.reset(); browser = CreateBrowser(GetProfile(), "x"); // Should be no tabs while waiting for restore. EXPECT_TRUE(browser->GetTabs().empty()); // Wait for the restore and navigation to complete. BrowserNavigationObserverImpl::WaitForNewTabToCompleteNavigation( browser.get(), url); ASSERT_EQ(1u, browser->GetTabs().size()); EXPECT_EQ(browser->GetTabs()[0], browser->GetActiveTab()); EXPECT_EQ(original_guid, browser->GetTabs()[0]->GetGuid()); } IN_PROC_BROWSER_TEST_F(BrowserPersisterTest, RestoresData) { ASSERT_TRUE(embedded_test_server()->Start()); std::unique_ptr browser = CreateBrowser(GetProfile(), "x"); Tab* tab = browser->CreateTab(); tab->SetData({{"abc", "efg"}}); const GURL url = embedded_test_server()->GetURL("/simple_page.html"); NavigateAndWaitForCompletion(url, tab); ShutdownBrowserPersisterAndWait(browser.get()); tab = nullptr; browser.reset(); browser = CreateBrowser(GetProfile(), "x"); // Should be no tabs while waiting for restore. EXPECT_TRUE(browser->GetTabs().empty()); // Wait for the restore and navigation to complete. BrowserNavigationObserverImpl::WaitForNewTabToCompleteNavigation( browser.get(), url); ASSERT_EQ(1u, browser->GetTabs().size()); EXPECT_EQ(browser->GetTabs()[0], browser->GetActiveTab()); EXPECT_THAT(browser->GetTabs()[0]->GetData(), UnorderedElementsAre(std::make_pair("abc", "efg"))); } IN_PROC_BROWSER_TEST_F(BrowserPersisterTest, RestoresMostRecentData) { ASSERT_TRUE(embedded_test_server()->Start()); std::unique_ptr browser = CreateBrowser(GetProfile(), "x"); Tab* tab = browser->CreateTab(); tab->SetData({{"xxx", "xxx"}}); const GURL url = embedded_test_server()->GetURL("/simple_page.html"); NavigateAndWaitForCompletion(url, tab); // Make sure the data has been saved, then set different data on the tab. BrowserPersisterTestHelper::GetCommandStorageManager( browser->browser_persister()) ->Save(); tab->SetData({{"abc", "efg"}}); ShutdownBrowserPersisterAndWait(browser.get()); tab = nullptr; browser.reset(); browser = CreateBrowser(GetProfile(), "x"); // Should be no tabs while waiting for restore. EXPECT_TRUE(browser->GetTabs().empty()); // Wait for the restore and navigation to complete. BrowserNavigationObserverImpl::WaitForNewTabToCompleteNavigation( browser.get(), url); ASSERT_EQ(1u, browser->GetTabs().size()); EXPECT_EQ(browser->GetTabs()[0], browser->GetActiveTab()); EXPECT_THAT(browser->GetTabs()[0]->GetData(), UnorderedElementsAre(std::make_pair("abc", "efg"))); } IN_PROC_BROWSER_TEST_F(BrowserPersisterTest, TwoTabs) { ASSERT_TRUE(embedded_test_server()->Start()); std::unique_ptr browser = CreateBrowser(GetProfile(), "x"); Tab* tab1 = browser->CreateTab(); const GURL url1 = embedded_test_server()->GetURL("/simple_page.html"); NavigateAndWaitForCompletion(url1, tab1); Tab* tab2 = browser->CreateTab(); const GURL url2 = embedded_test_server()->GetURL("/simple_page2.html"); NavigateAndWaitForCompletion(url2, tab2); browser->SetActiveTab(tab2); // Shut down the service. ShutdownBrowserPersisterAndWait(browser.get()); tab1 = tab2 = nullptr; browser.reset(); // Recreate the browser and run the assertions twice to ensure we handle // correctly storing state of tabs that need to be reloaded. for (int i = 0; i < 2; ++i) { browser = CreateBrowser(GetProfile(), "x"); // Should be no tabs while waiting for restore. EXPECT_TRUE(browser->GetTabs().empty()) << "iteration " << i; // Wait for the restore and navigation to complete. This waits for the // second tab as that was the active one. BrowserNavigationObserverImpl::WaitForNewTabToCompleteNavigation( browser.get(), url2, 1); ASSERT_EQ(2u, browser->GetTabs().size()) << "iteration " << i; // The first tab shouldn't have loaded yet, as it's not active. EXPECT_TRUE(static_cast(browser->GetTabs()[0]) ->web_contents() ->GetController() .NeedsReload()) << "iteration " << i; EXPECT_EQ(browser->GetTabs()[1], browser->GetActiveTab()) << "iteration " << i; EXPECT_EQ(1, browser->GetTabs()[1] ->GetNavigationController() ->GetNavigationListSize()) << "iteration " << i; ShutdownBrowserPersisterAndWait(browser.get()); } } IN_PROC_BROWSER_TEST_F(BrowserPersisterTest, MoveBetweenBrowsers) { ASSERT_TRUE(embedded_test_server()->Start()); // Create a browser with two tabs. std::unique_ptr browser1 = CreateBrowser(GetProfile(), "x"); Tab* tab1 = browser1->CreateTab(); const GURL url1 = embedded_test_server()->GetURL("/simple_page.html"); NavigateAndWaitForCompletion(url1, tab1); Tab* tab2 = browser1->CreateTab(); const GURL url2 = embedded_test_server()->GetURL("/simple_page2.html"); NavigateAndWaitForCompletion(url2, tab2); browser1->SetActiveTab(tab2); // Create another browser with a single tab. std::unique_ptr browser2 = CreateBrowser(GetProfile(), "y"); Tab* tab3 = browser2->CreateTab(); const GURL url3 = embedded_test_server()->GetURL("/simple_page3.html"); NavigateAndWaitForCompletion(url3, tab3); // Move |tab2| to |browser2|. browser2->AddTab(tab2); browser2->SetActiveTab(tab2); ShutdownBrowserPersisterAndWait(browser1.get()); ShutdownBrowserPersisterAndWait(browser2.get()); tab1 = nullptr; browser1.reset(); tab2 = tab3 = nullptr; browser2.reset(); // Restore the browsers. browser1 = CreateBrowser(GetProfile(), "x"); BrowserNavigationObserverImpl::WaitForNewTabToCompleteNavigation( browser1.get(), url1); ASSERT_EQ(1u, browser1->GetTabs().size()); EXPECT_EQ(1, browser1->GetTabs()[0] ->GetNavigationController() ->GetNavigationListSize()); browser2 = CreateBrowser(GetProfile(), "y"); BrowserNavigationObserverImpl::WaitForNewTabToCompleteNavigation( browser2.get(), url2, 1); ASSERT_EQ(2u, browser2->GetTabs().size()); EXPECT_EQ(1, browser2->GetTabs()[1] ->GetNavigationController() ->GetNavigationListSize()); // As |tab3| isn't active it needs to be loaded. Force that now. TabImpl* restored_tab_3 = static_cast(browser2->GetTabs()[0]); EXPECT_TRUE(restored_tab_3->web_contents()->GetController().NeedsReload()); restored_tab_3->web_contents()->GetController().LoadIfNecessary(); EXPECT_TRUE(content::WaitForLoadStop(restored_tab_3->web_contents())); } class BrowserPersisterTestWithTwoPersistedIds : public WebLayerBrowserTest { public: // WebLayerBrowserTest: void SetUpOnMainThread() override { WebLayerBrowserTest::SetUpOnMainThread(); // Configure two browsers with ids 'x' and 'y'. ASSERT_TRUE(embedded_test_server()->Start()); std::unique_ptr browser1 = CreateBrowser(GetProfile(), "x"); const GURL url1 = embedded_test_server()->GetURL("/simple_page.html"); NavigateAndWaitForCompletion(url1, browser1->CreateTab()); std::unique_ptr browser2 = CreateBrowser(GetProfile(), "y"); const GURL url2 = embedded_test_server()->GetURL("/simple_page3.html"); NavigateAndWaitForCompletion(url2, browser2->CreateTab()); // Shut down the browsers. ShutdownBrowserPersisterAndWait(browser1.get()); browser1.reset(); ShutdownBrowserPersisterAndWait(browser2.get()); browser2.reset(); } }; IN_PROC_BROWSER_TEST_F(BrowserPersisterTestWithTwoPersistedIds, GetBrowserPersistenceIds) { { // Create a file that has the name of a valid persistence file, but has // invalid contents. base::ScopedAllowBlockingForTesting allow_blocking; base::WriteFile(BuildPathForBrowserPersister( GetProfile()->GetBrowserPersisterDataBaseDir(), "z"), "a bogus persistence file"); } base::RunLoop run_loop; base::flat_set persistence_ids; GetProfile()->GetBrowserPersistenceIds( base::BindLambdaForTesting([&](base::flat_set ids) { persistence_ids = std::move(ids); run_loop.Quit(); })); run_loop.Run(); ASSERT_EQ(2u, persistence_ids.size()); EXPECT_TRUE(persistence_ids.contains("x")); EXPECT_TRUE(persistence_ids.contains("y")); } IN_PROC_BROWSER_TEST_F(BrowserPersisterTestWithTwoPersistedIds, RemoveBrowserPersistenceStorage) { base::FilePath file_path1 = BuildPathForBrowserPersister( GetProfile()->GetBrowserPersisterDataBaseDir(), "x"); base::FilePath file_path2 = BuildPathForBrowserPersister( GetProfile()->GetBrowserPersisterDataBaseDir(), "y"); { base::ScopedAllowBlockingForTesting allow_blocking; ASSERT_TRUE(base::PathExists(file_path1)); ASSERT_TRUE(base::PathExists(file_path2)); } base::RunLoop run_loop; base::flat_set persistence_ids; persistence_ids.insert("x"); persistence_ids.insert("y"); GetProfile()->RemoveBrowserPersistenceStorage( base::BindLambdaForTesting([&](bool result) { EXPECT_TRUE(result); run_loop.Quit(); }), std::move(persistence_ids)); run_loop.Run(); { base::ScopedAllowBlockingForTesting allow_blocking; EXPECT_FALSE(base::PathExists(file_path1)); EXPECT_FALSE(base::PathExists(file_path2)); } } } // namespace weblayer