// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/history/model/history_tab_helper.h"
#import "base/functional/bind.h"
#import "base/functional/callback.h"
#import "base/memory/raw_ptr.h"
#import "base/run_loop.h"
#import "base/strings/utf_string_conversions.h"
#import "base/test/bind.h"
#import "base/test/task_environment.h"
#import "components/history/core/browser/history_service.h"
#import "components/keyed_service/core/service_access_type.h"
#import "ios/chrome/browser/history/model/history_service_factory.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/browser/shared/model/url/chrome_url_constants.h"
#import "ios/web/public/navigation/navigation_item.h"
#import "ios/web/public/test/fakes/fake_navigation_context.h"
#import "ios/web/public/test/fakes/fake_navigation_manager.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/platform_test.h"
namespace {
class HistoryTabHelperTest : public PlatformTest {
public:
void SetUp() override {
TestChromeBrowserState::Builder test_cbs_builder;
test_cbs_builder.AddTestingFactory(
ios::HistoryServiceFactory::GetInstance(),
ios::HistoryServiceFactory::GetDefaultFactory());
chrome_browser_state_ = std::move(test_cbs_builder).Build();
auto navigation_manager = std::make_unique<web::FakeNavigationManager>();
navigation_manager_ = navigation_manager.get();
web_state_.SetNavigationManager(std::move(navigation_manager));
web_state_.SetBrowserState(chrome_browser_state_.get());
HistoryTabHelper::CreateForWebState(&web_state_);
}
// Queries the history service for information about the given `url` and
// returns the response. Spins the runloop until a response is received.
void QueryURL(const GURL& url) {
history::HistoryService* service =
ios::HistoryServiceFactory::GetForBrowserState(
chrome_browser_state_.get(), ServiceAccessType::EXPLICIT_ACCESS);
base::RunLoop loop;
service->QueryURL(
url, /*want_visits=*/true,
base::BindLambdaForTesting([&](history::QueryURLResult result) {
latest_row_result_ = std::move(result.row);
latest_visits_result_ = std::move(result.visits);
loop.Quit();
}),
&tracker_);
loop.Run();
}
// Adds an entry for the given `url` to the history database.
void AddVisitForURL(const GURL& url) {
history::HistoryService* service =
ios::HistoryServiceFactory::GetForBrowserState(
chrome_browser_state_.get(), ServiceAccessType::EXPLICIT_ACCESS);
service->AddPage(
url, base::Time::Now(), 0, 0, GURL(), history::RedirectList(),
ui::PAGE_TRANSITION_MANUAL_SUBFRAME, history::SOURCE_BROWSED, false);
}
protected:
base::test::TaskEnvironment task_environment_;
std::unique_ptr<TestChromeBrowserState> chrome_browser_state_;
web::FakeWebState web_state_;
raw_ptr<web::FakeNavigationManager> navigation_manager_;
base::CancelableTaskTracker tracker_;
// Cached data from the last call to `QueryURL()`.
history::URLRow latest_row_result_;
history::VisitVector latest_visits_result_;
};
} // namespace
// Tests that different urls can have different titles.
TEST_F(HistoryTabHelperTest, MultipleURLsWithTitles) {
GURL first_url("https://first.google.com/");
GURL second_url("https://second.google.com/");
std::string first_title = "First Title";
std::string second_title = "Second Title";
HistoryTabHelper* helper = HistoryTabHelper::FromWebState(&web_state_);
ASSERT_TRUE(helper);
std::unique_ptr<web::NavigationItem> first_item =
web::NavigationItem::Create();
first_item->SetVirtualURL(first_url);
first_item->SetTitle(base::UTF8ToUTF16(first_title));
std::unique_ptr<web::NavigationItem> second_item =
web::NavigationItem::Create();
second_item->SetVirtualURL(second_url);
second_item->SetTitle(base::UTF8ToUTF16(second_title));
AddVisitForURL(first_url);
AddVisitForURL(second_url);
helper->UpdateHistoryPageTitle(*first_item);
helper->UpdateHistoryPageTitle(*second_item);
// Verify that the first title was set properly.
QueryURL(first_url);
EXPECT_EQ(first_url, latest_row_result_.url());
EXPECT_EQ(base::UTF8ToUTF16(first_title), latest_row_result_.title());
// Verify that the first title was set properly.
QueryURL(second_url);
EXPECT_EQ(second_url, latest_row_result_.url());
EXPECT_EQ(base::UTF8ToUTF16(second_title), latest_row_result_.title());
}
// Tests that page titles are set properly and can be modified.
TEST_F(HistoryTabHelperTest, TitleUpdateForOneURL) {
GURL test_url("https://www.google.com/");
std::string first_title = "First Title";
std::string second_title = "Second Title";
HistoryTabHelper* helper = HistoryTabHelper::FromWebState(&web_state_);
ASSERT_TRUE(helper);
// Set the title and read it back again.
AddVisitForURL(test_url);
std::unique_ptr<web::NavigationItem> item = web::NavigationItem::Create();
item->SetVirtualURL(test_url);
item->SetTitle(base::UTF8ToUTF16(first_title));
helper->UpdateHistoryPageTitle(*item);
QueryURL(test_url);
EXPECT_EQ(test_url, latest_row_result_.url());
EXPECT_EQ(base::UTF8ToUTF16(first_title), latest_row_result_.title());
// Update the title and read it back again.
std::unique_ptr<web::NavigationItem> update = web::NavigationItem::Create();
update->SetVirtualURL(test_url);
update->SetTitle(base::UTF8ToUTF16(second_title));
helper->UpdateHistoryPageTitle(*update);
QueryURL(test_url);
EXPECT_EQ(base::UTF8ToUTF16(second_title), latest_row_result_.title());
}
// Tests that an empty title is not written to the history database. (In the
// current implementation, the page's URL is used as its title.)
TEST_F(HistoryTabHelperTest, EmptyTitleIsNotWrittenToHistory) {
GURL test_url("https://www.google.com/");
std::string test_title = "";
HistoryTabHelper* helper = HistoryTabHelper::FromWebState(&web_state_);
ASSERT_TRUE(helper);
std::unique_ptr<web::NavigationItem> item = web::NavigationItem::Create();
item->SetVirtualURL(test_url);
item->SetTitle(base::UTF8ToUTF16(test_title));
AddVisitForURL(test_url);
helper->UpdateHistoryPageTitle(*item);
QueryURL(test_url);
EXPECT_EQ(test_url, latest_row_result_.url());
EXPECT_FALSE(latest_row_result_.title().empty());
}
// Tests that setting the empty title overwrites a previous, non-empty title.
TEST_F(HistoryTabHelperTest, EmptyTitleOverwritesPreviousTitle) {
GURL test_url("https://www.google.com/");
std::string test_title = "Test Title";
HistoryTabHelper* helper = HistoryTabHelper::FromWebState(&web_state_);
ASSERT_TRUE(helper);
std::unique_ptr<web::NavigationItem> item = web::NavigationItem::Create();
item->SetVirtualURL(test_url);
item->SetTitle(base::UTF8ToUTF16(test_title));
AddVisitForURL(test_url);
helper->UpdateHistoryPageTitle(*item);
QueryURL(test_url);
EXPECT_EQ(test_url, latest_row_result_.url());
EXPECT_EQ(base::UTF8ToUTF16(test_title), latest_row_result_.title());
// Set the empty title and make sure the title is updated.
item->SetTitle(std::u16string());
helper->UpdateHistoryPageTitle(*item);
QueryURL(test_url);
EXPECT_NE(base::UTF8ToUTF16(test_title), latest_row_result_.title());
}
// Tests that the ntp is not saved to history.
TEST_F(HistoryTabHelperTest, TestNTPNotAdded) {
HistoryTabHelper* helper = HistoryTabHelper::FromWebState(&web_state_);
ASSERT_TRUE(helper);
std::unique_ptr<web::NavigationItem> item = web::NavigationItem::Create();
GURL test_url("https://www.google.com/");
item->SetVirtualURL(test_url);
AddVisitForURL(test_url);
QueryURL(test_url);
EXPECT_EQ(test_url, latest_row_result_.url());
item = web::NavigationItem::Create();
GURL ntp_url(kChromeUIAboutNewTabURL);
item->SetVirtualURL(ntp_url);
AddVisitForURL(ntp_url);
QueryURL(ntp_url);
EXPECT_NE(ntp_url, latest_row_result_.url());
}
// Tests that a file:// URL isn't added to history.
TEST_F(HistoryTabHelperTest, TestFileNotAdded) {
HistoryTabHelper* helper = HistoryTabHelper::FromWebState(&web_state_);
ASSERT_TRUE(helper);
std::unique_ptr<web::NavigationItem> item = web::NavigationItem::Create();
GURL test_url("https://www.google.com/");
item->SetVirtualURL(test_url);
AddVisitForURL(test_url);
QueryURL(test_url);
EXPECT_EQ(test_url, latest_row_result_.url());
item = web::NavigationItem::Create();
GURL file_url("file://path/to/file");
item->SetVirtualURL(file_url);
AddVisitForURL(file_url);
QueryURL(file_url);
EXPECT_NE(file_url, latest_row_result_.url());
}
TEST_F(HistoryTabHelperTest, ShouldUpdateVisitDurationInHistory) {
const GURL url1("https://url1.com");
const GURL url2("https://url2.com");
HistoryTabHelper* helper = HistoryTabHelper::FromWebState(&web_state_);
ASSERT_TRUE(helper);
web::FakeNavigationContext navigation_context;
navigation_context.SetHasCommitted(true);
navigation_context.SetResponseHeaders(
net::HttpResponseHeaders::TryToCreate("HTTP/1.1 234 OK\r\n\r\n"));
std::unique_ptr<web::NavigationItem> item = web::NavigationItem::Create();
navigation_manager_->SetLastCommittedItem(item.get());
// Navigate to `url1`.
item->SetURL(url1);
item->SetTimestamp(base::Time::Now());
navigation_context.SetUrl(url1);
static_cast<web::WebStateObserver*>(helper)->DidFinishNavigation(
&web_state_, &navigation_context);
// Make sure the visit showed up.
QueryURL(url1);
ASSERT_EQ(latest_row_result_.url(), url1);
ASSERT_FALSE(latest_visits_result_.empty());
// The duration shouldn't be set yet, since the visit is still open.
EXPECT_TRUE(latest_visits_result_.back().visit_duration.is_zero());
// Once the user navigates on, the duration of the first visit should be
// populated.
item->SetURL(url2);
item->SetTimestamp(base::Time::Now());
navigation_context.SetUrl(url2);
static_cast<web::WebStateObserver*>(helper)->DidFinishNavigation(
&web_state_, &navigation_context);
// The duration of the first visit should be populated now.
QueryURL(url1);
ASSERT_EQ(latest_row_result_.url(), url1);
ASSERT_FALSE(latest_visits_result_.empty());
EXPECT_FALSE(latest_visits_result_.back().visit_duration.is_zero());
// ...but not the duration of the second visit yet.
QueryURL(url2);
ASSERT_EQ(latest_row_result_.url(), url2);
ASSERT_FALSE(latest_visits_result_.empty());
EXPECT_TRUE(latest_visits_result_.back().visit_duration.is_zero());
// Closing the tab should finish the second visit and populate its duration.
static_cast<web::WebStateObserver*>(helper)->WebStateDestroyed(&web_state_);
QueryURL(url2);
ASSERT_EQ(latest_row_result_.url(), url2);
ASSERT_FALSE(latest_visits_result_.empty());
EXPECT_FALSE(latest_visits_result_.back().visit_duration.is_zero());
}
TEST_F(HistoryTabHelperTest,
CreateAddPageArgsPopulatesOnVisitContextAnnotations) {
std::unique_ptr<web::NavigationItem> item = web::NavigationItem::Create();
GURL test_url("https://www.google.com/");
item->SetVirtualURL(test_url);
web::FakeNavigationContext context;
context.SetUrl(test_url);
context.SetHasCommitted(true);
std::string raw_response_headers = "HTTP/1.1 234 OK\r\n\r\n";
scoped_refptr<net::HttpResponseHeaders> response_headers =
net::HttpResponseHeaders::TryToCreate(raw_response_headers);
DCHECK(response_headers);
context.SetResponseHeaders(response_headers);
HistoryTabHelper* helper = HistoryTabHelper::FromWebState(&web_state_);
history::HistoryAddPageArgs args =
helper->CreateHistoryAddPageArgs(item.get(), &context);
// Make sure the `context_annotations` are populated.
ASSERT_TRUE(args.context_annotations.has_value());
// Most of the actual fields can't be verified here, because the corresponding
// data sources don't exist in this unit test (e.g. there's no Browser, no
// other TabHelpers, etc). At least check the response code that was set up
// above.
EXPECT_EQ(args.context_annotations->response_code, 234);
}