#include "content/browser/preloading/prerender/prerender_host.h"
#include <memory>
#include "base/functional/bind.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/gmock_expected_support.h"
#include "build/build_config.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/browser/preloading/preload_pipeline_info_impl.h"
#include "content/browser/preloading/preloading.h"
#include "content/browser/preloading/prerender/prerender_attributes.h"
#include "content/browser/preloading/prerender/prerender_final_status.h"
#include "content/browser/preloading/prerender/prerender_host.h"
#include "content/browser/preloading/prerender/prerender_host_registry.h"
#include "content/browser/preloading/prerender/prerender_metrics.h"
#include "content/public/browser/preload_pipeline_info.h"
#include "content/public/browser/preloading.h"
#include "content/public/browser/preloading_data.h"
#include "content/public/test/mock_web_contents_observer.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/preloading_test_util.h"
#include "content/public/test/prerender_test_util.h"
#include "content/test/mock_commit_deferring_condition.h"
#include "content/test/navigation_simulator_impl.h"
#include "content/test/test_render_frame_host.h"
#include "content/test/test_render_view_host.h"
#include "content/test/test_web_contents.h"
#include "net/http/http_request_headers.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "third_party/blink/public/common/loader/loader_constants.h"
#include "third_party/blink/public/mojom/speculation_rules/speculation_rules.mojom-shared.h"
namespace content {
namespace {
using ::testing::_;
TEST(IsActivationHeaderMatchTest, OrderInsensitive) {
PrerenderCancellationReason reason = PrerenderCancellationReason(
PrerenderFinalStatus::kActivationNavigationParameterMismatch);
net::HttpRequestHeaders prerender_headers;
prerender_headers.SetHeader("name1", "value1");
prerender_headers.SetHeader("name2", "value2");
prerender_headers.SetHeader("name3", "value3");
net::HttpRequestHeaders potential_activation_headers;
potential_activation_headers.SetHeader("name2", "value2");
potential_activation_headers.SetHeader("name3", "value3");
potential_activation_headers.SetHeader("name1", "value1");
EXPECT_TRUE(PrerenderHost::IsActivationHeaderMatch(
potential_activation_headers, prerender_headers, reason));
}
TEST(IsActivationHeaderMatchTest, KeyCaseInsensitive) {
PrerenderCancellationReason reason = PrerenderCancellationReason(
PrerenderFinalStatus::kActivationNavigationParameterMismatch);
net::HttpRequestHeaders prerender_headers;
prerender_headers.SetHeader("NAME1", "value1");
prerender_headers.SetHeader("name2", "value2");
prerender_headers.SetHeader("name3", "value3");
net::HttpRequestHeaders potential_activation_headers;
potential_activation_headers.SetHeader("name1", "value1");
potential_activation_headers.SetHeader("name2", "value2");
potential_activation_headers.SetHeader("name3", "value3");
EXPECT_TRUE(PrerenderHost::IsActivationHeaderMatch(
potential_activation_headers, prerender_headers, reason));
}
TEST(IsActivationHeaderMatchTest, ValueCaseInsensitive) {
PrerenderCancellationReason reason = PrerenderCancellationReason(
PrerenderFinalStatus::kActivationNavigationParameterMismatch);
net::HttpRequestHeaders prerender_headers;
prerender_headers.SetHeader("name1", "value1");
prerender_headers.SetHeader("name2", "value2");
prerender_headers.SetHeader("name3", "value3");
net::HttpRequestHeaders potential_activation_headers;
potential_activation_headers.SetHeader("name1", "value1");
potential_activation_headers.SetHeader("name2", "VALUE2");
potential_activation_headers.SetHeader("name3", "value3");
EXPECT_TRUE(PrerenderHost::IsActivationHeaderMatch(
potential_activation_headers, prerender_headers, reason));
}
TEST(IsActivationHeaderMatchTest, CalculateMismatchedHeaders) {
auto same_key_value = [](const PrerenderMismatchedHeaders& a,
const PrerenderMismatchedHeaders& b) {
return a.header_name == b.header_name &&
a.initial_value == b.initial_value &&
a.activation_value == b.activation_value;
};
{
PrerenderCancellationReason reason = PrerenderCancellationReason(
PrerenderFinalStatus::kActivationNavigationParameterMismatch);
net::HttpRequestHeaders prerender_headers;
prerender_headers.SetHeader("name1", "value1");
prerender_headers.SetHeader("name2", "value2");
prerender_headers.SetHeader("name3", "value3");
net::HttpRequestHeaders potential_headers;
potential_headers.SetHeader("name1", "value1");
potential_headers.SetHeader("name2", "value2");
potential_headers.SetHeader("name3", "value3");
EXPECT_TRUE(PrerenderHost::IsActivationHeaderMatch(
potential_headers, prerender_headers, reason));
EXPECT_FALSE(reason.GetPrerenderMismatchedHeaders());
}
{
PrerenderCancellationReason reason = PrerenderCancellationReason(
PrerenderFinalStatus::kActivationNavigationParameterMismatch);
net::HttpRequestHeaders prerender_headers;
net::HttpRequestHeaders potential_headers;
EXPECT_TRUE(PrerenderHost::IsActivationHeaderMatch(
potential_headers, prerender_headers, reason));
EXPECT_FALSE(reason.GetPrerenderMismatchedHeaders());
}
{
PrerenderCancellationReason reason = PrerenderCancellationReason(
PrerenderFinalStatus::kActivationNavigationParameterMismatch);
net::HttpRequestHeaders prerender_headers;
prerender_headers.SetHeader("name1", "value1");
prerender_headers.SetHeader("name2", "value2");
prerender_headers.SetHeader("name3", "value3");
prerender_headers.SetHeader("name5", "value3");
net::HttpRequestHeaders potential_headers;
potential_headers.SetHeader("name1", "value1");
potential_headers.SetHeader("name3", "value2");
potential_headers.SetHeader("name4", "value4");
potential_headers.SetHeader("name5", "value3");
EXPECT_FALSE(PrerenderHost::IsActivationHeaderMatch(
potential_headers, prerender_headers, reason));
std::vector<PrerenderMismatchedHeaders> mismatched_headers_expected;
mismatched_headers_expected.emplace_back("name2", "value2", std::nullopt);
mismatched_headers_expected.emplace_back("name3", "value3", "value2");
mismatched_headers_expected.emplace_back("name4", std::nullopt, "value4");
EXPECT_TRUE(std::equal(reason.GetPrerenderMismatchedHeaders()->begin(),
reason.GetPrerenderMismatchedHeaders()->end(),
mismatched_headers_expected.begin(),
mismatched_headers_expected.end(), same_key_value));
}
{
PrerenderCancellationReason reason = PrerenderCancellationReason(
PrerenderFinalStatus::kActivationNavigationParameterMismatch);
net::HttpRequestHeaders prerender_headers;
prerender_headers.SetHeader("name5", "value1");
prerender_headers.SetHeader("name6", "value2");
prerender_headers.SetHeader("name7", "value3");
net::HttpRequestHeaders potential_headers;
potential_headers.SetHeader("name2", "value1");
EXPECT_FALSE(PrerenderHost::IsActivationHeaderMatch(
potential_headers, prerender_headers, reason));
std::vector<PrerenderMismatchedHeaders> mismatched_headers_expected;
mismatched_headers_expected.emplace_back("name2", std::nullopt, "value1");
mismatched_headers_expected.emplace_back("name5", "value1", std::nullopt);
mismatched_headers_expected.emplace_back("name6", "value2", std::nullopt);
mismatched_headers_expected.emplace_back("name7", "value3", std::nullopt);
EXPECT_TRUE(std::equal(reason.GetPrerenderMismatchedHeaders()->begin(),
reason.GetPrerenderMismatchedHeaders()->end(),
mismatched_headers_expected.begin(),
mismatched_headers_expected.end(), same_key_value));
}
{
PrerenderCancellationReason reason = PrerenderCancellationReason(
PrerenderFinalStatus::kActivationNavigationParameterMismatch);
net::HttpRequestHeaders prerender_headers;
prerender_headers.SetHeader("name5", "value1");
prerender_headers.SetHeader("name6", "value2");
net::HttpRequestHeaders potential_headers;
potential_headers.SetHeader("name2", "value1");
potential_headers.SetHeader("name6", "value2");
potential_headers.SetHeader("name7", "value3");
potential_headers.SetHeader("name8", "value3");
EXPECT_FALSE(PrerenderHost::IsActivationHeaderMatch(
potential_headers, prerender_headers, reason));
std::vector<PrerenderMismatchedHeaders> mismatched_headers_expected;
mismatched_headers_expected.emplace_back("name2", std::nullopt, "value1");
mismatched_headers_expected.emplace_back("name5", "value1", std::nullopt);
mismatched_headers_expected.emplace_back("name7", std::nullopt, "value3");
mismatched_headers_expected.emplace_back("name8", std::nullopt, "value3");
EXPECT_TRUE(std::equal(reason.GetPrerenderMismatchedHeaders()->begin(),
reason.GetPrerenderMismatchedHeaders()->end(),
mismatched_headers_expected.begin(),
mismatched_headers_expected.end(), same_key_value));
}
{
PrerenderCancellationReason reason = PrerenderCancellationReason(
PrerenderFinalStatus::kActivationNavigationParameterMismatch);
net::HttpRequestHeaders prerender_headers;
net::HttpRequestHeaders potential_headers;
potential_headers.SetHeader("name1", "value1");
potential_headers.SetHeader("name2", "value2");
potential_headers.SetHeader("name3", "value3");
EXPECT_FALSE(PrerenderHost::IsActivationHeaderMatch(
potential_headers, prerender_headers, reason));
std::vector<PrerenderMismatchedHeaders> mismatched_headers_expected;
mismatched_headers_expected.emplace_back("name1", std::nullopt, "value1");
mismatched_headers_expected.emplace_back("name2", std::nullopt, "value2");
mismatched_headers_expected.emplace_back("name3", std::nullopt, "value3");
EXPECT_TRUE(std::equal(reason.GetPrerenderMismatchedHeaders()->begin(),
reason.GetPrerenderMismatchedHeaders()->end(),
mismatched_headers_expected.begin(),
mismatched_headers_expected.end(), same_key_value));
}
{
PrerenderCancellationReason reason = PrerenderCancellationReason(
PrerenderFinalStatus::kActivationNavigationParameterMismatch);
net::HttpRequestHeaders prerender_headers;
prerender_headers.SetHeader("name1", "value1");
prerender_headers.SetHeader("name2", "value2");
prerender_headers.SetHeader("name3", "value3");
net::HttpRequestHeaders potential_headers;
EXPECT_FALSE(PrerenderHost::IsActivationHeaderMatch(
potential_headers, prerender_headers, reason));
std::vector<PrerenderMismatchedHeaders> mismatched_headers_expected;
mismatched_headers_expected.emplace_back("name1", "value1", std::nullopt);
mismatched_headers_expected.emplace_back("name2", "value2", std::nullopt);
mismatched_headers_expected.emplace_back("name3", "value3", std::nullopt);
EXPECT_TRUE(std::equal(reason.GetPrerenderMismatchedHeaders()->begin(),
reason.GetPrerenderMismatchedHeaders()->end(),
mismatched_headers_expected.begin(),
mismatched_headers_expected.end(), same_key_value));
}
}
using ExpectedReadyForActivationState =
base::StrongAlias<class ExpectedReadyForActivationStateType, bool>;
void CommitPrerenderNavigation(
PrerenderHost& host,
ExpectedReadyForActivationState ready_for_activation =
ExpectedReadyForActivationState(true),
scoped_refptr<net::HttpResponseHeaders> headers = nullptr) {
FrameTreeNode* ftn = FrameTreeNode::From(host.GetPrerenderedMainFrameHost());
std::unique_ptr<NavigationSimulator> sim =
NavigationSimulatorImpl::CreateFromPendingInFrame(ftn);
sim->SetResponseHeaders(headers);
sim->Commit();
EXPECT_EQ(host.is_ready_for_activation(), ready_for_activation.value());
}
std::unique_ptr<NavigationSimulatorImpl> CreateActivation(
const GURL& prerendering_url,
WebContentsImpl& web_contents) {
std::unique_ptr<NavigationSimulatorImpl> navigation =
NavigationSimulatorImpl::CreateRendererInitiated(
prerendering_url, web_contents.GetPrimaryMainFrame());
navigation->SetReferrer(blink::mojom::Referrer::New(
web_contents.GetPrimaryMainFrame()->GetLastCommittedURL(),
network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin));
return navigation;
}
class PrerenderHostTest : public RenderViewHostImplTestHarness {
public:
~PrerenderHostTest() override = default;
void SetUp() override {
RenderViewHostImplTestHarness::SetUp();
web_contents_delegate_ =
std::make_unique<test::ScopedPrerenderWebContentsDelegate>(*contents());
contents()->NavigateAndCommit(GURL("https://example.com"));
}
PrerenderAttributes GeneratePrerenderAttributes(const GURL& url) {
return GeneratePrerenderAttributesWithPredicate(url,
{});
}
PrerenderAttributes GeneratePrerenderAttributesWithPredicate(
const GURL& url,
base::RepeatingCallback<bool(const GURL&,
const std::optional<content::UrlMatchType>&)>
url_match_predicate) {
RenderFrameHostImpl* rfh = contents()->GetPrimaryMainFrame();
return PrerenderAttributes(
url, PreloadingTriggerType::kSpeculationRule,
"", SpeculationRulesParams(), Referrer(),
std::nullopt, rfh, contents()->GetWeakPtr(),
ui::PAGE_TRANSITION_LINK,
false,
false,
blink::mojom::SpeculationAction::kPrerender,
std::move(url_match_predicate),
{},
PreloadPipelineInfoImpl::Create(
PreloadingType::kPrerender),
false,
false);
}
void ExpectFinalStatus(PrerenderFinalStatus status) {
histogram_tester_.ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
status, 1);
bool final_status_entry_found = false;
const auto entries = ukm_recorder_.GetEntriesByName(
ukm::builders::PrerenderPageLoad::kEntryName);
for (const ukm::mojom::UkmEntry* entry : entries) {
if (ukm_recorder_.EntryHasMetric(
entry, ukm::builders::PrerenderPageLoad::kFinalStatusName)) {
final_status_entry_found = true;
ukm_recorder_.ExpectEntryMetric(
entry, ukm::builders::PrerenderPageLoad::kFinalStatusName,
static_cast<int>(status));
}
}
EXPECT_TRUE(final_status_entry_found);
}
PrerenderHostRegistry& registry() {
return *contents()->GetPrerenderHostRegistry();
}
private:
test::ScopedPrerenderFeatureList prerender_feature_list_;
std::unique_ptr<test::ScopedPrerenderWebContentsDelegate>
web_contents_delegate_;
base::HistogramTester histogram_tester_;
ukm::TestAutoSetUkmRecorder ukm_recorder_;
};
TEST_F(PrerenderHostTest, IsNoVarySearchHeaderSet) {
const GURL kPrerenderingUrl("https://example.com/next");
FrameTreeNodeId prerender_frame_tree_node_id =
contents()->AddPrerender(kPrerenderingUrl);
PrerenderHost* prerender_host =
registry().FindNonReservedHostById(prerender_frame_tree_node_id);
CommitPrerenderNavigation(
*prerender_host, ExpectedReadyForActivationState(true),
net::HttpResponseHeaders::Builder(net::HttpVersion(1, 1), "200 OK")
.AddHeader("No-Vary-Search", "params=(\"a\")")
.Build());
EXPECT_TRUE(prerender_host->no_vary_search().has_value());
}
TEST_F(PrerenderHostTest, Activate) {
const GURL kPrerenderingUrl("https://example.com/next");
FrameTreeNodeId prerender_frame_tree_node_id =
contents()->AddPrerender(kPrerenderingUrl);
PrerenderHost* prerender_host =
registry().FindNonReservedHostById(prerender_frame_tree_node_id);
CommitPrerenderNavigation(*prerender_host);
contents()->ActivatePrerenderedPage(kPrerenderingUrl);
ExpectFinalStatus(PrerenderFinalStatus::kActivated);
}
TEST_F(PrerenderHostTest, DontActivate) {
const GURL kPrerenderingUrl("https://example.com/next");
const FrameTreeNodeId prerender_frame_tree_node_id =
contents()->AddPrerender(kPrerenderingUrl);
registry().CancelHost(prerender_frame_tree_node_id,
PrerenderFinalStatus::kDestroyed);
ExpectFinalStatus(PrerenderFinalStatus::kDestroyed);
}
TEST_F(PrerenderHostTest, MainFrameNavigationForReservedHost) {
const GURL kPrerenderingUrl("https://example.com/next");
RenderFrameHostImpl* prerender_rfh =
contents()->AddPrerenderAndCommitNavigation(kPrerenderingUrl);
FrameTreeNode* ftn = prerender_rfh->frame_tree_node();
EXPECT_FALSE(ftn->HasNavigation());
test::PrerenderHostObserver prerender_host_observer(*contents(),
kPrerenderingUrl);
std::unique_ptr<NavigationSimulatorImpl> navigation;
{
MockCommitDeferringConditionInstaller installer(
kPrerenderingUrl, CommitDeferringCondition::Result::kDefer);
navigation = CreateActivation(kPrerenderingUrl, *contents());
navigation->Start();
installer.WaitUntilInstalled();
installer.condition().WaitUntilInvoked();
NavigationRequest* navigation_request =
static_cast<NavigationRequest*>(navigation->GetNavigationHandle());
EXPECT_TRUE(
navigation_request->IsCommitDeferringConditionDeferredForTesting());
EXPECT_EQ(contents()->GetLastCommittedURL(), "https://example.com/");
const GURL kBadUrl("https://example2.test/");
TestNavigationManager tno(contents(), kBadUrl);
auto navigation_2 = NavigationSimulatorImpl::CreateRendererInitiated(
kBadUrl, prerender_rfh);
navigation_2->Start();
EXPECT_EQ(NavigationThrottle::CANCEL,
navigation_2->GetLastThrottleCheckResult());
ASSERT_TRUE(tno.WaitForNavigationFinished());
EXPECT_FALSE(tno.was_committed());
installer.condition().CallResumeClosure();
prerender_host_observer.WaitForDestroyed();
EXPECT_FALSE(prerender_host_observer.was_activated());
EXPECT_EQ(registry().FindHostByUrlForTesting(kPrerenderingUrl), nullptr);
ExpectFinalStatus(
PrerenderFinalStatus::kCrossSiteNavigationInMainFrameNavigation);
}
navigation->Commit();
EXPECT_EQ(contents()->GetPrimaryMainFrame()->GetLastCommittedURL(),
kPrerenderingUrl);
}
TEST_F(PrerenderHostTest, ActivationAfterPageStateUpdate) {
const GURL kPrerenderingUrl("https://example.com/next");
const FrameTreeNodeId prerender_frame_tree_node_id =
registry().CreateAndStartHost(
GeneratePrerenderAttributes(kPrerenderingUrl));
PrerenderHost* prerender_host =
registry().FindNonReservedHostById(prerender_frame_tree_node_id);
CommitPrerenderNavigation(*prerender_host);
auto* prerender_root_ftn =
FrameTreeNode::GloballyFindByID(prerender_frame_tree_node_id);
RenderFrameHostImpl* prerender_rfh = prerender_root_ftn->current_frame_host();
NavigationEntryImpl* prerender_nav_entry =
prerender_root_ftn->frame_tree().controller().GetLastCommittedEntry();
FrameNavigationEntry* prerender_root_fne =
prerender_nav_entry->GetFrameEntry(prerender_root_ftn);
auto page_state = blink::PageState::CreateForTestingWithSequenceNumbers(
GURL("about:blank"), prerender_root_fne->item_sequence_number(),
prerender_root_fne->document_sequence_number());
static_cast<mojom::FrameHost*>(prerender_rfh)->UpdateState(page_state);
contents()->ActivatePrerenderedPage(kPrerenderingUrl);
ExpectFinalStatus(PrerenderFinalStatus::kActivated);
EXPECT_EQ(contents()->GetPrimaryMainFrame(), prerender_rfh);
NavigationEntryImpl* activated_nav_entry =
contents()->GetController().GetLastCommittedEntry();
EXPECT_EQ(page_state,
activated_nav_entry
->GetFrameEntry(contents()->GetPrimaryFrameTree().root())
->page_state());
}
TEST_F(PrerenderHostTest, LoadProgressChangedInvokedOnActivation) {
contents()->set_minimum_delay_between_loading_updates_for_testing(
base::Milliseconds(0));
testing::NiceMock<MockWebContentsObserver> observer(contents());
testing::InSequence s;
EXPECT_CALL(observer, LoadProgressChanged(testing::_)).Times(0);
const GURL kPrerenderingUrl("https://example.com/next");
constexpr double kPartialLoadProgress = 0.7;
RenderFrameHostImpl* prerender_rfh =
contents()->AddPrerenderAndCommitNavigation(kPrerenderingUrl);
FrameTreeNode* ftn = prerender_rfh->frame_tree_node();
EXPECT_FALSE(ftn->HasNavigation());
testing::Mock::VerifyAndClearExpectations(&observer);
{
EXPECT_CALL(observer, LoadProgressChanged(blink::kInitialLoadProgress));
EXPECT_CALL(observer, DidFinishNavigation(testing::_));
EXPECT_CALL(observer, LoadProgressChanged(kPartialLoadProgress));
EXPECT_CALL(observer, LoadProgressChanged(blink::kFinalLoadProgress));
}
prerender_rfh->GetPage().set_load_progress(kPartialLoadProgress);
contents()->ActivatePrerenderedPage(kPrerenderingUrl);
ExpectFinalStatus(PrerenderFinalStatus::kActivated);
}
TEST_F(PrerenderHostTest, DontCancelPrerenderWhenTriggerGetsHidden) {
const GURL kPrerenderingUrl = GURL("https://example.com/empty.html");
const FrameTreeNodeId prerender_frame_tree_node_id =
registry().CreateAndStartHost(
GeneratePrerenderAttributes(kPrerenderingUrl));
PrerenderHost* prerender_host =
registry().FindNonReservedHostById(prerender_frame_tree_node_id);
ASSERT_NE(prerender_host, nullptr);
CommitPrerenderNavigation(*prerender_host);
contents()->WasHidden();
contents()->WasShown();
contents()->ActivatePrerenderedPage(kPrerenderingUrl);
ExpectFinalStatus(PrerenderFinalStatus::kActivated);
}
TEST_F(PrerenderHostTest, CancelActivationFromHiddenPage) {
const GURL kPrerenderingUrl = GURL("https://example.com/empty.html");
const FrameTreeNodeId prerender_frame_tree_node_id =
registry().CreateAndStartHost(
GeneratePrerenderAttributes(kPrerenderingUrl));
PrerenderHost* prerender_host =
registry().FindNonReservedHostById(prerender_frame_tree_node_id);
ASSERT_NE(prerender_host, nullptr);
CommitPrerenderNavigation(*prerender_host);
contents()->WasHidden();
test::PrerenderHostObserver prerender_host_observer(
*contents(), prerender_frame_tree_node_id);
std::unique_ptr<NavigationSimulatorImpl> navigation =
NavigationSimulatorImpl::CreateRendererInitiated(
kPrerenderingUrl, contents()->GetPrimaryMainFrame());
navigation->SetReferrer(blink::mojom::Referrer::New(
contents()->GetPrimaryMainFrame()->GetLastCommittedURL(),
network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin));
navigation->Commit();
prerender_host_observer.WaitForDestroyed();
EXPECT_FALSE(prerender_host_observer.was_activated());
ExpectFinalStatus(PrerenderFinalStatus::kActivatedInBackground);
}
TEST_F(PrerenderHostTest, DontCancelPrerenderWhenTriggerGetsVisible) {
const GURL kPrerenderingUrl = GURL("https://example.com/empty.html");
const FrameTreeNodeId prerender_frame_tree_node_id =
registry().CreateAndStartHost(
GeneratePrerenderAttributes(kPrerenderingUrl));
PrerenderHost* prerender_host =
registry().FindNonReservedHostById(prerender_frame_tree_node_id);
ASSERT_NE(prerender_host, nullptr);
CommitPrerenderNavigation(*prerender_host);
contents()->WasShown();
contents()->ActivatePrerenderedPage(kPrerenderingUrl);
ExpectFinalStatus(PrerenderFinalStatus::kActivated);
}
#if !BUILDFLAG(IS_ANDROID)
TEST_F(PrerenderHostTest, DontCancelPrerenderWhenTriggerGetsOcculded) {
const GURL kPrerenderingUrl = GURL("https://example.com/empty.html");
const FrameTreeNodeId prerender_frame_tree_node_id =
registry().CreateAndStartHost(
GeneratePrerenderAttributes(kPrerenderingUrl));
PrerenderHost* prerender_host =
registry().FindNonReservedHostById(prerender_frame_tree_node_id);
ASSERT_NE(prerender_host, nullptr);
CommitPrerenderNavigation(*prerender_host);
contents()->WasOccluded();
contents()->ActivatePrerenderedPage(kPrerenderingUrl);
ExpectFinalStatus(PrerenderFinalStatus::kActivated);
}
#endif
TEST_F(PrerenderHostTest, UrlMatchPredicate) {
const GURL kPrerenderingUrl = GURL("https://example.com/empty.html");
base::RepeatingCallback callback = base::BindRepeating(
[](const GURL&, const std::optional<content::UrlMatchType>&) {
return true;
});
const FrameTreeNodeId prerender_frame_tree_node_id =
registry().CreateAndStartHost(
GeneratePrerenderAttributesWithPredicate(kPrerenderingUrl, callback));
PrerenderHost* prerender_host =
registry().FindNonReservedHostById(prerender_frame_tree_node_id);
ASSERT_NE(prerender_host, nullptr);
const GURL kActivatedUrl = GURL("https://example.com/empty.html?activate");
ASSERT_NE(kActivatedUrl, kPrerenderingUrl);
EXPECT_TRUE(prerender_host->IsUrlMatch(kActivatedUrl));
EXPECT_FALSE(
prerender_host->IsUrlMatch(GURL("https://example2.com/empty.html")));
}
TEST_F(PrerenderHostTest, CanceledPrerenderCannotBeReadyForActivation) {
const GURL kPrerenderingUrl = GURL("https://example.com/empty.html");
auto* preloading_data = PreloadingData::GetOrCreateForWebContents(contents());
PreloadingURLMatchCallback same_url_matcher =
PreloadingData::GetSameURLMatcher(kPrerenderingUrl);
PreloadingAttempt* preloading_attempt = preloading_data->AddPreloadingAttempt(
content_preloading_predictor::kSpeculationRules,
PreloadingType::kPrerender, std::move(same_url_matcher),
contents()->GetPrimaryMainFrame()->GetPageUkmSourceId());
const FrameTreeNodeId prerender_frame_tree_node_id =
registry().CreateAndStartHost(
GeneratePrerenderAttributes(kPrerenderingUrl), preloading_attempt);
PrerenderHost* prerender_host =
registry().FindNonReservedHostById(prerender_frame_tree_node_id);
ASSERT_NE(prerender_host, nullptr);
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(base::BindLambdaForTesting([&]() {
registry().CancelHost(prerender_frame_tree_node_id,
PrerenderFinalStatus::kTriggerDestroyed);
})));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
&PreloadingAttempt::SetFailureReason,
base::Unretained(preloading_attempt),
static_cast<PreloadingFailureReason>(
static_cast<int>(PrerenderFinalStatus::kTriggerDestroyed) +
static_cast<int>(PreloadingFailureReason::
kPreloadingFailureReasonCommonEnd))));
base::RunLoop run_loop;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
CommitPrerenderNavigation(*prerender_host,
ExpectedReadyForActivationState(false));
run_loop.Quit();
}));
run_loop.Run();
EXPECT_EQ(test::PreloadingAttemptAccessor(preloading_attempt)
.GetTriggeringOutcome(),
PreloadingTriggeringOutcome::kFailure);
}
TEST(AreHttpRequestHeadersCompatible, IgnoreRTT) {
PrerenderCancellationReason reason = PrerenderCancellationReason(
PrerenderFinalStatus::kActivationNavigationParameterMismatch);
const std::string prerender_headers = "rtt: 1 \r\n downlink: 3";
const std::string potential_activation_headers = "rtt: 2 \r\n downlink: 4";
EXPECT_TRUE(PrerenderHost::AreHttpRequestHeadersCompatible(
potential_activation_headers,
#if BUILDFLAG(IS_ANDROID)
"",
#endif
prerender_headers, PreloadingTriggerType::kSpeculationRule,
"", false,
reason));
}
TEST(AreHttpRequestHeadersCompatible, XHeaders) {
PrerenderCancellationReason reason = PrerenderCancellationReason(
PrerenderFinalStatus::kActivationNavigationParameterMismatch);
const std::string prerender_headers = "x-hello: 1";
const std::string potential_activation_headers = "X-world: 2";
EXPECT_FALSE(PrerenderHost::AreHttpRequestHeadersCompatible(
potential_activation_headers,
#if BUILDFLAG(IS_ANDROID)
"",
#endif
prerender_headers, PreloadingTriggerType::kSpeculationRule,
"", false,
reason));
EXPECT_TRUE(PrerenderHost::AreHttpRequestHeadersCompatible(
potential_activation_headers,
#if BUILDFLAG(IS_ANDROID)
"",
#endif
prerender_headers, PreloadingTriggerType::kSpeculationRule,
"", true,
reason));
}
}
}