blob: 2106e9d49c2763e220503b7db291a830d47a6d91 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <utility>
#include <vector>
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/history/history_tab_helper.h"
#include "chrome/browser/history/history_test_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/search_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/common/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/search_engines/template_url_service.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/webui_config_map.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_frame_navigation_observer.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "ui/webui/untrusted_web_ui_browsertest_util.h"
#include "url/gurl.h"
using content::BrowserThread;
class HistoryBrowserTest : public InProcessBrowserTest {
protected:
HistoryBrowserTest() {
// TODO(crbug.com/1394910): Use HTTPS URLs in tests to avoid having to
// disable this feature.
feature_list_.InitAndDisableFeature(features::kHttpsUpgrades);
test_server_.ServeFilesFromSourceDirectory(GetChromeTestDataDir());
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(test_server_.Start());
}
PrefService* GetPrefs() {
return GetProfile()->GetPrefs();
}
Profile* GetProfile() {
return browser()->profile();
}
std::vector<GURL> GetHistoryContents() {
ui_test_utils::HistoryEnumerator enumerator(GetProfile());
return enumerator.urls();
}
GURL GetTestUrl() {
return ui_test_utils::GetTestUrl(
base::FilePath(base::FilePath::kCurrentDirectory),
base::FilePath(FILE_PATH_LITERAL("title2.html")));
}
void ExpectEmptyHistory() {
std::vector<GURL> urls(GetHistoryContents());
EXPECT_EQ(0U, urls.size());
}
GURL GetTestFileURL(const char* filename) {
return test_server_.GetURL(std::string("/History/") + filename);
}
void LoadAndWaitForURL(const GURL& url) {
std::u16string expected_title(u"OK");
content::TitleWatcher title_watcher(
browser()->tab_strip_model()->GetActiveWebContents(), expected_title);
title_watcher.AlsoWaitForTitle(u"FAIL");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
}
void LoadAndWaitForFile(const char* filename) {
LoadAndWaitForURL(GetTestFileURL(filename));
}
bool HistoryContainsURL(const GURL& url) { return QueryURL(url).success; }
history::URLRow LookUpURLInHistory(const GURL& url) {
return QueryURL(url).row;
}
history::QueryURLResult QueryURL(const GURL& url) {
history::QueryURLResult query_url_result;
base::RunLoop run_loop;
base::CancelableTaskTracker tracker;
HistoryServiceFactory::GetForProfile(browser()->profile(),
ServiceAccessType::EXPLICIT_ACCESS)
->QueryURL(
url, true,
base::BindLambdaForTesting([&](history::QueryURLResult result) {
query_url_result = std::move(result);
run_loop.Quit();
}),
&tracker);
run_loop.Run();
return query_url_result;
}
std::vector<history::AnnotatedVisit> GetAllAnnotatedVisits() {
std::vector<history::AnnotatedVisit> annotated_visits;
history::HistoryService* history_service =
HistoryServiceFactory::GetForProfile(
browser()->profile(), ServiceAccessType::EXPLICIT_ACCESS);
base::CancelableTaskTracker tracker;
history::QueryOptions options;
options.duplicate_policy = history::QueryOptions::KEEP_ALL_DUPLICATES;
base::RunLoop run_loop;
history_service->GetAnnotatedVisits(
options,
/*compute_redirect_chain_start_properties=*/true,
base::BindLambdaForTesting(
[&](std::vector<history::AnnotatedVisit> visits) {
annotated_visits = std::move(visits);
run_loop.Quit();
}),
&tracker);
run_loop.Run();
return annotated_visits;
}
private:
// Callback for HistoryService::QueryURL.
void SaveResultAndQuit(bool* success_out,
history::URLRow* url_row_out,
base::OnceClosure closure,
bool success,
const history::URLRow& url_row,
const history::VisitVector& visit_vector) {
if (success_out)
*success_out = success;
if (url_row_out)
*url_row_out = url_row;
std::move(closure).Run();
}
base::test::ScopedFeatureList feature_list_;
net::EmbeddedTestServer test_server_;
};
// Test that the browser history is saved (default setting).
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, SavingHistoryEnabled) {
EXPECT_FALSE(GetPrefs()->GetBoolean(prefs::kSavingBrowserHistoryDisabled));
EXPECT_TRUE(HistoryServiceFactory::GetForProfile(
GetProfile(), ServiceAccessType::EXPLICIT_ACCESS));
EXPECT_TRUE(HistoryServiceFactory::GetForProfile(
GetProfile(), ServiceAccessType::IMPLICIT_ACCESS));
ui_test_utils::WaitForHistoryToLoad(HistoryServiceFactory::GetForProfile(
browser()->profile(), ServiceAccessType::EXPLICIT_ACCESS));
ExpectEmptyHistory();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
WaitForHistoryBackendToRun(GetProfile());
{
std::vector<GURL> urls(GetHistoryContents());
ASSERT_EQ(1U, urls.size());
EXPECT_EQ(GetTestUrl().spec(), urls[0].spec());
}
}
// Test that disabling saving browser history really works.
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, SavingHistoryDisabled) {
GetPrefs()->SetBoolean(prefs::kSavingBrowserHistoryDisabled, true);
EXPECT_TRUE(HistoryServiceFactory::GetForProfile(
GetProfile(), ServiceAccessType::EXPLICIT_ACCESS));
EXPECT_FALSE(HistoryServiceFactory::GetForProfile(
GetProfile(), ServiceAccessType::IMPLICIT_ACCESS));
ui_test_utils::WaitForHistoryToLoad(HistoryServiceFactory::GetForProfile(
browser()->profile(), ServiceAccessType::EXPLICIT_ACCESS));
ExpectEmptyHistory();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
WaitForHistoryBackendToRun(GetProfile());
ExpectEmptyHistory();
}
// Test that changing the pref takes effect immediately
// when the browser is running.
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, SavingHistoryEnabledThenDisabled) {
EXPECT_FALSE(GetPrefs()->GetBoolean(prefs::kSavingBrowserHistoryDisabled));
ui_test_utils::WaitForHistoryToLoad(HistoryServiceFactory::GetForProfile(
browser()->profile(), ServiceAccessType::EXPLICIT_ACCESS));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
WaitForHistoryBackendToRun(GetProfile());
{
std::vector<GURL> urls(GetHistoryContents());
ASSERT_EQ(1U, urls.size());
EXPECT_EQ(GetTestUrl().spec(), urls[0].spec());
}
GetPrefs()->SetBoolean(prefs::kSavingBrowserHistoryDisabled, true);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
WaitForHistoryBackendToRun(GetProfile());
{
// No additional entries should be present in the history.
std::vector<GURL> urls(GetHistoryContents());
ASSERT_EQ(1U, urls.size());
EXPECT_EQ(GetTestUrl().spec(), urls[0].spec());
}
}
// Test that changing the pref takes effect immediately
// when the browser is running.
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, SavingHistoryDisabledThenEnabled) {
GetPrefs()->SetBoolean(prefs::kSavingBrowserHistoryDisabled, true);
ui_test_utils::WaitForHistoryToLoad(HistoryServiceFactory::GetForProfile(
browser()->profile(), ServiceAccessType::EXPLICIT_ACCESS));
ExpectEmptyHistory();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
WaitForHistoryBackendToRun(GetProfile());
ExpectEmptyHistory();
GetPrefs()->SetBoolean(prefs::kSavingBrowserHistoryDisabled, false);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
WaitForHistoryBackendToRun(GetProfile());
{
std::vector<GURL> urls(GetHistoryContents());
ASSERT_EQ(1U, urls.size());
EXPECT_EQ(GetTestUrl().spec(), urls[0].spec());
}
}
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, VerifyHistoryLength1) {
// Test the history length for the following page transitions.
// -open-> Page 1.
LoadAndWaitForFile("history_length_test_page_1.html");
}
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, VerifyHistoryLength2) {
// Test the history length for the following page transitions.
// -open-> Page 2 -redirect-> Page 3.
LoadAndWaitForFile("history_length_test_page_2.html");
}
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, VerifyHistoryLength3) {
// Test the history length for the following page transitions.
// -open-> Page 1 -> open Page 2 -redirect Page 3. open Page 4
// -navigate_backward-> Page 3 -navigate_backward->Page 1
// -navigate_forward-> Page 3 -navigate_forward-> Page 4
LoadAndWaitForFile("history_length_test_page_1.html");
LoadAndWaitForFile("history_length_test_page_2.html");
LoadAndWaitForFile("history_length_test_page_4.html");
}
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest,
ConsiderRedirectAfterGestureAsUserInitiated) {
// Test the history length for the following page transition.
//
// -open-> Page 11 -slow_redirect-> Page 12.
//
// If redirect occurs after a user gesture, e.g., mouse click, the
// redirect is more likely to be user-initiated rather than automatic.
// Therefore, Page 11 should be in the history in addition to Page 12.
LoadAndWaitForFile("history_length_test_page_11.html");
content::SimulateMouseClick(
browser()->tab_strip_model()->GetActiveWebContents(), 0,
blink::WebMouseEvent::Button::kLeft);
LoadAndWaitForFile("history_length_test_page_11.html");
}
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest,
ConsiderSlowRedirectAsUserInitiated) {
// Test the history length for the following page transition.
//
// -open-> Page 21 -redirect-> Page 22.
//
// If redirect occurs more than 5 seconds later after the page is loaded,
// the redirect is likely to be user-initiated.
// Therefore, Page 21 should be in the history in addition to Page 22.
LoadAndWaitForFile("history_length_test_page_21.html");
}
// TODO(crbug.com/22111): Disabled because of flakiness and because for a while
// history didn't support #q=searchTerm. Now that it does support these type
// of URLs (crbug.com/619799), this test could be re-enabled if somebody goes
// through the effort to wait for the various stages of the page loading.
// The loading strategy of the new, Polymer version of chrome://history is
// sophisticated and multi-part, so we'd need to wait on or ensure a few things
// are happening before running the test.
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, DISABLED_HistorySearchXSS) {
GURL url(std::string(chrome::kChromeUIHistoryURL) +
"#q=%3Cimg%20src%3Dx%3Ax%20onerror%3D%22document.title%3D'XSS'%22%3E");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
// Mainly, this is to ensure we send a synchronous message to the renderer
// so that we're not susceptible (less susceptible?) to a race condition.
// Should a race condition ever trigger, it won't result in flakiness.
int num = ui_test_utils::FindInPage(
browser()->tab_strip_model()->GetActiveWebContents(), u"<img", true, true,
nullptr, nullptr);
EXPECT_GT(num, 0);
EXPECT_EQ(u"History",
browser()->tab_strip_model()->GetActiveWebContents()->GetTitle());
}
// Verify that history persists after session restart.
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, PRE_HistoryPersists) {
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
std::vector<GURL> urls(GetHistoryContents());
ASSERT_EQ(1u, urls.size());
ASSERT_EQ(GetTestUrl(), urls[0]);
}
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, HistoryPersists) {
std::vector<GURL> urls(GetHistoryContents());
ASSERT_EQ(1u, urls.size());
ASSERT_EQ(GetTestUrl(), urls[0]);
}
// Invalid URLs should not go in history.
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, InvalidURLNoHistory) {
GURL non_existant = ui_test_utils::GetTestUrl(
base::FilePath().AppendASCII("History"),
base::FilePath().AppendASCII("non_existant_file.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), non_existant));
ExpectEmptyHistory();
}
// URLs with special schemes should not go in history.
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, InvalidSchemeNoHistory) {
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
ExpectEmptyHistory();
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL("view-source:about:blank")));
ExpectEmptyHistory();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("chrome://about")));
ExpectEmptyHistory();
content::WebUIConfigMap::GetInstance().AddUntrustedWebUIConfig(
std::make_unique<ui::TestUntrustedWebUIConfig>("test-host"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), GURL("chrome-untrusted://test-host/title1.html")));
ExpectEmptyHistory();
}
// New tab page should not show up in history.
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, NewTabNoHistory) {
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(),
GURL(chrome::kChromeUINewTabURL)));
ExpectEmptyHistory();
}
// Incognito browsing should not show up in history.
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, IncognitoNoHistory) {
ASSERT_TRUE(
ui_test_utils::NavigateToURL(CreateIncognitoBrowser(), GetTestUrl()));
ExpectEmptyHistory();
}
// Multiple navigations to the same url should have a single history.
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, NavigateMultiTimes) {
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
std::vector<GURL> urls(GetHistoryContents());
ASSERT_EQ(1u, urls.size());
ASSERT_EQ(GetTestUrl(), urls[0]);
}
// Verify history with multiple windows and tabs.
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, MultiTabsWindowsHistory) {
GURL url1 = GetTestUrl();
GURL url2 = ui_test_utils::GetTestUrl(
base::FilePath(), base::FilePath(FILE_PATH_LITERAL("title1.html")));
GURL url3 = ui_test_utils::GetTestUrl(
base::FilePath(), base::FilePath(FILE_PATH_LITERAL("title3.html")));
GURL url4 = ui_test_utils::GetTestUrl(
base::FilePath(), base::FilePath(FILE_PATH_LITERAL("simple.html")));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url1));
Browser* browser2 = CreateBrowser(browser()->profile());
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser2, url2));
ui_test_utils::NavigateToURLWithDisposition(
browser2, url3, WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
ui_test_utils::NavigateToURLWithDisposition(
browser2, url4, WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
std::vector<GURL> urls(GetHistoryContents());
ASSERT_EQ(4u, urls.size());
ASSERT_EQ(url4, urls[0]);
ASSERT_EQ(url3, urls[1]);
ASSERT_EQ(url2, urls[2]);
ASSERT_EQ(url1, urls[3]);
}
// Downloaded URLs should not show up in history.
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, DownloadNoHistory) {
GURL download_url = ui_test_utils::GetTestUrl(
base::FilePath().AppendASCII("downloads"),
base::FilePath().AppendASCII("a_zip_file.zip"));
ui_test_utils::DownloadURL(browser(), download_url);
ExpectEmptyHistory();
}
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, HistoryRemovalRemovesTemplateURL) {
constexpr char origin[] = "foo.com";
constexpr char16_t origin16[] = u"foo.com";
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(origin, "/title3.html"));
// Creating keyword shortcut manually.
TemplateURLData data;
data.SetShortName(origin16);
data.SetKeyword(u"keyword");
data.SetURL(url.spec());
data.safe_for_autoreplace = true;
// Adding url to the history.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
WaitForHistoryBackendToRun(GetProfile());
EXPECT_TRUE(HistoryContainsURL(url));
// Adding the keyword in the template URL.
TemplateURLService* model =
TemplateURLServiceFactory::GetForProfile(browser()->profile());
// Waiting for the model to load.
search_test_utils::WaitForTemplateURLServiceToLoad(model);
TemplateURL* t_url = model->Add(std::make_unique<TemplateURL>(data));
EXPECT_EQ(t_url, model->GetTemplateURLForHost(origin));
auto* history_service = HistoryServiceFactory::GetForProfile(
browser()->profile(), ServiceAccessType::EXPLICIT_ACCESS);
history_service->DeleteURLs({url});
// The DeleteURL method runs an asynchronous task
// internally that deletes the data from db. The test
// must wait for the async delete to be finished in order to
// check if the delete was indeed successful. We emulate
// the wait by calling another method |FlushForTest|
// in the history service. Since, we know that that
// history processeses tasks synchronously, so when the
// callback is run for |FlushForTest| we know the deletion
// should have finished.
base::RunLoop run_loop;
history_service->FlushForTest(run_loop.QuitClosure());
run_loop.Run();
EXPECT_FALSE(model->GetTemplateURLForHost(origin));
}
namespace {
// Grabs the RenderFrameHost for the frame navigating to the given URL.
class RenderFrameHostGrabber : public content::WebContentsObserver {
public:
RenderFrameHostGrabber(content::WebContents* web_contents, const GURL& url)
: WebContentsObserver(web_contents), url_(url) {}
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override {
if (navigation_handle->GetURL() == url_) {
render_frame_host_ = navigation_handle->GetRenderFrameHost();
run_loop_.Quit();
}
}
void Wait() { run_loop_.Run(); }
content::RenderFrameHost* render_frame_host() { return render_frame_host_; }
private:
GURL url_;
raw_ptr<content::RenderFrameHost> render_frame_host_ = nullptr;
base::RunLoop run_loop_;
};
// Simulates user clicking on a link inside the frame.
// TODO(jam): merge with content/test/content_browser_test_utils_internal.h
void NavigateFrameToURL(content::RenderFrameHost* rfh, const GURL& url) {
content::TestFrameNavigationObserver observer(rfh);
content::NavigationController::LoadURLParams params(url);
params.transition_type = ui::PAGE_TRANSITION_LINK;
params.frame_tree_node_id = rfh->GetFrameTreeNodeId();
content::WebContents::FromRenderFrameHost(rfh)
->GetController()
.LoadURLWithParams(params);
observer.Wait();
}
} // namespace
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, Subframe) {
// Initial subframe requests should not show up in history.
GURL main_page = ui_test_utils::GetTestUrl(
base::FilePath().AppendASCII("History"),
base::FilePath().AppendASCII("page_with_iframe.html"));
GURL initial_subframe =
ui_test_utils::GetTestUrl(base::FilePath().AppendASCII("History"),
base::FilePath().AppendASCII("target.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_page));
content::RenderFrameHost* frame = ChildFrameAt(browser()
->tab_strip_model()
->GetActiveWebContents()
->GetPrimaryMainFrame(),
0);
ASSERT_TRUE(frame);
ASSERT_TRUE(HistoryContainsURL(main_page));
ASSERT_FALSE(HistoryContainsURL(initial_subframe));
// User-initiated subframe navigations should show up in history.
GURL manual_subframe =
ui_test_utils::GetTestUrl(base::FilePath().AppendASCII("History"),
base::FilePath().AppendASCII("landing.html"));
NavigateFrameToURL(frame, manual_subframe);
ASSERT_TRUE(HistoryContainsURL(manual_subframe));
// After navigation, the current RenderFrameHost may change.
frame = ChildFrameAt(browser()
->tab_strip_model()
->GetActiveWebContents()
->GetPrimaryMainFrame(),
0);
// Page-initiated location.replace subframe navigations should not show up in
// history.
std::string script = "location.replace('form.html')";
content::TestFrameNavigationObserver observer(frame);
EXPECT_TRUE(ExecJs(frame, script));
observer.Wait();
GURL auto_subframe =
ui_test_utils::GetTestUrl(base::FilePath().AppendASCII("History"),
base::FilePath().AppendASCII("form.html"));
ASSERT_FALSE(HistoryContainsURL(auto_subframe));
}
// HTTP meta-refresh redirects should only have an entry for the landing page.
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, RedirectHistory) {
GURL redirector = ui_test_utils::GetTestUrl(
base::FilePath().AppendASCII("History"),
base::FilePath().AppendASCII("redirector.html"));
GURL landing_url = ui_test_utils::GetTestUrl(
base::FilePath().AppendASCII("History"),
base::FilePath().AppendASCII("landing.html"));
ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
browser(), redirector, 2);
ASSERT_EQ(
landing_url,
browser()->tab_strip_model()->GetActiveWebContents()->GetVisibleURL());
std::vector<GURL> urls(GetHistoryContents());
ASSERT_EQ(1u, urls.size());
ASSERT_EQ(landing_url, urls[0]);
}
// Cross-site HTTP meta-refresh redirects should only have an entry for the
// landing page.
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, CrossSiteRedirectHistory) {
// Use the default embedded_test_server() for this test in order to support a
// cross-site redirect.
ASSERT_TRUE(embedded_test_server()->Start());
GURL landing_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
GURL redirector(embedded_test_server()->GetURL(
"bar.com", "/client-redirect?" + landing_url.spec()));
ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(browser(),
redirector, 2);
ASSERT_EQ(
landing_url,
browser()->tab_strip_model()->GetActiveWebContents()->GetVisibleURL());
std::vector<GURL> urls(GetHistoryContents());
ASSERT_EQ(1u, urls.size());
ASSERT_EQ(landing_url, urls[0]);
}
// Verify that navigation brings current page to top of history list.
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, NavigateBringPageToTop) {
GURL url1 = GetTestUrl();
GURL url2 = ui_test_utils::GetTestUrl(
base::FilePath(), base::FilePath(FILE_PATH_LITERAL("title3.html")));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url1));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url2));
std::vector<GURL> urls(GetHistoryContents());
ASSERT_EQ(2u, urls.size());
ASSERT_EQ(url2, urls[0]);
ASSERT_EQ(url1, urls[1]);
}
// Verify that reloading a page brings it to top of history list.
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, ReloadBringPageToTop) {
GURL url1 = GetTestUrl();
GURL url2 = ui_test_utils::GetTestUrl(
base::FilePath(), base::FilePath(FILE_PATH_LITERAL("title3.html")));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url1));
ui_test_utils::NavigateToURLWithDisposition(
browser(), url2, WindowOpenDisposition::NEW_BACKGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
std::vector<GURL> urls(GetHistoryContents());
ASSERT_EQ(2u, urls.size());
ASSERT_EQ(url2, urls[0]);
ASSERT_EQ(url1, urls[1]);
content::WebContents* tab =
browser()->tab_strip_model()->GetActiveWebContents();
tab->GetController().Reload(content::ReloadType::NORMAL, false);
EXPECT_TRUE(content::WaitForLoadStop(tab));
urls = GetHistoryContents();
ASSERT_EQ(2u, urls.size());
ASSERT_EQ(url1, urls[0]);
ASSERT_EQ(url2, urls[1]);
}
// Verify that back/forward brings current page to top of history list.
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, BackForwardBringPageToTop) {
GURL url1 = GetTestUrl();
GURL url2 = ui_test_utils::GetTestUrl(
base::FilePath(), base::FilePath(FILE_PATH_LITERAL("title3.html")));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url1));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url2));
content::WebContents* tab =
browser()->tab_strip_model()->GetActiveWebContents();
chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
EXPECT_TRUE(content::WaitForLoadStop(tab));
std::vector<GURL> urls(GetHistoryContents());
ASSERT_EQ(2u, urls.size());
ASSERT_EQ(url1, urls[0]);
ASSERT_EQ(url2, urls[1]);
chrome::GoForward(browser(), WindowOpenDisposition::CURRENT_TAB);
EXPECT_TRUE(content::WaitForLoadStop(tab));
urls = GetHistoryContents();
ASSERT_EQ(2u, urls.size());
ASSERT_EQ(url2, urls[0]);
ASSERT_EQ(url1, urls[1]);
}
// Verify that pushState() correctly sets the title of the second history entry.
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, PushStateSetsTitle) {
// Use the default embedded_test_server() for this test because pushState
// requires a real, non-file URL.
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL("foo.com", "/title3.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
std::u16string title = web_contents->GetTitle();
// Do a pushState to create a new navigation entry and a new history entry.
ASSERT_TRUE(
content::ExecJs(web_contents, "history.pushState({},'','test.html')"));
EXPECT_TRUE(content::WaitForLoadStop(web_contents));
// This should result in two history entries.
std::vector<GURL> urls(GetHistoryContents());
ASSERT_EQ(2u, urls.size());
EXPECT_NE(urls[0], urls[1]);
// History entry [0] is the newest one.
history::URLRow row0 = LookUpURLInHistory(urls[0]);
EXPECT_EQ(title, row0.title());
history::URLRow row1 = LookUpURLInHistory(urls[1]);
EXPECT_EQ(title, row1.title());
}
// Ensure that commits unrelated to the pending entry do not cause incorrect
// updates to history.
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, BeforeUnloadCommitDuringPending) {
// Use the default embedded_test_server() for this test because replaceState
// requires a real, non-file URL.
ASSERT_TRUE(embedded_test_server()->Start());
GURL url1(embedded_test_server()->GetURL("foo.com", "/title3.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url1));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
std::u16string title1 = web_contents->GetTitle();
// Create a beforeunload handler that does a replaceState during navigation,
// unrelated to the destination URL (similar to Twitter).
ASSERT_TRUE(content::ExecJs(web_contents,
"window.onbeforeunload = function() {"
"history.replaceState({},'','test.html');"
"};"));
GURL url2(embedded_test_server()->GetURL("foo.com", "/test.html"));
// Start a cross-site navigation to trigger the beforeunload, but don't let
// the new URL commit yet.
GURL url3(embedded_test_server()->GetURL("bar.com", "/title2.html"));
content::TestNavigationManager manager(web_contents, url3);
web_contents->GetController().LoadURL(
url3, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
EXPECT_TRUE(manager.WaitForRequestStart());
// The beforeunload commit should happen before request start, which should
// result in two history entries, with the newest in index 0. urls[0] was
// incorrectly url3 in https://crbug.com/956208.
{
std::vector<GURL> urls(GetHistoryContents());
ASSERT_EQ(2u, urls.size());
EXPECT_EQ(url2, urls[0]);
EXPECT_EQ(url1, urls[1]);
}
// After the pending navigation commits and the new title arrives, there
// should be another row with the new URL and title.
ASSERT_TRUE(manager.WaitForNavigationFinished());
EXPECT_TRUE(content::WaitForLoadStop(web_contents));
std::u16string title3 = web_contents->GetTitle();
EXPECT_NE(title1, title3);
{
std::vector<GURL> urls(GetHistoryContents());
ASSERT_EQ(3u, urls.size());
EXPECT_EQ(url3, urls[0]);
history::URLRow row0 = LookUpURLInHistory(urls[0]);
EXPECT_EQ(title3, row0.title());
EXPECT_EQ(url2, urls[1]);
history::URLRow row1 = LookUpURLInHistory(urls[1]);
EXPECT_EQ(title1, row1.title());
EXPECT_EQ(url1, urls[2]);
history::URLRow row2 = LookUpURLInHistory(urls[2]);
EXPECT_EQ(title1, row2.title());
}
}
// Verify that submitting form adds target page to history list.
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, SubmitFormAddsTargetPage) {
GURL form = ui_test_utils::GetTestUrl(
base::FilePath().AppendASCII("History"),
base::FilePath().AppendASCII("form.html"));
GURL target = ui_test_utils::GetTestUrl(
base::FilePath().AppendASCII("History"),
base::FilePath().AppendASCII("target.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), form));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
std::u16string expected_title(u"Target Page");
content::TitleWatcher title_watcher(
browser()->tab_strip_model()->GetActiveWebContents(), expected_title);
ASSERT_TRUE(content::ExecJs(web_contents,
"document.getElementById('form').submit()"));
EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
std::vector<GURL> urls(GetHistoryContents());
ASSERT_EQ(2u, urls.size());
ASSERT_EQ(target, urls[0]);
ASSERT_EQ(form, urls[1]);
}
// Verify history shortcut opens only one history tab per window. Also, make
// sure that existing history tab is activated.
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, OneHistoryTabPerWindow) {
GURL history_url(chrome::kChromeUIHistoryURL);
// Even after navigate completes, the currently-active tab title is
// 'Loading...' for a brief time while the history page loads.
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
std::u16string expected_title(u"History");
content::TitleWatcher title_watcher(web_contents, expected_title);
chrome::ExecuteCommand(browser(), IDC_SHOW_HISTORY);
EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(url::kAboutBlankURL),
WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
chrome::ExecuteCommand(browser(), IDC_SHOW_HISTORY);
content::WebContents* active_web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_EQ(web_contents, active_web_contents);
ASSERT_EQ(history_url, active_web_contents->GetVisibleURL());
content::WebContents* second_tab =
browser()->tab_strip_model()->GetWebContentsAt(1);
ASSERT_NE(history_url, second_tab->GetVisibleURL());
}
// Verifies history.replaceState() to the same url without a user gesture does
// not log a visit.
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, ReplaceStateSamePageIsNotRecorded) {
// Use the default embedded_test_server() for this test because replaceState
// requires a real, non-file URL.
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL("foo.com", "/title3.html"));
NavigateParams params(browser(), url, ui::PAGE_TRANSITION_TYPED);
params.user_gesture = false;
ui_test_utils::NavigateToURL(&params);
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
// Do a replaceState() to create a new navigation entry.
ASSERT_TRUE(
content::ExecJs(web_contents, "history.replaceState({foo: 'bar'},'')"));
content::WaitForLoadStop(web_contents);
// Because there was no user gesture and the url did not change, there should
// be a single url with a single visit.
std::vector<GURL> urls(GetHistoryContents());
ASSERT_EQ(1u, urls.size());
EXPECT_EQ(url, urls[0]);
history::QueryURLResult url_result = QueryURL(url);
EXPECT_EQ(1u, url_result.visits.size());
}
IN_PROC_BROWSER_TEST_F(HistoryBrowserTest, VisitAnnotations) {
ui_test_utils::WaitForHistoryToLoad(HistoryServiceFactory::GetForProfile(
browser()->profile(), ServiceAccessType::EXPLICIT_ACCESS));
// Navigate to some arbitrary page.
GURL url = GetTestFileURL("landing.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
// A visit should have been written to the DB.
std::vector<history::AnnotatedVisit> annotated_visits =
GetAllAnnotatedVisits();
ASSERT_EQ(annotated_visits.size(), 1u);
// ...and its on-visit annotation fields should be populated already.
history::AnnotatedVisit ongoing_visit = annotated_visits[0];
EXPECT_NE(ongoing_visit.context_annotations.on_visit.browser_type,
history::VisitContextAnnotations::BrowserType::kUnknown);
EXPECT_TRUE(ongoing_visit.context_annotations.on_visit.window_id.is_valid());
EXPECT_TRUE(ongoing_visit.context_annotations.on_visit.tab_id.is_valid());
EXPECT_NE(ongoing_visit.context_annotations.on_visit.task_id, -1);
EXPECT_GT(ongoing_visit.context_annotations.on_visit.response_code, 0);
// Navigate to a different page to "finish" the visit.
GURL url2 = GetTestFileURL("target.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url2));
std::vector<history::AnnotatedVisit> annotated_visits2 =
GetAllAnnotatedVisits();
ASSERT_EQ(annotated_visits2.size(), 2u);
// The most recent visit is returned first, so the second visit from this
// query should match the first visit from the previous query.
history::AnnotatedVisit finished_visit = annotated_visits2[1];
ASSERT_EQ(finished_visit.visit_row.visit_id,
ongoing_visit.visit_row.visit_id);
// The on-visit fields should be unchanged.
EXPECT_EQ(finished_visit.context_annotations.on_visit,
ongoing_visit.context_annotations.on_visit);
// The on-close fields should also be populated too now.
EXPECT_NE(finished_visit.context_annotations.page_end_reason, 0);
EXPECT_GT(finished_visit.context_annotations.total_foreground_duration,
base::Seconds(0));
}
// MPArch means Multiple Page Architecture, each WebContents may have additional
// FrameTrees which will have their own associated Page.
class HistoryMPArchBrowserTest : public HistoryBrowserTest {
public:
HistoryMPArchBrowserTest() = default;
~HistoryMPArchBrowserTest() override = default;
HistoryMPArchBrowserTest(const HistoryMPArchBrowserTest&) = delete;
HistoryMPArchBrowserTest& operator=(const HistoryMPArchBrowserTest&) = delete;
void SetUpOnMainThread() override {
ASSERT_TRUE(embedded_test_server()->Start());
}
};
// For tests which use prerender.
class HistoryPrerenderBrowserTest : public HistoryMPArchBrowserTest {
public:
HistoryPrerenderBrowserTest()
: prerender_helper_(
base::BindRepeating(&HistoryPrerenderBrowserTest::web_contents,
base::Unretained(this))) {}
void SetUp() override {
prerender_helper_.SetUp(embedded_test_server());
HistoryMPArchBrowserTest::SetUp();
}
content::test::PrerenderTestHelper& prerender_helper() {
return prerender_helper_;
}
content::WebContents* web_contents() {
return browser()->tab_strip_model()->GetActiveWebContents();
}
private:
content::test::PrerenderTestHelper prerender_helper_;
};
// Verify a prerendered page is not recorded if we do not activate it.
IN_PROC_BROWSER_TEST_F(HistoryPrerenderBrowserTest,
PrerenderPageIsNotRecordedUnlessActivated) {
const GURL initial_url = embedded_test_server()->GetURL("/empty.html");
const GURL prerendering_url =
embedded_test_server()->GetURL("/empty.html?prerender");
// Navigate to an initial page.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
// Start a prerender, but we don't activate it.
const int host_id = prerender_helper().AddPrerender(prerendering_url);
ASSERT_NE(host_id, content::RenderFrameHost::kNoFrameTreeNodeId);
// The prerendered page should not be recorded.
EXPECT_THAT(GetHistoryContents(), testing::ElementsAre(initial_url));
}
// Verify a prerendered page is recorded if we activate it.
IN_PROC_BROWSER_TEST_F(HistoryPrerenderBrowserTest,
PrerenderPageIsRecordedIfActivated) {
const GURL initial_url = embedded_test_server()->GetURL("/empty.html");
const GURL prerendering_url =
embedded_test_server()->GetURL("/empty.html?prerender");
// Navigate to an initial page.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
// Start a prerender.
const int host_id = prerender_helper().AddPrerender(prerendering_url);
ASSERT_NE(host_id, content::RenderFrameHost::kNoFrameTreeNodeId);
// Activate.
prerender_helper().NavigatePrimaryPage(prerendering_url);
ASSERT_EQ(prerendering_url, web_contents()->GetLastCommittedURL());
// The prerendered page should be recorded.
EXPECT_THAT(GetHistoryContents(),
testing::ElementsAre(prerendering_url, initial_url));
}
// Verify a prerendered page's last committed URL is recorded if we activate it.
IN_PROC_BROWSER_TEST_F(HistoryPrerenderBrowserTest,
PrerenderLastCommitedURLIsRecordedIfActivated) {
const GURL initial_url = embedded_test_server()->GetURL("/empty.html");
const GURL prerendering_url =
embedded_test_server()->GetURL("/empty.html?prerender");
const GURL prerendering_fragment_url =
embedded_test_server()->GetURL("/empty.html?prerender#test");
// Navigate to an initial page.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
// Start a prerender.
const int host_id = prerender_helper().AddPrerender(prerendering_url);
ASSERT_NE(host_id, content::RenderFrameHost::kNoFrameTreeNodeId);
// Do a fragment navigation in the prerendered page.
prerender_helper().NavigatePrerenderedPage(host_id,
prerendering_fragment_url);
prerender_helper().WaitForPrerenderLoadCompletion(host_id);
// Activate.
prerender_helper().NavigatePrimaryPage(prerendering_url);
ASSERT_EQ(prerendering_fragment_url, web_contents()->GetLastCommittedURL());
// The last committed URL of the prerendering page, instead of the original
// prerendering URL, should be recorded.
EXPECT_THAT(GetHistoryContents(),
testing::ElementsAre(prerendering_fragment_url, initial_url));
}
IN_PROC_BROWSER_TEST_F(HistoryPrerenderBrowserTest,
RedirectedPrerenderPageIsRecordedIfActivated) {
const GURL initial_url = embedded_test_server()->GetURL("/empty.html");
// Navigate to an initial page.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
// Start prerendering a URL that causes same-origin redirection.
const GURL redirected_url =
embedded_test_server()->GetURL("/empty.html?prerender");
const GURL prerendering_url = embedded_test_server()->GetURL(
"/server-redirect?" + redirected_url.spec());
prerender_helper().AddPrerender(prerendering_url);
EXPECT_EQ(prerender_helper().GetRequestCount(prerendering_url), 1);
EXPECT_EQ(prerender_helper().GetRequestCount(redirected_url), 1);
// The prerendering page should not be recorded.
EXPECT_THAT(GetHistoryContents(), testing::ElementsAre(initial_url));
// Activate.
prerender_helper().NavigatePrimaryPage(prerendering_url);
// The redirected URL of the prerendering page, instead of the original
// prerendering URL, should be recorded.
EXPECT_THAT(GetHistoryContents(),
testing::ElementsAre(redirected_url, initial_url));
}
// For tests which use fenced frame.
class HistoryFencedFrameBrowserTest : public HistoryMPArchBrowserTest {
public:
HistoryFencedFrameBrowserTest() = default;
~HistoryFencedFrameBrowserTest() override = default;
HistoryFencedFrameBrowserTest(const HistoryFencedFrameBrowserTest&) = delete;
HistoryFencedFrameBrowserTest& operator=(
const HistoryFencedFrameBrowserTest&) = delete;
content::test::FencedFrameTestHelper& fenced_frame_test_helper() {
return fenced_frame_helper_;
}
content::WebContents* web_contents() {
return browser()->tab_strip_model()->GetActiveWebContents();
}
private:
content::test::FencedFrameTestHelper fenced_frame_helper_;
};
IN_PROC_BROWSER_TEST_F(HistoryFencedFrameBrowserTest,
FencedFrameDoesNotAffectLoadingState) {
HistoryTabHelper* history_tab_helper =
HistoryTabHelper::FromWebContents(web_contents());
ASSERT_TRUE(history_tab_helper);
base::TimeTicks last_load_completion_before_navigation =
history_tab_helper->last_load_completion_;
auto initial_url = embedded_test_server()->GetURL("/empty.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
// |last_load_completion_| should be updated after finishing the normal
// navigation.
EXPECT_NE(last_load_completion_before_navigation,
history_tab_helper->last_load_completion_);
// Create a fenced frame.
GURL fenced_frame_url =
embedded_test_server()->GetURL("/fenced_frames/title1.html");
content::RenderFrameHost* fenced_frame_host =
fenced_frame_test_helper().CreateFencedFrame(
web_contents()->GetPrimaryMainFrame(), fenced_frame_url);
// Navigate the fenced frame.
last_load_completion_before_navigation =
history_tab_helper->last_load_completion_;
fenced_frame_test_helper().NavigateFrameInFencedFrameTree(fenced_frame_host,
fenced_frame_url);
// |last_load_completion_| should not be updated after finishing the
// navigation of the fenced frame.
EXPECT_EQ(last_load_completion_before_navigation,
history_tab_helper->last_load_completion_);
}