1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/command_line.h"
6 #include "base/process/process.h"
7 #include "chrome/browser/chrome_notification_types.h"
8 #include "chrome/browser/devtools/devtools_window.h"
9 #include "chrome/browser/search/search.h"
10 #include "chrome/browser/ui/browser.h"
11 #include "chrome/browser/ui/browser_commands.h"
12 #include "chrome/browser/ui/singleton_tabs.h"
13 #include "chrome/browser/ui/tabs/tab_strip_model.h"
14 #include "chrome/common/chrome_switches.h"
15 #include "chrome/common/url_constants.h"
16 #include "chrome/test/base/in_process_browser_test.h"
17 #include "chrome/test/base/test_switches.h"
18 #include "chrome/test/base/ui_test_utils.h"
19 #include "content/public/browser/notification_service.h"
20 #include "content/public/browser/render_process_host.h"
21 #include "content/public/browser/render_view_host.h"
22 #include "content/public/browser/render_widget_host_iterator.h"
23 #include "content/public/browser/web_contents.h"
24 #include "content/public/browser/web_contents_observer.h"
25 #include "content/public/test/browser_test_utils.h"
27 using content::RenderViewHost
;
28 using content::RenderWidgetHost
;
29 using content::WebContents
;
33 int RenderProcessHostCount() {
34 content::RenderProcessHost::iterator hosts
=
35 content::RenderProcessHost::AllHostsIterator();
37 while (!hosts
.IsAtEnd()) {
38 if (hosts
.GetCurrentValue()->HasConnection())
45 WebContents
* FindFirstDevToolsContents() {
46 scoped_ptr
<content::RenderWidgetHostIterator
> widgets(
47 RenderWidgetHost::GetRenderWidgetHosts());
48 while (content::RenderWidgetHost
* widget
= widgets
->GetNextHost()) {
49 if (!widget
->GetProcess()->HasConnection())
51 if (!widget
->IsRenderView())
53 RenderViewHost
* host
= RenderViewHost::From(widget
);
54 WebContents
* contents
= WebContents::FromRenderViewHost(host
);
55 GURL url
= contents
->GetURL();
56 if (url
.SchemeIs(content::kChromeDevToolsScheme
))
62 // TODO(rvargas) crbug.com/417532: Remove this code.
63 base::Process
ProcessFromHandle(base::ProcessHandle handle
) {
65 if (handle
== GetCurrentProcess())
66 return base::Process::Current();
68 base::ProcessHandle out_handle
;
69 if (!::DuplicateHandle(GetCurrentProcess(), handle
, GetCurrentProcess(),
70 &out_handle
, 0, FALSE
, DUPLICATE_SAME_ACCESS
)) {
71 return base::Process();
74 #endif // defined(OS_WIN)
75 return base::Process(handle
);
80 class ChromeRenderProcessHostTest
: public InProcessBrowserTest
{
82 ChromeRenderProcessHostTest() {}
84 // Show a tab, activating the current one if there is one, and wait for
85 // the renderer process to be created or foregrounded, returning the process
87 base::Process
ShowSingletonTab(const GURL
& page
) {
88 chrome::ShowSingletonTab(browser(), page
);
89 WebContents
* wc
= browser()->tab_strip_model()->GetActiveWebContents();
90 CHECK(wc
->GetURL() == page
);
92 WaitForLauncherThread();
93 WaitForMessageProcessing(wc
);
94 return ProcessFromHandle(wc
->GetRenderProcessHost()->GetHandle());
97 // Loads the given url in a new background tab and returns the handle of its
99 base::Process
OpenBackgroundTab(const GURL
& page
) {
100 ui_test_utils::NavigateToURLWithDisposition(browser(), page
,
101 NEW_BACKGROUND_TAB
, ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION
);
103 TabStripModel
* tab_strip
= browser()->tab_strip_model();
104 WebContents
* wc
= tab_strip
->GetWebContentsAt(
105 tab_strip
->active_index() + 1);
106 CHECK(wc
->GetVisibleURL() == page
);
108 WaitForLauncherThread();
109 WaitForMessageProcessing(wc
);
110 return ProcessFromHandle(wc
->GetRenderProcessHost()->GetHandle());
113 // Ensures that the backgrounding / foregrounding gets a chance to run.
114 void WaitForLauncherThread() {
115 content::BrowserThread::PostTaskAndReply(
116 content::BrowserThread::PROCESS_LAUNCHER
, FROM_HERE
,
117 base::Bind(&base::DoNothing
), base::MessageLoop::QuitClosure());
118 base::MessageLoop::current()->Run();
121 // Implicitly waits for the renderer process associated with the specified
122 // WebContents to process outstanding IPC messages by running some JavaScript
123 // and waiting for the result.
124 void WaitForMessageProcessing(WebContents
* wc
) {
126 ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
127 wc
, "window.domAutomationController.send(true);", &result
));
131 // When we hit the max number of renderers, verify that the way we do process
132 // sharing behaves correctly. In particular, this test is verifying that even
133 // when we hit the max process limit, that renderers of each type will wind up
134 // in a process of that type, even if that means creating a new process.
135 void TestProcessOverflow() {
138 WebContents
* tab1
= NULL
;
139 WebContents
* tab2
= NULL
;
140 content::RenderProcessHost
* rph1
= NULL
;
141 content::RenderProcessHost
* rph2
= NULL
;
142 content::RenderProcessHost
* rph3
= NULL
;
144 // Change the first tab to be the omnibox page (TYPE_WEBUI).
145 GURL
omnibox(chrome::kChromeUIOmniboxURL
);
146 ui_test_utils::NavigateToURL(browser(), omnibox
);
147 EXPECT_EQ(tab_count
, browser()->tab_strip_model()->count());
148 tab1
= browser()->tab_strip_model()->GetWebContentsAt(tab_count
- 1);
149 rph1
= tab1
->GetRenderProcessHost();
150 EXPECT_EQ(omnibox
, tab1
->GetURL());
151 EXPECT_EQ(host_count
, RenderProcessHostCount());
153 // Create a new TYPE_TABBED tab. It should be in its own process.
154 GURL
page1("data:text/html,hello world1");
156 ui_test_utils::WindowedTabAddedNotificationObserver
observer1(
157 content::NotificationService::AllSources());
158 chrome::ShowSingletonTab(browser(), page1
);
163 EXPECT_EQ(tab_count
, browser()->tab_strip_model()->count());
164 tab1
= browser()->tab_strip_model()->GetWebContentsAt(tab_count
- 1);
165 rph2
= tab1
->GetRenderProcessHost();
166 EXPECT_EQ(tab1
->GetURL(), page1
);
167 EXPECT_EQ(host_count
, RenderProcessHostCount());
168 EXPECT_NE(rph1
, rph2
);
170 // Create another TYPE_TABBED tab. It should share the previous process.
171 GURL
page2("data:text/html,hello world2");
172 ui_test_utils::WindowedTabAddedNotificationObserver
observer2(
173 content::NotificationService::AllSources());
174 chrome::ShowSingletonTab(browser(), page2
);
177 EXPECT_EQ(tab_count
, browser()->tab_strip_model()->count());
178 tab2
= browser()->tab_strip_model()->GetWebContentsAt(tab_count
- 1);
179 EXPECT_EQ(tab2
->GetURL(), page2
);
180 EXPECT_EQ(host_count
, RenderProcessHostCount());
181 EXPECT_EQ(tab2
->GetRenderProcessHost(), rph2
);
183 // Create another TYPE_WEBUI tab. It should share the process with omnibox.
184 // Note: intentionally create this tab after the TYPE_TABBED tabs to
185 // exercise bug 43448 where extension and WebUI tabs could get combined into
187 GURL
history(chrome::kChromeUIHistoryURL
);
188 ui_test_utils::WindowedTabAddedNotificationObserver
observer3(
189 content::NotificationService::AllSources());
190 chrome::ShowSingletonTab(browser(), history
);
193 EXPECT_EQ(tab_count
, browser()->tab_strip_model()->count());
194 tab2
= browser()->tab_strip_model()->GetWebContentsAt(tab_count
- 1);
195 EXPECT_EQ(tab2
->GetURL(), GURL(history
));
196 EXPECT_EQ(host_count
, RenderProcessHostCount());
197 EXPECT_EQ(tab2
->GetRenderProcessHost(), rph1
);
199 // Create a TYPE_EXTENSION tab. It should be in its own process.
200 // (the bookmark manager is implemented as an extension)
201 GURL
bookmarks(chrome::kChromeUIBookmarksURL
);
202 ui_test_utils::WindowedTabAddedNotificationObserver
observer4(
203 content::NotificationService::AllSources());
204 chrome::ShowSingletonTab(browser(), bookmarks
);
208 EXPECT_EQ(tab_count
, browser()->tab_strip_model()->count());
209 tab1
= browser()->tab_strip_model()->GetWebContentsAt(tab_count
- 1);
210 rph3
= tab1
->GetRenderProcessHost();
211 EXPECT_EQ(tab1
->GetURL(), bookmarks
);
212 EXPECT_EQ(host_count
, RenderProcessHostCount());
213 EXPECT_NE(rph1
, rph3
);
214 EXPECT_NE(rph2
, rph3
);
219 class ChromeRenderProcessHostTestWithCommandLine
220 : public ChromeRenderProcessHostTest
{
222 virtual void SetUpCommandLine(CommandLine
* command_line
) override
{
223 command_line
->AppendSwitchASCII(switches::kRendererProcessLimit
, "1");
227 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest
, ProcessPerTab
) {
228 // Set max renderers to 1 to force running out of processes.
229 content::RenderProcessHost::SetMaxRendererProcessCount(1);
231 CommandLine
& parsed_command_line
= *CommandLine::ForCurrentProcess();
232 parsed_command_line
.AppendSwitch(switches::kProcessPerTab
);
237 // Change the first tab to be the new tab page (TYPE_WEBUI).
238 GURL
omnibox(chrome::kChromeUIOmniboxURL
);
239 ui_test_utils::NavigateToURL(browser(), omnibox
);
240 EXPECT_EQ(tab_count
, browser()->tab_strip_model()->count());
241 EXPECT_EQ(host_count
, RenderProcessHostCount());
243 // Create a new TYPE_TABBED tab. It should be in its own process.
244 GURL
page1("data:text/html,hello world1");
245 ui_test_utils::WindowedTabAddedNotificationObserver
observer1(
246 content::NotificationService::AllSources());
247 chrome::ShowSingletonTab(browser(), page1
);
251 EXPECT_EQ(tab_count
, browser()->tab_strip_model()->count());
252 EXPECT_EQ(host_count
, RenderProcessHostCount());
254 // Create another TYPE_TABBED tab. It should share the previous process.
255 GURL
page2("data:text/html,hello world2");
256 ui_test_utils::WindowedTabAddedNotificationObserver
observer2(
257 content::NotificationService::AllSources());
258 chrome::ShowSingletonTab(browser(), page2
);
261 EXPECT_EQ(tab_count
, browser()->tab_strip_model()->count());
262 EXPECT_EQ(host_count
, RenderProcessHostCount());
264 // Create another omnibox tab. It should share the process with the other
266 ui_test_utils::NavigateToURLWithDisposition(
267 browser(), omnibox
, NEW_FOREGROUND_TAB
,
268 ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION
);
270 EXPECT_EQ(tab_count
, browser()->tab_strip_model()->count());
271 EXPECT_EQ(host_count
, RenderProcessHostCount());
273 // Create another omnibox tab. It should share the process with the other
275 ui_test_utils::NavigateToURLWithDisposition(
276 browser(), omnibox
, NEW_FOREGROUND_TAB
,
277 ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION
);
279 EXPECT_EQ(tab_count
, browser()->tab_strip_model()->count());
280 EXPECT_EQ(host_count
, RenderProcessHostCount());
283 // We don't change process priorities on Mac or Posix because the user lacks the
284 // permission to raise a process' priority even after lowering it.
285 #if defined(OS_WIN) || defined(OS_LINUX)
287 // Flaky test: crbug.com/394368
288 #define MAYBE_Backgrounding DISABLED_Backgrounding
290 #define MAYBE_Backgrounding Backgrounding
292 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest
, MAYBE_Backgrounding
) {
293 if (!base::Process::CanBackgroundProcesses()) {
294 LOG(ERROR
) << "Can't background processes";
297 CommandLine
& parsed_command_line
= *CommandLine::ForCurrentProcess();
298 parsed_command_line
.AppendSwitch(switches::kProcessPerTab
);
300 // Change the first tab to be the omnibox page (TYPE_WEBUI).
301 GURL
omnibox(chrome::kChromeUIOmniboxURL
);
302 ui_test_utils::NavigateToURL(browser(), omnibox
);
304 // Create a new tab. It should be foreground.
305 GURL
page1("data:text/html,hello world1");
306 base::Process process1
= ShowSingletonTab(page1
);
307 ASSERT_TRUE(process1
.IsValid());
308 EXPECT_FALSE(process1
.IsProcessBackgrounded());
310 // Create another tab. It should be foreground, and the first tab should
311 // now be background.
312 GURL
page2("data:text/html,hello world2");
313 base::Process process2
= ShowSingletonTab(page2
);
314 ASSERT_TRUE(process2
.IsValid());
315 EXPECT_NE(process1
.pid(), process2
.pid());
316 EXPECT_TRUE(process1
.IsProcessBackgrounded());
317 EXPECT_FALSE(process2
.IsProcessBackgrounded());
319 // Load another tab in background. The renderer of the new tab should be
320 // backgrounded, while visibility of the other renderers should not change.
321 GURL
page3("data:text/html,hello world3");
322 base::Process process3
= OpenBackgroundTab(page3
);
323 ASSERT_TRUE(process3
.IsValid());
324 EXPECT_NE(process3
.pid(), process1
.pid());
325 EXPECT_NE(process3
.pid(), process2
.pid());
326 EXPECT_TRUE(process1
.IsProcessBackgrounded());
327 EXPECT_FALSE(process2
.IsProcessBackgrounded());
328 EXPECT_TRUE(process3
.IsProcessBackgrounded());
330 // Navigate back to the first page. Its renderer should be in foreground
331 // again while the other renderers should be backgrounded.
332 EXPECT_EQ(process1
.pid(), ShowSingletonTab(page1
).pid());
333 EXPECT_FALSE(process1
.IsProcessBackgrounded());
334 EXPECT_TRUE(process2
.IsProcessBackgrounded());
335 EXPECT_TRUE(process3
.IsProcessBackgrounded());
339 // TODO(nasko): crbug.com/173137
341 #define MAYBE_ProcessOverflow DISABLED_ProcessOverflow
343 #define MAYBE_ProcessOverflow ProcessOverflow
346 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest
, MAYBE_ProcessOverflow
) {
347 // Set max renderers to 1 to force running out of processes.
348 content::RenderProcessHost::SetMaxRendererProcessCount(1);
349 TestProcessOverflow();
352 // Variation of the ProcessOverflow test, which is driven through command line
353 // parameter instead of direct function call into the class.
354 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTestWithCommandLine
,
356 TestProcessOverflow();
359 // Ensure that DevTools opened to debug DevTools is launched in a separate
360 // process when --process-per-tab is set. See crbug.com/69873.
361 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest
,
362 DevToolsOnSelfInOwnProcessPPT
) {
363 #if defined(OS_WIN) && defined(USE_ASH)
364 // Disable this test in Metro+Ash for now (http://crbug.com/262796).
365 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests
))
369 CommandLine
& parsed_command_line
= *CommandLine::ForCurrentProcess();
370 parsed_command_line
.AppendSwitch(switches::kProcessPerTab
);
375 GURL
page1("data:text/html,hello world1");
376 ui_test_utils::WindowedTabAddedNotificationObserver
observer1(
377 content::NotificationService::AllSources());
378 chrome::ShowSingletonTab(browser(), page1
);
382 EXPECT_EQ(tab_count
, browser()->tab_strip_model()->count());
383 EXPECT_EQ(host_count
, RenderProcessHostCount());
385 // DevTools start in docked mode (no new tab), in a separate process.
386 chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Inspect());
388 EXPECT_EQ(tab_count
, browser()->tab_strip_model()->count());
389 EXPECT_EQ(host_count
, RenderProcessHostCount());
391 WebContents
* devtools
= FindFirstDevToolsContents();
394 // DevTools start in a separate process.
395 DevToolsWindow::OpenDevToolsWindow(devtools
, DevToolsToggleAction::Inspect());
397 EXPECT_EQ(tab_count
, browser()->tab_strip_model()->count());
398 EXPECT_EQ(host_count
, RenderProcessHostCount());
400 // close docked devtools
401 content::WindowedNotificationObserver
close_observer(
402 content::NOTIFICATION_WEB_CONTENTS_DESTROYED
,
403 content::Source
<WebContents
>(devtools
));
405 chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Toggle());
406 close_observer
.Wait();
409 // Ensure that DevTools opened to debug DevTools is launched in a separate
410 // process. See crbug.com/69873.
411 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest
,
412 DevToolsOnSelfInOwnProcess
) {
413 #if defined(OS_WIN) && defined(USE_ASH)
414 // Disable this test in Metro+Ash for now (http://crbug.com/262796).
415 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests
))
422 GURL
page1("data:text/html,hello world1");
423 ui_test_utils::WindowedTabAddedNotificationObserver
observer1(
424 content::NotificationService::AllSources());
425 chrome::ShowSingletonTab(browser(), page1
);
429 EXPECT_EQ(tab_count
, browser()->tab_strip_model()->count());
430 EXPECT_EQ(host_count
, RenderProcessHostCount());
432 // DevTools start in docked mode (no new tab), in a separate process.
433 chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Inspect());
435 EXPECT_EQ(tab_count
, browser()->tab_strip_model()->count());
436 EXPECT_EQ(host_count
, RenderProcessHostCount());
438 WebContents
* devtools
= FindFirstDevToolsContents();
441 // DevTools start in a separate process.
442 DevToolsWindow::OpenDevToolsWindow(devtools
, DevToolsToggleAction::Inspect());
444 EXPECT_EQ(tab_count
, browser()->tab_strip_model()->count());
445 EXPECT_EQ(host_count
, RenderProcessHostCount());
447 // close docked devtools
448 content::WindowedNotificationObserver
close_observer(
449 content::NOTIFICATION_WEB_CONTENTS_DESTROYED
,
450 content::Source
<content::WebContents
>(devtools
));
451 chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Toggle());
452 close_observer
.Wait();
455 // This class's goal is to close the browser window when a renderer process has
456 // crashed. It does so by monitoring WebContents for RenderProcessGone event and
457 // closing the passed in TabStripModel. This is used in the following test case.
458 class WindowDestroyer
: public content::WebContentsObserver
{
460 WindowDestroyer(content::WebContents
* web_contents
, TabStripModel
* model
)
461 : content::WebContentsObserver(web_contents
),
462 tab_strip_model_(model
) {
465 virtual void RenderProcessGone(base::TerminationStatus status
) override
{
466 // Wait for the window to be destroyed, which will ensure all other
467 // RenderViewHost objects are deleted before we return and proceed with
468 // the next iteration of notifications.
469 content::WindowedNotificationObserver
observer(
470 chrome::NOTIFICATION_BROWSER_CLOSED
,
471 content::NotificationService::AllSources());
472 tab_strip_model_
->CloseAllTabs();
477 TabStripModel
* tab_strip_model_
;
479 DISALLOW_COPY_AND_ASSIGN(WindowDestroyer
);
482 // Test to ensure that while iterating through all listeners in
483 // RenderProcessHost and invalidating them, we remove them properly and don't
484 // access already freed objects. See http://crbug.com/255524.
485 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest
,
486 CloseAllTabsDuringProcessDied
) {
487 GURL
url(chrome::kChromeUIOmniboxURL
);
489 ui_test_utils::NavigateToURL(browser(), url
);
490 ui_test_utils::NavigateToURLWithDisposition(
491 browser(), url
, NEW_BACKGROUND_TAB
,
492 ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION
);
494 EXPECT_EQ(2, browser()->tab_strip_model()->count());
496 WebContents
* wc1
= browser()->tab_strip_model()->GetWebContentsAt(0);
497 WebContents
* wc2
= browser()->tab_strip_model()->GetWebContentsAt(1);
498 EXPECT_EQ(wc1
->GetRenderProcessHost(), wc2
->GetRenderProcessHost());
500 // Create an object that will close the window on a process crash.
501 WindowDestroyer
destroyer(wc1
, browser()->tab_strip_model());
503 // Use NOTIFICATION_BROWSER_CLOSED instead of NOTIFICATION_WINDOW_CLOSED,
504 // since the latter is not implemented on OSX and the test will timeout,
505 // causing it to fail.
506 content::WindowedNotificationObserver
observer(
507 chrome::NOTIFICATION_BROWSER_CLOSED
,
508 content::NotificationService::AllSources());
510 // Kill the renderer process, simulating a crash. This should the ProcessDied
511 // method to be called. Alternatively, RenderProcessHost::OnChannelError can
512 // be called to directly force a call to ProcessDied.
513 base::KillProcess(wc1
->GetRenderProcessHost()->GetHandle(), -1, true);