blob: ee638f1141d77d9f30add79cea945a4179bcd294 [file] [log] [blame]
// Copyright 2018 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/predictors/loading_predictor_tab_helper.h"
#include "base/memory/raw_ptr.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
#include "chrome/browser/predictors/loading_predictor.h"
#include "chrome/browser/predictors/loading_test_util.h"
#include "chrome/browser/predictors/predictors_enums.h"
#include "chrome/browser/predictors/predictors_features.h"
#include "chrome/browser/sessions/session_tab_helper_factory.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/testing_profile.h"
#include "components/optimization_guide/core/optimization_guide_features.h"
#include "components/sessions/content/session_tab_helper.h"
#include "components/variations/scoped_variations_ids_provider.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/test/navigation_simulator.h"
#include "services/network/public/mojom/fetch_api.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/loader/resource_load_info.mojom.h"
using ::testing::_;
using ::testing::ByRef;
using ::testing::DoAll;
using ::testing::Eq;
using ::testing::Invoke;
using ::testing::Mock;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::StrictMock;
using ::testing::WithArg;
namespace predictors {
class MockLoadingDataCollector : public LoadingDataCollector {
public:
explicit MockLoadingDataCollector(const LoadingPredictorConfig& config);
MOCK_METHOD4(RecordStartNavigation,
void(NavigationId, ukm::SourceId, const GURL&, base::TimeTicks));
MOCK_METHOD4(RecordFinishNavigation,
void(NavigationId, const GURL&, const GURL&, bool));
MOCK_METHOD2(RecordResourceLoadComplete,
void(NavigationId, const blink::mojom::ResourceLoadInfo&));
MOCK_METHOD2(RecordMainFrameLoadComplete,
void(NavigationId,
const absl::optional<OptimizationGuidePrediction>&));
MOCK_METHOD2(RecordFirstContentfulPaint, void(NavigationId, base::TimeTicks));
};
MockLoadingDataCollector::MockLoadingDataCollector(
const LoadingPredictorConfig& config)
: LoadingDataCollector(nullptr, nullptr, config) {}
class MockOptimizationGuideKeyedService : public OptimizationGuideKeyedService {
public:
explicit MockOptimizationGuideKeyedService(
content::BrowserContext* browser_context)
: OptimizationGuideKeyedService(browser_context) {}
~MockOptimizationGuideKeyedService() override = default;
MOCK_METHOD1(
RegisterOptimizationTypes,
void(const std::vector<optimization_guide::proto::OptimizationType>&));
MOCK_METHOD3(CanApplyOptimizationAsync,
void(content::NavigationHandle*,
optimization_guide::proto::OptimizationType,
optimization_guide::OptimizationGuideDecisionCallback));
};
class LoadingPredictorTabHelperTest : public ChromeRenderViewHostTestHarness {
public:
void SetUp() override;
void TearDown() override;
SessionID GetTabID();
void NavigateAndCommitInFrame(const std::string& url,
content::RenderFrameHost* rfh);
void NavigateAndCommitInMainFrameAndVerifyMetrics(const std::string& url);
protected:
std::unique_ptr<LoadingPredictor> loading_predictor_;
// Owned by |loading_predictor_|.
raw_ptr<StrictMock<MockLoadingDataCollector>> mock_collector_;
// Owned elsewhere.
raw_ptr<NiceMock<MockOptimizationGuideKeyedService>>
mock_optimization_guide_keyed_service_;
// Owned by |web_contents()|.
raw_ptr<LoadingPredictorTabHelper> tab_helper_;
};
void LoadingPredictorTabHelperTest::SetUp() {
ChromeRenderViewHostTestHarness::SetUp();
CreateSessionServiceTabHelper(web_contents());
mock_optimization_guide_keyed_service_ =
static_cast<NiceMock<MockOptimizationGuideKeyedService>*>(
OptimizationGuideKeyedServiceFactory::GetInstance()
->SetTestingFactoryAndUse(
profile(),
base::BindRepeating([](content::BrowserContext* context)
-> std::unique_ptr<KeyedService> {
return std::make_unique<
NiceMock<MockOptimizationGuideKeyedService>>(context);
})));
LoadingPredictorTabHelper::CreateForWebContents(web_contents());
tab_helper_ = LoadingPredictorTabHelper::FromWebContents(web_contents());
LoadingPredictorConfig config;
PopulateTestConfig(&config);
loading_predictor_ = std::make_unique<LoadingPredictor>(config, profile());
auto mock_collector =
std::make_unique<StrictMock<MockLoadingDataCollector>>(config);
mock_collector_ = mock_collector.get();
loading_predictor_->set_mock_loading_data_collector(
std::move(mock_collector));
tab_helper_->SetLoadingPredictorForTesting(loading_predictor_->GetWeakPtr());
}
void LoadingPredictorTabHelperTest::TearDown() {
loading_predictor_->Shutdown();
ChromeRenderViewHostTestHarness::TearDown();
}
void LoadingPredictorTabHelperTest::
NavigateAndCommitInMainFrameAndVerifyMetrics(const std::string& url) {
ukm::SourceId ukm_source_id;
GURL main_frame_url;
GURL old_main_frame_url;
GURL new_main_frame_url;
EXPECT_CALL(*mock_collector_, RecordStartNavigation(_, _, _, _))
.WillOnce(DoAll(SaveArg<1>(&ukm_source_id), SaveArg<2>(&main_frame_url)));
EXPECT_CALL(*mock_collector_,
RecordFinishNavigation(_, _, _,
/* is_error_page */ false))
.WillOnce(DoAll(SaveArg<1>(&old_main_frame_url),
SaveArg<2>(&new_main_frame_url)));
NavigateAndCommitInFrame(url, main_rfh());
EXPECT_EQ(ukm_source_id,
web_contents()->GetMainFrame()->GetPageUkmSourceId());
GURL gurl(url);
EXPECT_EQ(gurl, main_frame_url);
EXPECT_EQ(gurl, old_main_frame_url);
EXPECT_EQ(gurl, new_main_frame_url);
}
SessionID LoadingPredictorTabHelperTest::GetTabID() {
return sessions::SessionTabHelper::IdForTab(web_contents());
}
void LoadingPredictorTabHelperTest::NavigateAndCommitInFrame(
const std::string& url,
content::RenderFrameHost* rfh) {
auto navigation =
content::NavigationSimulator::CreateRendererInitiated(GURL(url), rfh);
// These tests simulate loading events manually.
// TODO(ahemery): Consider refactoring to rely on load events dispatched by
// NavigationSimulator.
navigation->SetKeepLoading(true);
navigation->Start();
navigation->Commit();
}
// Tests that a main frame navigation is correctly recorded by the
// LoadingDataCollector.
TEST_F(LoadingPredictorTabHelperTest, MainFrameNavigation) {
NavigateAndCommitInMainFrameAndVerifyMetrics("http://test.org");
}
// Tests that an old and new navigation ids are correctly set if a navigation
// has redirects.
TEST_F(LoadingPredictorTabHelperTest, MainFrameNavigationWithRedirects) {
GURL main_frame_url("http://test.org");
auto navigation = content::NavigationSimulator::CreateRendererInitiated(
main_frame_url, main_rfh());
// The problem here is that mock_collector_ is a strict mock, which expects
// a particular set of loading events and fails when extra is present.
// TOOO(ahemery): Consider refactoring this to rely on loading events
// in NavigationSimulator.
navigation->SetKeepLoading(true);
ukm::SourceId ukm_source_id;
EXPECT_CALL(*mock_collector_, RecordStartNavigation(_, _, main_frame_url, _))
.WillOnce(SaveArg<1>(&ukm_source_id));
navigation->Start();
navigation->Redirect(GURL("http://test2.org"));
navigation->Redirect(GURL("http://test3.org"));
GURL expected_main_frame_url("http://test3.org");
EXPECT_CALL(
*mock_collector_,
RecordFinishNavigation(_, main_frame_url, expected_main_frame_url, _));
navigation->Commit();
EXPECT_EQ(web_contents()->GetMainFrame()->GetPageUkmSourceId(),
ukm_source_id);
}
// Tests that a subframe navigation is not recorded.
TEST_F(LoadingPredictorTabHelperTest, SubframeNavigation) {
// We need to have a committed main frame navigation before navigating in sub
// frames.
NavigateAndCommitInMainFrameAndVerifyMetrics("http://test.org");
auto* subframe =
content::RenderFrameHostTester::For(main_rfh())->AppendChild("subframe");
// Subframe navigation shouldn't be recorded.
NavigateAndCommitInFrame("http://sub.test.org", subframe);
}
// Tests that a failed navigation is correctly recorded.
TEST_F(LoadingPredictorTabHelperTest, MainFrameNavigationFailed) {
GURL url("http://test.org");
auto navigation =
content::NavigationSimulator::CreateRendererInitiated(url, main_rfh());
navigation->SetKeepLoading(true);
// The problem here is that mock_collector_ is a strict mock, which expects
// a particular set of loading events and fails when extra is present.
// TOOO(ahemery): Consider refactoring this to rely on loading events
// in NavigationSimulator.
ukm::SourceId ukm_source_id;
EXPECT_CALL(*mock_collector_, RecordStartNavigation(_, _, url, _))
.WillOnce(SaveArg<1>(&ukm_source_id));
navigation->Start();
EXPECT_CALL(*mock_collector_,
RecordFinishNavigation(_, url, url,
/* is_error_page */ true));
navigation->Fail(net::ERR_TIMED_OUT);
navigation->CommitErrorPage();
EXPECT_EQ(ukm_source_id,
web_contents()->GetMainFrame()->GetPageUkmSourceId());
}
// Tests that a same document navigation is not recorded.
TEST_F(LoadingPredictorTabHelperTest, MainFrameNavigationSameDocument) {
NavigateAndCommitInMainFrameAndVerifyMetrics("http://test.org");
// Same document navigation shouldn't be recorded.
content::NavigationSimulator::CreateRendererInitiated(GURL("http://test.org"),
main_rfh())
->CommitSameDocument();
}
// Tests that document on load completed is recorded with correct navigation
// id.
TEST_F(LoadingPredictorTabHelperTest, DocumentOnLoadCompleted) {
NavigateAndCommitInMainFrameAndVerifyMetrics("http://test.org");
// Adding subframe navigation to ensure that the committed main frame url will
// be used.
auto* subframe =
content::RenderFrameHostTester::For(main_rfh())->AppendChild("subframe");
NavigateAndCommitInFrame("http://sub.test.org", subframe);
EXPECT_CALL(*mock_collector_, RecordMainFrameLoadComplete(_, _));
tab_helper_->DocumentOnLoadCompletedInPrimaryMainFrame();
}
// Tests that a resource load is correctly recorded.
TEST_F(LoadingPredictorTabHelperTest, ResourceLoadComplete) {
NavigateAndCommitInMainFrameAndVerifyMetrics("http://test.org");
auto resource_load_info = CreateResourceLoadInfo(
"http://test.org/script.js", network::mojom::RequestDestination::kScript);
EXPECT_CALL(*mock_collector_,
RecordResourceLoadComplete(_, Eq(ByRef(*resource_load_info))));
tab_helper_->ResourceLoadComplete(main_rfh(), content::GlobalRequestID(),
*resource_load_info);
}
// Tests that a resource loaded in a subframe is not recorded.
TEST_F(LoadingPredictorTabHelperTest, ResourceLoadCompleteInSubFrame) {
NavigateAndCommitInMainFrameAndVerifyMetrics("http://test.org");
auto* subframe =
content::RenderFrameHostTester::For(main_rfh())->AppendChild("subframe");
NavigateAndCommitInFrame("http://sub.test.org", subframe);
// Resource loaded in subframe shouldn't be recorded.
auto resource_load_info =
CreateResourceLoadInfo("http://sub.test.org/script.js",
network::mojom::RequestDestination::kScript,
/*always_access_network=*/false);
tab_helper_->ResourceLoadComplete(subframe, content::GlobalRequestID(),
*resource_load_info);
}
// Tests that a resource load from the memory cache is correctly recorded.
TEST_F(LoadingPredictorTabHelperTest, LoadResourceFromMemoryCache) {
NavigateAndCommitInMainFrameAndVerifyMetrics("http://test.org");
auto resource_load_info = CreateResourceLoadInfo(
"http://test.org/script.js", network::mojom::RequestDestination::kScript,
false);
resource_load_info->mime_type = "application/javascript";
resource_load_info->network_info->network_accessed = false;
EXPECT_CALL(*mock_collector_,
RecordResourceLoadComplete(_, Eq(ByRef(*resource_load_info))));
tab_helper_->DidLoadResourceFromMemoryCache(
main_rfh(), GURL("http://test.org/script.js"), "application/javascript",
network::mojom::RequestDestination::kScript);
}
class LoadingPredictorTabHelperOptimizationGuideDeciderTest
: public LoadingPredictorTabHelperTest {
public:
LoadingPredictorTabHelperOptimizationGuideDeciderTest() {
scoped_feature_list_.InitWithFeatures(
{features::kLoadingPredictorUseOptimizationGuide,
// Need to add otherwise GetForProfile() returns null.
optimization_guide::features::kOptimizationHints},
{
// Disabling prefetch here to test preconnect passthrough. Prefetch
// is tested in following test class.
features::kLoadingPredictorPrefetch,
// Disable local predictions to ensure that opt guide logic is
// consulted.
features::kLoadingPredictorUseLocalPredictions,
});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Tests that document on load completed is recorded with correct navigation
// id and that optimization guide is not consulted when from same-origin.
TEST_F(LoadingPredictorTabHelperOptimizationGuideDeciderTest,
DocumentOnLoadCompletedOptimizationGuideSameOrigin) {
NavigateAndCommitInMainFrameAndVerifyMetrics("http://test.org");
// Trigger onLoad to get rid of previous prediction.
EXPECT_CALL(*mock_collector_, RecordMainFrameLoadComplete(_, _));
tab_helper_->DocumentOnLoadCompletedInPrimaryMainFrame();
base::HistogramTester histogram_tester;
EXPECT_CALL(
*mock_optimization_guide_keyed_service_,
CanApplyOptimizationAsync(_, optimization_guide::proto::LOADING_PREDICTOR,
base::test::IsNotNullCallback()))
.Times(0);
NavigateAndCommitInMainFrameAndVerifyMetrics("http://test.org/otherpage");
// Adding subframe navigation to ensure that the committed main frame url will
// be used.
auto* subframe =
content::RenderFrameHostTester::For(main_rfh())->AppendChild("subframe");
NavigateAndCommitInFrame("http://sub.test.org", subframe);
const absl::optional<OptimizationGuidePrediction>
null_optimization_guide_prediction;
EXPECT_CALL(*mock_collector_, RecordMainFrameLoadComplete(
_, null_optimization_guide_prediction));
tab_helper_->DocumentOnLoadCompletedInPrimaryMainFrame();
histogram_tester.ExpectTotalCount(
"LoadingPredictor.OptimizationHintsReceiveStatus", 0);
}
// Tests that document on load completed is recorded with correct navigation
// id and optimization guide prediction.
TEST_F(LoadingPredictorTabHelperOptimizationGuideDeciderTest,
DocumentOnLoadCompletedOptimizationGuide) {
base::HistogramTester histogram_tester;
optimization_guide::OptimizationMetadata optimization_metadata;
optimization_guide::proto::LoadingPredictorMetadata lp_metadata;
lp_metadata.add_subresources()->set_url("http://test.org/resource1");
lp_metadata.add_subresources()->set_url("http://other.org/resource2");
lp_metadata.add_subresources()->set_url("http://other.org/resource3");
optimization_metadata.set_loading_predictor_metadata(lp_metadata);
EXPECT_CALL(
*mock_optimization_guide_keyed_service_,
CanApplyOptimizationAsync(_, optimization_guide::proto::LOADING_PREDICTOR,
base::test::IsNotNullCallback()))
.WillOnce(base::test::RunOnceCallback<2>(
optimization_guide::OptimizationGuideDecision::kTrue,
ByRef(optimization_metadata)));
NavigateAndCommitInMainFrameAndVerifyMetrics("http://test.org");
// Adding subframe navigation to ensure that the committed main frame url will
// be used.
auto* subframe =
content::RenderFrameHostTester::For(main_rfh())->AppendChild("subframe");
NavigateAndCommitInFrame("http://sub.test.org", subframe);
absl::optional<OptimizationGuidePrediction> prediction =
OptimizationGuidePrediction();
prediction->decision = optimization_guide::OptimizationGuideDecision::kTrue;
url::Origin main_frame_origin = url::Origin::Create(GURL("http://test.org"));
PreconnectPrediction preconnect_prediction = CreatePreconnectPrediction(
"", false,
{{url::Origin::Create(GURL("http://other.org")), 1,
net::NetworkIsolationKey(main_frame_origin, main_frame_origin)}});
prediction->preconnect_prediction = preconnect_prediction;
prediction->predicted_subresources = {GURL("http://test.org/resource1"),
GURL("http://other.org/resource2"),
GURL("http://other.org/resource3")};
EXPECT_CALL(*mock_collector_, RecordMainFrameLoadComplete(_, prediction));
tab_helper_->DocumentOnLoadCompletedInPrimaryMainFrame();
histogram_tester.ExpectUniqueSample(
"LoadingPredictor.OptimizationHintsReceiveStatus",
OptimizationHintsReceiveStatus::kBeforeNavigationFinish, 1);
}
// Tests that document on load completed is recorded with correct navigation
// id and optimization guide prediction.
TEST_F(LoadingPredictorTabHelperOptimizationGuideDeciderTest,
DocumentOnLoadCompletedOptimizationGuidePredictionComesAfterCommit) {
base::HistogramTester histogram_tester;
optimization_guide::OptimizationMetadata optimization_metadata;
optimization_guide::proto::LoadingPredictorMetadata lp_metadata;
lp_metadata.add_subresources()->set_url("http://test.org/resource1");
lp_metadata.add_subresources()->set_url("http://other.org/resource2");
lp_metadata.add_subresources()->set_url("http://other.org/resource3");
optimization_metadata.set_loading_predictor_metadata(lp_metadata);
optimization_guide::OptimizationGuideDecisionCallback callback;
EXPECT_CALL(
*mock_optimization_guide_keyed_service_,
CanApplyOptimizationAsync(_, optimization_guide::proto::LOADING_PREDICTOR,
base::test::IsNotNullCallback()))
.WillOnce(WithArg<2>(
Invoke([&](optimization_guide::OptimizationGuideDecisionCallback
got_callback) -> void {
callback = std::move(got_callback);
})));
NavigateAndCommitInMainFrameAndVerifyMetrics("http://test.org");
// Invoke callback after commit.
std::move(callback).Run(optimization_guide::OptimizationGuideDecision::kTrue,
optimization_metadata);
// Adding subframe navigation to ensure that the committed main frame url will
// be used.
auto* subframe =
content::RenderFrameHostTester::For(main_rfh())->AppendChild("subframe");
NavigateAndCommitInFrame("http://sub.test.org", subframe);
absl::optional<OptimizationGuidePrediction> prediction =
OptimizationGuidePrediction();
prediction->decision = optimization_guide::OptimizationGuideDecision::kTrue;
url::Origin main_frame_origin = url::Origin::Create(GURL("http://test.org"));
PreconnectPrediction preconnect_prediction = CreatePreconnectPrediction(
"", false,
{{url::Origin::Create(GURL("http://other.org")), 1,
net::NetworkIsolationKey(main_frame_origin, main_frame_origin)}});
prediction->preconnect_prediction = preconnect_prediction;
prediction->predicted_subresources = {GURL("http://test.org/resource1"),
GURL("http://other.org/resource2"),
GURL("http://other.org/resource3")};
EXPECT_CALL(*mock_collector_, RecordMainFrameLoadComplete(_, prediction));
tab_helper_->DocumentOnLoadCompletedInPrimaryMainFrame();
// Optimization guide predictions came after commit.
histogram_tester.ExpectUniqueSample(
"LoadingPredictor.OptimizationHintsReceiveStatus",
OptimizationHintsReceiveStatus::kAfterNavigationFinish, 1);
}
// Tests that predictions are recorded correctly when they arrive after a
// redirect.
TEST_F(LoadingPredictorTabHelperOptimizationGuideDeciderTest,
DocumentOnLoadCompletedOptimizationGuidePredictionArrivedAfterRedirect) {
base::HistogramTester histogram_tester;
auto navigation = content::NavigationSimulator::CreateRendererInitiated(
GURL("http://test.org"), main_rfh());
// The problem here is that mock_collector_ is a strict mock, which expects
// a particular set of loading events and fails when extra is present.
// TOOO(ahemery): Consider refactoring this to rely on loading events
// in NavigationSimulator.
navigation->SetKeepLoading(true);
EXPECT_CALL(*mock_collector_, RecordStartNavigation(_, _, _, _));
optimization_guide::OptimizationMetadata optimization_metadata;
optimization_guide::proto::LoadingPredictorMetadata lp_metadata;
lp_metadata.add_subresources()->set_url("http://test.org/resource1");
lp_metadata.add_subresources()->set_url("http://other.org/resource2");
lp_metadata.add_subresources()->set_url("http://other.org/resource3");
optimization_metadata.set_loading_predictor_metadata(lp_metadata);
optimization_guide::OptimizationGuideDecisionCallback callback;
EXPECT_CALL(
*mock_optimization_guide_keyed_service_,
CanApplyOptimizationAsync(_, optimization_guide::proto::LOADING_PREDICTOR,
base::test::IsNotNullCallback()))
.Times(3)
.WillOnce(WithArg<2>(
Invoke([&](optimization_guide::OptimizationGuideDecisionCallback
got_callback) -> void {
callback = std::move(got_callback);
})))
.WillRepeatedly(Return());
navigation->Start();
navigation->Redirect(GURL("http://test2.org"));
navigation->Redirect(GURL("http://test3.org"));
std::move(callback).Run(optimization_guide::OptimizationGuideDecision::kTrue,
optimization_metadata);
EXPECT_CALL(*mock_collector_,
RecordFinishNavigation(_, _, _,
/* is_error_page */ false));
navigation->Commit();
// Prediction decision should be unknown since what came in was for the wrong
// navigation ID.
absl::optional<OptimizationGuidePrediction> optimization_guide_prediction =
OptimizationGuidePrediction();
optimization_guide_prediction->decision =
optimization_guide::OptimizationGuideDecision::kUnknown;
EXPECT_CALL(*mock_collector_, RecordMainFrameLoadComplete(_, _));
tab_helper_->DocumentOnLoadCompletedInPrimaryMainFrame();
histogram_tester.ExpectUniqueSample(
"LoadingPredictor.OptimizationHintsReceiveStatus",
OptimizationHintsReceiveStatus::kAfterRedirectOrNextNavigationStart, 1);
}
// Tests that document on load completed is recorded with correct navigation
// id and optimization guide prediction when the prediction has not arrived.
TEST_F(LoadingPredictorTabHelperOptimizationGuideDeciderTest,
DocumentOnLoadCompletedOptimizationGuidePredictionHasNotArrived) {
base::HistogramTester histogram_tester;
EXPECT_CALL(
*mock_optimization_guide_keyed_service_,
CanApplyOptimizationAsync(_, optimization_guide::proto::LOADING_PREDICTOR,
base::test::IsNotNullCallback()));
NavigateAndCommitInMainFrameAndVerifyMetrics("http://test.org");
// Adding subframe navigation to ensure that the committed main frame url will
// be used.
auto* subframe =
content::RenderFrameHostTester::For(main_rfh())->AppendChild("subframe");
NavigateAndCommitInFrame("http://sub.test.org", subframe);
absl::optional<OptimizationGuidePrediction> prediction =
OptimizationGuidePrediction();
prediction->decision =
optimization_guide::OptimizationGuideDecision::kUnknown;
EXPECT_CALL(*mock_collector_, RecordMainFrameLoadComplete(_, prediction));
tab_helper_->DocumentOnLoadCompletedInPrimaryMainFrame();
// Histogram should not be recorded since prediction did not come back.
histogram_tester.ExpectTotalCount(
"LoadingPredictor.OptimizationHintsReceiveStatus", 0);
}
// Tests that document on load completed is recorded with correct navigation
// id and optimization guide prediction and does not crash if callback comes
// after everything has been recorded.
TEST_F(
LoadingPredictorTabHelperOptimizationGuideDeciderTest,
DocumentOnLoadCompletedOptimizationGuidePredictionComesAfterDocumentOnLoad) {
base::HistogramTester histogram_tester;
optimization_guide::OptimizationMetadata optimization_metadata;
optimization_guide::proto::LoadingPredictorMetadata lp_metadata;
lp_metadata.add_subresources()->set_url("http://test.org/resource1");
lp_metadata.add_subresources()->set_url("http://other.org/resource2");
lp_metadata.add_subresources()->set_url("http://other.org/resource3");
optimization_metadata.set_loading_predictor_metadata(lp_metadata);
optimization_guide::OptimizationGuideDecisionCallback callback;
EXPECT_CALL(
*mock_optimization_guide_keyed_service_,
CanApplyOptimizationAsync(_, optimization_guide::proto::LOADING_PREDICTOR,
base::test::IsNotNullCallback()))
.WillOnce(WithArg<2>(
Invoke([&](optimization_guide::OptimizationGuideDecisionCallback
got_callback) -> void {
callback = std::move(got_callback);
})));
NavigateAndCommitInMainFrameAndVerifyMetrics("http://test.org");
// Adding subframe navigation to ensure that the committed main frame url will
// be used.
auto* subframe =
content::RenderFrameHostTester::For(main_rfh())->AppendChild("subframe");
NavigateAndCommitInFrame("http://sub.test.org", subframe);
absl::optional<OptimizationGuidePrediction> prediction =
OptimizationGuidePrediction();
prediction->decision =
optimization_guide::OptimizationGuideDecision::kUnknown;
EXPECT_CALL(*mock_collector_, RecordMainFrameLoadComplete(_, prediction));
tab_helper_->DocumentOnLoadCompletedInPrimaryMainFrame();
// Invoke callback after document completed in main frame..
std::move(callback).Run(optimization_guide::OptimizationGuideDecision::kTrue,
optimization_metadata);
// Optimization guide predictions came after commit.
histogram_tester.ExpectUniqueSample(
"LoadingPredictor.OptimizationHintsReceiveStatus",
OptimizationHintsReceiveStatus::kAfterNavigationFinish, 1);
}
// Tests that document on load completed is recorded with correct navigation
// id and optimization guide prediction with no prediction..
TEST_F(LoadingPredictorTabHelperOptimizationGuideDeciderTest,
DocumentOnLoadCompletedOptimizationGuidePredictionArrivedNoPrediction) {
base::HistogramTester histogram_tester;
// The problem here is that mock_collector_ is a strict mock, which expects
// a particular set of loading events and fails when extra is present.
// TOOO(ahemery): Consider refactoring this to rely on loading events
// in NavigationSimulator.
optimization_guide::OptimizationMetadata optimization_metadata;
EXPECT_CALL(
*mock_optimization_guide_keyed_service_,
CanApplyOptimizationAsync(_, optimization_guide::proto::LOADING_PREDICTOR,
base::test::IsNotNullCallback()))
.WillOnce(base::test::RunOnceCallback<2>(
optimization_guide::OptimizationGuideDecision::kFalse,
ByRef(optimization_metadata)));
NavigateAndCommitInMainFrameAndVerifyMetrics("http://test.org");
// Adding subframe navigation to ensure that the committed main frame url will
// be used.
auto* subframe =
content::RenderFrameHostTester::For(main_rfh())->AppendChild("subframe");
NavigateAndCommitInFrame("http://sub.test.org", subframe);
absl::optional<OptimizationGuidePrediction> prediction =
OptimizationGuidePrediction();
prediction->decision = optimization_guide::OptimizationGuideDecision::kFalse;
EXPECT_CALL(*mock_collector_, RecordMainFrameLoadComplete(_, prediction));
tab_helper_->DocumentOnLoadCompletedInPrimaryMainFrame();
// Histogram should still be recorded even though no predictions were
// returned.
histogram_tester.ExpectUniqueSample(
"LoadingPredictor.OptimizationHintsReceiveStatus",
OptimizationHintsReceiveStatus::kBeforeNavigationFinish, 1);
}
// Tests that document on load completed is recorded with correct navigation
// id and optimization guide prediction with no prediction..
TEST_F(
LoadingPredictorTabHelperOptimizationGuideDeciderTest,
DocumentOnLoadCompletedOptimizationGuidePredictionArrivedNoLoadingPredictorMetadata) {
base::HistogramTester histogram_tester;
// The problem here is that mock_collector_ is a strict mock, which expects
// a particular set of loading events and fails when extra is present.
// TOOO(ahemery): Consider refactoring this to rely on loading events
// in NavigationSimulator.
optimization_guide::OptimizationMetadata optimization_metadata;
EXPECT_CALL(
*mock_optimization_guide_keyed_service_,
CanApplyOptimizationAsync(_, optimization_guide::proto::LOADING_PREDICTOR,
base::test::IsNotNullCallback()))
.WillOnce(base::test::RunOnceCallback<2>(
optimization_guide::OptimizationGuideDecision::kTrue,
ByRef(optimization_metadata)));
NavigateAndCommitInMainFrameAndVerifyMetrics("http://test.org");
// Adding subframe navigation to ensure that the committed main frame url will
// be used.
auto* subframe =
content::RenderFrameHostTester::For(main_rfh())->AppendChild("subframe");
NavigateAndCommitInFrame("http://sub.test.org", subframe);
// Decision should be unknown since we got invalid data.
absl::optional<OptimizationGuidePrediction> optimization_guide_prediction =
OptimizationGuidePrediction();
optimization_guide_prediction->decision =
optimization_guide::OptimizationGuideDecision::kUnknown;
EXPECT_CALL(*mock_collector_,
RecordMainFrameLoadComplete(_, optimization_guide_prediction));
tab_helper_->DocumentOnLoadCompletedInPrimaryMainFrame();
// Histogram should still be recorded even though no predictions were
// returned.
histogram_tester.ExpectUniqueSample(
"LoadingPredictor.OptimizationHintsReceiveStatus",
OptimizationHintsReceiveStatus::kBeforeNavigationFinish, 1);
}
class LoadingPredictorTabHelperOptimizationGuideDeciderWithPrefetchTest
: public LoadingPredictorTabHelperOptimizationGuideDeciderTest {
public:
LoadingPredictorTabHelperOptimizationGuideDeciderWithPrefetchTest() {
scoped_feature_list_.InitWithFeatures(
{features::kLoadingPredictorUseOptimizationGuide,
features::kLoadingPredictorPrefetch,
// Need to add otherwise GetForProfile() returns null.
optimization_guide::features::kOptimizationHints},
{});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
variations::ScopedVariationsIdsProvider scoped_variations_ids_provider_{
variations::VariationsIdsProvider::Mode::kUseSignedInState};
};
// Tests that document on load completed is recorded with correct navigation
// id and optimization guide prediction.
TEST_F(LoadingPredictorTabHelperOptimizationGuideDeciderWithPrefetchTest,
DocumentOnLoadCompletedOptimizationGuide) {
base::HistogramTester histogram_tester;
optimization_guide::OptimizationMetadata optimization_metadata;
optimization_guide::proto::LoadingPredictorMetadata lp_metadata;
lp_metadata.add_subresources()->set_url("http://test.org/resource1");
lp_metadata.add_subresources()->set_url("http://other.org/resource2");
lp_metadata.add_subresources()->set_url("http://other.org/resource3");
auto* preconnect_only_resource = lp_metadata.add_subresources();
preconnect_only_resource->set_url("http://preconnectonly.com/");
preconnect_only_resource->set_preconnect_only(true);
optimization_metadata.set_loading_predictor_metadata(lp_metadata);
EXPECT_CALL(
*mock_optimization_guide_keyed_service_,
CanApplyOptimizationAsync(_, optimization_guide::proto::LOADING_PREDICTOR,
base::test::IsNotNullCallback()))
.WillOnce(base::test::RunOnceCallback<2>(
optimization_guide::OptimizationGuideDecision::kTrue,
ByRef(optimization_metadata)));
NavigateAndCommitInMainFrameAndVerifyMetrics("http://test.org");
// Adding subframe navigation to ensure that the committed main frame url will
// be used.
auto* subframe =
content::RenderFrameHostTester::For(main_rfh())->AppendChild("subframe");
NavigateAndCommitInFrame("http://sub.test.org", subframe);
absl::optional<OptimizationGuidePrediction> prediction =
OptimizationGuidePrediction();
prediction->decision = optimization_guide::OptimizationGuideDecision::kTrue;
url::Origin main_frame_origin = url::Origin::Create(GURL("http://test.org"));
net::NetworkIsolationKey network_isolation_key(main_frame_origin,
main_frame_origin);
network::mojom::RequestDestination destination =
network::mojom::RequestDestination::kEmpty;
PreconnectPrediction preconnect_prediction = CreatePreconnectPrediction(
"", false,
{{url::Origin::Create(GURL("http://preconnectonly.com/")), 1,
network_isolation_key}});
preconnect_prediction.prefetch_requests.emplace_back(
GURL("http://test.org/resource1"), network_isolation_key, destination);
preconnect_prediction.prefetch_requests.emplace_back(
GURL("http://other.org/resource1"), network_isolation_key, destination);
preconnect_prediction.prefetch_requests.emplace_back(
GURL("http://other.org/resource2"), network_isolation_key, destination);
prediction->preconnect_prediction = preconnect_prediction;
prediction->predicted_subresources = {
GURL("http://test.org/resource1"), GURL("http://other.org/resource2"),
GURL("http://other.org/resource3"), GURL("http://preconnectonly.com/")};
EXPECT_CALL(*mock_collector_, RecordMainFrameLoadComplete(_, prediction));
tab_helper_->DocumentOnLoadCompletedInPrimaryMainFrame();
histogram_tester.ExpectUniqueSample(
"LoadingPredictor.OptimizationHintsReceiveStatus",
OptimizationHintsReceiveStatus::kBeforeNavigationFinish, 1);
}
class TestLoadingDataCollector : public LoadingDataCollector {
public:
explicit TestLoadingDataCollector(const LoadingPredictorConfig& config);
void RecordStartNavigation(NavigationId navigation_id,
ukm::SourceId ukm_source_id,
const GURL& main_frame_url,
base::TimeTicks creation_time) override {}
void RecordFinishNavigation(NavigationId navigation_id,
const GURL& old_main_frame_url,
const GURL& new_main_frame_url,
bool is_error_page) override {}
void RecordResourceLoadComplete(
NavigationId navigation_id,
const blink::mojom::ResourceLoadInfo& resource_load_info) override {
++count_resource_loads_completed_;
EXPECT_EQ(expected_request_priority_, resource_load_info.request_priority);
}
void RecordMainFrameLoadComplete(
NavigationId navigation_id,
const absl::optional<OptimizationGuidePrediction>&
optimization_guide_prediction) override {}
void RecordFirstContentfulPaint(
NavigationId navigation_id,
base::TimeTicks first_contentful_paint) override {}
void SetExpectedResourcePriority(net::RequestPriority request_priority) {
expected_request_priority_ = request_priority;
}
size_t count_resource_loads_completed() const {
return count_resource_loads_completed_;
}
private:
net::RequestPriority expected_request_priority_ = net::HIGHEST;
size_t count_resource_loads_completed_ = 0;
};
TestLoadingDataCollector::TestLoadingDataCollector(
const LoadingPredictorConfig& config)
: LoadingDataCollector(nullptr, nullptr, config) {}
class LoadingPredictorTabHelperTestCollectorTest
: public LoadingPredictorTabHelperTest {
public:
void SetUp() override;
protected:
raw_ptr<TestLoadingDataCollector> test_collector_;
};
void LoadingPredictorTabHelperTestCollectorTest::SetUp() {
ChromeRenderViewHostTestHarness::SetUp();
CreateSessionServiceTabHelper(web_contents());
LoadingPredictorTabHelper::CreateForWebContents(web_contents());
tab_helper_ = LoadingPredictorTabHelper::FromWebContents(web_contents());
LoadingPredictorConfig config;
PopulateTestConfig(&config);
loading_predictor_ = std::make_unique<LoadingPredictor>(config, profile());
auto test_collector = std::make_unique<TestLoadingDataCollector>(config);
test_collector_ = test_collector.get();
loading_predictor_->set_mock_loading_data_collector(
std::move(test_collector));
tab_helper_->SetLoadingPredictorForTesting(loading_predictor_->GetWeakPtr());
}
// Tests that a resource load is correctly recorded with the correct priority.
TEST_F(LoadingPredictorTabHelperTestCollectorTest, ResourceLoadComplete) {
NavigateAndCommitInFrame("http://test.org", main_rfh());
// Set expected priority to HIGHEST and load a HIGHEST priority resource.
test_collector_->SetExpectedResourcePriority(net::HIGHEST);
auto resource_load_info = CreateResourceLoadInfo(
"http://test.org/script.js", network::mojom::RequestDestination::kScript);
tab_helper_->ResourceLoadComplete(main_rfh(), content::GlobalRequestID(),
*resource_load_info);
EXPECT_EQ(1u, test_collector_->count_resource_loads_completed());
// Set expected priority to LOWEST and load a LOWEST priority resource.
test_collector_->SetExpectedResourcePriority(net::LOWEST);
resource_load_info = CreateLowPriorityResourceLoadInfo(
"http://test.org/script.js", network::mojom::RequestDestination::kScript);
tab_helper_->ResourceLoadComplete(main_rfh(), content::GlobalRequestID(),
*resource_load_info);
EXPECT_EQ(2u, test_collector_->count_resource_loads_completed());
}
class LoadingPredictorTabHelperTestCollectorFencedFramesTest
: public LoadingPredictorTabHelperTestCollectorTest {
public:
LoadingPredictorTabHelperTestCollectorFencedFramesTest() {
scoped_feature_list_.InitAndEnableFeatureWithParameters(
blink::features::kFencedFrames, {{"implementation_type", "mparch"}});
}
~LoadingPredictorTabHelperTestCollectorFencedFramesTest() override = default;
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_F(LoadingPredictorTabHelperTestCollectorFencedFramesTest,
DoNotRecordResourceLoadComplete) {
NavigateAndCommitInFrame("http://test.org", main_rfh());
content::RenderFrameHost* fenced_frame_root =
content::RenderFrameHostTester::For(main_rfh())->AppendFencedFrame();
// Navigate a fenced frame.
GURL fenced_frame_url = GURL("https://fencedframe.com");
std::unique_ptr<content::NavigationSimulator> navigation_simulator =
content::NavigationSimulator::CreateForFencedFrame(fenced_frame_url,
fenced_frame_root);
navigation_simulator->Commit();
EXPECT_EQ(0u, test_collector_->count_resource_loads_completed());
// Load a sub resource on the main frame and record it.
auto resource_load_info = CreateResourceLoadInfo(
"http://test.org/script.js", network::mojom::RequestDestination::kScript);
tab_helper_->ResourceLoadComplete(main_rfh(), content::GlobalRequestID(),
*resource_load_info);
EXPECT_EQ(1u, test_collector_->count_resource_loads_completed());
// Load a sub resource on the fenced frame and do not record it.
tab_helper_->ResourceLoadComplete(
fenced_frame_root, content::GlobalRequestID(), *resource_load_info);
EXPECT_EQ(1u, test_collector_->count_resource_loads_completed());
}
} // namespace predictors