#include <cstdint>
#include <optional>
#include <tuple>
#include "base/barrier_closure.h"
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/feature_list.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/memory_pressure_monitor.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/metrics_hashes.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/strings/escape.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/gtest_util.h"
#include "base/test/run_until.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/test/test_timeouts.h"
#include "base/thread_annotations.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "cc/base/features.h"
#include "components/input/render_widget_host_input_event_router.h"
#include "components/services/storage/public/mojom/storage_service.mojom.h"
#include "components/services/storage/public/mojom/test_api.test-mojom.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/browser/back_forward_cache_test_util.h"
#include "content/browser/preloading/prefetch/prefetch_features.h"
#include "content/browser/preloading/prefetch/prefetch_service.h"
#include "content/browser/preloading/prefetch/prefetch_test_util_internal.h"
#include "content/browser/preloading/preloading.h"
#include "content/browser/preloading/preloading_attempt_impl.h"
#include "content/browser/preloading/preloading_data_impl.h"
#include "content/browser/preloading/preloading_decider.h"
#include "content/browser/preloading/prerender/prerender_features.h"
#include "content/browser/preloading/prerender/prerender_final_status.h"
#include "content/browser/preloading/prerender/prerender_handle_impl.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/browser/preloading/prerender/prerender_no_vary_search_hint_commit_deferring_condition.h"
#include "content/browser/preloading/speculation_rules/speculation_rules_util.h"
#include "content/browser/renderer_host/back_forward_cache_impl.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/navigation_throttle_runner.h"
#include "content/browser/renderer_host/navigation_type.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/content_navigation_policy.h"
#include "content/common/features.h"
#include "content/common/input/synthetic_tap_gesture.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/disallow_activation_reason.h"
#include "content/public/browser/document_user_data.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/prerender_web_contents_delegate.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/site_isolation_policy.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/isolated_world_ids.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/background_color_change_waiter.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/browsing_data_remover_test_util.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_content_browser_client.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/file_system_chooser_test_helpers.h"
#include "content/public/test/mock_client_hints_controller_delegate.h"
#include "content/public/test/mock_web_contents_observer.h"
#include "content/public/test/mojo_capability_control_test_interfaces.mojom.h"
#include "content/public/test/mojo_capability_control_test_util.h"
#include "content/public/test/navigation_handle_observer.h"
#include "content/public/test/no_renderer_crashes_assertion.h"
#include "content/public/test/prefetch_test_util.h"
#include "content/public/test/preloading_test_util.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/scoped_accessibility_mode_override.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_navigation_throttle.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/theme_change_waiter.h"
#include "content/public/test/url_loader_interceptor.h"
#include "content/shell/browser/shell.h"
#include "content/shell/browser/shell_browser_context.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/mock_commit_deferring_condition.h"
#include "content/test/render_document_feature.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/bindings/remote_set.h"
#include "mojo/public/cpp/system/functions.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_request_headers.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
#include "net/test/embedded_test_server/default_handlers.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#include "services/metrics/public/cpp/metrics_utils.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/network/public/cpp/web_sandbox_flags.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/features_generated.h"
#include "third_party/blink/public/common/loader/loader_constants.h"
#include "third_party/blink/public/common/navigation/preloading_headers.h"
#include "third_party/blink/public/mojom/browser_interface_broker.mojom.h"
#include "third_party/blink/public/mojom/frame/fullscreen.mojom.h"
#include "third_party/blink/public/mojom/page/display_cutout.mojom.h"
#include "ui/color/color_id.h"
#include "ui/events/base_event_utils.h"
#include "ui/shell_dialogs/select_file_dialog.h"
#include "ui/shell_dialogs/select_file_dialog_factory.h"
#include "url/gurl.h"
#include "url/url_constants.h"
using ::testing::Exactly;
namespace content {
namespace {
enum class BackForwardCacheType {
kDisabled,
kEnabled,
};
constexpr char kPagehideEventPath[] = "/pagehideFired";
std::string ToString(const testing::TestParamInfo<BackForwardCacheType>& info) {
switch (info.param) {
case BackForwardCacheType::kDisabled:
return "Disabled";
case BackForwardCacheType::kEnabled:
return "Enabled";
}
}
int32_t InterfaceNameHasher(const std::string& interface_name) {
return static_cast<int32_t>(base::HashMetricNameAs32Bits(interface_name));
}
RenderFrameHost* FindRenderFrameHost(Page& page, const GURL& url) {
return FrameMatchingPredicate(page,
base::BindRepeating(&FrameHasSourceUrl, url));
}
ukm::SourceId ToSourceId(int64_t navigation_id) {
return ukm::ConvertToSourceId(navigation_id,
ukm::SourceIdType::NAVIGATION_ID);
}
class FakeMemoryPressureMonitor : public base::MemoryPressureMonitor {
public:
explicit FakeMemoryPressureMonitor(base::MemoryPressureLevel level)
: level_(level) {}
base::MemoryPressureLevel GetCurrentPressureLevel(
base::MemoryPressureMonitorTag tag) const override {
return level_;
}
private:
const base::MemoryPressureLevel level_ = base::MEMORY_PRESSURE_LEVEL_NONE;
};
class DocumentData : public DocumentUserData<DocumentData> {
public:
~DocumentData() override = default;
base::WeakPtr<DocumentData> GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
private:
explicit DocumentData(RenderFrameHost* render_frame_host)
: DocumentUserData<DocumentData>(render_frame_host) {}
friend class DocumentUserData<DocumentData>;
base::WeakPtrFactory<DocumentData> weak_ptr_factory_{this};
DOCUMENT_USER_DATA_KEY_DECL();
};
DOCUMENT_USER_DATA_KEY_IMPL(DocumentData);
using UkmEntry = ukm::TestUkmRecorder::HumanReadableUkmEntry;
using ukm::builders::Preloading_Attempt;
using ukm::builders::Preloading_Attempt_PreviousPrimaryPage;
using ukm::builders::Preloading_Prediction;
using ukm::builders::Preloading_Prediction_PreviousPrimaryPage;
static const auto kMockElapsedTime =
base::ScopedMockElapsedTimersForTest::kMockElapsedTime;
class PreloadingAttemptPreviousPrimaryPageUkmEntryBuilder {
public:
explicit PreloadingAttemptPreviousPrimaryPageUkmEntryBuilder(
PreloadingPredictor predictor)
: predictor_(predictor) {}
ukm::TestUkmRecorder::HumanReadableUkmEntry BuildEntry(
ukm::SourceId source_id,
PreloadingType preloading_type,
PreloadingEligibility eligibility,
PreloadingHoldbackStatus holdback_status,
PreloadingTriggeringOutcome triggering_outcome,
PreloadingFailureReason failure_reason,
bool accurate,
std::optional<base::TimeDelta> ready_time = std::nullopt,
std::optional<blink::mojom::SpeculationEagerness> eagerness =
std::nullopt) const {
std::map<std::string, int64_t> metrics = {
{Preloading_Attempt::kPreloadingTypeName,
static_cast<int64_t>(preloading_type)},
{Preloading_Attempt::kPreloadingPredictorName, predictor_.ukm_value()},
{Preloading_Attempt::kEligibilityName,
static_cast<int64_t>(eligibility)},
{Preloading_Attempt::kHoldbackStatusName,
static_cast<int64_t>(holdback_status)},
{Preloading_Attempt::kTriggeringOutcomeName,
static_cast<int64_t>(triggering_outcome)},
{Preloading_Attempt::kFailureReasonName,
static_cast<int64_t>(failure_reason)},
{Preloading_Attempt::kAccurateTriggeringName, accurate ? 1 : 0}};
if (ready_time) {
metrics.insert({Preloading_Attempt::kReadyTimeName,
ukm::GetExponentialBucketMinForCounts1000(
ready_time->InMilliseconds())});
}
if (eagerness) {
metrics.insert({Preloading_Attempt::kSpeculationEagernessName,
static_cast<int64_t>(eagerness.value())});
}
return UkmEntry{source_id, std::move(metrics)};
}
private:
PreloadingPredictor predictor_;
};
class PreloadingPredictionPreviousPrimaryPageUkmEntryBuilder {
public:
explicit PreloadingPredictionPreviousPrimaryPageUkmEntryBuilder(
PreloadingPredictor predictor)
: predictor_(predictor) {}
ukm::TestUkmRecorder::HumanReadableUkmEntry
BuildEntry(ukm::SourceId source_id, int confidence, bool accurate) const {
std::map<std::string, int64_t> metrics = {
{Preloading_Prediction::kConfidenceName,
static_cast<int64_t>(confidence)},
{Preloading_Attempt::kPreloadingPredictorName,
static_cast<int64_t>(predictor_.ukm_value())},
{Preloading_Prediction::kAccuratePredictionName, accurate ? 1 : 0}};
return UkmEntry{source_id, std::move(metrics)};
}
private:
PreloadingPredictor predictor_;
};
void ExpectWebContentsIsForNewTabPrerendering(WebContents& web_contents) {
auto& web_contents_impl = static_cast<WebContentsImpl&>(web_contents);
EXPECT_TRUE(web_contents_impl.GetLastCommittedURL().is_empty());
EXPECT_FALSE(web_contents_impl.HasOpener());
EXPECT_TRUE(web_contents_impl.IsHidden());
}
class FakeWebContentsDelegate : public WebContentsDelegate {
public:
PreloadingEligibility IsPrerender2Supported(
WebContents& web_contents,
PreloadingTriggerType trigger_type) override {
return PreloadingEligibility::kEligible;
}
};
class PrerenderBrowserTest : public ContentBrowserTest,
public WebContentsObserver {
public:
using LifecycleStateImpl = RenderFrameHostImpl::LifecycleStateImpl;
enum class OriginType {
kSameOrigin,
kSameSiteCrossOrigin,
kCrossSite,
};
PrerenderBrowserTest() {
prerender_helper_ = std::make_unique<test::PrerenderTestHelper>(
base::BindRepeating(&PrerenderBrowserTest::web_contents,
base::Unretained(this)),
true);
feature_list_.InitWithFeatures(
{::features::kSuppressesPrerenderingOnSlowNetwork,
blink::features::kFetchLaterAPI},
{blink::features::kDropInputEventsWhilePaintHolding});
}
~PrerenderBrowserTest() override = default;
void SetUp() override {
ssl_server_.RegisterRequestHandler(
base::BindRepeating(&net::test_server::HandlePrefixedRequest,
"/server-redirect-credentialed-prerender",
base::BindRepeating(HandleCredentialedRequest)));
prerender_helper_->RegisterServerRequestMonitor(&ssl_server_);
ContentBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
host_resolver()->AddRule("*", "127.0.0.1");
attempt_ukm_entry_builder_ =
std::make_unique<test::PreloadingAttemptUkmEntryBuilder>(
PredictorToExpectInUkm());
attempt_previous_ukm_entry_builder_ =
std::make_unique<PreloadingAttemptPreviousPrimaryPageUkmEntryBuilder>(
PredictorToExpectInUkm());
prediction_ukm_entry_builder_ =
std::make_unique<test::PreloadingPredictionUkmEntryBuilder>(
PredictorToExpectInUkm());
prediction_previous_ukm_entry_builder_ = std::make_unique<
PreloadingPredictionPreviousPrimaryPageUkmEntryBuilder>(
PredictorToExpectInUkm());
ssl_server_.AddDefaultHandlers(GetTestDataFilePath());
ssl_server_.SetSSLConfig(
net::test_server::EmbeddedTestServer::CERT_TEST_NAMES);
pagehide_event_receiver_ =
std::make_unique<net::test_server::ControllableHttpResponse>(
&ssl_server_, kPagehideEventPath);
ASSERT_TRUE(ssl_server_.Start(port_));
WebContentsObserver::Observe(shell()->web_contents());
ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
}
void TearDownOnMainThread() override {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
EXPECT_TRUE(ssl_server_.ShutdownAndWaitUntilComplete());
}
static std::unique_ptr<net::test_server::HttpResponse>
HandleCredentialedRequest(const net::test_server::HttpRequest& request) {
GURL request_url = request.GetURL();
std::string dest = base::UnescapeBinaryURLComponent(request_url.query());
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_FOUND);
http_response->AddCustomHeader("Location", dest);
http_response->AddCustomHeader("Access-Control-Allow-Origin", "*");
http_response->AddCustomHeader("Supports-Loading-Mode",
"credentialed-prerender");
http_response->set_content_type("text/html");
http_response->set_content(base::StringPrintf(
"<!doctype html><p>Redirecting to %s", dest.c_str()));
return http_response;
}
void WaitForRequest(const GURL& url, int count) {
prerender_helper_->WaitForRequest(url, count);
}
FrameTreeNodeId AddPrerender(const GURL& prerendering_url,
int32_t world_id = ISOLATED_WORLD_ID_GLOBAL) {
return prerender_helper_->AddPrerender(prerendering_url, world_id);
}
FrameTreeNodeId AddPrerenderWithTags(const GURL& prerendering_url,
std::optional<std::string> tag) {
return prerender_helper_->AddPrerender(
prerendering_url, std::nullopt,
std::string(),
std::string(), tag);
}
FrameTreeNodeId AddPrerender(const GURL& prerendering_url,
std::string no_vary_search_hint,
int32_t world_id = ISOLATED_WORLD_ID_GLOBAL) {
return prerender_helper_->AddPrerender(
prerendering_url, std::nullopt, no_vary_search_hint,
std::string(), std::nullopt, world_id);
}
void AddPrerenderAsync(const GURL& prerendering_url) {
prerender_helper_->AddPrerenderAsync(prerendering_url);
}
void AddPrerenderAsync(const GURL& prerendering_url,
std::string no_vary_search_hint) {
prerender_helper_->AddPrerendersAsync({prerendering_url},
std::nullopt,
no_vary_search_hint,
std::string());
}
void AddPrefetchAsync(const GURL& prefetch_url) {
prerender_helper_->AddPrefetchAsync(prefetch_url);
}
void AddPrerendersAsync(const std::vector<GURL>& prerendering_urls) {
prerender_helper_->AddPrerendersAsync(prerendering_urls,
std::nullopt,
std::string());
}
void AddPrerendersAsync(
const std::vector<GURL>& prerendering_urls,
std::optional<blink::mojom::SpeculationEagerness> eagerness,
const std::string& target_hint) {
prerender_helper_->AddPrerendersAsync(prerendering_urls, eagerness,
target_hint);
}
void AddPrerenderWithEagernessAsync(
const GURL& prerendering_url,
blink::mojom::SpeculationEagerness eagerness) {
prerender_helper_->AddPrerendersAsync({prerendering_url}, eagerness,
std::string());
}
std::unique_ptr<PrerenderHandle> AddEmbedderTriggeredPrerender(
const GURL& prerendering_url,
PreloadingAttempt* preloading_attempt = nullptr,
bool should_warm_up_compositor = false) {
std::unique_ptr<PrerenderHandle> handle =
AddEmbedderTriggeredPrerenderAsync(prerendering_url, preloading_attempt,
should_warm_up_compositor);
EXPECT_TRUE(handle);
test::PrerenderTestHelper::WaitForPrerenderLoadCompletion(*web_contents(),
prerendering_url);
return handle;
}
std::unique_ptr<PrerenderHandle> AddEmbedderTriggeredPrerenderAsync(
const GURL& prerendering_url,
PreloadingAttempt* preloading_attempt = nullptr,
bool should_warm_up_compositor = false) {
return web_contents_impl()->StartPrerendering(
prerendering_url, PreloadingTriggerType::kEmbedder,
"EmbedderSuffixForTest",
net::HttpRequestHeaders(),
std::nullopt,
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
should_warm_up_compositor, true,
PreloadingHoldbackStatus::kUnspecified,
PreloadPipelineInfo::Create(
PreloadingType::kPrerender),
preloading_attempt,
{},
{},
false);
}
bool AddTestUtilJS(RenderFrameHost* host) {
std::string js = R"(
const script = document.createElement("script");
new Promise(resolve => {
script.addEventListener('load', () => {
resolve(true);
});
script.addEventListener('error', () => {
resolve(false);
});
script.src = "/prerender/test_utils.js";
document.body.appendChild(script);
});
)";
return EvalJs(host, js).ExtractBool();
}
void RegisterServiceWorker(const std::string& service_worker_url) {
const std::string_view script = R"(
(async () => {
if (await navigator.serviceWorker.getRegistration('/') !== undefined) {
return 'FAIL';
}
await navigator.serviceWorker.register($1, {scope: '/'});
await navigator.serviceWorker.ready;
return 'DONE';
})();
)";
EXPECT_EQ("DONE", EvalJs(web_contents_impl()->GetPrimaryMainFrame(),
JsReplace(script, service_worker_url)));
}
void NavigatePrimaryPage(const GURL& url) {
prerender_helper_->NavigatePrimaryPage(url);
}
void NavigatePrimaryPageFromAddressBar(const GURL& url) {
prerender_helper_->NavigatePrimaryPageAsync(
url, ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR));
}
FrameTreeNodeId GetHostForUrl(const GURL& url) {
return prerender_helper_->GetHostForUrl(url);
}
RenderFrameHostImpl* GetPrerenderedMainFrameHost(FrameTreeNodeId host_id) {
return static_cast<RenderFrameHostImpl*>(
prerender_helper_->GetPrerenderedMainFrameHost(host_id));
}
void NavigatePrerenderedPage(FrameTreeNodeId host_id, const GURL& url) {
return prerender_helper_->NavigatePrerenderedPage(host_id, url);
}
void CancelPrerenderedPage(FrameTreeNodeId host_id) {
return prerender_helper_->CancelPrerenderedPage(host_id);
}
bool HasHostForUrl(WebContents& web_contents, const GURL& url) {
FrameTreeNodeId host_id =
content::test::PrerenderTestHelper::GetHostForUrl(web_contents, url);
return !host_id.is_null();
}
bool HasHostForUrl(const GURL& url) {
FrameTreeNodeId host_id = GetHostForUrl(url);
return !host_id.is_null();
}
void WaitForPrerenderLoadCompletion(FrameTreeNodeId host_id) {
prerender_helper_->WaitForPrerenderLoadCompletion(host_id);
}
void WaitForPrerenderLoadCompletion(const GURL& url) {
prerender_helper_->WaitForPrerenderLoadCompletion(url);
}
GURL GetUrl(const std::string& path) const {
return ssl_server_.GetURL("a.test", path);
}
GURL GetSameSiteCrossOriginUrl(const std::string& path) {
return ssl_server().GetURL("b.a.test", path);
}
GURL GetCrossSiteUrl(const std::string& path) {
return ssl_server_.GetURL("b.test", path);
}
void ResetSSLConfig(
net::test_server::EmbeddedTestServer::ServerCertificate cert,
const net::SSLServerConfig& ssl_config) {
ASSERT_TRUE(ssl_server_.ResetSSLConfig(cert, ssl_config));
}
int GetRequestCount(const GURL& url) {
return prerender_helper_->GetRequestCount(url);
}
net::test_server::HttpRequest::HeaderMap GetRequestHeaders(const GURL& url) {
return prerender_helper_->GetRequestHeaders(url);
}
WebContents* web_contents() const { return shell()->web_contents(); }
WebContentsImpl* web_contents_impl() const {
return static_cast<WebContentsImpl*>(web_contents());
}
RenderFrameHostImpl* current_frame_host() {
return web_contents_impl()->GetPrimaryMainFrame();
}
ukm::TestAutoSetUkmRecorder* test_ukm_recorder() {
return ukm_recorder_.get();
}
ukm::SourceId PrimaryPageSourceId() {
return current_frame_host()->GetPageUkmSourceId();
}
const test::PreloadingAttemptUkmEntryBuilder& attempt_ukm_entry_builder() {
return *attempt_ukm_entry_builder_;
}
const PreloadingAttemptPreviousPrimaryPageUkmEntryBuilder&
attempt_previous_ukm_entry_builder() {
return *attempt_previous_ukm_entry_builder_;
}
const test::PreloadingPredictionUkmEntryBuilder&
prediction_ukm_entry_builder() {
return *prediction_ukm_entry_builder_;
}
const PreloadingPredictionPreviousPrimaryPageUkmEntryBuilder&
prediction_previous_ukm_entry_builder() {
return *prediction_previous_ukm_entry_builder_;
}
void ExpectPreloadingAttemptUkm(
const std::vector<UkmEntry>& expected_attempt_entries) {
test::ExpectPreloadingAttemptUkm(*test_ukm_recorder(),
expected_attempt_entries);
}
void ExpectPreloadingAttemptPreviousPrimaryPageUkm(
const UkmEntry& expected_attempt_entry) {
auto attempt_entries = test_ukm_recorder()->GetEntries(
Preloading_Attempt_PreviousPrimaryPage::kEntryName,
test::kPreloadingAttemptUkmMetrics);
ASSERT_EQ(attempt_entries.size(), 1u);
EXPECT_EQ(attempt_entries[0], expected_attempt_entry)
<< test::ActualVsExpectedUkmEntryToString(attempt_entries[0],
expected_attempt_entry);
}
void ExpectPreloadingPredictionUkm(
const std::vector<UkmEntry>& expected_prediction_entries) {
test::ExpectPreloadingPredictionUkm(*test_ukm_recorder(),
expected_prediction_entries);
}
void ExpectPreloadingPredictioPreviousPrimaryPageUkm(
const UkmEntry& expected_prediction_entry) {
auto prediction_entries = test_ukm_recorder()->GetEntries(
Preloading_Prediction_PreviousPrimaryPage::kEntryName,
test::kPreloadingPredictionUkmMetrics);
ASSERT_EQ(prediction_entries.size(), 1u);
EXPECT_EQ(prediction_entries[0], expected_prediction_entry)
<< test::ActualVsExpectedUkmEntryToString(prediction_entries[0],
expected_prediction_entry);
}
void TestHostPrerenderingState(const GURL& prerender_url) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
RenderFrameHostImpl* initiator_render_frame_host = current_frame_host();
EXPECT_TRUE(initiator_render_frame_host->frame_tree()->is_primary());
EXPECT_EQ(initiator_render_frame_host->lifecycle_state(),
LifecycleStateImpl::kActive);
AddPrerender(prerender_url);
EXPECT_TRUE(prerender_helper_->VerifyPrerenderingState(prerender_url));
NavigatePrimaryPage(prerender_url);
EXPECT_EQ(web_contents()->GetLastCommittedURL(), prerender_url);
RenderFrameHostImpl* navigated_render_frame_host = current_frame_host();
navigated_render_frame_host->ForEachRenderFrameHostImpl(
[](RenderFrameHostImpl* rfhi) {
EXPECT_EQ(rfhi->lifecycle_state(),
RenderFrameHostImpl::LifecycleStateImpl::kActive);
EXPECT_FALSE(rfhi->frame_tree()->is_prerendering());
const std::string kMojoScript = R"(
navigator.locks.request('hi', {mode:'shared'}, () => {});
)";
EXPECT_TRUE(ExecJs(rfhi, kMojoScript));
});
}
void TestPrerenderAllowedOnIframeWithStatusCode(OriginType origin_type,
std::string status_code);
test::PrerenderTestHelper* prerender_helper() {
return prerender_helper_.get();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
#if BUILDFLAG(IS_ANDROID)
command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures,
"DisplayCutoutAPI");
#endif
}
void TestNavigationHistory(const GURL& expected_current_url,
int expected_history_index,
int expected_history_length) {
ASSERT_EQ(expected_current_url, web_contents()->GetLastCommittedURL());
EXPECT_EQ(expected_history_index,
web_contents()->GetController().GetCurrentEntryIndex());
EXPECT_EQ(expected_history_length,
web_contents()->GetController().GetEntryCount());
EXPECT_EQ(expected_history_length,
EvalJs(web_contents(), "history.length"));
}
void AssertPrerenderHistoryLength(FrameTreeNodeId host_id,
RenderFrameHost* prerender_frame_host) {
EXPECT_EQ(1, FrameTreeNode::GloballyFindByID(host_id)
->frame_tree()
.controller()
.GetEntryCount());
ASSERT_EQ(1, EvalJs(prerender_frame_host, "history.length"));
}
void GoBack() {
web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(web_contents()));
}
void GoForward() {
web_contents()->GetController().GoForward();
EXPECT_TRUE(WaitForLoadStop(web_contents()));
}
void ExpectFinalStatus(const std::string& final_status_name,
PrerenderFinalStatus status) {
histogram_tester().ExpectUniqueSample(final_status_name, 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);
}
void ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus status) {
ExpectFinalStatus(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
status);
}
void ExpectFinalStatusForSpeculationRuleFromIsolatedWorld(
PrerenderFinalStatus status) {
ExpectFinalStatus(
"Prerender.Experimental.PrerenderHostFinalStatus."
"SpeculationRuleFromIsolatedWorld",
status);
}
void ExpectFinalStatusForSpeculationRuleFromAutoSpeculationRules(
PrerenderFinalStatus status) {
ExpectFinalStatus(
"Prerender.Experimental.PrerenderHostFinalStatus."
"SpeculationRuleFromAutoSpeculationRules",
status);
}
void ExpectFinalStatusForEmbedder(PrerenderFinalStatus status) {
if (status != PrerenderFinalStatus::kActivated) {
return;
}
ExpectFinalStatus(
"Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
"EmbedderSuffixForTest",
status);
}
const base::HistogramTester& histogram_tester() { return histogram_tester_; }
std::string GetBodyTextContent() {
base::RunLoop loop;
base::Value result;
web_contents()->GetPrimaryMainFrame()->ExecuteJavaScriptForTests(
base::UTF8ToUTF16(std::string("document.body.textContent.trim()")),
base::BindOnce(
[](base::RunLoop* loop, base::Value* result, base::Value value) {
*result = std::move(value);
loop->QuitClosure().Run();
},
&loop, &result),
ISOLATED_WORLD_ID_GLOBAL);
loop.Run();
CHECK(result.is_string());
return result.GetString();
}
void WaitForPageHide() { pagehide_event_receiver_->WaitForRequest(); }
bool PageHideReceived() {
return pagehide_event_receiver_->has_received_request();
}
RenderProcessHost* GetProcessForPrerenderHost(
FrameTreeNodeId prerender_host_id) {
FrameTreeNode* frame_tree_node =
FrameTreeNode::GloballyFindByID(prerender_host_id);
return frame_tree_node->current_frame_host()->GetProcess();
}
std::vector<int64_t> navigation_ids_;
protected:
void TestCancelPrerendersWhenTimeout(
std::vector<Visibility> visibility_transitions);
void TestCancelOnlyEmbedderTriggeredPrerenderWhenTimeout(
std::vector<Visibility> visibility_transitions);
void TestTimerResetWhenPageGoBackToForeground(Visibility visibility);
void TestCancelPrerenderWithTargetBlankWhenTimeout(Visibility visibility);
void TestEmbedderTriggerWithUnsupportedScheme(const GURL& prerendering_url);
void TestSequentialPrerenderingVisibilityStateTransition(
Visibility initial_visibility,
Visibility background_visibility);
net::test_server::EmbeddedTestServer& ssl_server() { return ssl_server_; }
virtual PreloadingPredictor PredictorToExpectInUkm() {
return content_preloading_predictor::kSpeculationRules;
}
void ResetPointerPosition() {
#if !BUILDFLAG(IS_ANDROID)
InputEventAckWaiter waiter(
web_contents()->GetPrimaryMainFrame()->GetRenderWidgetHost(),
blink::WebInputEvent::Type::kMouseMove);
SimulateMouseEvent(web_contents(), blink::WebMouseEvent::Type::kMouseMove,
blink::WebMouseEvent::Button::kNoButton,
gfx::Point(0, 0));
waiter.Wait();
#else
#endif
}
void PointerHoverToAnchor(
const GURL& url,
const std::optional<base::TimeDelta>& hover_time = std::nullopt) {
ResetPointerPosition();
#if !BUILDFLAG(IS_ANDROID)
const auto point = CalculateCenterPointOfAnchorElement(url);
InputEventAckWaiter waiter(
web_contents()->GetPrimaryMainFrame()->GetRenderWidgetHost(),
blink::WebInputEvent::Type::kMouseMove);
SimulateMouseEvent(web_contents(), blink::WebMouseEvent::Type::kMouseMove,
blink::WebMouseEvent::Button::kNoButton, point);
waiter.Wait();
if (hover_time) {
base::RunLoop run_loop;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), *hover_time);
run_loop.Run();
ResetPointerPosition();
}
#else
#endif
}
void PointerDownToAnchor(const GURL& url) {
ResetPointerPosition();
#if !BUILDFLAG(IS_ANDROID)
const auto point = CalculateCenterPointOfAnchorElement(url);
InputEventAckWaiter waiter(
web_contents()->GetPrimaryMainFrame()->GetRenderWidgetHost(),
blink::WebInputEvent::Type::kMouseDown);
SimulateMouseEventForClick(blink::WebMouseEvent::Type::kMouseDown,
blink::WebMouseEvent::Button::kLeft, point);
waiter.Wait();
#else
#endif
}
void PointerUpToAnchor(const GURL& url) {
#if !BUILDFLAG(IS_ANDROID)
const auto point = CalculateCenterPointOfAnchorElement(url);
InputEventAckWaiter waiter(
web_contents()->GetPrimaryMainFrame()->GetRenderWidgetHost(),
blink::WebInputEvent::Type::kMouseUp);
SimulateMouseEventForClick(blink::WebMouseEvent::Type::kMouseUp,
blink::WebMouseEvent::Button::kLeft, point);
waiter.Wait();
#else
#endif
}
void InsertAnchor(const GURL url) {
const std::string script = R"(
const anchor = document.createElement('a');
anchor.href = $1;
anchor.text = $1;
anchor.style = "margin: 100px; display: block;";
document.body.appendChild(anchor);
)";
ASSERT_TRUE(ExecJs(web_contents(), JsReplace(script, url.spec())));
}
void ClickAnchor(const GURL url) {
PointerDownToAnchor(url);
PointerUpToAnchor(url);
}
void set_port(int port) {
ASSERT_FALSE(ssl_server_.Started());
port_ = port;
}
private:
void DidStartNavigation(NavigationHandle* handle) override {
navigation_ids_.push_back(handle->GetNavigationId());
}
void SimulateMouseEventForClick(blink::WebInputEvent::Type type,
blink::WebMouseEvent::Button button,
const gfx::Point& point) {
auto* web_contents_impl = static_cast<WebContentsImpl*>(web_contents());
auto* rwhvb = static_cast<RenderWidgetHostViewBase*>(
web_contents()->GetRenderWidgetHostView());
blink::WebMouseEvent mouse_event(type, 0, ui::EventTimeForNow());
mouse_event.button = button;
mouse_event.SetPositionInWidget(point.x(), point.y());
gfx::Rect offset = web_contents()->GetContainerBounds();
mouse_event.SetPositionInScreen(point.x() + offset.x(),
point.y() + offset.y());
mouse_event.click_count = 1;
web_contents_impl->GetInputEventRouter()->RouteMouseEvent(
rwhvb, &mouse_event, ui::LatencyInfo());
}
gfx::Point CalculateCenterPointOfAnchorElement(const GURL& url) {
const std::string script_get_x = R"(
const anchor = document.querySelector('a[href=$1]');
const bounds = anchor.getBoundingClientRect();
Math.floor(bounds.left + bounds.width / 2);
)";
const std::string script_get_y = R"(
const anchor = document.querySelector('a[href=$1]');
const bounds = anchor.getBoundingClientRect();
Math.floor(bounds.top + bounds.height / 2);
)";
const float x = EvalJs(web_contents(), JsReplace(script_get_x, url.spec()))
.ExtractDouble();
const float y = EvalJs(web_contents(), JsReplace(script_get_y, url.spec()))
.ExtractDouble();
return gfx::ToFlooredPoint(gfx::PointF(x, y));
}
int port_ = 0;
base::ScopedMockElapsedTimersForTest scoped_test_timer_;
net::test_server::EmbeddedTestServer ssl_server_{
net::test_server::EmbeddedTestServer::TYPE_HTTPS};
std::unique_ptr<test::PrerenderTestHelper> prerender_helper_;
base::HistogramTester histogram_tester_;
std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_;
std::unique_ptr<test::PreloadingAttemptUkmEntryBuilder>
attempt_ukm_entry_builder_;
std::unique_ptr<PreloadingAttemptPreviousPrimaryPageUkmEntryBuilder>
attempt_previous_ukm_entry_builder_;
std::unique_ptr<test::PreloadingPredictionUkmEntryBuilder>
prediction_ukm_entry_builder_;
std::unique_ptr<PreloadingPredictionPreviousPrimaryPageUkmEntryBuilder>
prediction_previous_ukm_entry_builder_;
std::unique_ptr<net::test_server::ControllableHttpResponse>
pagehide_event_receiver_;
base::test::ScopedFeatureList feature_list_;
};
class NoVarySearchPrerenderBrowserTest : public PrerenderBrowserTest {
public:
using StartedReason = PrerenderHost::WaitingForHeadersStartedReason;
using FinishedReason = PrerenderHost::WaitingForHeadersFinishedReason;
NoVarySearchPrerenderBrowserTest() {
feature_list_.InitAndEnableFeatureWithParameters(
features::kPrerender2NoVarySearch,
{{"wait_for_headers_timeout_eager_prerender", "500"}});
}
~NoVarySearchPrerenderBrowserTest() override = default;
protected:
void TestNoVarySearchHeaderFailure(const std::string& no_vary_search_header,
FinishedReason expected_finished_reason);
private:
base::test::ScopedFeatureList feature_list_;
};
class NoVarySearchHintPrerenderHostObserver : public PrerenderHost::Observer {
public:
explicit NoVarySearchHintPrerenderHostObserver(
PrerenderHost& prerender_host) {
observation_.Observe(&prerender_host);
}
void OnWaitingForHeadersStarted(
NavigationHandle& navigation_handle,
PrerenderHost::WaitingForHeadersStartedReason reason) override {
DCHECK(static_cast<NavigationRequest*>(&navigation_handle));
ASSERT_FALSE(static_cast<NavigationRequest*>(&navigation_handle)
->IsCommitDeferringConditionDeferredForTesting());
ASSERT_FALSE(wait_for_headers_start_reason_.has_value());
ASSERT_FALSE(wait_for_headers_finish_reason_.has_value());
wait_for_headers_start_reason_ = reason;
}
void OnWaitingForHeadersFinished(
PrerenderHost::WaitingForHeadersFinishedReason reason) override {
ASSERT_FALSE(wait_for_headers_finish_reason_.has_value());
wait_for_headers_finish_reason_ = reason;
observation_.Reset();
}
std::optional<PrerenderHost::WaitingForHeadersStartedReason>
wait_for_headers_start_reason() const {
return wait_for_headers_start_reason_;
}
std::optional<PrerenderHost::WaitingForHeadersFinishedReason>
wait_for_headers_finish_reason() const {
return wait_for_headers_finish_reason_;
}
private:
std::optional<PrerenderHost::WaitingForHeadersStartedReason>
wait_for_headers_start_reason_;
std::optional<PrerenderHost::WaitingForHeadersFinishedReason>
wait_for_headers_finish_reason_;
base::ScopedObservation<PrerenderHost, PrerenderHost::Observer> observation_{
this};
};
}
IN_PROC_BROWSER_TEST_F(
NoVarySearchPrerenderBrowserTest,
EagerTimerWorksCorrectlyForHeadersThatArriveBeforeTimeout) {
const std::string kTestingRelativeUrl =
"/delayed_with_no_vary_search?prerender";
const std::string kPrerenderingRelativeUrl = kTestingRelativeUrl + "&a=5";
net::test_server::ControllableHttpResponse main_prerender_response(
embedded_test_server(), kPrerenderingRelativeUrl);
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
const GURL kPrerenderingUrl =
embedded_test_server()->GetURL(kPrerenderingRelativeUrl);
const GURL kNavigationUrl =
embedded_test_server()->GetURL(kTestingRelativeUrl + "&a=3");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
PrerenderNoVarySearchHintCommitDeferringCondition::
SetTimerTaskRunnerForTesting(task_runner);
content::test::PrerenderHostCreationWaiter host_creation_waiter;
AddPrerenderAsync(kPrerenderingUrl, R"(params=(\\\"a\\\"))");
FrameTreeNodeId host_id = host_creation_waiter.Wait();
auto* host =
web_contents_impl()->GetPrerenderHostRegistry()->FindNonReservedHostById(
host_id);
ASSERT_TRUE(host);
ASSERT_TRUE(host->no_vary_search_hint().has_value());
NoVarySearchHintPrerenderHostObserver observer(*host);
TestActivationManager primary_page_manager(web_contents(), kNavigationUrl);
std::unique_ptr<content::TestNavigationObserver> nav_observer =
test::PrerenderTestHelper::NavigatePrimaryPageAsync(*web_contents_impl(),
kNavigationUrl);
ASSERT_TRUE(primary_page_manager.WaitForBeforeChecks());
primary_page_manager.ResumeActivation();
ASSERT_FALSE(host->were_headers_received());
ASSERT_TRUE(host->WaitUntilHeadTimeout().is_positive());
auto* prerender_web_contents =
content::WebContents::FromFrameTreeNodeId(host_id);
content::test::PrerenderHostObserver host_observer(*prerender_web_contents,
host_id);
main_prerender_response.WaitForRequest();
task_runner->FastForwardBy(host->WaitUntilHeadTimeout() / 2);
main_prerender_response.Send(
net::HTTP_OK, "text/html", "",
{}, {"No-Vary-Search: params=(\"a\")"});
main_prerender_response.Send("Some Content");
main_prerender_response.Done();
ASSERT_TRUE(primary_page_manager.WaitForAfterChecks());
primary_page_manager.ResumeActivation();
nav_observer->Wait();
primary_page_manager.WaitForNavigationFinished();
ASSERT_TRUE(host_observer.was_activated());
ASSERT_EQ(false, EvalJs(web_contents(), "document.prerendering"));
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
ASSERT_TRUE(observer.wait_for_headers_start_reason().has_value());
ASSERT_TRUE(observer.wait_for_headers_finish_reason().has_value());
EXPECT_EQ(observer.wait_for_headers_start_reason().value(),
StartedReason::kWithTimeout);
EXPECT_EQ(observer.wait_for_headers_finish_reason().value(),
FinishedReason::kNoVarySearchHeaderReceivedAndMatched);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.WaitingForHeadersFinishedReason.SpeculationRule",
FinishedReason::kNoVarySearchHeaderReceivedAndMatched, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.MatchableHostCountOnActivation", 1, 1);
}
IN_PROC_BROWSER_TEST_F(NoVarySearchPrerenderBrowserTest,
FailureOnPrerenderNavigation) {
const std::string kTestingRelativeUrl =
"/delayed_with_no_vary_search?prerender";
const std::string kPrerenderingRelativeUrl = kTestingRelativeUrl + "&a=5";
net::test_server::ControllableHttpResponse main_prerender_response(
embedded_test_server(), kPrerenderingRelativeUrl);
ASSERT_TRUE(embedded_test_server()->Start());
const GURL initial_url = embedded_test_server()->GetURL("/empty.html");
const GURL prerendering_url =
embedded_test_server()->GetURL(kPrerenderingRelativeUrl);
const GURL navigation_url =
embedded_test_server()->GetURL(kTestingRelativeUrl + "&a=3");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), initial_url);
content::test::PrerenderHostCreationWaiter host_creation_waiter;
AddPrerenderAsync(prerendering_url, R"(params=(\\\"a\\\"))");
FrameTreeNodeId host_id = host_creation_waiter.Wait();
auto* host =
web_contents_impl()->GetPrerenderHostRegistry()->FindNonReservedHostById(
host_id);
ASSERT_TRUE(host);
ASSERT_TRUE(host->no_vary_search_hint().has_value());
NoVarySearchHintPrerenderHostObserver observer(*host);
TestActivationManager primary_page_manager(web_contents(), navigation_url);
std::unique_ptr<content::TestNavigationObserver> nav_observer =
test::PrerenderTestHelper::NavigatePrimaryPageAsync(*web_contents_impl(),
navigation_url);
ASSERT_TRUE(primary_page_manager.WaitForBeforeChecks());
primary_page_manager.ResumeActivation();
ASSERT_FALSE(host->were_headers_received());
auto* prerender_web_contents =
content::WebContents::FromFrameTreeNodeId(host_id);
content::test::PrerenderHostObserver host_observer(*prerender_web_contents,
host_id);
main_prerender_response.WaitForRequest();
main_prerender_response.Done();
ASSERT_TRUE(primary_page_manager.WaitForAfterChecks());
primary_page_manager.ResumeActivation();
nav_observer->Wait();
primary_page_manager.WaitForNavigationFinished();
ASSERT_FALSE(host_observer.was_activated());
host_observer.WaitForDestroyed();
ASSERT_TRUE(observer.wait_for_headers_start_reason().has_value());
ASSERT_TRUE(observer.wait_for_headers_finish_reason().has_value());
EXPECT_EQ(observer.wait_for_headers_start_reason().value(),
StartedReason::kWithTimeout);
EXPECT_EQ(observer.wait_for_headers_finish_reason().value(),
FinishedReason::kPrerenderNavigationFailed);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.WaitingForHeadersFinishedReason.SpeculationRule",
FinishedReason::kPrerenderNavigationFailed, 1);
}
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_EagerTimerWorksCorrectlyForHeadersThatArriveAfterTimeout \
DISABLED_EagerTimerWorksCorrectlyForHeadersThatArriveAfterTimeout
#else
#define MAYBE_EagerTimerWorksCorrectlyForHeadersThatArriveAfterTimeout \
EagerTimerWorksCorrectlyForHeadersThatArriveAfterTimeout
#endif
IN_PROC_BROWSER_TEST_F(
NoVarySearchPrerenderBrowserTest,
MAYBE_EagerTimerWorksCorrectlyForHeadersThatArriveAfterTimeout) {
const std::string kTestingRelativeUrl =
"/delayed_with_no_vary_search?prerender";
const std::string kPrerenderingRelativeUrl = kTestingRelativeUrl + "&a=5";
net::test_server::ControllableHttpResponse main_prerender_response(
embedded_test_server(), kPrerenderingRelativeUrl);
const std::string kNavigationRelativeUrl = kTestingRelativeUrl + "&a=3";
net::test_server::ControllableHttpResponse main_navigation_response(
embedded_test_server(), kNavigationRelativeUrl);
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
const GURL kPrerenderingUrl =
embedded_test_server()->GetURL(kPrerenderingRelativeUrl);
const GURL kNavigationUrl =
embedded_test_server()->GetURL(kNavigationRelativeUrl);
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
PrerenderNoVarySearchHintCommitDeferringCondition::
SetTimerTaskRunnerForTesting(task_runner);
content::test::PrerenderHostCreationWaiter host_creation_waiter;
AddPrerenderAsync(kPrerenderingUrl, R"(params=(\\\"a\\\"))");
FrameTreeNodeId host_id = host_creation_waiter.Wait();
auto* host =
web_contents_impl()->GetPrerenderHostRegistry()->FindNonReservedHostById(
host_id);
ASSERT_TRUE(host);
ASSERT_TRUE(host->no_vary_search_hint().has_value());
NoVarySearchHintPrerenderHostObserver observer(*host);
TestActivationManager primary_page_manager(web_contents(), kNavigationUrl);
std::unique_ptr<content::TestNavigationObserver> nav_observer =
test::PrerenderTestHelper::NavigatePrimaryPageAsync(*web_contents_impl(),
kNavigationUrl);
ASSERT_TRUE(primary_page_manager.WaitForBeforeChecks());
primary_page_manager.ResumeActivation();
ASSERT_FALSE(host->were_headers_received());
ASSERT_TRUE(host->WaitUntilHeadTimeout().is_positive());
auto* prerender_web_contents =
content::WebContents::FromFrameTreeNodeId(host_id);
content::test::PrerenderHostObserver host_observer(*prerender_web_contents,
host_id);
main_prerender_response.WaitForRequest();
task_runner->FastForwardBy(2 * host->WaitUntilHeadTimeout());
main_prerender_response.Send(
net::HTTP_OK, "text/html", "",
{}, {"No-Vary-Search: params=(\"a\")"});
main_prerender_response.Send("Some Content");
main_prerender_response.Done();
ASSERT_TRUE(primary_page_manager.WaitForAfterChecks());
main_navigation_response.WaitForRequest();
main_navigation_response.Send("Some Content");
main_navigation_response.Done();
nav_observer->Wait();
primary_page_manager.WaitForNavigationFinished();
ASSERT_FALSE(host_observer.was_activated());
ASSERT_EQ(false, EvalJs(web_contents(), "document.prerendering"));
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
ASSERT_TRUE(observer.wait_for_headers_start_reason().has_value());
ASSERT_TRUE(observer.wait_for_headers_finish_reason().has_value());
EXPECT_EQ(observer.wait_for_headers_start_reason().value(),
StartedReason::kWithTimeout);
EXPECT_EQ(observer.wait_for_headers_finish_reason().value(),
FinishedReason::kTimeoutElapsed);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.WaitingForHeadersFinishedReason.SpeculationRule",
FinishedReason::kTimeoutElapsed, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.MatchableHostCountOnActivation", 1, 1);
}
void NoVarySearchPrerenderBrowserTest::TestNoVarySearchHeaderFailure(
const std::string& no_vary_search_header,
FinishedReason expected_finished_reason) {
const std::string kTestingRelativeUrl =
"/delayed_with_no_vary_search?prerender";
const std::string kPrerenderingRelativeUrl = kTestingRelativeUrl + "&a=5";
net::test_server::ControllableHttpResponse main_prerender_response(
embedded_test_server(), kPrerenderingRelativeUrl);
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
const GURL kPrerenderingUrl =
embedded_test_server()->GetURL(kPrerenderingRelativeUrl);
const GURL kNavigationUrl =
embedded_test_server()->GetURL(kTestingRelativeUrl + "&a=3");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
PrerenderNoVarySearchHintCommitDeferringCondition::
SetTimerTaskRunnerForTesting(task_runner);
content::test::PrerenderHostCreationWaiter host_creation_waiter;
AddPrerenderAsync(kPrerenderingUrl, R"(params=(\\\"a\\\"))");
FrameTreeNodeId host_id = host_creation_waiter.Wait();
auto* host =
web_contents_impl()->GetPrerenderHostRegistry()->FindNonReservedHostById(
host_id);
ASSERT_TRUE(host);
ASSERT_TRUE(host->no_vary_search_hint().has_value());
NoVarySearchHintPrerenderHostObserver observer(*host);
TestActivationManager primary_page_manager(web_contents(), kNavigationUrl);
std::unique_ptr<content::TestNavigationObserver> nav_observer =
test::PrerenderTestHelper::NavigatePrimaryPageAsync(*web_contents_impl(),
kNavigationUrl);
ASSERT_TRUE(primary_page_manager.WaitForBeforeChecks());
primary_page_manager.ResumeActivation();
ASSERT_FALSE(host->were_headers_received());
ASSERT_TRUE(host->WaitUntilHeadTimeout().is_positive());
auto* prerender_web_contents =
content::WebContents::FromFrameTreeNodeId(host_id);
content::test::PrerenderHostObserver host_observer(*prerender_web_contents,
host_id);
main_prerender_response.WaitForRequest();
main_prerender_response.Send(net::HTTP_OK, "text/html",
"",
{}, {no_vary_search_header});
main_prerender_response.Send("Some Content");
main_prerender_response.Done();
nav_observer->Wait();
primary_page_manager.WaitForNavigationFinished();
ASSERT_FALSE(host_observer.was_activated());
ASSERT_TRUE(observer.wait_for_headers_start_reason().has_value());
ASSERT_TRUE(observer.wait_for_headers_finish_reason().has_value());
EXPECT_EQ(observer.wait_for_headers_start_reason().value(),
StartedReason::kWithTimeout);
EXPECT_EQ(observer.wait_for_headers_finish_reason().value(),
expected_finished_reason);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.WaitingForHeadersFinishedReason.SpeculationRule",
expected_finished_reason, 1);
histogram_tester().ExpectTotalCount(
"Prerender.Experimental.MatchableHostCountOnActivation", 1);
}
IN_PROC_BROWSER_TEST_F(NoVarySearchPrerenderBrowserTest,
MalformedNoVarySearchHeader) {
TestNoVarySearchHeaderFailure("No-Vary-Search: malformed(\"a\")",
FinishedReason::kNoVarySearchHeaderParseFailed);
}
IN_PROC_BROWSER_TEST_F(NoVarySearchPrerenderBrowserTest,
NoVarySearchHeaderWithDefaultValue) {
TestNoVarySearchHeaderFailure(
"No-Vary-Search: params=()",
FinishedReason::kNoVarySearchHeaderReceivedButDefaultValue);
}
IN_PROC_BROWSER_TEST_F(NoVarySearchPrerenderBrowserTest, NoNoVarySearchHeader) {
TestNoVarySearchHeaderFailure("",
FinishedReason::kNoVarySearchHeaderNotReceived);
}
IN_PROC_BROWSER_TEST_F(NoVarySearchPrerenderBrowserTest,
UnmatchedNoVarySearchHeader) {
TestNoVarySearchHeaderFailure(
"No-Vary-Search: params=(\"different\")",
FinishedReason::kNoVarySearchHeaderReceivedButNotMatched);
}
IN_PROC_BROWSER_TEST_F(NoVarySearchPrerenderBrowserTest,
HintActivationSuccessful) {
const std::string kTestingRelativeUrl =
"/delayed_with_no_vary_search?prerender";
const std::string kPrerenderingRelativeUrl = kTestingRelativeUrl + "&a=5";
net::test_server::ControllableHttpResponse main_prerender_response(
embedded_test_server(), kPrerenderingRelativeUrl);
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
const GURL kPrerenderingUrl =
embedded_test_server()->GetURL(kPrerenderingRelativeUrl);
const GURL kNavigationUrl =
embedded_test_server()->GetURL(kTestingRelativeUrl + "&a=3");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
content::test::PrerenderHostCreationWaiter host_creation_waiter;
AddPrerenderAsync(kPrerenderingUrl, R"(params=(\\\"a\\\"))");
FrameTreeNodeId host_id = host_creation_waiter.Wait();
auto* host =
web_contents_impl()->GetPrerenderHostRegistry()->FindNonReservedHostById(
host_id);
ASSERT_TRUE(host);
ASSERT_TRUE(host->no_vary_search_hint().has_value());
PrerenderHost::Observer empty_observer;
host->AddObserver(&empty_observer);
NavigationHandleObserver activation_observer(web_contents(), kNavigationUrl);
TestActivationManager primary_page_manager(shell()->web_contents(),
kNavigationUrl);
std::unique_ptr<content::TestNavigationObserver> nav_observer =
test::PrerenderTestHelper::NavigatePrimaryPageAsync(*web_contents_impl(),
kNavigationUrl);
ASSERT_TRUE(primary_page_manager.WaitForBeforeChecks());
primary_page_manager.ResumeActivation();
ASSERT_FALSE(host->were_headers_received());
ASSERT_FALSE(host->IsUrlMatch(kNavigationUrl));
auto* prerender_web_contents =
content::WebContents::FromFrameTreeNodeId(host_id);
content::test::PrerenderHostObserver host_observer(*prerender_web_contents,
host_id);
NavigationRequest* nav_request =
web_contents_impl()->GetPrimaryFrameTree().root()->navigation_request();
EXPECT_TRUE(nav_request->IsCommitDeferringConditionDeferredForTesting());
EXPECT_EQ(nav_request->state(), NavigationRequest::NOT_STARTED);
main_prerender_response.WaitForRequest();
main_prerender_response.Send(
net::HTTP_OK, "text/html", "",
{}, {"No-Vary-Search: params=(\"a\")"});
host_observer.WaitForHeaders();
ASSERT_TRUE(host->were_headers_received());
ASSERT_TRUE(host->IsUrlMatch(kNavigationUrl));
EXPECT_TRUE(nav_request->IsCommitDeferringConditionDeferredForTesting());
main_prerender_response.Send("Some Content");
main_prerender_response.Done();
ASSERT_TRUE(primary_page_manager.WaitForAfterChecks());
primary_page_manager.ResumeActivation();
nav_observer->Wait();
primary_page_manager.WaitForNavigationFinished();
ASSERT_TRUE(host_observer.was_activated());
ASSERT_EQ(false, EvalJs(web_contents(), "document.prerendering"));
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kActivated);
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kNavigationUrl);
ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id();
ExpectPreloadingAttemptUkm({attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kSuccess,
PreloadingFailureReason::kUnspecified,
true,
kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate)});
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.MatchableHostCountOnActivation", 1, 1);
}
IN_PROC_BROWSER_TEST_F(NoVarySearchPrerenderBrowserTest,
HintActivationUnsuccessful) {
const std::string kTestingRelativeUrl =
"/delayed_without_no_vary_search?prerender";
const std::string kPrerenderingRelativeUrl = kTestingRelativeUrl + "&a=5";
net::test_server::ControllableHttpResponse main_prerender_response(
embedded_test_server(), kPrerenderingRelativeUrl);
const std::string kNavigationRelativeUrl = kTestingRelativeUrl + "&a=3";
net::test_server::ControllableHttpResponse main_navigation_response(
embedded_test_server(), kNavigationRelativeUrl);
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
const GURL kPrerenderingUrl =
embedded_test_server()->GetURL(kPrerenderingRelativeUrl);
const GURL kNavigationUrl =
embedded_test_server()->GetURL(kNavigationRelativeUrl);
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
content::test::PrerenderHostCreationWaiter host_creation_waiter;
AddPrerenderAsync(kPrerenderingUrl, R"(params=(\\\"a\\\"))");
FrameTreeNodeId host_id = host_creation_waiter.Wait();
auto* host =
web_contents_impl()->GetPrerenderHostRegistry()->FindNonReservedHostById(
host_id);
ASSERT_TRUE(host);
ASSERT_TRUE(host->no_vary_search_hint().has_value());
TestActivationManager primary_page_manager(shell()->web_contents(),
kNavigationUrl);
std::unique_ptr<content::TestNavigationObserver> nav_observer =
test::PrerenderTestHelper::NavigatePrimaryPageAsync(*web_contents_impl(),
kNavigationUrl);
ASSERT_TRUE(primary_page_manager.WaitForBeforeChecks());
primary_page_manager.ResumeActivation();
ASSERT_FALSE(host->were_headers_received());
NavigationRequest* nav_request =
web_contents_impl()->GetPrimaryFrameTree().root()->navigation_request();
ASSERT_TRUE(host->IsNoVarySearchHintUrlMatch(kNavigationUrl));
ASSERT_FALSE(host->IsUrlMatch(kNavigationUrl));
EXPECT_TRUE(nav_request->IsCommitDeferringConditionDeferredForTesting());
EXPECT_EQ(nav_request->state(), NavigationRequest::NOT_STARTED);
auto* prerender_web_contents =
content::WebContents::FromFrameTreeNodeId(host_id);
content::test::PrerenderHostObserver host_observer(*prerender_web_contents,
host_id);
main_prerender_response.WaitForRequest();
main_prerender_response.Send(net::HTTP_OK, "text/html",
"Some Content");
host_observer.WaitForHeaders();
ASSERT_TRUE(host->were_headers_received());
ASSERT_FALSE(host->IsUrlMatch(kNavigationUrl));
main_prerender_response.Done();
main_navigation_response.WaitForRequest();
main_navigation_response.Send(net::HTTP_OK, "text/html",
"Other Content");
main_navigation_response.Done();
primary_page_manager.WaitForNavigationFinished();
ASSERT_FALSE(host_observer.was_activated());
nav_observer->Wait();
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kNavigationUrl);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.MatchableHostCountOnActivation", 1, 1);
}
IN_PROC_BROWSER_TEST_F(NoVarySearchPrerenderBrowserTest,
HintActivationSuccessful_ExactUrl) {
const std::string testing_relative_url =
"/delayed_with_no_vary_search?prerender";
const std::string prerendering_relative_url = testing_relative_url + "&a=5";
net::test_server::ControllableHttpResponse main_prerender_response(
embedded_test_server(), prerendering_relative_url);
ASSERT_TRUE(embedded_test_server()->Start());
const GURL initial_url = embedded_test_server()->GetURL("/empty.html");
const GURL prerendering_url =
embedded_test_server()->GetURL(prerendering_relative_url);
const GURL navigation_url =
embedded_test_server()->GetURL(prerendering_relative_url);
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), initial_url);
content::test::PrerenderHostCreationWaiter host_creation_waiter;
AddPrerenderAsync(prerendering_url, R"(params=(\\\"a\\\"))");
FrameTreeNodeId host_id = host_creation_waiter.Wait();
auto* host =
web_contents_impl()->GetPrerenderHostRegistry()->FindNonReservedHostById(
host_id);
ASSERT_TRUE(host);
ASSERT_TRUE(host->no_vary_search_hint().has_value());
NavigationHandleObserver activation_observer(web_contents(), navigation_url);
TestActivationManager primary_page_manager(shell()->web_contents(),
navigation_url);
std::unique_ptr<content::TestNavigationObserver> nav_observer =
test::PrerenderTestHelper::NavigatePrimaryPageAsync(*web_contents_impl(),
navigation_url);
ASSERT_TRUE(primary_page_manager.WaitForBeforeChecks());
primary_page_manager.ResumeActivation();
ASSERT_FALSE(host->were_headers_received());
std::optional<UrlMatchType> match_type = host->IsUrlMatch(navigation_url);
ASSERT_TRUE(match_type.has_value());
ASSERT_EQ(match_type.value(), UrlMatchType::kExact);
auto* prerender_web_contents =
content::WebContents::FromFrameTreeNodeId(host_id);
content::test::PrerenderHostObserver host_observer(*prerender_web_contents,
host_id);
NavigationRequest* nav_request =
web_contents_impl()->GetPrimaryFrameTree().root()->navigation_request();
EXPECT_TRUE(nav_request->IsCommitDeferringConditionDeferredForTesting());
EXPECT_EQ(nav_request->state(), NavigationRequest::NOT_STARTED);
main_prerender_response.WaitForRequest();
main_prerender_response.Send(
net::HTTP_OK, "text/html", "",
{}, {"No-Vary-Search: params=(\"a\")"});
host_observer.WaitForHeaders();
ASSERT_TRUE(host->were_headers_received());
ASSERT_TRUE(host->IsUrlMatch(navigation_url));
EXPECT_TRUE(nav_request->IsCommitDeferringConditionDeferredForTesting());
main_prerender_response.Send("Some Content");
main_prerender_response.Done();
ASSERT_TRUE(primary_page_manager.WaitForAfterChecks());
primary_page_manager.ResumeActivation();
nav_observer->Wait();
primary_page_manager.WaitForNavigationFinished();
ASSERT_TRUE(host_observer.was_activated());
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kActivated);
histogram_tester().ExpectTotalCount(
"Prerender.Experimental.WaitingForHeadersFinishedReason.SpeculationRule",
0);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.MatchableHostCountOnActivation", 1, 1);
}
IN_PROC_BROWSER_TEST_F(NoVarySearchPrerenderBrowserTest,
MultipleMatchableHosts) {
const std::string testing_relative_url =
"/delayed_with_no_vary_search?prerender";
const std::string prerendering_relative_url1 = testing_relative_url + "&a=5";
const std::string prerendering_relative_url2 = testing_relative_url + "&a=7";
net::test_server::ControllableHttpResponse main_prerender_response1(
embedded_test_server(), prerendering_relative_url1);
const std::string navigation_relative_url = testing_relative_url + "&a=3";
ASSERT_TRUE(embedded_test_server()->Start());
const GURL initial_url = embedded_test_server()->GetURL("/empty.html");
const GURL prerendering_url1 =
embedded_test_server()->GetURL(prerendering_relative_url1);
const GURL prerendering_url2 =
embedded_test_server()->GetURL(prerendering_relative_url2);
const GURL navigation_url =
embedded_test_server()->GetURL(navigation_relative_url);
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), initial_url);
test::PrerenderHostCreationWaiter host_creation_waiter1;
AddPrerenderAsync(prerendering_url1, R"(params=(\\\"a\\\"))");
FrameTreeNodeId host_id1 = host_creation_waiter1.Wait();
auto* host1 =
web_contents_impl()->GetPrerenderHostRegistry()->FindNonReservedHostById(
host_id1);
ASSERT_TRUE(host1);
ASSERT_TRUE(host1->no_vary_search_hint().has_value());
test::PrerenderHostObserver host_observer1(*web_contents(), host_id1);
test::PrerenderHostCreationWaiter host_creation_waiter2;
AddPrerenderAsync(prerendering_url2, R"(params=(\\\"a\\\"))");
FrameTreeNodeId host_id2 = host_creation_waiter2.Wait();
auto* host2 =
web_contents_impl()->GetPrerenderHostRegistry()->FindNonReservedHostById(
host_id2);
ASSERT_TRUE(host2);
ASSERT_TRUE(host2->no_vary_search_hint().has_value());
test::PrerenderHostObserver host_observer2(*web_contents(), host_id2);
NavigationHandleObserver activation_observer(web_contents(), navigation_url);
TestActivationManager primary_page_manager(shell()->web_contents(),
navigation_url);
std::unique_ptr<TestNavigationObserver> nav_observer =
test::PrerenderTestHelper::NavigatePrimaryPageAsync(*web_contents(),
navigation_url);
ASSERT_TRUE(primary_page_manager.WaitForBeforeChecks());
primary_page_manager.ResumeActivation();
ASSERT_FALSE(host1->were_headers_received());
host_observer2.WaitForDestroyed();
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kOtherPrerenderedPageActivated);
NavigationRequest* nav_request =
web_contents_impl()->GetPrimaryFrameTree().root()->navigation_request();
EXPECT_TRUE(nav_request->IsCommitDeferringConditionDeferredForTesting());
EXPECT_EQ(nav_request->state(), NavigationRequest::NOT_STARTED);
main_prerender_response1.WaitForRequest();
main_prerender_response1.Send(
net::HTTP_OK, "text/html", "",
{}, {"No-Vary-Search: params=(\"a\")"});
host_observer1.WaitForHeaders();
ASSERT_TRUE(host1->were_headers_received());
ASSERT_TRUE(host1->IsUrlMatch(navigation_url));
main_prerender_response1.Send("Some Content");
main_prerender_response1.Done();
ASSERT_TRUE(primary_page_manager.WaitForAfterChecks());
primary_page_manager.ResumeActivation();
nav_observer->Wait();
primary_page_manager.WaitForNavigationFinished();
ASSERT_TRUE(host_observer1.was_activated());
histogram_tester().ExpectBucketCount(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kActivated, 1);
histogram_tester().ExpectTotalCount(
"Prerender.Experimental.WaitingForHeadersFinishedReason.SpeculationRule",
1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.MatchableHostCountOnActivation", 2, 1);
}
IN_PROC_BROWSER_TEST_F(NoVarySearchPrerenderBrowserTest, HintIsPopulated) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/no_vary_search_a.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0);
FrameTreeNodeId host_id =
AddPrerender(kPrerenderingUrl, R"(params=(\\\"a\\\"))");
auto* host =
web_contents_impl()->GetPrerenderHostRegistry()->FindNonReservedHostById(
host_id);
ASSERT_TRUE(host);
ASSERT_TRUE(host->no_vary_search_hint().has_value());
}
IN_PROC_BROWSER_TEST_F(NoVarySearchPrerenderBrowserTest, ExactUrlMatch) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/no_vary_search_a.html?prerender");
const GURL kNavigationUrl = kPrerenderingUrl;
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0);
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
ASSERT_TRUE(host_id);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1);
NavigationHandleObserver activation_observer(web_contents(), kNavigationUrl);
NavigatePrimaryPage(kNavigationUrl);
ASSERT_EQ(false, EvalJs(web_contents(), "document.prerendering"));
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
EXPECT_EQ(GetRequestCount(kNavigationUrl), 1);
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kActivated);
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kNavigationUrl);
histogram_tester().ExpectTotalCount(
"Navigation.Prerender.NoVarySearchCommitDeferTime.SpeculationRule", 0);
ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id();
ExpectPreloadingAttemptUkm({attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kSuccess,
PreloadingFailureReason::kUnspecified,
true,
kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate)});
}
IN_PROC_BROWSER_TEST_F(NoVarySearchPrerenderBrowserTest, InexactUrlMatch) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/no_vary_search_a.html?prerender");
const GURL kNavigationUrl = GetUrl("/no_vary_search_a.html?prerender&a=3");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0);
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
ASSERT_TRUE(host_id);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1);
NavigationHandleObserver activation_observer(web_contents(), kNavigationUrl);
NavigatePrimaryPage(kNavigationUrl);
ASSERT_EQ(false, EvalJs(web_contents(), "document.prerendering"));
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
EXPECT_FALSE(HasHostForUrl(kNavigationUrl));
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_EQ(GetRequestCount(kNavigationUrl), 0);
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kActivated);
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kNavigationUrl);
ASSERT_EQ(kNavigationUrl, EvalJs(web_contents(), "window.location.href"));
histogram_tester().ExpectTotalCount(
"Navigation.Prerender.NoVarySearchCommitDeferTime.SpeculationRule", 1);
ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id();
ExpectPreloadingAttemptUkm({attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kSuccess,
PreloadingFailureReason::kUnspecified,
true,
kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate)});
}
IN_PROC_BROWSER_TEST_F(NoVarySearchPrerenderBrowserTest,
ExactMatchWithUrlRedirection) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kRedirectedUrl = GetUrl("/no_vary_search_a.html?prerender");
const GURL kPrerenderingUrl =
GetUrl("/server-redirect?" + kRedirectedUrl.spec());
AddPrerender(kPrerenderingUrl);
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_EQ(GetRequestCount(kRedirectedUrl), 1);
EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl));
EXPECT_FALSE(HasHostForUrl(kRedirectedUrl));
NavigationHandleObserver activation_observer(web_contents(),
kPrerenderingUrl);
NavigatePrimaryPage(kPrerenderingUrl);
ASSERT_EQ(false, EvalJs(web_contents(), "document.prerendering"));
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_EQ(GetRequestCount(kRedirectedUrl), 1);
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kActivated);
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kRedirectedUrl);
histogram_tester().ExpectTotalCount(
"Navigation.Prerender.NoVarySearchCommitDeferTime.SpeculationRule", 0);
ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id();
ExpectPreloadingAttemptUkm({attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kSuccess,
PreloadingFailureReason::kUnspecified,
true,
kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate)});
}
IN_PROC_BROWSER_TEST_F(NoVarySearchPrerenderBrowserTest,
InexactMatchWithUrlRedirection) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kRedirectedUrl = GetUrl("/no_vary_search_a.html?prerender&a=2");
const GURL kRedirectedUrlWithIgnoredQueryParam =
GetUrl("/no_vary_search_a.html?prerender&a=3");
const GURL kPrerenderingUrl =
GetUrl("/server-redirect?" + kRedirectedUrl.spec());
const GURL kNavigationUrl =
GetUrl("/server-redirect?" + kRedirectedUrlWithIgnoredQueryParam.spec());
AddPrerender(kPrerenderingUrl);
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_EQ(GetRequestCount(kRedirectedUrl), 1);
EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl));
EXPECT_FALSE(HasHostForUrl(kRedirectedUrl));
NavigationHandleObserver activation_observer(web_contents(), kNavigationUrl);
NavigatePrimaryPage(kNavigationUrl);
ASSERT_EQ(false, EvalJs(web_contents(), "document.prerendering"));
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_EQ(GetRequestCount(kRedirectedUrl), 1);
EXPECT_EQ(GetRequestCount(kNavigationUrl), 0);
EXPECT_EQ(GetRequestCount(kRedirectedUrlWithIgnoredQueryParam), 0);
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kActivated);
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kRedirectedUrl);
ASSERT_EQ(kRedirectedUrl, EvalJs(web_contents(), "window.location.href"));
histogram_tester().ExpectTotalCount(
"Navigation.Prerender.NoVarySearchCommitDeferTime.SpeculationRule", 0);
ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id();
ExpectPreloadingAttemptUkm({attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kSuccess,
PreloadingFailureReason::kUnspecified,
true,
kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate)});
}
IN_PROC_BROWSER_TEST_F(NoVarySearchPrerenderBrowserTest,
InexactUrlMatchWithMainFrameNavigation) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/no_vary_search_a.html?prerender");
const GURL kPrerenderingNextUrl = GetUrl("/empty.html?next");
const GURL kNavigationUrl = GetUrl("/no_vary_search_a.html?prerender&a=3");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0);
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
ASSERT_TRUE(host_id);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1);
NavigatePrerenderedPage(host_id, kPrerenderingNextUrl);
WaitForPrerenderLoadCompletion(host_id);
NavigationHandleObserver activation_observer(web_contents(), kNavigationUrl);
NavigatePrimaryPage(kNavigationUrl);
ASSERT_EQ(false, EvalJs(web_contents(), "document.prerendering"));
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
EXPECT_FALSE(HasHostForUrl(kNavigationUrl));
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_EQ(GetRequestCount(kPrerenderingNextUrl), 1);
EXPECT_EQ(GetRequestCount(kNavigationUrl), 0);
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kActivated);
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingNextUrl);
ASSERT_EQ(kPrerenderingNextUrl,
EvalJs(web_contents(), "window.location.href"));
ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id();
ExpectPreloadingAttemptUkm({attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kSuccess,
PreloadingFailureReason::kUnspecified,
true,
kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate)});
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, SpeculationRulesPrerender) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0);
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
ASSERT_TRUE(host_id);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1);
NavigationHandleObserver activation_observer(web_contents(),
kPrerenderingUrl);
NavigatePrimaryPage(kPrerenderingUrl);
ASSERT_EQ(false, EvalJs(web_contents(), "document.prerendering"));
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kActivated);
{
ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id();
auto attempt_ukm_entries = test_ukm_recorder()->GetEntries(
Preloading_Attempt::kEntryName, test::kPreloadingAttemptUkmMetrics);
auto prediction_ukm_entries =
test_ukm_recorder()->GetEntries(Preloading_Prediction::kEntryName,
test::kPreloadingPredictionUkmMetrics);
EXPECT_EQ(prediction_ukm_entries.size(), 1u);
EXPECT_EQ(attempt_ukm_entries.size(), 1u);
auto prerender_page_load_ukm_entries =
test_ukm_recorder()->GetEntriesByName(
ukm::builders::PrerenderPageLoad::kEntryName);
ukm::SourceId activation_id = ToSourceId(navigation_ids_[2]);
EXPECT_EQ(activation_id, prerender_page_load_ukm_entries.back()->source_id);
EXPECT_EQ(activation_id, prediction_ukm_entries.back().source_id);
EXPECT_EQ(activation_id, attempt_ukm_entries.back().source_id);
ExpectPreloadingAttemptUkm({attempt_ukm_entry_builder().BuildEntry(
activation_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kSuccess,
PreloadingFailureReason::kUnspecified,
true,
kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate)});
ExpectPreloadingPredictionUkm({prediction_ukm_entry_builder().BuildEntry(
ukm_source_id,
100,
true)});
}
FetchHistogramsFromChildProcesses();
histogram_tester().ExpectTotalCount(
"Prerender.Experimental.ActivationIPCDelay.SpeculationRule", 1u);
}
class PrerenderTargetAgnosticBrowserTest
: public PrerenderBrowserTest,
public testing::WithParamInterface<std::string> {
public:
void ActivatePrerenderedPage(WebContents& prerender_web_contents,
const GURL& url) {
test::PrerenderHostObserver prerender_observer(prerender_web_contents, url);
if (GetTargetHint() == "_blank") {
TestNavigationObserver observer(&prerender_web_contents);
test::PrerenderTestHelper::OpenNewWindowWithoutOpener(*web_contents(),
url);
observer.WaitForNavigationFinished();
} else {
test::PrerenderTestHelper::NavigatePrimaryPage(*web_contents(), url);
}
ASSERT_TRUE(prerender_observer.was_activated());
}
protected:
std::string GetTargetHint() { return GetParam(); }
};
INSTANTIATE_TEST_SUITE_P(All,
PrerenderTargetAgnosticBrowserTest,
testing::Values("_self", "_blank"),
[](const testing::TestParamInfo<std::string>& info) {
return info.param;
});
class AutoSpeculationRulesPrerenderBrowserTest : public PrerenderBrowserTest {
public:
AutoSpeculationRulesPrerenderBrowserTest() {
sub_feature_list_.InitAndEnableFeatureWithParameters(
blink::features::kAutoSpeculationRules, {{"config", GetConfig()}});
}
protected:
void SetUp() override {
ssl_server().RegisterRequestHandler(base::BindLambdaForTesting(
[&](const net::test_server::HttpRequest& request)
-> std::unique_ptr<net::test_server::HttpResponse> {
if (request.relative_url == kInitialUrlPath) {
auto response =
std::make_unique<net::test_server::BasicHttpResponse>();
response->set_code(net::HTTP_OK);
response->set_content_type("text/html");
response->set_content(
"<!DOCTYPE html><main data-reactroot></main>");
return response;
}
return nullptr;
}));
PrerenderBrowserTest::SetUp();
}
PreloadingPredictor PredictorToExpectInUkm() override {
return content_preloading_predictor::
kSpeculationRulesFromAutoSpeculationRules;
}
GURL GetInitialUrl() const { return GetUrl(kInitialUrlPath); }
GURL GetPrerenderedUrl() const { return GetUrl(kPrerenderedUrlPath); }
std::string GetConfig() const {
return base::StringPrintf(R"(
{
"framework_to_speculation_rules": {
"9": "{\"prerender\":[{\"source\":\"list\", \"urls\":[\"%s\"]}]}"
}
}
)",
kPrerenderedUrlPath);
}
private:
base::test::ScopedFeatureList sub_feature_list_;
static constexpr char kInitialUrlPath[] = "/start.html";
static constexpr char kPrerenderedUrlPath[] = "/empty.html?prerender";
};
class AutoSpeculationRulesPrerenderBrowserTestWithHoldback
: public AutoSpeculationRulesPrerenderBrowserTest {
public:
AutoSpeculationRulesPrerenderBrowserTestWithHoldback() {
sub_feature_list_.InitAndEnableFeatureWithParameters(
blink::features::kAutoSpeculationRules,
{{"config", GetConfig()}, {"holdback", "true"}});
}
private:
base::test::ScopedFeatureList sub_feature_list_;
};
IN_PROC_BROWSER_TEST_F(AutoSpeculationRulesPrerenderBrowserTest, Metrics) {
const GURL kInitialUrl = GetInitialUrl();
const GURL kPrerenderingUrl = GetPrerenderedUrl();
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
WaitForPrerenderLoadCompletion(kPrerenderingUrl);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1);
NavigationHandleObserver activation_observer(web_contents(),
kPrerenderingUrl);
NavigatePrimaryPage(kPrerenderingUrl);
ASSERT_EQ(false, EvalJs(web_contents(), "document.prerendering"));
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
ExpectFinalStatusForSpeculationRuleFromAutoSpeculationRules(
PrerenderFinalStatus::kActivated);
{
ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id();
auto attempt_ukm_entries = test_ukm_recorder()->GetEntries(
Preloading_Attempt::kEntryName, test::kPreloadingAttemptUkmMetrics);
auto prediction_ukm_entries =
test_ukm_recorder()->GetEntries(Preloading_Prediction::kEntryName,
test::kPreloadingPredictionUkmMetrics);
EXPECT_EQ(prediction_ukm_entries.size(), 1u);
EXPECT_EQ(attempt_ukm_entries.size(), 1u);
auto prerender_page_load_ukm_entries =
test_ukm_recorder()->GetEntriesByName(
ukm::builders::PrerenderPageLoad::kEntryName);
ukm::SourceId activation_id = ToSourceId(navigation_ids_[2]);
EXPECT_EQ(activation_id, prerender_page_load_ukm_entries.back()->source_id);
EXPECT_EQ(activation_id, prediction_ukm_entries.back().source_id);
EXPECT_EQ(activation_id, attempt_ukm_entries.back().source_id);
ExpectPreloadingAttemptUkm({attempt_ukm_entry_builder().BuildEntry(
activation_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kSuccess,
PreloadingFailureReason::kUnspecified,
true,
kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate)});
ExpectPreloadingPredictionUkm({prediction_ukm_entry_builder().BuildEntry(
ukm_source_id,
100,
true)});
}
}
IN_PROC_BROWSER_TEST_F(AutoSpeculationRulesPrerenderBrowserTestWithHoldback,
Metrics) {
const GURL kInitialUrl = GetInitialUrl();
const GURL kPrerenderingUrl = GetPrerenderedUrl();
test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl());
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
registry_observer.WaitForTrigger(kPrerenderingUrl);
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 0);
NavigationHandleObserver next_page_observer(web_contents(), kPrerenderingUrl);
NavigatePrimaryPage(kPrerenderingUrl);
{
ukm::SourceId ukm_source_id = next_page_observer.next_page_ukm_source_id();
auto attempt_ukm_entries = test_ukm_recorder()->GetEntries(
Preloading_Attempt::kEntryName, test::kPreloadingAttemptUkmMetrics);
auto prediction_ukm_entries =
test_ukm_recorder()->GetEntries(Preloading_Prediction::kEntryName,
test::kPreloadingPredictionUkmMetrics);
EXPECT_EQ(prediction_ukm_entries.size(), 1u);
EXPECT_EQ(attempt_ukm_entries.size(), 1u);
auto prerender_page_load_ukm_entries =
test_ukm_recorder()->GetEntriesByName(
ukm::builders::PrerenderPageLoad::kEntryName);
ukm::SourceId next_page_id = ToSourceId(navigation_ids_[1]);
EXPECT_TRUE(prerender_page_load_ukm_entries.empty());
EXPECT_EQ(prediction_ukm_entries.back().source_id, next_page_id);
EXPECT_EQ(attempt_ukm_entries.back().source_id, next_page_id);
ExpectPreloadingAttemptUkm({attempt_ukm_entry_builder().BuildEntry(
next_page_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kHoldback,
PreloadingTriggeringOutcome::kUnspecified,
PreloadingFailureReason::kUnspecified,
true,
std::nullopt,
blink::mojom::SpeculationEagerness::kImmediate)});
ExpectPreloadingPredictionUkm({prediction_ukm_entry_builder().BuildEntry(
ukm_source_id,
100,
true)});
}
}
enum class PrerenderingResult { kSuccess, kFailed };
enum class BodySize { kSmall, kLarge };
class PrerenderAndPrefetchBrowserTest
: public PrerenderBrowserTest,
public testing::WithParamInterface<
std::tuple<PrerenderingResult, BodySize>> {
public:
static std::string DescribeParams(
const testing::TestParamInfo<ParamType>& info) {
auto [prerendering_result, body_size] = info.param;
std::stringstream params_description;
switch (prerendering_result) {
case PrerenderingResult::kSuccess:
params_description << "PrerenderSucceeded";
break;
case PrerenderingResult::kFailed:
params_description << "PrerenderFailed";
break;
}
switch (body_size) {
case BodySize::kSmall:
params_description << "_SmallBody";
break;
case BodySize::kLarge:
params_description << "_LargeBody";
break;
}
return params_description.str();
}
private:
void SetUp() override {
sub_feature_list_.InitWithFeaturesAndParameters(
{
{features::kPrefetchTesting,
{
{features::kPrefetchReusableBodySizeLimit.name, "102118"},
}},
},
{});
PrerenderBrowserTest::SetUp();
}
void TearDown() override {
PrerenderBrowserTest::TearDown();
sub_feature_list_.Reset();
}
base::test::ScopedFeatureList sub_feature_list_;
};
IN_PROC_BROWSER_TEST_P(PrerenderAndPrefetchBrowserTest,
SpeculationRulesPrefetchThenPrerender) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = std::get<1>(GetParam()) == BodySize::kSmall
? GetUrl("/cacheable.html?prerender")
: GetUrl("/cacheable_long.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
PrefetchService* prefetch_service = PrefetchService::GetFromFrameTreeNodeId(
current_frame_host()->GetFrameTreeNodeId());
ASSERT_TRUE(prefetch_service);
base::RunLoop run_loop;
PrefetchContainer::SetPrefetchResponseCompletedCallbackForTesting(
base::BindRepeating(
[](base::RunLoop* run_loop, const GURL& url,
base::WeakPtr<PrefetchContainer> prefetch_container) {
CHECK(prefetch_container);
CHECK_EQ(prefetch_container->GetURL(), url);
run_loop->Quit();
},
&run_loop, kPrerenderingUrl));
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0);
AddPrefetchAsync(kPrerenderingUrl);
run_loop.Run();
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1);
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
ASSERT_TRUE(host_id);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1);
switch (std::get<0>(GetParam())) {
case PrerenderingResult::kSuccess:
EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl));
break;
case PrerenderingResult::kFailed:
ASSERT_TRUE(web_contents_impl()->CancelPrerendering(
FrameTreeNode::GloballyFindByID(host_id),
PrerenderFinalStatus::kCancelAllHostsForTesting));
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
break;
}
NavigationHandleObserver activation_observer(web_contents(),
kPrerenderingUrl);
NavigatePrimaryPage(kPrerenderingUrl);
auto delivery_type =
EvalJs(web_contents()->GetPrimaryMainFrame(),
"performance.getEntriesByType('navigation')[0].deliveryType");
switch (std::get<0>(GetParam())) {
case PrerenderingResult::kSuccess:
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kActivated);
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_EQ(delivery_type, "navigational-prefetch");
break;
case PrerenderingResult::kFailed:
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kCancelAllHostsForTesting);
if (std::get<1>(GetParam()) == BodySize::kSmall) {
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_EQ(delivery_type, "navigational-prefetch");
} else {
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_EQ(delivery_type, "cache");
}
break;
}
switch (std::get<1>(GetParam())) {
case BodySize::kSmall:
EXPECT_EQ(GetBodyTextContent(), "This page is cacheable");
break;
case BodySize::kLarge:
EXPECT_EQ(GetBodyTextContent().size(), 102119u);
break;
}
}
INSTANTIATE_TEST_SUITE_P(
,
PrerenderAndPrefetchBrowserTest,
testing::Combine(testing::Values(PrerenderingResult::kSuccess,
PrerenderingResult::kFailed),
testing::Values(BodySize::kSmall, BodySize::kLarge)),
PrerenderAndPrefetchBrowserTest::DescribeParams);
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, SpeculationInitiatorNavigateAway) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
test::PrerenderHostObserver host_observer(*web_contents_impl(), host_id);
NavigatePrimaryPage(GetUrl("/empty.html?elsewhere"));
host_observer.WaitForDestroyed();
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
ukm::SourceId ukm_source_id = PrimaryPageSourceId();
ExpectPreloadingAttemptUkm({attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kReady,
PreloadingFailureReason::kUnspecified,
false,
kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate)});
ExpectPreloadingPredictionUkm({prediction_ukm_entry_builder().BuildEntry(
ukm_source_id, 100,
false)});
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ActivateOnLinkClick) {
const GURL kInitialUrl = GetUrl("/simple_links.html");
const GURL kPrerenderingUrl = GetUrl("/title2.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId prerender_host_id = AddPrerender(kPrerenderingUrl);
test::PrerenderHostObserver prerender_observer(*web_contents(),
prerender_host_id);
TestNavigationObserver nav_observer(web_contents());
const std::string kLinkClickScript = R"(
const link = document.querySelector('#same_site_link');
link.click();
)";
EXPECT_TRUE(ExecJs(web_contents(), kLinkClickScript));
nav_observer.WaitForNavigationFinished();
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
EXPECT_TRUE(prerender_observer.was_activated());
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ActivateOnLinkClick_TargetBlank) {
const GURL kInitialUrl = GetUrl("/simple_links.html");
const GURL kPrerenderingUrl = GetUrl("/title2.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId prerender_host_id = AddPrerender(kPrerenderingUrl);
test::PrerenderHostObserver prerender_observer(*web_contents(),
prerender_host_id);
TestNavigationObserver nav_observer(kPrerenderingUrl);
nav_observer.StartWatchingNewWebContents();
const std::string kLinkClickScript = R"(
clickSameSiteNewWindowLink();
)";
EXPECT_TRUE(ExecJs(web_contents(), kLinkClickScript));
nav_observer.WaitForNavigationFinished();
EXPECT_EQ(nav_observer.last_navigation_url(), kPrerenderingUrl);
EXPECT_FALSE(prerender_observer.was_activated());
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl));
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
EXPECT_TRUE(prerender_observer.was_activated());
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kActivated);
}
class PrerenderTargetHintBrowserTest : public PrerenderBrowserTest {
public:
void TestActivateOnWindowOpen(std::string_view window_features);
std::string SpeculationRulesInsertionScriptWithBothTargetHint(
const GURL& url,
const std::string& action = "prerender") {
constexpr char add_speculationrules_with_both_target_hints[] = R"({
var script = document.createElement('script');
script.type = 'speculationrules';
script.text = `{"$2": [
{"target_hint": "_self", "urls": ["$1"]},
{"target_hint": "_blank", "urls": ["$1"]}
]
}`;
document.head.appendChild(script);
})";
return base::ReplaceStringPlaceholders(
add_speculationrules_with_both_target_hints, {url.spec(), action},
nullptr);
}
std::string SpeculationRulesInsertionScriptWithOneSelfAndTwoBlankTargetHint(
const GURL& url,
const std::string& action = "prerender") {
constexpr char add_speculationrules_with_both_target_hints[] = R"({
var script = document.createElement('script');
script.type = 'speculationrules';
script.text = `{"$2": [
{"target_hint": "_self", "urls": ["$1"]},
{"target_hint": "_blank", "urls": ["$1"]},
{"target_hint": "_blank", "urls": ["$1"]}
]
}`;
document.head.appendChild(script);
})";
return base::ReplaceStringPlaceholders(
add_speculationrules_with_both_target_hints, {url.spec(), action},
nullptr);
}
std::string SpeculationRulesWithIdAndTargetHint(
const GURL& url,
const std::string& id,
const std::string& target_hint,
const std::string& action = "prerender") {
constexpr char add_speculationrules_with_id_and_target_hint[] = R"({
var script = document.createElement('script');
script.type = 'speculationrules';
script.id = '$1';
script.text = `{"$4":
[{
"target_hint": "$2",
"urls": ["$3"]
}]
}`;
document.head.appendChild(script);
})";
return base::ReplaceStringPlaceholders(
add_speculationrules_with_id_and_target_hint,
{id, target_hint, url.spec(), action}, nullptr);
}
};
IN_PROC_BROWSER_TEST_F(PrerenderTargetHintBrowserTest,
ActivateOnLinkClick_TargetBlank_WithTargetHintBlank) {
const GURL kInitialUrl = GetUrl("/simple_links.html");
const GURL kPrerenderingUrl = GetUrl("/title2.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = prerender_helper()->AddPrerender(
kPrerenderingUrl, std::nullopt, "_blank");
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
ASSERT_NE(prerender_web_contents, web_contents_impl());
ExpectWebContentsIsForNewTabPrerendering(*prerender_web_contents);
TestNavigationObserver activation_observer(kPrerenderingUrl);
activation_observer.WatchExistingWebContents();
test::PrerenderHostObserver prerender_observer(*prerender_web_contents,
host_id);
const std::string kLinkClickScript = R"(
clickSameSiteNewWindowLink();
)";
EXPECT_TRUE(ExecJs(web_contents(), kLinkClickScript));
activation_observer.WaitForNavigationFinished();
EXPECT_EQ(prerender_web_contents->GetLastCommittedURL(), kPrerenderingUrl);
EXPECT_EQ(activation_observer.last_navigation_url(), kPrerenderingUrl);
EXPECT_TRUE(prerender_observer.was_activated());
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kActivated);
ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id();
ExpectPreloadingAttemptUkm({attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kSuccess,
PreloadingFailureReason::kUnspecified,
true,
kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate)});
ExpectPreloadingPredictionUkm({prediction_ukm_entry_builder().BuildEntry(
ukm_source_id,
100,
true)});
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
}
IN_PROC_BROWSER_TEST_F(
PrerenderTargetHintBrowserTest,
DoesNotActivateOnMismatchedLinkClick_TargetBlank_WithTargetHintBlank) {
const GURL initial_url = GetUrl("/simple_links.html");
const GURL prerendering_url = GetUrl("/title2.html?different");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
FrameTreeNodeId host_id = prerender_helper()->AddPrerender(
prerendering_url, std::nullopt, "_blank");
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
ASSERT_NE(prerender_web_contents, web_contents_impl());
ExpectWebContentsIsForNewTabPrerendering(*prerender_web_contents);
TestNavigationObserver nav_observer(GetUrl("/title2.html"));
nav_observer.StartWatchingNewWebContents();
test::PrerenderHostObserver prerender_observer(*prerender_web_contents,
host_id);
const std::string kLinkClickScript = R"(
clickSameSiteNewWindowLink();
)";
EXPECT_TRUE(ExecJs(web_contents(), kLinkClickScript));
nav_observer.WaitForNavigationFinished();
EXPECT_FALSE(prerender_observer.was_activated());
EXPECT_FALSE(HasHostForUrl(prerendering_url));
EXPECT_TRUE(prerender_helper()->HasNewTabHandle(host_id));
EXPECT_EQ(web_contents()->GetLastCommittedURL(), initial_url);
}
IN_PROC_BROWSER_TEST_F(
PrerenderTargetHintBrowserTest,
DoesNotActivateOnWindowOpen_WithCustomizedWindowName_WithTargetHintBlank) {
const GURL initial_url = GetUrl("/simple_links.html");
const GURL prerendering_url = GetUrl("/title2.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
FrameTreeNodeId host_id = prerender_helper()->AddPrerender(
prerendering_url, std::nullopt, "_blank");
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
ASSERT_NE(prerender_web_contents, web_contents_impl());
ExpectWebContentsIsForNewTabPrerendering(*prerender_web_contents);
TestNavigationObserver nav_observer(prerendering_url);
nav_observer.StartWatchingNewWebContents();
test::PrerenderHostObserver prerender_observer(*prerender_web_contents,
host_id);
std::string window_open_script =
R"(window.open($1, 'WindowName', 'noopener');)";
EXPECT_TRUE(ExecJs(web_contents(),
JsReplace(window_open_script, prerendering_url.spec())));
nav_observer.WaitForNavigationFinished();
EXPECT_FALSE(prerender_observer.was_activated());
EXPECT_TRUE(prerender_helper()->HasNewTabHandle(host_id));
EXPECT_EQ(web_contents()->GetLastCommittedURL(), initial_url);
}
IN_PROC_BROWSER_TEST_F(
PrerenderTargetHintBrowserTest,
BackgroundedPage_ActivateOnLinkClick_TargetBlank_WithTargetHintBlank) {
const GURL initial_url = GetUrl("/simple_links.html");
const GURL prerender_url = GetUrl("/title2.html");
auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
PrerenderHostRegistry* registry =
web_contents_impl()->GetPrerenderHostRegistry();
registry->SetTaskRunnerForTesting(task_runner);
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
web_contents()->WasHidden();
ASSERT_TRUE(registry->GetEmbedderTimerForTesting()->IsRunning());
ASSERT_TRUE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
FrameTreeNodeId host_id = prerender_helper()->AddPrerender(
prerender_url, std::nullopt, "_blank");
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
ASSERT_NE(prerender_web_contents, web_contents_impl());
ExpectWebContentsIsForNewTabPrerendering(*prerender_web_contents);
web_contents()->WasShown();
ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
ASSERT_FALSE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
task_runner->FastForwardBy(
PrerenderHostRegistry::kTimeToLiveInBackgroundForSpeculationRules);
TestNavigationObserver activation_observer(prerender_url);
activation_observer.WatchExistingWebContents();
test::PrerenderHostObserver prerender_observer(*prerender_web_contents,
host_id);
const std::string kLinkClickScript = R"(
clickSameSiteNewWindowLink();
)";
EXPECT_TRUE(ExecJs(web_contents(), kLinkClickScript));
activation_observer.WaitForNavigationFinished();
EXPECT_EQ(prerender_web_contents->GetLastCommittedURL(), prerender_url);
EXPECT_EQ(activation_observer.last_navigation_url(), prerender_url);
EXPECT_TRUE(prerender_observer.was_activated());
EXPECT_FALSE(HasHostForUrl(prerender_url));
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kActivated);
ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id();
ExpectPreloadingAttemptUkm({attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kSuccess,
PreloadingFailureReason::kUnspecified,
true,
kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate)});
ExpectPreloadingPredictionUkm({prediction_ukm_entry_builder().BuildEntry(
ukm_source_id,
100,
true)});
EXPECT_EQ(web_contents()->GetLastCommittedURL(), initial_url);
}
#if BUILDFLAG(IS_WIN)
#define MAYBE_BackgroundedPageTimeout_TargetBlank_WithTargetHintBlank \
DISABLED_BackgroundedPageTimeout_TargetBlank_WithTargetHintBlank
#else
#define MAYBE_BackgroundedPageTimeout_TargetBlank_WithTargetHintBlank \
BackgroundedPageTimeout_TargetBlank_WithTargetHintBlank
#endif
IN_PROC_BROWSER_TEST_F(
PrerenderTargetHintBrowserTest,
MAYBE_BackgroundedPageTimeout_TargetBlank_WithTargetHintBlank) {
const GURL initial_url = GetUrl("/simple_links.html");
const GURL prerender_url = GetUrl("/title2.html");
auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
PrerenderHostRegistry* registry =
web_contents_impl()->GetPrerenderHostRegistry();
registry->SetTaskRunnerForTesting(task_runner);
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
web_contents()->WasHidden();
ASSERT_TRUE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
FrameTreeNodeId host_id = prerender_helper()->AddPrerender(
prerender_url, std::nullopt, "_blank");
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
ASSERT_NE(prerender_web_contents, web_contents_impl());
ExpectWebContentsIsForNewTabPrerendering(*prerender_web_contents);
test::PrerenderHostObserver prerender_observer(*prerender_web_contents,
host_id);
task_runner->FastForwardBy(
PrerenderHostRegistry::kTimeToLiveInBackgroundForSpeculationRules);
prerender_observer.WaitForDestroyed();
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kTimeoutBackgrounded, 1);
}
IN_PROC_BROWSER_TEST_F(
PrerenderTargetHintBrowserTest,
PrerenderWhenInitiatorInBackground_Queue_Processing_WithTargetHint) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL initial_url = embedded_test_server()->GetURL("/empty.html");
const GURL prerender_url1 =
embedded_test_server()->GetURL("/empty.html?prerender1");
const GURL prerender_url2 =
embedded_test_server()->GetURL("/empty.html?prerender2");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
web_contents()->WasHidden();
WebContents* prerender_web_contents = nullptr;
WebContents* prerender2_web_contents = nullptr;
base::RunLoop run_loop;
auto creation_subscription = RegisterWebContentsCreationCallback(
base::BindLambdaForTesting([&](WebContents* web_contents) {
if (prerender_web_contents == nullptr) {
prerender_web_contents = web_contents;
} else {
prerender2_web_contents = web_contents;
}
run_loop.QuitClosure().Run();
}));
AddPrerendersAsync({prerender_url1, prerender_url2},
std::nullopt,
"_blank");
run_loop.Run();
ExpectWebContentsIsForNewTabPrerendering(*prerender_web_contents);
prerender_helper()->WaitForPrerenderLoadCompletion(*prerender_web_contents,
prerender_url1);
PrerenderHost* prerender_host =
static_cast<WebContentsImpl*>(prerender_web_contents)
->GetPrerenderHostRegistry()
->FindHostByUrlForTesting(prerender_url1);
auto* preloading_attempt_impl = static_cast<PreloadingAttemptImpl*>(
prerender_host->preloading_attempt().get());
EXPECT_EQ(test::PreloadingAttemptAccessor(preloading_attempt_impl)
.GetTriggeringOutcome(),
PreloadingTriggeringOutcome::kReady);
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kTabClosedWithoutUserGesture);
}
IN_PROC_BROWSER_TEST_F(PrerenderTargetHintBrowserTest,
ActivateOnLinkClick_TargetBlankWithNoopener) {
const GURL kInitialUrl = GetUrl("/simple_links.html");
const GURL kPrerenderingUrl = GetUrl("/title2.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId prerender_host_id = AddPrerender(kPrerenderingUrl);
test::PrerenderHostObserver prerender_observer(*web_contents(),
prerender_host_id);
TestNavigationObserver nav_observer(kPrerenderingUrl);
nav_observer.StartWatchingNewWebContents();
const std::string kLinkClickScript = R"(
clickSameSiteNewWindowWithNoopenerLink();
)";
EXPECT_TRUE(ExecJs(web_contents(), kLinkClickScript));
nav_observer.WaitForNavigationFinished();
EXPECT_EQ(nav_observer.last_navigation_url(), kPrerenderingUrl);
EXPECT_FALSE(prerender_observer.was_activated());
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl));
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
EXPECT_TRUE(prerender_observer.was_activated());
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kActivated);
}
IN_PROC_BROWSER_TEST_F(
PrerenderTargetHintBrowserTest,
ActivateOnLinkClick_TargetBlankWithNoopener_WithTargetHintBlank) {
const GURL kInitialUrl = GetUrl("/simple_links.html");
const GURL kPrerenderingUrl = GetUrl("/title2.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = prerender_helper()->AddPrerender(
kPrerenderingUrl, std::nullopt, "_blank");
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
ASSERT_NE(prerender_web_contents, web_contents_impl());
ExpectWebContentsIsForNewTabPrerendering(*prerender_web_contents);
TestNavigationObserver activation_observer(kPrerenderingUrl);
activation_observer.WatchExistingWebContents();
test::PrerenderHostObserver prerender_observer(*prerender_web_contents,
host_id);
const std::string kLinkClickScript = R"(
clickSameSiteNewWindowWithNoopenerLink();
)";
EXPECT_TRUE(ExecJs(web_contents(), kLinkClickScript));
activation_observer.WaitForNavigationFinished();
EXPECT_EQ(prerender_web_contents->GetLastCommittedURL(), kPrerenderingUrl);
EXPECT_EQ(activation_observer.last_navigation_url(), kPrerenderingUrl);
EXPECT_EQ(prerender_web_contents->GetLastCommittedURL(), kPrerenderingUrl);
EXPECT_TRUE(prerender_observer.was_activated());
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kActivated);
ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id();
ExpectPreloadingAttemptUkm({attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kSuccess,
PreloadingFailureReason::kUnspecified,
true,
kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate)});
ExpectPreloadingPredictionUkm({prediction_ukm_entry_builder().BuildEntry(
ukm_source_id,
100,
true)});
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
}
IN_PROC_BROWSER_TEST_F(PrerenderTargetHintBrowserTest,
ActivateOnLinkClick_TargetBlankWithOpener) {
const GURL kInitialUrl = GetUrl("/simple_links.html");
const GURL kPrerenderingUrl = GetUrl("/title2.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId prerender_host_id = AddPrerender(kPrerenderingUrl);
test::PrerenderHostObserver prerender_observer(*web_contents(),
prerender_host_id);
TestNavigationObserver nav_observer(kPrerenderingUrl);
nav_observer.StartWatchingNewWebContents();
const std::string kLinkClickScript = R"(
clickSameSiteNewWindowWithOpenerLink();
)";
EXPECT_TRUE(ExecJs(web_contents(), kLinkClickScript));
nav_observer.WaitForNavigationFinished();
EXPECT_EQ(nav_observer.last_navigation_url(), kPrerenderingUrl);
EXPECT_FALSE(prerender_observer.was_activated());
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl));
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
EXPECT_FALSE(prerender_observer.was_activated());
prerender_observer.WaitForDestroyed();
EXPECT_TRUE(GetHostForUrl(kPrerenderingUrl).is_null());
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kActivatedWithAuxiliaryBrowsingContexts);
}
IN_PROC_BROWSER_TEST_F(
PrerenderTargetHintBrowserTest,
ActivateOnLinkClick_TargetBlankWithOpener_WithTargetHintBlank) {
const GURL kInitialUrl = GetUrl("/simple_links.html");
const GURL kPrerenderingUrl = GetUrl("/title2.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = prerender_helper()->AddPrerender(
kPrerenderingUrl, std::nullopt, "_blank");
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
ASSERT_NE(prerender_web_contents, web_contents_impl());
ExpectWebContentsIsForNewTabPrerendering(*prerender_web_contents);
ukm::SourceId triggering_primary_page_source_id =
web_contents_impl()->GetPrimaryMainFrame()->GetPageUkmSourceId();
test::PrerenderHostObserver prerender_observer(*prerender_web_contents,
host_id);
const std::string kLinkClickScript = R"(
clickSameSiteNewWindowWithOpenerLink();
)";
EXPECT_TRUE(ExecJs(web_contents(), kLinkClickScript));
EXPECT_NE(prerender_web_contents->GetLastCommittedURL(), kPrerenderingUrl);
EXPECT_FALSE(prerender_observer.was_activated());
EXPECT_TRUE(HasHostForUrl(*prerender_web_contents, kPrerenderingUrl));
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
EXPECT_FALSE(prerender_observer.was_activated());
prerender_observer.WaitForDestroyed();
EXPECT_TRUE(GetHostForUrl(kPrerenderingUrl).is_null());
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kTriggerDestroyed);
base::RunLoop().RunUntilIdle();
ExpectPreloadingAttemptPreviousPrimaryPageUkm(
attempt_previous_ukm_entry_builder().BuildEntry(
triggering_primary_page_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kReady,
PreloadingFailureReason::kUnspecified,
false,
kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate));
ExpectPreloadingPredictioPreviousPrimaryPageUkm(
{prediction_previous_ukm_entry_builder().BuildEntry(
triggering_primary_page_source_id,
100,
false)});
}
void PrerenderTargetHintBrowserTest::TestActivateOnWindowOpen(
std::string_view window_features) {
const GURL initial_url = GetUrl("/simple_links.html");
const GURL prerendering_url = GetUrl("/title2.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
FrameTreeNodeId host_id = prerender_helper()->AddPrerender(
prerendering_url, std::nullopt, "_blank");
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
ASSERT_NE(prerender_web_contents, web_contents_impl());
ExpectWebContentsIsForNewTabPrerendering(*prerender_web_contents);
TestNavigationObserver activation_observer(prerendering_url);
activation_observer.WatchExistingWebContents();
test::PrerenderHostObserver prerender_observer(*prerender_web_contents,
host_id);
const std::string script = base::StringPrintf(R"(
window.open("title2.html", "_blank", "%s");
)",
window_features);
EXPECT_TRUE(ExecJs(web_contents(), script));
activation_observer.WaitForNavigationFinished();
EXPECT_EQ(prerender_web_contents->GetLastCommittedURL(), prerendering_url);
EXPECT_EQ(activation_observer.last_navigation_url(), prerendering_url);
EXPECT_TRUE(prerender_observer.was_activated());
EXPECT_FALSE(HasHostForUrl(*prerender_web_contents, prerendering_url));
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kActivated);
ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id();
ExpectPreloadingAttemptUkm({attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kSuccess,
PreloadingFailureReason::kUnspecified,
true,
kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate)});
EXPECT_EQ(web_contents()->GetLastCommittedURL(), initial_url);
}
IN_PROC_BROWSER_TEST_F(PrerenderTargetHintBrowserTest,
PrerenderBothTargetHintButRemovesTargetHintSelf) {
const GURL initial_url = GetUrl("/simple_links.html");
const GURL prerendering_url = GetUrl("/title2.html");
const std::string add_speculation_rules_target_hint_self_script =
SpeculationRulesWithIdAndTargetHint(prerendering_url, "self_specrules",
"_self");
const std::string add_speculation_rules_target_hint_blank_script =
SpeculationRulesWithIdAndTargetHint(prerendering_url, "blank_specrules",
"_blank");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
EXPECT_TRUE(
ExecJs(web_contents(), add_speculation_rules_target_hint_self_script));
prerender_helper()->WaitForPrerenderLoadCompletion(*web_contents(),
prerendering_url);
WebContents* new_tab_prerender_web_contents = nullptr;
base::RunLoop run_loop;
auto creation_subscription = RegisterWebContentsCreationCallback(
base::BindLambdaForTesting([&](content::WebContents* web_contents) {
new_tab_prerender_web_contents = web_contents;
run_loop.QuitClosure().Run();
}));
EXPECT_TRUE(
ExecJs(web_contents(), add_speculation_rules_target_hint_blank_script));
run_loop.Run();
prerender_helper()->WaitForPrerenderLoadCompletion(
*new_tab_prerender_web_contents, prerendering_url);
ASSERT_NE(new_tab_prerender_web_contents, web_contents_impl());
ExpectWebContentsIsForNewTabPrerendering(*new_tab_prerender_web_contents);
ASSERT_TRUE(
ExecJs(web_contents_impl()->GetPrimaryMainFrame(),
"document.querySelector('script[id=self_specrules]').remove()"));
EXPECT_FALSE(HasHostForUrl(*web_contents(), prerendering_url));
EXPECT_TRUE(HasHostForUrl(*new_tab_prerender_web_contents, prerendering_url));
test::PrerenderHostObserver new_tab_prerender_observer(
*new_tab_prerender_web_contents, prerendering_url);
{
const std::string new_tab_opener_script =
"window.open(\"title2.html\", \"_blank\", \"noopener\")";
EXPECT_TRUE(ExecJs(web_contents(), new_tab_opener_script));
new_tab_prerender_observer.WaitForActivation();
EXPECT_EQ(new_tab_prerender_web_contents->GetLastCommittedURL(),
prerendering_url);
EXPECT_FALSE(
HasHostForUrl(*new_tab_prerender_web_contents, prerendering_url));
}
}
IN_PROC_BROWSER_TEST_F(PrerenderTargetHintBrowserTest,
PrerenderBothTargetHintButRemovesTargetHintBlank) {
const GURL initial_url = GetUrl("/simple_links.html");
const GURL prerendering_url = GetUrl("/title2.html");
const std::string add_speculation_rules_target_hint_self_script =
SpeculationRulesWithIdAndTargetHint(prerendering_url, "self_specrules",
"_self");
const std::string add_speculation_rules_target_hint_blank_script =
SpeculationRulesWithIdAndTargetHint(prerendering_url, "blank_specrules",
"_blank");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
EXPECT_TRUE(
ExecJs(web_contents(), add_speculation_rules_target_hint_self_script));
prerender_helper()->WaitForPrerenderLoadCompletion(*web_contents(),
prerendering_url);
WebContents* new_tab_prerender_web_contents = nullptr;
base::RunLoop run_loop;
auto creation_subscription = RegisterWebContentsCreationCallback(
base::BindLambdaForTesting([&](content::WebContents* web_contents) {
new_tab_prerender_web_contents = web_contents;
run_loop.QuitClosure().Run();
}));
EXPECT_TRUE(
ExecJs(web_contents(), add_speculation_rules_target_hint_blank_script));
run_loop.Run();
prerender_helper()->WaitForPrerenderLoadCompletion(
*new_tab_prerender_web_contents, prerendering_url);
ASSERT_NE(new_tab_prerender_web_contents, web_contents_impl());
ExpectWebContentsIsForNewTabPrerendering(*new_tab_prerender_web_contents);
FrameTreeNodeId new_tab_host_id = test::PrerenderTestHelper::GetHostForUrl(
*new_tab_prerender_web_contents, prerendering_url);
test::PrerenderHostObserver new_tab_prerender_observer(
*new_tab_prerender_web_contents, new_tab_host_id);
ASSERT_TRUE(
ExecJs(web_contents_impl()->GetPrimaryMainFrame(),
"document.querySelector('script[id=blank_specrules]').remove()"));
EXPECT_TRUE(HasHostForUrl(*web_contents(), prerendering_url));
new_tab_prerender_observer.WaitForDestroyed();
test::PrerenderHostObserver prerender_observer(*web_contents(),
prerendering_url);
{
ASSERT_TRUE(ExecJs(web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", prerendering_url)));
prerender_observer.WaitForActivation();
}
}
IN_PROC_BROWSER_TEST_F(PrerenderTargetHintBrowserTest,
ActivateOnBothTargetHint) {
const GURL initial_url = GetUrl("/simple_links.html");
const GURL prerendering_url = GetUrl("/title2.html");
const std::string add_speculation_rules_script =
SpeculationRulesInsertionScriptWithBothTargetHint(prerendering_url);
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
WebContents* new_tab_prerender_web_contents = nullptr;
base::RunLoop run_loop;
auto creation_subscription = RegisterWebContentsCreationCallback(
base::BindLambdaForTesting([&](content::WebContents* web_contents) {
new_tab_prerender_web_contents = web_contents;
run_loop.QuitClosure().Run();
}));
EXPECT_TRUE(ExecJs(web_contents(), add_speculation_rules_script));
run_loop.Run();
prerender_helper()->WaitForPrerenderLoadCompletion(*web_contents(),
prerendering_url);
prerender_helper()->WaitForPrerenderLoadCompletion(
*new_tab_prerender_web_contents, prerendering_url);
ASSERT_NE(new_tab_prerender_web_contents, web_contents_impl());
ExpectWebContentsIsForNewTabPrerendering(*new_tab_prerender_web_contents);
test::PrerenderHostObserver new_tab_prerender_observer(
*new_tab_prerender_web_contents, prerendering_url);
test::PrerenderHostObserver prerender_observer(*web_contents(),
prerendering_url);
{
const std::string new_tab_opener_script =
"window.open(\"title2.html\", \"_blank\", \"noopener\")";
EXPECT_TRUE(ExecJs(web_contents(), new_tab_opener_script));
new_tab_prerender_observer.WaitForActivation();
EXPECT_EQ(new_tab_prerender_web_contents->GetLastCommittedURL(),
prerendering_url);
EXPECT_TRUE(HasHostForUrl(*web_contents(), prerendering_url));
EXPECT_FALSE(
HasHostForUrl(*new_tab_prerender_web_contents, prerendering_url));
}
{
ASSERT_TRUE(ExecJs(web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", prerendering_url)));
prerender_observer.WaitForActivation();
}
}
IN_PROC_BROWSER_TEST_F(PrerenderTargetHintBrowserTest,
ActivateOnOneSelfAndTwoBlankTargetHint) {
const GURL initial_url = GetUrl("/simple_links.html");
const GURL prerendering_url = GetUrl("/title2.html");
const std::string add_speculation_rules_script =
SpeculationRulesInsertionScriptWithOneSelfAndTwoBlankTargetHint(
prerendering_url);
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
WebContents* new_tab_prerender_web_contents = nullptr;
base::RunLoop run_loop;
auto creation_subscription = RegisterWebContentsCreationCallback(
base::BindLambdaForTesting([&](content::WebContents* web_contents) {
new_tab_prerender_web_contents = web_contents;
run_loop.QuitClosure().Run();
}));
EXPECT_TRUE(ExecJs(web_contents(), add_speculation_rules_script));
run_loop.Run();
prerender_helper()->WaitForPrerenderLoadCompletion(*web_contents(),
prerendering_url);
prerender_helper()->WaitForPrerenderLoadCompletion(
*new_tab_prerender_web_contents, prerendering_url);
ASSERT_NE(new_tab_prerender_web_contents, web_contents_impl());
ExpectWebContentsIsForNewTabPrerendering(*new_tab_prerender_web_contents);
test::PrerenderHostObserver new_tab_prerender_observer(
*new_tab_prerender_web_contents, prerendering_url);
test::PrerenderHostObserver prerender_observer(*web_contents(),
prerendering_url);
{
const std::string new_tab_opener_script =
"window.open(\"title2.html\", \"_blank\", \"noopener\")";
EXPECT_TRUE(ExecJs(web_contents(), new_tab_opener_script));
new_tab_prerender_observer.WaitForActivation();
EXPECT_EQ(new_tab_prerender_web_contents->GetLastCommittedURL(),
prerendering_url);
EXPECT_TRUE(HasHostForUrl(*web_contents(), prerendering_url));
EXPECT_FALSE(
HasHostForUrl(*new_tab_prerender_web_contents, prerendering_url));
}
{
ASSERT_TRUE(ExecJs(web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", prerendering_url)));
prerender_observer.WaitForActivation();
}
}
IN_PROC_BROWSER_TEST_F(PrerenderTargetHintBrowserTest,
ActivateOnWindowOpen_NewTab) {
TestActivateOnWindowOpen("noopener");
}
IN_PROC_BROWSER_TEST_F(PrerenderTargetHintBrowserTest,
ActivateOnWindowOpen_PopUp) {
TestActivateOnWindowOpen("noopener,popup");
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ResponseHeaders) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/set-header?X-Foo: bar");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
NavigationHandleObserver observer1(web_contents(), kPrerenderingUrl);
AddPrerender(kPrerenderingUrl);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_TRUE(observer1.has_committed());
EXPECT_EQ("bar", observer1.GetNormalizedResponseHeader("x-foo"));
NavigationHandleObserver observer2(web_contents(), kPrerenderingUrl);
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_TRUE(observer2.has_committed());
EXPECT_EQ("bar", observer2.GetNormalizedResponseHeader("x-foo"));
}
IN_PROC_BROWSER_TEST_F(PrerenderTargetHintBrowserTest,
NewTabPrerenderCancellationOnInitiatorPHR) {
const GURL initial_url = GetUrl("/empty.html");
const GURL prerendering_url = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
FrameTreeNodeId host_id = prerender_helper()->AddPrerender(
prerendering_url, std::nullopt, "_blank");
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
WebContentsDestroyedWatcher wc_destroyed_watcher(prerender_web_contents);
test::PrerenderHostObserver host_observer(*prerender_web_contents, host_id);
web_contents_impl()->GetPrerenderHostRegistry()->CancelHost(
host_id, PrerenderFinalStatus::kDestroyed);
host_observer.WaitForDestroyed();
wc_destroyed_watcher.Wait();
EXPECT_FALSE(prerender_helper()->HasNewTabHandle(host_id));
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kDestroyed);
}
IN_PROC_BROWSER_TEST_F(PrerenderTargetHintBrowserTest,
NewTabPrerenderCancellationOnNewTabPHR) {
const GURL initial_url = GetUrl("/empty.html");
const GURL prerendering_url = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
FrameTreeNodeId host_id = prerender_helper()->AddPrerender(
prerendering_url, std::nullopt, "_blank");
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
WebContentsDestroyedWatcher wc_destroyed_watcher(prerender_web_contents);
test::PrerenderHostObserver host_observer(*prerender_web_contents, host_id);
static_cast<WebContentsImpl*>(prerender_web_contents)
->GetPrerenderHostRegistry()
->CancelHost(host_id, PrerenderFinalStatus::kDestroyed);
host_observer.WaitForDestroyed();
wc_destroyed_watcher.Wait();
EXPECT_FALSE(prerender_helper()->HasNewTabHandle(host_id));
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kDestroyed);
}
IN_PROC_BROWSER_TEST_F(PrerenderTargetHintBrowserTest,
NewTabPrerenderCancellationByInitiatorWCClosure) {
const GURL initial_url = GetUrl("/empty.html");
const GURL prerendering_url = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
FrameTreeNodeId host_id = prerender_helper()->AddPrerender(
prerendering_url, std::nullopt, "_blank");
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
WebContentsDestroyedWatcher wc_destroyed_watcher(prerender_web_contents);
test::PrerenderHostObserver host_observer(*prerender_web_contents, host_id);
shell()->Close();
host_observer.WaitForDestroyed();
wc_destroyed_watcher.Wait();
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kTabClosedWithoutUserGesture);
}
IN_PROC_BROWSER_TEST_P(PrerenderTargetAgnosticBrowserTest,
PrerenderCancelledOnEmptyBody404) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/404");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
test::PrerenderHostCreationWaiter host_creation_waiter;
prerender_helper()->AddPrerendersAsync(
{kPrerenderingUrl}, std::nullopt, GetTargetHint());
FrameTreeNodeId host_id = host_creation_waiter.Wait();
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
test::PrerenderHostObserver host_observer(*prerender_web_contents, host_id);
host_observer.WaitForDestroyed();
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kNavigationBadHttpStatus);
}
IN_PROC_BROWSER_TEST_P(PrerenderTargetAgnosticBrowserTest,
PrerenderCancelledOnNonEmptyBody404) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/page404.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
test::PrerenderHostCreationWaiter host_creation_waiter;
prerender_helper()->AddPrerendersAsync(
{kPrerenderingUrl}, std::nullopt, GetTargetHint());
FrameTreeNodeId host_id = host_creation_waiter.Wait();
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
test::PrerenderHostObserver host_observer(*prerender_web_contents, host_id);
host_observer.WaitForDestroyed();
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kNavigationBadHttpStatus);
}
IN_PROC_BROWSER_TEST_P(PrerenderTargetAgnosticBrowserTest,
PrerenderCancelledOn500Page) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/page500.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
test::PrerenderHostCreationWaiter host_creation_waiter;
prerender_helper()->AddPrerendersAsync(
{kPrerenderingUrl}, std::nullopt, GetTargetHint());
FrameTreeNodeId host_id = host_creation_waiter.Wait();
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
test::PrerenderHostObserver host_observer(*prerender_web_contents, host_id);
host_observer.WaitForDestroyed();
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kNavigationBadHttpStatus);
}
IN_PROC_BROWSER_TEST_P(PrerenderTargetAgnosticBrowserTest,
PrerenderCancelledOn204Page) {
const GURL kInitialUrl = GetUrl("/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kPrerenderingUrl = GetUrl("/echo?status=204");
test::PrerenderHostCreationWaiter host_creation_waiter;
prerender_helper()->AddPrerendersAsync(
{kPrerenderingUrl}, std::nullopt, GetTargetHint());
FrameTreeNodeId host_id = host_creation_waiter.Wait();
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
test::PrerenderHostObserver host_observer(*prerender_web_contents, host_id);
host_observer.WaitForDestroyed();
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kNavigationBadHttpStatus);
}
IN_PROC_BROWSER_TEST_P(PrerenderTargetAgnosticBrowserTest,
PrerenderCancelledOn205Page) {
const GURL kInitialUrl = GetUrl("/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kPrerenderingUrl = GetUrl("/echo?status=205");
test::PrerenderHostCreationWaiter host_creation_waiter;
prerender_helper()->AddPrerendersAsync(
{kPrerenderingUrl}, std::nullopt, GetTargetHint());
FrameTreeNodeId host_id = host_creation_waiter.Wait();
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
test::PrerenderHostObserver host_observer(*prerender_web_contents, host_id);
host_observer.WaitForDestroyed();
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kNavigationBadHttpStatus);
}
namespace {
void PrerenderBrowserTest::TestPrerenderAllowedOnIframeWithStatusCode(
OriginType origin_type,
std::string status_code) {
ASSERT_TRUE(status_code == "204" || status_code == "205");
const GURL kInitialUrl = GetUrl("/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kPrerenderingUrl = GetUrl("/title1.html");
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
test::PrerenderHostObserver host_observer(*web_contents_impl(), host_id);
GURL iframe_url;
std::string file_path = "/echo?status=" + status_code;
switch (origin_type) {
case OriginType::kSameOrigin:
iframe_url = GetUrl(file_path);
break;
case OriginType::kSameSiteCrossOrigin:
iframe_url = GetSameSiteCrossOriginUrl(file_path);
break;
case OriginType::kCrossSite:
iframe_url = GetCrossSiteUrl(file_path);
break;
}
TestNavigationManager iframe_navigation_manager(web_contents(), iframe_url);
RenderFrameHost* prerender_rfh = GetPrerenderedMainFrameHost(host_id);
std::ignore = ExecJs(prerender_rfh, JsReplace(R"(
const i = document.createElement('iframe');
i.src = $1;
document.body.appendChild(i);
)",
iframe_url.spec()));
switch (origin_type) {
case OriginType::kSameOrigin:
ASSERT_TRUE(iframe_navigation_manager.WaitForNavigationFinished());
break;
case OriginType::kSameSiteCrossOrigin:
case OriginType::kCrossSite:
ASSERT_TRUE(
iframe_navigation_manager.WaitForFirstYieldAfterDidStartNavigation());
auto* request = static_cast<NavigationRequest*>(
iframe_navigation_manager.GetNavigationHandle());
EXPECT_TRUE(request->IsDeferredForTesting());
ASSERT_EQ(1u, request->GetNavigationThrottleRegistryForTesting()
->GetDeferringThrottles()
.size());
EXPECT_STREQ("PrerenderSubframeNavigationThrottle",
(*request->GetNavigationThrottleRegistryForTesting()
->GetDeferringThrottles()
.begin())
->GetNameForLogging());
break;
}
EXPECT_EQ(GetHostForUrl(kPrerenderingUrl), host_id);
}
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
PrerenderAllowedOnIframe_204_SameOrigin) {
TestPrerenderAllowedOnIframeWithStatusCode(OriginType::kSameOrigin, "204");
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
PrerenderAllowedOnIframe_204_SameSiteCrossOrigin) {
TestPrerenderAllowedOnIframeWithStatusCode(OriginType::kSameSiteCrossOrigin,
"204");
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
PrerenderAllowedOnIframe_204_CrossSite) {
TestPrerenderAllowedOnIframeWithStatusCode(OriginType::kCrossSite, "204");
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
PrerenderAllowedOnIframe_205_SameOrigin) {
TestPrerenderAllowedOnIframeWithStatusCode(OriginType::kSameOrigin, "205");
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
PrerenderAllowedOnIframe_205_SameSiteCrossOrigin) {
TestPrerenderAllowedOnIframeWithStatusCode(OriginType::kSameSiteCrossOrigin,
"205");
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
PrerenderAllowedOnIframe_205_CrossSite) {
TestPrerenderAllowedOnIframeWithStatusCode(OriginType::kCrossSite, "205");
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CancelOnAuthRequested) {
const GURL kInitialUrl = GetUrl("/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kPrerenderingUrl = GetUrl("/auth-basic");
test::PrerenderHostObserver host_observer(*web_contents_impl(),
kPrerenderingUrl);
AddPrerenderAsync(kPrerenderingUrl);
host_observer.WaitForDestroyed();
EXPECT_TRUE(GetHostForUrl(kPrerenderingUrl).is_null());
const GURL kNavigatedURL = GetUrl("/title2.html");
ASSERT_TRUE(NavigateToURL(shell(), kNavigatedURL));
ExpectPreloadingAttemptUkm({attempt_ukm_entry_builder().BuildEntry(
PrimaryPageSourceId(), PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kFailure,
ToPreloadingFailureReason(PrerenderFinalStatus::kLoginAuthRequested),
false,
std::nullopt,
blink::mojom::SpeculationEagerness::kImmediate)});
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kLoginAuthRequested);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CancelOnAuthRequestedSubframe) {
const GURL kInitialUrl = GetUrl("/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kPrerenderingUrl = GetUrl("/title1.html");
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
test::PrerenderHostObserver host_observer(*web_contents_impl(), host_id);
const GURL kAuthIFrameUrl = GetUrl("/auth-basic");
RenderFrameHost* prerender_rfh = GetPrerenderedMainFrameHost(host_id);
std::ignore =
ExecJs(prerender_rfh,
"const i = document.createElement('iframe'); i.src = '" +
kAuthIFrameUrl.spec() + "'; document.body.appendChild(i);");
host_observer.WaitForDestroyed();
EXPECT_TRUE(GetHostForUrl(kPrerenderingUrl).is_null());
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kLoginAuthRequested);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CancelOnAuthRequestedSubResource) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kPrerenderingUrl = GetUrl("/title1.html");
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
test::PrerenderHostObserver host_observer(*web_contents_impl(), host_id);
ASSERT_TRUE(GetHostForUrl(kPrerenderingUrl));
std::string fetch_subresource_script = R"(
const imgElement = document.createElement('img');
imgElement.src = '/auth-basic/favicon.gif';
document.body.appendChild(imgElement);
)";
std::ignore =
ExecJs(GetPrerenderedMainFrameHost(host_id), fetch_subresource_script);
host_observer.WaitForDestroyed();
EXPECT_TRUE(GetHostForUrl(kPrerenderingUrl).is_null());
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kLoginAuthRequested);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
PrefetchCancelOnAuthRequested_NoServiceWorker) {
const GURL kInitialUrl = GetUrl("/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
test::TestPrefetchWatcher test_prefetch_watcher;
const GURL prefetch_url = GetUrl("/auth-basic");
AddPrefetchAsync(prefetch_url);
test_prefetch_watcher.WaitUntilPrefetchResponseCompleted(
static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame())
->GetDocumentToken(),
prefetch_url);
histogram_tester().ExpectUniqueSample("Preloading.Prefetch.PrefetchStatus",
PrefetchStatus::kPrefetchFailedNon2XX,
1);
ASSERT_TRUE(NavigateToURL(shell(), prefetch_url));
EXPECT_FALSE(test_prefetch_watcher.PrefetchUsedInLastNavigation());
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
PrefetchCancelOnAuthRequested_ServiceWorkerFallback) {
const GURL kInitialUrl = GetUrl("/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
RegisterServiceWorker("/fetch_event_passthrough.js");
test::TestPrefetchWatcher test_prefetch_watcher;
const GURL prefetch_url = GetUrl("/auth-basic");
AddPrefetchAsync(prefetch_url);
test_prefetch_watcher.WaitUntilPrefetchResponseCompleted(
static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame())
->GetDocumentToken(),
prefetch_url);
histogram_tester().ExpectUniqueSample("Preloading.Prefetch.PrefetchStatus",
PrefetchStatus::kPrefetchFailedNon2XX,
1);
ASSERT_TRUE(NavigateToURL(shell(), prefetch_url));
EXPECT_FALSE(test_prefetch_watcher.PrefetchUsedInLastNavigation());
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
PrefetchCancelOnAuthRequested_ServiceWorkerSubresource) {
const GURL kInitialUrl = GetUrl("/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
RegisterServiceWorker("/fetch_event_respond_with_fetch.js");
test::TestPrefetchWatcher test_prefetch_watcher;
const GURL prefetch_url = GetUrl("/auth-basic");
AddPrefetchAsync(prefetch_url);
test_prefetch_watcher.WaitUntilPrefetchResponseCompleted(
static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame())
->GetDocumentToken(),
prefetch_url);
histogram_tester().ExpectUniqueSample("Preloading.Prefetch.PrefetchStatus",
PrefetchStatus::kPrefetchFailedNon2XX,
1);
ASSERT_TRUE(NavigateToURL(shell(), prefetch_url));
EXPECT_FALSE(test_prefetch_watcher.PrefetchUsedInLastNavigation());
}
IN_PROC_BROWSER_TEST_F(PrerenderTargetHintBrowserTest,
CancelOnSpeculationCandidateRemoved) {
GURL url_ping(GetUrl(kPagehideEventPath));
const GURL kInitialUrl = GetUrl("/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kPrerenderingUrl = GetUrl("/title1.html?prerender");
test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl());
AddPrerenderAsync(kPrerenderingUrl);
registry_observer.WaitForTrigger(kPrerenderingUrl);
FrameTreeNodeId host_id = GetHostForUrl(kPrerenderingUrl);
ASSERT_TRUE(host_id);
WaitForPrerenderLoadCompletion(kPrerenderingUrl);
RenderFrameHost* prerender_host = GetPrerenderedMainFrameHost(host_id);
std::string js = R"(
addEventListener('pagehide', () => {
fetchLater($1);
});)";
EXPECT_TRUE(ExecJs(prerender_host, JsReplace(js, url_ping)));
EXPECT_FALSE(PageHideReceived());
test::PrerenderHostObserver host_observer(*web_contents_impl(), host_id);
ASSERT_TRUE(ExecJs(
web_contents_impl()->GetPrimaryMainFrame(),
"document.querySelector('script[type=speculationrules]').remove()"));
host_observer.WaitForDestroyed();
EXPECT_TRUE(GetHostForUrl(kPrerenderingUrl).is_null());
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kSpeculationRuleRemoved);
WaitForPageHide();
EXPECT_TRUE(PageHideReceived());
}
IN_PROC_BROWSER_TEST_F(
PrerenderTargetHintBrowserTest,
CancelOnSpeculationCandidateRemoved_WithTargetHintBlank) {
GURL url_ping(GetUrl(kPagehideEventPath));
const GURL kInitialUrl = GetUrl("/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kPrerenderingUrl = GetUrl("/title1.html?prerender");
FrameTreeNodeId host_id = prerender_helper()->AddPrerender(
kPrerenderingUrl, std::nullopt, "_blank");
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
ASSERT_NE(prerender_web_contents, web_contents_impl());
ExpectWebContentsIsForNewTabPrerendering(*prerender_web_contents);
std::string js = R"(
addEventListener('pagehide', () => {
fetchLater($1);
});)";
RenderFrameHost* prerender_host =
prerender_helper()->GetPrerenderedMainFrameHost(*prerender_web_contents,
host_id);
EXPECT_TRUE(ExecJs(prerender_host, JsReplace(js, url_ping)));
EXPECT_FALSE(PageHideReceived());
base::WeakPtr<WebContents> prerender_web_contents_weak =
prerender_web_contents->GetWeakPtr();
test::PrerenderHostObserver host_observer(*prerender_web_contents, host_id);
ASSERT_TRUE(ExecJs(
web_contents_impl()->GetPrimaryMainFrame(),
"document.querySelector('script[type=speculationrules]').remove()"));
host_observer.WaitForDestroyed();
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kSpeculationRuleRemoved);
WaitForPageHide();
EXPECT_TRUE(PageHideReceived());
EXPECT_FALSE(prerender_web_contents_weak);
ukm::SourceId triggering_primary_page_source_id =
web_contents_impl()->GetPrimaryMainFrame()->GetPageUkmSourceId();
ExpectPreloadingAttemptPreviousPrimaryPageUkm(
attempt_previous_ukm_entry_builder().BuildEntry(
triggering_primary_page_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kReady,
PreloadingFailureReason::kUnspecified,
false,
kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate));
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
DontCancelOnSpeculationUpdateIfStillEligible) {
const GURL kInitialUrl = GetUrl("/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kPrerenderingUrl = GetUrl("/title2.html");
test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl());
ASSERT_TRUE(ExecJs(web_contents_impl()->GetPrimaryMainFrame(),
JsReplace(
R"(
let sc = document.createElement('script');
sc.type = 'speculationrules';
sc.textContent = JSON.stringify({
prerender: [
{source: "list", urls: [$1]}
]
});
document.head.appendChild(sc);
)",
kPrerenderingUrl)));
registry_observer.WaitForTrigger(kPrerenderingUrl);
FrameTreeNodeId host_id = GetHostForUrl(kPrerenderingUrl);
ASSERT_TRUE(host_id);
ASSERT_TRUE(ExecJs(web_contents_impl()->GetPrimaryMainFrame(),
JsReplace(
R"(
document.querySelector('script[type=speculationrules]')
.remove();
let sc = document.createElement('script');
sc.type = 'speculationrules';
sc.textContent = JSON.stringify({
prerender: [
{source: "list", urls: ["/empty.html", $1]}
]
});
document.head.appendChild(sc);
)",
kPrerenderingUrl)));
{
base::RunLoop run_loop;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), TestTimeouts::action_timeout());
run_loop.Run();
ASSERT_TRUE(GetHostForUrl(kPrerenderingUrl));
}
test::PrerenderHostObserver host_observer(*web_contents_impl(), host_id);
ASSERT_TRUE(ExecJs(
web_contents_impl()->GetPrimaryMainFrame(),
"document.querySelector('script[type=speculationrules]').remove()"));
host_observer.WaitForDestroyed();
EXPECT_TRUE(GetHostForUrl(kPrerenderingUrl).is_null());
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
CanStartSecondPrerenderWhenCancellingFirst) {
const GURL kInitialUrl = GetUrl("/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kPrerenderingUrl = GetUrl("/title2.html");
test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl());
ASSERT_TRUE(ExecJs(web_contents_impl()->GetPrimaryMainFrame(),
JsReplace(
R"(
let sc = document.createElement('script');
sc.type = 'speculationrules';
sc.textContent = JSON.stringify({
prerender: [
{source: "list", urls: [$1]}
]
});
document.head.appendChild(sc);
)",
kPrerenderingUrl)));
registry_observer.WaitForTrigger(kPrerenderingUrl);
FrameTreeNodeId host_id = GetHostForUrl(kPrerenderingUrl);
ASSERT_TRUE(host_id);
const GURL kPrerenderingUrl2 = GetUrl("/title3.html");
test::PrerenderHostObserver host_observer(*web_contents_impl(), host_id);
ASSERT_TRUE(ExecJs(web_contents_impl()->GetPrimaryMainFrame(),
JsReplace(
R"(
document.querySelector('script[type=speculationrules]')
.remove();
let sc = document.createElement('script');
sc.type = 'speculationrules';
sc.textContent = JSON.stringify({
prerender: [
{source: "list", urls: [$1]}
]
});
document.head.appendChild(sc);
)",
kPrerenderingUrl2)));
host_observer.WaitForDestroyed();
EXPECT_TRUE(GetHostForUrl(kPrerenderingUrl).is_null());
registry_observer.WaitForTrigger(kPrerenderingUrl2);
FrameTreeNodeId second_host_id = GetHostForUrl(kPrerenderingUrl2);
EXPECT_TRUE(second_host_id);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, RetriggerPrerenderAfterRemoval) {
const GURL kInitialUrl = GetUrl("/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kPrerenderingUrl = GetUrl("/title2.html");
{
test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl());
ASSERT_TRUE(ExecJs(web_contents_impl()->GetPrimaryMainFrame(),
JsReplace(
R"(
let sc = document.createElement('script');
sc.type = 'speculationrules';
sc.textContent = JSON.stringify({
prerender: [
{source: "list", urls: [$1]}
]
});
document.head.appendChild(sc);
)",
kPrerenderingUrl)));
registry_observer.WaitForTrigger(kPrerenderingUrl);
FrameTreeNodeId host_id = GetHostForUrl(kPrerenderingUrl);
ASSERT_TRUE(host_id);
test::PrerenderHostObserver host_observer(*web_contents_impl(), host_id);
ASSERT_TRUE(ExecJs(
web_contents_impl()->GetPrimaryMainFrame(),
"document.querySelector('script[type=speculationrules]').remove()"));
host_observer.WaitForDestroyed();
EXPECT_TRUE(GetHostForUrl(kPrerenderingUrl).is_null());
}
{
AddPrerender(kPrerenderingUrl);
FrameTreeNodeId host_id = GetHostForUrl(kPrerenderingUrl);
EXPECT_TRUE(host_id);
}
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderChain) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderChain1 =
GetUrl("/prerender/page_with_trigger_function.html?1");
const GURL kPrerenderChain2 =
GetUrl("/prerender/page_with_trigger_function.html?2");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = AddPrerender(kPrerenderChain1);
EXPECT_EQ(GetRequestCount(kPrerenderChain1), 1);
EXPECT_TRUE(host_id);
RenderFrameHost* prerender_host = GetPrerenderedMainFrameHost(host_id);
EXPECT_TRUE(AddTestUtilJS(prerender_host));
EXPECT_TRUE(ExecJs(prerender_host,
JsReplace("add_speculation_rules($1)", kPrerenderChain2)));
EXPECT_TRUE(ExecJs(prerender_host, R"(
const idlePromise = new Promise(resolve => requestIdleCallback(resolve));
idlePromise;
)"));
EXPECT_TRUE(ExecJs(prerender_host, "add_iframe_async('/title1.html')",
EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
WaitForRequest(GetUrl("/title1.html"), 1);
EXPECT_EQ(GetRequestCount(kPrerenderChain2), 0);
EXPECT_FALSE(HasHostForUrl(kPrerenderChain2));
NavigatePrimaryPage(kPrerenderChain1);
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderChain1);
WaitForPrerenderLoadCompletion(kPrerenderChain2);
EXPECT_EQ(GetRequestCount(kPrerenderChain2), 1);
EXPECT_TRUE(HasHostForUrl(kPrerenderChain2));
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, IgnoreSubFrameInitiatedPrerender) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kSubFrameUrl =
GetUrl("/prerender/page_with_trigger_function.html");
const GURL kPrerenderingUrl = GetUrl("/title.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
RenderFrameHostImpl* main_frame_host = current_frame_host();
EXPECT_TRUE(AddTestUtilJS(main_frame_host));
EXPECT_EQ("LOADED",
EvalJs(web_contents(), JsReplace("add_iframe($1)", kSubFrameUrl)));
RenderFrameHost* child_frame_host = ChildFrameAt(main_frame_host, 0);
ASSERT_NE(child_frame_host, nullptr);
ASSERT_EQ(child_frame_host->GetLastCommittedURL(), kSubFrameUrl);
EXPECT_TRUE(ExecJs(child_frame_host,
JsReplace("add_speculation_rules($1)", kPrerenderingUrl)));
EXPECT_TRUE(ExecJs(child_frame_host, R"(
const idlePromise = new Promise(resolve => requestIdleCallback(resolve));
idlePromise;
)"));
EXPECT_TRUE(ExecJs(main_frame_host, "add_iframe_async('/title1.html')",
EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
WaitForRequest(GetUrl("/title1.html"), 1);
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 0);
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CloseOnPrerendering) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0);
AddPrerender(kPrerenderingUrl);
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl));
shell()->Close();
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kTabClosedWithoutUserGesture);
}
namespace {
class RedirectChainObserver : public WebContentsObserver {
public:
RedirectChainObserver(WebContents& web_contents, const GURL& url)
: WebContentsObserver(&web_contents), url_(url) {}
std::vector<GURL>& redirect_chain() { return redirect_chain_; }
private:
void DidFinishNavigation(NavigationHandle* handle) override {
if (handle->GetURL() != url_) {
return;
}
redirect_chain_ = handle->GetRedirectChain();
}
const GURL url_;
std::vector<GURL> redirect_chain_;
};
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, SameOriginRedirection) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kRedirectedUrl = GetUrl("/empty.html?prerender");
const GURL kPrerenderingUrl =
GetUrl("/server-redirect?" + kRedirectedUrl.spec());
RedirectChainObserver redirect_chain_observer(*shell()->web_contents(),
kRedirectedUrl);
AddPrerender(kPrerenderingUrl);
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_EQ(GetRequestCount(kRedirectedUrl), 1);
ASSERT_EQ(2u, redirect_chain_observer.redirect_chain().size());
EXPECT_EQ(kPrerenderingUrl, redirect_chain_observer.redirect_chain()[0]);
EXPECT_EQ(kRedirectedUrl, redirect_chain_observer.redirect_chain()[1]);
EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl));
EXPECT_FALSE(HasHostForUrl(kRedirectedUrl));
RedirectChainObserver activation_redirect_chain_observer(
*shell()->web_contents(), kRedirectedUrl);
NavigationHandleObserver activation_observer(web_contents(),
kPrerenderingUrl);
NavigatePrimaryPage(kPrerenderingUrl);
ASSERT_EQ(1u, activation_redirect_chain_observer.redirect_chain().size());
EXPECT_EQ(kRedirectedUrl,
activation_redirect_chain_observer.redirect_chain()[0]);
ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id();
ExpectPreloadingAttemptUkm({attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kSuccess,
PreloadingFailureReason::kUnspecified,
true,
kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate)});
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CrossSiteRedirection) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kRedirectedUrl = GetCrossSiteUrl("/empty.html?prerender");
const GURL kPrerenderingUrl =
GetUrl("/server-redirect?" + kRedirectedUrl.spec());
test::PrerenderHostObserver host_observer(*web_contents_impl(),
kPrerenderingUrl);
AddPrerenderAsync(kPrerenderingUrl);
host_observer.WaitForDestroyed();
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_EQ(GetRequestCount(kRedirectedUrl), 0);
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
EXPECT_FALSE(HasHostForUrl(kRedirectedUrl));
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kCrossSiteRedirectInInitialNavigation);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, Activation_iFrame) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
EXPECT_TRUE(AddTestUtilJS(current_frame_host()));
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_EQ("LOADED", EvalJs(web_contents(),
JsReplace("add_iframe($1)", kPrerenderingUrl)));
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 2);
EXPECT_EQ(GetHostForUrl(kPrerenderingUrl), host_id);
}
IN_PROC_BROWSER_TEST_F(
PrerenderBrowserTest,
SessionHistoryShouldHaveSingleNavigationEntryInPrerender) {
const GURL kInitialUrl = GetUrl("/empty.html?initial");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
TestNavigationHistory(kInitialUrl, 0,
1);
const GURL k2ndUrl = GetUrl("/empty.html?2nd");
ASSERT_TRUE(NavigateToURL(shell(), k2ndUrl));
TestNavigationHistory(k2ndUrl, 1,
2);
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHost* prerender_frame_host = GetPrerenderedMainFrameHost(host_id);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1);
TestNavigationHistory(k2ndUrl, 1,
2);
AssertPrerenderHistoryLength(host_id, prerender_frame_host);
{
FrameNavigateParamsCapturer capturer(
FrameTreeNode::From(prerender_frame_host));
ASSERT_EQ(base::Value(),
EvalJs(prerender_frame_host,
"history.replaceState('state1', null, null)"));
TestNavigationHistory(k2ndUrl, 1,
2);
AssertPrerenderHistoryLength(host_id, prerender_frame_host);
EXPECT_EQ("state1", EvalJs(prerender_frame_host, "history.state"));
EXPECT_EQ(NAVIGATION_TYPE_MAIN_FRAME_EXISTING_ENTRY,
capturer.navigation_type());
EXPECT_TRUE(capturer.is_same_document());
EXPECT_TRUE(capturer.did_replace_entry());
}
{
FrameNavigateParamsCapturer capturer(
FrameTreeNode::From(prerender_frame_host));
ASSERT_EQ(base::Value(), EvalJs(prerender_frame_host,
"history.pushState('state2', null, null)"));
TestNavigationHistory(k2ndUrl, 1,
2);
AssertPrerenderHistoryLength(host_id, prerender_frame_host);
EXPECT_EQ("state2", EvalJs(prerender_frame_host, "history.state"));
EXPECT_EQ(NAVIGATION_TYPE_MAIN_FRAME_EXISTING_ENTRY,
capturer.navigation_type());
EXPECT_TRUE(capturer.is_same_document());
EXPECT_TRUE(capturer.did_replace_entry());
}
{
FrameNavigateParamsCapturer capturer(
FrameTreeNode::From(prerender_frame_host));
const GURL kPrerenderingAnchorUrl = GetUrl("/empty.html?prerender#anchor");
NavigatePrerenderedPage(host_id, kPrerenderingAnchorUrl);
WaitForPrerenderLoadCompletion(host_id);
ASSERT_EQ(GetRequestCount(kPrerenderingAnchorUrl), 1);
TestNavigationHistory(k2ndUrl, 1,
2);
AssertPrerenderHistoryLength(host_id, prerender_frame_host);
EXPECT_EQ(base::Value(), EvalJs(prerender_frame_host, "history.state"));
EXPECT_EQ(NAVIGATION_TYPE_MAIN_FRAME_EXISTING_ENTRY,
capturer.navigation_type());
EXPECT_TRUE(capturer.is_same_document());
EXPECT_TRUE(capturer.did_replace_entry());
}
{
const GURL kSameOriginSubframeUrl1 =
GetUrl("/empty.html?same_origin_iframe1");
EXPECT_TRUE(AddTestUtilJS(prerender_frame_host));
ASSERT_EQ("LOADED",
EvalJs(prerender_frame_host,
JsReplace("add_iframe($1)", kSameOriginSubframeUrl1)));
ASSERT_EQ(GetRequestCount(kSameOriginSubframeUrl1), 1);
auto* child_frame = ChildFrameAt(prerender_frame_host, 0);
ASSERT_NE(nullptr, child_frame);
EXPECT_EQ(kSameOriginSubframeUrl1, child_frame->GetLastCommittedURL());
{
FrameNavigateParamsCapturer capturer(FrameTreeNode::From(child_frame));
const GURL kSameOriginSubframeUrl2 =
GetUrl("/empty.html?same_origin_iframe2");
ASSERT_EQ(kSameOriginSubframeUrl2,
EvalJs(child_frame,
JsReplace("location = $1", kSameOriginSubframeUrl2)));
capturer.Wait();
child_frame = ChildFrameAt(prerender_frame_host, 0);
EXPECT_EQ(kSameOriginSubframeUrl2, child_frame->GetLastCommittedURL());
ASSERT_EQ(GetRequestCount(kSameOriginSubframeUrl2), 1);
TestNavigationHistory(k2ndUrl, 1,
2);
AssertPrerenderHistoryLength(host_id, prerender_frame_host);
EXPECT_EQ(base::Value(), EvalJs(prerender_frame_host, "history.state"));
EXPECT_EQ(NAVIGATION_TYPE_AUTO_SUBFRAME, capturer.navigation_type());
EXPECT_FALSE(capturer.is_same_document());
EXPECT_TRUE(capturer.did_replace_entry());
EXPECT_TRUE(capturer.is_renderer_initiated());
}
{
FrameNavigateParamsCapturer capturer(FrameTreeNode::From(child_frame));
const GURL kSameOriginSubframeUrl3 =
GetUrl("/empty.html?same_origin_iframe3");
shell()->web_contents()->OpenURL(
OpenURLParams(kSameOriginSubframeUrl3, Referrer(),
child_frame->GetFrameTreeNodeId(),
WindowOpenDisposition::CURRENT_TAB,
ui::PAGE_TRANSITION_AUTO_SUBFRAME,
false),
{});
capturer.Wait();
child_frame = ChildFrameAt(prerender_frame_host, 0);
EXPECT_EQ(kSameOriginSubframeUrl3, child_frame->GetLastCommittedURL());
ASSERT_EQ(GetRequestCount(kSameOriginSubframeUrl3), 1);
TestNavigationHistory(k2ndUrl, 1,
2);
AssertPrerenderHistoryLength(host_id, prerender_frame_host);
EXPECT_EQ(base::Value(), EvalJs(prerender_frame_host, "history.state"));
EXPECT_EQ(NAVIGATION_TYPE_AUTO_SUBFRAME, capturer.navigation_type());
EXPECT_FALSE(capturer.is_same_document());
EXPECT_TRUE(capturer.did_replace_entry());
EXPECT_FALSE(capturer.is_renderer_initiated());
}
}
{
int current_request_count = GetRequestCount(k2ndUrl);
ASSERT_EQ(base::Value(), EvalJs(prerender_frame_host, "history.back()"));
EXPECT_FALSE(FrameTreeNode::GloballyFindByID(host_id)
->frame_tree()
.IsLoadingIncludingInnerFrameTrees());
TestNavigationHistory(k2ndUrl, 1,
2);
AssertPrerenderHistoryLength(host_id, prerender_frame_host);
EXPECT_EQ(base::Value(), EvalJs(prerender_frame_host, "history.state"));
EXPECT_EQ(current_request_count, GetRequestCount(k2ndUrl));
}
{
int current_request_count = GetRequestCount(k2ndUrl);
ASSERT_EQ(base::Value(), EvalJs(prerender_frame_host, "history.forward()"));
EXPECT_FALSE(FrameTreeNode::GloballyFindByID(host_id)
->frame_tree()
.IsLoadingIncludingInnerFrameTrees());
TestNavigationHistory(k2ndUrl, 1,
2);
AssertPrerenderHistoryLength(host_id, prerender_frame_host);
EXPECT_EQ(base::Value(), EvalJs(prerender_frame_host, "history.state"));
EXPECT_EQ(current_request_count, GetRequestCount(k2ndUrl));
}
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, SessionHistoryAfterActivation) {
const GURL kInitialUrl = GetUrl("/empty.html?initial");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
TestNavigationHistory(kInitialUrl, 0, 1);
const GURL k2ndUrl = GetUrl("/empty.html?2nd");
ASSERT_TRUE(NavigateToURL(shell(), k2ndUrl));
ASSERT_EQ(GetRequestCount(k2ndUrl), 1);
TestNavigationHistory(k2ndUrl, 1, 2);
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHost* prerender_frame_host = GetPrerenderedMainFrameHost(host_id);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1);
TestNavigationHistory(k2ndUrl, 1, 2);
ASSERT_EQ(base::Value(),
EvalJs(prerender_frame_host,
"history.pushState('teststate', null, null)"));
TestNavigationHistory(k2ndUrl, 1, 2);
AssertPrerenderHistoryLength(host_id, prerender_frame_host);
EXPECT_EQ("teststate", EvalJs(prerender_frame_host, "history.state"));
NavigatePrimaryPage(kPrerenderingUrl);
TestNavigationHistory(kPrerenderingUrl, 2, 3);
EXPECT_EQ("teststate", EvalJs(web_contents(), "history.state"));
FrameTreeNode* root = static_cast<WebContentsImpl*>(web_contents())
->GetPrimaryFrameTree()
.root();
{
FrameNavigateParamsCapturer capturer(root);
GoBack();
TestNavigationHistory(k2ndUrl, 1, 3);
EXPECT_EQ(base::Value(), EvalJs(web_contents(), "history.state"));
EXPECT_EQ(NAVIGATION_TYPE_MAIN_FRAME_EXISTING_ENTRY,
capturer.navigation_type());
EXPECT_FALSE(capturer.is_same_document());
}
{
FrameNavigateParamsCapturer capturer(root);
GoForward();
TestNavigationHistory(kPrerenderingUrl, 2, 3);
EXPECT_EQ("teststate", EvalJs(web_contents(), "history.state"));
EXPECT_EQ(NAVIGATION_TYPE_MAIN_FRAME_EXISTING_ENTRY,
capturer.navigation_type());
EXPECT_FALSE(capturer.is_same_document());
}
}
class PrerenderOopsifBrowserTest : public PrerenderBrowserTest {
public:
PrerenderOopsifBrowserTest() {
feature_list_.InitWithFeaturesAndParameters(
{{blink::features::kIsolateSandboxedIframes,
{{"grouping", "per-origin"}}}},
{});
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(PrerenderOopsifBrowserTest,
OopsifSrcdocSandboxIframeWithPostmessage) {
const GURL kInitialUrl =
GetUrl("/prerender/cross_origin_prerender.html?initial");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
EXPECT_TRUE(AddTestUtilJS(current_frame_host()));
const GURL kPrerenderingUrl =
GetUrl("/prerender/cross_origin_srcdoc_sandboxed_postmessage.html");
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHost* prerender_frame_host = GetPrerenderedMainFrameHost(host_id);
EXPECT_TRUE(AddTestUtilJS(prerender_frame_host));
EXPECT_TRUE(ExecJs(prerender_frame_host, "createSrcdoc();"));
base::RunLoop().RunUntilIdle();
const GURL kSameOriginSubframeUrl =
GetUrl("/prerender/cross_origin_prerender.html");
ASSERT_EQ("LOADED",
EvalJs(prerender_frame_host,
JsReplace("add_iframe($1)", kSameOriginSubframeUrl)));
NavigatePrimaryPage(kPrerenderingUrl);
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
RenderFrameHostImpl* main_frame =
static_cast<RenderFrameHostImpl*>(web_contents()->GetPrimaryMainFrame());
EXPECT_TRUE(ExecJs(
main_frame,
"Promise.all([child_response_promise, prerender_handler_promise]);"));
if (AreAllSitesIsolatedForTesting()) {
RenderFrameHostImpl* sandboxed_render_frame_host =
main_frame->child_at(0)->current_frame_host();
EXPECT_TRUE(sandboxed_render_frame_host->GetSiteInstance()
->GetSiteInfo()
.is_sandboxed());
ASSERT_NE(main_frame->GetProcess(),
sandboxed_render_frame_host->GetProcess());
}
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
DeferCrossOriginSubframeNavigation) {
const GURL kInitialUrl =
GetUrl("/prerender/cross_origin_prerender.html?initial");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
EXPECT_TRUE(AddTestUtilJS(current_frame_host()));
const GURL kPrerenderingUrl =
GetUrl("/prerender/cross_origin_prerender.html?prerender");
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
const GURL kSameOriginSubframeUrl =
GetUrl("/prerender/cross_origin_prerender.html?same_origin_iframe");
const GURL kCrossOriginSubframeUrl = GetCrossSiteUrl(
"/prerender/cross_origin_prerender.html?cross_origin_iframe");
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1);
ASSERT_EQ(GetRequestCount(kSameOriginSubframeUrl), 0);
ASSERT_EQ(GetRequestCount(kCrossOriginSubframeUrl), 0);
RenderFrameHost* prerender_frame_host = GetPrerenderedMainFrameHost(host_id);
EXPECT_TRUE(AddTestUtilJS(prerender_frame_host));
ExecuteScriptAsync(prerender_frame_host, JsReplace("add_iframe_async($1)",
kCrossOriginSubframeUrl));
base::RunLoop().RunUntilIdle();
ASSERT_EQ("LOADED",
EvalJs(prerender_frame_host,
JsReplace("add_iframe($1)", kSameOriginSubframeUrl)));
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1);
ASSERT_EQ(GetRequestCount(kSameOriginSubframeUrl), 1);
ASSERT_EQ(GetRequestCount(kCrossOriginSubframeUrl), 0);
NavigatePrimaryPage(kPrerenderingUrl);
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
ASSERT_EQ("LOADED",
EvalJs(prerender_frame_host, JsReplace("wait_iframe_async($1)",
kCrossOriginSubframeUrl)));
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1);
ASSERT_EQ(GetRequestCount(kSameOriginSubframeUrl), 1);
EXPECT_EQ(GetRequestCount(kCrossOriginSubframeUrl), 1);
const char kInitialDocumentPrerenderingScript[] =
"initial_document_prerendering";
const char kCurrentDocumentPrerenderingScript[] = "document.prerendering";
const char kOnprerenderingchangeObservedScript[] =
"onprerenderingchange_observed";
const char kActivationStartScript[] =
"performance.getEntriesByType('navigation')[0].activationStart";
EXPECT_EQ(true,
EvalJs(prerender_frame_host, kInitialDocumentPrerenderingScript));
EXPECT_EQ(false,
EvalJs(prerender_frame_host, kCurrentDocumentPrerenderingScript));
EXPECT_EQ(true,
EvalJs(prerender_frame_host, kOnprerenderingchangeObservedScript));
EXPECT_NE(0, EvalJs(prerender_frame_host, kActivationStartScript));
RenderFrameHost* same_origin_render_frame_host = FindRenderFrameHost(
prerender_frame_host->GetPage(), kSameOriginSubframeUrl);
CHECK(same_origin_render_frame_host);
EXPECT_EQ(true, EvalJs(same_origin_render_frame_host,
kInitialDocumentPrerenderingScript));
EXPECT_EQ(false, EvalJs(same_origin_render_frame_host,
kCurrentDocumentPrerenderingScript));
EXPECT_EQ(true, EvalJs(same_origin_render_frame_host,
kOnprerenderingchangeObservedScript));
EXPECT_NE(0, EvalJs(same_origin_render_frame_host, kActivationStartScript));
RenderFrameHost* cross_origin_render_frame_host = FindRenderFrameHost(
prerender_frame_host->GetPage(), kCrossOriginSubframeUrl);
CHECK(cross_origin_render_frame_host);
EXPECT_EQ(false, EvalJs(cross_origin_render_frame_host,
kInitialDocumentPrerenderingScript));
EXPECT_EQ(false, EvalJs(cross_origin_render_frame_host,
kCurrentDocumentPrerenderingScript));
EXPECT_EQ(false, EvalJs(cross_origin_render_frame_host,
kOnprerenderingchangeObservedScript));
EXPECT_EQ(0, EvalJs(cross_origin_render_frame_host, kActivationStartScript));
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
DeferCrossOriginRedirectsOnSubframeNavigation) {
const GURL kInitialUrl =
GetUrl("/prerender/cross_origin_prerender.html?initial");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kPrerenderingUrl =
GetUrl("/prerender/cross_origin_prerender.html?prerender");
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
const GURL kCrossOriginSubframeUrl = GetCrossSiteUrl(
"/prerender/cross_origin_prerender.html?cross_origin_iframe");
const GURL kServerRedirectSubframeUrl =
GetUrl("/server-redirect?" + kCrossOriginSubframeUrl.spec());
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1);
ASSERT_EQ(GetRequestCount(kServerRedirectSubframeUrl), 0);
ASSERT_EQ(GetRequestCount(kCrossOriginSubframeUrl), 0);
RenderFrameHost* prerender_frame_host = GetPrerenderedMainFrameHost(host_id);
EXPECT_TRUE(AddTestUtilJS(prerender_frame_host));
ExecuteScriptAsync(
prerender_frame_host,
JsReplace("add_iframe_async($1)", kServerRedirectSubframeUrl));
WaitForRequest(kServerRedirectSubframeUrl, 1);
ASSERT_EQ(GetRequestCount(kServerRedirectSubframeUrl), 1);
ASSERT_EQ(GetRequestCount(kCrossOriginSubframeUrl), 0);
NavigatePrimaryPage(kPrerenderingUrl);
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
ASSERT_EQ("LOADED", EvalJs(prerender_frame_host,
JsReplace("wait_iframe_async($1)",
kServerRedirectSubframeUrl)));
EXPECT_EQ(GetRequestCount(kServerRedirectSubframeUrl), 1);
EXPECT_EQ(GetRequestCount(kCrossOriginSubframeUrl), 1);
const char kInitialDocumentPrerenderingScript[] =
"initial_document_prerendering";
const char kCurrentDocumentPrerenderingScript[] = "document.prerendering";
const char kOnprerenderingchangeObservedScript[] =
"onprerenderingchange_observed";
EXPECT_EQ(true,
EvalJs(prerender_frame_host, kInitialDocumentPrerenderingScript));
EXPECT_EQ(false,
EvalJs(prerender_frame_host, kCurrentDocumentPrerenderingScript));
EXPECT_EQ(true,
EvalJs(prerender_frame_host, kOnprerenderingchangeObservedScript));
RenderFrameHost* cross_origin_render_frame_host = FindRenderFrameHost(
prerender_frame_host->GetPage(), kCrossOriginSubframeUrl);
CHECK(cross_origin_render_frame_host);
EXPECT_EQ(false, EvalJs(cross_origin_render_frame_host,
kInitialDocumentPrerenderingScript));
EXPECT_EQ(false, EvalJs(cross_origin_render_frame_host,
kCurrentDocumentPrerenderingScript));
EXPECT_EQ(false, EvalJs(cross_origin_render_frame_host,
kOnprerenderingchangeObservedScript));
}
class PrerenderMainFrameNavigationBrowserTest
: public testing::WithParamInterface<PreloadingTriggerType>,
public PrerenderBrowserTest {
protected:
enum class NavigationType {
kSameOrigin,
kSameSiteCrossOrigin,
kSameSiteCrossOriginWithOptIn,
kCrossSite,
};
void TestMainFrameNavigation(
const std::vector<NavigationType>& navigation_types,
PrerenderFinalStatus expected_status) {
ASSERT_FALSE(navigation_types.empty());
PreloadingTriggerType trigger_type = GetParam();
std::vector<GURL> urls;
for (auto type : navigation_types) {
urls.push_back(CreateUrl(type));
}
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id;
std::unique_ptr<PrerenderHandle> prerender_handle;
switch (trigger_type) {
case PreloadingTriggerType::kSpeculationRule:
host_id = AddPrerender(kPrerenderingUrl);
break;
case PreloadingTriggerType::kSpeculationRuleFromIsolatedWorld:
host_id = AddPrerender(kPrerenderingUrl, 1);
break;
case PreloadingTriggerType::kEmbedder:
prerender_handle = AddEmbedderTriggeredPrerender(kPrerenderingUrl);
host_id = static_cast<PrerenderHandleImpl*>(prerender_handle.get())
->frame_tree_node_id_for_testing();
break;
case PreloadingTriggerType::kSpeculationRuleFromAutoSpeculationRules:
FAIL() << "Auto speculation rules does not work with empty.html";
}
ASSERT_TRUE(host_id);
test::PrerenderHostObserver observer(*web_contents_impl(), host_id);
for (auto it = urls.begin(); it != urls.end() - 1; ++it) {
TestNavigationManager navigation_observer(web_contents(), *it);
NavigatePrerenderedPage(host_id, *it);
ASSERT_TRUE(navigation_observer.WaitForNavigationFinished());
EXPECT_TRUE(navigation_observer.was_successful());
}
const GURL& last_url = urls.back();
switch (expected_status) {
case PrerenderFinalStatus::kActivated: {
TestNavigationManager navigation_observer(web_contents(), last_url);
NavigatePrerenderedPage(host_id, last_url);
ASSERT_TRUE(navigation_observer.WaitForNavigationFinished());
EXPECT_TRUE(navigation_observer.was_successful());
switch (trigger_type) {
case PreloadingTriggerType::kSpeculationRule:
case PreloadingTriggerType::kSpeculationRuleFromIsolatedWorld:
NavigatePrimaryPage(kPrerenderingUrl);
break;
case PreloadingTriggerType::kEmbedder:
NavigatePrimaryPageFromAddressBar(kPrerenderingUrl);
break;
case PreloadingTriggerType::kSpeculationRuleFromAutoSpeculationRules:
FAIL() << "Auto speculation rules does not work with empty.html";
}
observer.WaitForActivation();
EXPECT_TRUE(observer.was_activated());
EXPECT_EQ(web_contents()->GetLastCommittedURL(), last_url);
break;
}
default: {
NavigatePrerenderedPage(host_id, last_url);
observer.WaitForDestroyed();
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
break;
}
}
switch (trigger_type) {
case PreloadingTriggerType::kSpeculationRule:
ExpectFinalStatusForSpeculationRule(expected_status);
break;
case PreloadingTriggerType::kSpeculationRuleFromIsolatedWorld:
ExpectFinalStatusForSpeculationRuleFromIsolatedWorld(expected_status);
break;
case PreloadingTriggerType::kEmbedder:
ExpectFinalStatusForEmbedder(expected_status);
break;
case PreloadingTriggerType::kSpeculationRuleFromAutoSpeculationRules:
FAIL() << "Auto speculation rules does not work with empty.html";
}
}
void TestMainFrameRedirection(
const std::vector<NavigationType>& redirection_types,
PrerenderFinalStatus expected_status) {
ASSERT_FALSE(redirection_types.empty());
PreloadingTriggerType trigger_type = GetParam();
const GURL final_url = CreateUrl(redirection_types.back());
GURL url = final_url;
for (auto it = redirection_types.rbegin() + 1;
it != redirection_types.rend(); ++it) {
url = CreateRedirectionUrl(*it, url);
}
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id;
std::unique_ptr<PrerenderHandle> prerender_handle;
switch (trigger_type) {
case PreloadingTriggerType::kSpeculationRule:
host_id = AddPrerender(kPrerenderingUrl);
break;
case PreloadingTriggerType::kSpeculationRuleFromIsolatedWorld:
host_id = AddPrerender(kPrerenderingUrl, 1);
break;
case PreloadingTriggerType::kEmbedder:
prerender_handle = AddEmbedderTriggeredPrerender(kPrerenderingUrl);
host_id = static_cast<PrerenderHandleImpl*>(prerender_handle.get())
->frame_tree_node_id_for_testing();
break;
case PreloadingTriggerType::kSpeculationRuleFromAutoSpeculationRules:
FAIL() << "Auto speculation rules does not work with empty.html";
}
ASSERT_TRUE(host_id);
test::PrerenderHostObserver observer(*web_contents_impl(), host_id);
TestNavigationManager navigation_observer(web_contents(), url);
NavigatePrerenderedPage(host_id, url);
ASSERT_TRUE(navigation_observer.WaitForNavigationFinished());
switch (expected_status) {
case PrerenderFinalStatus::kActivated: {
EXPECT_TRUE(navigation_observer.was_successful());
switch (trigger_type) {
case PreloadingTriggerType::kSpeculationRule:
case PreloadingTriggerType::kSpeculationRuleFromIsolatedWorld:
NavigatePrimaryPage(kPrerenderingUrl);
break;
case PreloadingTriggerType::kEmbedder:
NavigatePrimaryPageFromAddressBar(kPrerenderingUrl);
break;
case PreloadingTriggerType::kSpeculationRuleFromAutoSpeculationRules:
FAIL() << "Auto speculation rules does not work with empty.html";
}
observer.WaitForActivation();
EXPECT_TRUE(observer.was_activated());
EXPECT_EQ(web_contents()->GetLastCommittedURL(), final_url);
break;
}
default: {
EXPECT_FALSE(navigation_observer.was_successful());
observer.WaitForDestroyed();
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
break;
}
}
switch (trigger_type) {
case PreloadingTriggerType::kSpeculationRule:
ExpectFinalStatusForSpeculationRule(expected_status);
break;
case PreloadingTriggerType::kSpeculationRuleFromIsolatedWorld:
ExpectFinalStatusForSpeculationRuleFromIsolatedWorld(expected_status);
break;
case PreloadingTriggerType::kEmbedder:
ExpectFinalStatusForEmbedder(expected_status);
break;
case PreloadingTriggerType::kSpeculationRuleFromAutoSpeculationRules:
FAIL() << "Auto speculation rules does not work with empty.html";
}
}
private:
GURL CreateUrl(NavigationType type) {
static int number_for_prefix = 0;
std::string prefix = base::NumberToString(number_for_prefix++);
switch (type) {
case NavigationType::kSameOrigin:
return GetUrl("/empty.html?" + prefix);
case NavigationType::kSameSiteCrossOrigin:
return GetSameSiteCrossOriginUrl("/empty.html?" + prefix);
case NavigationType::kSameSiteCrossOriginWithOptIn:
return GetSameSiteCrossOriginUrl(
"/prerender/prerender_with_opt_in_header.html?" + prefix);
case NavigationType::kCrossSite:
return GetCrossSiteUrl("/empty.html?" + prefix);
}
}
GURL CreateRedirectionUrl(NavigationType type, const GURL& url_to_redirect) {
switch (type) {
case NavigationType::kSameOrigin:
return GetUrl("/server-redirect?" + url_to_redirect.spec());
case NavigationType::kSameSiteCrossOrigin:
return GetSameSiteCrossOriginUrl("/server-redirect?" +
url_to_redirect.spec());
case NavigationType::kSameSiteCrossOriginWithOptIn:
return GetSameSiteCrossOriginUrl(
"/server-redirect-credentialed-prerender?" +
url_to_redirect.spec());
case NavigationType::kCrossSite:
return GetCrossSiteUrl("/server-redirect?" + url_to_redirect.spec());
}
}
};
INSTANTIATE_TEST_SUITE_P(
All,
PrerenderMainFrameNavigationBrowserTest,
testing::Values(PreloadingTriggerType::kSpeculationRule,
PreloadingTriggerType::kSpeculationRuleFromIsolatedWorld,
PreloadingTriggerType::kEmbedder),
[](const testing::TestParamInfo<PreloadingTriggerType>& info) {
switch (info.param) {
case PreloadingTriggerType::kSpeculationRule:
return "SpeculationRule";
case PreloadingTriggerType::kSpeculationRuleFromIsolatedWorld:
return "SpeculationRuleFromIsolatedWorld";
case PreloadingTriggerType::kEmbedder:
return "Embedder";
case PreloadingTriggerType::kSpeculationRuleFromAutoSpeculationRules:
ADD_FAILURE() << "Auto speculation rules does not work with "
"TestMainFrameNavigation";
return "SpeculationRuleFromAutoSpeculationRules";
}
});
IN_PROC_BROWSER_TEST_P(PrerenderMainFrameNavigationBrowserTest, SameOrigin) {
std::vector<NavigationType> navigations = {NavigationType::kSameOrigin};
TestMainFrameNavigation(navigations, PrerenderFinalStatus::kActivated);
}
IN_PROC_BROWSER_TEST_P(PrerenderMainFrameNavigationBrowserTest,
SameSiteCrossOriginWithOptIn) {
std::vector<NavigationType> navigations = {
NavigationType::kSameSiteCrossOriginWithOptIn};
TestMainFrameNavigation(navigations, PrerenderFinalStatus::kActivated);
}
IN_PROC_BROWSER_TEST_P(PrerenderMainFrameNavigationBrowserTest,
SameSiteCrossOrigin) {
std::vector<NavigationType> navigations = {
NavigationType::kSameSiteCrossOrigin};
TestMainFrameNavigation(
navigations,
PrerenderFinalStatus::
kSameSiteCrossOriginNavigationNotOptInInMainFrameNavigation);
}
IN_PROC_BROWSER_TEST_P(PrerenderMainFrameNavigationBrowserTest, CrossSite) {
std::vector<NavigationType> navigations = {NavigationType::kCrossSite};
TestMainFrameNavigation(
navigations,
PrerenderFinalStatus::kCrossSiteNavigationInMainFrameNavigation);
}
IN_PROC_BROWSER_TEST_P(PrerenderMainFrameNavigationBrowserTest,
SameSiteCrossOriginWithOptIn_SameOrigin) {
std::vector<NavigationType> navigations = {
NavigationType::kSameSiteCrossOriginWithOptIn,
NavigationType::kSameOrigin};
TestMainFrameNavigation(navigations, PrerenderFinalStatus::kActivated);
}
IN_PROC_BROWSER_TEST_P(
PrerenderMainFrameNavigationBrowserTest,
SameSiteCrossOriginWithOptIn_SameSiteCrossOriginWithOptIn) {
std::vector<NavigationType> navigations = {
NavigationType::kSameSiteCrossOriginWithOptIn,
NavigationType::kSameSiteCrossOriginWithOptIn};
TestMainFrameNavigation(navigations, PrerenderFinalStatus::kActivated);
}
IN_PROC_BROWSER_TEST_P(PrerenderMainFrameNavigationBrowserTest,
SameSiteCrossOriginWithOptIn_SameSiteCrossOrigin) {
std::vector<NavigationType> navigations = {
NavigationType::kSameSiteCrossOriginWithOptIn,
NavigationType::kSameSiteCrossOrigin};
TestMainFrameNavigation(
navigations,
PrerenderFinalStatus::
kSameSiteCrossOriginNavigationNotOptInInMainFrameNavigation);
}
IN_PROC_BROWSER_TEST_P(PrerenderMainFrameNavigationBrowserTest,
SameSiteCrossOrigin_CrossSite) {
std::vector<NavigationType> navigations = {
NavigationType::kSameSiteCrossOriginWithOptIn,
NavigationType::kCrossSite};
TestMainFrameNavigation(
navigations,
PrerenderFinalStatus::kCrossSiteNavigationInMainFrameNavigation);
}
IN_PROC_BROWSER_TEST_P(PrerenderMainFrameNavigationBrowserTest,
Redirection_SameOrigin_SameOrigin) {
std::vector<NavigationType> redirections = {NavigationType::kSameOrigin,
NavigationType::kSameOrigin};
TestMainFrameRedirection(redirections, PrerenderFinalStatus::kActivated);
}
IN_PROC_BROWSER_TEST_P(PrerenderMainFrameNavigationBrowserTest,
Redirection_SameOrigin_SameSiteCrossOriginWithOptIn) {
std::vector<NavigationType> redirections = {
NavigationType::kSameOrigin,
NavigationType::kSameSiteCrossOriginWithOptIn};
TestMainFrameRedirection(redirections, PrerenderFinalStatus::kActivated);
}
IN_PROC_BROWSER_TEST_P(PrerenderMainFrameNavigationBrowserTest,
Redirection_SameOrigin_SameSiteCrossOrigin) {
std::vector<NavigationType> redirections = {
NavigationType::kSameOrigin, NavigationType::kSameSiteCrossOrigin};
TestMainFrameRedirection(
redirections,
PrerenderFinalStatus::
kSameSiteCrossOriginRedirectNotOptInInMainFrameNavigation);
}
IN_PROC_BROWSER_TEST_P(PrerenderMainFrameNavigationBrowserTest,
Redirection_SameOrigin_CrossSite) {
std::vector<NavigationType> redirections = {NavigationType::kSameOrigin,
NavigationType::kCrossSite};
TestMainFrameRedirection(
redirections,
PrerenderFinalStatus::kCrossSiteRedirectInMainFrameNavigation);
}
IN_PROC_BROWSER_TEST_P(PrerenderMainFrameNavigationBrowserTest,
Redirection_SameSiteCrossOriginWithOptIn_SameOrigin) {
std::vector<NavigationType> redirections = {
NavigationType::kSameSiteCrossOriginWithOptIn,
NavigationType::kSameOrigin};
TestMainFrameRedirection(redirections, PrerenderFinalStatus::kActivated);
}
IN_PROC_BROWSER_TEST_P(
PrerenderMainFrameNavigationBrowserTest,
Redirection_SameSiteCrossOriginWithOptIn_SameSiteCrossOriginWithOptIn) {
std::vector<NavigationType> redirections = {
NavigationType::kSameSiteCrossOriginWithOptIn,
NavigationType::kSameSiteCrossOriginWithOptIn};
TestMainFrameRedirection(redirections, PrerenderFinalStatus::kActivated);
}
IN_PROC_BROWSER_TEST_P(
PrerenderMainFrameNavigationBrowserTest,
Redirection_SameSiteCrossOriginWithOptIn_SameSiteCrossOrigin) {
std::vector<NavigationType> redirections = {
NavigationType::kSameSiteCrossOriginWithOptIn,
NavigationType::kSameSiteCrossOrigin};
TestMainFrameRedirection(
redirections,
PrerenderFinalStatus::
kSameSiteCrossOriginRedirectNotOptInInMainFrameNavigation);
}
IN_PROC_BROWSER_TEST_P(PrerenderMainFrameNavigationBrowserTest,
Redirection_SameSiteCrossOriginWithOptIn_CrossSite) {
std::vector<NavigationType> redirections = {
NavigationType::kSameSiteCrossOriginWithOptIn,
NavigationType::kCrossSite};
TestMainFrameRedirection(
redirections,
PrerenderFinalStatus::kCrossSiteRedirectInMainFrameNavigation);
}
IN_PROC_BROWSER_TEST_P(
PrerenderMainFrameNavigationBrowserTest,
Redirection_SameOrigin_SameSiteCrossOriginWithOptIn_SameOrigin) {
std::vector<NavigationType> redirections = {
NavigationType::kSameOrigin,
NavigationType::kSameSiteCrossOriginWithOptIn,
NavigationType::kSameOrigin};
TestMainFrameRedirection(redirections, PrerenderFinalStatus::kActivated);
}
IN_PROC_BROWSER_TEST_P(
PrerenderMainFrameNavigationBrowserTest,
Redirection_SameOrigin_SameSiteCrossOriginWithOptIn_SameSiteCrossOriginWithOptIn) {
std::vector<NavigationType> redirections = {
NavigationType::kSameOrigin,
NavigationType::kSameSiteCrossOriginWithOptIn,
NavigationType::kSameSiteCrossOriginWithOptIn};
TestMainFrameRedirection(redirections, PrerenderFinalStatus::kActivated);
}
IN_PROC_BROWSER_TEST_P(
PrerenderMainFrameNavigationBrowserTest,
Redirection_SameOrigin_SameSiteCrossOriginWithOptIn_SameSiteCrossOrigin) {
std::vector<NavigationType> redirections = {
NavigationType::kSameOrigin,
NavigationType::kSameSiteCrossOriginWithOptIn,
NavigationType::kSameSiteCrossOrigin};
TestMainFrameRedirection(
redirections,
PrerenderFinalStatus::
kSameSiteCrossOriginRedirectNotOptInInMainFrameNavigation);
}
IN_PROC_BROWSER_TEST_P(
PrerenderMainFrameNavigationBrowserTest,
Redirection_SameOrigin_SameSiteCrossOriginWithOptIn_CrossSite) {
std::vector<NavigationType> redirections = {
NavigationType::kSameOrigin,
NavigationType::kSameSiteCrossOriginWithOptIn,
NavigationType::kCrossSite};
TestMainFrameRedirection(
redirections,
PrerenderFinalStatus::kCrossSiteRedirectInMainFrameNavigation);
}
IN_PROC_BROWSER_TEST_P(
PrerenderMainFrameNavigationBrowserTest,
Redirection_SameSiteCrossOriginWithOptIn_SameOrigin_SameOrigin) {
std::vector<NavigationType> redirections = {
NavigationType::kSameSiteCrossOriginWithOptIn,
NavigationType::kSameOrigin, NavigationType::kSameOrigin};
TestMainFrameRedirection(redirections, PrerenderFinalStatus::kActivated);
}
IN_PROC_BROWSER_TEST_P(
PrerenderMainFrameNavigationBrowserTest,
Redirection_SameSiteCrossOriginWithOptIn_SameOrigin_SameSiteCrossOriginWithOptIn) {
std::vector<NavigationType> redirections = {
NavigationType::kSameSiteCrossOriginWithOptIn,
NavigationType::kSameOrigin,
NavigationType::kSameSiteCrossOriginWithOptIn};
TestMainFrameRedirection(redirections, PrerenderFinalStatus::kActivated);
}
IN_PROC_BROWSER_TEST_P(
PrerenderMainFrameNavigationBrowserTest,
Redirection_SameSiteCrossOriginWithOptIn_SameOrigin_SameSiteCrossOrigin) {
std::vector<NavigationType> redirections = {
NavigationType::kSameSiteCrossOriginWithOptIn,
NavigationType::kSameOrigin, NavigationType::kSameSiteCrossOrigin};
TestMainFrameRedirection(
redirections,
PrerenderFinalStatus::
kSameSiteCrossOriginRedirectNotOptInInMainFrameNavigation);
}
IN_PROC_BROWSER_TEST_P(
PrerenderMainFrameNavigationBrowserTest,
Redirection_SameSiteCrossOriginWithOptIn_SameOrigin_CrossSite) {
std::vector<NavigationType> redirections = {
NavigationType::kSameSiteCrossOriginWithOptIn,
NavigationType::kSameOrigin, NavigationType::kCrossSite};
TestMainFrameRedirection(
redirections,
PrerenderFinalStatus::kCrossSiteRedirectInMainFrameNavigation);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, MainFrameNavigation_NonHttpUrl) {
const GURL initial_url = GetUrl("/empty.html");
const GURL prerendering_url = GetUrl("/empty.html?prerender");
const GURL non_http_url("ftp://example.com/");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), initial_url);
FrameTreeNodeId host_id = AddPrerender(prerendering_url);
ASSERT_TRUE(host_id);
TestNavigationManager navigation_observer(web_contents(), non_http_url);
NavigatePrerenderedPage(host_id, non_http_url);
ASSERT_TRUE(navigation_observer.WaitForNavigationFinished());
EXPECT_FALSE(navigation_observer.was_successful());
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kInvalidSchemeNavigation);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, MainFrameFragmentNavigation) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl =
GetUrl("/navigation_controller/hash_anchor_with_iframe.html");
const GURL kAnchorUrl =
GetUrl("/navigation_controller/hash_anchor_with_iframe.html#Test");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
NavigatePrerenderedPage(host_id, kAnchorUrl);
WaitForPrerenderLoadCompletion(host_id);
RedirectChainObserver redirect_chain_observer(*shell()->web_contents(),
kAnchorUrl);
NavigatePrimaryPage(kPrerenderingUrl);
ASSERT_EQ(1u, redirect_chain_observer.redirect_chain().size());
EXPECT_EQ(kAnchorUrl, redirect_chain_observer.redirect_chain()[0]);
NavigatePrimaryPage(kAnchorUrl);
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, Activation_PopUpWindow) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
EXPECT_TRUE(AddTestUtilJS(current_frame_host()));
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_EQ("LOADED", EvalJs(web_contents(),
JsReplace("open_window($1)", kPrerenderingUrl)));
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 2);
EXPECT_EQ(GetHostForUrl(kPrerenderingUrl), host_id);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, Activation_PageWithPopUpWindow) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
EXPECT_TRUE(AddTestUtilJS(current_frame_host()));
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender_next");
AddPrerender(kPrerenderingUrl);
ASSERT_TRUE(HasHostForUrl(kPrerenderingUrl));
const GURL kWindowUrl = GetUrl("/empty.html?prerender_window");
EXPECT_EQ("LOADED",
EvalJs(web_contents(), JsReplace("open_window($1)", kWindowUrl)));
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1);
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 2);
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kActivatedWithAuxiliaryBrowsingContexts);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
Activation_PageWithPopUpWindow_OpenerIsNullified) {
const GURL initial_url = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
EXPECT_TRUE(AddTestUtilJS(current_frame_host()));
const GURL prerendering_url = GetUrl("/empty.html?prerender_next");
AddPrerender(prerendering_url);
ASSERT_TRUE(HasHostForUrl(prerendering_url));
const GURL window_url = GetUrl("/empty.html?prerender_window");
const std::string kOpenWindowAndNullifyScript = R"(
const win = window.open($1, '_blank');
win.opener = null;
)";
TestNavigationObserver nav_observer(window_url);
nav_observer.StartWatchingNewWebContents();
EXPECT_TRUE(ExecJs(web_contents(),
JsReplace(kOpenWindowAndNullifyScript, window_url)));
nav_observer.WaitForNavigationFinished();
ASSERT_EQ(GetRequestCount(prerendering_url), 1);
NavigatePrimaryPage(prerendering_url);
EXPECT_EQ(web_contents()->GetLastCommittedURL(), prerendering_url);
EXPECT_EQ(GetRequestCount(prerendering_url), 2);
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kActivatedWithAuxiliaryBrowsingContexts);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderIframe) {
TestHostPrerenderingState(GetUrl("/page_with_iframe.html"));
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderBlankIframe) {
TestHostPrerenderingState(GetUrl("/page_with_blank_iframe.html"));
}
using PrerenderBrowserDeathTest = PrerenderBrowserTest;
IN_PROC_BROWSER_TEST_F(PrerenderBrowserDeathTest,
PrerenderCannotHaveInnerContents) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/page_with_blank_iframe.html");
const GURL kInnerContentsUrl = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHostImpl* prerendered_render_frame_host =
GetPrerenderedMainFrameHost(host_id);
EXPECT_CHECK_DEATH({
CreateAndAttachInnerContents(
prerendered_render_frame_host->child_at(0)->current_frame_host());
});
}
class IsActivationObserver : public WebContentsObserver {
public:
IsActivationObserver(WebContents& web_contents, const GURL& url)
: WebContentsObserver(&web_contents), url_(url) {}
bool did_navigate() { return did_navigate_; }
bool was_activation() { return was_activation_; }
private:
void DidStartNavigation(NavigationHandle* handle) override {
if (handle->GetURL() != url_) {
return;
}
did_navigate_ = true;
was_activation_ = handle->IsPrerenderedPageActivation();
}
const GURL url_;
bool did_navigate_ = false;
bool was_activation_ = false;
};
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
NavigationRequestIsPrerenderedPageActivation) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
test::PrerenderHostObserver prerender_observer(*shell()->web_contents(),
kPrerenderingUrl);
{
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
AddPrerender(kPrerenderingUrl);
}
IsActivationObserver is_activation_observer(*shell()->web_contents(),
kPrerenderingUrl);
{
ASSERT_TRUE(ExecJs(web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", kPrerenderingUrl)));
prerender_observer.WaitForActivation();
}
ASSERT_TRUE(is_activation_observer.did_navigate());
EXPECT_TRUE(is_activation_observer.was_activation());
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ActivationDoesntRunThrottles) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
test::PrerenderHostObserver prerender_observer(*shell()->web_contents(),
kPrerenderingUrl);
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
NavigationThrottle* throttle = nullptr;
ShellContentBrowserClient::Get()
->set_create_throttles_for_navigation_callback(base::BindLambdaForTesting(
[&throttle](NavigationThrottleRegistry& registry) -> void {
auto throttle_ptr =
std::make_unique<TestNavigationThrottle>(registry);
CHECK(!throttle);
throttle = throttle_ptr.get();
throttle_ptr->SetResponse(
TestNavigationThrottle::WILL_START_REQUEST,
TestNavigationThrottle::SYNCHRONOUS, NavigationThrottle::DEFER);
registry.AddThrottle(std::move(throttle_ptr));
}));
{
TestNavigationManager prerender_manager(shell()->web_contents(),
kPrerenderingUrl);
AddPrerenderAsync(kPrerenderingUrl);
ASSERT_TRUE(prerender_manager.WaitForFirstYieldAfterDidStartNavigation());
ASSERT_NE(throttle, nullptr);
auto* request =
NavigationRequest::From(prerender_manager.GetNavigationHandle());
ASSERT_TRUE(request->IsDeferredForTesting());
auto* registry = request->GetNavigationThrottleRegistryForTesting();
ASSERT_EQ(1u, registry->GetDeferringThrottles().size());
EXPECT_TRUE(registry->GetDeferringThrottles().contains(throttle));
registry->ResumeProcessingNavigationEvent(throttle);
throttle = nullptr;
ASSERT_TRUE(prerender_manager.WaitForNavigationFinished());
FrameTreeNodeId host_id = GetHostForUrl(kPrerenderingUrl);
EXPECT_EQ(GetPrerenderedMainFrameHost(host_id)->GetLastCommittedURL(),
kPrerenderingUrl);
}
{
NavigatePrimaryPage(kPrerenderingUrl);
prerender_observer.WaitForActivation();
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
EXPECT_EQ(throttle, nullptr);
}
}
IN_PROC_BROWSER_TEST_P(PrerenderTargetAgnosticBrowserTest, SuppressOpenURL) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender1");
const GURL kSecondUrl = GetUrl("/empty.html?prerender2");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0);
FrameTreeNodeId host_id = prerender_helper()->AddPrerender(
kPrerenderingUrl, std::nullopt, GetTargetHint());
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
RenderFrameHost* prerendered_render_frame_host =
test::PrerenderTestHelper::GetPrerenderedMainFrameHost(
*prerender_web_contents, host_id);
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
OpenURLParams params(kSecondUrl, Referrer(),
prerendered_render_frame_host->GetFrameTreeNodeId(),
WindowOpenDisposition::NEW_WINDOW,
ui::PAGE_TRANSITION_LINK, true);
params.initiator_origin =
prerendered_render_frame_host->GetLastCommittedOrigin();
params.source_render_process_id =
prerendered_render_frame_host->GetProcess()->GetDeprecatedID();
params.source_render_frame_id = prerendered_render_frame_host->GetRoutingID();
auto* new_web_contents = prerender_web_contents->OpenURL(
params, {});
EXPECT_EQ(nullptr, new_web_contents);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ForEachRenderFrameHostImpl) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl =
GetUrl("/cross_site_iframe_factory.html?a.test(a.test(a.test),a.test)");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
RenderFrameHostImpl* initiator_render_frame_host = current_frame_host();
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHostImpl* prerendered_render_frame_host =
GetPrerenderedMainFrameHost(host_id);
RenderFrameHostImpl* rfh_sub_1 =
prerendered_render_frame_host->child_at(0)->current_frame_host();
RenderFrameHostImpl* rfh_sub_1_1 =
rfh_sub_1->child_at(0)->current_frame_host();
RenderFrameHostImpl* rfh_sub_2 =
prerendered_render_frame_host->child_at(1)->current_frame_host();
EXPECT_THAT(CollectAllRenderFrameHosts(prerendered_render_frame_host),
testing::ElementsAre(prerendered_render_frame_host, rfh_sub_1,
rfh_sub_2, rfh_sub_1_1));
EXPECT_THAT(CollectAllRenderFrameHosts(web_contents_impl()),
testing::UnorderedElementsAre(initiator_render_frame_host,
prerendered_render_frame_host,
rfh_sub_1, rfh_sub_2, rfh_sub_1_1));
EXPECT_EQ(nullptr, initiator_render_frame_host->GetParentOrOuterDocument());
EXPECT_EQ(nullptr, prerendered_render_frame_host->GetParentOrOuterDocument());
EXPECT_EQ(prerendered_render_frame_host,
rfh_sub_1->GetParentOrOuterDocument());
EXPECT_EQ(rfh_sub_1, rfh_sub_1_1->GetParentOrOuterDocument());
EXPECT_EQ(prerendered_render_frame_host,
rfh_sub_2->GetParentOrOuterDocument());
EXPECT_EQ(initiator_render_frame_host,
initiator_render_frame_host->GetOutermostMainFrame());
EXPECT_EQ(initiator_render_frame_host,
initiator_render_frame_host->GetOutermostMainFrameOrEmbedder());
EXPECT_EQ(prerendered_render_frame_host,
prerendered_render_frame_host->GetOutermostMainFrame());
EXPECT_EQ(prerendered_render_frame_host, rfh_sub_1->GetOutermostMainFrame());
EXPECT_EQ(prerendered_render_frame_host,
rfh_sub_1_1->GetOutermostMainFrame());
EXPECT_EQ(prerendered_render_frame_host, rfh_sub_2->GetOutermostMainFrame());
EXPECT_EQ(prerendered_render_frame_host,
prerendered_render_frame_host->GetOutermostMainFrameOrEmbedder());
EXPECT_EQ(prerendered_render_frame_host,
rfh_sub_1->GetOutermostMainFrameOrEmbedder());
EXPECT_EQ(prerendered_render_frame_host,
rfh_sub_1_1->GetOutermostMainFrameOrEmbedder());
EXPECT_EQ(prerendered_render_frame_host,
rfh_sub_2->GetOutermostMainFrameOrEmbedder());
bool visited_prerender_frame_tree = false;
web_contents_impl()->ForEachFrameTree([&](FrameTree& frame_tree) {
if (&frame_tree == prerendered_render_frame_host->frame_tree()) {
visited_prerender_frame_tree = true;
}
});
EXPECT_TRUE(visited_prerender_frame_tree);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, TabVisibleURL) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(shell()->web_contents()->GetVisibleURL(), kInitialUrl);
AddPrerender(kPrerenderingUrl);
EXPECT_EQ(shell()->web_contents()->GetVisibleURL(), kInitialUrl);
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ(shell()->web_contents()->GetVisibleURL(), kPrerenderingUrl);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CancelOnPreferredSizeChanged) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
test::PrerenderHostObserver host_observer(*web_contents_impl(), host_id);
RenderFrameHostImpl* prerender_main_frame =
GetPrerenderedMainFrameHost(host_id);
prerender_main_frame->GetRenderViewHost()->EnablePreferredSizeMode();
host_observer.WaitForDestroyed();
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kInactivePageRestriction);
histogram_tester().ExpectUniqueSample(
"Prerender.CanceledForInactivePageRestriction.DisallowActivationReason."
"SpeculationRule",
DisallowActivationReasonId::kContentsPreferredSizeChanged, 1);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, NoPopupWidget) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHostWrapper prerender_main_frame(
GetPrerenderedMainFrameHost(host_id));
std::string create_element_script = R"(
const widgetElement = document.createElement('input');
widgetElement.type = 'color';
widgetElement.id = 'chooser';
widgetElement.value = '#000000';
document.body.appendChild(widgetElement);
)";
EXPECT_TRUE(ExecJs(prerender_main_frame.get(), create_element_script,
EvalJsOptions::EXECUTE_SCRIPT_NO_USER_GESTURE));
std::string click_element_script = R"(
const element = document.getElementById('chooser');
element.click();
)";
EXPECT_TRUE(ExecJs(prerender_main_frame.get(), click_element_script));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl));
}
class TestPrerenderCancellerSubframeNavigationThrottle
: public NavigationThrottle {
public:
explicit TestPrerenderCancellerSubframeNavigationThrottle(
NavigationThrottleRegistry& registry)
: NavigationThrottle(registry),
navigation_request_(
NavigationRequest::From(®istry.GetNavigationHandle())) {}
ThrottleCheckResult WillStartRequest() override {
FrameTreeNode* frame_tree_node = navigation_request_->frame_tree_node();
if (frame_tree_node->frame_tree().is_prerendering() &&
!frame_tree_node->IsMainFrame()) {
PrerenderHostRegistry* prerender_host_registry =
frame_tree_node->current_frame_host()
->delegate()
->GetPrerenderHostRegistry();
prerender_host_registry->CancelHost(
frame_tree_node->frame_tree().root()->frame_tree_node_id(),
PrerenderFinalStatus::kMaxValue);
}
return PROCEED;
}
const char* GetNameForLogging() override {
return "TestPrerenderCancellerSubframeNavigationThrottle";
}
private:
raw_ptr<NavigationRequest> navigation_request_;
};
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
SubframeNavigationWhilePrerenderHostIsBeingDestroyed) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
const GURL kCrossOriginSubframeUrl =
GetCrossSiteUrl("/empty.html?cross_origin_iframe");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
test::PrerenderHostObserver observer(*web_contents_impl(), host_id);
ShellContentBrowserClient::Get()
->set_create_throttles_for_navigation_callback(base::BindLambdaForTesting(
[](NavigationThrottleRegistry& registry) -> void {
registry.AddThrottle(
std::make_unique<
TestPrerenderCancellerSubframeNavigationThrottle>(
registry));
}));
RenderFrameHost* prerender_frame_host = GetPrerenderedMainFrameHost(host_id);
EXPECT_TRUE(AddTestUtilJS(prerender_frame_host));
ExecuteScriptAsync(prerender_frame_host, JsReplace("add_iframe_async($1)",
kCrossOriginSubframeUrl));
observer.WaitForDestroyed();
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kMaxValue);
}
class MojoCapabilityControlTestContentBrowserClient
: public ContentBrowserTestContentBrowserClient,
public test::MojoCapabilityControlTestHelper {
public:
MojoCapabilityControlTestContentBrowserClient() = default;
~MojoCapabilityControlTestContentBrowserClient() override = default;
MojoCapabilityControlTestContentBrowserClient(
const MojoCapabilityControlTestContentBrowserClient&) = delete;
MojoCapabilityControlTestContentBrowserClient& operator=(
const MojoCapabilityControlTestContentBrowserClient&) = delete;
void RegisterBrowserInterfaceBindersForFrame(
RenderFrameHost* render_frame_host,
mojo::BinderMapWithContext<RenderFrameHost*>* map) override {
RegisterTestBrowserInterfaceBindersForFrame(render_frame_host, map);
}
void RegisterMojoBinderPoliciesForSameOriginPrerendering(
MojoBinderPolicyMap& policy_map) override {
RegisterTestMojoBinderPolicies(policy_map);
}
};
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, MojoCapabilityControl) {
MojoCapabilityControlTestContentBrowserClient test_browser_client;
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/page_with_iframe.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHost* prerendered_render_frame_host =
GetPrerenderedMainFrameHost(host_id);
std::vector<RenderFrameHost*> frames =
CollectAllRenderFrameHosts(prerendered_render_frame_host);
base::RunLoop run_loop;
auto barrier_closure =
base::BarrierClosure(frames.size(), run_loop.QuitClosure());
mojo::RemoteSet<mojom::TestInterfaceForDefer> defer_remote_set;
mojo::RemoteSet<mojom::TestInterfaceForGrant> grant_remote_set;
for (auto* frame : frames) {
auto* rfhi = static_cast<RenderFrameHostImpl*>(frame);
EXPECT_TRUE(rfhi->frame_tree()->is_prerendering());
EXPECT_EQ(rfhi->lifecycle_state(), LifecycleStateImpl::kPrerendering);
EXPECT_EQ(rfhi->GetLifecycleState(),
RenderFrameHost::LifecycleState::kPrerendering);
mojo::Receiver<blink::mojom::BrowserInterfaceBroker>& bib =
rfhi->browser_interface_broker_receiver_for_testing();
blink::mojom::BrowserInterfaceBroker* prerender_broker =
bib.internal_state()->impl();
mojo::Remote<mojom::TestInterfaceForDefer> prerender_defer_remote;
prerender_broker->GetInterface(
prerender_defer_remote.BindNewPipeAndPassReceiver());
prerender_defer_remote->Ping(barrier_closure);
defer_remote_set.Add(std::move(prerender_defer_remote));
mojo::Remote<mojom::TestInterfaceForGrant> prerender_grant_remote;
prerender_broker->GetInterface(
prerender_grant_remote.BindNewPipeAndPassReceiver());
grant_remote_set.Add(std::move(prerender_grant_remote));
}
EXPECT_EQ(test_browser_client.GetDeferReceiverSetSize(), 0U);
EXPECT_EQ(test_browser_client.GetGrantReceiverSetSize(), frames.size());
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
run_loop.Run();
EXPECT_EQ(test_browser_client.GetDeferReceiverSetSize(), frames.size());
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
MojoCapabilityControl_CancelMainFrame) {
MojoCapabilityControlTestContentBrowserClient test_browser_client;
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/page_with_iframe.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
auto* prerendered_render_frame_host = GetPrerenderedMainFrameHost(host_id);
mojo::Receiver<blink::mojom::BrowserInterfaceBroker>& bib =
prerendered_render_frame_host
->browser_interface_broker_receiver_for_testing();
blink::mojom::BrowserInterfaceBroker* prerender_broker =
bib.internal_state()->impl();
EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl));
mojo::Remote<mojom::TestInterfaceForCancel> remote;
prerender_broker->GetInterface(remote.BindNewPipeAndPassReceiver());
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kMojoBinderPolicy);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderCancelledInterface.SpeculationRule",
PrerenderCancelledInterface::kUnknown, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderCancelledUnknownInterface."
"SpeculationRule",
InterfaceNameHasher(mojom::TestInterfaceForCancel::Name_), 1);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
MojoCapabilityControl_CancelIframe) {
MojoCapabilityControlTestContentBrowserClient test_browser_client;
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/page_with_iframe.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
auto* main_render_frame_host = GetPrerenderedMainFrameHost(host_id);
ASSERT_GE(main_render_frame_host->child_count(), 1U);
RenderFrameHostImpl* child_render_frame_host =
main_render_frame_host->child_at(0U)->current_frame_host();
EXPECT_NE(main_render_frame_host->GetLastCommittedURL(),
child_render_frame_host->GetLastCommittedURL());
mojo::Receiver<blink::mojom::BrowserInterfaceBroker>& bib =
child_render_frame_host->browser_interface_broker_receiver_for_testing();
blink::mojom::BrowserInterfaceBroker* prerender_broker =
bib.internal_state()->impl();
EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl));
mojo::Remote<mojom::TestInterfaceForCancel> remote;
prerender_broker->GetInterface(remote.BindNewPipeAndPassReceiver());
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kMojoBinderPolicy);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderCancelledInterface.SpeculationRule",
PrerenderCancelledInterface::kUnknown, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderCancelledUnknownInterface."
"SpeculationRule",
InterfaceNameHasher(mojom::TestInterfaceForCancel::Name_), 1);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
MojoCapabilityControl_HandleUnexpected) {
MojoCapabilityControlTestContentBrowserClient test_browser_client;
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender1");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
std::string bad_message_error;
mojo::SetDefaultProcessErrorHandler(
base::BindLambdaForTesting([&](const std::string& error) {
EXPECT_FALSE(error.empty());
EXPECT_TRUE(bad_message_error.empty());
bad_message_error = error;
}));
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
auto* main_render_frame_host = GetPrerenderedMainFrameHost(host_id);
ASSERT_TRUE(
main_render_frame_host->ResetBrowserInterfaceBrokerReceiverForTesting());
mojo::Remote<blink::mojom::BrowserInterfaceBroker> remote_broker;
mojo::PendingReceiver<blink::mojom::BrowserInterfaceBroker> fake_receiver =
remote_broker.BindNewPipeAndPassReceiver();
main_render_frame_host->BindBrowserInterfaceBrokerReceiver(
std::move(fake_receiver));
EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl));
mojo::Remote<mojom::TestInterfaceForUnexpected> remote;
remote_broker->GetInterface(remote.BindNewPipeAndPassReceiver());
remote_broker.FlushForTesting();
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
EXPECT_EQ(bad_message_error,
"MBPA_BAD_INTERFACE: content.mojom.TestInterfaceForUnexpected");
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, MojoCapabilityControl_LoosenMode) {
MojoCapabilityControlTestContentBrowserClient test_browser_client;
IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
GURL initial_url = GetUrl("/empty.html");
GURL prerendering_url =
GetUrl("/cross_site_iframe_factory.html?a.test(a.test,a.test)");
GURL cross_origin_iframe_url = GetCrossSiteUrl("/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
FrameTreeNodeId host_id = AddPrerender(prerendering_url);
RenderFrameHostImpl* prerendered_render_frame_host =
GetPrerenderedMainFrameHost(host_id);
TestNavigationManager subframe_navigation_manager(web_contents(),
cross_origin_iframe_url);
std::string js = R"(
const frame = document.getElementById($1);
frame.contentWindow.location.href = $2;
)";
EXPECT_TRUE(ExecJs(prerendered_render_frame_host,
JsReplace(js, "child-0", cross_origin_iframe_url.spec())));
ASSERT_TRUE(
subframe_navigation_manager.WaitForFirstYieldAfterDidStartNavigation());
FrameTreeNode* child_ftn =
FrameTreeNode::GloballyFindByID(host_id)->child_at(0);
NavigationRequest* child_navigation = child_ftn->navigation_request();
ASSERT_NE(child_navigation, nullptr);
ASSERT_TRUE(child_navigation->IsDeferredForTesting());
std::vector<RenderFrameHostImpl*> all_prerender_frames;
size_t count_speculative = 0;
prerendered_render_frame_host->ForEachRenderFrameHostImplIncludingSpeculative(
[&](RenderFrameHostImpl* rfh) {
all_prerender_frames.push_back(rfh);
count_speculative +=
(rfh->lifecycle_state() == LifecycleStateImpl::kSpeculative);
});
if (base::FeatureList::IsEnabled(features::kDeferSpeculativeRFHCreation)) {
ASSERT_EQ(all_prerender_frames.size(), 3u);
ASSERT_EQ(count_speculative, 0u);
} else {
ASSERT_EQ(all_prerender_frames.size(), 4u);
ASSERT_EQ(count_speculative, 1u);
}
base::RunLoop run_loop;
auto barrier_closure =
base::BarrierClosure(all_prerender_frames.size(), run_loop.QuitClosure());
mojo::RemoteSet<mojom::TestInterfaceForDefer> defer_remote_set;
mojo::RemoteSet<mojom::TestInterfaceForGrant> grant_remote_set;
for (auto* rfhi : all_prerender_frames) {
mojo::Receiver<blink::mojom::BrowserInterfaceBroker>& bib =
rfhi->browser_interface_broker_receiver_for_testing();
blink::mojom::BrowserInterfaceBroker* prerender_broker =
bib.internal_state()->impl();
mojo::Remote<mojom::TestInterfaceForDefer> prerender_defer_remote;
prerender_broker->GetInterface(
prerender_defer_remote.BindNewPipeAndPassReceiver());
prerender_defer_remote->Ping(barrier_closure);
defer_remote_set.Add(std::move(prerender_defer_remote));
mojo::Remote<mojom::TestInterfaceForGrant> prerender_grant_remote;
prerender_broker->GetInterface(
prerender_grant_remote.BindNewPipeAndPassReceiver());
grant_remote_set.Add(std::move(prerender_grant_remote));
}
EXPECT_EQ(test_browser_client.GetDeferReceiverSetSize(), 0U);
EXPECT_EQ(test_browser_client.GetGrantReceiverSetSize(),
all_prerender_frames.size());
TestActivationManager prerendered_activation_navigation(web_contents(),
prerendering_url);
ASSERT_TRUE(ExecJs(web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", prerendering_url)));
prerendered_activation_navigation.WaitForNavigationFinished();
EXPECT_TRUE(prerendered_activation_navigation.was_activated());
run_loop.Run();
EXPECT_EQ(test_browser_client.GetDeferReceiverSetSize(),
all_prerender_frames.size());
for (auto* rfhi : all_prerender_frames) {
mojo::Receiver<blink::mojom::BrowserInterfaceBroker>& bib =
rfhi->browser_interface_broker_receiver_for_testing();
blink::mojom::BrowserInterfaceBroker* prerender_broker =
bib.internal_state()->impl();
mojo::Remote<mojom::TestInterfaceForCancel> remote;
prerender_broker->GetInterface(remote.BindNewPipeAndPassReceiver());
remote.FlushForTesting();
}
}
void PrerenderBrowserTest::TestCancelPrerendersWhenTimeout(
std::vector<Visibility> visibility_transitions) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderUrl1 = GetUrl("/empty.html?prerender1");
const GURL kPrerenderUrl2 = GetUrl("/empty.html?prerender2");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
AddPrerender(kPrerenderUrl1);
AddPrerender(kPrerenderUrl2);
test::PrerenderHostObserver prerender_observer(*web_contents_impl(),
kPrerenderUrl1);
PrerenderHostRegistry* registry =
web_contents_impl()->GetPrerenderHostRegistry();
ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
ASSERT_FALSE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
registry->SetTaskRunnerForTesting(task_runner);
for (Visibility visibility : visibility_transitions) {
switch (visibility) {
case Visibility::HIDDEN:
web_contents()->WasHidden();
ASSERT_TRUE(registry->GetEmbedderTimerForTesting()->IsRunning());
ASSERT_TRUE(
registry->GetSpeculationRulesTimerForTesting()->IsRunning());
break;
case Visibility::OCCLUDED:
web_contents()->WasOccluded();
ASSERT_TRUE(registry->GetEmbedderTimerForTesting()->IsRunning());
ASSERT_TRUE(
registry->GetSpeculationRulesTimerForTesting()->IsRunning());
break;
case Visibility::VISIBLE:
web_contents()->WasShown();
ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
ASSERT_FALSE(
registry->GetSpeculationRulesTimerForTesting()->IsRunning());
break;
}
}
ASSERT_TRUE(registry->GetEmbedderTimerForTesting()->IsRunning());
ASSERT_TRUE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
task_runner->FastForwardBy(
PrerenderHostRegistry::kTimeToLiveInBackgroundForSpeculationRules);
ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
ASSERT_FALSE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
prerender_observer.WaitForDestroyed();
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kTimeoutBackgrounded, 2);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
CancelPrerendersWhenTimeout_Hidden) {
TestCancelPrerendersWhenTimeout({Visibility::HIDDEN});
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
CancelPrerendersWhenTimeout_Occluded) {
TestCancelPrerendersWhenTimeout({Visibility::OCCLUDED});
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
CancelPrerendersWhenTimeout_OccludedHidden) {
TestCancelPrerendersWhenTimeout({Visibility::OCCLUDED, Visibility::HIDDEN});
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
CancelPrerendersWhenTimeout_OccludedVisibleHidden) {
TestCancelPrerendersWhenTimeout(
{Visibility::OCCLUDED, Visibility::VISIBLE, Visibility::HIDDEN});
}
void PrerenderBrowserTest::TestCancelOnlyEmbedderTriggeredPrerenderWhenTimeout(
std::vector<Visibility> visibility_transitions) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderUrl1 = GetUrl("/empty.html?prerender1");
const GURL kPrerenderUrl2 = GetUrl("/empty.html?prerender2");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
AddPrerender(kPrerenderUrl1);
test::PrerenderHostObserver host_observer(*web_contents_impl(),
kPrerenderUrl2);
std::unique_ptr<PrerenderHandle> prerender_handle =
AddEmbedderTriggeredPrerenderAsync(kPrerenderUrl2);
PrerenderHostRegistry* registry =
web_contents_impl()->GetPrerenderHostRegistry();
ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
ASSERT_FALSE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
registry->SetTaskRunnerForTesting(task_runner);
for (Visibility visibility : visibility_transitions) {
switch (visibility) {
case Visibility::HIDDEN:
web_contents()->WasHidden();
ASSERT_TRUE(registry->GetEmbedderTimerForTesting()->IsRunning());
ASSERT_TRUE(
registry->GetSpeculationRulesTimerForTesting()->IsRunning());
break;
case Visibility::OCCLUDED:
web_contents()->WasOccluded();
ASSERT_TRUE(registry->GetEmbedderTimerForTesting()->IsRunning());
ASSERT_TRUE(
registry->GetSpeculationRulesTimerForTesting()->IsRunning());
break;
case Visibility::VISIBLE:
web_contents()->WasShown();
ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
ASSERT_FALSE(
registry->GetSpeculationRulesTimerForTesting()->IsRunning());
break;
}
}
ASSERT_TRUE(registry->GetEmbedderTimerForTesting()->IsRunning());
ASSERT_TRUE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
ASSERT_GT(PrerenderHostRegistry::kTimeToLiveInBackgroundForSpeculationRules,
PrerenderHostRegistry::kTimeToLiveInBackgroundForEmbedder);
task_runner->FastForwardBy(
PrerenderHostRegistry::kTimeToLiveInBackgroundForEmbedder);
host_observer.WaitForDestroyed();
ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
ASSERT_TRUE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
EXPECT_TRUE(GetHostForUrl(kPrerenderUrl1));
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
"EmbedderSuffixForTest",
PrerenderFinalStatus::kTimeoutBackgrounded, 1);
histogram_tester().ExpectBucketCount(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kTimeoutBackgrounded, 0);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
CancelOnlyEmbedderTriggeredPrerenderWhenTimeout_Hidden) {
TestCancelOnlyEmbedderTriggeredPrerenderWhenTimeout({Visibility::HIDDEN});
}
IN_PROC_BROWSER_TEST_F(
PrerenderBrowserTest,
CancelOnlyEmbedderTriggeredPrerenderWhenTimeout_Occluded) {
TestCancelOnlyEmbedderTriggeredPrerenderWhenTimeout({Visibility::OCCLUDED});
}
IN_PROC_BROWSER_TEST_F(
PrerenderBrowserTest,
CancelOnlyEmbedderTriggeredPrerenderWhenTimeout_OccludedHidden) {
TestCancelOnlyEmbedderTriggeredPrerenderWhenTimeout(
{Visibility::OCCLUDED, Visibility::HIDDEN});
}
IN_PROC_BROWSER_TEST_F(
PrerenderBrowserTest,
CancelOnlyEmbedderTriggeredPrerenderWhenTimeout_OccludedVisibleHidden) {
TestCancelOnlyEmbedderTriggeredPrerenderWhenTimeout(
{Visibility::OCCLUDED, Visibility::VISIBLE, Visibility::HIDDEN});
}
void PrerenderBrowserTest::TestTimerResetWhenPageGoBackToForeground(
Visibility visibility) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderUrl = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
AddPrerender(kPrerenderUrl);
PrerenderHostRegistry* registry =
web_contents_impl()->GetPrerenderHostRegistry();
ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
ASSERT_FALSE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
switch (visibility) {
case Visibility::HIDDEN:
web_contents()->WasHidden();
break;
case Visibility::OCCLUDED:
web_contents()->WasOccluded();
break;
case Visibility::VISIBLE:
ASSERT_TRUE(false);
break;
}
ASSERT_TRUE(registry->GetEmbedderTimerForTesting()->IsRunning());
ASSERT_TRUE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
web_contents()->WasShown();
ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
ASSERT_FALSE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
test::PrerenderHostObserver prerender_observer(*web_contents(),
GetHostForUrl(kPrerenderUrl));
NavigatePrimaryPage(kPrerenderUrl);
prerender_observer.WaitForActivation();
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderUrl);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kActivated, 1);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
TimerResetWhenPageGoBackToForeground_Hidden) {
TestTimerResetWhenPageGoBackToForeground(Visibility::HIDDEN);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
TimerResetWhenPageGoBackToForeground_Occluded) {
TestTimerResetWhenPageGoBackToForeground(Visibility::OCCLUDED);
}
void PrerenderBrowserTest::TestCancelPrerenderWithTargetBlankWhenTimeout(
Visibility visibility) {
const GURL kInitialUrl = GetUrl("/simple_links.html");
const GURL kPrerenderUrl = GetUrl("/title2.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = prerender_helper()->AddPrerender(
kPrerenderUrl, std::nullopt, "_blank");
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
ASSERT_NE(prerender_web_contents, web_contents_impl());
ExpectWebContentsIsForNewTabPrerendering(*prerender_web_contents);
PrerenderHostRegistry* registry =
web_contents_impl()->GetPrerenderHostRegistry();
ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
ASSERT_FALSE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
registry->SetTaskRunnerForTesting(task_runner);
test::PrerenderHostObserver prerender_observer(*prerender_web_contents,
host_id);
switch (visibility) {
case Visibility::HIDDEN:
web_contents()->WasHidden();
break;
case Visibility::OCCLUDED:
web_contents()->WasOccluded();
break;
case Visibility::VISIBLE:
ASSERT_TRUE(false);
break;
}
ASSERT_TRUE(registry->GetEmbedderTimerForTesting()->IsRunning());
ASSERT_TRUE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
task_runner->FastForwardBy(
PrerenderHostRegistry::kTimeToLiveInBackgroundForSpeculationRules);
ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
ASSERT_FALSE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
prerender_observer.WaitForDestroyed();
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kTimeoutBackgrounded, 1);
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
}
IN_PROC_BROWSER_TEST_F(PrerenderTargetHintBrowserTest,
CancelPrerenderWithTargetBlankWhenTimeout_Hidden) {
TestCancelPrerenderWithTargetBlankWhenTimeout(Visibility::HIDDEN);
}
IN_PROC_BROWSER_TEST_F(PrerenderTargetHintBrowserTest,
CancelPrerenderWithTargetBlankWhenTimeout_Occluded) {
TestCancelPrerenderWithTargetBlankWhenTimeout(Visibility::OCCLUDED);
}
enum class SSLPrerenderTestErrorBlockType { kClientCertRequested, kCertError };
std::string SSLPrerenderTestErrorBlockTypeToString(
const testing::TestParamInfo<SSLPrerenderTestErrorBlockType>& info) {
switch (info.param) {
case SSLPrerenderTestErrorBlockType::kClientCertRequested:
return "ClientCertRequested";
case SSLPrerenderTestErrorBlockType::kCertError:
return "CertError";
}
}
class SSLPrerenderBrowserTest
: public testing::WithParamInterface<SSLPrerenderTestErrorBlockType>,
public PrerenderBrowserTest {
protected:
void RequireClientCertsOrSendExpiredCerts() {
net::SSLServerConfig ssl_config;
switch (GetParam()) {
case SSLPrerenderTestErrorBlockType::kClientCertRequested:
ssl_config.client_cert_type =
net::SSLServerConfig::ClientCertType::REQUIRE_CLIENT_CERT;
ResetSSLConfig(net::test_server::EmbeddedTestServer::CERT_TEST_NAMES,
ssl_config);
break;
case SSLPrerenderTestErrorBlockType::kCertError:
ResetSSLConfig(net::test_server::EmbeddedTestServer::CERT_EXPIRED,
ssl_config);
break;
}
}
PrerenderFinalStatus GetExpectedFinalStatus() {
switch (GetParam()) {
case SSLPrerenderTestErrorBlockType::kClientCertRequested:
return PrerenderFinalStatus::kClientCertRequested;
case SSLPrerenderTestErrorBlockType::kCertError:
return PrerenderFinalStatus::kSslCertificateError;
}
}
int GetExpectedNetError() {
switch (GetParam()) {
case SSLPrerenderTestErrorBlockType::kClientCertRequested:
return net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
case SSLPrerenderTestErrorBlockType::kCertError:
return net::ERR_CERT_COMMON_NAME_INVALID;
}
}
};
INSTANTIATE_TEST_SUITE_P(
All,
SSLPrerenderBrowserTest,
testing::Values(SSLPrerenderTestErrorBlockType::kClientCertRequested,
SSLPrerenderTestErrorBlockType::kCertError),
SSLPrerenderTestErrorBlockTypeToString);
IN_PROC_BROWSER_TEST_P(SSLPrerenderBrowserTest,
CertificateValidation_Navigation) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
RequireClientCertsOrSendExpiredCerts();
const GURL kPrerenderingUrl = GetUrl("/title1.html");
test::PrerenderHostObserver host_observer(*web_contents(), kPrerenderingUrl);
prerender_helper()->AddPrerenderAsync(kPrerenderingUrl);
host_observer.WaitForDestroyed();
EXPECT_TRUE(prerender_helper()->GetHostForUrl(kPrerenderingUrl).is_null());
ExpectFinalStatusForSpeculationRule(GetExpectedFinalStatus());
}
IN_PROC_BROWSER_TEST_P(SSLPrerenderBrowserTest,
CertificateValidation_Subresource) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kPrerenderingUrl = GetUrl("/title1.html");
FrameTreeNodeId host_id = prerender_helper()->AddPrerender(kPrerenderingUrl);
test::PrerenderHostObserver host_observer(*web_contents(), host_id);
RequireClientCertsOrSendExpiredCerts();
ASSERT_TRUE(prerender_helper()->GetHostForUrl(kPrerenderingUrl));
std::string fetch_subresource_script = R"(
const imgElement = document.createElement('img');
imgElement.src = '/load_image/image.png';
document.body.appendChild(imgElement);
)";
std::ignore = ExecJs(prerender_helper()->GetPrerenderedMainFrameHost(host_id),
fetch_subresource_script);
host_observer.WaitForDestroyed();
EXPECT_TRUE(prerender_helper()->GetHostForUrl(kPrerenderingUrl).is_null());
ExpectFinalStatusForSpeculationRule(GetExpectedFinalStatus());
}
IN_PROC_BROWSER_TEST_P(SSLPrerenderBrowserTest,
CertificateValidation_SWMainResource) {
const GURL kInitialUrl = GetUrl("/workers/service_worker_setup.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
EXPECT_EQ("ok", EvalJs(web_contents(), "setup();"));
RequireClientCertsOrSendExpiredCerts();
const GURL kPrerenderingUrl = GetUrl("/workers/simple.html?intercept");
test::PrerenderHostObserver host_observer(*web_contents(), kPrerenderingUrl);
prerender_helper()->AddPrerenderAsync(kPrerenderingUrl);
host_observer.WaitForDestroyed();
EXPECT_TRUE(prerender_helper()->GetHostForUrl(kPrerenderingUrl).is_null());
ExpectFinalStatusForSpeculationRule(
GetParam() == SSLPrerenderTestErrorBlockType::kClientCertRequested
? PrerenderFinalStatus::kClientCertRequested
: PrerenderFinalStatus::kNavigationRequestNetworkError);
}
IN_PROC_BROWSER_TEST_P(SSLPrerenderBrowserTest,
CertificateValidation_SWSubResource) {
if (GetParam() == SSLPrerenderTestErrorBlockType::kCertError) {
return;
}
const GURL kInitialUrl = GetUrl("/workers/service_worker_setup.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
EXPECT_EQ("ok", EvalJs(current_frame_host(), "setup();"));
const GURL kPrerenderingUrl = GetUrl("/workers/empty.html");
FrameTreeNodeId host_id = prerender_helper()->AddPrerender(kPrerenderingUrl);
test::PrerenderHostObserver host_observer(*web_contents(), host_id);
RequireClientCertsOrSendExpiredCerts();
std::string resource_url = GetUrl("/workers/empty.js?intercept").spec();
std::ignore = ExecJs(prerender_helper()->GetPrerenderedMainFrameHost(host_id),
JsReplace("fetch($1);", resource_url));
host_observer.WaitForDestroyed();
EXPECT_TRUE(prerender_helper()->GetHostForUrl(kPrerenderingUrl).is_null());
ExpectFinalStatusForSpeculationRule(GetExpectedFinalStatus());
}
IN_PROC_BROWSER_TEST_P(SSLPrerenderBrowserTest,
PrefetchCertificateValidation_NoServiceWorker) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
RequireClientCertsOrSendExpiredCerts();
test::TestPrefetchWatcher test_prefetch_watcher;
const GURL prefetch_url = GetUrl("/title1.html");
AddPrefetchAsync(prefetch_url);
test_prefetch_watcher.WaitUntilPrefetchResponseCompleted(
static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame())
->GetDocumentToken(),
prefetch_url);
histogram_tester().ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.NetError",
std::abs(GetExpectedNetError()), 1);
}
IN_PROC_BROWSER_TEST_P(SSLPrerenderBrowserTest,
PrefetchCertificateValidation_ServiceWorkerFallback) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
RegisterServiceWorker("/fetch_event_passthrough.js");
RequireClientCertsOrSendExpiredCerts();
test::TestPrefetchWatcher test_prefetch_watcher;
const GURL prefetch_url = GetUrl("/title1.html");
AddPrefetchAsync(prefetch_url);
test_prefetch_watcher.WaitUntilPrefetchResponseCompleted(
static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame())
->GetDocumentToken(),
prefetch_url);
histogram_tester().ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.NetError",
std::abs(GetExpectedNetError()), 1);
}
IN_PROC_BROWSER_TEST_P(SSLPrerenderBrowserTest,
PrefetchCertificateValidation_ServiceWorkerSubresource) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
RegisterServiceWorker("/fetch_event_respond_with_fetch.js");
RequireClientCertsOrSendExpiredCerts();
test::TestPrefetchWatcher test_prefetch_watcher;
const GURL prefetch_url = GetUrl("/title1.html");
AddPrefetchAsync(prefetch_url);
test_prefetch_watcher.WaitUntilPrefetchResponseCompleted(
static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame())
->GetDocumentToken(),
prefetch_url);
histogram_tester().ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.NetError", std::abs(net::ERR_FAILED),
1);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, FeatureRestriction_WindowOpen) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerendering");
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
auto* prerender_frame = GetPrerenderedMainFrameHost(host_id);
EXPECT_TRUE(AddTestUtilJS(prerender_frame));
const GURL kWindowOpenUrl = GetUrl("/empty.html?prerender");
EXPECT_EQ("FAILED", EvalJs(prerender_frame,
JsReplace("open_window($1)", kWindowOpenUrl)));
EXPECT_EQ(GetRequestCount(kWindowOpenUrl), 0);
EXPECT_EQ(GetHostForUrl(kPrerenderingUrl), host_id);
}
IN_PROC_BROWSER_TEST_P(PrerenderTargetAgnosticBrowserTest,
RenderFrameHostLifecycleState) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
EXPECT_EQ(current_frame_host()->lifecycle_state(),
LifecycleStateImpl::kActive);
FrameTreeNodeId host_id = prerender_helper()->AddPrerender(
kPrerenderingUrl, std::nullopt, GetTargetHint());
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
auto* rfh_a = static_cast<RenderFrameHostImpl*>(
test::PrerenderTestHelper::GetPrerenderedMainFrameHost(
*prerender_web_contents, host_id));
EXPECT_TRUE(AddTestUtilJS(rfh_a));
EXPECT_EQ("LOADED",
EvalJs(rfh_a, JsReplace("add_iframe($1)",
GetUrl("/empty.html?prerender"))));
RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host();
EXPECT_EQ(LifecycleStateImpl::kPrerendering, rfh_a->lifecycle_state());
EXPECT_EQ(LifecycleStateImpl::kPrerendering, rfh_b->lifecycle_state());
EXPECT_FALSE(rfh_a->IsInPrimaryMainFrame());
EXPECT_FALSE(rfh_b->IsInPrimaryMainFrame());
ActivatePrerenderedPage(*prerender_web_contents, kPrerenderingUrl);
EXPECT_EQ(LifecycleStateImpl::kActive, rfh_a->lifecycle_state());
EXPECT_EQ(LifecycleStateImpl::kActive, rfh_b->lifecycle_state());
EXPECT_TRUE(rfh_a->IsInPrimaryMainFrame());
EXPECT_FALSE(rfh_b->IsInPrimaryMainFrame());
histogram_tester().ExpectTotalCount(
"Navigation.TimeToActivatePrerender.SpeculationRule", 1u);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
SupportActivationWithOngoingMainFrameNavigation) {
net::test_server::ControllableHttpResponse main_document_response(
embedded_test_server(), "/main_document");
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
const GURL kPrerenderingUrl =
embedded_test_server()->GetURL("/main_document");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
{
test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl());
AddPrerenderAsync(kPrerenderingUrl);
registry_observer.WaitForTrigger(kPrerenderingUrl);
EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl));
}
FrameTreeNodeId host_id = GetHostForUrl(kPrerenderingUrl);
test::PrerenderHostObserver prerender_observer(*web_contents_impl(), host_id);
EXPECT_FALSE(prerender_observer.was_activated());
{
TestActivationManager primary_page_manager(shell()->web_contents(),
kPrerenderingUrl);
ASSERT_TRUE(ExecJs(shell()->web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", kPrerenderingUrl)));
NavigationRequest* request =
web_contents_impl()->GetPrimaryFrameTree().root()->navigation_request();
ASSERT_TRUE(primary_page_manager.WaitForBeforeChecks());
primary_page_manager.ResumeActivation();
EXPECT_TRUE(request->IsCommitDeferringConditionDeferredForTesting());
EXPECT_EQ(request->state(), NavigationRequest::NOT_STARTED);
main_document_response.WaitForRequest();
main_document_response.Send(net::HTTP_OK, "main_document");
main_document_response.Done();
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), kInitialUrl);
EXPECT_FALSE(prerender_observer.was_activated());
primary_page_manager.WaitForNavigationFinished();
prerender_observer.WaitForActivation();
}
{
EXPECT_TRUE(prerender_observer.was_activated());
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
}
histogram_tester().ExpectTotalCount(
"Navigation.Prerender.ActivationCommitDeferTime.SpeculationRule", 1u);
}
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_DeferPrivateOriginFileSystem DISABLED_DeferPrivateOriginFileSystem
#else
#define MAYBE_DeferPrivateOriginFileSystem DeferPrivateOriginFileSystem
#endif
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
MAYBE_DeferPrivateOriginFileSystem) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl =
GetUrl("/prerender/restriction_file_system.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id);
EXPECT_EQ(
true,
ExecJs(prerender_render_frame_host, "accessOriginPrivateFileSystem();",
EvalJsOptions::EXECUTE_SCRIPT_NO_USER_GESTURE |
EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
EXPECT_TRUE(ExecJs(prerender_render_frame_host, "runLoop();"));
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ(true, EvalJs(prerender_render_frame_host, "result;"));
base::Value::List results_list =
EvalJs(prerender_render_frame_host, "eventsSeen").TakeValue().TakeList();
std::vector<std::string> eventsSeen;
for (auto& result : results_list) {
eventsSeen.push_back(result.GetString());
}
EXPECT_THAT(eventsSeen,
testing::ElementsAreArray(
{"accessOriginPrivateFileSystem (prerendering: true)",
"prerenderingchange (prerendering: false)",
"getDirectory (prerendering: false)"}));
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, DocumentUserData) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id);
DocumentData::CreateForCurrentDocument(prerender_render_frame_host);
base::WeakPtr<DocumentData> data =
DocumentData::GetForCurrentDocument(prerender_render_frame_host)
->GetWeakPtr();
EXPECT_TRUE(data);
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
base::WeakPtr<DocumentData> data_after_activation =
DocumentData::GetForCurrentDocument(current_frame_host())->GetWeakPtr();
EXPECT_TRUE(data_after_activation);
EXPECT_EQ(data_after_activation.get(), data.get());
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, GamepadMonitorCancelPrerendering) {
const GURL initial_url = GetUrl("/empty.html");
const GURL prerender_url = GetUrl("/empty.html?prerender");
GURL url_ping(GetUrl(kPagehideEventPath));
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
FrameTreeNodeId host_id = AddPrerender(prerender_url);
auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id);
std::string js = R"(
addEventListener('pagehide', () => {
fetchLater($1);
});)";
EXPECT_TRUE(ExecJs(prerender_render_frame_host, JsReplace(js, url_ping)));
EXPECT_FALSE(PageHideReceived());
std::ignore = EvalJs(prerender_render_frame_host, "navigator.getGamepads()",
EvalJsOptions::EXECUTE_SCRIPT_NO_USER_GESTURE);
EXPECT_FALSE(HasHostForUrl(prerender_url));
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kMojoBinderPolicy);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderCancelledInterface.SpeculationRule",
PrerenderCancelledInterface::kGamepadMonitor, 1);
EXPECT_FALSE(PageHideReceived());
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, GamepadMonitorAfterNavigation) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/prerender/restriction-gamepad.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0);
AddPrerender(kPrerenderingUrl);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1);
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
EXPECT_EQ(true, EvalJs(shell()->web_contents(), "prerenderingChanged"));
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ClipboardByExecCommandFail) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id);
EXPECT_EQ(false,
EvalJs(prerender_render_frame_host, "document.execCommand('copy');",
EvalJsOptions::EXECUTE_SCRIPT_NO_USER_GESTURE));
EXPECT_EQ(false, EvalJs(prerender_render_frame_host,
"document.execCommand('paste');",
EvalJsOptions::EXECUTE_SCRIPT_NO_USER_GESTURE));
}
void LoadAndWaitForPrerenderDestroyed(test::PrerenderTestHelper* helper,
const GURL prerendering_url,
const std::string& target_hint) {
test::PrerenderHostCreationWaiter host_creation_waiter;
helper->AddPrerendersAsync({prerendering_url}, std::nullopt,
target_hint);
FrameTreeNodeId host_id = host_creation_waiter.Wait();
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
test::PrerenderHostObserver host_observer(*prerender_web_contents, host_id);
host_observer.WaitForDestroyed();
if (target_hint == "_blank") {
EXPECT_FALSE(helper->HasNewTabHandle(host_id));
} else {
EXPECT_TRUE(helper->GetHostForUrl(*prerender_web_contents, prerendering_url)
.is_null());
}
}
#if BUILDFLAG(IS_ANDROID)
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, NotificationConstructorAndroid) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id);
EXPECT_EQ(false, EvalJs(prerender_render_frame_host, R"(
(() => {
try { new Notification('My Notification'); return true;
} catch(e) { return false; }
})();
)"));
}
#endif
IN_PROC_BROWSER_TEST_P(PrerenderTargetAgnosticBrowserTest, DownloadByScript) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerendering");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
test::PrerenderHostCreationWaiter host_creation_waiter;
prerender_helper()->AddPrerendersAsync(
{kPrerenderingUrl}, std::nullopt, GetTargetHint());
FrameTreeNodeId host_id = host_creation_waiter.Wait();
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
test::PrerenderTestHelper::WaitForPrerenderLoadCompletion(
*prerender_web_contents, kPrerenderingUrl);
test::PrerenderHostObserver host_observer(*prerender_web_contents, host_id);
auto* prerender_host = test::PrerenderTestHelper::GetPrerenderedMainFrameHost(
*prerender_web_contents, host_id);
const std::string js_string = R"(
document.body.innerHTML =
"<a id='target' download='download-link' href='cache.txt'>here</a>";
document.getElementById('target').click();
)";
ExecuteScriptAsync(prerender_host, js_string);
host_observer.WaitForDestroyed();
if (GetTargetHint() == "_blank") {
EXPECT_FALSE(prerender_helper()->HasNewTabHandle(host_id));
} else {
EXPECT_TRUE(prerender_helper()
->GetHostForUrl(*prerender_web_contents, kPrerenderingUrl)
.is_null());
}
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kDownload);
}
IN_PROC_BROWSER_TEST_P(PrerenderTargetAgnosticBrowserTest,
DownloadInMainFrame) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kDownloadUrl =
GetUrl("/set-header?Content-Disposition: attachment");
LoadAndWaitForPrerenderDestroyed(prerender_helper(), kDownloadUrl,
GetTargetHint());
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kDownload);
}
IN_PROC_BROWSER_TEST_P(PrerenderTargetAgnosticBrowserTest, DownloadInSubframe) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerendering");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
test::PrerenderHostCreationWaiter host_creation_waiter;
prerender_helper()->AddPrerendersAsync(
{kPrerenderingUrl}, std::nullopt, GetTargetHint());
FrameTreeNodeId host_id = host_creation_waiter.Wait();
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
test::PrerenderTestHelper::WaitForPrerenderLoadCompletion(
*prerender_web_contents, kPrerenderingUrl);
test::PrerenderHostObserver host_observer(*prerender_web_contents, host_id);
auto* prerender_host = test::PrerenderTestHelper::GetPrerenderedMainFrameHost(
*prerender_web_contents, host_id);
EXPECT_TRUE(AddTestUtilJS(prerender_host));
const GURL kDownloadUrl =
GetUrl("/set-header?Content-Disposition: attachment");
ExecuteScriptAsync(prerender_host,
JsReplace("add_iframe_async($1)", kDownloadUrl));
host_observer.WaitForDestroyed();
if (GetTargetHint() == "_blank") {
EXPECT_FALSE(prerender_helper()->HasNewTabHandle(host_id));
} else {
EXPECT_TRUE(prerender_helper()
->GetHostForUrl(*prerender_web_contents, kPrerenderingUrl)
.is_null());
}
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kDownload);
}
#if BUILDFLAG(IS_ANDROID)
namespace {
class TestViewportWebContentsObserver : public WebContentsObserver {
public:
TestViewportWebContentsObserver(WebContents* web_contents,
blink::mojom::ViewportFit wanted_value)
: WebContentsObserver(web_contents), wanted_value_(wanted_value) {}
TestViewportWebContentsObserver(const TestViewportWebContentsObserver&) =
delete;
TestViewportWebContentsObserver& operator=(
const TestViewportWebContentsObserver&) = delete;
void ViewportFitChanged(blink::mojom::ViewportFit value) override {
value_ = value;
if (waiting_for_wanted_value_ && value == wanted_value_) {
std::move(waiting_for_wanted_value_).Run();
}
}
void WaitForWantedValue() {
if (value_.has_value() && value_.value() == wanted_value_) {
return;
}
base::RunLoop loop;
waiting_for_wanted_value_ = loop.QuitClosure();
loop.Run();
}
private:
base::OnceClosure waiting_for_wanted_value_;
std::optional<blink::mojom::ViewportFit> value_;
const blink::mojom::ViewportFit wanted_value_;
};
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ViewportFit) {
const GURL kInitialUrl = GetUrl("/prerender/viewport.html");
const GURL kPrerenderingUrl = GetUrl("/prerender/viewport.html?prerendering");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
test::PrerenderHostObserver host_observer(*web_contents(), host_id);
RenderFrameHostImpl* prerender_rfh = GetPrerenderedMainFrameHost(host_id);
RenderFrameHostImpl* primary_rfh = web_contents_impl()->GetPrimaryMainFrame();
{
TestViewportWebContentsObserver observer(web_contents_impl(),
blink::mojom::ViewportFit::kCover);
EXPECT_TRUE(ExecJs(prerender_rfh, "setViewportFit('contain')"));
EXPECT_TRUE(ExecJs(primary_rfh, "setViewportFit('cover')"));
web_contents_impl()->FullscreenStateChanged(
primary_rfh, true, blink::mojom::FullscreenOptions::New());
observer.WaitForWantedValue();
}
{
TestViewportWebContentsObserver observer(
web_contents_impl(), blink::mojom::ViewportFit::kContain);
prerender_helper()->NavigatePrimaryPage(kPrerenderingUrl);
web_contents_impl()->FullscreenStateChanged(
prerender_rfh, true, blink::mojom::FullscreenOptions::New());
observer.WaitForWantedValue();
}
EXPECT_TRUE(host_observer.was_activated());
}
#endif
class PrerenderLowMemoryBrowserTest : public PrerenderBrowserTest {
public:
PrerenderLowMemoryBrowserTest() {
std::string memory_threshold = base::NumberToString(
base::SysInfo::AmountOfPhysicalMemory().InMiB() + 1);
feature_list_.InitWithFeaturesAndParameters(
{{blink::features::kPrerender2MemoryControls,
{{blink::features::kPrerender2MemoryThresholdParamName,
memory_threshold}}}},
{});
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(PrerenderLowMemoryBrowserTest, NoPrerender) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
test::PrerenderHostRegistryObserver observer(*web_contents_impl());
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
AddPrerenderAsync(kPrerenderingUrl);
observer.WaitForTrigger(kPrerenderingUrl);
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kLowEndDevice);
NavigatePrimaryPage(kPrerenderingUrl);
ExpectPreloadingAttemptUkm({attempt_ukm_entry_builder().BuildEntry(
PrimaryPageSourceId(), PreloadingType::kPrerender,
PreloadingEligibility::kLowMemory, PreloadingHoldbackStatus::kUnspecified,
PreloadingTriggeringOutcome::kUnspecified,
PreloadingFailureReason::kUnspecified,
true,
std::nullopt,
blink::mojom::SpeculationEagerness::kImmediate)});
}
namespace {
class SequentialPrerenderObserver : public WebContentsObserver {
public:
enum class EventType {
kStart,
kFinish,
};
SequentialPrerenderObserver(WebContents& web_contents, const GURL& target_url)
: WebContentsObserver(&web_contents), target_url_(target_url) {}
const std::vector<std::pair<GURL, EventType>>& events_sequence() const {
return events_sequence_;
}
void WaitForTargetNavigationFinished() {
if (target_navigation_finished_) {
return;
}
base::RunLoop loop;
quit_closure_ = loop.QuitClosure();
loop.Run();
}
private:
void DidStartNavigation(NavigationHandle* handle) override {
events_sequence_.emplace_back(handle->GetURL(), EventType::kStart);
}
void DidFinishNavigation(NavigationHandle* handle) override {
events_sequence_.emplace_back(handle->GetURL(), EventType::kFinish);
if (handle->GetURL() != target_url_) {
return;
}
target_navigation_finished_ = true;
if (quit_closure_) {
std::move(quit_closure_).Run();
}
}
const GURL target_url_;
base::OnceClosure quit_closure_;
bool target_navigation_finished_ = false;
std::vector<std::pair<GURL, EventType>> events_sequence_;
};
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, SequentialPrerendering) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
std::vector<GURL> prerender_urls;
for (int i = 0; i < 3; i++) {
prerender_urls.push_back(
GetUrl("/empty.html?prerender" + base::NumberToString(i)));
}
SequentialPrerenderObserver observer(*web_contents(), prerender_urls[2]);
AddPrerendersAsync(prerender_urls);
observer.WaitForTargetNavigationFinished();
std::vector<std::pair<GURL, SequentialPrerenderObserver::EventType>>
expected_sequence = {
{prerender_urls[0], SequentialPrerenderObserver::EventType::kStart},
{prerender_urls[1], SequentialPrerenderObserver::EventType::kStart},
{prerender_urls[0], SequentialPrerenderObserver::EventType::kFinish},
{prerender_urls[2], SequentialPrerenderObserver::EventType::kStart},
{prerender_urls[1], SequentialPrerenderObserver::EventType::kFinish},
{prerender_urls[2], SequentialPrerenderObserver::EventType::kFinish},
};
EXPECT_EQ(observer.events_sequence(), expected_sequence);
std::vector<std::unique_ptr<test::PrerenderHostObserver>> prerender_observers;
for (int i = 0; i < 3; i++) {
prerender_observers.push_back(std::make_unique<test::PrerenderHostObserver>(
*web_contents(), GetHostForUrl(prerender_urls[i])));
}
NavigatePrimaryPage(prerender_urls[1]);
prerender_observers[0]->WaitForDestroyed();
prerender_observers[1]->WaitForActivation();
prerender_observers[2]->WaitForDestroyed();
EXPECT_TRUE(prerender_observers[1]->was_activated());
EXPECT_FALSE(HasHostForUrl(prerender_urls[1]));
EXPECT_EQ(web_contents()->GetLastCommittedURL(), prerender_urls[1]);
histogram_tester().ExpectBucketCount(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kActivated, 1);
histogram_tester().ExpectBucketCount(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kOtherPrerenderedPageActivated, 2);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
SkipCancelledPrerenderAndStartNextPrerender) {
net::test_server::ControllableHttpResponse response1(
embedded_test_server(), "/empty.html?prerender1");
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
const GURL kPrerender1 =
embedded_test_server()->GetURL("/empty.html?prerender1");
const GURL kPrerender2 =
embedded_test_server()->GetURL("/empty.html?prerender2");
const GURL kPrerender3 =
embedded_test_server()->GetURL("/empty.html?prerender3");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl());
AddPrerendersAsync({kPrerender1, kPrerender2, kPrerender3});
registry_observer.WaitForTrigger(kPrerender3);
test::PrerenderHostObserver prerender3_observer(*web_contents(),
GetHostForUrl(kPrerender3));
response1.WaitForRequest();
web_contents_impl()->GetPrerenderHostRegistry()->CancelHost(
GetHostForUrl(kPrerender2), PrerenderFinalStatus::kDestroyed);
response1.Send(net::HTTP_OK, "");
response1.Done();
WaitForPrerenderLoadCompletion(kPrerender3);
NavigatePrimaryPage(kPrerender3);
prerender3_observer.WaitForActivation();
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerender3);
EXPECT_TRUE(prerender3_observer.was_activated());
histogram_tester().ExpectBucketCount(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kOtherPrerenderedPageActivated, 1);
histogram_tester().ExpectBucketCount(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kDestroyed, 1);
histogram_tester().ExpectBucketCount(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kActivated, 1);
}
IN_PROC_BROWSER_TEST_F(
PrerenderBrowserTest,
IframeNavigationFinishDontDisruptPrerenderNavigationFinish) {
net::test_server::ControllableHttpResponse response2(
embedded_test_server(), "/empty.html?prerender2");
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
const GURL kPrerender1 =
embedded_test_server()->GetURL("/empty.html?prerender1");
const GURL kPrerender2 =
embedded_test_server()->GetURL("/empty.html?prerender2");
const GURL kPrerender3 =
embedded_test_server()->GetURL("/empty.html?prerender3");
const GURL kIframeUrl = embedded_test_server()->GetURL("/empty.html?iframe");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
AddPrerendersAsync({kPrerender1, kPrerender2, kPrerender3});
response2.WaitForRequest();
WaitForPrerenderLoadCompletion(kPrerender1);
FrameTreeNodeId host_id = GetHostForUrl(kPrerender1);
ASSERT_TRUE(host_id);
RenderFrameHost* prerender_frame_host = GetPrerenderedMainFrameHost(host_id);
EXPECT_TRUE(AddTestUtilJS(prerender_frame_host));
EXPECT_EQ("LOADED", EvalJs(prerender_frame_host,
JsReplace("add_iframe($1)", kIframeUrl)));
RenderFrameHost* child_frame_host = ChildFrameAt(prerender_frame_host, 0);
ASSERT_NE(child_frame_host, nullptr);
ASSERT_EQ(child_frame_host->GetLastCommittedURL(), kIframeUrl);
PrerenderHost* prerender3_host =
web_contents_impl()->GetPrerenderHostRegistry()->FindHostByUrlForTesting(
kPrerender3);
EXPECT_FALSE(prerender3_host->GetInitialNavigationId().has_value());
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ActivateBeforePrerenderStarts) {
net::test_server::ControllableHttpResponse response(embedded_test_server(),
"/empty.html?prerender1");
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
const GURL kPrerender1 =
embedded_test_server()->GetURL("/empty.html?prerender1");
const GURL kPrerender2 =
embedded_test_server()->GetURL("/empty.html?prerender2");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl());
AddPrerendersAsync({kPrerender1, kPrerender2});
registry_observer.WaitForTrigger(kPrerender2);
test::PrerenderHostObserver prerender2_observer(*web_contents(),
GetHostForUrl(kPrerender2));
response.WaitForRequest();
NavigatePrimaryPage(kPrerender2);
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerender2);
EXPECT_FALSE(prerender2_observer.was_activated());
histogram_tester().ExpectBucketCount(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kTriggerDestroyed, 1);
histogram_tester().ExpectBucketCount(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kActivatedBeforeStarted, 1);
ukm::SourceId ukm_source_id = PrimaryPageSourceId();
ExpectPreloadingAttemptUkm({
attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kRunning,
PreloadingFailureReason::kUnspecified,
false,
std::nullopt,
blink::mojom::SpeculationEagerness::kImmediate),
attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kTriggeredButPending,
PreloadingFailureReason::kUnspecified,
true,
std::nullopt,
blink::mojom::SpeculationEagerness::kImmediate),
});
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ExceedTheRequestNumberLimit) {
net::test_server::ControllableHttpResponse response(embedded_test_server(),
"/empty.html?prerender1");
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
std::vector<GURL> prerender_urls;
for (int i = 0;
i <
PrerenderHostRegistry::kMaxRunningSpeculationRulesImmediatePrerenders +
1;
i++) {
prerender_urls.push_back(embedded_test_server()->GetURL(
"/empty.html?prerender" + base::NumberToString(i)));
}
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl());
AddPrerendersAsync(prerender_urls);
response.WaitForRequest();
registry_observer.WaitForTrigger(prerender_urls.back());
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kMaxNumOfRunningImmediatePrerendersExceeded, 1);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
EmbedderPrerenderHandledImmediately) {
net::test_server::ControllableHttpResponse prerender1_response(
embedded_test_server(), "/empty.html?prerender1");
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
const GURL kPrerender1 =
embedded_test_server()->GetURL("/empty.html?prerender1");
const GURL kPrerender2 =
embedded_test_server()->GetURL("/empty.html?prerender2");
const GURL kEmbedderPrerender =
embedded_test_server()->GetURL("/empty.html?embedder");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
AddPrerendersAsync({kPrerender1, kPrerender2});
prerender1_response.WaitForRequest();
std::unique_ptr<PrerenderHandle> prerender_handle =
AddEmbedderTriggeredPrerender(kEmbedderPrerender);
EXPECT_TRUE(prerender_handle);
PrerenderHost* prerender2_host =
web_contents_impl()->GetPrerenderHostRegistry()->FindHostByUrlForTesting(
kPrerender2);
EXPECT_FALSE(prerender2_host->GetInitialNavigationId());
prerender1_response.Send(net::HTTP_OK, "");
prerender1_response.Done();
WaitForPrerenderLoadCompletion(kPrerender2);
EXPECT_TRUE(HasHostForUrl(kPrerender2));
test::PrerenderHostObserver embedder_observer(
*web_contents(), GetHostForUrl(kEmbedderPrerender));
prerender_helper()->NavigatePrimaryPageAsync(
kEmbedderPrerender,
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR));
embedder_observer.WaitForActivation();
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kEmbedderPrerender);
EXPECT_TRUE(embedder_observer.was_activated());
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
RunningHostCancellationStartPendingPrerender) {
net::test_server::ControllableHttpResponse response(embedded_test_server(),
"/empty.html?prerender1");
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
const GURL kPrerender1 =
embedded_test_server()->GetURL("/empty.html?prerender1");
const GURL kPrerender2 =
embedded_test_server()->GetURL("/empty.html?prerender2");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl());
AddPrerendersAsync({kPrerender1, kPrerender2});
registry_observer.WaitForTrigger(kPrerender2);
test::PrerenderHostObserver prerender2_observer(*web_contents(),
GetHostForUrl(kPrerender2));
response.WaitForRequest();
web_contents_impl()->GetPrerenderHostRegistry()->CancelHost(
GetHostForUrl(kPrerender1), PrerenderFinalStatus::kDestroyed);
WaitForPrerenderLoadCompletion(kPrerender2);
NavigatePrimaryPage(kPrerender2);
prerender2_observer.WaitForActivation();
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerender2);
EXPECT_TRUE(prerender2_observer.was_activated());
histogram_tester().ExpectBucketCount(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kDestroyed, 1);
histogram_tester().ExpectBucketCount(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kActivated, 1);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
SpeculationRulesUpdateStartPendingPrerender) {
net::test_server::ControllableHttpResponse response(embedded_test_server(),
"/empty.html?prerender1");
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
const GURL kPrerender1 =
embedded_test_server()->GetURL("/empty.html?prerender1");
const GURL kPrerender2 =
embedded_test_server()->GetURL("/empty.html?prerender2");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl());
std::string script = R"(
let sc = document.createElement('script');
sc.type = 'speculationrules';
sc.id = $1;
sc.textContent = JSON.stringify({
prerender: [
{source: "list", urls: [$2]}
]
});
document.head.appendChild(sc);
)";
ASSERT_TRUE(ExecJs(web_contents_impl()->GetPrimaryMainFrame(),
JsReplace(script, "prerender1", kPrerender1)));
ASSERT_TRUE(ExecJs(web_contents_impl()->GetPrimaryMainFrame(),
JsReplace(script, "prerender2", kPrerender2)));
registry_observer.WaitForTrigger(kPrerender2);
test::PrerenderHostObserver prerender2_observer(*web_contents(),
GetHostForUrl(kPrerender2));
response.WaitForRequest();
ASSERT_TRUE(ExecJs(web_contents_impl()->GetPrimaryMainFrame(),
"document.querySelector('#prerender1').remove()"));
WaitForPrerenderLoadCompletion(kPrerender2);
NavigatePrimaryPage(kPrerender2);
prerender2_observer.WaitForActivation();
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerender2);
EXPECT_TRUE(prerender2_observer.was_activated());
histogram_tester().ExpectBucketCount(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kSpeculationRuleRemoved, 1);
histogram_tester().ExpectBucketCount(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kActivated, 1);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
PreloadingTriggeringOutcomeForPendingPrerender) {
net::test_server::ControllableHttpResponse response1(
embedded_test_server(), "/empty.html?prerender1");
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
const GURL kPrerender1 =
embedded_test_server()->GetURL("/empty.html?prerender1");
const GURL kPrerender2 =
embedded_test_server()->GetURL("/empty.html?prerender2");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl());
AddPrerendersAsync({kPrerender1, kPrerender2});
registry_observer.WaitForTrigger(kPrerender2);
response1.WaitForRequest();
PrerenderHost* prerender2_host =
web_contents_impl()->GetPrerenderHostRegistry()->FindHostByUrlForTesting(
kPrerender2);
auto* preloading_attempt_impl = static_cast<PreloadingAttemptImpl*>(
prerender2_host->preloading_attempt().get());
EXPECT_EQ(test::PreloadingAttemptAccessor(preloading_attempt_impl)
.GetTriggeringOutcome(),
PreloadingTriggeringOutcome::kTriggeredButPending);
NavigationHandleObserver activation_observer(web_contents(), kPrerender1);
test::PrerenderHostObserver prerender1_observer(*web_contents(),
GetHostForUrl(kPrerender1));
TestActivationManager primary_page_manager(shell()->web_contents(),
kPrerender1);
ASSERT_TRUE(ExecJs(shell()->web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", kPrerender1)));
NavigationRequest* request =
web_contents_impl()->GetPrimaryFrameTree().root()->navigation_request();
ASSERT_TRUE(primary_page_manager.WaitForBeforeChecks());
primary_page_manager.ResumeActivation();
EXPECT_TRUE(request->IsCommitDeferringConditionDeferredForTesting());
response1.Send(net::HTTP_OK, "");
response1.Done();
primary_page_manager.WaitForNavigationFinished();
prerender1_observer.WaitForActivation();
ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id();
ExpectPreloadingAttemptUkm({
attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kSuccess,
PreloadingFailureReason::kUnspecified,
true,
kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate),
attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kTriggeredButPending,
PreloadingFailureReason::kUnspecified,
false,
std::nullopt,
blink::mojom::SpeculationEagerness::kImmediate),
});
}
IN_PROC_BROWSER_TEST_F(
PrerenderBrowserTest,
PreloadingTriggeringOutcomeForStartingPrerenderBeforeDestruction) {
net::test_server::ControllableHttpResponse response2(
embedded_test_server(), "/empty.html?prerender2");
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
const GURL kPrerender1 =
embedded_test_server()->GetURL("/empty.html?prerender1");
const GURL kPrerender2 =
embedded_test_server()->GetURL("/empty.html?prerender2");
const GURL kPrerender3 =
embedded_test_server()->GetURL("/empty.html?prerender3");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl());
AddPrerendersAsync({kPrerender1, kPrerender2, kPrerender3});
registry_observer.WaitForTrigger(kPrerender3);
test::PrerenderHostObserver prerender1_observer(*web_contents(),
GetHostForUrl(kPrerender1));
response2.WaitForRequest();
NavigationHandleObserver activation_observer(web_contents(), kPrerender1);
NavigatePrimaryPage(kPrerender1);
prerender1_observer.WaitForActivation();
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerender1);
EXPECT_TRUE(prerender1_observer.was_activated());
ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id();
ExpectPreloadingAttemptUkm({
attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kSuccess,
PreloadingFailureReason::kUnspecified,
true,
kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate),
attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kRunning,
PreloadingFailureReason::kUnspecified,
false,
std::nullopt,
blink::mojom::SpeculationEagerness::kImmediate),
attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kTriggeredButPending,
PreloadingFailureReason::kUnspecified,
false,
std::nullopt,
blink::mojom::SpeculationEagerness::kImmediate),
});
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
CancelAllPrerenderUponActivationRequestArrival) {
net::test_server::ControllableHttpResponse response3(
embedded_test_server(), "/empty.html?prerender3");
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
ASSERT_GE(
PrerenderHostRegistry::kMaxRunningSpeculationRulesImmediatePrerenders, 4);
std::vector<GURL> prerender_urls;
for (int i = 1; i <= 4; i++) {
prerender_urls.push_back(embedded_test_server()->GetURL(
"/empty.html?prerender" + base::NumberToString(i)));
}
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl());
AddPrerendersAsync(prerender_urls);
registry_observer.WaitForTrigger(prerender_urls[3]);
response3.WaitForRequest();
NavigationHandleObserver activation_observer(web_contents(),
prerender_urls[0]);
test::PrerenderHostObserver prerender1_observer(
*web_contents(), GetHostForUrl(prerender_urls[0]));
TestActivationManager primary_page_manager(shell()->web_contents(),
prerender_urls[0]);
ASSERT_TRUE(ExecJs(shell()->web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", prerender_urls[0])));
ASSERT_TRUE(primary_page_manager.WaitForBeforeChecks());
NavigationRequest* request =
web_contents_impl()->GetPrimaryFrameTree().root()->navigation_request();
ASSERT_EQ(request->GetURL(), prerender_urls[0]);
for (auto& url : prerender_urls) {
if (url == prerender_urls[0]) {
continue;
}
EXPECT_TRUE(GetHostForUrl(url).is_null());
}
primary_page_manager.ResumeActivation();
prerender1_observer.WaitForActivation();
ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id();
ExpectPreloadingAttemptUkm({
attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kSuccess,
PreloadingFailureReason::kUnspecified,
true,
kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate),
attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kReady,
PreloadingFailureReason::kUnspecified,
false,
kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate),
attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kRunning,
PreloadingFailureReason::kUnspecified,
false,
std::nullopt,
blink::mojom::SpeculationEagerness::kImmediate),
attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kTriggeredButPending,
PreloadingFailureReason::kUnspecified,
false,
std::nullopt,
blink::mojom::SpeculationEagerness::kImmediate),
});
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, MultipleNewTabPrerendering) {
GURL initial_url = GetUrl("/simple_links.html");
std::vector<GURL> prerendering_urls = {GetUrl("/title2.html"),
GetUrl("/title2.html?2"),
GetUrl("/title2.html?3")};
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
std::vector<FrameTreeNodeId> prerender_host_ids;
std::vector<WebContents*> prerender_web_contents_list;
for (const GURL& prerendering_url : prerendering_urls) {
FrameTreeNodeId host_id = prerender_helper()->AddPrerender(
prerendering_url, std::nullopt, "_blank");
EXPECT_FALSE(base::Contains(prerender_host_ids, host_id));
prerender_host_ids.push_back(host_id);
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
ASSERT_TRUE(prerender_web_contents);
EXPECT_NE(prerender_web_contents, web_contents_impl());
ExpectWebContentsIsForNewTabPrerendering(*prerender_web_contents);
EXPECT_FALSE(
base::Contains(prerender_web_contents_list, prerender_web_contents));
prerender_web_contents_list.push_back(prerender_web_contents);
}
test::PrerenderHostObserver prerender_observer(
*prerender_web_contents_list[0], prerender_host_ids[0]);
const std::string kLinkClickScript = R"(
clickSameSiteNewWindowLink();
)";
EXPECT_TRUE(ExecJs(web_contents(), kLinkClickScript));
prerender_observer.WaitForActivation();
EXPECT_EQ(prerender_web_contents_list[0]->GetLastCommittedURL(),
prerendering_urls[0]);
EXPECT_TRUE(prerender_observer.was_activated());
EXPECT_FALSE(
HasHostForUrl(*prerender_web_contents_list[0], prerendering_urls[0]));
EXPECT_TRUE(
HasHostForUrl(*prerender_web_contents_list[1], prerendering_urls[1]));
EXPECT_TRUE(
HasHostForUrl(*prerender_web_contents_list[2], prerendering_urls[2]));
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kActivated);
EXPECT_EQ(web_contents()->GetLastCommittedURL(), initial_url);
}
void PrerenderBrowserTest::TestSequentialPrerenderingVisibilityStateTransition(
Visibility initial_visibility,
Visibility next_visibility) {
net::test_server::ControllableHttpResponse response1(
embedded_test_server(), "/empty.html?prerender1");
ASSERT_TRUE(embedded_test_server()->Start());
const GURL initial_url = embedded_test_server()->GetURL("/empty.html");
const GURL prerender_url1 =
embedded_test_server()->GetURL("/empty.html?prerender1");
const GURL prerender_url2 =
embedded_test_server()->GetURL("/empty.html?prerender2");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
switch (initial_visibility) {
case Visibility::VISIBLE:
web_contents()->WasShown();
break;
case Visibility::HIDDEN:
web_contents()->WasHidden();
break;
case Visibility::OCCLUDED:
web_contents()->WasOccluded();
break;
}
test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl());
AddPrerendersAsync({prerender_url1, prerender_url2});
registry_observer.WaitForTrigger(prerender_url2);
test::PrerenderHostObserver prerender2_observer(
*web_contents(), GetHostForUrl(prerender_url2));
response1.WaitForRequest();
switch (next_visibility) {
case Visibility::HIDDEN:
web_contents()->WasHidden();
break;
case Visibility::OCCLUDED:
web_contents()->WasOccluded();
break;
case Visibility::VISIBLE:
break;
}
response1.Send(net::HTTP_OK, "");
response1.Done();
WaitForPrerenderLoadCompletion(prerender_url1);
PrerenderHost* prerender_host =
web_contents_impl()->GetPrerenderHostRegistry()->FindHostByUrlForTesting(
prerender_url1);
auto* preloading_attempt_impl = static_cast<PreloadingAttemptImpl*>(
prerender_host->preloading_attempt().get());
EXPECT_EQ(test::PreloadingAttemptAccessor(preloading_attempt_impl)
.GetTriggeringOutcome(),
PreloadingTriggeringOutcome::kReady);
PrerenderHost* prerender2_host =
web_contents_impl()->GetPrerenderHostRegistry()->FindHostByUrlForTesting(
prerender_url2);
auto* preloading_attempt_impl2 = static_cast<PreloadingAttemptImpl*>(
prerender2_host->preloading_attempt().get());
EXPECT_EQ(test::PreloadingAttemptAccessor(preloading_attempt_impl2)
.GetTriggeringOutcome(),
PreloadingTriggeringOutcome::kTriggeredButPending);
web_contents()->WasShown();
WaitForPrerenderLoadCompletion(prerender_url2);
auto* preloading_attempt_impl2_2 = static_cast<PreloadingAttemptImpl*>(
prerender2_host->preloading_attempt().get());
EXPECT_EQ(test::PreloadingAttemptAccessor(preloading_attempt_impl2_2)
.GetTriggeringOutcome(),
PreloadingTriggeringOutcome::kReady);
NavigatePrimaryPage(prerender_url2);
prerender2_observer.WaitForActivation();
EXPECT_EQ(web_contents()->GetLastCommittedURL(), prerender_url2);
EXPECT_TRUE(prerender2_observer.was_activated());
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
PrerenderInBackground_InitialyVisible_Hidden) {
TestSequentialPrerenderingVisibilityStateTransition(Visibility::VISIBLE,
Visibility::HIDDEN);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
PrerenderInBackground_InitialyVisible_Occluded) {
TestSequentialPrerenderingVisibilityStateTransition(Visibility::VISIBLE,
Visibility::OCCLUDED);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
PrerenderInBackground_InitialyOccluded_Hidden) {
TestSequentialPrerenderingVisibilityStateTransition(Visibility::OCCLUDED,
Visibility::HIDDEN);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
PrerenderInBackground_InitialyOccluded_Occluded) {
TestSequentialPrerenderingVisibilityStateTransition(Visibility::OCCLUDED,
Visibility::OCCLUDED);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
PrerenderInBackground_InitialyHidden_Hidden) {
TestSequentialPrerenderingVisibilityStateTransition(Visibility::HIDDEN,
Visibility::HIDDEN);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
PrerenderInBackground_InitialyHidden_Occluded) {
TestSequentialPrerenderingVisibilityStateTransition(Visibility::HIDDEN,
Visibility::OCCLUDED);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
PrerenderInBackground_InitialyHidden_Visible) {
TestSequentialPrerenderingVisibilityStateTransition(Visibility::HIDDEN,
Visibility::VISIBLE);
}
#if BUILDFLAG(IS_WIN)
#define MAYBE_PrerenderWhenInitiatorInBackground_Queue_Processing \
DISABLED_PrerenderWhenInitiatorInBackground_Queue_Processing
#else
#define MAYBE_PrerenderWhenInitiatorInBackground_Queue_Processing \
PrerenderWhenInitiatorInBackground_Queue_Processing
#endif
IN_PROC_BROWSER_TEST_F(
PrerenderBrowserTest,
MAYBE_PrerenderWhenInitiatorInBackground_Queue_Processing) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL initial_url = embedded_test_server()->GetURL("/empty.html");
const GURL prerender_url1 =
embedded_test_server()->GetURL("/empty.html?prerender1");
const GURL prerender_url2 =
embedded_test_server()->GetURL("/empty.html?prerender2");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
web_contents()->WasHidden();
test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl());
AddPrerendersAsync({prerender_url1, prerender_url2});
registry_observer.WaitForTrigger(prerender_url2);
WaitForPrerenderLoadCompletion(prerender_url1);
PrerenderHost* prerender_host =
web_contents_impl()->GetPrerenderHostRegistry()->FindHostByUrlForTesting(
prerender_url1);
auto* preloading_attempt_impl = static_cast<PreloadingAttemptImpl*>(
prerender_host->preloading_attempt().get());
EXPECT_EQ(test::PreloadingAttemptAccessor(preloading_attempt_impl)
.GetTriggeringOutcome(),
PreloadingTriggeringOutcome::kReady);
PrerenderHost* prerender2_host =
web_contents_impl()->GetPrerenderHostRegistry()->FindHostByUrlForTesting(
prerender_url2);
auto* preloading_attempt_impl2 = static_cast<PreloadingAttemptImpl*>(
prerender2_host->preloading_attempt().get());
EXPECT_EQ(test::PreloadingAttemptAccessor(preloading_attempt_impl2)
.GetTriggeringOutcome(),
PreloadingTriggeringOutcome::kTriggeredButPending);
web_contents_impl()->GetPrerenderHostRegistry()->CancelHost(
GetHostForUrl(prerender_url1), PrerenderFinalStatus::kDestroyed);
WaitForPrerenderLoadCompletion(prerender_url2);
test::PrerenderHostObserver prerender2_observer(
*web_contents(), GetHostForUrl(prerender_url2));
auto* preloading_attempt_impl2_2 = static_cast<PreloadingAttemptImpl*>(
prerender2_host->preloading_attempt().get());
EXPECT_EQ(test::PreloadingAttemptAccessor(preloading_attempt_impl2_2)
.GetTriggeringOutcome(),
PreloadingTriggeringOutcome::kReady);
web_contents()->WasShown();
NavigatePrimaryPage(prerender_url2);
prerender2_observer.WaitForActivation();
EXPECT_EQ(web_contents()->GetLastCommittedURL(), prerender_url2);
EXPECT_TRUE(prerender2_observer.was_activated());
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
IsInactiveAndDisallowActivationCancelsPrerendering) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id);
EXPECT_EQ(prerender_render_frame_host->lifecycle_state(),
RenderFrameHostImpl::LifecycleStateImpl::kPrerendering);
EXPECT_TRUE(prerender_render_frame_host->IsInactiveAndDisallowActivation(
DisallowActivationReasonId::kForTesting));
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 2);
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kInactivePageRestriction);
histogram_tester().ExpectUniqueSample(
"Prerender.CanceledForInactivePageRestriction.DisallowActivationReason."
"SpeculationRule",
DisallowActivationReasonId::kForTesting, 1);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, InputRoutedToPrimaryFrameTree) {
const GURL kInitialUrl = GetUrl("/prerender/simple_prerender.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
WaitForPrerenderLoadCompletion(kPrerenderingUrl);
TestNavigationObserver navigation_observer(web_contents());
SyntheticTapGestureParams params;
params.gesture_source_type = mojom::GestureSourceType::kTouchInput;
params.position = GetCenterCoordinatesOfElementWithId(web_contents(), "link");
web_contents_impl()->GetRenderViewHost()->GetWidget()->QueueSyntheticGesture(
std::make_unique<SyntheticTapGesture>(params), base::DoNothing());
navigation_observer.Wait();
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, VisibilityWhilePrerendering) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(shell()->web_contents()->GetLastCommittedURL(), kInitialUrl);
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
auto* prerendered_render_frame_host = GetPrerenderedMainFrameHost(host_id);
auto* rvh = static_cast<RenderViewHostImpl*>(
prerendered_render_frame_host->GetRenderViewHost());
EXPECT_EQ(rvh->GetPageLifecycleStateManager()
->CalculatePageLifecycleState()
->visibility,
PageVisibilityState::kHidden);
EXPECT_EQ(prerendered_render_frame_host->GetVisibilityState(),
PageVisibilityState::kHidden);
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ(rvh->GetPageLifecycleStateManager()
->CalculatePageLifecycleState()
->visibility,
PageVisibilityState::kVisible);
EXPECT_EQ(prerendered_render_frame_host->GetVisibilityState(),
PageVisibilityState::kVisible);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, TitleWhilePrerendering) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/simple_page.html");
const std::u16string kInitialTitle(u"title");
const std::u16string kPrerenderingTitle(u"OK");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
EXPECT_TRUE(ExecJs(shell()->web_contents(),
JsReplace("document.title = $1", kInitialTitle)));
EXPECT_EQ(shell()->web_contents()->GetTitle(), kInitialTitle);
ASSERT_TRUE(AddPrerender(kPrerenderingUrl));
EXPECT_EQ(shell()->web_contents()->GetTitle(), kInitialTitle);
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
EXPECT_EQ(shell()->web_contents()->GetTitle(), kPrerenderingTitle);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, TitleWasSetWithPrerendering) {
const GURL kInitialUrl = GetUrl("/title2.html");
const GURL kPrerenderingUrlWithTitle = GetUrl("/simple_page.html");
const GURL kPrerenderingUrlWithoutTitle = GetUrl("/title1.html");
const std::u16string kInitialTitle(u"Title Of Awesomeness");
const std::u16string kPrerenderingTitle(u"OK");
{
testing::NiceMock<MockWebContentsObserver> mock_observer(
shell()->web_contents());
EXPECT_CALL(mock_observer, TitleWasSet(testing::_));
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
EXPECT_EQ(shell()->web_contents()->GetTitle(), kInitialTitle);
}
{
testing::NiceMock<MockWebContentsObserver> mock_observer(
shell()->web_contents());
EXPECT_CALL(mock_observer, TitleWasSet(testing::_)).Times(0);
ASSERT_TRUE(AddPrerender(kPrerenderingUrlWithTitle));
}
{
testing::NiceMock<MockWebContentsObserver> mock_observer(
shell()->web_contents());
EXPECT_CALL(mock_observer, TitleWasSet(testing::_))
.WillOnce([kPrerenderingTitle](NavigationEntry* entry) {
EXPECT_EQ(entry->GetTitleForDisplay(), kPrerenderingTitle);
});
NavigatePrimaryPage(kPrerenderingUrlWithTitle);
}
{
testing::NiceMock<MockWebContentsObserver> mock_observer(
shell()->web_contents());
EXPECT_CALL(mock_observer, TitleWasSet(testing::_)).Times(0);
ASSERT_TRUE(AddPrerender(kPrerenderingUrlWithoutTitle));
NavigatePrimaryPage(kPrerenderingUrlWithoutTitle);
}
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, EmbedderPrerenderToNonHttpUrl) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderUrl = GURL("file://example.txt");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(shell()->web_contents()->GetLastCommittedURL(), kInitialUrl);
std::unique_ptr<PrerenderHandle> prerender_handle =
AddEmbedderTriggeredPrerenderAsync(kPrerenderUrl);
EXPECT_FALSE(prerender_handle);
EXPECT_TRUE(GetHostForUrl(kPrerenderUrl).is_null());
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
"EmbedderSuffixForTest",
PrerenderFinalStatus::kInvalidSchemeNavigation, 1);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, OpenURLInPrerenderingFrame) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/page_with_blank_iframe.html");
const GURL kNewIframeUrl = GetUrl("/simple_page.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(shell()->web_contents()->GetLastCommittedURL(), kInitialUrl);
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
auto* prerendered_render_frame_host = GetPrerenderedMainFrameHost(host_id);
auto* child_frame = ChildFrameAt(prerendered_render_frame_host, 0);
ASSERT_TRUE(child_frame);
TestNavigationManager iframe_observer(shell()->web_contents(), kNewIframeUrl);
shell()->web_contents()->OpenURL(
OpenURLParams(
kNewIframeUrl, Referrer(), child_frame->GetFrameTreeNodeId(),
WindowOpenDisposition::CURRENT_TAB, ui::PAGE_TRANSITION_AUTO_SUBFRAME,
false),
{});
ASSERT_TRUE(iframe_observer.WaitForNavigationFinished());
EXPECT_TRUE(iframe_observer.was_committed());
EXPECT_TRUE(iframe_observer.was_successful());
EXPECT_EQ(child_frame->GetLastCommittedURL(), kNewIframeUrl);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, DidFailLoadCancelsPrerendering) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/page_with_iframe.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
testing::NiceMock<MockWebContentsObserver> observer(shell()->web_contents());
EXPECT_CALL(observer, DidFailLoad(testing::_, testing::_, testing::_))
.Times(0);
FrameTreeNodeId prerender_host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHostImpl* prerender_frame_host =
GetPrerenderedMainFrameHost(prerender_host_id);
prerender_frame_host->DidFailLoadWithError(kPrerenderingUrl, net::ERR_FAILED);
test::PrerenderHostObserver prerender_observer(*web_contents(),
kPrerenderingUrl);
TestNavigationManager navigation_observer(shell()->web_contents(),
kPrerenderingUrl);
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
ASSERT_TRUE(ExecJs(web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", kPrerenderingUrl)));
ASSERT_TRUE(navigation_observer.WaitForNavigationFinished());
EXPECT_FALSE(prerender_observer.was_activated());
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kDidFailLoad);
}
class DidFailLoadWebContentsObserver : public WebContentsObserver {
public:
explicit DidFailLoadWebContentsObserver(WebContents* web_contents)
: WebContentsObserver(web_contents) {}
bool WasDidFailLoadCalled() { return was_did_fail_load_called_; }
int GetErrorCode() const { return error_code_; }
const GURL& GetUrl() const { return url_; }
private:
void DidFailLoad(RenderFrameHost* rfh,
const GURL& url,
int error_code) override {
was_did_fail_load_called_ = true;
url_ = url;
error_code_ = error_code;
EXPECT_FALSE(rfh->IsErrorDocument());
EXPECT_TRUE(rfh->IsInLifecycleState(
RenderFrameHost::LifecycleState::kPrerendering));
}
bool was_did_fail_load_called_ = false;
int error_code_ = net::OK;
GURL url_;
};
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
DidFailLoadSubframesDoesNotCancelPrerendering) {
DidFailLoadWebContentsObserver observer(web_contents());
TestHostPrerenderingState(GetUrl("/page_with_stop_iframe.html"));
EXPECT_TRUE(observer.WasDidFailLoadCalled());
EXPECT_EQ(net::ERR_ABORTED, observer.GetErrorCode());
EXPECT_EQ(GetUrl("/stop.html"), observer.GetUrl());
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
DidFailLoadMainFrameCancelsPrerendering) {
DidFailLoadWebContentsObserver observer(web_contents());
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/stop.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
test::PrerenderHostObserver host_observer(*web_contents_impl(),
kPrerenderingUrl);
AddPrerenderAsync(kPrerenderingUrl);
host_observer.WaitForDestroyed();
EXPECT_FALSE(observer.WasDidFailLoadCalled());
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kDidFailLoad);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
OpenURLCrossOriginInPrerenderingFrame) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/page_with_blank_iframe.html");
const GURL kNewIframeUrl = GetCrossSiteUrl("/simple_page.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(shell()->web_contents()->GetLastCommittedURL(), kInitialUrl);
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
auto* prerendered_render_frame_host = GetPrerenderedMainFrameHost(host_id);
auto* child_frame = ChildFrameAt(prerendered_render_frame_host, 0);
ASSERT_TRUE(child_frame);
TestNavigationManager iframe_observer(shell()->web_contents(), kNewIframeUrl);
{
shell()->web_contents()->OpenURL(
OpenURLParams(kNewIframeUrl, Referrer(),
child_frame->GetFrameTreeNodeId(),
WindowOpenDisposition::CURRENT_TAB,
ui::PAGE_TRANSITION_AUTO_SUBFRAME,
false),
{});
ASSERT_TRUE(iframe_observer.WaitForFirstYieldAfterDidStartNavigation());
NavigationRequest* request =
static_cast<NavigationRequest*>(iframe_observer.GetNavigationHandle());
EXPECT_EQ(request->state(), NavigationRequest::WILL_START_REQUEST);
EXPECT_TRUE(request->IsDeferredForTesting());
}
{
test::PrerenderHostObserver prerender_observer(*web_contents(),
kPrerenderingUrl);
ASSERT_TRUE(ExecJs(web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", kPrerenderingUrl)));
prerender_observer.WaitForActivation();
}
{
ASSERT_TRUE(iframe_observer.WaitForNavigationFinished());
child_frame = ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0);
ASSERT_TRUE(child_frame);
EXPECT_EQ(child_frame->GetLastCommittedURL(), kNewIframeUrl);
}
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
MainFrameNavigationDuringActivation) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?1");
const GURL kPrerenderingUrl2 = GetUrl("/empty.html?2");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId prerender_host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHostImpl* prerendered_rfh =
GetPrerenderedMainFrameHost(prerender_host_id);
test::PrerenderHostObserver prerender_observer(*web_contents(),
prerender_host_id);
auto* prerender_ftn = prerendered_rfh->frame_tree_node();
EXPECT_FALSE(prerender_ftn->HasNavigation());
TestActivationManager activation_observer(shell()->web_contents(),
kPrerenderingUrl);
{
ASSERT_TRUE(ExecJs(web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", kPrerenderingUrl)));
EXPECT_TRUE(activation_observer.WaitForBeforeChecks());
EXPECT_TRUE(activation_observer.GetNavigationHandle()
->IsCommitDeferringConditionDeferredForTesting());
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
}
TestNavigationManager navigation_observer(web_contents(), kPrerenderingUrl2);
NavigatePrerenderedPage(prerender_host_id, kPrerenderingUrl2);
ASSERT_TRUE(navigation_observer.WaitForNavigationFinished());
EXPECT_TRUE(navigation_observer.was_successful());
EXPECT_TRUE(prerender_helper()->VerifyPrerenderingState(kPrerenderingUrl));
activation_observer.ResumeActivation();
activation_observer.WaitForNavigationFinished();
EXPECT_FALSE(
web_contents_impl()->GetPrerenderHostRegistry()->FindNonReservedHostById(
prerender_host_id));
EXPECT_FALSE(
web_contents_impl()->GetPrerenderHostRegistry()->HasReservedHost());
EXPECT_TRUE(activation_observer.was_activated());
EXPECT_TRUE(activation_observer.was_successful());
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), kPrerenderingUrl2);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
MainFrameNavigationConcurrentWithActivation) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?1");
const GURL kPrerenderingUrl2 = GetUrl("/empty.html?2");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId prerender_host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHostImpl* prerendered_rfh =
GetPrerenderedMainFrameHost(prerender_host_id);
test::PrerenderHostObserver prerender_observer(*web_contents(),
prerender_host_id);
auto* prerender_ftn = prerendered_rfh->frame_tree_node();
EXPECT_FALSE(prerender_ftn->HasNavigation());
TestActivationManager activation_observer(shell()->web_contents(),
kPrerenderingUrl);
{
ASSERT_TRUE(ExecJs(web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", kPrerenderingUrl)));
EXPECT_TRUE(activation_observer.WaitForBeforeChecks());
EXPECT_TRUE(activation_observer.GetNavigationHandle()
->IsCommitDeferringConditionDeferredForTesting());
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
}
TestNavigationManager navigation_observer(web_contents(), kPrerenderingUrl2);
NavigatePrerenderedPage(prerender_host_id, kPrerenderingUrl2);
activation_observer.ResumeActivation();
ASSERT_TRUE(navigation_observer.WaitForNavigationFinished());
EXPECT_TRUE(navigation_observer.was_successful());
EXPECT_TRUE(prerender_helper()->VerifyPrerenderingState(kPrerenderingUrl));
activation_observer.WaitForNavigationFinished();
EXPECT_FALSE(
web_contents_impl()->GetPrerenderHostRegistry()->FindNonReservedHostById(
prerender_host_id));
EXPECT_FALSE(
web_contents_impl()->GetPrerenderHostRegistry()->HasReservedHost());
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl2);
EXPECT_TRUE(activation_observer.was_activated());
EXPECT_TRUE(activation_observer.was_successful());
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), kPrerenderingUrl2);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
MainFrameNavigationAfterActivationIsResumed) {
embedded_test_server()->RegisterRequestHandler(base::BindLambdaForTesting(
[&](const net::test_server::HttpRequest& request)
-> std::unique_ptr<net::test_server::HttpResponse> {
if (request.relative_url != "/empty.html?3") {
return nullptr;
}
return std::make_unique<net::test_server::HungResponse>();
}));
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
const GURL kPrerenderingUrl = embedded_test_server()->GetURL("/empty.html?1");
const GURL kPrerenderingUrl2 =
embedded_test_server()->GetURL("/empty.html?2");
const GURL kPrerenderingUrl3 =
embedded_test_server()->GetURL("/empty.html?3");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId prerender_host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHostImpl* prerendered_rfh =
GetPrerenderedMainFrameHost(prerender_host_id);
test::PrerenderHostObserver prerender_observer(*web_contents(),
prerender_host_id);
auto* prerender_ftn = prerendered_rfh->frame_tree_node();
EXPECT_FALSE(prerender_ftn->HasNavigation());
TestActivationManager activation_observer(shell()->web_contents(),
kPrerenderingUrl);
activation_observer.SetCallbackCalledAfterActivationIsReady(base::BindOnce(
&PrerenderBrowserTest::NavigatePrerenderedPage, base::Unretained(this),
prerender_host_id, kPrerenderingUrl3));
{
ASSERT_TRUE(ExecJs(web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", kPrerenderingUrl)));
EXPECT_TRUE(activation_observer.WaitForBeforeChecks());
EXPECT_TRUE(activation_observer.GetNavigationHandle()
->IsCommitDeferringConditionDeferredForTesting());
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
}
TestNavigationManager navigation_observer(web_contents(), kPrerenderingUrl2);
NavigatePrerenderedPage(prerender_host_id, kPrerenderingUrl2);
EXPECT_TRUE(prerender_helper()->VerifyPrerenderingState(kPrerenderingUrl));
activation_observer.ResumeActivation();
ASSERT_TRUE(navigation_observer.WaitForNavigationFinished());
EXPECT_TRUE(navigation_observer.was_successful());
activation_observer.WaitForNavigationFinished();
EXPECT_FALSE(
web_contents_impl()->GetPrerenderHostRegistry()->FindNonReservedHostById(
prerender_host_id));
EXPECT_FALSE(
web_contents_impl()->GetPrerenderHostRegistry()->HasReservedHost());
EXPECT_FALSE(activation_observer.was_activated());
EXPECT_TRUE(activation_observer.was_successful());
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kActivatedDuringMainFrameNavigation);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
CancelPrerenderWhenDeferringActivationNavigation) {
const char prerendering_url_c[] = "/empty.html?prerender";
net::test_server::ControllableHttpResponse response_for_initial_navigation(
embedded_test_server(), prerendering_url_c);
net::test_server::ControllableHttpResponse response_for_activation_navigation(
embedded_test_server(), prerendering_url_c);
ASSERT_TRUE(embedded_test_server()->Start());
const GURL initial_url = embedded_test_server()->GetURL("/empty.html");
const GURL prerendering_url =
embedded_test_server()->GetURL(prerendering_url_c);
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
content::test::PrerenderHostCreationWaiter host_creation_waiter;
AddPrerenderAsync(prerendering_url);
FrameTreeNodeId host_id = host_creation_waiter.Wait();
response_for_initial_navigation.WaitForRequest();
test::PrerenderHostObserver prerender_observer(*web_contents(), host_id);
TestActivationManager activation_observer(web_contents(), prerendering_url);
test::PrerenderTestHelper::NavigatePrimaryPageAsync(*web_contents_impl(),
prerendering_url);
NavigationRequest* request =
web_contents_impl()->GetPrimaryFrameTree().root()->navigation_request();
ASSERT_TRUE(activation_observer.WaitForBeforeChecks());
activation_observer.ResumeActivation();
ASSERT_TRUE(request->IsCommitDeferringConditionDeferredForTesting());
ASSERT_EQ(request->state(), NavigationRequest::NOT_STARTED);
CancelPrerenderedPage(host_id);
prerender_observer.WaitForDestroyed();
response_for_activation_navigation.WaitForRequest();
response_for_activation_navigation.Send(net::HTTP_OK, "");
response_for_activation_navigation.Done();
activation_observer.WaitForNavigationFinished();
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), prerendering_url);
EXPECT_FALSE(activation_observer.was_activated());
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
DidFinishLoadInvokedAfterActivation) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/simple_page.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
testing::NiceMock<MockWebContentsObserver> observer(shell()->web_contents());
EXPECT_CALL(observer, DidFinishLoad(testing::_, testing::_)).Times(0);
FrameTreeNodeId prerender_host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHostImpl* prerender_frame_host =
GetPrerenderedMainFrameHost(prerender_host_id);
EXPECT_EQ(0u, prerender_frame_host->child_count());
testing::Mock::VerifyAndClearExpectations(&observer);
testing::InSequence s;
{
EXPECT_CALL(observer, DidFinishNavigation(testing::_));
EXPECT_CALL(observer,
DidFinishLoad(prerender_frame_host, kPrerenderingUrl));
}
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
DidFinishLoadInvokedAfterActivationWithSubframes) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/page_with_iframe.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
testing::NiceMock<MockWebContentsObserver> observer(shell()->web_contents());
testing::InSequence s;
EXPECT_CALL(observer, DidFinishLoad(testing::_, testing::_)).Times(0);
FrameTreeNodeId prerender_host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHostImpl* prerender_main_frame_host =
GetPrerenderedMainFrameHost(prerender_host_id);
RenderFrameHost* child_frame = ChildFrameAt(prerender_main_frame_host, 0);
EXPECT_EQ(1u, prerender_main_frame_host->child_count());
testing::Mock::VerifyAndClearExpectations(&observer);
{
EXPECT_CALL(observer, DidFinishNavigation(testing::_));
EXPECT_CALL(observer,
DidFinishLoad(prerender_main_frame_host, kPrerenderingUrl));
EXPECT_CALL(observer,
DidFinishLoad(child_frame, child_frame->GetLastCommittedURL()));
}
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
DOMContentLoadedInvokedAfterActivationWithSubframes) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/page_with_iframe.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
testing::NiceMock<MockWebContentsObserver> observer(shell()->web_contents());
EXPECT_CALL(observer, DOMContentLoaded(testing::_)).Times(0);
FrameTreeNodeId prerender_host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHostImpl* prerender_main_frame_host =
GetPrerenderedMainFrameHost(prerender_host_id);
RenderFrameHost* child_frame = ChildFrameAt(prerender_main_frame_host, 0);
EXPECT_EQ(prerender_main_frame_host->child_count(), 1u);
ASSERT_TRUE(prerender_host_id);
testing::Mock::VerifyAndClearExpectations(&observer);
testing::InSequence s;
{
EXPECT_CALL(observer, DidFinishNavigation(testing::_));
EXPECT_CALL(observer, DOMContentLoaded(prerender_main_frame_host)).Times(1);
EXPECT_CALL(observer, DOMContentLoaded(child_frame)).Times(1);
}
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
}
IN_PROC_BROWSER_TEST_F(
PrerenderBrowserTest,
DocumentOnLoadCompletedInPrimaryMainFrameInvokedAfterActivation) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/page_with_iframe.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
testing::NiceMock<MockWebContentsObserver> observer(shell()->web_contents());
EXPECT_CALL(observer, DocumentOnLoadCompletedInPrimaryMainFrame()).Times(0);
FrameTreeNodeId prerender_host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHostImpl* prerender_frame_host =
GetPrerenderedMainFrameHost(prerender_host_id);
EXPECT_EQ(prerender_frame_host->child_count(), 1u);
ASSERT_TRUE(prerender_host_id);
testing::Mock::VerifyAndClearExpectations(&observer);
testing::InSequence s;
{
EXPECT_CALL(observer, DidFinishNavigation(testing::_));
EXPECT_CALL(observer, DocumentOnLoadCompletedInPrimaryMainFrame()).Times(1);
}
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
}
IN_PROC_BROWSER_TEST_F(
PrerenderBrowserTest,
PrimaryMainDocumentElementAvailableInvokedAfterActivation) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/page_with_iframe.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
testing::NiceMock<MockWebContentsObserver> observer(shell()->web_contents());
EXPECT_CALL(observer, PrimaryMainDocumentElementAvailable()).Times(0);
FrameTreeNodeId prerender_host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHostImpl* prerender_frame_host =
GetPrerenderedMainFrameHost(prerender_host_id);
EXPECT_EQ(prerender_frame_host->child_count(), 1u);
ASSERT_TRUE(prerender_host_id);
testing::Mock::VerifyAndClearExpectations(&observer);
testing::InSequence s;
EXPECT_CALL(observer, DidFinishNavigation(testing::_));
EXPECT_CALL(observer, PrimaryMainDocumentElementAvailable()).Times(1);
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
LoadProgressChangedInvokedOnActivation) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/simple_page.html");
web_contents_impl()->set_minimum_delay_between_loading_updates_for_testing(
base::Milliseconds(0));
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
testing::NiceMock<MockWebContentsObserver> observer(shell()->web_contents());
testing::InSequence s;
EXPECT_CALL(observer, LoadProgressChanged(testing::_)).Times(0);
FrameTreeNodeId prerender_host_id = AddPrerender(kPrerenderingUrl);
ASSERT_TRUE(prerender_host_id);
RenderFrameHostImpl* prerender_frame_host =
GetPrerenderedMainFrameHost(prerender_host_id);
testing::Mock::VerifyAndClearExpectations(&observer);
{
EXPECT_CALL(observer, LoadProgressChanged(blink::kInitialLoadProgress));
EXPECT_CALL(observer, DidFinishNavigation(testing::_));
EXPECT_CALL(observer, LoadProgressChanged(blink::kFinalLoadProgress))
.Times(1);
}
prerender_frame_host->GetPage().set_load_progress(blink::kFinalLoadProgress);
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
DidStopLoadingInvokedOnActivation) {
const GURL initial_url = GetUrl("/empty.html");
const GURL prerendering_url = GetUrl("/simple_page.html");
web_contents_impl()->set_minimum_delay_between_loading_updates_for_testing(
base::Milliseconds(0));
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
testing::NiceMock<MockWebContentsObserver> observer(shell()->web_contents());
EXPECT_CALL(observer, DidStopLoading()).Times(0);
FrameTreeNodeId prerender_host_id = AddPrerender(prerendering_url);
ASSERT_TRUE(prerender_host_id);
testing::Mock::VerifyAndClearExpectations(&observer);
EXPECT_CALL(observer, DidStopLoading()).Times(1);
test::PrerenderHostObserver host_observer(*web_contents(), prerender_host_id);
NavigatePrimaryPage(prerendering_url);
EXPECT_TRUE(host_observer.was_activated());
EXPECT_EQ(web_contents()->GetLastCommittedURL(), prerendering_url);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
DidStopLoadingInvokedAfterActivation) {
net::test_server::ControllableHttpResponse response(embedded_test_server(),
"/simple_page.html");
ASSERT_TRUE(embedded_test_server()->Start());
const GURL initial_url = embedded_test_server()->GetURL("/empty.html");
const GURL prerendering_url =
embedded_test_server()->GetURL("/simple_page.html");
web_contents_impl()->set_minimum_delay_between_loading_updates_for_testing(
base::Milliseconds(0));
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
testing::NiceMock<MockWebContentsObserver> observer(shell()->web_contents());
EXPECT_CALL(observer, DidStopLoading()).Times(0);
AddPrerenderAsync(prerendering_url);
const char kHttpResponseHeader[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"\r\n";
response.WaitForRequest();
response.Send(kHttpResponseHeader);
EXPECT_CALL(observer, DidStopLoading()).Times(0);
test::PrerenderHostObserver host_observer(*web_contents(), prerendering_url);
prerender_helper()->NavigatePrimaryPageAsync(prerendering_url);
host_observer.WaitForActivation();
EXPECT_TRUE(host_observer.was_activated());
EXPECT_EQ(web_contents()->GetLastCommittedURL(), prerendering_url);
testing::Mock::VerifyAndClearExpectations(&observer);
base::RunLoop run_loop;
EXPECT_CALL(observer, DidStopLoading()).WillOnce([&]() { run_loop.Quit(); });
response.Send("0\r\n");
response.Send("\r\n");
response.Done();
run_loop.Run();
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, OrderingOfDifferentLoadEvents) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/simple_page.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
testing::NiceMock<MockWebContentsObserver> observer(shell()->web_contents());
FrameTreeNodeId prerender_host_id = AddPrerender(kPrerenderingUrl);
ASSERT_TRUE(prerender_host_id);
testing::Mock::VerifyAndClearExpectations(&observer);
testing::InSequence s;
{
EXPECT_CALL(observer, DidStartLoading()).Times(1);
EXPECT_CALL(observer, DidFinishNavigation(testing::_)).Times(1);
EXPECT_CALL(observer, DOMContentLoaded(testing::_)).Times(1);
EXPECT_CALL(observer, DocumentOnLoadCompletedInPrimaryMainFrame()).Times(1);
EXPECT_CALL(observer, DidFinishLoad(testing::_, testing::_)).Times(1);
EXPECT_CALL(observer, DidStopLoading()).Times(1);
}
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
CrossOriginSubframeNavigationDuringActivation) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/page_with_blank_iframe.html");
const GURL kCrossOriginUrl = GetCrossSiteUrl("/simple_page.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(shell()->web_contents()->GetLastCommittedURL(), kInitialUrl);
FrameTreeNodeId prerender_host_id;
RenderFrameHost* prerender_main_frame = nullptr;
{
prerender_host_id = AddPrerender(kPrerenderingUrl);
prerender_main_frame = GetPrerenderedMainFrameHost(prerender_host_id);
RenderFrameHost* child_frame = ChildFrameAt(prerender_main_frame, 0);
ASSERT_TRUE(child_frame);
}
test::PrerenderHostObserver prerender_observer(*web_contents(),
kPrerenderingUrl);
TestActivationManager activation_observer(shell()->web_contents(),
kPrerenderingUrl);
{
ASSERT_TRUE(ExecJs(web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", kPrerenderingUrl)));
EXPECT_TRUE(activation_observer.WaitForBeforeChecks());
EXPECT_TRUE(activation_observer.GetNavigationHandle()
->IsCommitDeferringConditionDeferredForTesting());
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
}
std::string kNavigateScript = R"(
document.querySelector('iframe').src = $1;
)";
TestNavigationManager iframe_nav_observer(shell()->web_contents(),
kCrossOriginUrl);
ASSERT_TRUE(ExecJs(prerender_main_frame,
JsReplace(kNavigateScript, kCrossOriginUrl)));
ASSERT_TRUE(iframe_nav_observer.WaitForFirstYieldAfterDidStartNavigation());
auto* child_ftn =
FrameTreeNode::GloballyFindByID(prerender_host_id)->child_at(0);
auto* child_navigation = child_ftn->navigation_request();
ASSERT_NE(child_navigation, nullptr);
EXPECT_TRUE(child_navigation->IsDeferredForTesting());
activation_observer.WaitForNavigationFinished();
EXPECT_TRUE(activation_observer.was_activated());
ASSERT_TRUE(iframe_nav_observer.WaitForNavigationFinished());
EXPECT_EQ(ChildFrameAt(prerender_main_frame, 0)->GetLastCommittedURL(),
kCrossOriginUrl);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
OpenURLInSubframeDuringActivation) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/page_with_blank_iframe.html");
const GURL kNewIframeUrl = GetUrl("/simple_page.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(shell()->web_contents()->GetLastCommittedURL(), kInitialUrl);
FrameTreeNodeId prerender_host_id;
RenderFrameHost* child_frame = nullptr;
{
prerender_host_id = AddPrerender(kPrerenderingUrl);
auto* prerendered_render_frame_host =
GetPrerenderedMainFrameHost(prerender_host_id);
child_frame = ChildFrameAt(prerendered_render_frame_host, 0);
ASSERT_TRUE(child_frame);
}
test::PrerenderHostObserver prerender_observer(*web_contents(),
kPrerenderingUrl);
TestActivationManager activation_observer(shell()->web_contents(),
kPrerenderingUrl);
{
ASSERT_TRUE(ExecJs(web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", kPrerenderingUrl)));
EXPECT_TRUE(activation_observer.WaitForBeforeChecks());
EXPECT_TRUE(activation_observer.GetNavigationHandle()
->IsCommitDeferringConditionDeferredForTesting());
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
}
{
TestNavigationManager iframe_observer(shell()->web_contents(),
kNewIframeUrl);
shell()->web_contents()->OpenURL(
OpenURLParams(kNewIframeUrl, Referrer(),
child_frame->GetFrameTreeNodeId(),
WindowOpenDisposition::CURRENT_TAB,
ui::PAGE_TRANSITION_AUTO_SUBFRAME,
false),
{});
ASSERT_TRUE(iframe_observer.WaitForNavigationFinished());
EXPECT_EQ(child_frame->GetLastCommittedURL(), kNewIframeUrl);
}
activation_observer.WaitForNavigationFinished();
EXPECT_TRUE(activation_observer.was_activated());
}
class InvisiblePageLazyLoadingImageBrowserTest
: public PrerenderBrowserTest,
public testing::WithParamInterface<
blink::features::EnableLazyLoadImageForInvisiblePageType> {
public:
static std::string GetFieldTrialParamName(
blink::features::EnableLazyLoadImageForInvisiblePageType
target_page_type) {
switch (target_page_type) {
case blink::features::EnableLazyLoadImageForInvisiblePageType::
kAllInvisiblePage:
return "all_invisible_page";
case blink::features::EnableLazyLoadImageForInvisiblePageType::
kPrerenderPage:
return "prerender_page";
}
}
InvisiblePageLazyLoadingImageBrowserTest() {
feature_list_.InitWithFeaturesAndParameters(
{{blink::features::kEnableLazyLoadImageForInvisiblePage,
{
{"enabled_page_type", GetFieldTrialParamName(GetParam())},
}}},
{});
}
private:
base::test::ScopedFeatureList feature_list_;
};
INSTANTIATE_TEST_SUITE_P(
All,
InvisiblePageLazyLoadingImageBrowserTest,
testing::Values(blink::features::EnableLazyLoadImageForInvisiblePageType::
kAllInvisiblePage,
blink::features::EnableLazyLoadImageForInvisiblePageType::
kPrerenderPage),
[](const testing::TestParamInfo<
blink::features::EnableLazyLoadImageForInvisiblePageType>& info) {
return InvisiblePageLazyLoadingImageBrowserTest::GetFieldTrialParamName(
info.param);
});
IN_PROC_BROWSER_TEST_P(InvisiblePageLazyLoadingImageBrowserTest, LazyLoading) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/prerender/image_loading_lazy.html");
const GURL kImageUrl = GetUrl("/blank.jpg");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(shell()->web_contents()->GetLastCommittedURL(), kInitialUrl);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0);
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHost* prerender_frame_host = GetPrerenderedMainFrameHost(host_id);
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_EQ(GetRequestCount(kImageUrl), 0);
EXPECT_TRUE(ExecJs(prerender_frame_host, "runLoop()"));
EXPECT_EQ(EvalJs(prerender_frame_host, "image_loaded"), false);
test::PrerenderHostObserver host_observer(*web_contents(), kPrerenderingUrl);
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_TRUE(host_observer.was_activated());
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
EXPECT_TRUE(ExecJs(prerender_frame_host, "promise_with_resolvers.promise"));
EXPECT_EQ(EvalJs(prerender_frame_host, "image_loaded"), true);
}
class DisabledInvisiblePageLazyLoadingImageBrowserTest
: public PrerenderBrowserTest {
public:
DisabledInvisiblePageLazyLoadingImageBrowserTest() {
feature_list_.InitAndDisableFeature(
blink::features::kEnableLazyLoadImageForInvisiblePage);
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(DisabledInvisiblePageLazyLoadingImageBrowserTest,
LazyLoading) {
ASSERT_FALSE(base::FeatureList::IsEnabled(
blink::features::kEnableLazyLoadImageForInvisiblePage));
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/prerender/image_loading_lazy.html");
const GURL kImageUrl = GetUrl("/blank.jpg");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(shell()->web_contents()->GetLastCommittedURL(), kInitialUrl);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0);
AddPrerender(kPrerenderingUrl);
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_EQ(GetRequestCount(kImageUrl), 1);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
SessionStorageAfterBackNavigation_NoProcessReuse) {
DisableBackForwardCacheForTesting(shell()->web_contents(),
BackForwardCache::TEST_REQUIRES_NO_CACHING);
const GURL kInitialUrl = GetUrl("/prerender/session_storage.html");
const GURL kPrerenderingUrl =
GetUrl("/prerender/session_storage.html?prerendering=");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
std::unique_ptr<RenderProcessHostWatcher> process_host_watcher =
std::make_unique<RenderProcessHostWatcher>(
current_frame_host()->GetProcess(),
RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION);
AddPrerender(kPrerenderingUrl);
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ("initial", EvalJs(current_frame_host(),
"window.sessionKeysInPrerenderingchange")
.ExtractString());
EXPECT_EQ(
"activated, initial",
EvalJs(current_frame_host(), "getSessionStorageKeys()").ExtractString());
EXPECT_TRUE(ExecJs(shell()->web_contents(),
"new Promise(resolve => requestIdleCallback(resolve));"));
process_host_watcher->Wait();
TestNavigationObserver observer(shell()->web_contents());
shell()->GoBackOrForward(-1);
observer.Wait();
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), kInitialUrl);
EXPECT_EQ(
"activated, initial",
EvalJs(current_frame_host(), "getSessionStorageKeys()").ExtractString());
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
SessionStorageAfterBackNavigation_KeepInitialProcess) {
const GURL kInitialUrl = GetUrl("/prerender/session_storage.html");
const GURL kPrerenderingUrl =
GetUrl("/prerender/session_storage.html?prerendering=");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
RenderProcessHostImpl* initial_process_host =
static_cast<RenderProcessHostImpl*>(current_frame_host()->GetProcess());
initial_process_host->IncrementWorkerRefCount();
AddPrerender(kPrerenderingUrl);
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ("initial", EvalJs(current_frame_host(),
"window.sessionKeysInPrerenderingchange")
.ExtractString());
EXPECT_EQ(
"activated, initial",
EvalJs(current_frame_host(), "getSessionStorageKeys()").ExtractString());
EXPECT_TRUE(ExecJs(shell()->web_contents(),
"new Promise(resolve => requestIdleCallback(resolve));"));
TestNavigationObserver observer(shell()->web_contents());
shell()->GoBackOrForward(-1);
observer.Wait();
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), kInitialUrl);
EXPECT_EQ(
"activated, initial",
EvalJs(current_frame_host(), "getSessionStorageKeys()").ExtractString());
}
IN_PROC_BROWSER_TEST_F(PrerenderTargetHintBrowserTest,
SessionStorage_TargetBlank_WithTargetHintBlank) {
const GURL kInitialUrl = GetUrl("/prerender/session_storage.html");
const GURL kPrerenderingUrl =
GetUrl("/prerender/session_storage.html?prerendering=");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = prerender_helper()->AddPrerender(
kPrerenderingUrl, std::nullopt, "_blank");
auto* prerender_web_contents =
static_cast<WebContentsImpl*>(WebContents::FromFrameTreeNodeId(host_id));
ExpectWebContentsIsForNewTabPrerendering(*prerender_web_contents);
auto* initiator_web_contents = web_contents_impl();
ASSERT_NE(prerender_web_contents, initiator_web_contents);
std::string prerender_session_storage_id_before_activation =
FrameTreeNode::GloballyFindByID(host_id)
->frame_tree()
.controller()
.GetSessionStorageNamespace(prerender_web_contents->GetSiteInstance()
->GetStoragePartitionConfig())
->id();
EXPECT_EQ(
"prerendering",
EvalJs(content::test::PrerenderTestHelper::GetPrerenderedMainFrameHost(
*prerender_web_contents, host_id),
"getSessionStorageKeys()")
.ExtractString());
TestNavigationObserver activation_observer(kPrerenderingUrl);
activation_observer.WatchExistingWebContents();
test::PrerenderHostObserver prerender_observer(*prerender_web_contents,
host_id);
const std::string kLinkClickScript = R"(
clickSameSiteNewWindowLink();
)";
EXPECT_TRUE(ExecJs(web_contents(), kLinkClickScript));
activation_observer.WaitForNavigationFinished();
EXPECT_EQ(prerender_web_contents->GetLastCommittedURL(), kPrerenderingUrl);
EXPECT_EQ(activation_observer.last_navigation_url(), kPrerenderingUrl);
EXPECT_TRUE(prerender_observer.was_activated());
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
EXPECT_EQ("initial", EvalJs(initiator_web_contents->GetPrimaryMainFrame(),
"getSessionStorageKeys()")
.ExtractString());
EXPECT_EQ("activated", EvalJs(prerender_web_contents->GetPrimaryMainFrame(),
"getSessionStorageKeys()")
.ExtractString());
EXPECT_EQ(
prerender_session_storage_id_before_activation,
prerender_web_contents->GetPrimaryFrameTree()
.controller()
.GetSessionStorageNamespace(prerender_web_contents->GetSiteInstance()
->GetStoragePartitionConfig())
->id());
EXPECT_NE(
prerender_session_storage_id_before_activation,
initiator_web_contents->GetPrimaryFrameTree()
.controller()
.GetSessionStorageNamespace(prerender_web_contents->GetSiteInstance()
->GetStoragePartitionConfig())
->id());
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, AbandonIfRendererProcessCrashes) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
{
test::PrerenderHostObserver host_observer(*web_contents_impl(), host_id);
RenderProcessHost* process =
GetPrerenderedMainFrameHost(host_id)->GetProcess();
ScopedAllowRendererCrashes allow_renderer_crashes(process);
process->ForceCrash();
host_observer.WaitForDestroyed();
}
ExpectFinalStatusForSpeculationRule(
#if BUILDFLAG(IS_ANDROID)
PrerenderFinalStatus::kRendererProcessKilled);
#else
PrerenderFinalStatus::kRendererProcessCrashed);
#endif
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, AbandonIfRendererProcessIsKilled) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
{
test::PrerenderHostObserver host_observer(*web_contents_impl(), host_id);
RenderProcessHost* process =
GetPrerenderedMainFrameHost(host_id)->GetProcess();
ScopedAllowRendererCrashes allow_renderer_crashes(process);
EXPECT_TRUE(process->Shutdown(0));
host_observer.WaitForDestroyed();
}
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kRendererProcessKilled);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
AbandonIfPrimaryMainFrameRendererProcessIsKilled) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
{
test::PrerenderHostObserver host_observer(*web_contents_impl(), host_id);
RenderProcessHost* process = current_frame_host()->GetProcess();
ScopedAllowRendererCrashes allow_renderer_crashes(process);
EXPECT_TRUE(process->Shutdown(0));
host_observer.WaitForDestroyed();
}
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kPrimaryMainFrameRendererProcessKilled);
}
class PrerenderBackForwardCacheBrowserTest : public PrerenderBrowserTest {
public:
PrerenderBackForwardCacheBrowserTest() {
feature_list_.InitWithFeaturesAndParameters(
{{::features::kBackForwardCache, {}},
{kBackForwardCacheNoTimeEviction, {}}},
{::features::kBackForwardCacheMemoryControls});
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(PrerenderBackForwardCacheBrowserTest,
SessionStorageAfterBackNavigation) {
const GURL kInitialUrl = GetUrl("/prerender/session_storage.html");
const GURL kPrerenderingUrl =
GetUrl("/prerender/session_storage.html?prerendering=");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
RenderFrameHostWrapper main_frame(
shell()->web_contents()->GetPrimaryMainFrame());
AddPrerender(kPrerenderingUrl);
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ("initial", EvalJs(current_frame_host(),
"window.sessionKeysInPrerenderingchange")
.ExtractString());
EXPECT_EQ(
"activated, initial",
EvalJs(current_frame_host(), "getSessionStorageKeys()").ExtractString());
EXPECT_TRUE(ExecJs(shell()->web_contents(),
"new Promise(resolve => requestIdleCallback(resolve));"));
shell()->GoBackOrForward(-1);
WaitForLoadStop(shell()->web_contents());
ASSERT_EQ(shell()->web_contents()->GetPrimaryMainFrame(), main_frame.get());
EXPECT_EQ(
"activated, initial",
EvalJs(current_frame_host(), "getSessionStorageKeys()").ExtractString());
}
#if !BUILDFLAG(IS_ANDROID)
class PrerenderRestartStorageServiceBrowserTest : public PrerenderBrowserTest {
public:
PrerenderRestartStorageServiceBrowserTest() = default;
protected:
void CrashStorageServiceAndWaitForRestart() {
mojo::Remote<storage::mojom::StorageService>& service =
StoragePartitionImpl::GetStorageService();
base::RunLoop loop;
service.set_disconnect_handler(base::BindLambdaForTesting([&] {
loop.Quit();
service.reset();
}));
mojo::Remote<storage::mojom::TestApi> test_api;
StoragePartitionImpl::GetStorageService()->BindTestApi(
test_api.BindNewPipeAndPassReceiver().PassPipe());
test_api->CrashNow();
loop.Run();
}
};
IN_PROC_BROWSER_TEST_F(PrerenderRestartStorageServiceBrowserTest,
RestartStorageServiceBeforePrerendering) {
const GURL kInitialUrl = GetUrl("/prerender/session_storage.html");
const GURL kPrerenderingUrl =
GetUrl("/prerender/session_storage.html?prerendering=");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
CrashStorageServiceAndWaitForRestart();
EXPECT_EQ(
"initial",
EvalJs(current_frame_host(), "getSessionStorageKeys()").ExtractString());
AddPrerender(kPrerenderingUrl);
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ("initial", EvalJs(current_frame_host(),
"window.sessionKeysInPrerenderingchange")
.ExtractString());
EXPECT_EQ(
"activated, initial",
EvalJs(current_frame_host(), "getSessionStorageKeys()").ExtractString());
}
IN_PROC_BROWSER_TEST_F(PrerenderRestartStorageServiceBrowserTest,
RestartStorageServiceWhilePrerendering) {
const GURL kInitialUrl = GetUrl("/prerender/session_storage.html");
const GURL kPrerenderingUrl =
GetUrl("/prerender/session_storage.html?prerendering=");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
CrashStorageServiceAndWaitForRestart();
EXPECT_EQ(
"initial",
EvalJs(current_frame_host(), "getSessionStorageKeys()").ExtractString());
EXPECT_EQ(
"initial, prerendering",
EvalJs(GetPrerenderedMainFrameHost(host_id), "getSessionStorageKeys()")
.ExtractString());
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ("initial", EvalJs(current_frame_host(),
"window.sessionKeysInPrerenderingchange")
.ExtractString());
EXPECT_EQ(
"activated, initial",
EvalJs(current_frame_host(), "getSessionStorageKeys()").ExtractString());
}
#endif
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, SpeculationRulesScript) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0);
AddPrerender(kPrerenderingUrl);
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl));
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
}
class PrerenderEagernessBrowserTest : public PrerenderBrowserTest {
public:
void SetUp() override {
#if !BUILDFLAG(IS_ANDROID)
sub_feature_list_.InitAndEnableFeatureWithParameters(
blink::features::kPreloadingEagerHoverHeuristics,
{{"hover_dwell_time", "50ms"}});
PrerenderBrowserTest::SetUp();
#else
GTEST_SKIP();
#endif
}
void TearDown() override {
PrerenderBrowserTest::TearDown();
sub_feature_list_.Reset();
}
private:
base::test::ScopedFeatureList sub_feature_list_;
};
namespace {
class PreloadingDeciderObserverForPrerenderTesting
: public PreloadingDeciderObserverForTesting {
public:
explicit PreloadingDeciderObserverForPrerenderTesting(
RenderFrameHostImpl& rfh)
: rfh_(rfh.GetWeakPtr()) {
auto* preloading_decider =
PreloadingDecider::GetOrCreateForCurrentDocument(rfh_.get());
old_observer_ = preloading_decider->SetObserverForTesting(this);
events_called_.fill(false);
}
~PreloadingDeciderObserverForPrerenderTesting() override {
if (!rfh_) {
old_observer_ = nullptr;
return;
}
auto* preloading_decider =
PreloadingDecider::GetOrCreateForCurrentDocument(rfh_.get());
EXPECT_EQ(this, preloading_decider->SetObserverForTesting(old_observer_));
}
void UpdateSpeculationCandidates(
const std::vector<blink::mojom::SpeculationCandidatePtr>& candidates)
override {
OnEventCalled(Events::kUpdateSpeculationCandidates);
}
void OnPointerHover(
const GURL& url,
blink::mojom::SpeculationEagerness target_eagerness) override {
OnEventCalled(Events::kOnPointerHover);
}
void OnPointerDown(const GURL& url) override {
OnEventCalled(Events::kOnPointerDown);
}
void WaitUpdateSpeculationCandidates() {
WaitEvent(Events::kUpdateSpeculationCandidates);
}
void WaitOnPointerHover() { WaitEvent(Events::kOnPointerHover); }
void WaitOnPointerDown() { WaitEvent(Events::kOnPointerDown); }
private:
enum Events {
kUpdateSpeculationCandidates = 0,
kOnPointerHover,
kOnPointerDown,
kMaxValue = kOnPointerDown,
};
void WaitEvent(Events event) {
if (events_called_[event]) {
return;
}
ASSERT_FALSE(quit_closures_[event]);
base::RunLoop loop;
quit_closures_[event] = loop.QuitClosure();
loop.Run();
}
void OnEventCalled(Events event) {
events_called_[event] = true;
if (quit_closures_[event]) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(quit_closures_[event]));
}
}
base::WeakPtr<RenderFrameHostImpl> rfh_;
raw_ptr<PreloadingDeciderObserverForTesting> old_observer_;
std::array<base::OnceClosure, Events::kMaxValue + 1> quit_closures_;
std::array<bool, Events::kMaxValue + 1> events_called_;
};
}
IN_PROC_BROWSER_TEST_F(PrerenderEagernessBrowserTest, kImmediate) {
const GURL initial_url = GetUrl("/empty.html");
const GURL prerendering_url = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
InsertAnchor(prerendering_url);
RenderFrameHostImpl* rfh = current_frame_host();
auto* preloading_decider =
PreloadingDecider::GetOrCreateForCurrentDocument(rfh);
AddPrerenderWithEagernessAsync(
prerendering_url, blink::mojom::SpeculationEagerness::kImmediate);
WaitForPrerenderLoadCompletion(prerendering_url);
EXPECT_TRUE(HasHostForUrl(prerendering_url));
EXPECT_FALSE(preloading_decider->IsOnStandByForTesting(
prerendering_url, blink::mojom::SpeculationAction::kPrerender));
FrameTreeNodeId host_id = GetHostForUrl(prerendering_url);
test::PrerenderHostObserver prerender_observer(*web_contents(), host_id);
PointerDownToAnchor(prerendering_url);
PointerUpToAnchor(prerendering_url);
prerender_observer.WaitForActivation();
EXPECT_EQ(web_contents()->GetLastCommittedURL(), prerendering_url);
EXPECT_TRUE(prerender_observer.was_activated());
}
IN_PROC_BROWSER_TEST_F(PrerenderEagernessBrowserTest, kEager) {
const GURL initial_url = GetUrl("/empty.html");
const GURL prerendering_url = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
InsertAnchor(prerendering_url);
if (base::FeatureList::IsEnabled(
blink::features::kPreloadingEagerHoverHeuristics)) {
const base::TimeDelta moderate_hover_time = base::Milliseconds(200);
const base::TimeDelta eager_hover_time =
blink::features::kPreloadingEagerHoverHeuristicsDwellTime.Get();
ASSERT_LE(eager_hover_time, moderate_hover_time);
const base::TimeDelta mid_hover_time =
eager_hover_time + (moderate_hover_time - eager_hover_time) / 10;
RenderFrameHostImpl* rfh = current_frame_host();
ASSERT_TRUE(rfh);
PreloadingDeciderObserverForPrerenderTesting preloading_decider_observer(
*rfh);
auto* preloading_decider =
PreloadingDecider::GetOrCreateForCurrentDocument(rfh);
AddPrerenderWithEagernessAsync(prerendering_url,
blink::mojom::SpeculationEagerness::kEager);
preloading_decider_observer.WaitUpdateSpeculationCandidates();
EXPECT_FALSE(HasHostForUrl(prerendering_url));
EXPECT_TRUE(preloading_decider->IsOnStandByForTesting(
prerendering_url, blink::mojom::SpeculationAction::kPrerender));
#if !BUILDFLAG(IS_MAC)
ASSERT_GT(eager_hover_time, base::Milliseconds(0));
PointerHoverToAnchor(prerendering_url, eager_hover_time / 10);
EXPECT_TRUE(preloading_decider->IsOnStandByForTesting(
prerendering_url, blink::mojom::SpeculationAction::kPrerender));
#endif
PointerHoverToAnchor(prerendering_url, mid_hover_time);
preloading_decider_observer.WaitOnPointerHover();
WaitForPrerenderLoadCompletion(prerendering_url);
EXPECT_TRUE(HasHostForUrl(prerendering_url));
EXPECT_FALSE(preloading_decider->IsOnStandByForTesting(
prerendering_url, blink::mojom::SpeculationAction::kPrerender));
} else {
RenderFrameHostImpl* rfh = current_frame_host();
auto* preloading_decider =
PreloadingDecider::GetOrCreateForCurrentDocument(rfh);
AddPrerenderWithEagernessAsync(prerendering_url,
blink::mojom::SpeculationEagerness::kEager);
WaitForPrerenderLoadCompletion(prerendering_url);
EXPECT_TRUE(HasHostForUrl(prerendering_url));
EXPECT_FALSE(preloading_decider->IsOnStandByForTesting(
prerendering_url, blink::mojom::SpeculationAction::kPrerender));
}
FrameTreeNodeId host_id = GetHostForUrl(prerendering_url);
test::PrerenderHostObserver prerender_observer(*web_contents(), host_id);
PointerDownToAnchor(prerendering_url);
PointerUpToAnchor(prerendering_url);
prerender_observer.WaitForActivation();
EXPECT_EQ(web_contents()->GetLastCommittedURL(), prerendering_url);
EXPECT_TRUE(prerender_observer.was_activated());
}
IN_PROC_BROWSER_TEST_F(PrerenderEagernessBrowserTest, kModerate) {
const GURL initial_url = GetUrl("/empty.html");
const GURL prerendering_url = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
InsertAnchor(prerendering_url);
RenderFrameHostImpl* rfh = current_frame_host();
ASSERT_TRUE(rfh);
PreloadingDeciderObserverForPrerenderTesting preloading_decider_observer(
*rfh);
auto* preloading_decider =
PreloadingDecider::GetOrCreateForCurrentDocument(rfh);
AddPrerenderWithEagernessAsync(prerendering_url,
blink::mojom::SpeculationEagerness::kModerate);
preloading_decider_observer.WaitUpdateSpeculationCandidates();
EXPECT_FALSE(HasHostForUrl(prerendering_url));
EXPECT_TRUE(preloading_decider->IsOnStandByForTesting(
prerendering_url, blink::mojom::SpeculationAction::kPrerender));
if (base::FeatureList::IsEnabled(
blink::features::kPreloadingEagerHoverHeuristics)) {
const base::TimeDelta moderate_hover_time = base::Milliseconds(200);
const base::TimeDelta eager_hover_time =
blink::features::kPreloadingEagerHoverHeuristicsDwellTime.Get();
const base::TimeDelta mid_hover_time =
eager_hover_time + (moderate_hover_time - eager_hover_time) / 10;
ASSERT_LT(mid_hover_time, moderate_hover_time);
PointerHoverToAnchor(prerendering_url, mid_hover_time);
preloading_decider_observer.WaitOnPointerHover();
EXPECT_FALSE(HasHostForUrl(prerendering_url));
EXPECT_TRUE(preloading_decider->IsOnStandByForTesting(
prerendering_url, blink::mojom::SpeculationAction::kPrerender));
}
PointerHoverToAnchor(prerendering_url);
preloading_decider_observer.WaitOnPointerHover();
WaitForPrerenderLoadCompletion(prerendering_url);
EXPECT_TRUE(HasHostForUrl(prerendering_url));
EXPECT_FALSE(preloading_decider->IsOnStandByForTesting(
prerendering_url, blink::mojom::SpeculationAction::kPrerender));
FrameTreeNodeId host_id = GetHostForUrl(prerendering_url);
test::PrerenderHostObserver prerender_observer(*web_contents(), host_id);
PointerDownToAnchor(prerendering_url);
PointerUpToAnchor(prerendering_url);
prerender_observer.WaitForActivation();
EXPECT_EQ(web_contents()->GetLastCommittedURL(), prerendering_url);
EXPECT_TRUE(prerender_observer.was_activated());
}
IN_PROC_BROWSER_TEST_F(PrerenderEagernessBrowserTest, kConservative) {
const GURL initial_url = GetUrl("/empty.html");
const GURL prerendering_url = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
InsertAnchor(prerendering_url);
RenderFrameHostImpl* rfh = current_frame_host();
ASSERT_TRUE(rfh);
PreloadingDeciderObserverForPrerenderTesting preloading_decider_observer(
*rfh);
auto* preloading_decider =
PreloadingDecider::GetOrCreateForCurrentDocument(rfh);
AddPrerenderWithEagernessAsync(
prerendering_url, blink::mojom::SpeculationEagerness::kConservative);
preloading_decider_observer.WaitUpdateSpeculationCandidates();
EXPECT_FALSE(HasHostForUrl(prerendering_url));
EXPECT_TRUE(preloading_decider->IsOnStandByForTesting(
prerendering_url, blink::mojom::SpeculationAction::kPrerender));
PointerDownToAnchor(prerendering_url);
preloading_decider_observer.WaitOnPointerDown();
WaitForPrerenderLoadCompletion(prerendering_url);
EXPECT_TRUE(HasHostForUrl(prerendering_url));
EXPECT_FALSE(preloading_decider->IsOnStandByForTesting(
prerendering_url, blink::mojom::SpeculationAction::kPrerender));
FrameTreeNodeId host_id = GetHostForUrl(prerendering_url);
test::PrerenderHostObserver prerender_observer(*web_contents(), host_id);
PointerUpToAnchor(prerendering_url);
prerender_observer.WaitForActivation();
EXPECT_EQ(web_contents()->GetLastCommittedURL(), prerendering_url);
EXPECT_TRUE(prerender_observer.was_activated());
}
#if !BUILDFLAG(IS_FUCHSIA) && !BUILDFLAG(IS_IOS)
IN_PROC_BROWSER_TEST_F(PrerenderEagernessBrowserTest,
ReceivedPrerendersPerPrimaryPageChangedCount) {
auto GetAllSamples = [&](const std::string& eagerness_category) {
return histogram_tester().GetAllSamples(
"Prerender.Experimental.ReceivedPrerendersPerPrimaryPageChangedCount2."
"SpeculationRule." +
eagerness_category);
};
const GURL initial_url = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
EXPECT_THAT(GetAllSamples("Total"), testing::IsEmpty());
const GURL prerendering_url = GetUrl("/empty.html?prerender");
AddPrerender(prerendering_url);
const GURL next_url = GetUrl("/empty.html?next");
ASSERT_TRUE(NavigateToURL(shell(), next_url));
EXPECT_THAT(GetAllSamples("Conservative"),
base::BucketsAre(base::Bucket(0, 1)));
EXPECT_THAT(GetAllSamples("Moderate"), base::BucketsAre(base::Bucket(0, 1)));
EXPECT_THAT(GetAllSamples("Eager2"), base::BucketsAre(base::Bucket(0, 1)));
EXPECT_THAT(GetAllSamples("Immediate2"),
base::BucketsAre(base::Bucket(1, 1)));
for (int i = 0; i < 4; ++i) {
GURL prerendering_url_immediate =
GetUrl("/empty.html?prerender_immediate_" + base::NumberToString(i));
AddPrerender(prerendering_url_immediate);
}
for (int i = 0; i < 2; ++i) {
GURL prerendering_url_eager =
GetUrl("/empty.html?prerender_eager_" + base::NumberToString(i));
if (base::FeatureList::IsEnabled(
blink::features::kPreloadingEagerHoverHeuristics)) {
InsertAnchor(prerendering_url_eager);
AddPrerenderWithEagernessAsync(
prerendering_url_eager, blink::mojom::SpeculationEagerness::kEager);
PointerHoverToAnchor(prerendering_url_eager);
WaitForPrerenderLoadCompletion(prerendering_url_eager);
} else {
AddPrerender(prerendering_url_eager);
}
}
for (int i = 0; i < 2; ++i) {
GURL prerendering_url_moderate =
GetUrl("/empty.html?prerender_moderate_" + base::NumberToString(i));
InsertAnchor(prerendering_url_moderate);
AddPrerenderWithEagernessAsync(
prerendering_url_moderate,
blink::mojom::SpeculationEagerness::kModerate);
PointerHoverToAnchor(prerendering_url_moderate);
WaitForPrerenderLoadCompletion(prerendering_url_moderate);
}
const GURL prerendering_url_conservative =
GetUrl("/empty.html?prerender_conservative");
InsertAnchor(prerendering_url_conservative);
AddPrerenderWithEagernessAsync(
prerendering_url_conservative,
blink::mojom::SpeculationEagerness::kConservative);
TestActivationManager activation_manager(web_contents(),
prerendering_url_conservative);
ClickAnchor(prerendering_url_conservative);
activation_manager.WaitForNavigationFinished();
ASSERT_EQ(web_contents()->GetLastCommittedURL(),
prerendering_url_conservative);
ASSERT_TRUE(activation_manager.was_activated());
EXPECT_THAT(GetAllSamples("Immediate2"),
base::BucketsAre(base::Bucket(1, 1), base::Bucket(4, 1)));
EXPECT_THAT(GetAllSamples("Eager2"),
base::BucketsAre(base::Bucket(0, 1), base::Bucket(2, 1)));
EXPECT_THAT(GetAllSamples("Moderate"),
base::BucketsAre(base::Bucket(0, 1), base::Bucket(2, 1)));
EXPECT_THAT(GetAllSamples("Conservative"),
base::BucketsAre(base::Bucket(0, 1), base::Bucket(1, 1)));
}
IN_PROC_BROWSER_TEST_F(PrerenderEagernessBrowserTest,
NonImmediatePrerendersCanBeRetriggeredAfterTimeout) {
const GURL initial_url = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
const GURL prerendering_url = GetUrl("/empty.html?prerender");
InsertAnchor(prerendering_url);
AddPrerenderWithEagernessAsync(prerendering_url,
blink::mojom::SpeculationEagerness::kModerate);
test::PrerenderHostCreationWaiter host_creation_waiter_a;
PointerHoverToAnchor(prerendering_url);
FrameTreeNodeId host_id_a = host_creation_waiter_a.Wait();
test::PrerenderHostObserver prerender_observer_a(*web_contents_impl(),
host_id_a);
PrerenderHostRegistry* prerender_host_registry =
web_contents_impl()->GetPrerenderHostRegistry();
ASSERT_FALSE(prerender_host_registry->GetSpeculationRulesTimerForTesting()
->IsRunning());
auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
prerender_host_registry->SetTaskRunnerForTesting(task_runner);
web_contents()->WasHidden();
ASSERT_TRUE(prerender_host_registry->GetSpeculationRulesTimerForTesting()
->IsRunning());
task_runner->FastForwardBy(
PrerenderHostRegistry::kTimeToLiveInBackgroundForSpeculationRules);
ASSERT_FALSE(prerender_host_registry->GetSpeculationRulesTimerForTesting()
->IsRunning());
prerender_observer_a.WaitForDestroyed();
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kTimeoutBackgrounded, 1);
web_contents()->WasShown();
test::PrerenderHostCreationWaiter host_creation_waiter_b;
PointerHoverToAnchor(prerendering_url);
FrameTreeNodeId host_id_b = host_creation_waiter_b.Wait();
test::PrerenderHostObserver prerender_observer_b(*web_contents(), host_id_b);
NavigatePrimaryPage(prerendering_url);
EXPECT_EQ(web_contents()->GetLastCommittedURL(), prerendering_url);
EXPECT_TRUE(prerender_observer_b.was_activated());
}
IN_PROC_BROWSER_TEST_P(PrerenderTargetAgnosticBrowserTest,
ResetForNonImmediatePrerender) {
#if !BUILDFLAG(IS_ANDROID)
const GURL initial_url = GetUrl("/empty.html");
std::vector<GURL> prerendering_urls;
std::vector<base::WeakPtr<WebContents>> prerender_web_contents_list;
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
int num_of_attempts =
PrerenderHostRegistry::kMaxRunningSpeculationRulesNonImmediatePrerenders +
1;
for (int i = 0; i < num_of_attempts; i++) {
ASSERT_TRUE(current_frame_host());
PreloadingDeciderObserverForPrerenderTesting preloading_decider_observer(
*current_frame_host());
const GURL prerendering_url =
GetUrl("/empty.html?prerender" + base::ToString(i));
prerendering_urls.push_back(prerendering_url);
InsertAnchor(prerendering_url);
AddPrerendersAsync({prerendering_url},
blink::mojom::SpeculationEagerness::kModerate,
GetTargetHint());
preloading_decider_observer.WaitUpdateSpeculationCandidates();
test::PrerenderHostCreationWaiter host_creation_waiter;
PointerHoverToAnchor(prerendering_url);
FrameTreeNodeId host_id = host_creation_waiter.Wait();
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
prerender_web_contents_list.push_back(prerender_web_contents->GetWeakPtr());
test::PrerenderTestHelper::WaitForPrerenderLoadCompletion(
*prerender_web_contents, prerendering_url);
}
for (int i = 0; i < num_of_attempts; i++) {
bool host_existing_in_registry =
prerender_web_contents_list[i] &&
HasHostForUrl(*prerender_web_contents_list[i], prerendering_urls[i]);
if (i == 0) {
ASSERT_FALSE(host_existing_in_registry);
} else {
ASSERT_TRUE(host_existing_in_registry);
}
}
const auto& prerendering_url_first = prerendering_urls[0];
test::PrerenderHostCreationWaiter host_creation_waiter;
PointerHoverToAnchor(prerendering_url_first);
FrameTreeNodeId host_id = host_creation_waiter.Wait();
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
prerender_web_contents_list[0] = prerender_web_contents->GetWeakPtr();
test::PrerenderTestHelper::WaitForPrerenderLoadCompletion(
*prerender_web_contents, prerendering_url_first);
for (int i = 0; i < num_of_attempts; i++) {
bool host_existing_in_registry =
prerender_web_contents_list[i] &&
HasHostForUrl(*prerender_web_contents_list[i], prerendering_urls[i]);
if (i == 1) {
EXPECT_FALSE(host_existing_in_registry);
} else {
EXPECT_TRUE(host_existing_in_registry);
}
}
#else
GTEST_SKIP();
#endif
}
#endif
class PrerenderWithBackForwardCacheBrowserTest
: public PrerenderBrowserTest,
public testing::WithParamInterface<BackForwardCacheType> {
public:
PrerenderWithBackForwardCacheBrowserTest() {
switch (GetParam()) {
case BackForwardCacheType::kDisabled:
feature_list_.InitAndDisableFeature(::features::kBackForwardCache);
break;
case BackForwardCacheType::kEnabled:
feature_list_.InitWithFeaturesAndParameters(
GetDefaultEnabledBackForwardCacheFeaturesForTesting(
false),
GetDefaultDisabledBackForwardCacheFeaturesForTesting());
break;
}
}
private:
base::test::ScopedFeatureList feature_list_;
};
INSTANTIATE_TEST_SUITE_P(All,
PrerenderWithBackForwardCacheBrowserTest,
testing::Values(BackForwardCacheType::kDisabled,
BackForwardCacheType::kEnabled),
ToString);
IN_PROC_BROWSER_TEST_P(PrerenderWithBackForwardCacheBrowserTest,
HistoryNavigationAfterActivation) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
RenderFrameHostImpl* initial_frame_host = current_frame_host();
blink::LocalFrameToken initial_frame_token =
initial_frame_host->GetFrameToken();
RenderFrameDeletedObserver delete_observer(initial_frame_host);
AddPrerender(kPrerenderingUrl);
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
switch (GetParam()) {
case BackForwardCacheType::kDisabled:
EXPECT_NE(current_frame_host(), initial_frame_host);
delete_observer.WaitUntilDeleted();
break;
case BackForwardCacheType::kEnabled:
ASSERT_TRUE(IsBackForwardCacheEnabled());
EXPECT_TRUE(initial_frame_host->IsInBackForwardCache());
break;
}
TestNavigationObserver observer(web_contents());
shell()->GoBackOrForward(-1);
observer.Wait();
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
switch (GetParam()) {
case BackForwardCacheType::kDisabled:
EXPECT_NE(current_frame_host()->GetFrameToken(), initial_frame_token);
break;
case BackForwardCacheType::kEnabled:
EXPECT_EQ(current_frame_host()->GetFrameToken(), initial_frame_token);
EXPECT_FALSE(initial_frame_host->IsInBackForwardCache());
break;
}
}
IN_PROC_BROWSER_TEST_P(PrerenderWithBackForwardCacheBrowserTest,
CancelOnAfterTriggerIsStoredInBackForwardCache_Forward) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kNextUrl = GetUrl("/empty.html?next");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
RenderFrameHostImplWrapper initial_frame_host(current_frame_host());
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
test::PrerenderHostObserver prerender_observer(*web_contents_impl(), host_id);
ASSERT_TRUE(NavigateToURL(shell(), kNextUrl));
switch (GetParam()) {
case BackForwardCacheType::kDisabled:
if (ShouldCreateNewHostForAllFrames()) {
ASSERT_TRUE(initial_frame_host.WaitUntilRenderFrameDeleted());
} else {
ASSERT_FALSE(initial_frame_host->IsInBackForwardCache());
}
break;
case BackForwardCacheType::kEnabled:
ASSERT_TRUE(IsBackForwardCacheEnabled());
ASSERT_TRUE(initial_frame_host->IsInBackForwardCache());
break;
}
prerender_observer.WaitForDestroyed();
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kTriggerDestroyed);
}
IN_PROC_BROWSER_TEST_P(PrerenderWithBackForwardCacheBrowserTest,
CancelOnAfterTriggerIsStoredInBackForwardCache_Back) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kNextUrl = GetUrl("/empty.html?next");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_TRUE(NavigateToURL(shell(), kNextUrl));
RenderFrameHostImplWrapper next_frame_host(current_frame_host());
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
test::PrerenderHostObserver prerender_observer(*web_contents_impl(), host_id);
TestNavigationObserver navigation_observer(web_contents());
shell()->GoBackOrForward(-1);
navigation_observer.Wait();
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
switch (GetParam()) {
case BackForwardCacheType::kDisabled:
if (ShouldCreateNewHostForAllFrames()) {
ASSERT_TRUE(next_frame_host.WaitUntilRenderFrameDeleted());
} else {
ASSERT_FALSE(next_frame_host->IsInBackForwardCache());
}
break;
case BackForwardCacheType::kEnabled:
ASSERT_TRUE(IsBackForwardCacheEnabled());
ASSERT_TRUE(next_frame_host->IsInBackForwardCache());
break;
}
prerender_observer.WaitForDestroyed();
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kTriggerDestroyed);
}
class PrerenderBackForwardCacheRestorationBrowserTest
: public PrerenderEagernessBrowserTest,
public BackForwardCacheMetricsTestMatcher,
public testing::WithParamInterface<blink::mojom::SpeculationEagerness> {
public:
PrerenderBackForwardCacheRestorationBrowserTest() {
feature_list_.InitWithFeaturesAndParameters(
GetDefaultEnabledBackForwardCacheFeaturesForTesting(
false),
GetDefaultDisabledBackForwardCacheFeaturesForTesting());
}
protected:
blink::mojom::SpeculationEagerness GetSpeculationEagerness() const {
return GetParam();
}
const ukm::TestAutoSetUkmRecorder& ukm_recorder() override {
return *PrerenderBrowserTest::test_ukm_recorder();
}
const base::HistogramTester& histogram_tester() override {
return PrerenderBrowserTest::histogram_tester();
}
private:
base::test::ScopedFeatureList feature_list_;
};
INSTANTIATE_TEST_SUITE_P(
All,
PrerenderBackForwardCacheRestorationBrowserTest,
testing::Values(blink::mojom::SpeculationEagerness::kImmediate,
blink::mojom::SpeculationEagerness::kEager,
blink::mojom::SpeculationEagerness::kModerate,
blink::mojom::SpeculationEagerness::kConservative),
[](const testing::TestParamInfo<blink::mojom::SpeculationEagerness>& info) {
return base::ToString(info.param);
});
IN_PROC_BROWSER_TEST_P(PrerenderBackForwardCacheRestorationBrowserTest,
RestoredViaForwardNavigation) {
const GURL initial_url = GetUrl("/empty.html");
const GURL next_url = GetUrl("/empty.html?next");
const GURL prerendering_url = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
ASSERT_TRUE(NavigateToURL(shell(), next_url));
RenderFrameHostImpl* rfh_next = current_frame_host();
InsertAnchor(prerendering_url);
ASSERT_TRUE(rfh_next);
PreloadingDeciderObserverForPrerenderTesting preloading_decider_observer(
*rfh_next);
auto* preloading_decider =
PreloadingDecider::GetOrCreateForCurrentDocument(rfh_next);
AddPrerenderWithEagernessAsync(prerendering_url, GetSpeculationEagerness());
if (IsImmediateSpeculationEagerness(GetSpeculationEagerness())) {
WaitForPrerenderLoadCompletion(prerendering_url);
ASSERT_TRUE(HasHostForUrl(prerendering_url));
} else {
preloading_decider_observer.WaitUpdateSpeculationCandidates();
ASSERT_TRUE(preloading_decider->IsOnStandByForTesting(
prerendering_url, blink::mojom::SpeculationAction::kPrerender));
}
GoBack();
ASSERT_EQ(web_contents()->GetLastCommittedURL(), initial_url);
ExpectRestored(FROM_HERE);
ASSERT_TRUE(rfh_next->IsInBackForwardCache());
GoForward();
ASSERT_EQ(web_contents()->GetLastCommittedURL(), next_url);
ExpectRestored(FROM_HERE);
if (IsImmediateSpeculationEagerness(GetSpeculationEagerness())) {
WaitForPrerenderLoadCompletion(prerendering_url);
FrameTreeNodeId host_id_retriggered = GetHostForUrl(prerendering_url);
test::PrerenderHostObserver prerender_observer(*web_contents(),
host_id_retriggered);
NavigatePrimaryPage(prerendering_url);
prerender_observer.WaitForActivation();
ASSERT_EQ(web_contents()->GetLastCommittedURL(), prerendering_url);
EXPECT_TRUE(prerender_observer.was_activated());
} else {
ASSERT_FALSE(HasHostForUrl(prerendering_url));
EXPECT_TRUE(preloading_decider->IsOnStandByForTesting(
prerendering_url, blink::mojom::SpeculationAction::kPrerender));
{
TestActivationManager activation_manager(web_contents(),
prerendering_url);
ClickAnchor(prerendering_url);
activation_manager.WaitForNavigationFinished();
ASSERT_EQ(web_contents()->GetLastCommittedURL(), prerendering_url);
ASSERT_TRUE(activation_manager.was_activated());
}
}
}
IN_PROC_BROWSER_TEST_P(PrerenderBackForwardCacheRestorationBrowserTest,
RestoredViaBackwardNavigation) {
const GURL initial_url = GetUrl("/empty.html");
const GURL prerendering_url_a = GetUrl("/empty.html?prerender_a");
const GURL prerendering_url_b = GetUrl("/empty.html?prerender_b");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
RenderFrameHostImpl* rfh_initial = current_frame_host();
InsertAnchor(prerendering_url_a);
InsertAnchor(prerendering_url_b);
if (IsImmediateSpeculationEagerness(GetSpeculationEagerness())) {
AddPrerenderWithEagernessAsync(prerendering_url_a,
GetSpeculationEagerness());
AddPrerenderWithEagernessAsync(prerendering_url_b,
GetSpeculationEagerness());
WaitForPrerenderLoadCompletion(prerendering_url_a);
WaitForPrerenderLoadCompletion(prerendering_url_b);
FrameTreeNodeId host_id_a = GetHostForUrl(prerendering_url_a);
test::PrerenderHostObserver prerender_observer_a(*web_contents(),
host_id_a);
NavigatePrimaryPage(prerendering_url_a);
prerender_observer_a.WaitForActivation();
ASSERT_EQ(web_contents()->GetLastCommittedURL(), prerendering_url_a);
ASSERT_TRUE(prerender_observer_a.was_activated());
ASSERT_TRUE(rfh_initial->IsInBackForwardCache());
GoBack();
ASSERT_EQ(web_contents()->GetLastCommittedURL(), initial_url);
ExpectRestored(FROM_HERE);
WaitForPrerenderLoadCompletion(prerendering_url_a);
WaitForPrerenderLoadCompletion(prerendering_url_b);
FrameTreeNodeId host_id_a_retriggered = GetHostForUrl(prerendering_url_a);
test::PrerenderHostObserver prerender_observer_a_retriggered(
*web_contents(), host_id_a_retriggered);
NavigatePrimaryPage(prerendering_url_a);
prerender_observer_a_retriggered.WaitForActivation();
ASSERT_EQ(web_contents()->GetLastCommittedURL(), prerendering_url_a);
EXPECT_TRUE(prerender_observer_a_retriggered.was_activated());
} else {
auto* preloading_decider =
PreloadingDecider::GetOrCreateForCurrentDocument(rfh_initial);
{
ASSERT_TRUE(rfh_initial);
PreloadingDeciderObserverForPrerenderTesting preloading_decider_observer(
*rfh_initial);
AddPrerenderWithEagernessAsync(prerendering_url_a,
GetSpeculationEagerness());
preloading_decider_observer.WaitUpdateSpeculationCandidates();
}
{
ASSERT_TRUE(rfh_initial);
PreloadingDeciderObserverForPrerenderTesting preloading_decider_observer(
*rfh_initial);
AddPrerenderWithEagernessAsync(prerendering_url_b,
GetSpeculationEagerness());
preloading_decider_observer.WaitUpdateSpeculationCandidates();
}
ASSERT_TRUE(preloading_decider->IsOnStandByForTesting(
prerendering_url_a, blink::mojom::SpeculationAction::kPrerender));
ASSERT_TRUE(preloading_decider->IsOnStandByForTesting(
prerendering_url_b, blink::mojom::SpeculationAction::kPrerender));
{
TestActivationManager activation_manager(web_contents(),
prerendering_url_a);
ClickAnchor(prerendering_url_a);
activation_manager.WaitForNavigationFinished();
ASSERT_EQ(web_contents()->GetLastCommittedURL(), prerendering_url_a);
ASSERT_TRUE(activation_manager.was_activated());
ASSERT_TRUE(rfh_initial->IsInBackForwardCache());
}
GoBack();
ASSERT_EQ(web_contents()->GetLastCommittedURL(), initial_url);
ExpectRestored(FROM_HERE);
EXPECT_TRUE(preloading_decider->IsOnStandByForTesting(
prerendering_url_b, blink::mojom::SpeculationAction::kPrerender));
{
WaitForPrerenderLoadCompletion(prerendering_url_a);
EXPECT_TRUE(HasHostForUrl(prerendering_url_a));
EXPECT_FALSE(preloading_decider->IsOnStandByForTesting(
prerendering_url_a, blink::mojom::SpeculationAction::kPrerender));
}
ASSERT_FALSE(HasHostForUrl(prerendering_url_b));
{
TestActivationManager activation_manager(web_contents(),
prerendering_url_a);
ClickAnchor(prerendering_url_a);
activation_manager.WaitForNavigationFinished();
ASSERT_EQ(web_contents()->GetLastCommittedURL(), prerendering_url_a);
ASSERT_TRUE(activation_manager.was_activated());
}
}
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, StartByEmbeddersMultipleTimes) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kFirstPrerenderingUrl = GetUrl("/empty.html?prerender1");
const GURL kSecondPrerenderingUrl = GetUrl("/empty.html?prerender2");
const GURL kThirdPrerenderingUrl = GetUrl("/empty.html?prerender3");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
std::unique_ptr<PrerenderHandle> prerender_handle1 =
AddEmbedderTriggeredPrerenderAsync(kFirstPrerenderingUrl);
EXPECT_TRUE(prerender_handle1);
histogram_tester().ExpectBucketCount(
"Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
"EmbedderSuffixForTest",
PrerenderFinalStatus::kMaxNumOfRunningImmediatePrerendersExceeded, 0);
std::unique_ptr<PrerenderHandle> prerender_handle2 =
AddEmbedderTriggeredPrerenderAsync(kSecondPrerenderingUrl);
EXPECT_TRUE(prerender_handle2);
histogram_tester().ExpectBucketCount(
"Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
"EmbedderSuffixForTest",
PrerenderFinalStatus::kMaxNumOfRunningEmbedderPrerendersExceeded, 0);
std::unique_ptr<PrerenderHandle> prerender_handle3 =
AddEmbedderTriggeredPrerenderAsync(kThirdPrerenderingUrl);
EXPECT_FALSE(prerender_handle3);
histogram_tester().ExpectBucketCount(
"Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
"EmbedderSuffixForTest",
PrerenderFinalStatus::kMaxNumOfRunningEmbedderPrerendersExceeded, 1);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
StartByEmbeddersAndSpeculationRulesMultipleTimes) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kSpeculationRulesPrerenderingUrl =
GetUrl("/empty.html?prerender1");
const GURL kEmbedderPrerenderingUrl1 = GetUrl("/empty.html?prerender2");
const GURL kEmbedderPrerenderingUrl2 = GetUrl("/empty.html?prerender3");
const GURL kEmbedderPrerenderingUrl3 = GetUrl("/empty.html?prerender4");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
AddPrerender(kSpeculationRulesPrerenderingUrl);
histogram_tester().ExpectBucketCount(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kMaxNumOfRunningImmediatePrerendersExceeded, 0);
histogram_tester().ExpectBucketCount(
"Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
"EmbedderSuffixForTest",
PrerenderFinalStatus::kMaxNumOfRunningEmbedderPrerendersExceeded, 0);
std::unique_ptr<PrerenderHandle> prerender_handle1 =
AddEmbedderTriggeredPrerenderAsync(kEmbedderPrerenderingUrl1);
EXPECT_TRUE(prerender_handle1);
std::unique_ptr<PrerenderHandle> prerender_handle2 =
AddEmbedderTriggeredPrerenderAsync(kEmbedderPrerenderingUrl2);
EXPECT_TRUE(prerender_handle2);
histogram_tester().ExpectBucketCount(
"Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
"EmbedderSuffixForTest",
PrerenderFinalStatus::kMaxNumOfRunningEmbedderPrerendersExceeded, 0);
std::unique_ptr<PrerenderHandle> prerender_handle3 =
AddEmbedderTriggeredPrerenderAsync(kEmbedderPrerenderingUrl3);
EXPECT_FALSE(prerender_handle3);
histogram_tester().ExpectBucketCount(
"Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
"EmbedderSuffixForTest",
PrerenderFinalStatus::kMaxNumOfRunningEmbedderPrerendersExceeded, 1);
prerender_handle2.reset();
prerender_handle3 =
AddEmbedderTriggeredPrerenderAsync(kEmbedderPrerenderingUrl3);
EXPECT_TRUE(prerender_handle3);
}
class MultiplePrerendersBrowserTest : public PrerenderBrowserTest {
public:
MultiplePrerendersBrowserTest() {
feature_list_.InitWithFeaturesAndParameters(
{{blink::features::kPrerender2MemoryControls,
{{"acceptable_percent_of_system_memory", "100"},
{"memory_threshold_in_mb", "0"}}}},
{});
}
private:
base::test::ScopedFeatureList feature_list_;
};
class MultiplePrerendersWithLimitedMemoryBrowserTest
: public MultiplePrerendersBrowserTest {
public:
MultiplePrerendersWithLimitedMemoryBrowserTest() {
feature_list_.InitWithFeaturesAndParameters(
{{blink::features::kPrerender2MemoryControls,
{{"acceptable_percent_of_system_memory", "0"},
{"memory_threshold_in_mb", "0"}}}},
{});
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(MultiplePrerendersBrowserTest,
MemoryPressureOnTrigger_Moderate) {
GURL initial_url = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
FakeMemoryPressureMonitor memory_pressure_monitor(
base::MEMORY_PRESSURE_LEVEL_MODERATE);
ASSERT_EQ(base::MemoryPressureMonitor::Get()->GetCurrentPressureLevel(
base::MemoryPressureMonitorTag::kTest),
base::MEMORY_PRESSURE_LEVEL_MODERATE);
GURL prerender_url = GetUrl("/empty.html?prerender");
AddPrerender(prerender_url);
EXPECT_TRUE(HasHostForUrl(prerender_url));
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kMemoryPressureOnTrigger, 0);
}
IN_PROC_BROWSER_TEST_F(MultiplePrerendersBrowserTest,
MemoryPressureOnTrigger_Critical) {
GURL initial_url = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
FakeMemoryPressureMonitor memory_pressure_monitor(
base::MEMORY_PRESSURE_LEVEL_CRITICAL);
ASSERT_EQ(base::MemoryPressureMonitor::Get()->GetCurrentPressureLevel(
base::MemoryPressureMonitorTag::kTest),
base::MEMORY_PRESSURE_LEVEL_CRITICAL);
GURL prerender_url = GetUrl("/empty.html?prerender");
test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl());
AddPrerenderAsync(prerender_url);
registry_observer.WaitForTrigger(prerender_url);
EXPECT_FALSE(HasHostForUrl(prerender_url));
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kMemoryPressureOnTrigger, 1);
}
IN_PROC_BROWSER_TEST_F(MultiplePrerendersBrowserTest,
MemoryPressureAfterTriggered_Moderate) {
GURL initial_url = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
std::vector<GURL> prerender_urls = {
GetUrl("/empty.html?prerender0"),
GetUrl("/empty.html?prerender1"),
GetUrl("/empty.html?prerender2"),
};
for (const GURL& prerender_url : prerender_urls) {
AddPrerender(prerender_url);
}
base::MemoryPressureListener::NotifyMemoryPressure(
base::MEMORY_PRESSURE_LEVEL_MODERATE);
base::RunLoop().RunUntilIdle();
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kMemoryPressureAfterTriggered, 0);
}
IN_PROC_BROWSER_TEST_F(MultiplePrerendersBrowserTest,
MemoryPressureAfterTriggered_Critical) {
GURL initial_url = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
std::vector<GURL> prerender_urls = {
GetUrl("/empty.html?prerender0"),
GetUrl("/empty.html?prerender1"),
GetUrl("/empty.html?prerender2"),
};
std::vector<std::unique_ptr<test::PrerenderHostObserver>> observers;
for (const GURL& prerender_url : prerender_urls) {
FrameTreeNodeId host_id = AddPrerender(prerender_url);
observers.push_back(std::make_unique<test::PrerenderHostObserver>(
*web_contents(), host_id));
}
base::MemoryPressureListener::NotifyMemoryPressure(
base::MEMORY_PRESSURE_LEVEL_CRITICAL);
for (auto& observer : observers) {
observer->WaitForDestroyed();
}
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kMemoryPressureAfterTriggered,
prerender_urls.size());
}
IN_PROC_BROWSER_TEST_F(MultiplePrerendersBrowserTest,
AddSpeculationRulesMultipleTimes) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
for (int i = 0;
i <
PrerenderHostRegistry::kMaxRunningSpeculationRulesImmediatePrerenders;
i++) {
GURL prerendering_url =
GetUrl("/empty.html?prerender" + base::NumberToString(i));
AddPrerender(prerendering_url);
}
test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl());
const GURL kExceededPrerenderingUrl =
GetUrl("/empty.html?exceeded-prerender");
AddPrerenderAsync(kExceededPrerenderingUrl);
registry_observer.WaitForTrigger(kExceededPrerenderingUrl);
EXPECT_FALSE(HasHostForUrl(kExceededPrerenderingUrl));
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kMaxNumOfRunningImmediatePrerendersExceeded);
const GURL kEmbedderTriggeredPrerenderingUrl =
GetUrl("/empty.html?embedder-triggered-prerender");
std::unique_ptr<PrerenderHandle> prerender_handle =
AddEmbedderTriggeredPrerenderAsync(kEmbedderTriggeredPrerenderingUrl);
EXPECT_TRUE(prerender_handle);
}
IN_PROC_BROWSER_TEST_F(MultiplePrerendersWithLimitedMemoryBrowserTest,
DevToolsOverride) {
GURL initial_url = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
scoped_refptr<DevToolsAgentHost> dev_tools_agent_host =
content::DevToolsAgentHost::GetOrCreateFor(web_contents());
ASSERT_TRUE(dev_tools_agent_host);
std::vector<GURL> urls = {
GetUrl("/empty.html?prerender0"),
GetUrl("/empty.html?prerender1"),
GetUrl("/empty.html?prerender2"),
};
for (const GURL& url : urls) {
AddPrerender(url);
}
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kMemoryLimitExceeded, 0);
NavigatePrimaryPage(urls[0]);
ASSERT_EQ(web_contents()->GetLastCommittedURL(), urls[0]);
histogram_tester().ExpectBucketCount(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kActivated, 1);
histogram_tester().ExpectBucketCount(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kOtherPrerenderedPageActivated, 2);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, SkipCrossSitePrerender) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetCrossSiteUrl("/empty.html?crossorigin");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl());
url::Origin initiator_origin = url::Origin::Create(kInitialUrl);
url::Origin prerender_origin = url::Origin::Create(kPrerenderingUrl);
AddPrerenderAsync(kPrerenderingUrl);
registry_observer.WaitForTrigger(kPrerenderingUrl);
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kCrossSiteNavigationInInitialNavigation);
ASSERT_TRUE(NavigateToURL(shell(), kPrerenderingUrl));
ExpectPreloadingAttemptUkm({attempt_ukm_entry_builder().BuildEntry(
PrimaryPageSourceId(), PreloadingType::kPrerender,
PreloadingEligibility::kCrossOrigin,
PreloadingHoldbackStatus::kUnspecified,
PreloadingTriggeringOutcome::kUnspecified,
PreloadingFailureReason::kUnspecified,
true,
std::nullopt,
blink::mojom::SpeculationEagerness::kImmediate)});
}
IN_PROC_BROWSER_TEST_F(
PrerenderBrowserTest,
SameSiteCrossOriginNavigationSpeculationRulesWithoutOptInHeader) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl =
GetSameSiteCrossOriginUrl("/empty.html?samesitecrossorigin");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl());
AddPrerenderAsync(kPrerenderingUrl);
registry_observer.WaitForTrigger(kPrerenderingUrl);
NavigatePrimaryPage(kPrerenderingUrl);
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::
kSameSiteCrossOriginNavigationNotOptInInInitialNavigation);
}
IN_PROC_BROWSER_TEST_F(
PrerenderBrowserTest,
SameSiteCrossOriginRedirectionSpeculationRulesWithoutOptInHeader) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kRedirectedUrl =
GetSameSiteCrossOriginUrl("/empty.html?samesitecrossorigin");
const GURL kPrerenderingUrl =
GetUrl("/server-redirect?" + kRedirectedUrl.spec());
test::PrerenderHostObserver host_observer(*web_contents_impl(),
kPrerenderingUrl);
AddPrerenderAsync(kPrerenderingUrl);
host_observer.WaitForDestroyed();
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_EQ(GetRequestCount(kRedirectedUrl), 1);
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
EXPECT_FALSE(HasHostForUrl(kRedirectedUrl));
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::
kSameSiteCrossOriginRedirectNotOptInInInitialNavigation);
}
IN_PROC_BROWSER_TEST_F(
PrerenderBrowserTest,
SameSiteCrossOriginCredentialedPrerenderRedirectionSpeculationRulesWithoutOptInHeader) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kRedirectedUrl =
GetSameSiteCrossOriginUrl("/empty.html?samesitecrossorigin");
const GURL kPrerenderingUrl = GetUrl(
"/server-redirect-credentialed-prerender?" + kRedirectedUrl.spec());
test::PrerenderHostObserver host_observer(*web_contents_impl(),
kPrerenderingUrl);
AddPrerenderAsync(kPrerenderingUrl);
host_observer.WaitForDestroyed();
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_EQ(GetRequestCount(kRedirectedUrl), 1);
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
EXPECT_FALSE(HasHostForUrl(kRedirectedUrl));
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::
kSameSiteCrossOriginRedirectNotOptInInInitialNavigation);
}
IN_PROC_BROWSER_TEST_F(
PrerenderBrowserTest,
SameSiteCrossOriginCredentialedPrerenderRedirectionSpeculationRulesWithoutOptInHeader2) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kRedirectedUrl =
GetSameSiteCrossOriginUrl("/empty.html?samesitecrossorigin");
const GURL kPrerenderingUrl = GetSameSiteCrossOriginUrl(
"/server-redirect-credentialed-prerender?" + kRedirectedUrl.spec());
test::PrerenderHostObserver host_observer(*web_contents_impl(),
kPrerenderingUrl);
AddPrerenderAsync(kPrerenderingUrl);
host_observer.WaitForDestroyed();
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_EQ(GetRequestCount(kRedirectedUrl), 1);
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
EXPECT_FALSE(HasHostForUrl(kRedirectedUrl));
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::
kSameSiteCrossOriginRedirectNotOptInInInitialNavigation);
}
IN_PROC_BROWSER_TEST_F(
PrerenderBrowserTest,
SameSiteCrossOriginNavigationBackToSameOriginWithoutOptInHeader) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kRedirectedUrl = GetUrl("/empty.html?samesitecrossorigin");
const GURL kPrerenderingUrl =
GetSameSiteCrossOriginUrl("/server-redirect?" + kRedirectedUrl.spec());
test::PrerenderHostObserver host_observer(*web_contents_impl(),
kPrerenderingUrl);
RedirectChainObserver redirect_chain_observer(*shell()->web_contents(),
kRedirectedUrl);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0);
AddPrerender(kPrerenderingUrl);
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1);
ASSERT_EQ(2u, redirect_chain_observer.redirect_chain().size());
EXPECT_EQ(kPrerenderingUrl, redirect_chain_observer.redirect_chain()[0]);
EXPECT_EQ(kRedirectedUrl, redirect_chain_observer.redirect_chain()[1]);
NavigatePrimaryPage(kPrerenderingUrl);
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kRedirectedUrl);
EXPECT_EQ(GetRequestCount(kRedirectedUrl), 1);
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kActivated);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
CrossSiteMultipleRedirectionSpeculationRules) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kRedirectedUrl = GetSameSiteCrossOriginUrl(
"/prerender/prerender_with_opt_in_header.html?prerender");
const GURL kRedirectedUrl2 =
GetCrossSiteUrl("/server-redirect?" + kRedirectedUrl.spec());
const GURL kPrerenderingUrl =
GetUrl("/server-redirect?" + kRedirectedUrl2.spec());
test::PrerenderHostObserver host_observer(*web_contents_impl(),
kPrerenderingUrl);
AddPrerenderAsync(kPrerenderingUrl);
host_observer.WaitForDestroyed();
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_EQ(GetRequestCount(kRedirectedUrl), 0);
EXPECT_EQ(GetRequestCount(kRedirectedUrl2), 0);
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
EXPECT_FALSE(HasHostForUrl(kRedirectedUrl));
EXPECT_FALSE(HasHostForUrl(kRedirectedUrl2));
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kCrossSiteRedirectInInitialNavigation);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
CheckSameSiteCrossOriginSpeculationRulesPrerender) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl =
GetSameSiteCrossOriginUrl("/prerender/prerender_with_opt_in_header.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0);
AddPrerender(kPrerenderingUrl);
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
NavigatePrimaryPage(kPrerenderingUrl);
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kActivated);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
SameSiteCrossOriginSpeculationRulesRedirection) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kRedirectedUrl = GetSameSiteCrossOriginUrl(
"/prerender/prerender_with_opt_in_header.html?prerender");
const GURL kPrerenderingUrl =
GetSameSiteCrossOriginUrl("/server-redirect?" + kRedirectedUrl.spec());
RedirectChainObserver redirect_chain_observer(*shell()->web_contents(),
kRedirectedUrl);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0);
ASSERT_EQ(GetRequestCount(kRedirectedUrl), 0);
AddPrerender(kPrerenderingUrl);
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_EQ(GetRequestCount(kRedirectedUrl), 1);
ASSERT_EQ(2u, redirect_chain_observer.redirect_chain().size());
EXPECT_EQ(kPrerenderingUrl, redirect_chain_observer.redirect_chain()[0]);
EXPECT_EQ(kRedirectedUrl, redirect_chain_observer.redirect_chain()[1]);
EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl));
EXPECT_FALSE(HasHostForUrl(kRedirectedUrl));
RedirectChainObserver activation_redirect_chain_observer(
*shell()->web_contents(), kRedirectedUrl);
NavigationHandleObserver activation_observer(web_contents(),
kPrerenderingUrl);
NavigatePrimaryPage(kPrerenderingUrl);
ASSERT_EQ(1u, activation_redirect_chain_observer.redirect_chain().size());
EXPECT_EQ(kRedirectedUrl,
activation_redirect_chain_observer.redirect_chain()[0]);
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kRedirectedUrl);
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kActivated);
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_EQ(GetRequestCount(kRedirectedUrl), 1);
ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id();
ExpectPreloadingAttemptUkm({attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kSuccess,
PreloadingFailureReason::kUnspecified,
true,
kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate)});
}
IN_PROC_BROWSER_TEST_F(
PrerenderBrowserTest,
SameSiteCrossOriginSpeculationRulesMultipleRedirections) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kRedirectedUrl = GetSameSiteCrossOriginUrl(
"/prerender/prerender_with_opt_in_header.html?prerender");
const GURL kRedirectedUrl2 =
GetSameSiteCrossOriginUrl("/server-redirect?" + kRedirectedUrl.spec());
const GURL kPrerenderingUrl =
GetSameSiteCrossOriginUrl("/server-redirect?" + kRedirectedUrl2.spec());
RedirectChainObserver redirect_chain_observer(*shell()->web_contents(),
kRedirectedUrl);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0);
ASSERT_EQ(GetRequestCount(kRedirectedUrl), 0);
ASSERT_EQ(GetRequestCount(kRedirectedUrl2), 0);
AddPrerender(kPrerenderingUrl);
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_EQ(GetRequestCount(kRedirectedUrl), 1);
EXPECT_EQ(GetRequestCount(kRedirectedUrl2), 1);
ASSERT_EQ(3u, redirect_chain_observer.redirect_chain().size());
EXPECT_EQ(kPrerenderingUrl, redirect_chain_observer.redirect_chain()[0]);
EXPECT_EQ(kRedirectedUrl2, redirect_chain_observer.redirect_chain()[1]);
EXPECT_EQ(kRedirectedUrl, redirect_chain_observer.redirect_chain()[2]);
EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl));
EXPECT_FALSE(HasHostForUrl(kRedirectedUrl));
EXPECT_FALSE(HasHostForUrl(kRedirectedUrl2));
RedirectChainObserver activation_redirect_chain_observer(
*shell()->web_contents(), kRedirectedUrl);
NavigationHandleObserver activation_observer(web_contents(),
kPrerenderingUrl);
NavigatePrimaryPage(kPrerenderingUrl);
ASSERT_EQ(1u, activation_redirect_chain_observer.redirect_chain().size());
EXPECT_EQ(kRedirectedUrl,
activation_redirect_chain_observer.redirect_chain()[0]);
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kRedirectedUrl);
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kActivated);
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
EXPECT_EQ(GetRequestCount(kRedirectedUrl), 1);
EXPECT_EQ(GetRequestCount(kRedirectedUrl2), 1);
ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id();
ExpectPreloadingAttemptUkm({attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kSuccess,
PreloadingFailureReason::kUnspecified,
true,
kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate)});
}
void PrerenderBrowserTest::TestEmbedderTriggerWithUnsupportedScheme(
const GURL& prerendering_url) {
const GURL initial_url = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
ASSERT_FALSE(prerendering_url.SchemeIsHTTPOrHTTPS());
auto* preloading_data =
PreloadingData::GetOrCreateForWebContents(web_contents_impl());
PreloadingPredictor preloading_predictor(100, "Embedder");
PreloadingURLMatchCallback same_url_matcher =
PreloadingData::GetSameURLMatcher(prerendering_url);
PreloadingAttempt* preloading_attempt = preloading_data->AddPreloadingAttempt(
preloading_predictor, PreloadingType::kPrerender,
std::move(same_url_matcher),
web_contents_impl()->GetPrimaryMainFrame()->GetPageUkmSourceId());
std::unique_ptr<PrerenderHandle> prerender_handle =
AddEmbedderTriggeredPrerenderAsync(prerendering_url, preloading_attempt);
EXPECT_FALSE(prerender_handle);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
"EmbedderSuffixForTest",
PrerenderFinalStatus::kInvalidSchemeNavigation, 1);
const GURL navigated_url = GetUrl("/empty.html?navigated");
ASSERT_TRUE(NavigateToURL(shell(), navigated_url));
auto attempt_ukm_entry_builder =
std::make_unique<test::PreloadingAttemptUkmEntryBuilder>(
preloading_predictor);
ExpectPreloadingAttemptUkm({attempt_ukm_entry_builder->BuildEntry(
PrimaryPageSourceId(), PreloadingType::kPrerender,
PreloadingEligibility::kHttpOrHttpsOnly,
PreloadingHoldbackStatus::kUnspecified,
PreloadingTriggeringOutcome::kUnspecified,
PreloadingFailureReason::kUnspecified,
false,
std::nullopt,
std::nullopt)});
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
EmbedderTrigger_UnsupportedScheme_ViewSource) {
const GURL prerendering_url =
GURL("view-source:" + GetUrl("/empty.html?prerender").spec());
TestEmbedderTriggerWithUnsupportedScheme(prerendering_url);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
EmbedderTrigger_UnsupportedScheme_DataUrl) {
const GURL prerendering_url(
"data:text/html,%3Ch1%3EHello%2C%20World%21%3C%2Fh1%3E");
TestEmbedderTriggerWithUnsupportedScheme(prerendering_url);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
EmbedderTrigger_SameOriginRedirection) {
const GURL initial_url = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
const GURL redirected_url_node_2 = GetUrl("/empty.html?prerender");
const GURL redirected_url_node_1 =
GetUrl("/server-redirect?" + redirected_url_node_2.spec());
const GURL prerender_initial_url =
GetUrl("/server-redirect?" + redirected_url_node_1.spec());
RedirectChainObserver redirect_chain_observer(*shell()->web_contents(),
redirected_url_node_2);
std::unique_ptr<PrerenderHandle> prerender_handle =
AddEmbedderTriggeredPrerender(prerender_initial_url);
ASSERT_EQ(3u, redirect_chain_observer.redirect_chain().size());
EXPECT_TRUE(HasHostForUrl(prerender_initial_url));
RedirectChainObserver activation_redirect_chain_observer(
*shell()->web_contents(), redirected_url_node_2);
test::PrerenderHostObserver prerender_observer(*web_contents_impl(),
prerender_initial_url);
prerender_helper()->NavigatePrimaryPageAsync(
prerender_initial_url,
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR));
prerender_observer.WaitForActivation();
ASSERT_EQ(1u, activation_redirect_chain_observer.redirect_chain().size());
EXPECT_EQ(redirected_url_node_2,
activation_redirect_chain_observer.redirect_chain()[0]);
}
IN_PROC_BROWSER_TEST_F(
PrerenderBrowserTest,
EmbedderTrigger_CancelIfCrossOriginUrlInRedirectionChain) {
GURL initial_url = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
GURL cross_origin_redirected_url = GetCrossSiteUrl("/empty.html");
GURL same_origin_redirected_url =
GetUrl("/server-redirect?" + cross_origin_redirected_url.spec());
GURL prerendering_initial_url =
GetUrl("/server-redirect?" + same_origin_redirected_url.spec());
RedirectChainObserver redirect_chain_observer(*shell()->web_contents(),
cross_origin_redirected_url);
std::unique_ptr<PrerenderHandle> prerender_handle =
AddEmbedderTriggeredPrerender(prerendering_initial_url);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
"EmbedderSuffixForTest",
PrerenderFinalStatus::kCrossSiteRedirectInInitialNavigation, 1);
EXPECT_FALSE(HasHostForUrl(prerendering_initial_url));
}
std::unique_ptr<PrerenderHandle>
PrerenderEmbedderTriggeredCrossOriginRedirectionPage(
WebContentsImpl& web_contents,
const GURL& prerendering_url,
const GURL& cross_origin_url) {
EXPECT_FALSE(url::IsSameOriginWith(prerendering_url, cross_origin_url));
RedirectChainObserver redirect_chain_observer{web_contents, cross_origin_url};
std::unique_ptr<PrerenderHandle> prerender_handle =
web_contents.StartPrerendering(
prerendering_url, PreloadingTriggerType::kEmbedder,
"EmbedderSuffixForTest",
net::HttpRequestHeaders(),
std::nullopt,
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
false,
true,
PreloadingHoldbackStatus::kUnspecified,
PreloadPipelineInfo::Create(
PreloadingType::kPrerender),
nullptr, {},
{},
false);
EXPECT_TRUE(prerender_handle);
test::PrerenderTestHelper::WaitForPrerenderLoadCompletion(web_contents,
prerendering_url);
EXPECT_EQ(2u, redirect_chain_observer.redirect_chain().size());
return prerender_handle;
}
namespace {
class FrameDisplayStateChangedObserver : public WebContentsObserver {
public:
explicit FrameDisplayStateChangedObserver(RenderFrameHost& host)
: WebContentsObserver(WebContents::FromRenderFrameHost(&host)),
target_host_(&host) {}
void WaitForFrameDisplayStateChanged() {
if (changed_count_ > 0) {
changed_count_--;
} else {
base::RunLoop loop;
callback_ = loop.QuitClosure();
loop.Run();
}
}
void FrameDisplayStateChanged(RenderFrameHost* host,
bool is_display_none) override {
if (host == target_host_) {
if (callback_) {
std::move(callback_).Run();
} else {
changed_count_++;
}
}
}
int changed_count_ = 0;
const raw_ptr<RenderFrameHost> target_host_;
base::OnceClosure callback_;
};
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, FrameOwnerPropertiesDisplayNone) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl =
GetUrl("/prerender/doc-with-display-none-iframe.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
EXPECT_TRUE(AddTestUtilJS(current_frame_host()));
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHost* prerender_frame_host = GetPrerenderedMainFrameHost(host_id);
EXPECT_TRUE(ExecJs(prerender_frame_host, "loaded;"));
RenderFrameHost* iframe_host = FindRenderFrameHost(
prerender_frame_host->GetPage(), GetUrl("/empty.html"));
EXPECT_FALSE(prerender_frame_host->IsFrameDisplayNone());
EXPECT_TRUE(iframe_host->IsFrameDisplayNone());
NavigatePrimaryPage(kPrerenderingUrl);
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
EXPECT_FALSE(prerender_frame_host->IsFrameDisplayNone());
EXPECT_TRUE(iframe_host->IsFrameDisplayNone());
FrameDisplayStateChangedObserver obs(*iframe_host);
EXPECT_TRUE(
ExecJs(prerender_frame_host,
"document.querySelector('iframe').style = 'display: block;'"));
obs.WaitForFrameDisplayStateChanged();
EXPECT_FALSE(prerender_frame_host->IsFrameDisplayNone());
EXPECT_FALSE(iframe_host->IsFrameDisplayNone());
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, TriggeredPrerenderUkm) {
ukm::TestAutoSetUkmRecorder ukm_recorder;
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
EXPECT_EQ(0u,
ukm_recorder
.GetEntriesByName(ukm::builders::PrerenderPageLoad::kEntryName)
.size());
ASSERT_TRUE(AddPrerender(kPrerenderingUrl));
const std::vector<raw_ptr<const ukm::mojom::UkmEntry, VectorExperimental>>
entries = ukm_recorder.GetEntriesByName(
ukm::builders::PrerenderPageLoad::kEntryName);
ASSERT_EQ(1u, entries.size());
EXPECT_EQ(web_contents()->GetPrimaryMainFrame()->GetPageUkmSourceId(),
entries.front()->source_id);
ukm_recorder.ExpectEntryMetric(
entries.front(),
ukm::builders::PrerenderPageLoad::kTriggeredPrerenderName, 1);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ColorSchemeDarkInNonPrimaryPage) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/color-scheme-dark.html");
BackgroundColorChangeWaiter empty_page_background_waiter(web_contents());
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
empty_page_background_waiter.Wait();
{
testing::NiceMock<MockWebContentsObserver> background_color_observer(
web_contents());
EXPECT_CALL(background_color_observer, OnBackgroundColorChanged())
.Times(Exactly(0));
AddPrerender(kPrerenderingUrl);
}
BackgroundColorChangeWaiter prerendered_page_background_waiter(
web_contents());
testing::NiceMock<MockWebContentsObserver> background_color_observer(
web_contents());
EXPECT_CALL(background_color_observer, OnBackgroundColorChanged())
.Times(Exactly(1));
NavigatePrimaryPage(kPrerenderingUrl);
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
prerendered_page_background_waiter.Wait();
}
#if (BUILDFLAG(IS_WIN) && defined(ADDRESS_SANITIZER))
#define MAYBE_ThemeColorSchemeChangeInNonPrimaryPage \
DISABLED_ThemeColorSchemeChangeInNonPrimaryPage
#else
#define MAYBE_ThemeColorSchemeChangeInNonPrimaryPage \
ThemeColorSchemeChangeInNonPrimaryPage
#endif
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
MAYBE_ThemeColorSchemeChangeInNonPrimaryPage) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/theme_color.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
{
testing::NiceMock<MockWebContentsObserver> theme_color_observer(
web_contents());
EXPECT_CALL(theme_color_observer, DidChangeThemeColor()).Times(Exactly(0));
AddPrerender(kPrerenderingUrl);
}
ThemeChangeWaiter theme_change_waiter(web_contents());
testing::NiceMock<MockWebContentsObserver> theme_color_observer(
web_contents());
EXPECT_CALL(theme_color_observer, DidChangeThemeColor()).Times(Exactly(1));
NavigatePrimaryPage(kPrerenderingUrl);
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
theme_change_waiter.Wait();
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
TextAutosizerInfoChangeInNonPrimaryPage) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
RenderFrameHostImpl* primary_frame_host = current_frame_host();
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHostImpl* prerender_frame_host =
GetPrerenderedMainFrameHost(host_id);
blink::mojom::TextAutosizerPageInfo prerender_page_info(
320,
480,
1.f);
prerender_frame_host->TextAutosizerPageInfoChanged(
prerender_page_info.Clone());
EXPECT_TRUE(prerender_page_info.Equals(
prerender_frame_host->GetPage().text_autosizer_page_info()));
EXPECT_FALSE(prerender_page_info.Equals(
primary_frame_host->GetPage().text_autosizer_page_info()));
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_TRUE(prerender_page_info.Equals(
current_frame_host()->GetPage().text_autosizer_page_info()));
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
VerifyFrameNameMaintainedAfterActivation) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHostImpl* prerendered_render_frame_host =
GetPrerenderedMainFrameHost(host_id);
ASSERT_TRUE(
ExecJs(prerendered_render_frame_host, "window.name = 'prerender_page'"));
EXPECT_EQ(prerendered_render_frame_host->GetFrameName(), "prerender_page");
EXPECT_EQ(current_frame_host()->GetFrameName(), "");
test::PrerenderHostObserver host_observer(*web_contents(), kPrerenderingUrl);
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_TRUE(host_observer.was_activated());
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
EXPECT_EQ(current_frame_host()->GetFrameName(), "prerender_page");
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ActivateWhileReloadingSubframe) {
const char kSubframePath[] = "/title1.html";
net::test_server::ControllableHttpResponse first_response(
embedded_test_server(), kSubframePath);
net::test_server::ControllableHttpResponse second_response(
embedded_test_server(), kSubframePath);
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
const GURL kPrerenderingUrl =
embedded_test_server()->GetURL("/page_with_iframe.html");
const GURL kSubframeUrl = embedded_test_server()->GetURL(kSubframePath);
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl());
AddPrerenderAsync(kPrerenderingUrl);
first_response.WaitForRequest();
first_response.Send(net::HTTP_OK, "");
first_response.Done();
registry_observer.WaitForTrigger(kPrerenderingUrl);
FrameTreeNodeId host_id = GetHostForUrl(kPrerenderingUrl);
WaitForPrerenderLoadCompletion(host_id);
RenderFrameHostImpl* prerender_rfh = GetPrerenderedMainFrameHost(host_id);
RenderFrameHostImpl* child_rfh =
prerender_rfh->child_at(0)->current_frame_host();
EXPECT_EQ(child_rfh->GetLastCommittedURL(), kSubframeUrl);
EXPECT_TRUE(ExecJs(child_rfh, "window.location.reload();"));
second_response.WaitForRequest();
TestNavigationObserver nav_observer(web_contents());
EXPECT_TRUE(
ExecJs(web_contents(), JsReplace("location = $1", kPrerenderingUrl)));
second_response.Send(net::HTTP_OK, "");
second_response.Done();
nav_observer.WaitForNavigationFinished();
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, DoNotUpdateUserActivationState) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHostImpl* prerendered_rfh = GetPrerenderedMainFrameHost(host_id);
EXPECT_FALSE(
current_frame_host()->frame_tree_node()->HasStickyUserActivation());
EXPECT_FALSE(prerendered_rfh->frame_tree_node()->HasStickyUserActivation());
prerendered_rfh->UpdateUserActivationState(
blink::mojom::UserActivationUpdateType::kNotifyActivation,
blink::mojom::UserActivationNotificationType::kTest);
EXPECT_FALSE(prerendered_rfh->frame_tree_node()->HasStickyUserActivation());
EXPECT_FALSE(prerendered_rfh->HasTransientUserActivation());
EXPECT_FALSE(
current_frame_host()->frame_tree_node()->HasStickyUserActivation());
EXPECT_FALSE(
current_frame_host()->frame_tree_node()->HasTransientUserActivation());
current_frame_host()->UpdateUserActivationState(
blink::mojom::UserActivationUpdateType::kNotifyActivation,
blink::mojom::UserActivationNotificationType::kTest);
EXPECT_TRUE(
current_frame_host()->frame_tree_node()->HasStickyUserActivation());
EXPECT_FALSE(prerendered_rfh->frame_tree_node()->HasStickyUserActivation());
}
IN_PROC_BROWSER_TEST_P(PrerenderTargetAgnosticBrowserTest, MixedContent) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerendering");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = prerender_helper()->AddPrerender(
kPrerenderingUrl, std::nullopt, GetTargetHint());
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
auto* prerendered_rfh =
test::PrerenderTestHelper::GetPrerenderedMainFrameHost(
*prerender_web_contents, host_id);
CHECK(prerendered_rfh);
EXPECT_TRUE(AddTestUtilJS(prerendered_rfh));
test::PrerenderHostObserver host_observer(*prerender_web_contents, host_id);
std::ignore =
ExecJs(prerendered_rfh,
"add_iframe_async('http://a.test/empty.html?prerendering')",
EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES);
host_observer.WaitForDestroyed();
if (GetTargetHint() == "_blank") {
EXPECT_FALSE(prerender_helper()->HasNewTabHandle(host_id));
} else {
EXPECT_TRUE(prerender_helper()
->GetHostForUrl(*prerender_web_contents, kPrerenderingUrl)
.is_null());
}
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kMixedContent);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
ActivatePageWithCspHeaderFrameSrc) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl =
GetUrl("/set-header?Content-Security-Policy: frame-src 'none'");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHostImpl* prerendered_render_frame_host =
GetPrerenderedMainFrameHost(host_id);
{
const std::vector<network::mojom::ContentSecurityPolicyPtr>& root_csp_pre =
prerendered_render_frame_host->policy_container_host()
->policies()
.content_security_policies;
EXPECT_EQ(1u, root_csp_pre.size());
EXPECT_EQ("frame-src 'none'", root_csp_pre[0]->header->header_value);
EXPECT_EQ(prerendered_render_frame_host->active_sandbox_flags(),
network::mojom::WebSandboxFlags::kNone);
}
test::PrerenderHostObserver host_observer(*web_contents(), kPrerenderingUrl);
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_TRUE(host_observer.was_activated());
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
{
const std::vector<network::mojom::ContentSecurityPolicyPtr>& root_csp_post =
current_frame_host()
->policy_container_host()
->policies()
.content_security_policies;
EXPECT_EQ(1u, root_csp_post.size());
EXPECT_EQ("frame-src 'none'", root_csp_post[0]->header->header_value);
EXPECT_EQ(current_frame_host()->active_sandbox_flags(),
network::mojom::WebSandboxFlags::kNone);
EXPECT_EQ(static_cast<WebContentsImpl*>(web_contents())
->GetPrimaryFrameTree()
.root()
->active_sandbox_flags(),
network::mojom::WebSandboxFlags::kNone);
}
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
ActivatePageWithCspHeaderSandboxFlags) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl =
GetUrl("/set-header?Content-Security-Policy: sandbox allow-scripts");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHostImpl* prerendered_render_frame_host =
GetPrerenderedMainFrameHost(host_id);
{
const std::vector<network::mojom::ContentSecurityPolicyPtr>& root_csp_pre =
prerendered_render_frame_host->policy_container_host()
->policies()
.content_security_policies;
EXPECT_EQ(1u, root_csp_pre.size());
EXPECT_EQ("sandbox allow-scripts", root_csp_pre[0]->header->header_value);
EXPECT_EQ(prerendered_render_frame_host->active_sandbox_flags(),
network::mojom::WebSandboxFlags::kAll &
~network::mojom::WebSandboxFlags::kScripts &
~network::mojom::WebSandboxFlags::kAutomaticFeatures);
}
test::PrerenderHostObserver host_observer(*web_contents(), kPrerenderingUrl);
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_TRUE(host_observer.was_activated());
EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
{
const std::vector<network::mojom::ContentSecurityPolicyPtr>& root_csp_post =
current_frame_host()
->policy_container_host()
->policies()
.content_security_policies;
EXPECT_EQ(1u, root_csp_post.size());
EXPECT_EQ("sandbox allow-scripts", root_csp_post[0]->header->header_value);
EXPECT_EQ(current_frame_host()->active_sandbox_flags(),
network::mojom::WebSandboxFlags::kAll &
~network::mojom::WebSandboxFlags::kScripts &
~network::mojom::WebSandboxFlags::kAutomaticFeatures);
EXPECT_EQ(static_cast<WebContentsImpl*>(web_contents())
->GetPrimaryFrameTree()
.root()
->active_sandbox_flags(),
network::mojom::WebSandboxFlags::kAll &
~network::mojom::WebSandboxFlags::kScripts &
~network::mojom::WebSandboxFlags::kAutomaticFeatures);
}
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, VerifyPrerenderProcessVisibility) {
const GURL kInitialUrl = GetUrl("/empty.html?initial");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHost* prerender_frame_host = GetPrerenderedMainFrameHost(host_id);
RenderProcessHost* prerender_process_host =
prerender_frame_host->GetProcess();
ASSERT_NE(prerender_frame_host, nullptr);
EXPECT_TRUE(prerender_process_host->GetPriority() ==
base::Process::Priority::kBestEffort);
test::PrerenderHostObserver host_observer(*web_contents(), kPrerenderingUrl);
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_TRUE(host_observer.was_activated());
EXPECT_NE(prerender_process_host->GetPriority(),
base::Process::Priority::kBestEffort);
}
class PrerenderSpecificRequestHeadersBrowserTest
: public PrerenderBrowserTest,
public testing::WithParamInterface<bool> {
public:
PrerenderSpecificRequestHeadersBrowserTest() {
if (GetParam()) {
feature_list_.InitAndEnableFeature(
blink::features::kRemovePurposeHeaderForPrefetch);
} else {
feature_list_.InitAndDisableFeature(
blink::features::kRemovePurposeHeaderForPrefetch);
}
}
~PrerenderSpecificRequestHeadersBrowserTest() override = default;
void SetUp() override {
ssl_server().RegisterRequestHandler(
base::BindRepeating(&HandleCorsRequest));
PrerenderBrowserTest::SetUp();
}
bool IsRemovePurposeHeaderEnabled() const { return GetParam(); }
static std::unique_ptr<net::test_server::HttpResponse> HandleCorsRequest(
const net::test_server::HttpRequest& request) {
EXPECT_NE(request.method_string, "OPTIONS");
if (request.relative_url.find("cors") == std::string::npos) {
return nullptr;
}
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
response->AddCustomHeader("Access-Control-Allow-Origin", "*");
response->set_code(net::HTTP_OK);
response->set_content("");
response->set_content_type("text/plain");
return response;
}
bool TestSecPurposePrefetchHeader(const GURL& url) {
net::test_server::HttpRequest::HeaderMap headers = GetRequestHeaders(url);
auto purpose_it = headers.find(blink::kPurposeHeaderName);
if (IsRemovePurposeHeaderEnabled()) {
EXPECT_EQ(headers.end(), purpose_it)
<< "Purpose header should not be present when feature is enabled";
} else {
if (purpose_it == headers.end()) {
return false;
}
EXPECT_EQ(blink::kSecPurposePrefetchHeaderValue, purpose_it->second);
}
auto sec_purpose_it = headers.find(blink::kSecPurposeHeaderName);
if (sec_purpose_it == headers.end()) {
return false;
}
EXPECT_EQ(blink::kSecPurposePrefetchPrerenderHeaderValue,
sec_purpose_it->second);
return true;
}
bool HasSecSpeculationTagsHeader(const GURL& url) {
net::test_server::HttpRequest::HeaderMap headers = GetRequestHeaders(url);
return headers.contains(blink::kSecSpeculationTagsHeaderName);
}
std::string GetSecSpeculationTagsHeader(const GURL& url) {
net::test_server::HttpRequest::HeaderMap headers = GetRequestHeaders(url);
EXPECT_TRUE(headers.contains(blink::kSecSpeculationTagsHeaderName));
return headers[blink::kSecSpeculationTagsHeaderName];
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_P(PrerenderSpecificRequestHeadersBrowserTest,
InitialNavigation_Embedder) {
ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));
const GURL prerender_url = GetUrl("/empty.html?prerender");
std::unique_ptr<PrerenderHandle> prerender_handle =
AddEmbedderTriggeredPrerender(prerender_url);
EXPECT_TRUE(prerender_handle);
EXPECT_TRUE(TestSecPurposePrefetchHeader(prerender_url));
EXPECT_FALSE(HasSecSpeculationTagsHeader(prerender_url));
}
IN_PROC_BROWSER_TEST_P(PrerenderSpecificRequestHeadersBrowserTest,
RedirectionOnInitialNavigation) {
ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));
const GURL kRedirectedUrl = GetUrl("/empty.html?prerender");
const GURL kPrerenderingUrl =
GetUrl("/server-redirect?" + kRedirectedUrl.spec());
RedirectChainObserver redirect_chain_observer(*shell()->web_contents(),
kRedirectedUrl);
AddPrerender(kPrerenderingUrl);
ASSERT_EQ(2u, redirect_chain_observer.redirect_chain().size());
EXPECT_EQ(kPrerenderingUrl, redirect_chain_observer.redirect_chain()[0]);
EXPECT_EQ(kRedirectedUrl, redirect_chain_observer.redirect_chain()[1]);
EXPECT_TRUE(TestSecPurposePrefetchHeader(kPrerenderingUrl));
EXPECT_TRUE(HasSecSpeculationTagsHeader(kPrerenderingUrl));
EXPECT_EQ(GetSecSpeculationTagsHeader(kPrerenderingUrl), "null");
EXPECT_TRUE(TestSecPurposePrefetchHeader(kRedirectedUrl));
EXPECT_TRUE(HasSecSpeculationTagsHeader(kRedirectedUrl));
EXPECT_EQ(GetSecSpeculationTagsHeader(kRedirectedUrl), "null");
}
IN_PROC_BROWSER_TEST_P(PrerenderSpecificRequestHeadersBrowserTest,
SpeculationRulesTagsMergingForImmediateCandidates) {
const GURL initial_url =
GetUrl("/prerender/multiple_prerender_with_tags.html");
const GURL prerender_url = GetUrl("/prerender/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
WaitForPrerenderLoadCompletion(prerender_url);
EXPECT_TRUE(HasSecSpeculationTagsHeader(prerender_url));
EXPECT_EQ(GetSecSpeculationTagsHeader(prerender_url), "\"tag1\", \"tag2\"");
}
IN_PROC_BROWSER_TEST_P(PrerenderSpecificRequestHeadersBrowserTest,
SpeculationRulesTagForSameSiteCrossOrigin) {
const GURL initial_url = GetUrl("/prerender/empty.html");
const GURL prerender_url =
GetSameSiteCrossOriginUrl("/prerender/prerender_with_opt_in_header.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
FrameTreeNodeId host_id = AddPrerenderWithTags(prerender_url, "tag1");
auto* prerendered_rfh = GetPrerenderedMainFrameHost(host_id);
EXPECT_TRUE(prerendered_rfh != nullptr);
EXPECT_TRUE(HasSecSpeculationTagsHeader(prerender_url));
EXPECT_EQ(GetSecSpeculationTagsHeader(prerender_url), "\"tag1\"");
}
IN_PROC_BROWSER_TEST_P(PrerenderSpecificRequestHeadersBrowserTest, Prefetch) {
ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));
test::TestPrefetchWatcher test_prefetch_watcher;
const GURL prefetch_url = GetUrl("/empty.html?prefetch");
AddPrefetchAsync(prefetch_url);
test_prefetch_watcher.WaitUntilPrefetchResponseCompleted(
static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame())
->GetDocumentToken(),
prefetch_url);
EXPECT_TRUE(HasSecSpeculationTagsHeader(prefetch_url));
EXPECT_EQ(GetSecSpeculationTagsHeader(prefetch_url), "null");
}
IN_PROC_BROWSER_TEST_P(PrerenderSpecificRequestHeadersBrowserTest,
SpeculationRulesTagsMergingForNonImmediateCandidates) {
#if !BUILDFLAG(IS_ANDROID)
const GURL initial_url = GetUrl(
"/prerender/multiple_prerender_with_tags_and_different_eagerness.html");
const GURL prerender_url = GetUrl("/prerender/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
InsertAnchor(prerender_url);
PointerDownToAnchor(prerender_url);
WaitForPrerenderLoadCompletion(prerender_url);
EXPECT_TRUE(HasSecSpeculationTagsHeader(prerender_url));
EXPECT_EQ(GetSecSpeculationTagsHeader(prerender_url),
"\"conservative\", \"moderate\"");
#else
GTEST_SKIP();
#endif
}
IN_PROC_BROWSER_TEST_P(PrerenderSpecificRequestHeadersBrowserTest,
SpeculationRulesTagsNoMergingForNonImmediateCandidates) {
#if !BUILDFLAG(IS_ANDROID)
const GURL initial_url = GetUrl(
"/prerender/multiple_prerender_with_tags_and_different_eagerness.html");
const GURL prerender_url = GetUrl("/prerender/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
InsertAnchor(prerender_url);
PointerHoverToAnchor(prerender_url);
WaitForPrerenderLoadCompletion(prerender_url);
EXPECT_TRUE(HasSecSpeculationTagsHeader(prerender_url));
EXPECT_EQ(GetSecSpeculationTagsHeader(prerender_url), "\"moderate\"");
#else
GTEST_SKIP();
#endif
}
INSTANTIATE_TEST_SUITE_P(RemovePurposeHeaderVariations,
PrerenderSpecificRequestHeadersBrowserTest,
::testing::Bool());
class PrerenderUserAgentOverrideBrowserTest : public PrerenderBrowserTest {
public:
PrerenderUserAgentOverrideBrowserTest() {
feature_list_.InitAndEnableFeature(
features::kPreloadingRespectUserAgentOverride);
}
~PrerenderUserAgentOverrideBrowserTest() override = default;
std::string GetUserAgentHeader(const GURL& url) {
net::test_server::HttpRequest::HeaderMap headers = GetRequestHeaders(url);
EXPECT_TRUE(headers.contains(net::HttpRequestHeaders::kUserAgent));
return headers[net::HttpRequestHeaders::kUserAgent];
}
private:
base::test::ScopedFeatureList feature_list_;
};
class ScopedUserAgentOverrideTestDelegate : public WebContentsDelegate {
public:
explicit ScopedUserAgentOverrideTestDelegate(WebContents& web_contents)
: web_contents_(web_contents.GetWeakPtr()) {
web_contents_->SetDelegate(this);
}
~ScopedUserAgentOverrideTestDelegate() override {
if (web_contents_) {
web_contents_->SetDelegate(nullptr);
}
}
NavigationController::UserAgentOverrideOption
ShouldOverrideUserAgentForPreloading(const GURL& url) override {
return override_option_;
}
PreloadingEligibility IsPrerender2Supported(
WebContents& web_contents,
PreloadingTriggerType trigger_type) override {
return PreloadingEligibility::kEligible;
}
void InheritOverride() {
override_option_ =
NavigationController::UserAgentOverrideOption::UA_OVERRIDE_INHERIT;
}
void ForceEnableOverride() {
override_option_ =
NavigationController::UserAgentOverrideOption::UA_OVERRIDE_TRUE;
}
void ForceDisableOverride() {
override_option_ =
NavigationController::UserAgentOverrideOption::UA_OVERRIDE_FALSE;
}
private:
NavigationController::UserAgentOverrideOption override_option_ =
NavigationController::UserAgentOverrideOption::UA_OVERRIDE_INHERIT;
base::WeakPtr<WebContents> web_contents_;
};
IN_PROC_BROWSER_TEST_F(PrerenderUserAgentOverrideBrowserTest,
EnabledToDisabled) {
const GURL initial_url = GetUrl("/prerender/empty.html");
const GURL prerendering_url_1 = GetUrl("/prerender/empty.html?prerender1");
const GURL prerendering_url_2 = GetUrl("/prerender/empty.html?prerender2");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
shell()->web_contents()->SetUserAgentOverride(
blink::UserAgentOverride::UserAgentOnly("fake"), true);
ScopedUserAgentOverrideTestDelegate ua_override_delegate(
*shell()->web_contents());
ua_override_delegate.ForceEnableOverride();
std::unique_ptr<PrerenderHandle> prerender_handle =
AddEmbedderTriggeredPrerender(prerendering_url_1);
ASSERT_TRUE(prerender_handle);
EXPECT_EQ(GetUserAgentHeader(prerendering_url_1), "fake");
ua_override_delegate.ForceDisableOverride();
std::unique_ptr<PrerenderHandle> prerender_handle_2 =
AddEmbedderTriggeredPrerender(prerendering_url_2);
ASSERT_TRUE(prerender_handle_2);
EXPECT_NE(GetUserAgentHeader(prerendering_url_2), "fake");
}
IN_PROC_BROWSER_TEST_F(PrerenderUserAgentOverrideBrowserTest, Inherit) {
const GURL initial_url_1 = GetUrl("/empty.html");
const GURL initial_url_2 = GetUrl("/title1.html");
const GURL prerendering_url_1 = GetUrl("/prerender/empty.html?prerender");
const GURL prerendering_url_2 = GetUrl("/prerender/empty.html?prerender2");
shell()->web_contents()->SetUserAgentOverride(
blink::UserAgentOverride::UserAgentOnly("fake"), true);
ScopedUserAgentOverrideTestDelegate ua_override_delegate(
*shell()->web_contents());
ua_override_delegate.InheritOverride();
{
ASSERT_TRUE(NavigateToURL(shell(), initial_url_1));
ASSERT_FALSE(shell()
->web_contents()
->GetPrimaryMainFrame()
->GetController()
.GetLastCommittedEntry()
->GetIsOverridingUserAgent());
std::unique_ptr<PrerenderHandle> prerender_handle =
AddEmbedderTriggeredPrerender(prerendering_url_1);
ASSERT_TRUE(prerender_handle);
EXPECT_NE(GetUserAgentHeader(prerendering_url_1), "fake");
}
{
shell()
->web_contents()
->GetPrimaryMainFrame()
->GetController()
.GetLastCommittedEntry()
->SetIsOverridingUserAgent(true);
ASSERT_TRUE(NavigateToURL(shell(), initial_url_2));
ASSERT_TRUE(shell()
->web_contents()
->GetPrimaryMainFrame()
->GetController()
.GetLastCommittedEntry()
->GetIsOverridingUserAgent());
std::unique_ptr<PrerenderHandle> prerender_handle =
AddEmbedderTriggeredPrerender(prerendering_url_2);
ASSERT_TRUE(prerender_handle);
EXPECT_EQ(GetUserAgentHeader(prerendering_url_2), "fake");
}
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, EnterFullscreen) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerendering");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
auto* prerendered_rfh = GetPrerenderedMainFrameHost(host_id);
prerendered_rfh->EnterFullscreen(
blink::mojom::FullscreenOptions::New(),
base::BindOnce([](bool value) { EXPECT_FALSE(value); }));
EXPECT_FALSE(web_contents_impl()->IsFullscreen());
}
namespace {
class TestJavaScriptDialogManager : public JavaScriptDialogManager,
public WebContentsDelegate {
public:
TestJavaScriptDialogManager() = default;
~TestJavaScriptDialogManager() override = default;
JavaScriptDialogManager* GetJavaScriptDialogManager(
WebContents* source) override {
return this;
}
void RunJavaScriptDialog(WebContents* web_contents,
RenderFrameHost* render_frame_host,
JavaScriptDialogType dialog_type,
const std::u16string& message_text,
const std::u16string& default_prompt_text,
DialogClosedCallback callback,
bool* did_suppress_message) override {}
void RunBeforeUnloadDialog(WebContents* web_contents,
RenderFrameHost* render_frame_host,
bool is_reload,
DialogClosedCallback callback) override {}
void CancelDialogs(WebContents* web_contents, bool reset_state) override {
cancel_dialogs_called_ = true;
}
bool cancel_dialogs_called() { return cancel_dialogs_called_; }
private:
bool cancel_dialogs_called_ = false;
};
class PrerenderWithRenderDocumentBrowserTest : public PrerenderBrowserTest {
public:
PrerenderWithRenderDocumentBrowserTest() {
InitAndEnableRenderDocumentFeature(
&feature_list_,
GetRenderDocumentLevelName(RenderDocumentLevel::kSubframe));
}
~PrerenderWithRenderDocumentBrowserTest() override = default;
private:
base::test::ScopedFeatureList feature_list_;
};
}
IN_PROC_BROWSER_TEST_F(
PrerenderWithRenderDocumentBrowserTest,
ModalDialogShouldNotBeDismissedAfterPrerenderSubframeNavigation) {
const GURL kPrerenderingUrl = GetUrl("/title1.html");
const GURL kSubframeUrl1 = GetUrl("/empty.html");
const GURL kSubframeUrl2 = GetUrl("/title2.html");
ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHost* prerender_rfh = GetPrerenderedMainFrameHost(host_id);
CHECK(prerender_rfh);
AddTestUtilJS(prerender_rfh);
ASSERT_TRUE(
ExecJs(prerender_rfh, JsReplace("add_iframe($1)", kSubframeUrl1)));
TestJavaScriptDialogManager dialog_manager;
web_contents_impl()->SetDelegate(&dialog_manager);
web_contents_impl()->RunJavaScriptDialog(
web_contents_impl()->GetPrimaryMainFrame(), u"", u"",
JAVASCRIPT_DIALOG_TYPE_ALERT, false, base::NullCallback());
TestNavigationManager subframe_nav_manager(web_contents(), kSubframeUrl2);
ASSERT_TRUE(ExecJs(
prerender_rfh,
JsReplace("document.querySelector('iframe').src = $1", kSubframeUrl2)));
ASSERT_TRUE(subframe_nav_manager.WaitForNavigationFinished());
EXPECT_FALSE(dialog_manager.cancel_dialogs_called());
web_contents_impl()->SetDelegate(nullptr);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, NavigationHandleFrameType) {
{
const GURL kInitialUrl = GetUrl("/empty.html");
DidFinishNavigationObserver observer(
web_contents(),
base::BindLambdaForTesting([](NavigationHandle* navigation_handle) {
EXPECT_TRUE(navigation_handle->IsInPrimaryMainFrame());
CHECK_EQ(navigation_handle->GetNavigatingFrameType(),
FrameType::kPrimaryMainFrame);
}));
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
}
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
{
DidFinishNavigationObserver observer(
web_contents(),
base::BindLambdaForTesting([](NavigationHandle* navigation_handle) {
EXPECT_TRUE(navigation_handle->IsInPrerenderedMainFrame());
CHECK_EQ(navigation_handle->GetNavigatingFrameType(),
FrameType::kPrerenderMainFrame);
}));
AddPrerender(kPrerenderingUrl);
}
{
DidFinishNavigationObserver observer(
web_contents(),
base::BindLambdaForTesting([](NavigationHandle* navigation_handle) {
EXPECT_TRUE(navigation_handle->IsInPrimaryMainFrame());
EXPECT_TRUE(navigation_handle->IsPrerenderedPageActivation());
CHECK_EQ(navigation_handle->GetNavigatingFrameType(),
FrameType::kPrimaryMainFrame);
}));
NavigatePrimaryPage(kPrerenderingUrl);
}
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
NavigationHandleIsRendererInitiatedTrue) {
const GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
{
DidFinishNavigationObserver observer(
web_contents(),
base::BindLambdaForTesting([](NavigationHandle* navigation_handle) {
EXPECT_TRUE(navigation_handle->IsInPrerenderedMainFrame());
EXPECT_TRUE(navigation_handle->IsRendererInitiated());
}));
AddPrerender(kPrerenderingUrl);
}
NavigatePrimaryPage(kPrerenderingUrl);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
HasReceivedUserGestureBeforeNavigation) {
const GURL initial_url = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
ASSERT_FALSE(current_frame_host()
->frame_tree_node()
->has_received_user_gesture_before_nav());
const GURL prerendering_url = GetUrl("/empty.html?prerender");
FrameTreeNodeId host_id = AddPrerender(prerendering_url);
RenderFrameHostImpl* prerendered_render_frame_host =
GetPrerenderedMainFrameHost(host_id);
EXPECT_FALSE(prerendered_render_frame_host->frame_tree_node()
->has_received_user_gesture_before_nav());
test::PrerenderHostObserver host_observer(*web_contents(), host_id);
NavigatePrimaryPage(prerendering_url);
ASSERT_TRUE(host_observer.was_activated());
EXPECT_FALSE(current_frame_host()
->frame_tree_node()
->has_received_user_gesture_before_nav());
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
HasReceivedUserGestureBeforeNavigation_Propagation) {
const GURL initial_url = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
current_frame_host()->HadStickyUserActivationBeforeNavigationChanged(true);
ASSERT_TRUE(current_frame_host()
->frame_tree_node()
->has_received_user_gesture_before_nav());
const GURL prerendering_url = GetUrl("/empty.html?prerender");
FrameTreeNodeId host_id = AddPrerender(prerendering_url);
RenderFrameHostImpl* prerendered_render_frame_host =
GetPrerenderedMainFrameHost(host_id);
EXPECT_FALSE(prerendered_render_frame_host->frame_tree_node()
->has_received_user_gesture_before_nav());
test::PrerenderHostObserver host_observer(*web_contents(), host_id);
NavigatePrimaryPage(prerendering_url);
ASSERT_TRUE(host_observer.was_activated());
EXPECT_TRUE(current_frame_host()
->frame_tree_node()
->has_received_user_gesture_before_nav());
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
CancelPrerenderWhenIsOverridingUserAgentDiffers) {
const std::string user_agent_override = "foo";
const GURL initial_url = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
UserAgentInjector injector(shell()->web_contents(), user_agent_override);
const GURL prerendering_url = GetUrl("/empty.html?prerender");
const FrameTreeNodeId host_id = AddPrerender(prerendering_url);
RenderFrameHostImpl* prerender_rfh =
static_cast<RenderFrameHostImpl*>(GetPrerenderedMainFrameHost(host_id));
EXPECT_EQ(user_agent_override, EvalJs(prerender_rfh, "navigator.userAgent"));
injector.set_is_overriding_user_agent(false);
test::PrerenderHostObserver host_observer(*web_contents(), host_id);
NavigatePrimaryPage(prerendering_url);
host_observer.WaitForDestroyed();
ExpectFinalStatusForSpeculationRule(
PrerenderFinalStatus::kActivationNavigationParameterMismatch);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.ActivationHeadersMismatch.SpeculationRule",
-511888193, 1);
}
class PrerenderSpeculationRulesHoldbackBrowserTest
: public PrerenderBrowserTest {
public:
PrerenderSpeculationRulesHoldbackBrowserTest() {
prerender_helper()->SetHoldback(
PreloadingType::kPrerender,
content_preloading_predictor::kSpeculationRules, true);
}
~PrerenderSpeculationRulesHoldbackBrowserTest() override = default;
};
IN_PROC_BROWSER_TEST_F(PrerenderSpeculationRulesHoldbackBrowserTest,
PrerenderHoldbackTest) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl());
AddPrerenderAsync(kPrerenderingUrl);
registry_observer.WaitForTrigger(kPrerenderingUrl);
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
NavigationHandleObserver activation_observer(web_contents(),
kPrerenderingUrl);
NavigatePrimaryPage(kPrerenderingUrl);
ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id();
ExpectPreloadingAttemptUkm({attempt_ukm_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrerender,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kHoldback,
PreloadingTriggeringOutcome::kUnspecified,
PreloadingFailureReason::kUnspecified,
true,
std::nullopt,
blink::mojom::SpeculationEagerness::kImmediate)});
}
class PrerenderFencedFrameBrowserTest : public PrerenderBrowserTest {
public:
PrerenderFencedFrameBrowserTest() {
feature_list_.InitWithFeaturesAndParameters(
{{blink::features::kFencedFrames, {}},
{::features::kPrivacySandboxAdsAPIsOverride, {}},
{blink::features::kFencedFramesAPIChanges, {}},
{blink::features::kFencedFramesDefaultMode, {}}},
{});
}
~PrerenderFencedFrameBrowserTest() override = default;
void SetUp() override {
ssl_server().RegisterRequestHandler(base::BindRepeating(
&net::test_server::HandlePrefixedRequest,
"/fenced-frame-with-speculation-rules",
base::BindRepeating(HandleFencedFrameWithSpeculationRulesRequest)));
ssl_server().RegisterRequestHandler(base::BindRepeating(
&net::test_server::HandlePrefixedRequest,
"/fenced-frame-with-speculation-rules-header",
base::BindRepeating(
HandleFencedFrameWithSpeculationRulesHeaderRequest)));
ssl_server().RegisterRequestHandler(base::BindRepeating(
&net::test_server::HandlePrefixedRequest, "/prerender.json",
base::BindRepeating(HandlePrerenderJsonRequest)));
PrerenderBrowserTest::SetUp();
}
static std::unique_ptr<net::test_server::HttpResponse>
HandleFencedFrameWithSpeculationRulesRequest(
const net::test_server::HttpRequest& request) {
constexpr char kSpeculationRule[] = R"({
<!doctype html>
<script type="speculationrules">
{
"prerender":[
{"source": "list", "urls": ["/empty.html"]}
]
}
</script>
})";
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
http_response->AddCustomHeader("Supports-Loading-Mode", "fenced-frame");
http_response->set_content_type("text/html");
http_response->set_content(kSpeculationRule);
return http_response;
}
static std::unique_ptr<net::test_server::HttpResponse>
HandleFencedFrameWithSpeculationRulesHeaderRequest(
const net::test_server::HttpRequest& request) {
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
http_response->AddCustomHeader("Supports-Loading-Mode", "fenced-frame");
http_response->AddCustomHeader("Speculation-Rules", "\"/prerender.json\"");
http_response->set_content_type("text/html");
http_response->set_content("<!doctype html>nothing");
return http_response;
}
static std::unique_ptr<net::test_server::HttpResponse>
HandlePrerenderJsonRequest(const net::test_server::HttpRequest& request) {
constexpr char kSpeculationRule[] = R"(
{
"prerender":[
{"source": "list", "urls": ["/empty.html"]}
]
}
)";
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
http_response->set_content_type("application/speculationrules+json");
http_response->set_content(kSpeculationRule);
return http_response;
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(PrerenderFencedFrameBrowserTest,
CreateFencedFrameInPrerenderedPage) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
const GURL kFencedFrameUrl = GetUrl("/title1.html");
constexpr char kAddFencedFrameScript[] = R"({
const fenced_frame = document.createElement('fencedframe');
fenced_frame.config = new FencedFrameConfig($1);
document.body.appendChild(fenced_frame);
})";
const int kNumNavigations = 3;
TestNavigationObserver nav_observer(web_contents(), kNumNavigations);
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
EXPECT_EQ(kInitialUrl, nav_observer.last_navigation_url());
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
auto* prerendered_rfh = GetPrerenderedMainFrameHost(host_id);
EXPECT_TRUE(ExecJs(prerendered_rfh,
JsReplace(kAddFencedFrameScript, kFencedFrameUrl)));
size_t child_frame_count = 0;
prerendered_rfh->ForEachRenderFrameHostImpl([&](RenderFrameHostImpl* rfh) {
if (rfh != prerendered_rfh) {
child_frame_count++;
}
});
EXPECT_EQ(0lu, child_frame_count);
NavigatePrimaryPage(kPrerenderingUrl);
EXPECT_EQ(kPrerenderingUrl, nav_observer.last_navigation_url());
nav_observer.Wait();
EXPECT_EQ(kFencedFrameUrl, nav_observer.last_navigation_url());
}
IN_PROC_BROWSER_TEST_F(PrerenderFencedFrameBrowserTest,
PrerenderFromFencedFrame_SpeculationRules) {
const GURL initial_url = GetUrl("/empty.html");
const GURL fenced_frame_url = GetUrl("/fenced-frame-with-speculation-rules");
constexpr char kAddFencedFrameScript[] = R"({
const fenced_frame = document.createElement('fencedframe');
fenced_frame.config = new FencedFrameConfig($1);
document.body.appendChild(fenced_frame);
})";
const char* console_pattern =
"The SpeculationRules API does not support prerendering in fenced "
"frames.";
WebContentsConsoleObserver console_observer(web_contents());
console_observer.SetPattern(console_pattern);
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
RenderFrameHostImpl* primary_rfh = web_contents_impl()->GetPrimaryMainFrame();
EXPECT_TRUE(
ExecJs(primary_rfh, JsReplace(kAddFencedFrameScript, fenced_frame_url)));
ASSERT_TRUE(console_observer.Wait());
histogram_tester().ExpectTotalCount(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", 0);
}
IN_PROC_BROWSER_TEST_F(PrerenderFencedFrameBrowserTest,
PrerenderFromFencedFrame_LinkSpeculationRules) {
const GURL initial_url = GetUrl("/empty.html");
const GURL fenced_frame_url =
GetUrl("/fenced-frame-with-speculation-rules-header");
constexpr char kAddFencedFrameScript[] = R"({
const fenced_frame = document.createElement('fencedframe');
fenced_frame.config = new FencedFrameConfig($1);
document.body.appendChild(fenced_frame);
})";
const char* console_pattern =
"The SpeculationRules API does not support prerendering in fenced "
"frames.";
WebContentsConsoleObserver console_observer(web_contents());
console_observer.SetPattern(console_pattern);
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
RenderFrameHostImpl* primary_rfh = web_contents_impl()->GetPrimaryMainFrame();
EXPECT_TRUE(
ExecJs(primary_rfh, JsReplace(kAddFencedFrameScript, fenced_frame_url)));
ASSERT_TRUE(console_observer.Wait());
histogram_tester().ExpectTotalCount(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", 0);
}
namespace {
class PrerenderWithSiteIsolationDisabledBrowserTest
: public PrerenderBrowserTest {
public:
PrerenderWithSiteIsolationDisabledBrowserTest() = default;
~PrerenderWithSiteIsolationDisabledBrowserTest() override = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
PrerenderBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kDisableSiteIsolation);
}
};
}
IN_PROC_BROWSER_TEST_F(PrerenderWithSiteIsolationDisabledBrowserTest,
ForceSiteInstanceSwapForInitialPrerenderNavigation) {
if (AreAllSitesIsolatedForTesting()) {
LOG(ERROR) << "Site Isolation should be disabled for this test.";
return;
}
RenderProcessHost::SetMaxRendererProcessCount(1);
const GURL kInitialUrl =
ssl_server().GetURL("isolated.b.test", "/empty.html");
const GURL kIframeUrl = ssl_server().GetURL("a.test", "/empty.html");
const GURL kPrerenderingUrl =
ssl_server().GetURL("isolated.b.test", "/title1.html");
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
policy->AddFutureIsolatedOrigins(
{url::Origin::Create(kInitialUrl)},
ChildProcessSecurityPolicy::IsolatedOriginSource::TEST);
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_TRUE(AddTestUtilJS(current_frame_host()));
ASSERT_TRUE(
ExecJs(current_frame_host(), JsReplace("add_iframe($1)", kIframeUrl)));
RenderFrameHostImplWrapper iframe(
static_cast<RenderFrameHostImpl*>(ChildFrameAt(current_frame_host(), 0)));
ASSERT_NE(current_frame_host()->GetProcess(), iframe->GetProcess());
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHostImplWrapper prerender_rfh(
GetPrerenderedMainFrameHost(host_id));
EXPECT_EQ(prerender_rfh->lifecycle_state(),
LifecycleStateImpl::kPrerendering);
EXPECT_EQ(prerender_rfh->GetProcess(), current_frame_host()->GetProcess());
}
class PrerenderClientHintsBrowserTest : public PrerenderBrowserTest {
public:
PrerenderClientHintsBrowserTest() = default;
~PrerenderClientHintsBrowserTest() override = default;
void SetUp() override {
ssl_server().RegisterRequestHandler(base::BindRepeating(&HandleRequest));
PrerenderBrowserTest::SetUp();
}
static std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
const net::test_server::HttpRequest& request) {
if (request.relative_url.find("acceptch") == std::string::npos) {
return nullptr;
}
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
if (request.relative_url.find("full-version") != std::string::npos) {
response->AddCustomHeader("Accept-CH", "sec-ch-ua-full-version");
} else if (request.relative_url.find("bitness") != std::string::npos) {
response->AddCustomHeader("Accept-CH", "sec-ch-ua-bitness");
} else if (request.relative_url.find("viewport-width") !=
std::string::npos) {
response->AddCustomHeader("Accept-CH", "viewport-width");
response->AddCustomHeader("Accept-CH", "sec-ch-viewport-width");
} else if (request.relative_url.find("viewport-height") !=
std::string::npos) {
response->AddCustomHeader("Accept-CH", "sec-ch-viewport-height");
} else if (request.relative_url.find("no-value") != std::string::npos) {
response->AddCustomHeader("Accept-CH", "");
}
response->set_code(net::HTTP_OK);
if (request.relative_url.find("iframe") != std::string::npos) {
response->set_content(R"(
<html><head><title>iframe test</title></head>
<body>
<iframe src="title1.html" id="test"></iframe>
</body></html>
)");
response->set_content_type("text/html");
} else if (request.relative_url.find("image") != std::string::npos) {
response->set_content(R"(
<html>
<head></head>
<body>
<img src="./blank.jpg"/>
<p>This page has an image. Yay for images!
</body>
</html>
)");
response->set_content_type("text/html");
} else {
response->set_content("");
response->set_content_type("text/plain");
}
return response;
}
protected:
bool HasRequestHeader(const GURL& url, const std::string& key) {
net::test_server::HttpRequest::HeaderMap headers = GetRequestHeaders(url);
return headers.find(key) != headers.end();
}
};
IN_PROC_BROWSER_TEST_F(PrerenderClientHintsBrowserTest,
PrerenderResponseChangesClientHintsLocally) {
MockClientHintsControllerDelegate client_hints_controller_delegate(
GetShellUserAgentMetadata());
ShellContentBrowserClient::Get()
->browser_context()
->set_client_hints_controller_delegate(&client_hints_controller_delegate);
GURL url = GetUrl("/empty.html?acceptch-bitness");
ASSERT_TRUE(NavigateToURL(shell(), url));
GURL prerender_url = GetUrl("/iframe.html?acceptch-full-version");
FrameTreeNodeId host_id = AddPrerender(prerender_url);
EXPECT_TRUE(HasRequestHeader(prerender_url, "sec-ch-ua-bitness"));
EXPECT_FALSE(HasRequestHeader(prerender_url, "sec-ch-ua-full-version"));
GURL prerender_iframe_url = GetUrl("/title1.html");
WaitForRequest(prerender_iframe_url, 1);
EXPECT_TRUE(HasRequestHeader(prerender_iframe_url, "sec-ch-ua-full-version"));
EXPECT_TRUE(HasRequestHeader(prerender_iframe_url, "sec-ch-ua-bitness"));
test::PrerenderHostObserver prerender_observer(*web_contents_impl(), host_id);
NavigatePrimaryPage(prerender_url);
prerender_observer.WaitForActivation();
GURL real_navigate_url = GetUrl("/empty.html?real");
NavigatePrimaryPage(real_navigate_url);
EXPECT_TRUE(HasRequestHeader(real_navigate_url, "sec-ch-ua-full-version"));
EXPECT_TRUE(HasRequestHeader(prerender_iframe_url, "sec-ch-ua-bitness"));
}
IN_PROC_BROWSER_TEST_F(PrerenderClientHintsBrowserTest,
ChangesToClientHintsAreDiscardIfNoActivation) {
MockClientHintsControllerDelegate client_hints_controller_delegate(
GetShellUserAgentMetadata());
ShellContentBrowserClient::Get()
->browser_context()
->set_client_hints_controller_delegate(&client_hints_controller_delegate);
GURL url = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), url));
GURL prerender_url = GetUrl("/empty.html?acceptch");
GURL real_navigate_url = GetUrl("/empty.html?real");
FrameTreeNodeId host_id = AddPrerender(prerender_url);
test::PrerenderHostObserver prerender_observer(*web_contents_impl(), host_id);
NavigatePrimaryPage(real_navigate_url);
EXPECT_FALSE(HasRequestHeader(real_navigate_url, "sec-ch-ua-full-version"));
GURL real_navigate_url_2 = GetUrl("/empty.html?real2");
NavigatePrimaryPage(real_navigate_url_2);
EXPECT_FALSE(HasRequestHeader(real_navigate_url_2, "sec-ch-ua-full-version"));
}
IN_PROC_BROWSER_TEST_F(PrerenderClientHintsBrowserTest,
PrimaryResponsesDoNotResetPrenderSettings) {
MockClientHintsControllerDelegate client_hints_controller_delegate(
GetShellUserAgentMetadata());
ShellContentBrowserClient::Get()
->browser_context()
->set_client_hints_controller_delegate(&client_hints_controller_delegate);
GURL url = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), url));
GURL prerender_url = GetUrl("/iframe.html?acceptch-full-version");
FrameTreeNodeId host_id = AddPrerender(prerender_url);
EXPECT_FALSE(HasRequestHeader(prerender_url, "sec-ch-ua-full-version"));
GURL prerender_iframe_url = GetUrl("/title1.html");
WaitForRequest(prerender_iframe_url, 1);
EXPECT_TRUE(HasRequestHeader(prerender_iframe_url, "sec-ch-ua-full-version"));
GURL new_tab_url = GetUrl("/image.html?acceptch-no-value");
OpenURLParams params(
new_tab_url, Referrer(), WindowOpenDisposition::NEW_BACKGROUND_TAB,
ui::PAGE_TRANSITION_LINK, false);
auto* new_web_contents =
web_contents_impl()->OpenURL(params, {});
ASSERT_NE(nullptr, new_web_contents);
GURL new_tab_image_url = GetUrl("/blank.jpg");
WaitForRequest(new_tab_image_url, 1);
EXPECT_FALSE(HasRequestHeader(new_tab_url, "sec-ch-ua-full-version"));
test::PrerenderHostObserver prerender_observer(*web_contents_impl(), host_id);
NavigatePrimaryPage(prerender_url);
prerender_observer.WaitForActivation();
GURL real_navigate_url = GetUrl("/empty.html?real");
NavigatePrimaryPage(real_navigate_url);
EXPECT_TRUE(HasRequestHeader(real_navigate_url, "sec-ch-ua-full-version"));
}
IN_PROC_BROWSER_TEST_F(PrerenderClientHintsBrowserTest, ViewPort_Width) {
MockClientHintsControllerDelegate client_hints_controller_delegate(
GetShellUserAgentMetadata());
ShellContentBrowserClient::Get()
->browser_context()
->set_client_hints_controller_delegate(&client_hints_controller_delegate);
web_contents_impl()->Resize(gfx::Rect(10, 20));
GURL url = GetUrl("/empty.html?acceptch-viewport-width");
ASSERT_TRUE(NavigateToURL(shell(), url));
GURL prerender_url = GetUrl("/iframe.html?acceptch");
FrameTreeNodeId host_id = AddPrerender(prerender_url);
EXPECT_FALSE(HasRequestHeader(prerender_url, "viewport-width"));
EXPECT_FALSE(HasRequestHeader(prerender_url, "sec-ch-viewport-width"));
web_contents_impl()->Resize(gfx::Rect(30, 40));
test::PrerenderHostObserver prerender_observer(*web_contents_impl(), host_id);
NavigatePrimaryPage(prerender_url);
prerender_observer.WaitForActivation();
EXPECT_FALSE(HasRequestHeader(prerender_url, "viewport-width"));
EXPECT_FALSE(HasRequestHeader(prerender_url, "sec-ch-viewport-width"));
}
IN_PROC_BROWSER_TEST_F(PrerenderClientHintsBrowserTest, ViewPort_Height) {
MockClientHintsControllerDelegate client_hints_controller_delegate(
GetShellUserAgentMetadata());
ShellContentBrowserClient::Get()
->browser_context()
->set_client_hints_controller_delegate(&client_hints_controller_delegate);
web_contents_impl()->Resize(gfx::Rect(10, 20));
GURL url = GetUrl("/empty.html?acceptch-viewport-height");
ASSERT_TRUE(NavigateToURL(shell(), url));
GURL prerender_url = GetUrl("/iframe.html?acceptch");
FrameTreeNodeId host_id = AddPrerender(prerender_url);
EXPECT_FALSE(HasRequestHeader(prerender_url, "sec-ch-viewport-height"));
web_contents_impl()->Resize(gfx::Rect(30, 40));
test::PrerenderHostObserver prerender_observer(*web_contents_impl(), host_id);
NavigatePrimaryPage(prerender_url);
prerender_observer.WaitForActivation();
EXPECT_FALSE(HasRequestHeader(prerender_url, "sec-ch-viewport-height"));
}
void CheckExpectedCrossOriginMetrics(
const base::HistogramTester& histogram_tester,
PrerenderCrossOriginRedirectionMismatch mismatch_type,
std::optional<PrerenderCrossOriginRedirectionProtocolChange>
protocol_change) {
histogram_tester.ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
"EmbedderSuffixForTest",
PrerenderFinalStatus::kCrossSiteRedirectInInitialNavigation, 1);
histogram_tester.ExpectUniqueSample(
"Prerender.Experimental.PrerenderCrossOriginRedirectionMismatch.Embedder_"
"EmbedderSuffixForTest",
mismatch_type, 1);
if (protocol_change.has_value()) {
histogram_tester.ExpectUniqueSample(
"Prerender.Experimental.CrossOriginRedirectionProtocolChange.Embedder_"
"EmbedderSuffixForTest",
protocol_change.value(), 1);
}
}
IN_PROC_BROWSER_TEST_F(
PrerenderBrowserTest,
EmbedderTrigger_CrossOriginRedirection_SchemeHostPortMismatch) {
base::HistogramTester histogram_tester;
embedded_test_server()->AddDefaultHandlers(GetTestDataFilePath());
ASSERT_TRUE(embedded_test_server()->Start());
GURL initial_url = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
GURL redirected_url = embedded_test_server()->GetURL("b.test", "/empty.html");
GURL prerendering_url = GetUrl("/server-redirect?" + redirected_url.spec());
ASSERT_NE(prerendering_url.GetScheme(), redirected_url.GetScheme());
ASSERT_NE(prerendering_url.GetHost(), redirected_url.GetHost());
ASSERT_NE(prerendering_url.GetPort(), redirected_url.GetPort());
PrerenderEmbedderTriggeredCrossOriginRedirectionPage(
*web_contents_impl(), prerendering_url, redirected_url);
CheckExpectedCrossOriginMetrics(
histogram_tester,
PrerenderCrossOriginRedirectionMismatch::kSchemeHostPortMismatch,
std::nullopt);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
EmbedderTrigger_CrossOriginRedirection_ProtocolUpgrade) {
base::HistogramTester histogram_tester;
embedded_test_server()->AddDefaultHandlers(GetTestDataFilePath());
ASSERT_TRUE(embedded_test_server()->Start());
GURL initial_url = embedded_test_server()->GetURL("a.test", "/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
GURL redirected_url = ssl_server().GetURL("a.test", "/empty.html");
GURL prerendering_url = embedded_test_server()->GetURL(
"a.test", "/server-redirect?" + redirected_url.spec());
ASSERT_NE(prerendering_url.GetScheme(), redirected_url.GetScheme());
ASSERT_NE(prerendering_url.GetPort(), redirected_url.GetPort());
ASSERT_EQ(prerendering_url.GetScheme(), url::kHttpScheme);
ASSERT_EQ(redirected_url.GetScheme(), url::kHttpsScheme);
PrerenderEmbedderTriggeredCrossOriginRedirectionPage(
*web_contents_impl(), prerendering_url, redirected_url);
CheckExpectedCrossOriginMetrics(
histogram_tester,
PrerenderCrossOriginRedirectionMismatch::kSchemePortMismatch,
PrerenderCrossOriginRedirectionProtocolChange::kHttpProtocolUpgrade);
}
IN_PROC_BROWSER_TEST_F(
PrerenderBrowserTest,
EmbedderTrigger_CrossOriginRedirection_ProtocolDowngrade) {
base::HistogramTester histogram_tester;
GURL initial_url = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
GURL::Replacements downgrade_protocol;
downgrade_protocol.SetSchemeStr(url::kHttpScheme);
std::string port_str(base::NumberToString(ssl_server().port() + 1));
downgrade_protocol.SetPortStr(port_str);
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
GURL redirected_url =
GetUrl("/empty.html").ReplaceComponents(downgrade_protocol);
GURL prerendering_url = GetUrl("/server-redirect?" + redirected_url.spec());
ASSERT_NE(prerendering_url.GetScheme(), redirected_url.GetScheme());
ASSERT_NE(prerendering_url.GetPort(), redirected_url.GetPort());
ASSERT_EQ(prerendering_url.GetScheme(), url::kHttpsScheme);
ASSERT_EQ(redirected_url.GetScheme(), "http");
PrerenderEmbedderTriggeredCrossOriginRedirectionPage(
*web_contents_impl(), prerendering_url, redirected_url);
CheckExpectedCrossOriginMetrics(
histogram_tester,
PrerenderCrossOriginRedirectionMismatch::kSchemePortMismatch,
PrerenderCrossOriginRedirectionProtocolChange::kHttpProtocolDowngrade);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
EmbedderTrigger_CrossOriginRedirection_ToSubdomain) {
GURL initial_url = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
GURL::Replacements set_host;
set_host.SetHostStr("www.a.test");
GURL redirected_url = GetUrl("/empty.html").ReplaceComponents(set_host);
GURL prerendering_url = GetUrl("/server-redirect?" + redirected_url.spec());
std::unique_ptr<PrerenderHandle> prerender_handle =
PrerenderEmbedderTriggeredCrossOriginRedirectionPage(
*web_contents_impl(), prerendering_url, redirected_url);
test::PrerenderHostObserver prerender_observer(*web_contents_impl(),
prerendering_url);
prerender_helper()->NavigatePrimaryPageAsync(
prerendering_url,
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR));
prerender_observer.WaitForActivation();
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
"EmbedderSuffixForTest",
PrerenderFinalStatus::kActivated, 1);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
EmbedderTrigger_CrossOriginRedirection_FromSubdomain) {
GURL initial_url = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
GURL::Replacements set_host;
set_host.SetHostStr("www.a.test");
GURL redirected_url = GetUrl("/empty.html");
GURL prerendering_url = GetUrl("/server-redirect?" + redirected_url.spec())
.ReplaceComponents(set_host);
std::unique_ptr<PrerenderHandle> prerender_handle =
PrerenderEmbedderTriggeredCrossOriginRedirectionPage(
*web_contents_impl(), prerendering_url, redirected_url);
test::PrerenderHostObserver prerender_observer(*web_contents_impl(),
prerendering_url);
prerender_helper()->NavigatePrimaryPageAsync(
prerendering_url,
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR));
prerender_observer.WaitForActivation();
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
"EmbedderSuffixForTest",
PrerenderFinalStatus::kActivated, 1);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
EmbedderTrigger_CrossOriginRedirection_DifferentDomain) {
base::HistogramTester histogram_tester;
GURL kInitialUrl = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
GURL kRedirectedUrl = GetCrossSiteUrl("/empty.html?prerender");
GURL kPrerenderingUrl = GetUrl("/server-redirect?" + kRedirectedUrl.spec());
PrerenderEmbedderTriggeredCrossOriginRedirectionPage(
*web_contents_impl(), kPrerenderingUrl, kRedirectedUrl);
CheckExpectedCrossOriginMetrics(
histogram_tester, PrerenderCrossOriginRedirectionMismatch::kHostMismatch,
std::nullopt);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
PrerenderWithAccessibilityEnabled) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/page_with_iframe.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
ScopedAccessibilityModeOverride inner_scoped_accessibility_mode(
shell()->web_contents(), ui::kAXModeComplete);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0);
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
ASSERT_TRUE(host_id);
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1);
test::PrerenderHostObserver prerender_observer(*web_contents_impl(),
kPrerenderingUrl);
NavigatePrimaryPage(kPrerenderingUrl);
prerender_observer.WaitForActivation();
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kActivated);
}
class UpdateTargetURLDelegate : public WebContentsDelegate {
public:
explicit UpdateTargetURLDelegate(WebContents* web_contents) {
web_contents->SetDelegate(this);
}
UpdateTargetURLDelegate(const UpdateTargetURLDelegate&) = delete;
UpdateTargetURLDelegate& operator=(const UpdateTargetURLDelegate&) = delete;
bool is_updated_target_url() { return is_updated_target_url_; }
private:
void UpdateTargetURL(WebContents* source, const GURL& url) override {
is_updated_target_url_ = true;
}
bool is_updated_target_url_ = false;
};
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, FocusChangeInPrerenderedPage) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/simple_links.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
RenderFrameHostImpl* prerender_frame_host =
GetPrerenderedMainFrameHost(host_id);
UpdateTargetURLDelegate delegate(shell()->web_contents());
EXPECT_TRUE(ExecJs(prerender_frame_host,
"document.getElementById('same_site_link').focus();"));
EXPECT_FALSE(delegate.is_updated_target_url());
}
IN_PROC_BROWSER_TEST_F(
PrerenderBrowserTest,
UnusedRenderWidgetHostFrameTreePointerUpdatedOnActivation) {
if (base::FeatureList::IsEnabled(features::kDeferSpeculativeRFHCreation)) {
return;
}
IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
const GURL kInitialUrl = GetUrl("/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
EXPECT_TRUE(AddTestUtilJS(current_frame_host()));
const GURL kPrerenderingUrl = GetUrl("/title2.html");
FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
const GURL kCrossOriginSubframeUrl = GetCrossSiteUrl("/title2.html");
RenderFrameHostImpl* prerender_rfh = GetPrerenderedMainFrameHost(host_id);
EXPECT_TRUE(AddTestUtilJS(prerender_rfh));
EXPECT_TRUE(ExecJs(prerender_rfh, JsReplace("add_iframe_async($1)",
kCrossOriginSubframeUrl)));
base::RunLoop().RunUntilIdle();
ASSERT_EQ(prerender_rfh->child_count(), 1u);
FrameTreeNode* iframe = prerender_rfh->child_at(0);
ASSERT_TRUE(iframe->render_manager()->speculative_frame_host());
RenderViewHostImpl* render_view_host =
iframe->render_manager()->speculative_frame_host()->render_view_host();
NavigatePrimaryPage(kPrerenderingUrl);
ASSERT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
ASSERT_EQ("LOADED",
EvalJs(prerender_rfh, JsReplace("wait_iframe_async($1)",
kCrossOriginSubframeUrl)));
EXPECT_EQ(render_view_host, iframe->current_frame_host()->render_view_host());
RenderWidgetHostImpl* render_widget_host = render_view_host->GetWidget();
EXPECT_NE(render_widget_host,
iframe->current_frame_host()->GetRenderWidgetHost());
EXPECT_EQ(render_widget_host->frame_tree(),
current_frame_host()->frame_tree());
const GURL kCrossOriginUrl = GetCrossSiteUrl("/title1.html");
DisableProactiveBrowsingInstanceSwapFor(current_frame_host());
NavigatePrimaryPage(kCrossOriginUrl);
ASSERT_EQ(current_frame_host()->render_view_host(), render_view_host);
ASSERT_EQ(current_frame_host()->GetRenderWidgetHost(), render_widget_host);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, WindowClosedSpeculationRules) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
const GURL kPrerenderingUrl = embedded_test_server()->GetURL("/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
AddPrerender(kPrerenderingUrl);
FrameTreeNodeId host_id = GetHostForUrl(kPrerenderingUrl);
WaitForPrerenderLoadCompletion(host_id);
test::PrerenderHostObserver host_observer(*web_contents(), host_id);
RenderFrameHostImpl* prerender_rfh = GetPrerenderedMainFrameHost(host_id);
ASSERT_TRUE(ExecJs(prerender_rfh, "window.close()"));
host_observer.WaitForDestroyed();
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kWindowClosed);
EXPECT_TRUE(ExecJs(web_contents(), ""));
}
IN_PROC_BROWSER_TEST_F(PrerenderTargetHintBrowserTest,
WindowClosedSpeculationRules_WithTargetHintBlank) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
const GURL kPrerenderingUrl = embedded_test_server()->GetURL("/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
FrameTreeNodeId host_id = prerender_helper()->AddPrerender(
kPrerenderingUrl, std::nullopt, "_blank");
auto* prerender_web_contents = WebContents::FromFrameTreeNodeId(host_id);
ASSERT_NE(prerender_web_contents, web_contents_impl());
ExpectWebContentsIsForNewTabPrerendering(*prerender_web_contents);
test::PrerenderHostObserver host_observer(*prerender_web_contents, host_id);
RenderFrameHost* prerender_rfh =
test::PrerenderTestHelper::GetPrerenderedMainFrameHost(
*prerender_web_contents, host_id);
ASSERT_TRUE(ExecJs(prerender_rfh, "window.close()"));
host_observer.WaitForDestroyed();
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kWindowClosed);
EXPECT_TRUE(ExecJs(web_contents(), ""));
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, SlowNetwork) {
MockClientHintsControllerDelegate client_hints_controller_delegate(
GetShellUserAgentMetadata());
ShellContentBrowserClient::Get()
->browser_context()
->set_client_hints_controller_delegate(&client_hints_controller_delegate);
network::NetworkQualityTracker& network_quality_tracker =
*client_hints_controller_delegate.GetNetworkQualityTracker();
base::TimeDelta http_rtt =
base::Milliseconds(1) +
::features::kSuppressesPrerenderingOnSlowNetworkThreshold.Get();
network_quality_tracker.ReportRTTsAndThroughputForTesting(
http_rtt, network_quality_tracker.GetDownstreamThroughputKbps());
ASSERT_TRUE(embedded_test_server()->Start());
GURL initial_url = GetUrl("/empty.html");
GURL prerendering_url = GetUrl("/empty.html?prerender");
test::PrerenderHostRegistryObserver observer(*web_contents_impl());
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
AddPrerenderAsync(prerendering_url);
observer.WaitForTrigger(prerendering_url);
EXPECT_FALSE(HasHostForUrl(prerendering_url));
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kSlowNetwork);
NavigatePrimaryPage(prerendering_url);
ExpectPreloadingAttemptUkm({attempt_ukm_entry_builder().BuildEntry(
PrimaryPageSourceId(), PreloadingType::kPrerender,
PreloadingEligibility::kSlowNetwork,
PreloadingHoldbackStatus::kUnspecified,
PreloadingTriggeringOutcome::kUnspecified,
PreloadingFailureReason::kUnspecified,
true,
std::nullopt,
blink::mojom::SpeculationEagerness::kImmediate)});
}
class V8OptimizerContentBrowserClient
: public ContentBrowserTestContentBrowserClient {
public:
explicit V8OptimizerContentBrowserClient(bool enable) : enable_(enable) {}
~V8OptimizerContentBrowserClient() override = default;
bool AreV8OptimizationsEnabledForSite(
BrowserContext* browser_context,
const std::optional<base::SafeRef<content::ProcessSelectionUserData>>&
process_selection_user_data,
const GURL& site_url) override {
return enable_;
}
public:
const bool enable_ = true;
};
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderCOOPWithoutV8Optimizer) {
V8OptimizerContentBrowserClient test_browser_client(false);
const GURL initial_url = GetUrl("/empty.html");
const GURL prerendering_url =
GetUrl("/set-header?Cross-Origin-Opener-Policy: same-origin");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), initial_url);
FrameTreeNodeId host_id = AddPrerender(prerendering_url);
test::PrerenderHostObserver prerender_observer(*web_contents(), host_id);
NavigatePrimaryPage(prerendering_url);
prerender_observer.WaitForActivation();
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kActivated);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
PrerenderNonCOOPWithoutV8Optimizer) {
V8OptimizerContentBrowserClient test_browser_client(false);
const GURL initial_url = GetUrl("/empty.html");
const GURL prerendering_url = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), initial_url);
FrameTreeNodeId host_id = AddPrerender(prerendering_url);
test::PrerenderHostObserver prerender_observer(*web_contents(), host_id);
NavigatePrimaryPage(prerendering_url);
prerender_observer.WaitForActivation();
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kActivated);
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderCOOPWithV8Optimizer) {
V8OptimizerContentBrowserClient test_browser_client(true);
const GURL initial_url = GetUrl("/empty.html");
const GURL prerendering_url =
GetUrl("/set-header?Cross-Origin-Opener-Policy: same-origin");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
ASSERT_EQ(web_contents()->GetLastCommittedURL(), initial_url);
FrameTreeNodeId host_id = AddPrerender(prerendering_url);
ASSERT_TRUE(host_id);
NavigatePrimaryPage(prerendering_url);
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kActivated);
}
class PrerenderSessionHistoryBrowserTest
: public PrerenderBrowserTest,
public testing::WithParamInterface<bool> {
public:
static std::string DescribeParams(
const testing::TestParamInfo<ParamType>& info) {
return info.param ? "FromBrowser" : "FromRenderer";
}
void NavigateAway(WebContentsImpl* web_contents, const GURL& url) {
const bool from_browser = GetParam();
if (from_browser) {
ASSERT_TRUE(NavigateToURL(web_contents, url));
} else {
ASSERT_TRUE(NavigateToURLFromRenderer(web_contents, url));
}
}
void PerformInitialNavigations(WebContentsImpl* web_contents,
const GURL& url1,
const GURL& url2) {
ASSERT_TRUE(NavigateToURL(web_contents, url1));
NavigateAway(web_contents, url2);
}
void PredictBackNavigation(WebContentsImpl* web_contents) {
PrerenderHostRegistry* registry = web_contents->GetPrerenderHostRegistry();
const auto predictor = content_preloading_predictor::kMouseBackButton;
registry->BackNavigationLikely(predictor);
WaitForHttpCacheQueryCompletion(web_contents);
}
void PerformBackNavigation(WebContentsImpl* web_contents) {
NavigationControllerImpl& controller = web_contents->GetController();
ASSERT_TRUE(controller.CanGoBack());
TestNavigationObserver back_observer(web_contents);
controller.GoBack();
back_observer.Wait();
}
void WaitForHttpCacheQueryCompletion(WebContentsImpl* web_contents) {
PrerenderHostRegistry* registry = web_contents->GetPrerenderHostRegistry();
EXPECT_TRUE(base::test::RunUntil(
[&]() { return !registry->HasOngoingHttpCacheQueryForTesting(); }));
}
void ClearBackForwardCache(WebContentsImpl* web_contents) {
web_contents->GetController().GetBackForwardCache().Flush();
}
void ClearAllCaches(WebContentsImpl* web_contents) {
BrowsingDataRemover* cache_remover =
web_contents->GetBrowserContext()->GetBrowsingDataRemover();
BrowsingDataRemoverCompletionObserver cache_clear_completion_observer(
cache_remover);
cache_remover->RemoveAndReply(
base::Time::Min(), base::Time::Max(),
BrowsingDataRemover::DATA_TYPE_CACHE,
BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB,
&cache_clear_completion_observer);
cache_clear_completion_observer.BlockUntilCompletion();
}
void ExpectAttemptUkm(ukm::TestUkmRecorder& ukm_recorder,
bool accurate,
PreloadingEligibility eligibility,
ukm::SourceId source_id) {
std::vector<ukm::TestUkmRecorder::HumanReadableUkmEntry> attempts =
ukm_recorder.GetEntries(ukm::builders::Preloading_Attempt::kEntryName,
test::kPreloadingAttemptUkmMetrics);
ASSERT_EQ(attempts.size(), 1u);
const auto predictor = content_preloading_predictor::kMouseBackButton;
const PreloadingHoldbackStatus holdback_status =
eligibility == PreloadingEligibility::kEligible
? PreloadingHoldbackStatus::kAllowed
: PreloadingHoldbackStatus::kUnspecified;
const PreloadingTriggeringOutcome triggering_outcome =
eligibility == PreloadingEligibility::kEligible
? PreloadingTriggeringOutcome::kNoOp
: PreloadingTriggeringOutcome::kUnspecified;
test::PreloadingAttemptUkmEntryBuilder entry_builder(predictor);
ukm::TestUkmRecorder::HumanReadableUkmEntry expected_entry =
entry_builder.BuildEntry(
source_id, PreloadingType::kPrerender, eligibility, holdback_status,
triggering_outcome, PreloadingFailureReason::kUnspecified,
accurate);
EXPECT_EQ(attempts[0], expected_entry)
<< test::ActualVsExpectedUkmEntryToString(attempts[0], expected_entry);
}
};
INSTANTIATE_TEST_SUITE_P(All,
PrerenderSessionHistoryBrowserTest,
testing::Bool(),
PrerenderSessionHistoryBrowserTest::DescribeParams);
IN_PROC_BROWSER_TEST_P(PrerenderSessionHistoryBrowserTest,
BackButtonNavigation) {
const GURL url1 = GetUrl("/title1.html");
const GURL url2 = GetCrossSiteUrl("/title2.html");
PerformInitialNavigations(web_contents_impl(), url1, url2);
ClearBackForwardCache(web_contents_impl());
base::HistogramTester histogram_tester;
NavigationControllerImpl& controller = web_contents_impl()->GetController();
ASSERT_TRUE(controller.CanGoBack());
TestNavigationObserver back_observer(web_contents_impl());
InputEventAckWaiter mouse_down_waiter(
web_contents_impl()->GetPrimaryMainFrame()->GetRenderWidgetHost(),
blink::WebInputEvent::Type::kMouseDown);
const gfx::Point click_location(50, 50);
SimulateMouseEvent(web_contents_impl(),
blink::WebInputEvent::Type::kMouseDown,
blink::WebMouseEvent::Button::kBack, click_location);
mouse_down_waiter.Wait();
WaitForHttpCacheQueryCompletion(web_contents_impl());
SimulateMouseEvent(web_contents_impl(), blink::WebInputEvent::Type::kMouseUp,
blink::WebMouseEvent::Button::kBack, click_location);
back_observer.Wait();
histogram_tester.ExpectUniqueSample(
"Preloading.PrerenderBackNavigationEligibility.MouseBackButton",
PrerenderBackNavigationEligibility::kEligible, 1);
}
IN_PROC_BROWSER_TEST_P(PrerenderSessionHistoryBrowserTest,
PredictionForEligibleBackNavigation) {
const GURL url1 = GetUrl("/title1.html");
const GURL url2 = GetCrossSiteUrl("/title2.html");
PerformInitialNavigations(web_contents_impl(), url1, url2);
ClearBackForwardCache(web_contents_impl());
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
PredictBackNavigation(web_contents_impl());
PerformBackNavigation(web_contents_impl());
ukm::SourceId source_id =
web_contents_impl()->GetPrimaryMainFrame()->GetPageUkmSourceId();
histogram_tester.ExpectUniqueSample(
"Preloading.PrerenderBackNavigationEligibility.MouseBackButton",
PrerenderBackNavigationEligibility::kEligible, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Predictor.MouseBackButton.Precision",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Predictor.MouseBackButton.Recall",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Prerender.Attempt.MouseBackButton.Precision",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Prerender.Attempt.MouseBackButton.Recall",
PredictorConfusionMatrix::kTruePositive, 1);
ExpectAttemptUkm(ukm_recorder, true, PreloadingEligibility::kEligible,
source_id);
}
IN_PROC_BROWSER_TEST_P(PrerenderSessionHistoryBrowserTest,
NoPredictionDueToBfcache) {
if (!BackForwardCache::IsBackForwardCacheFeatureEnabled()) {
GTEST_SKIP()
<< "This test assumes the back navigation is restoring from bfcache.";
}
const GURL url1 = GetUrl("/title1.html");
const GURL url2 = GetCrossSiteUrl("/title2.html");
PerformInitialNavigations(web_contents_impl(), url1, url2);
base::HistogramTester histogram_tester;
PredictBackNavigation(web_contents_impl());
PerformBackNavigation(web_contents_impl());
histogram_tester.ExpectUniqueSample(
"Preloading.PrerenderBackNavigationEligibility.MouseBackButton",
PrerenderBackNavigationEligibility::kBfcacheEntryExists, 1);
histogram_tester.ExpectTotalCount(
"Preloading.Predictor.MouseBackButton.Precision", 0);
histogram_tester.ExpectTotalCount(
"Preloading.Predictor.MouseBackButton.Recall", 0);
histogram_tester.ExpectTotalCount(
"Preloading.Prerender.Attempt.MouseBackButton.Precision", 0);
histogram_tester.ExpectTotalCount(
"Preloading.Prerender.Attempt.MouseBackButton.Recall", 0);
}
IN_PROC_BROWSER_TEST_P(PrerenderSessionHistoryBrowserTest,
RendererNavigationAfterBackPrediction) {
const GURL url1 = GetUrl("/title1.html");
const GURL url2 = GetCrossSiteUrl("/title2.html");
const GURL url3 = GetCrossSiteUrl("/title3.html");
PerformInitialNavigations(web_contents_impl(), url1, url2);
ClearBackForwardCache(web_contents_impl());
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
PredictBackNavigation(web_contents_impl());
TestNavigationObserver nav_observer(web_contents_impl());
ASSERT_TRUE(ExecJs(web_contents_impl(), JsReplace("location = $1;", url3)));
nav_observer.Wait();
ukm::SourceId source_id =
web_contents_impl()->GetPrimaryMainFrame()->GetPageUkmSourceId();
histogram_tester.ExpectUniqueSample(
"Preloading.PrerenderBackNavigationEligibility.MouseBackButton",
PrerenderBackNavigationEligibility::kEligible, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Predictor.MouseBackButton.Precision",
PredictorConfusionMatrix::kFalsePositive, 1);
histogram_tester.ExpectTotalCount(
"Preloading.Predictor.MouseBackButton.Recall", 0);
histogram_tester.ExpectUniqueSample(
"Preloading.Prerender.Attempt.MouseBackButton.Precision",
PredictorConfusionMatrix::kFalsePositive, 1);
histogram_tester.ExpectTotalCount(
"Preloading.Prerender.Attempt.MouseBackButton.Recall", 0);
ExpectAttemptUkm(ukm_recorder, false, PreloadingEligibility::kEligible,
source_id);
}
IN_PROC_BROWSER_TEST_P(PrerenderSessionHistoryBrowserTest,
NotEligibleForSameDocument) {
const GURL url1 = GetUrl("/title1.html");
const GURL url2 = GetUrl("/title1.html#same");
PerformInitialNavigations(web_contents_impl(), url1, url2);
base::HistogramTester histogram_tester;
PredictBackNavigation(web_contents_impl());
PerformBackNavigation(web_contents_impl());
histogram_tester.ExpectUniqueSample(
"Preloading.PrerenderBackNavigationEligibility.MouseBackButton",
PrerenderBackNavigationEligibility::kTargetIsSameDocument, 1);
histogram_tester.ExpectTotalCount(
"Preloading.Predictor.MouseBackButton.Precision", 0);
histogram_tester.ExpectTotalCount(
"Preloading.Predictor.MouseBackButton.Recall", 0);
histogram_tester.ExpectTotalCount(
"Preloading.Prerender.Attempt.MouseBackButton.Precision", 0);
histogram_tester.ExpectTotalCount(
"Preloading.Prerender.Attempt.MouseBackButton.Recall", 0);
}
IN_PROC_BROWSER_TEST_P(PrerenderSessionHistoryBrowserTest,
NotEligibleForSameSite) {
const GURL url1 = GetUrl("/title1.html");
const GURL url2 = GetSameSiteCrossOriginUrl("/title2.html");
PerformInitialNavigations(web_contents_impl(), url1, url2);
ClearBackForwardCache(web_contents_impl());
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
PredictBackNavigation(web_contents_impl());
PerformBackNavigation(web_contents_impl());
ukm::SourceId source_id =
web_contents_impl()->GetPrimaryMainFrame()->GetPageUkmSourceId();
histogram_tester.ExpectUniqueSample(
"Preloading.PrerenderBackNavigationEligibility.MouseBackButton",
PrerenderBackNavigationEligibility::kTargetIsSameSite, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Predictor.MouseBackButton.Precision",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Predictor.MouseBackButton.Recall",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Prerender.Attempt.MouseBackButton.Precision",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Prerender.Attempt.MouseBackButton.Recall",
PredictorConfusionMatrix::kTruePositive, 1);
ExpectAttemptUkm(ukm_recorder, true,
ToPreloadingEligibility(
PrerenderBackNavigationEligibility::kTargetIsSameSite),
source_id);
}
IN_PROC_BROWSER_TEST_P(PrerenderSessionHistoryBrowserTest,
NotEligibleForUncached) {
const GURL url1 = GetUrl("/title1.html");
const GURL url2 = GetCrossSiteUrl("/title2.html");
PerformInitialNavigations(web_contents_impl(), url1, url2);
ClearAllCaches(web_contents_impl());
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
PredictBackNavigation(web_contents_impl());
PerformBackNavigation(web_contents_impl());
ukm::SourceId source_id =
web_contents_impl()->GetPrimaryMainFrame()->GetPageUkmSourceId();
histogram_tester.ExpectUniqueSample(
"Preloading.PrerenderBackNavigationEligibility.MouseBackButton",
PrerenderBackNavigationEligibility::kNoHttpCacheEntry, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Predictor.MouseBackButton.Precision",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Predictor.MouseBackButton.Recall",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Prerender.Attempt.MouseBackButton.Precision",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Prerender.Attempt.MouseBackButton.Recall",
PredictorConfusionMatrix::kTruePositive, 1);
ExpectAttemptUkm(ukm_recorder, true,
ToPreloadingEligibility(
PrerenderBackNavigationEligibility::kNoHttpCacheEntry),
source_id);
}
IN_PROC_BROWSER_TEST_P(PrerenderSessionHistoryBrowserTest,
NotEligibleForPostMethod) {
const GURL url1 = GetUrl("/form_that_posts_to_echoall.html");
const GURL url2 = GetUrl("/echoall");
const GURL url3 = GetCrossSiteUrl("/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), url1));
TestNavigationObserver form_post_observer(web_contents_impl());
ASSERT_TRUE(
ExecJs(web_contents_impl(), "document.getElementById('form').submit();"));
form_post_observer.Wait();
ASSERT_EQ(url2, web_contents_impl()->GetLastCommittedURL());
ASSERT_TRUE(web_contents_impl()
->GetController()
.GetLastCommittedEntry()
->GetHasPostData());
NavigateAway(web_contents_impl(), url3);
ClearBackForwardCache(web_contents_impl());
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
PredictBackNavigation(web_contents_impl());
PerformBackNavigation(web_contents_impl());
ukm::SourceId source_id =
web_contents_impl()->GetPrimaryMainFrame()->GetPageUkmSourceId();
histogram_tester.ExpectUniqueSample(
"Preloading.PrerenderBackNavigationEligibility.MouseBackButton",
PrerenderBackNavigationEligibility::kMethodNotGet, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Predictor.MouseBackButton.Precision",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectTotalCount(
"Preloading.Predictor.MouseBackButton.Recall", 0);
histogram_tester.ExpectUniqueSample(
"Preloading.Prerender.Attempt.MouseBackButton.Precision",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectTotalCount(
"Preloading.Prerender.Attempt.MouseBackButton.Recall", 0);
ExpectAttemptUkm(ukm_recorder, true,
ToPreloadingEligibility(
PrerenderBackNavigationEligibility::kMethodNotGet),
source_id);
}
IN_PROC_BROWSER_TEST_P(PrerenderSessionHistoryBrowserTest,
NotEligibleForFailedNavigation) {
const GURL url1 = GetUrl("/page404.html");
const GURL url2 = GetCrossSiteUrl("/title1.html");
PerformInitialNavigations(web_contents_impl(), url1, url2);
ClearBackForwardCache(web_contents_impl());
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
PredictBackNavigation(web_contents_impl());
PerformBackNavigation(web_contents_impl());
ukm::SourceId source_id =
web_contents_impl()->GetPrimaryMainFrame()->GetPageUkmSourceId();
histogram_tester.ExpectUniqueSample(
"Preloading.PrerenderBackNavigationEligibility.MouseBackButton",
PrerenderBackNavigationEligibility::kTargetIsFailedNavigation, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Predictor.MouseBackButton.Precision",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Predictor.MouseBackButton.Recall",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Prerender.Attempt.MouseBackButton.Precision",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Prerender.Attempt.MouseBackButton.Recall",
PredictorConfusionMatrix::kTruePositive, 1);
ExpectAttemptUkm(
ukm_recorder, true,
ToPreloadingEligibility(
PrerenderBackNavigationEligibility::kTargetIsFailedNavigation),
source_id);
}
IN_PROC_BROWSER_TEST_P(PrerenderSessionHistoryBrowserTest,
NotEligibleForNonHttpScheme) {
const GURL url1 = GURL("data:text/html,test");
const GURL url2 = GetUrl("/title1.html");
PerformInitialNavigations(web_contents_impl(), url1, url2);
ClearBackForwardCache(web_contents_impl());
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
PredictBackNavigation(web_contents_impl());
PerformBackNavigation(web_contents_impl());
ukm::SourceId source_id =
web_contents_impl()->GetPrimaryMainFrame()->GetPageUkmSourceId();
histogram_tester.ExpectUniqueSample(
"Preloading.PrerenderBackNavigationEligibility.MouseBackButton",
PrerenderBackNavigationEligibility::kTargetIsNonHttp, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Predictor.MouseBackButton.Precision",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectTotalCount(
"Preloading.Predictor.MouseBackButton.Recall", 0);
histogram_tester.ExpectUniqueSample(
"Preloading.Prerender.Attempt.MouseBackButton.Precision",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectTotalCount(
"Preloading.Prerender.Attempt.MouseBackButton.Recall", 0);
ExpectAttemptUkm(ukm_recorder, true,
ToPreloadingEligibility(
PrerenderBackNavigationEligibility::kTargetIsNonHttp),
source_id);
}
bool IsScriptable(WebContentsImpl* opener, WebContentsImpl* openee) {
const std::string kPropName = "mrPostman";
const std::string kPropValue = "a property for me";
if (EvalJs(opener, JsReplace(R"((() => {
let result = '';
try {
newWindow[$1] = $2;
result = newWindow[$1] || '';
} catch {}
return result;
})();)",
kPropName, kPropValue))
.ExtractString() != kPropValue) {
return false;
}
return EvalJs(openee, JsReplace("window[$1] || '';", kPropName))
.ExtractString() == kPropValue;
}
IN_PROC_BROWSER_TEST_P(PrerenderSessionHistoryBrowserTest,
NotEligibleForRelatedActiveContents) {
const GURL url1 = GetUrl("/title1.html");
const GURL url2 = GetCrossSiteUrl("/title2.html");
ASSERT_TRUE(NavigateToURL(shell(), url1));
RenderFrameHostImplWrapper opener_rfh(current_frame_host());
EXPECT_EQ(1u, opener_rfh->GetSiteInstance()->GetRelatedActiveContentsCount());
ShellAddedObserver shell_observer;
EXPECT_TRUE(
ExecJs(shell(), JsReplace("window.newWindow = window.open($1);", url1)));
Shell* popup = shell_observer.GetShell();
WebContentsImpl* popup_contents =
static_cast<WebContentsImpl*>(popup->web_contents());
EXPECT_TRUE(WaitForLoadStop(popup_contents));
EXPECT_TRUE(IsScriptable(web_contents_impl(), popup_contents));
EXPECT_EQ(2u, opener_rfh->GetSiteInstance()->GetRelatedActiveContentsCount());
NavigateAway(popup_contents, url2);
ClearBackForwardCache(popup_contents);
EXPECT_FALSE(IsScriptable(web_contents_impl(), popup_contents));
RenderFrameHostImplWrapper cross_site_popup_rfh(
popup_contents->GetPrimaryMainFrame());
if (cross_site_popup_rfh->GetSiteInstance() ==
opener_rfh->GetSiteInstance()) {
EXPECT_EQ(2u,
opener_rfh->GetSiteInstance()->GetRelatedActiveContentsCount());
} else if (cross_site_popup_rfh->GetSiteInstance()->IsRelatedSiteInstance(
opener_rfh->GetSiteInstance())) {
EXPECT_EQ(2u,
opener_rfh->GetSiteInstance()->GetRelatedActiveContentsCount());
} else {
EXPECT_EQ(1u,
opener_rfh->GetSiteInstance()->GetRelatedActiveContentsCount());
}
SiteInstanceImpl* target_site_instance =
popup_contents->GetController().GetEntryAtOffset(-1)->site_instance();
EXPECT_TRUE(opener_rfh->GetSiteInstance()->IsRelatedSiteInstance(
target_site_instance));
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
PredictBackNavigation(popup_contents);
PerformBackNavigation(popup_contents);
EXPECT_TRUE(IsScriptable(web_contents_impl(), popup_contents));
EXPECT_EQ(2u, opener_rfh->GetSiteInstance()->GetRelatedActiveContentsCount());
ukm::SourceId source_id =
popup_contents->GetPrimaryMainFrame()->GetPageUkmSourceId();
histogram_tester.ExpectUniqueSample(
"Preloading.PrerenderBackNavigationEligibility.MouseBackButton",
PrerenderBackNavigationEligibility::kRelatedActiveContents, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Predictor.MouseBackButton.Precision",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Predictor.MouseBackButton.Recall",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Prerender.Attempt.MouseBackButton.Precision",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Prerender.Attempt.MouseBackButton.Recall",
PredictorConfusionMatrix::kTruePositive, 1);
ExpectAttemptUkm(
ukm_recorder, true,
ToPreloadingEligibility(
PrerenderBackNavigationEligibility::kRelatedActiveContents),
source_id);
}
IN_PROC_BROWSER_TEST_P(PrerenderSessionHistoryBrowserTest,
PredictAfterOpeneeDestroyed) {
const GURL url1 = GetUrl("/title1.html");
const GURL url2 = GetCrossSiteUrl("/title2.html");
ASSERT_TRUE(NavigateToURL(shell(), url1));
RenderFrameHostImplWrapper opener_rfh(current_frame_host());
ShellAddedObserver shell_observer;
EXPECT_TRUE(
ExecJs(shell(), JsReplace("window.newWindow = window.open($1);", url1)));
Shell* popup = shell_observer.GetShell();
WebContentsImpl* popup_contents =
static_cast<WebContentsImpl*>(popup->web_contents());
EXPECT_TRUE(WaitForLoadStop(popup_contents));
EXPECT_EQ(2u, opener_rfh->GetSiteInstance()->GetRelatedActiveContentsCount());
NavigateAway(web_contents_impl(), url2);
ClearBackForwardCache(web_contents_impl());
WebContentsDestroyedWatcher close_popup_waiter(popup_contents);
popup_contents->ClosePage();
close_popup_waiter.Wait();
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
PredictBackNavigation(web_contents_impl());
PerformBackNavigation(web_contents_impl());
ukm::SourceId source_id =
web_contents_impl()->GetPrimaryMainFrame()->GetPageUkmSourceId();
histogram_tester.ExpectUniqueSample(
"Preloading.PrerenderBackNavigationEligibility.MouseBackButton",
PrerenderBackNavigationEligibility::kEligible, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Predictor.MouseBackButton.Precision",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Predictor.MouseBackButton.Recall",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Prerender.Attempt.MouseBackButton.Precision",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Prerender.Attempt.MouseBackButton.Recall",
PredictorConfusionMatrix::kTruePositive, 1);
ExpectAttemptUkm(ukm_recorder, true, PreloadingEligibility::kEligible,
source_id);
}
IN_PROC_BROWSER_TEST_P(PrerenderSessionHistoryBrowserTest,
BackNavigationOfCloneWebContents) {
const GURL url1 = GetUrl("/title1.html");
const GURL url2 = GetCrossSiteUrl("/title2.html");
PerformInitialNavigations(web_contents_impl(), url1, url2);
SiteInstanceImpl* prev_site_instance = web_contents_impl()
->GetController()
.GetEntryAtOffset(-1)
->site_instance();
const bool original_navs_swapped_browsing_instance =
!web_contents_impl()->GetSiteInstance()->IsRelatedSiteInstance(
prev_site_instance);
FakeWebContentsDelegate clone_delegate;
std::unique_ptr<WebContents> new_web_contents_owned =
web_contents_impl()->Clone();
WebContentsImpl* new_web_contents =
static_cast<WebContentsImpl*>(new_web_contents_owned.get());
new_web_contents->SetDelegate(&clone_delegate);
TestNavigationObserver clone_load_observer(new_web_contents);
new_web_contents->GetController().LoadIfNecessary();
clone_load_observer.Wait();
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
PredictBackNavigation(new_web_contents);
PerformBackNavigation(new_web_contents);
const PrerenderBackNavigationEligibility expected_eligibility =
original_navs_swapped_browsing_instance
? PrerenderBackNavigationEligibility::kEligible
: PrerenderBackNavigationEligibility::kRelatedActiveContents;
ukm::SourceId source_id =
new_web_contents->GetPrimaryMainFrame()->GetPageUkmSourceId();
histogram_tester.ExpectUniqueSample(
"Preloading.PrerenderBackNavigationEligibility.MouseBackButton",
expected_eligibility, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Predictor.MouseBackButton.Precision",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Predictor.MouseBackButton.Recall",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Prerender.Attempt.MouseBackButton.Precision",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Prerender.Attempt.MouseBackButton.Recall",
PredictorConfusionMatrix::kTruePositive, 1);
ExpectAttemptUkm(ukm_recorder, true,
ToPreloadingEligibility(expected_eligibility), source_id);
}
IN_PROC_BROWSER_TEST_P(
PrerenderSessionHistoryBrowserTest,
BackNavigationOfClonedWebContentsWithOriginalAtTargetEntry) {
const GURL url1 = GetUrl("/title1.html");
const GURL url2 = GetCrossSiteUrl("/title2.html");
PerformInitialNavigations(web_contents_impl(), url1, url2);
FakeWebContentsDelegate clone_delegate;
std::unique_ptr<WebContents> new_web_contents_owned =
web_contents_impl()->Clone();
WebContentsImpl* new_web_contents =
static_cast<WebContentsImpl*>(new_web_contents_owned.get());
new_web_contents->SetDelegate(&clone_delegate);
TestNavigationObserver clone_load_observer(new_web_contents);
new_web_contents->GetController().LoadIfNecessary();
clone_load_observer.Wait();
PerformBackNavigation(web_contents_impl());
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
PredictBackNavigation(new_web_contents);
PerformBackNavigation(new_web_contents);
ukm::SourceId source_id =
new_web_contents->GetPrimaryMainFrame()->GetPageUkmSourceId();
histogram_tester.ExpectUniqueSample(
"Preloading.PrerenderBackNavigationEligibility.MouseBackButton",
PrerenderBackNavigationEligibility::kRelatedActiveContents, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Predictor.MouseBackButton.Precision",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Predictor.MouseBackButton.Recall",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Prerender.Attempt.MouseBackButton.Precision",
PredictorConfusionMatrix::kTruePositive, 1);
histogram_tester.ExpectUniqueSample(
"Preloading.Prerender.Attempt.MouseBackButton.Recall",
PredictorConfusionMatrix::kTruePositive, 1);
ExpectAttemptUkm(
ukm_recorder, true,
ToPreloadingEligibility(
PrerenderBackNavigationEligibility::kRelatedActiveContents),
source_id);
}
IN_PROC_BROWSER_TEST_P(
PrerenderSessionHistoryBrowserTest,
BackButtonNavigationDoesNotUseSpeculationRulePrerenders) {
const GURL url1 = GetUrl("/title1.html");
const GURL url2 = GetUrl("/title2.html");
PerformInitialNavigations(web_contents_impl(), url1, url2);
ClearBackForwardCache(web_contents_impl());
FrameTreeNodeId host_id = AddPrerender(url1);
test::PrerenderHostObserver prerender_observer(*web_contents(), host_id);
PerformBackNavigation(web_contents_impl());
EXPECT_FALSE(prerender_observer.was_activated());
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, WarmingUpCCDoesntInvokeCrashes) {
const GURL initial_url = GetUrl("/empty.html");
const GURL prerendering_url = GetUrl("/empty.html?prerender");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
auto prerender_handle = AddEmbedderTriggeredPrerender(
prerendering_url, nullptr,
true);
FrameTreeNodeId prerender_host_id =
static_cast<PrerenderHandleImpl*>(prerender_handle.get())
->frame_tree_node_id_for_testing();
test::PrerenderHostObserver prerender_observer(*web_contents(),
prerender_host_id);
NavigatePrimaryPageFromAddressBar(prerendering_url);
prerender_observer.WaitForActivation();
EXPECT_EQ(web_contents()->GetLastCommittedURL(), prerendering_url);
EXPECT_TRUE(prerender_observer.was_activated());
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
PrerenderHostIdAssignedToNavigationRequest) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL initial_url = GetUrl("/empty.html");
const GURL prerendering_url = GetUrl("/page_with_iframe.html");
const GURL subframe_url = GetUrl("/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
TestNavigationManager main_navigation_manager(web_contents(),
prerendering_url);
TestNavigationManager subframe_navigation_manager(web_contents(),
subframe_url);
AddPrerenderAsync(prerendering_url);
EXPECT_TRUE(main_navigation_manager.WaitForResponse());
auto* main_request =
NavigationRequest::From(main_navigation_manager.GetNavigationHandle());
PrerenderHostId prerender_host_id = main_request->GetPrerenderHostId();
EXPECT_TRUE(prerender_host_id);
main_navigation_manager.ResumeNavigation();
EXPECT_TRUE(
subframe_navigation_manager.WaitForFirstYieldAfterDidStartNavigation());
auto* subframe_request = NavigationRequest::From(
subframe_navigation_manager.GetNavigationHandle());
EXPECT_EQ(subframe_request->GetPrerenderHostId(), prerender_host_id);
EXPECT_TRUE(main_navigation_manager.WaitForNavigationFinished());
test::PrerenderHostObserver prerender_observer(*web_contents_impl(),
prerendering_url);
prerender_helper()->NavigatePrimaryPageAsync(prerendering_url);
EXPECT_TRUE(subframe_navigation_manager.WaitForNavigationFinished());
prerender_observer.WaitForActivation();
}
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
ProcessIsOnlyHostingPrerenderedFramesOrEmpty) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL initial_url =
embedded_test_server()->GetURL("b.test", "/page_with_iframe.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
RenderFrameHost* original_subframe_host =
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0);
RenderProcessHost* original_subframe_process =
original_subframe_host->GetProcess();
ASSERT_TRUE(original_subframe_process);
const GURL prerender_url1 =
embedded_test_server()->GetURL("a.test", "/title1.html");
auto prerender_handle1 = AddEmbedderTriggeredPrerenderAsync(prerender_url1);
prerender_helper()->WaitForPrerenderLoadCompletion(prerender_url1);
FrameTreeNodeId host_id1 = prerender_helper()->GetHostForUrl(prerender_url1);
ASSERT_NE(host_id1, FrameTreeNodeId());
RenderProcessHostImpl* process1 =
static_cast<RenderProcessHostImpl*>(GetProcessForPrerenderHost(host_id1));
ASSERT_TRUE(process1);
EXPECT_TRUE(process1->IsOnlyHostingPrerenderedFramesOrEmpty());
WebContents* active_web_contents = web_contents();
const GURL navigation_url =
embedded_test_server()->GetURL("a.test", "/title2.html");
EXPECT_TRUE(
NavigateIframeToURL(active_web_contents, "test_iframe", navigation_url));
RenderFrameHost* subframe_host =
ChildFrameAt(active_web_contents->GetPrimaryMainFrame(), 0);
ASSERT_TRUE(subframe_host);
RenderProcessHostImpl* process2 =
static_cast<RenderProcessHostImpl*>(subframe_host->GetProcess());
if (AreAllSitesIsolatedForTesting()) {
EXPECT_EQ(process1, process2);
EXPECT_FALSE(process1->IsOnlyHostingPrerenderedFramesOrEmpty());
} else {
EXPECT_EQ(original_subframe_process, process2);
EXPECT_TRUE(process1->IsOnlyHostingPrerenderedFramesOrEmpty());
}
RenderFrameDeletedObserver delete_observer(subframe_host);
const std::string remove_iframe_script = R"(
const subframe = document.getElementById('test_iframe');
subframe.remove();
)";
EXPECT_TRUE(ExecJs(shell(), remove_iframe_script));
delete_observer.WaitUntilDeleted();
EXPECT_TRUE(process1->IsOnlyHostingPrerenderedFramesOrEmpty());
}
class PrerenderProcessReuseBrowserTest : public PrerenderBrowserTest {
public:
PrerenderProcessReuseBrowserTest() {
feature_list_.InitWithFeatures(
{features::kReusePrerenderingProcessForMainFrames},
{features::kProcessPerSiteUpToMainFrameThreshold});
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(PrerenderProcessReuseBrowserTest,
ReusePrerenderProcessInNavigation) {
if (!AreAllSitesIsolatedForTesting()) {
return;
}
base::HistogramTester histogram_tester;
ASSERT_TRUE(embedded_test_server()->Start());
const GURL initial_url =
embedded_test_server()->GetURL("b.test", "/page_with_iframe.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
const GURL prerender_url =
embedded_test_server()->GetURL("a.test", "/title1.html");
std::unique_ptr<PrerenderHandle> prerender_handle1 =
AddEmbedderTriggeredPrerenderAsync(prerender_url);
prerender_helper()->WaitForPrerenderLoadCompletion(prerender_url);
FrameTreeNodeId prerender_host_id =
prerender_helper()->GetHostForUrl(prerender_url);
ASSERT_TRUE(prerender_host_id);
RenderProcessHostImpl* prerender_process =
static_cast<RenderProcessHostImpl*>(
GetProcessForPrerenderHost(prerender_host_id));
ASSERT_TRUE(prerender_process);
ASSERT_TRUE(prerender_process->IsOnlyHostingPrerenderedFramesOrEmpty());
const GURL navigation_url =
embedded_test_server()->GetURL("a.test", "/title2.html");
EXPECT_TRUE(NavigateToURL(shell(), navigation_url));
RenderProcessHost* navigation_process = current_frame_host()->GetProcess();
EXPECT_EQ(navigation_process, prerender_process);
histogram_tester.ExpectUniqueSample(
"BrowserRenderProcessHost.ReuseExistingProcess.ReusePolicy",
ProcessReusePolicy::kReusePrerenderingProcessForMainFrame, 1);
const GURL new_window_url =
embedded_test_server()->GetURL("a.test", "/red.html");
Shell* new_window_shell =
Shell::CreateNewWindow(shell()->web_contents()->GetBrowserContext(),
initial_url, nullptr, gfx::Size());
EXPECT_TRUE(NavigateToURL(new_window_shell, navigation_url));
FrameTreeNode* new_window_root =
static_cast<WebContentsImpl*>(new_window_shell->web_contents())
->GetPrimaryFrameTree()
.root();
RenderFrameHostImpl* new_window_rfh = new_window_root->current_frame_host();
RenderProcessHost* new_window_process = new_window_rfh->GetProcess();
ASSERT_TRUE(new_window_process);
EXPECT_NE(new_window_process, prerender_process);
}
class PrerenderUntilScriptBaseBrowserTest
: public PrerenderTargetHintBrowserTest {
public:
void StartPrerenderUntilScript(const GURL& prerender_url) {
test::PrerenderHostRegistryObserver observer(*web_contents_impl());
prerender_helper()->AddPrerenderUntilScriptAsync(prerender_url);
observer.WaitForTrigger(prerender_url);
FrameTreeNodeId host_id = test::PrerenderTestHelper::GetHostForUrl(
*web_contents(), prerender_url);
ASSERT_TRUE(host_id);
PrerenderHost* prerender_host = web_contents_impl()
->GetPrerenderHostRegistry()
->FindNonReservedHostById(host_id);
ASSERT_TRUE(prerender_host);
EXPECT_TRUE(prerender_host->should_pause_javascript_execution());
}
protected:
void RunBasicFunctionalityCheck() {
GURL prerender_url = GetUrl("/prerender/inline_script.html");
StartPrerenderUntilScript(prerender_url);
GURL before_script_element_url = GetUrl("/image.jpg");
prerender_helper()->WaitForRequest(before_script_element_url, 1);
GURL image_url = GetUrl("/blank.jpg");
prerender_helper()->WaitForRequest(image_url, 1);
NavigatePrimaryPage(prerender_url);
GURL beacon_url = GetUrl("/activation-beacon");
prerender_helper()->WaitForRequest(beacon_url, 1);
ASSERT_EQ(false, EvalJs(web_contents_impl(), "document.prerendering"));
EXPECT_EQ(false,
EvalJs(web_contents_impl(), "executed_during_prerendering"));
}
};
class PrerenderUntilScriptBrowserTest
: public PrerenderUntilScriptBaseBrowserTest {
public:
PrerenderUntilScriptBrowserTest() {
feature_list_.InitAndEnableFeature(blink::features::kPrerenderUntilScript);
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(PrerenderUntilScriptBrowserTest, InlineScript) {
GURL url = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(web_contents(), url));
RunBasicFunctionalityCheck();
}
IN_PROC_BROWSER_TEST_F(PrerenderUntilScriptBrowserTest, ExternalSyncScript) {
GURL url = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(web_contents(), url));
GURL prerender_url = GetUrl("/prerender/external_sync_script.html");
StartPrerenderUntilScript(prerender_url);
GURL image_url = GetUrl("/blank.jpg");
prerender_helper()->WaitForRequest(image_url, 1);
NavigatePrimaryPage(prerender_url);
GURL beacon_url = GetUrl("/activation-beacon");
prerender_helper()->WaitForRequest(beacon_url, 1);
ASSERT_EQ(false, EvalJs(web_contents_impl(), "document.prerendering"));
EXPECT_EQ(false, EvalJs(web_contents_impl(), "executed_during_prerendering"));
}
IN_PROC_BROWSER_TEST_F(PrerenderUntilScriptBrowserTest, AsyncScript) {
GURL url = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(web_contents(), url));
GURL prerender_url = GetUrl("/prerender/async_script.html");
StartPrerenderUntilScript(prerender_url);
GURL script_url = GetUrl("/prerender/status_script.js");
prerender_helper()->WaitForRequest(script_url, 1);
GURL image_url = GetUrl("/blank.jpg");
prerender_helper()->WaitForRequest(image_url, 1);
NavigatePrimaryPage(prerender_url);
GURL beacon_url = GetUrl("/activation-beacon");
prerender_helper()->WaitForRequest(beacon_url, 1);
ASSERT_EQ(false, EvalJs(web_contents_impl(), "document.prerendering"));
EXPECT_EQ(false, EvalJs(web_contents_impl(), "executed_during_prerendering"));
}
IN_PROC_BROWSER_TEST_F(PrerenderUntilScriptBrowserTest, DeferredScript) {
GURL url = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(web_contents(), url));
GURL prerender_url = GetUrl("/prerender/deferred_script.html");
StartPrerenderUntilScript(prerender_url);
GURL image_url = GetUrl("/blank.jpg");
prerender_helper()->WaitForRequest(image_url, 1);
NavigatePrimaryPage(prerender_url);
GURL beacon_url = GetUrl("/activation-beacon");
prerender_helper()->WaitForRequest(beacon_url, 1);
ASSERT_EQ(false, EvalJs(web_contents_impl(), "document.prerendering"));
EXPECT_EQ(false, EvalJs(web_contents_impl(), "executed_during_prerendering"));
}
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_NonImmediateEagerness DISABLED_NonImmediateEagerness
#else
#define MAYBE_NonImmediateEagerness NonImmediateEagerness
#endif
IN_PROC_BROWSER_TEST_F(PrerenderUntilScriptBrowserTest,
MAYBE_NonImmediateEagerness) {
GURL url = GetUrl("/empty.html");
ASSERT_TRUE(NavigateToURL(web_contents(), url));
GURL prerender_url = GetUrl("/prerender/deferred_script.html");
InsertAnchor(prerender_url);
RenderFrameHostImpl* rfh = current_frame_host();
ASSERT_TRUE(rfh);
PreloadingDeciderObserverForPrerenderTesting preloading_decider_observer(
*rfh);
auto* preloading_decider =
PreloadingDecider::GetOrCreateForCurrentDocument(rfh);
prerender_helper()->AddPrerenderUntilScriptAsync(
prerender_url, blink::mojom::SpeculationEagerness::kModerate);
preloading_decider_observer.WaitUpdateSpeculationCandidates();
EXPECT_FALSE(HasHostForUrl(prerender_url));
EXPECT_TRUE(preloading_decider->IsOnStandByForTesting(
prerender_url, blink::mojom::SpeculationAction::kPrerenderUntilScript));
test::PrerenderHostRegistryObserver observer(*web_contents_impl());
PointerHoverToAnchor(prerender_url);
preloading_decider_observer.WaitOnPointerHover();
observer.WaitForTrigger(prerender_url);
EXPECT_TRUE(HasHostForUrl(prerender_url));
EXPECT_FALSE(preloading_decider->IsOnStandByForTesting(
prerender_url, blink::mojom::SpeculationAction::kPrerenderUntilScript));
GURL image_url = GetUrl("/blank.jpg");
prerender_helper()->WaitForRequest(image_url, 1);
NavigatePrimaryPage(prerender_url);
GURL beacon_url = GetUrl("/activation-beacon");
prerender_helper()->WaitForRequest(beacon_url, 1);
ASSERT_EQ(false, EvalJs(web_contents_impl(), "document.prerendering"));
EXPECT_EQ(false, EvalJs(web_contents_impl(), "executed_during_prerendering"));
}
IN_PROC_BROWSER_TEST_F(PrerenderUntilScriptBrowserTest, TargetBlank) {
const GURL url = GetUrl("/simple_links.html");
const GURL prerender_url = GetUrl("/prerender/deferred_script.html");
ASSERT_TRUE(NavigateToURL(shell(), url));
std::string add_link_script = base::ReplaceStringPlaceholders(
R"(
const link = document.getElementById('same_site_new_window_link');
link.href = "$1";
)",
{prerender_url.spec()}, nullptr);
ASSERT_TRUE(ExecJs(web_contents(), add_link_script));
const std::string add_speculation_rules_target_hint_blank_script =
SpeculationRulesWithIdAndTargetHint(prerender_url, "blank_specrules",
"_blank", "prerender_until_script");
WebContents* prerender_web_contents = nullptr;
base::RunLoop run_loop;
auto creation_subscription = RegisterWebContentsCreationCallback(
base::BindLambdaForTesting([&](content::WebContents* web_contents) {
prerender_web_contents = web_contents;
run_loop.QuitClosure().Run();
}));
EXPECT_TRUE(
ExecJs(web_contents(), add_speculation_rules_target_hint_blank_script));
run_loop.Run();
ASSERT_NE(prerender_web_contents, web_contents_impl());
ExpectWebContentsIsForNewTabPrerendering(*prerender_web_contents);
GURL image_url = GetUrl("/blank.jpg");
prerender_helper()->WaitForRequest(image_url, 1);
test::PrerenderHostObserver prerender_observer(*prerender_web_contents,
prerender_url);
EXPECT_TRUE(ExecJs(web_contents(), "clickSameSiteNewWindowLink();"));
GURL beacon_url = GetUrl("/activation-beacon");
prerender_helper()->WaitForRequest(beacon_url, 1);
EXPECT_EQ(prerender_web_contents->GetLastCommittedURL(), prerender_url);
EXPECT_TRUE(prerender_observer.was_activated());
EXPECT_FALSE(HasHostForUrl(prerender_url));
ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus::kActivated);
EXPECT_EQ(prerender_helper()->GetRequestCount(prerender_url), 1);
EXPECT_EQ(web_contents()->GetLastCommittedURL(), url);
EXPECT_EQ(false, EvalJs(prerender_web_contents, "document.prerendering"));
EXPECT_EQ(false,
EvalJs(prerender_web_contents, "executed_during_prerendering"));
}
class PrerenderUntilScriptOriginTrialBrowserTest
: public PrerenderUntilScriptBaseBrowserTest {
public:
PrerenderUntilScriptOriginTrialBrowserTest() {
set_port(45324);
}
protected:
static std::string GetOriginTrialToken() {
static const std::string token =
"A73nhliKkuPv6mhHxW3dx37TH9rtTamsxp+UZG+YbCSvxxUTFRHUd5bkRdnxrahQ0WL/"
"Wu1neptJrBPf1Mu64QEAAABbeyJvcmlnaW4iOiAiaHR0cHM6Ly9hLnRlc3Q6NDUzMjQiLC"
"AiZmVhdHVyZSI6ICJQcmVyZW5kZXJVbnRpbFNjcmlwdCIsICJleHBpcnkiOiAyMDAwMDAw"
"MDAwfQ==";
return token;
}
};
IN_PROC_BROWSER_TEST_F(PrerenderUntilScriptOriginTrialBrowserTest, Basic) {
const GURL url = GetUrl("/empty.html");
URLLoaderInterceptor interceptor(base::BindLambdaForTesting(
[&](URLLoaderInterceptor::RequestParams* params) {
if (params->url_request.url != url) {
return false;
}
URLLoaderInterceptor::WriteResponse(
"HTTP/1.1 200 OK\n"
"Content-type: text/html\n"
"Origin-Trial: " +
GetOriginTrialToken() + "\n\n",
"", params->client.get());
return true;
}));
ASSERT_TRUE(NavigateToURL(shell(), url));
RunBasicFunctionalityCheck();
}
}