 Chromium Code Reviews
 Chromium Code Reviews Issue 16267002:
  Re-fix http://crbug.com/87176, and add a test.  (Closed) 
  Base URL: svn://svn.chromium.org/chrome/trunk/src
    
  
    Issue 16267002:
  Re-fix http://crbug.com/87176, and add a test.  (Closed) 
  Base URL: svn://svn.chromium.org/chrome/trunk/src| Index: content/browser/renderer_host/render_process_host_impl_unittest.cc | 
| diff --git a/content/browser/renderer_host/render_process_host_impl_unittest.cc b/content/browser/renderer_host/render_process_host_impl_unittest.cc | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..5e22b71c5dce538a7ce941596974c3921054e83d | 
| --- /dev/null | 
| +++ b/content/browser/renderer_host/render_process_host_impl_unittest.cc | 
| @@ -0,0 +1,257 @@ | 
| +// Copyright 2013 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/browser/renderer_host/render_process_host_impl.h" | 
| + | 
| +#include "base/command_line.h" | 
| +#include "base/process_util.h" | 
| +#include "base/run_loop.h" | 
| +#include "content/browser/child_process_launcher.h" | 
| +#include "content/browser/loader/resource_dispatcher_host_impl.h" | 
| +#include "content/browser/site_instance_impl.h" | 
| +#include "content/browser/storage_partition_impl.h" | 
| +#include "content/browser/webui/web_ui_controller_factory_registry.h" | 
| +#include "content/common/child_process_messages.h" | 
| +#include "content/common/view_messages.h" | 
| +#include "content/public/browser/render_process_host_factory.h" | 
| +#include "content/public/common/sandboxed_process_launcher_delegate.h" | 
| +#include "content/public/test/test_browser_context.h" | 
| +#include "content/public/test/test_browser_thread.h" | 
| +#include "content/test/test_content_browser_client.h" | 
| +#include "content/test/test_render_view_host_factory.h" | 
| +#include "content/test/test_web_contents.h" | 
| +#include "ipc/ipc_switches.h" | 
| +#include "ipc/ipc_test_sink.h" | 
| +#include "net/url_request/url_request_test_util.h" | 
| +#include "testing/gtest/include/gtest/gtest.h" | 
| + | 
| +namespace content { | 
| +namespace { | 
| + | 
| +class FakeChildProcessLauncher; | 
| + | 
| +struct LaunchedChildProcess { | 
| + // Becomes NULL after the FakeChildProcessLauncher is destroyed. | 
| + FakeChildProcessLauncher* launcher_; | 
| + // Collects messages sent to the child process. | 
| + IPC::TestSink messages_; | 
| +}; | 
| + | 
| +class FakeChildProcessLauncher : public ChildProcessLauncher { | 
| + public: | 
| + FakeChildProcessLauncher(LaunchedChildProcess* info, | 
| + const IPC::ChannelHandle& channel, | 
| + Client* client) | 
| + : info_(info), | 
| + client_(client), | 
| + starting_(true), | 
| + termination_status_(base::TERMINATION_STATUS_NORMAL_TERMINATION), | 
| + channel_(new IPC::ChannelProxy( | 
| + channel, | 
| + IPC::Channel::MODE_CLIENT, | 
| + &info->messages_, | 
| + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO))), | 
| + weak_this_factory_(this) { | 
| + info_->launcher_ = this; | 
| + | 
| + // Automatically start the child process so the RenderProcessHostImpl sends | 
| + // messages to it. To mimic real child processes, don't start instantly. | 
| + base::MessageLoopProxy::current()->PostTask( | 
| + FROM_HERE, | 
| + base::Bind(&FakeChildProcessLauncher::Start, | 
| + weak_this_factory_.GetWeakPtr())); | 
| + } | 
| + | 
| + virtual ~FakeChildProcessLauncher() { info_->launcher_ = NULL; } | 
| + | 
| + void Send(IPC::Message* msg) { | 
| + EXPECT_TRUE(channel_->Send(msg)); | 
| + } | 
| + | 
| + // Implementation of ChildProcessLauncher. | 
| + virtual bool IsStarting() OVERRIDE { return starting_; } | 
| + virtual base::ProcessHandle GetHandle() OVERRIDE { | 
| + return base::Process::Current().handle(); | 
| + } | 
| + virtual base::TerminationStatus GetChildTerminationStatus(bool known_dead, | 
| + int* exit_code) | 
| + OVERRIDE { | 
| + return termination_status_; | 
| + } | 
| + virtual void SetProcessBackgrounded(bool background) OVERRIDE {} | 
| + virtual void SetTerminateChildOnShutdown(bool terminate_on_shutdown) | 
| + OVERRIDE {} | 
| + | 
| + private: | 
| + void Start() { | 
| + starting_ = false; | 
| + client_->OnProcessLaunched(); | 
| + } | 
| + | 
| + LaunchedChildProcess* info_; | 
| + Client* client_; | 
| + bool starting_; | 
| + base::TerminationStatus termination_status_; | 
| + scoped_ptr<IPC::ChannelProxy> channel_; | 
| + base::WeakPtrFactory<FakeChildProcessLauncher> weak_this_factory_; | 
| +}; | 
| + | 
| +scoped_ptr<ChildProcessLauncher> NewFakeChildProcessLauncher( | 
| + ScopedVector<LaunchedChildProcess>* child_processes, | 
| +#if defined(OS_WIN) | 
| + SandboxedProcessLauncherDelegate* delegate, | 
| +#elif defined(OS_POSIX) | 
| + bool use_zygote, | 
| + const base::EnvironmentVector& environ, | 
| + int ipcfd, | 
| +#endif | 
| + CommandLine* cmd_line, | 
| + int child_process_id, | 
| + ChildProcessLauncher::Client* client) { | 
| +#if defined(OS_WIN) | 
| + delete delegate; | 
| +#endif | 
| + scoped_ptr<CommandLine> cmd_line_deleter(cmd_line); | 
| + child_processes->push_back(new LaunchedChildProcess()); | 
| + std::string channel_id = | 
| + cmd_line->GetSwitchValueASCII(switches::kProcessChannelID); | 
| + return scoped_ptr<ChildProcessLauncher>(new FakeChildProcessLauncher( | 
| + child_processes->back(), | 
| + IPC::ChannelHandle(channel_id | 
| +#if defined(OS_POSIX) | 
| + , | 
| + base::FileDescriptor(ipcfd, false) | 
| +#endif | 
| + ), | 
| + client)); | 
| +} | 
| + | 
| +class RPHIBrowserContext : public TestBrowserContext { | 
| + private: | 
| + virtual net::URLRequestContextGetter* GetRequestContextForRenderProcess( | 
| + int renderer_child_id) OVERRIDE { | 
| + return GetRequestContext(); | 
| + } | 
| +}; | 
| + | 
| +class RPHIBrowserClient : public TestContentBrowserClient { | 
| + public: | 
| + RPHIBrowserClient() | 
| + : request_context_getter_(new net::TestURLRequestContextGetter( | 
| + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO))) {} | 
| + | 
| + virtual net::URLRequestContextGetter* CreateRequestContext( | 
| + BrowserContext* browser_context, | 
| + ProtocolHandlerMap* protocol_handlers) OVERRIDE { | 
| + return request_context_getter_.get(); | 
| + } | 
| + | 
| + private: | 
| + scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_; | 
| +}; | 
| + | 
| +class FakeChildProcessRPHFactory : public RenderProcessHostFactory { | 
| + public: | 
| + FakeChildProcessRPHFactory( | 
| + ScopedVector<LaunchedChildProcess>* child_processes, | 
| + SiteInstance* site_instance) | 
| + : child_processes_(child_processes), site_instance_(site_instance) { | 
| + SiteInstanceImpl::set_render_process_host_factory(this); | 
| + } | 
| + virtual ~FakeChildProcessRPHFactory() { | 
| + SiteInstanceImpl::set_render_process_host_factory(NULL); | 
| + } | 
| + | 
| + virtual RenderProcessHost* CreateRenderProcessHost( | 
| + BrowserContext* browser_context) const OVERRIDE { | 
| + RenderProcessHostImpl* host = | 
| + new RenderProcessHostImpl(browser_context, | 
| + static_cast<StoragePartitionImpl*>( | 
| + TestBrowserContext::GetStoragePartition( | 
| + browser_context, site_instance_)), | 
| + false, | 
| + false); | 
| + host->SetChildProcessLauncherFactory( | 
| + base::Bind(&NewFakeChildProcessLauncher, child_processes_)); | 
| + return host; | 
| + } | 
| + | 
| + private: | 
| + ScopedVector<LaunchedChildProcess>* child_processes_; | 
| + SiteInstance* site_instance_; | 
| 
Charlie Reis
2013/06/05 19:07:10
It's not clear why this needs a SiteInstance, sinc
 
Jeffrey Yasskin
2013/06/05 21:23:27
Sure, done.
Another possible refactoring would be
 
Charlie Reis
2013/06/05 21:54:09
Good point!  That seems like a better solution, so
 
Jeffrey Yasskin
2013/06/05 22:53:20
I've put that in https://codereview.chromium.org/1
 | 
| +}; | 
| + | 
| +class RenderProcessHostImplTest : public testing::Test { | 
| + public: | 
| + RenderProcessHostImplTest() | 
| + : ui_thread_(BrowserThread::UI, &message_loop_), | 
| + webkit_thread_(BrowserThread::WEBKIT_DEPRECATED, &message_loop_), | 
| + file_user_blocking_thread_(BrowserThread::FILE_USER_BLOCKING, | 
| + &message_loop_), | 
| + io_thread_(BrowserThread::IO, &message_loop_), | 
| + old_browser_client_(SetBrowserClientForTesting(&test_browser_client_)) { | 
| + } | 
| + | 
| + virtual ~RenderProcessHostImplTest() { | 
| + base::RunLoop().RunUntilIdle(); | 
| + SetBrowserClientForTesting(old_browser_client_); | 
| + } | 
| + | 
| + protected: | 
| + ScopedVector<LaunchedChildProcess> child_processes_; | 
| + base::MessageLoopForIO message_loop_; | 
| + TestBrowserThread ui_thread_; | 
| + TestBrowserThread webkit_thread_; | 
| + TestBrowserThread file_user_blocking_thread_; | 
| + TestBrowserThread io_thread_; | 
| + | 
| + RPHIBrowserContext browser_context_; | 
| + RPHIBrowserClient test_browser_client_; | 
| + ContentBrowserClient* old_browser_client_; | 
| + ResourceDispatcherHostImpl resource_dispatcher_host_; | 
| + | 
| + DISALLOW_COPY_AND_ASSIGN(RenderProcessHostImplTest); | 
| +}; | 
| + | 
| +// http://crbug.com/87176 | 
| +TEST_F(RenderProcessHostImplTest, | 
| + RejectShutdownFromChildProcessWithOneActiveView) { | 
| + const GURL kUrl("http://example.com"); | 
| + | 
| + scoped_refptr<SiteInstance> site( | 
| + SiteInstance::CreateForURL(&browser_context_, kUrl)); | 
| + FakeChildProcessRPHFactory rph_factory(&child_processes_, site.get()); | 
| + | 
| + WebContents::CreateParams params(&browser_context_, site.get()); | 
| + scoped_ptr<WebContentsImpl> tab( | 
| + WebContentsImpl::CreateWithOpener(params, NULL)); | 
| + tab->GetController().LoadURL(kUrl, Referrer(), PAGE_TRANSITION_TYPED, ""); | 
| 
Charlie Reis
2013/06/05 19:07:10
How does example.com commit?  Am I missing a part
 
Jeffrey Yasskin
2013/06/05 21:23:27
No, AFAIK, there's no hard-coded response for it,
 
Charlie Reis
2013/06/05 21:54:09
Well, we usually use a TestWebContents and call Te
 
Jeffrey Yasskin
2013/06/05 22:53:20
I started this test with a TestWebContents, but Te
 | 
| + base::RunLoop().RunUntilIdle(); | 
| + ASSERT_EQ(1U, child_processes_.size()); | 
| + | 
| + child_processes_[0]->messages_.ClearMessages(); | 
| + | 
| + // Double-check that the loading tab isn't pending. | 
| + EXPECT_EQ(NULL, | 
| 
Charlie Reis
2013/06/05 19:07:10
Any reason not to use EXPECT_FALSE?  That seems to
 
Jeffrey Yasskin
2013/06/05 21:23:27
No particular reason. Done.
 | 
| + tab->GetRenderManagerForTesting()->pending_render_view_host()); | 
| + EXPECT_EQ(1, | 
| + static_cast<RenderProcessHostImpl*>( | 
| + tab->GetSiteInstance()->GetProcess())->GetActiveViewCount()); | 
| + | 
| + // Sometimes the renderer process's ShutdownRequest (corresponding to the | 
| + // ViewMsg_WasSwappedOut from a previous navigation) doesn't arrive until | 
| 
Charlie Reis
2013/06/05 19:07:10
Interesting.  I like that we don't have to simulat
 | 
| + // after the browser process decides to re-use the renderer for a new purpose. | 
| + // This test makes sure the browser doesn't let the renderer die in that case. | 
| + child_processes_[0]->launcher_ | 
| + ->Send(new ChildProcessHostMsg_ShutdownRequest()); | 
| 
Charlie Reis
2013/06/05 19:07:10
nit: -> on previous line
 
Jeffrey Yasskin
2013/06/05 21:23:27
Done. Since clang-format puts the -> at the beginn
 
Charlie Reis
2013/06/05 21:54:09
Thanks.  Not sure if there's an official rule on t
 | 
| + base::RunLoop().RunUntilIdle(); | 
| + EXPECT_EQ(NULL, | 
| 
Charlie Reis
2013/06/05 19:07:10
EXPECT_FALSE?
 
Jeffrey Yasskin
2013/06/05 21:23:27
Done.
 | 
| + child_processes_[0]->messages_.GetFirstMessageMatching( | 
| + ChildProcessMsg_Shutdown::ID)) | 
| + << "Host sent a message confirming that the renderer process should shut " | 
| + << "down even though it was still being used."; | 
| +} | 
| + | 
| +} // namespace | 
| +} // namespace content |