blob: 01ac477acf04f206a89e9c7fe47b0bb8240164a5 [file] [log] [blame]
// Copyright (c) 2012 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/test_utils.h"
#include <utility>
#include "base/base_switches.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/message_loop/message_loop_current.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "base/task/task_scheduler/task_scheduler.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/variations/variations_params_manager.h"
#include "content/browser/frame_host/render_frame_host_delegate.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/common/url_schemes.h"
#include "content/public/browser/browser_child_process_host_iterator.h"
#include "content/public/browser/browser_plugin_guest_delegate.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/notification_service.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/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/process_type.h"
#include "content/public/test/test_launcher.h"
#include "content/public/test/test_service_manager_context.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/fetch/fetch_api_request_headers_map.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"
#include "url/url_util.h"
namespace content {
namespace {
// Number of times to repost a Quit task so that the MessageLoop finishes up
// pending tasks and tasks posted by those pending tasks without risking the
// potential hang behavior of MessageLoop::QuitWhenIdle.
// The criteria for choosing this number: it should be high enough to make the
// quit act like QuitWhenIdle, while taking into account that any page which is
// animating may be rendering another frame for each quit deferral. For an
// animating page, the potential delay to quitting the RunLoop would be
// kNumQuitDeferrals * frame_render_time. Some perf tests run slow, such as
// 200ms/frame.
constexpr int kNumQuitDeferrals = 10;
void DeferredQuitRunLoop(const base::Closure& quit_task,
int num_quit_deferrals) {
if (num_quit_deferrals <= 0) {
quit_task.Run();
} else {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&DeferredQuitRunLoop, quit_task,
num_quit_deferrals - 1));
}
}
// Class used to handle result callbacks for ExecuteScriptAndGetValue.
class ScriptCallback {
public:
ScriptCallback() { }
virtual ~ScriptCallback() { }
void ResultCallback(const base::Value* result);
std::unique_ptr<base::Value> result() { return std::move(result_); }
private:
std::unique_ptr<base::Value> result_;
DISALLOW_COPY_AND_ASSIGN(ScriptCallback);
};
void ScriptCallback::ResultCallback(const base::Value* result) {
if (result)
result_.reset(result->DeepCopy());
base::RunLoop::QuitCurrentWhenIdleDeprecated();
}
// Monitors if any task is processed by the message loop.
class TaskObserver : public base::MessageLoop::TaskObserver {
public:
TaskObserver() : processed_(false) {}
~TaskObserver() override {}
// MessageLoop::TaskObserver overrides.
void WillProcessTask(const base::PendingTask& pending_task) override {}
void DidProcessTask(const base::PendingTask& pending_task) override {
processed_ = true;
}
// Returns true if any task was processed.
bool processed() const { return processed_; }
private:
bool processed_;
DISALLOW_COPY_AND_ASSIGN(TaskObserver);
};
// Adapter that makes a WindowedNotificationObserver::ConditionTestCallback from
// a WindowedNotificationObserver::ConditionTestCallbackWithoutSourceAndDetails
// by ignoring the notification source and details.
bool IgnoreSourceAndDetails(
const WindowedNotificationObserver::
ConditionTestCallbackWithoutSourceAndDetails& callback,
const NotificationSource& source,
const NotificationDetails& details) {
return callback.Run();
}
} // namespace
blink::mojom::FetchAPIRequestPtr CreateFetchAPIRequest(
const GURL& url,
const std::string& method,
const blink::FetchAPIRequestHeadersMap& headers,
blink::mojom::ReferrerPtr referrer,
bool is_reload) {
auto request = blink::mojom::FetchAPIRequest::New();
request->url = url;
request->method = method;
request->headers = {headers.begin(), headers.end()};
request->referrer = std::move(referrer);
request->is_reload = is_reload;
return request;
}
void RunMessageLoop() {
base::RunLoop run_loop;
RunThisRunLoop(&run_loop);
}
void RunThisRunLoop(base::RunLoop* run_loop) {
base::MessageLoopCurrent::ScopedNestableTaskAllower allow;
run_loop->Run();
}
void RunAllPendingInMessageLoop() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, GetDeferredQuitTaskForRunLoop(&run_loop));
RunThisRunLoop(&run_loop);
}
void RunAllPendingInMessageLoop(BrowserThread::ID thread_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (thread_id == BrowserThread::UI) {
RunAllPendingInMessageLoop();
return;
}
// Post a DeferredQuitRunLoop() task to |thread_id|. Then, run a RunLoop on
// this thread. When a few generations of pending tasks have run on
// |thread_id|, a task will be posted to this thread to exit the RunLoop.
base::RunLoop run_loop;
const base::Closure post_quit_run_loop_to_ui_thread = base::Bind(
base::IgnoreResult(&base::SingleThreadTaskRunner::PostTask),
base::ThreadTaskRunnerHandle::Get(), FROM_HERE, run_loop.QuitClosure());
base::PostTaskWithTraits(
FROM_HERE, {thread_id},
base::BindOnce(&DeferredQuitRunLoop, post_quit_run_loop_to_ui_thread,
kNumQuitDeferrals));
RunThisRunLoop(&run_loop);
}
void RunAllTasksUntilIdle() {
while (true) {
// Setup a task observer to determine if MessageLoop tasks run in the
// current loop iteration and loop in case the MessageLoop posts tasks to
// the Task Scheduler after the initial flush.
TaskObserver task_observer;
base::MessageLoopCurrent::Get()->AddTaskObserver(&task_observer);
base::RunLoop run_loop;
base::TaskScheduler::GetInstance()->FlushAsyncForTesting(
run_loop.QuitWhenIdleClosure());
run_loop.Run();
base::MessageLoopCurrent::Get()->RemoveTaskObserver(&task_observer);
if (!task_observer.processed())
break;
}
}
base::Closure GetDeferredQuitTaskForRunLoop(base::RunLoop* run_loop) {
return base::Bind(&DeferredQuitRunLoop, run_loop->QuitClosure(),
kNumQuitDeferrals);
}
std::unique_ptr<base::Value> ExecuteScriptAndGetValue(
RenderFrameHost* render_frame_host,
const std::string& script) {
ScriptCallback observer;
render_frame_host->ExecuteJavaScriptForTests(
base::UTF8ToUTF16(script),
base::Bind(&ScriptCallback::ResultCallback, base::Unretained(&observer)));
base::RunLoop().Run();
return observer.result();
}
bool AreAllSitesIsolatedForTesting() {
return SiteIsolationPolicy::UseDedicatedProcessesForAllSites();
}
void IsolateAllSitesForTesting(base::CommandLine* command_line) {
command_line->AppendSwitch(switches::kSitePerProcess);
}
void ResetSchemesAndOriginsWhitelist() {
url::Shutdown();
RegisterContentSchemes(false);
url::Initialize();
}
void DeprecatedEnableFeatureWithParam(const base::Feature& feature,
const std::string& param_name,
const std::string& param_value,
base::CommandLine* command_line) {
static const char kFakeTrialName[] = "TrialNameForTesting";
static const char kFakeTrialGroupName[] = "TrialGroupForTesting";
// Enable all the |feature|, associating them with |trial_name|.
command_line->AppendSwitchASCII(
switches::kEnableFeatures,
std::string(feature.name) + "<" + kFakeTrialName);
std::map<std::string, std::string> param_values = {{param_name, param_value}};
variations::testing::VariationParamsManager::AppendVariationParams(
kFakeTrialName, kFakeTrialGroupName, param_values, command_line);
}
namespace {
// Helper class for CreateAndAttachInnerContents.
//
// TODO(lfg): https://crbug.com/821187 Inner webcontentses currently require
// supplying a BrowserPluginGuestDelegate; however, the oopif architecture
// doesn't really require it. Refactor this so that we can create an inner
// contents without any of the guest machinery.
class InnerWebContentsHelper : public WebContentsObserver {
public:
explicit InnerWebContentsHelper() : WebContentsObserver() {}
~InnerWebContentsHelper() override = default;
// WebContentsObserver:
void WebContentsDestroyed() override { delete this; }
void SetInnerWebContents(WebContents* inner_web_contents) {
Observe(inner_web_contents);
}
private:
DISALLOW_COPY_AND_ASSIGN(InnerWebContentsHelper);
};
} // namespace
WebContents* CreateAndAttachInnerContents(RenderFrameHost* rfh) {
WebContents* outer_contents =
static_cast<RenderFrameHostImpl*>(rfh)->delegate()->GetAsWebContents();
if (!outer_contents)
return nullptr;
auto guest_delegate = std::make_unique<InnerWebContentsHelper>();
WebContents::CreateParams inner_params(outer_contents->GetBrowserContext());
std::unique_ptr<WebContents> inner_contents_ptr =
WebContents::Create(inner_params);
// Attach. |inner_contents| becomes owned by |outer_contents|.
WebContents* inner_contents = inner_contents_ptr.get();
inner_contents->AttachToOuterWebContentsFrame(std::move(inner_contents_ptr),
rfh);
// |guest_delegate| becomes owned by |inner_contents|.
guest_delegate.release()->SetInnerWebContents(inner_contents);
return inner_contents;
}
MessageLoopRunner::MessageLoopRunner(QuitMode quit_mode)
: quit_mode_(quit_mode), loop_running_(false), quit_closure_called_(false) {
}
MessageLoopRunner::~MessageLoopRunner() = default;
void MessageLoopRunner::Run() {
DCHECK(thread_checker_.CalledOnValidThread());
// Do not run the message loop if our quit closure has already been called.
// This helps in scenarios where the closure has a chance to run before
// we Run explicitly.
if (quit_closure_called_)
return;
loop_running_ = true;
RunThisRunLoop(&run_loop_);
}
base::Closure MessageLoopRunner::QuitClosure() {
return base::Bind(&MessageLoopRunner::Quit, this);
}
void MessageLoopRunner::Quit() {
DCHECK(thread_checker_.CalledOnValidThread());
quit_closure_called_ = true;
// Only run the quit task if we are running the message loop.
if (loop_running_) {
switch (quit_mode_) {
case QuitMode::DEFERRED:
GetDeferredQuitTaskForRunLoop(&run_loop_).Run();
break;
case QuitMode::IMMEDIATE:
run_loop_.Quit();
break;
}
loop_running_ = false;
}
}
WindowedNotificationObserver::WindowedNotificationObserver(
int notification_type,
const NotificationSource& source)
: source_(NotificationService::AllSources()) {
AddNotificationType(notification_type, source);
}
WindowedNotificationObserver::WindowedNotificationObserver(
int notification_type,
const ConditionTestCallback& callback)
: callback_(callback), source_(NotificationService::AllSources()) {
AddNotificationType(notification_type, source_);
}
WindowedNotificationObserver::WindowedNotificationObserver(
int notification_type,
const ConditionTestCallbackWithoutSourceAndDetails& callback)
: callback_(base::Bind(&IgnoreSourceAndDetails, callback)),
source_(NotificationService::AllSources()) {
registrar_.Add(this, notification_type, source_);
}
WindowedNotificationObserver::~WindowedNotificationObserver() = default;
void WindowedNotificationObserver::AddNotificationType(
int notification_type,
const NotificationSource& source) {
registrar_.Add(this, notification_type, source);
}
void WindowedNotificationObserver::Wait() {
if (!seen_)
run_loop_.Run();
EXPECT_TRUE(seen_);
}
void WindowedNotificationObserver::Observe(int type,
const NotificationSource& source,
const NotificationDetails& details) {
source_ = source;
details_ = details;
if (!callback_.is_null() && !callback_.Run(source, details))
return;
seen_ = true;
run_loop_.Quit();
}
InProcessUtilityThreadHelper::InProcessUtilityThreadHelper()
: child_thread_count_(0), shell_context_(new TestServiceManagerContext) {
RenderProcessHost::SetRunRendererInProcess(true);
BrowserChildProcessObserver::Add(this);
}
InProcessUtilityThreadHelper::~InProcessUtilityThreadHelper() {
if (child_thread_count_) {
DCHECK(BrowserThread::IsThreadInitialized(BrowserThread::UI));
DCHECK(BrowserThread::IsThreadInitialized(BrowserThread::IO));
run_loop_.reset(new base::RunLoop);
run_loop_->Run();
}
BrowserChildProcessObserver::Remove(this);
RenderProcessHost::SetRunRendererInProcess(false);
}
void InProcessUtilityThreadHelper::BrowserChildProcessHostConnected(
const ChildProcessData& data) {
child_thread_count_++;
}
void InProcessUtilityThreadHelper::BrowserChildProcessHostDisconnected(
const ChildProcessData& data) {
if (--child_thread_count_)
return;
if (run_loop_)
run_loop_->Quit();
}
RenderFrameDeletedObserver::RenderFrameDeletedObserver(RenderFrameHost* rfh)
: WebContentsObserver(WebContents::FromRenderFrameHost(rfh)),
process_id_(rfh->GetProcess()->GetID()),
routing_id_(rfh->GetRoutingID()),
deleted_(false) {}
RenderFrameDeletedObserver::~RenderFrameDeletedObserver() {}
void RenderFrameDeletedObserver::RenderFrameDeleted(
RenderFrameHost* render_frame_host) {
if (render_frame_host->GetProcess()->GetID() == process_id_ &&
render_frame_host->GetRoutingID() == routing_id_) {
deleted_ = true;
if (runner_.get())
runner_->Quit();
}
}
bool RenderFrameDeletedObserver::deleted() {
return deleted_;
}
void RenderFrameDeletedObserver::WaitUntilDeleted() {
if (deleted_)
return;
runner_.reset(new base::RunLoop());
runner_->Run();
runner_.reset();
}
WebContentsDestroyedWatcher::WebContentsDestroyedWatcher(
WebContents* web_contents)
: WebContentsObserver(web_contents) {
EXPECT_TRUE(web_contents != nullptr);
}
WebContentsDestroyedWatcher::~WebContentsDestroyedWatcher() {
}
void WebContentsDestroyedWatcher::Wait() {
run_loop_.Run();
}
void WebContentsDestroyedWatcher::WebContentsDestroyed() {
run_loop_.Quit();
}
TestPageScaleObserver::TestPageScaleObserver(WebContents* web_contents)
: WebContentsObserver(web_contents) {}
TestPageScaleObserver::~TestPageScaleObserver() {}
void TestPageScaleObserver::OnPageScaleFactorChanged(float page_scale_factor) {
last_scale_ = page_scale_factor;
seen_page_scale_change_ = true;
if (done_callback_)
std::move(done_callback_).Run();
}
float TestPageScaleObserver::WaitForPageScaleUpdate() {
if (!seen_page_scale_change_) {
base::RunLoop run_loop;
done_callback_ = run_loop.QuitClosure();
run_loop.Run();
}
seen_page_scale_change_ = false;
return last_scale_;
}
GURL EffectiveURLContentBrowserClient::GetEffectiveURL(
BrowserContext* browser_context,
const GURL& url) {
if (url == url_to_modify_)
return url_to_return_;
return url;
}
} // namespace content