blob: 9a1234fff8ef6cb126a7213b1cbf38414534a278 [file] [log] [blame]
// Copyright (c) 2013 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 "chrome/browser/ui/webui/history/browsing_history_handler.h"
#include <stdint.h>
#include <memory>
#include <set>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/simple_test_clock.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/history/web_history_service_factory.h"
#include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/testing_profile.h"
#include "components/history/core/browser/browsing_history_service.h"
#include "components/history/core/test/fake_web_history_service.h"
#include "components/sync/base/model_type.h"
#include "components/sync/driver/test_sync_service.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/test_web_ui.h"
#include "net/http/http_status_code.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "url/gurl.h"
namespace {
base::Time PretendNow() {
base::Time::Exploded exploded_reference_time;
exploded_reference_time.year = 2015;
exploded_reference_time.month = 1;
exploded_reference_time.day_of_month = 2;
exploded_reference_time.day_of_week = 5;
exploded_reference_time.hour = 11;
exploded_reference_time.minute = 0;
exploded_reference_time.second = 0;
exploded_reference_time.millisecond = 0;
base::Time out_time;
EXPECT_TRUE(
base::Time::FromLocalExploded(exploded_reference_time, &out_time));
return out_time;
}
class BrowsingHistoryHandlerWithWebUIForTesting
: public BrowsingHistoryHandler {
public:
explicit BrowsingHistoryHandlerWithWebUIForTesting(content::WebUI* web_ui) {
set_clock(&test_clock_);
set_web_ui(web_ui);
test_clock_.SetNow(PretendNow());
}
void SendHistoryQuery(int count, const base::string16& query) override {
if (postpone_query_results_) {
return;
}
BrowsingHistoryHandler::SendHistoryQuery(count, query);
}
void PostponeResults() { postpone_query_results_ = true; }
base::SimpleTestClock* test_clock() { return &test_clock_; }
private:
base::SimpleTestClock test_clock_;
bool postpone_query_results_ = false;
DISALLOW_COPY_AND_ASSIGN(BrowsingHistoryHandlerWithWebUIForTesting);
};
} // namespace
class BrowsingHistoryHandlerTest : public ChromeRenderViewHostTestHarness {
public:
void SetUp() override {
ChromeRenderViewHostTestHarness::SetUp();
sync_service_ = static_cast<syncer::TestSyncService*>(
ProfileSyncServiceFactory::GetForProfile(profile()));
web_history_service_ = static_cast<history::FakeWebHistoryService*>(
WebHistoryServiceFactory::GetForProfile(profile()));
ASSERT_TRUE(web_history_service_);
web_ui_ = std::make_unique<content::TestWebUI>();
web_ui_->set_web_contents(web_contents());
}
void TearDown() override {
web_ui_.reset();
ChromeRenderViewHostTestHarness::TearDown();
}
TestingProfile::TestingFactories GetTestingFactories() const override {
return {
{ProfileSyncServiceFactory::GetInstance(),
base::BindRepeating(&BuildTestSyncService)},
{WebHistoryServiceFactory::GetInstance(),
base::BindRepeating(&BuildFakeWebHistoryService)},
{BookmarkModelFactory::GetInstance(),
BookmarkModelFactory::GetDefaultFactory()},
};
}
void VerifyHistoryDeletedFired(content::TestWebUI::CallData& data) {
EXPECT_EQ("cr.webUIListenerCallback", data.function_name());
std::string event_fired;
ASSERT_TRUE(data.arg1()->GetAsString(&event_fired));
EXPECT_EQ("history-deleted", event_fired);
}
void InitializeWebUI(BrowsingHistoryHandlerWithWebUIForTesting& handler) {
// Send historyLoaded so that JS will be allowed.
base::Value init_args(base::Value::Type::LIST);
init_args.Append("query-history-callback-id");
init_args.Append("");
init_args.Append(150);
handler.HandleQueryHistory(&base::Value::AsListValue(init_args));
}
syncer::TestSyncService* sync_service() { return sync_service_; }
history::WebHistoryService* web_history_service() {
return web_history_service_;
}
content::TestWebUI* web_ui() { return web_ui_.get(); }
private:
static std::unique_ptr<KeyedService> BuildTestSyncService(
content::BrowserContext* context) {
return std::make_unique<syncer::TestSyncService>();
}
static std::unique_ptr<KeyedService> BuildFakeWebHistoryService(
content::BrowserContext* context) {
std::unique_ptr<history::FakeWebHistoryService> service =
std::make_unique<history::FakeWebHistoryService>();
service->SetupFakeResponse(true /* success */, net::HTTP_OK);
return service;
}
syncer::TestSyncService* sync_service_ = nullptr;
history::FakeWebHistoryService* web_history_service_ = nullptr;
std::unique_ptr<content::TestWebUI> web_ui_;
};
// Tests that BrowsingHistoryHandler is informed about WebHistoryService
// deletions.
TEST_F(BrowsingHistoryHandlerTest, ObservingWebHistoryDeletions) {
base::Callback<void(bool)> callback = base::DoNothing();
// BrowsingHistoryHandler is informed about WebHistoryService history
// deletions.
{
sync_service()->SetTransportState(
syncer::SyncService::TransportState::ACTIVE);
BrowsingHistoryHandlerWithWebUIForTesting handler(web_ui());
handler.RegisterMessages();
handler.StartQueryHistory();
InitializeWebUI(handler);
// QueryHistory triggers 2 calls to HasOtherFormsOfBrowsingHistory that fire
// before the callback is resolved if the sync service is active when the
// first query is sent. The handler should also resolve the initial
// queryHistory callback.
EXPECT_EQ(3U, web_ui()->call_data().size());
web_history_service()->ExpireHistoryBetween(
std::set<GURL>(), base::Time(), base::Time::Max(), callback,
PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS);
EXPECT_EQ(4U, web_ui()->call_data().size());
VerifyHistoryDeletedFired(*web_ui()->call_data().back());
}
// BrowsingHistoryHandler will be informed about WebHistoryService deletions
// even if history sync is activated later.
{
sync_service()->SetTransportState(
syncer::SyncService::TransportState::INITIALIZING);
BrowsingHistoryHandlerWithWebUIForTesting handler(web_ui());
handler.RegisterMessages();
handler.StartQueryHistory();
sync_service()->SetTransportState(
syncer::SyncService::TransportState::ACTIVE);
sync_service()->FireStateChanged();
web_history_service()->ExpireHistoryBetween(
std::set<GURL>(), base::Time(), base::Time::Max(), callback,
PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS);
EXPECT_EQ(4U, web_ui()->call_data().size());
// Simulate initialization after history has been deleted. The
// history-deleted event will happen before the historyResults() callback,
// since AllowJavascript is called before returning the results.
InitializeWebUI(handler);
// QueryHistory triggers 1 call to HasOtherFormsOfBrowsingHistory that fire
// before the callback is resolved if the sync service is inactive when the
// first query is sent. The handler should also have fired history-deleted
// and resolved the initial queryHistory callback.
EXPECT_EQ(7U, web_ui()->call_data().size());
VerifyHistoryDeletedFired(
*web_ui()->call_data()[web_ui()->call_data().size() - 2]);
}
// BrowsingHistoryHandler does not fire historyDeleted while a web history
// delete request is happening.
{
sync_service()->SetTransportState(
syncer::SyncService::TransportState::ACTIVE);
BrowsingHistoryHandlerWithWebUIForTesting handler(web_ui());
handler.RegisterMessages();
handler.StartQueryHistory();
InitializeWebUI(handler);
// QueryHistory triggers 2 calls to HasOtherFormsOfBrowsingHistory that fire
// before the callback is resolved if the sync service is active when the
// first query is sent. The handler should also resolve the initial
// queryHistory callback.
EXPECT_EQ(10U, web_ui()->call_data().size());
// Simulate a delete request.
base::Value args(base::Value::Type::LIST);
args.Append("remove-visits-callback-id");
base::Value to_remove(base::Value::Type::LIST);
base::Value visit(base::Value::Type::DICTIONARY);
visit.SetStringKey("url", "https://www.google.com");
base::Value timestamps(base::Value::Type::LIST);
timestamps.Append(12345678.0);
visit.SetKey("timestamps", std::move(timestamps));
to_remove.Append(std::move(visit));
args.Append(std::move(to_remove));
handler.HandleRemoveVisits(&base::Value::AsListValue(args));
EXPECT_EQ(11U, web_ui()->call_data().size());
const content::TestWebUI::CallData& data = *web_ui()->call_data().back();
EXPECT_EQ("cr.webUIResponse", data.function_name());
std::string callback_id;
ASSERT_TRUE(data.arg1()->GetAsString(&callback_id));
EXPECT_EQ("remove-visits-callback-id", callback_id);
bool success = false;
ASSERT_TRUE(data.arg2()->GetAsBoolean(&success));
ASSERT_TRUE(success);
}
// When history sync is not active, we don't listen to WebHistoryService
// deletions. The WebHistoryService object still exists (because it's a
// BrowserContextKeyedService), but is not visible to BrowsingHistoryHandler.
{
sync_service()->SetTransportState(
syncer::SyncService::TransportState::INITIALIZING);
BrowsingHistoryHandlerWithWebUIForTesting handler(web_ui());
handler.RegisterMessages();
handler.StartQueryHistory();
web_history_service()->ExpireHistoryBetween(
std::set<GURL>(), base::Time(), base::Time::Max(), callback,
PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS);
// No additional WebUI calls were made.
EXPECT_EQ(11U, web_ui()->call_data().size());
}
}
#if !defined(OS_ANDROID)
TEST_F(BrowsingHistoryHandlerTest, MdTruncatesTitles) {
history::BrowsingHistoryService::HistoryEntry long_url_entry;
long_url_entry.url = GURL(
"http://loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo"
"oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo"
"oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo"
"oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo"
"oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo"
"oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo"
"ngurlislong.com");
ASSERT_GT(long_url_entry.url.spec().size(), 300U);
BrowsingHistoryHandlerWithWebUIForTesting handler(web_ui());
ASSERT_TRUE(web_ui()->call_data().empty());
handler.OnQueryComplete({long_url_entry},
history::BrowsingHistoryService::QueryResultsInfo(),
base::OnceClosure());
InitializeWebUI(handler);
ASSERT_FALSE(web_ui()->call_data().empty());
// Request should be resolved successfully.
ASSERT_TRUE(web_ui()->call_data().front()->arg2()->GetBool());
const base::Value* arg3 = web_ui()->call_data().front()->arg3();
ASSERT_TRUE(arg3->is_dict());
const base::Value* list = arg3->FindListKey("value");
ASSERT_TRUE(list->is_list());
const base::Value& first_entry = list->GetList()[0];
ASSERT_TRUE(first_entry.is_dict());
const std::string* title = first_entry.FindStringKey("title");
ASSERT_TRUE(title);
ASSERT_EQ(0u, title->find("http://loooo"));
EXPECT_EQ(300u, title->size());
}
TEST_F(BrowsingHistoryHandlerTest, Reload) {
BrowsingHistoryHandlerWithWebUIForTesting handler(web_ui());
handler.RegisterMessages();
handler.PostponeResults();
handler.StartQueryHistory();
ASSERT_TRUE(web_ui()->call_data().empty());
InitializeWebUI(handler);
// Still empty, since no results are available yet.
ASSERT_TRUE(web_ui()->call_data().empty());
// Simulate page refresh and results being returned asynchronously.
handler.OnJavascriptDisallowed();
history::BrowsingHistoryService::HistoryEntry url_entry;
url_entry.url = GURL("https://www.chromium.org");
handler.OnQueryComplete({url_entry},
history::BrowsingHistoryService::QueryResultsInfo(),
base::OnceClosure());
// There should be no new Web UI calls, since JS is still disallowed.
ASSERT_TRUE(web_ui()->call_data().empty());
}
#endif