blob: 003625c552a8c3b2429f149f912477f52573d78b [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/optimization_guide/chrome_hints_manager.h"
#include "base/command_line.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
#include "chrome/browser/optimization_guide/optimization_guide_tab_url_provider.h"
#include "chrome/browser/optimization_guide/optimization_guide_web_contents_observer.h"
#include "chrome/test/base/testing_profile.h"
#include "components/optimization_guide/core/hints/hints_fetcher.h"
#include "components/optimization_guide/core/hints/optimization_guide_decider.h"
#include "components/optimization_guide/core/hints/optimization_guide_store.h"
#include "components/optimization_guide/core/optimization_guide_features.h"
#include "components/optimization_guide/core/optimization_guide_logger.h"
#include "components/optimization_guide/core/optimization_guide_prefs.h"
#include "components/optimization_guide/core/optimization_guide_switches.h"
#include "components/optimization_guide/core/proto_database_provider_test_base.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "components/unified_consent/unified_consent_service.h"
#include "components/variations/scoped_variations_ids_provider.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/mock_navigation_handle.h"
#include "content/public/test/test_web_contents_factory.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace optimization_guide {
// A mock class implementation of TabUrlProvider.
class FakeTabUrlProvider : public optimization_guide::TabUrlProvider {
public:
const std::vector<GURL> GetUrlsOfActiveTabs(
const base::TimeDelta& duration_since_last_shown) override {
num_urls_called_++;
return urls_;
}
void SetUrls(const std::vector<GURL>& urls) { urls_ = urls; }
int get_num_urls_called() const { return num_urls_called_; }
private:
std::vector<GURL> urls_;
int num_urls_called_ = 0;
};
class ChromeHintsManagerFetchingTest
: public optimization_guide::ProtoDatabaseProviderTestBase {
public:
ChromeHintsManagerFetchingTest() {
scoped_feature_list_.InitWithFeaturesAndParameters(
{{optimization_guide::features::kOptimizationHints,
{{"max_host_keyed_hint_cache_size", "1"}}}},
{});
}
ChromeHintsManagerFetchingTest(const ChromeHintsManagerFetchingTest&) =
delete;
ChromeHintsManagerFetchingTest& operator=(
const ChromeHintsManagerFetchingTest&) = delete;
~ChromeHintsManagerFetchingTest() override = default;
void SetUp() override {
optimization_guide::ProtoDatabaseProviderTestBase::SetUp();
web_contents_factory_ = std::make_unique<content::TestWebContentsFactory>();
CreateHintsManager();
}
void TearDown() override {
ResetHintsManager();
optimization_guide::ProtoDatabaseProviderTestBase::TearDown();
}
void CreateHintsManager() {
if (hints_manager_)
ResetHintsManager();
pref_service_ =
std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
optimization_guide::prefs::RegisterProfilePrefs(pref_service_->registry());
unified_consent::UnifiedConsentService::RegisterPrefs(
pref_service_->registry());
url_loader_factory_ =
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
&test_url_loader_factory_);
hint_store_ = std::make_unique<optimization_guide::OptimizationGuideStore>(
db_provider_.get(), temp_dir(),
task_environment_.GetMainThreadTaskRunner());
tab_url_provider_ = std::make_unique<FakeTabUrlProvider>();
hints_manager_ = std::make_unique<ChromeHintsManager>(
&testing_profile_, pref_service(), hint_store_->AsWeakPtr(),
/*top_host_provider=*/nullptr, tab_url_provider_.get(),
url_loader_factory_,
OptimizationGuideKeyedService::MaybeCreatePushNotificationManager(
&testing_profile_),
/*identity_manager=*/nullptr, &optimization_guide_logger_);
hints_manager_->SetClockForTesting(task_environment_.GetMockClock());
// Run until hint cache is initialized and the ChromeHintsManager is ready
// to process hints.
RunUntilIdle();
}
void ResetHintsManager() {
hints_manager_->Shutdown();
hints_manager_.reset();
tab_url_provider_.reset();
hint_store_.reset();
pref_service_.reset();
RunUntilIdle();
}
// Creates a navigation handle with the OptimizationGuideWebContentsObserver
// attached.
std::unique_ptr<content::MockNavigationHandle>
CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
const GURL& url) {
content::WebContents* web_contents =
web_contents_factory_->CreateWebContents(&testing_profile_);
OptimizationGuideWebContentsObserver::CreateForWebContents(web_contents);
std::unique_ptr<content::MockNavigationHandle> navigation_handle =
std::make_unique<::testing::NiceMock<content::MockNavigationHandle>>(
web_contents);
navigation_handle->set_url(url);
return navigation_handle;
}
// Creates a navigation handle WITHOUT the
// OptimizationGuideWebContentsObserver attached.
std::unique_ptr<content::MockNavigationHandle> CreateMockNavigationHandle(
const GURL& url) {
content::WebContents* web_contents =
web_contents_factory_->CreateWebContents(&testing_profile_);
std::unique_ptr<content::MockNavigationHandle> navigation_handle =
std::make_unique<::testing::NiceMock<content::MockNavigationHandle>>(
web_contents);
navigation_handle->set_url(url);
return navigation_handle;
}
content::WebContents* Navigate(GURL url) {
auto navigation_handle =
CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(url);
return navigation_handle->GetWebContents();
}
void FetchHintsUsingWebContentsObserverURLs(
content::WebContents* web_contents) {
auto* observer =
OptimizationGuideWebContentsObserver::FromWebContents(web_contents);
observer->FetchHintsUsingManager(
hints_manager(), web_contents->GetPrimaryPage().GetWeakPtr());
}
ChromeHintsManager* hints_manager() const { return hints_manager_.get(); }
base::FilePath temp_dir() const { return temp_dir_.GetPath(); }
PrefService* pref_service() const { return pref_service_.get(); }
FakeTabUrlProvider* tab_url_provider() const {
return tab_url_provider_.get();
}
void RunUntilIdle() { task_environment_.RunUntilIdle(); }
private:
content::BrowserTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
base::test::ScopedFeatureList scoped_feature_list_;
variations::ScopedVariationsIdsProvider scoped_variations_ids_provider_{
variations::VariationsIdsProvider::Mode::kUseSignedInState};
TestingProfile testing_profile_;
std::unique_ptr<content::TestWebContentsFactory> web_contents_factory_;
std::unique_ptr<optimization_guide::OptimizationGuideStore> hint_store_;
std::unique_ptr<FakeTabUrlProvider> tab_url_provider_;
std::unique_ptr<ChromeHintsManager> hints_manager_;
std::unique_ptr<sync_preferences::TestingPrefServiceSyncable> pref_service_;
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
network::TestURLLoaderFactory test_url_loader_factory_;
OptimizationGuideLogger optimization_guide_logger_;
};
TEST_F(ChromeHintsManagerFetchingTest, HintsFetched_AtSRP_DuplicatesRemoved) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
optimization_guide::switches::kDisableCheckingUserPermissionsForTesting);
hints_manager()->RegisterOptimizationTypes(
{optimization_guide::proto::DEFER_ALL_SCRIPT});
std::vector<GURL> sorted_predicted_urls;
sorted_predicted_urls.emplace_back("https://foo.com/page1.html");
sorted_predicted_urls.emplace_back("https://foo.com/page2.html");
sorted_predicted_urls.emplace_back("https://foo.com/page3.html");
sorted_predicted_urls.emplace_back("https://bar.com/");
GURL url("https://www.google.com/search?q=a");
content::WebContents* web_contents = Navigate(url);
NavigationPredictorKeyedService::Prediction prediction(
web_contents, url,
NavigationPredictorKeyedService::PredictionSource::
kAnchorElementsParsedFromWebPage,
sorted_predicted_urls);
{
base::HistogramTester histogram_tester;
hints_manager()->OnPredictionUpdated(prediction);
FetchHintsUsingWebContentsObserverURLs(web_contents);
// Ensure that we only include 2 hosts in the request. These would be
// foo.com and bar.com.
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount", 2, 1);
// Ensure that we include all URLs in the request.
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.HintsFetcher.GetHintsRequest.UrlCount", 4, 1);
RunUntilIdle();
}
{
base::HistogramTester histogram_tester;
hints_manager()->OnPredictionUpdated(prediction);
FetchHintsUsingWebContentsObserverURLs(web_contents);
// Ensure that URLs are not re-fetched.
histogram_tester.ExpectTotalCount(
"OptimizationGuide.HintsFetcher.GetHintsRequest.UrlCount", 0);
}
}
TEST_F(ChromeHintsManagerFetchingTest,
HintsFetched_AtSRP_NonHTTPOrHTTPSHostsRemoved) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
optimization_guide::switches::kDisableCheckingUserPermissionsForTesting);
hints_manager()->RegisterOptimizationTypes(
{optimization_guide::proto::DEFER_ALL_SCRIPT});
base::HistogramTester histogram_tester;
std::vector<GURL> sorted_predicted_urls;
sorted_predicted_urls.emplace_back("https://foo.com/page1.html");
sorted_predicted_urls.emplace_back("file://non-web-bar.com/");
sorted_predicted_urls.emplace_back("http://httppage.com/");
GURL url("https://www.google.com/search?q=a");
content::WebContents* web_contents = Navigate(url);
NavigationPredictorKeyedService::Prediction prediction(
web_contents, url,
NavigationPredictorKeyedService::PredictionSource::
kAnchorElementsParsedFromWebPage,
sorted_predicted_urls);
hints_manager()->OnPredictionUpdated(prediction);
FetchHintsUsingWebContentsObserverURLs(web_contents);
// Ensure that we include both web hosts in the request. These would be
// foo.com and httppage.com.
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount", 2, 1);
// Ensure that we only include 2 URLs in the request.
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.HintsFetcher.GetHintsRequest.UrlCount", 2, 1);
}
TEST_F(ChromeHintsManagerFetchingTest, HintsFetched_AtSRP) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
optimization_guide::switches::kDisableCheckingUserPermissionsForTesting);
hints_manager()->RegisterOptimizationTypes(
{optimization_guide::proto::DEFER_ALL_SCRIPT});
base::HistogramTester histogram_tester;
std::vector<GURL> sorted_predicted_urls;
sorted_predicted_urls.emplace_back("https://foo.com/");
GURL url("https://www.google.com/search?q=a");
content::WebContents* web_contents = Navigate(url);
NavigationPredictorKeyedService::Prediction prediction(
web_contents, url,
NavigationPredictorKeyedService::PredictionSource::
kAnchorElementsParsedFromWebPage,
sorted_predicted_urls);
hints_manager()->OnPredictionUpdated(prediction);
FetchHintsUsingWebContentsObserverURLs(web_contents);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount", 1);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.HintsFetcher.GetHintsRequest.UrlCount", 1);
}
TEST_F(ChromeHintsManagerFetchingTest, HintsFetched_AtSRP_GoogleLinksIgnored) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
optimization_guide::switches::kDisableCheckingUserPermissionsForTesting);
hints_manager()->RegisterOptimizationTypes(
{optimization_guide::proto::DEFER_ALL_SCRIPT});
base::HistogramTester histogram_tester;
std::vector<GURL> sorted_predicted_urls;
sorted_predicted_urls.emplace_back("https://foo.com/");
sorted_predicted_urls.emplace_back("https://google.com/bar");
GURL url("https://www.google.com/search?q=a");
content::WebContents* web_contents = Navigate(url);
NavigationPredictorKeyedService::Prediction prediction(
web_contents, url,
NavigationPredictorKeyedService::PredictionSource::
kAnchorElementsParsedFromWebPage,
sorted_predicted_urls);
hints_manager()->OnPredictionUpdated(prediction);
FetchHintsUsingWebContentsObserverURLs(web_contents);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount", 1);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.HintsFetcher.GetHintsRequest.UrlCount", 1);
}
TEST_F(ChromeHintsManagerFetchingTest, HintsFetched_AtNonSRP) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
optimization_guide::switches::kDisableCheckingUserPermissionsForTesting);
hints_manager()->RegisterOptimizationTypes(
{optimization_guide::proto::DEFER_ALL_SCRIPT});
base::HistogramTester histogram_tester;
std::vector<GURL> sorted_predicted_urls;
sorted_predicted_urls.emplace_back("https://foo.com/");
GURL url("https://www.not-google.com/");
content::WebContents* web_contents = Navigate(url);
NavigationPredictorKeyedService::Prediction prediction(
web_contents, url,
NavigationPredictorKeyedService::PredictionSource::
kAnchorElementsParsedFromWebPage,
sorted_predicted_urls);
hints_manager()->OnPredictionUpdated(prediction);
FetchHintsUsingWebContentsObserverURLs(web_contents);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount", 0);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.HintsFetcher.GetHintsRequest.UrlCount", 0);
}
class ChromeHintsManagerPushEnabledTest
: public ChromeHintsManagerFetchingTest {
public:
ChromeHintsManagerPushEnabledTest() {
scoped_feature_list_.InitAndEnableFeature(
optimization_guide::features::kPushNotifications);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_F(ChromeHintsManagerPushEnabledTest, PushManagerSet) {
EXPECT_TRUE(hints_manager()->push_notification_manager());
}
class ChromeHintsManagerPushDisabledTest
: public ChromeHintsManagerFetchingTest {
public:
ChromeHintsManagerPushDisabledTest() {
scoped_feature_list_.InitAndDisableFeature(
optimization_guide::features::kPushNotifications);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_F(ChromeHintsManagerPushDisabledTest, PushManagerSet) {
EXPECT_FALSE(hints_manager()->push_notification_manager());
}
TEST_F(ChromeHintsManagerFetchingTest, NoOptimizationGuideWebContentsObserver) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
optimization_guide::switches::kDisableCheckingUserPermissionsForTesting);
hints_manager()->RegisterOptimizationTypes(
{optimization_guide::proto::DEFER_ALL_SCRIPT});
std::vector<GURL> sorted_predicted_urls;
sorted_predicted_urls.emplace_back("https://foo.com/page1.html");
GURL url("https://www.google.com/search?q=a");
auto navigation_handle = CreateMockNavigationHandle(url);
content::WebContents* web_contents = navigation_handle->GetWebContents();
NavigationPredictorKeyedService::Prediction prediction(
web_contents, url,
NavigationPredictorKeyedService::PredictionSource::
kAnchorElementsParsedFromWebPage,
sorted_predicted_urls);
// Calling `OnPredictionUpdated` without having a valid
// `OptimizationGuideWebContentsObserver` should not cause a crash.
hints_manager()->OnPredictionUpdated(prediction);
}
} // namespace optimization_guide