// 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 "base/bind.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" #include "content/public/browser/browser_child_process_host_iterator.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/common/process_type.h" #include "content/public/test/test_launcher.h" #include "testing/gtest/include/gtest/gtest.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. static const int kNumQuitDeferrals = 10; static void DeferredQuitRunLoop(const base::Closure& quit_task, int num_quit_deferrals) { if (num_quit_deferrals <= 0) { quit_task.Run(); } else { base::MessageLoop::current()->PostTask( FROM_HERE, base::Bind(&DeferredQuitRunLoop, quit_task, num_quit_deferrals - 1)); } } void RunAllPendingMessageAndSendQuit(BrowserThread::ID thread_id, const base::Closure& quit_task) { RunAllPendingInMessageLoop(); BrowserThread::PostTask(thread_id, FROM_HERE, quit_task); } // Class used handle result callbacks for ExecuteScriptAndGetValue. class ScriptCallback { public: ScriptCallback() { } virtual ~ScriptCallback() { } void ResultCallback(const base::Value* result); scoped_ptr result() { return result_.Pass(); } private: scoped_ptr result_; DISALLOW_COPY_AND_ASSIGN(ScriptCallback); }; void ScriptCallback::ResultCallback(const base::Value* result) { if (result) result_.reset(result->DeepCopy()); base::MessageLoop::current()->Quit(); } // 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 void RunMessageLoop() { base::RunLoop run_loop; RunThisRunLoop(&run_loop); } void RunThisRunLoop(base::RunLoop* run_loop) { base::MessageLoop::ScopedNestableTaskAllower allow( base::MessageLoop::current()); // If we're running inside a browser test, we might need to allow the test // launcher to do extra work before/after running a nested message loop. TestLauncherDelegate* delegate = NULL; #if !defined(OS_IOS) delegate = GetCurrentTestLauncherDelegate(); #endif if (delegate) delegate->PreRunMessageLoop(run_loop); run_loop->Run(); if (delegate) delegate->PostRunMessageLoop(); } void RunAllPendingInMessageLoop() { base::MessageLoop::current()->PostTask( FROM_HERE, base::MessageLoop::QuitWhenIdleClosure()); RunMessageLoop(); } void RunAllPendingInMessageLoop(BrowserThread::ID thread_id) { if (BrowserThread::CurrentlyOn(thread_id)) { RunAllPendingInMessageLoop(); return; } BrowserThread::ID current_thread_id; if (!BrowserThread::GetCurrentThreadIdentifier(¤t_thread_id)) { NOTREACHED(); return; } base::RunLoop run_loop; BrowserThread::PostTask(thread_id, FROM_HERE, base::Bind(&RunAllPendingMessageAndSendQuit, current_thread_id, run_loop.QuitClosure())); RunThisRunLoop(&run_loop); } base::Closure GetQuitTaskForRunLoop(base::RunLoop* run_loop) { return base::Bind(&DeferredQuitRunLoop, run_loop->QuitClosure(), kNumQuitDeferrals); } scoped_ptr ExecuteScriptAndGetValue( RenderFrameHost* render_frame_host, const std::string& script) { ScriptCallback observer; render_frame_host->ExecuteJavaScript( base::UTF8ToUTF16(script), base::Bind(&ScriptCallback::ResultCallback, base::Unretained(&observer))); base::MessageLoop* loop = base::MessageLoop::current(); loop->Run(); return observer.result().Pass(); } MessageLoopRunner::MessageLoopRunner() : loop_running_(false), quit_closure_called_(false) { } MessageLoopRunner::~MessageLoopRunner() { } void MessageLoopRunner::Run() { // 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() { quit_closure_called_ = true; // Only run the quit task if we are running the message loop. if (loop_running_) { GetQuitTaskForRunLoop(&run_loop_).Run(); loop_running_ = false; } } WindowedNotificationObserver::WindowedNotificationObserver( int notification_type, const NotificationSource& source) : seen_(false), running_(false), source_(NotificationService::AllSources()) { AddNotificationType(notification_type, source); } WindowedNotificationObserver::WindowedNotificationObserver( int notification_type, const ConditionTestCallback& callback) : seen_(false), running_(false), callback_(callback), source_(NotificationService::AllSources()) { AddNotificationType(notification_type, source_); } WindowedNotificationObserver::WindowedNotificationObserver( int notification_type, const ConditionTestCallbackWithoutSourceAndDetails& callback) : seen_(false), running_(false), callback_(base::Bind(&IgnoreSourceAndDetails, callback)), source_(NotificationService::AllSources()) { registrar_.Add(this, notification_type, source_); } WindowedNotificationObserver::~WindowedNotificationObserver() {} void WindowedNotificationObserver::AddNotificationType( int notification_type, const NotificationSource& source) { registrar_.Add(this, notification_type, source); } void WindowedNotificationObserver::Wait() { if (seen_) return; running_ = true; message_loop_runner_ = new MessageLoopRunner; message_loop_runner_->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; if (!running_) return; message_loop_runner_->Quit(); running_ = false; } InProcessUtilityThreadHelper::InProcessUtilityThreadHelper() : child_thread_count_(0) { RenderProcessHost::SetRunRendererInProcess(true); BrowserChildProcessObserver::Add(this); } InProcessUtilityThreadHelper::~InProcessUtilityThreadHelper() { if (child_thread_count_) { DCHECK(BrowserThread::IsMessageLoopValid(BrowserThread::UI)); DCHECK(BrowserThread::IsMessageLoopValid(BrowserThread::IO)); runner_ = new MessageLoopRunner; runner_->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 (runner_.get()) runner_->Quit(); } } // namespace content