blob: f66416137f687158353b44fdd79139d30e1d94e3 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/public/test/prerender_test_util.h"
#include "base/callback_helpers.h"
#include "content/browser/prerender/prerender_host_registry.h"
#include "content/browser/renderer_host/frame_tree.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "third_party/blink/public/common/features.h"
namespace content {
namespace test {
namespace {
PrerenderHostRegistry& GetPrerenderHostRegistry(content::WebContents* tab) {
EXPECT_TRUE(content::BrowserThread::CurrentlyOn(BrowserThread::UI));
return *static_cast<WebContentsImpl*>(tab)->GetPrerenderHostRegistry();
}
PrerenderHost* GetPrerenderHostById(content::WebContents* tab, int host_id) {
auto& registry = GetPrerenderHostRegistry(tab);
return registry.FindNonReservedHostById(host_id);
}
} // namespace
class PrerenderHostRegistryObserverImpl
: public PrerenderHostRegistry::Observer {
public:
explicit PrerenderHostRegistryObserverImpl(content::WebContents* tab) {
observation_.Observe(&GetPrerenderHostRegistry(tab));
}
// Returns immediately if `url` was ever triggered before.
void WaitForTrigger(const GURL& url) {
if (triggered_.contains(url)) {
return;
}
EXPECT_FALSE(waiting_.contains(url));
base::RunLoop loop;
waiting_[url] = loop.QuitClosure();
loop.Run();
}
void OnTrigger(const GURL& url) override {
auto iter = waiting_.find(url);
if (iter != waiting_.end()) {
auto callback = std::move(iter->second);
waiting_.erase(iter);
std::move(callback).Run();
} else {
EXPECT_FALSE(triggered_.contains(url))
<< "this observer doesn't yet support multiple triggers";
triggered_.insert(url);
}
}
void OnRegistryDestroyed() override {
EXPECT_TRUE(waiting_.empty());
observation_.Reset();
}
base::ScopedObservation<PrerenderHostRegistry,
PrerenderHostRegistry::Observer>
observation_{this};
base::flat_map<GURL, base::OnceClosure> waiting_;
base::flat_set<GURL> triggered_;
};
PrerenderHostRegistryObserver::PrerenderHostRegistryObserver(
content::WebContents* tab)
: impl_(std::make_unique<PrerenderHostRegistryObserverImpl>(tab)) {}
PrerenderHostRegistryObserver::~PrerenderHostRegistryObserver() = default;
void PrerenderHostRegistryObserver::WaitForTrigger(const GURL& gurl) {
impl_->WaitForTrigger(gurl);
}
class PrerenderHostObserverImpl : public PrerenderHost::Observer {
public:
explicit PrerenderHostObserverImpl(content::WebContents* tab, int host_id) {
observation_.Observe(GetPrerenderHostById(tab, host_id));
}
void OnActivated() override {
was_activated_ = true;
if (waiting_for_activation_)
std::move(waiting_for_activation_).Run();
}
void OnHostDestroyed() override {
observation_.Reset();
if (waiting_for_destruction_)
std::move(waiting_for_destruction_).Run();
}
void WaitForActivation() {
DCHECK(observation_.IsObserving());
EXPECT_FALSE(waiting_for_activation_);
base::RunLoop loop;
waiting_for_activation_ = loop.QuitClosure();
loop.Run();
}
void WaitForDestroyed() {
if (!observation_.IsObserving())
return;
EXPECT_FALSE(waiting_for_destruction_);
base::RunLoop loop;
waiting_for_destruction_ = loop.QuitClosure();
loop.Run();
}
bool was_activated() { return was_activated_; }
private:
base::ScopedObservation<PrerenderHost, PrerenderHost::Observer> observation_{
this};
base::OnceClosure waiting_for_activation_;
base::OnceClosure waiting_for_destruction_;
bool was_activated_ = false;
};
PrerenderHostObserver::PrerenderHostObserver(content::WebContents* tab,
int prerender_host)
: impl_(std::make_unique<PrerenderHostObserverImpl>(tab, prerender_host)) {}
PrerenderHostObserver::~PrerenderHostObserver() = default;
void PrerenderHostObserver::WaitForActivation() {
impl_->WaitForActivation();
}
void PrerenderHostObserver::WaitForDestroyed() {
impl_->WaitForDestroyed();
}
bool PrerenderHostObserver::was_activated() {
return impl_->was_activated();
}
PrerenderTestHelper::PrerenderTestHelper(const content::WebContents::Getter& fn,
const std::string& prerendering_impl)
: get_web_contents_fn_(fn) {
std::map<std::string, std::string> parameters;
parameters["implementation"] = prerendering_impl;
feature_list_.InitAndEnableFeatureWithParameters(blink::features::kPrerender2,
parameters);
}
PrerenderTestHelper::PrerenderTestHelper(const content::WebContents::Getter& fn)
: PrerenderTestHelper(fn, "mparch") {}
PrerenderTestHelper::~PrerenderTestHelper() = default;
void PrerenderTestHelper::SetUpOnMainThread(
net::test_server::EmbeddedTestServer* http_server) {
EXPECT_TRUE(content::BrowserThread::CurrentlyOn(BrowserThread::UI));
EXPECT_FALSE(http_server->Started());
http_server->RegisterRequestMonitor(base::BindRepeating(
&PrerenderTestHelper::MonitorResourceRequest, base::Unretained(this)));
}
int PrerenderTestHelper::GetHostForUrl(const GURL& gurl) {
auto* host =
GetPrerenderHostRegistry(GetWebContents()).FindHostByUrlForTesting(gurl);
return host ? host->frame_tree_node_id()
: RenderFrameHost::kNoFrameTreeNodeId;
}
void PrerenderTestHelper::WaitForPrerenderLoadCompletion(int host_id) {
auto* host = GetPrerenderHostById(GetWebContents(), host_id);
ASSERT_NE(host, nullptr);
host->WaitForLoadStopForTesting();
}
void PrerenderTestHelper::WaitForPrerenderLoadCompletion(const GURL& gurl) {
PrerenderHostRegistry& registry = GetPrerenderHostRegistry(GetWebContents());
PrerenderHost* host = registry.FindHostByUrlForTesting(gurl);
// Wait for the host to be created if it hasn't yet.
if (!host) {
PrerenderHostRegistryObserver observer(GetWebContents());
observer.WaitForTrigger(gurl);
host = registry.FindHostByUrlForTesting(gurl);
ASSERT_NE(host, nullptr);
}
host->WaitForLoadStopForTesting();
}
int PrerenderTestHelper::AddPrerender(const GURL& gurl) {
EXPECT_TRUE(content::BrowserThread::CurrentlyOn(BrowserThread::UI));
// Add the link tag that will prerender the URL.
EXPECT_TRUE(ExecJs(GetWebContents(), JsReplace("add_prerender($1)", gurl)))
<< "AddPrerender failed. Did you load add_prerender.html?";
WaitForPrerenderLoadCompletion(gurl);
int host_id = GetHostForUrl(gurl);
EXPECT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId);
return host_id;
}
void PrerenderTestHelper::AddPrerenderAsync(const GURL& gurl) {
EXPECT_TRUE(content::BrowserThread::CurrentlyOn(BrowserThread::UI));
// Add the link tag that will prerender the URL.
EXPECT_TRUE(ExecJs(GetWebContents(), JsReplace("add_prerender($1)", gurl)))
<< "AddPrerender failed. Did you load add_prerender.html?";
}
void PrerenderTestHelper::NavigatePrerenderedPage(int host_id,
const GURL& gurl) {
auto* prerender_host = GetPrerenderHostById(GetWebContents(), host_id);
ASSERT_NE(prerender_host, nullptr);
RenderFrameHostImpl* prerender_render_frame_host =
prerender_host->GetPrerenderedMainFrameHost();
// Ignore the result of ExecJs().
//
// Navigation from the prerendered page could cancel prerendering and
// destroy the prerendered frame before ExecJs() gets a result from that.
// This results in execution failure even when the execution succeeded. See
// https://crbug.com/1186584 for details.
//
// This part will drastically be modified by the MPArch, so we take the
// approach just to ignore it instead of fixing the timing issue. When
// ExecJs() actually fails, the remaining test steps should fail, so it
// should be safe to ignore it.
ignore_result(
ExecJs(prerender_render_frame_host, JsReplace("location = $1", gurl)));
}
void PrerenderTestHelper::NavigatePrimaryPage(const GURL& url) {
EXPECT_TRUE(content::BrowserThread::CurrentlyOn(BrowserThread::UI));
content::TestNavigationObserver observer(GetWebContents());
// Ignore the result of ExecJs().
//
// Depending on timing, activation could destroy the current WebContents
// before ExecJs() gets a result from the frame that executed scripts. This
// results in execution failure even when the execution succeeded. See
// https://crbug.com/1156141 for details.
//
// This part will drastically be modified by the MPArch, so we take the
// approach just to ignore it instead of fixing the timing issue. When
// ExecJs() actually fails, the remaining test steps should fail, so it
// should be safe to ignore it.
ignore_result(ExecJs(GetWebContents()->GetMainFrame(),
JsReplace("location = $1", url)));
observer.Wait();
}
::testing::AssertionResult PrerenderTestHelper::VerifyPrerenderingState(
const GURL& gurl) {
PrerenderHostRegistry& registry = GetPrerenderHostRegistry(GetWebContents());
PrerenderHost* prerender_host = registry.FindHostByUrlForTesting(gurl);
RenderFrameHostImpl* prerendered_render_frame_host =
prerender_host->GetPrerenderedMainFrameHost();
std::vector<RenderFrameHost*> frames =
prerendered_render_frame_host->GetFramesInSubtree();
for (auto* frame : frames) {
auto* rfhi = static_cast<RenderFrameHostImpl*>(frame);
// All the subframes should be in LifecycleStateImpl::kPrerendering state
// before activation.
if (rfhi->lifecycle_state() !=
RenderFrameHostImpl::LifecycleStateImpl::kPrerendering) {
return ::testing::AssertionFailure() << "subframe in incorrect state";
}
if (rfhi->frame_tree()->type() != FrameTree::Type::kPrerender) {
return ::testing::AssertionFailure() << "frame tree had incorrect type";
}
}
return ::testing::AssertionSuccess();
}
RenderFrameHost* PrerenderTestHelper::GetPrerenderedMainFrameHost(int host_id) {
auto* prerender_host = GetPrerenderHostById(GetWebContents(), host_id);
EXPECT_NE(prerender_host, nullptr);
return prerender_host->GetPrerenderedMainFrameHost();
}
int PrerenderTestHelper::GetRequestCount(const GURL& url) {
EXPECT_TRUE(content::BrowserThread::CurrentlyOn(BrowserThread::UI));
base::AutoLock auto_lock(lock_);
return request_count_by_path_[url.PathForRequest()];
}
void PrerenderTestHelper::WaitForRequest(const GURL& url, int count) {
for (;;) {
base::RunLoop run_loop;
{
base::AutoLock auto_lock(lock_);
if (request_count_by_path_[url.PathForRequest()] >= count)
return;
monitor_callback_ = run_loop.QuitClosure();
}
run_loop.Run();
}
}
void PrerenderTestHelper::MonitorResourceRequest(
const net::test_server::HttpRequest& request) {
// This should be called on `EmbeddedTestServer::io_thread_`.
EXPECT_FALSE(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
base::AutoLock auto_lock(lock_);
request_count_by_path_[request.GetURL().PathForRequest()]++;
if (monitor_callback_)
std::move(monitor_callback_).Run();
}
content::WebContents* PrerenderTestHelper::GetWebContents() {
return get_web_contents_fn_.Run();
}
} // namespace test
} // namespace content