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 "chrome/browser/chrome_notification_types.h"
7 #include "chrome/browser/devtools/devtools_window.h"
8 #include "chrome/browser/search/search.h"
9 #include "chrome/browser/ui/browser.h"
10 #include "chrome/browser/ui/browser_commands.h"
11 #include "chrome/browser/ui/singleton_tabs.h"
12 #include "chrome/browser/ui/tabs/tab_strip_model.h"
13 #include "chrome/common/chrome_switches.h"
14 #include "chrome/common/url_constants.h"
15 #include "chrome/test/base/in_process_browser_test.h"
16 #include "chrome/test/base/test_switches.h"
17 #include "chrome/test/base/ui_test_utils.h"
18 #include "content/public/browser/notification_service.h"
19 #include "content/public/browser/render_process_host.h"
20 #include "content/public/browser/render_view_host.h"
21 #include "content/public/browser/render_widget_host_iterator.h"
22 #include "content/public/browser/web_contents.h"
23 #include "content/public/browser/web_contents_observer.h"
25 using content::RenderViewHost;
26 using content::RenderWidgetHost;
27 using content::WebContents;
31 int RenderProcessHostCount() {
32 content::RenderProcessHost::iterator hosts =
33 content::RenderProcessHost::AllHostsIterator();
35 while (!hosts.IsAtEnd()) {
36 if (hosts.GetCurrentValue()->HasConnection())
43 RenderViewHost* FindFirstDevToolsHost() {
44 scoped_ptr<content::RenderWidgetHostIterator> widgets(
45 RenderWidgetHost::GetRenderWidgetHosts());
46 while (content::RenderWidgetHost* widget = widgets->GetNextHost()) {
47 if (!widget->GetProcess()->HasConnection())
49 if (!widget->IsRenderView())
51 RenderViewHost* host = RenderViewHost::From(widget);
52 WebContents* contents = WebContents::FromRenderViewHost(host);
53 GURL url = contents->GetURL();
54 if (url.SchemeIs(chrome::kChromeDevToolsScheme))
62 class ChromeRenderProcessHostTest : public InProcessBrowserTest {
64 ChromeRenderProcessHostTest() {}
66 // Show a tab, activating the current one if there is one, and wait for
67 // the renderer process to be created or foregrounded, returning the process
69 base::ProcessHandle ShowSingletonTab(const GURL& page) {
70 chrome::ShowSingletonTab(browser(), page);
71 WebContents* wc = browser()->tab_strip_model()->GetActiveWebContents();
72 CHECK(wc->GetURL() == page);
74 // Ensure that the backgrounding / foregrounding gets a chance to run.
75 content::BrowserThread::PostTaskAndReply(
76 content::BrowserThread::PROCESS_LAUNCHER, FROM_HERE,
77 base::Bind(&base::DoNothing), base::MessageLoop::QuitClosure());
78 base::MessageLoop::current()->Run();
80 return wc->GetRenderProcessHost()->GetHandle();
83 // When we hit the max number of renderers, verify that the way we do process
84 // sharing behaves correctly. In particular, this test is verifying that even
85 // when we hit the max process limit, that renderers of each type will wind up
86 // in a process of that type, even if that means creating a new process.
87 void TestProcessOverflow() {
90 WebContents* tab1 = NULL;
91 WebContents* tab2 = NULL;
92 content::RenderProcessHost* rph1 = NULL;
93 content::RenderProcessHost* rph2 = NULL;
94 content::RenderProcessHost* rph3 = NULL;
96 // Change the first tab to be the new tab page (TYPE_WEBUI).
97 GURL newtab(chrome::kChromeUINewTabURL);
98 ui_test_utils::NavigateToURL(browser(), newtab);
99 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
100 tab1 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
101 rph1 = tab1->GetRenderProcessHost();
102 EXPECT_TRUE(chrome::IsNTPURL(tab1->GetURL(), browser()->profile()));
103 EXPECT_EQ(host_count, RenderProcessHostCount());
105 // Create a new TYPE_TABBED tab. It should be in its own process.
106 GURL page1("data:text/html,hello world1");
108 ui_test_utils::WindowedTabAddedNotificationObserver observer1(
109 content::NotificationService::AllSources());
110 chrome::ShowSingletonTab(browser(), page1);
115 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
116 tab1 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
117 rph2 = tab1->GetRenderProcessHost();
118 EXPECT_EQ(tab1->GetURL(), page1);
119 EXPECT_EQ(host_count, RenderProcessHostCount());
120 EXPECT_NE(rph1, rph2);
122 // Create another TYPE_TABBED tab. It should share the previous process.
123 GURL page2("data:text/html,hello world2");
124 ui_test_utils::WindowedTabAddedNotificationObserver observer2(
125 content::NotificationService::AllSources());
126 chrome::ShowSingletonTab(browser(), page2);
129 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
130 tab2 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
131 EXPECT_EQ(tab2->GetURL(), page2);
132 EXPECT_EQ(host_count, RenderProcessHostCount());
133 EXPECT_EQ(tab2->GetRenderProcessHost(), rph2);
135 // Create another TYPE_WEBUI tab. It should share the process with newtab.
136 // Note: intentionally create this tab after the TYPE_TABBED tabs to
137 // exercise bug 43448 where extension and WebUI tabs could get combined into
139 GURL history(chrome::kChromeUIHistoryURL);
140 ui_test_utils::WindowedTabAddedNotificationObserver observer3(
141 content::NotificationService::AllSources());
142 chrome::ShowSingletonTab(browser(), history);
145 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
146 tab2 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
147 EXPECT_EQ(tab2->GetURL(), GURL(history));
148 EXPECT_EQ(host_count, RenderProcessHostCount());
149 EXPECT_EQ(tab2->GetRenderProcessHost(), rph1);
151 // Create a TYPE_EXTENSION tab. It should be in its own process.
152 // (the bookmark manager is implemented as an extension)
153 GURL bookmarks(chrome::kChromeUIBookmarksURL);
154 ui_test_utils::WindowedTabAddedNotificationObserver observer4(
155 content::NotificationService::AllSources());
156 chrome::ShowSingletonTab(browser(), bookmarks);
160 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
161 tab1 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
162 rph3 = tab1->GetRenderProcessHost();
163 EXPECT_EQ(tab1->GetURL(), bookmarks);
164 EXPECT_EQ(host_count, RenderProcessHostCount());
165 EXPECT_NE(rph1, rph3);
166 EXPECT_NE(rph2, rph3);
171 class ChromeRenderProcessHostTestWithCommandLine
172 : public ChromeRenderProcessHostTest {
174 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
175 command_line->AppendSwitchASCII(switches::kRendererProcessLimit, "1");
179 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, ProcessPerTab) {
180 // Set max renderers to 1 to force running out of processes.
181 content::RenderProcessHost::SetMaxRendererProcessCount(1);
183 CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
184 parsed_command_line.AppendSwitch(switches::kProcessPerTab);
189 // Change the first tab to be the new tab page (TYPE_WEBUI).
190 GURL newtab(chrome::kChromeUINewTabURL);
191 ui_test_utils::NavigateToURL(browser(), newtab);
192 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
193 EXPECT_EQ(host_count, RenderProcessHostCount());
195 // Create a new TYPE_TABBED tab. It should be in its own process.
196 GURL page1("data:text/html,hello world1");
197 ui_test_utils::WindowedTabAddedNotificationObserver observer1(
198 content::NotificationService::AllSources());
199 chrome::ShowSingletonTab(browser(), page1);
203 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
204 EXPECT_EQ(host_count, RenderProcessHostCount());
206 // Create another TYPE_TABBED tab. It should share the previous process.
207 GURL page2("data:text/html,hello world2");
208 ui_test_utils::WindowedTabAddedNotificationObserver observer2(
209 content::NotificationService::AllSources());
210 chrome::ShowSingletonTab(browser(), page2);
213 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
214 EXPECT_EQ(host_count, RenderProcessHostCount());
216 // Create another new tab. It should share the process with the other WebUI.
217 ui_test_utils::WindowedTabAddedNotificationObserver observer3(
218 content::NotificationService::AllSources());
219 chrome::NewTab(browser());
222 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
223 EXPECT_EQ(host_count, RenderProcessHostCount());
225 // Create another new tab. It should share the process with the other WebUI.
226 ui_test_utils::WindowedTabAddedNotificationObserver observer4(
227 content::NotificationService::AllSources());
228 chrome::NewTab(browser());
231 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
232 EXPECT_EQ(host_count, RenderProcessHostCount());
235 // We don't change process priorities on Mac or Posix because the user lacks the
236 // permission to raise a process' priority even after lowering it.
237 #if defined(OS_WIN) || defined(OS_LINUX)
238 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, Backgrounding) {
239 if (!base::Process::CanBackgroundProcesses()) {
240 LOG(ERROR) << "Can't background processes";
243 CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
244 parsed_command_line.AppendSwitch(switches::kProcessPerTab);
246 // Change the first tab to be the new tab page (TYPE_WEBUI).
247 GURL newtab(chrome::kChromeUINewTabURL);
248 ui_test_utils::NavigateToURL(browser(), newtab);
250 // Create a new tab. It should be foreground.
251 GURL page1("data:text/html,hello world1");
252 base::ProcessHandle pid1 = ShowSingletonTab(page1);
253 EXPECT_FALSE(base::Process(pid1).IsProcessBackgrounded());
255 // Create another tab. It should be foreground, and the first tab should
256 // now be background.
257 GURL page2("data:text/html,hello world2");
258 base::ProcessHandle pid2 = ShowSingletonTab(page2);
259 EXPECT_NE(pid1, pid2);
260 EXPECT_TRUE(base::Process(pid1).IsProcessBackgrounded());
261 EXPECT_FALSE(base::Process(pid2).IsProcessBackgrounded());
263 // Navigate back to first page. It should be foreground again, and the second
264 // tab should be background.
265 EXPECT_EQ(pid1, ShowSingletonTab(page1));
266 EXPECT_FALSE(base::Process(pid1).IsProcessBackgrounded());
267 EXPECT_TRUE(base::Process(pid2).IsProcessBackgrounded());
271 // TODO(nasko): crbug.com/173137
273 #define MAYBE_ProcessOverflow DISABLED_ProcessOverflow
275 #define MAYBE_ProcessOverflow ProcessOverflow
278 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, MAYBE_ProcessOverflow) {
279 // Set max renderers to 1 to force running out of processes.
280 content::RenderProcessHost::SetMaxRendererProcessCount(1);
281 TestProcessOverflow();
284 // Variation of the ProcessOverflow test, which is driven through command line
285 // parameter instead of direct function call into the class.
286 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTestWithCommandLine,
288 TestProcessOverflow();
291 // Ensure that DevTools opened to debug DevTools is launched in a separate
292 // process when --process-per-tab is set. See crbug.com/69873.
293 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,
294 DevToolsOnSelfInOwnProcessPPT) {
295 #if defined(OS_WIN) && defined(USE_ASH)
296 // Disable this test in Metro+Ash for now (http://crbug.com/262796).
297 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests))
301 CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
302 parsed_command_line.AppendSwitch(switches::kProcessPerTab);
307 GURL page1("data:text/html,hello world1");
308 ui_test_utils::WindowedTabAddedNotificationObserver observer1(
309 content::NotificationService::AllSources());
310 chrome::ShowSingletonTab(browser(), page1);
314 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
315 EXPECT_EQ(host_count, RenderProcessHostCount());
317 // DevTools start in docked mode (no new tab), in a separate process.
318 chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Inspect());
320 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
321 EXPECT_EQ(host_count, RenderProcessHostCount());
323 RenderViewHost* devtools = FindFirstDevToolsHost();
326 // DevTools start in a separate process.
327 DevToolsWindow::ToggleDevToolsWindow(
328 devtools, true, DevToolsToggleAction::Inspect());
330 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
331 EXPECT_EQ(host_count, RenderProcessHostCount());
333 // close docked devtools
334 content::WindowedNotificationObserver close_observer(
335 content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
336 content::Source<WebContents>(WebContents::FromRenderViewHost(devtools)));
338 chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Toggle());
339 close_observer.Wait();
342 // Ensure that DevTools opened to debug DevTools is launched in a separate
343 // process. See crbug.com/69873.
344 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,
345 DevToolsOnSelfInOwnProcess) {
346 #if defined(OS_WIN) && defined(USE_ASH)
347 // Disable this test in Metro+Ash for now (http://crbug.com/262796).
348 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests))
355 GURL page1("data:text/html,hello world1");
356 ui_test_utils::WindowedTabAddedNotificationObserver observer1(
357 content::NotificationService::AllSources());
358 chrome::ShowSingletonTab(browser(), page1);
362 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
363 EXPECT_EQ(host_count, RenderProcessHostCount());
365 // DevTools start in docked mode (no new tab), in a separate process.
366 chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Inspect());
368 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
369 EXPECT_EQ(host_count, RenderProcessHostCount());
371 RenderViewHost* devtools = FindFirstDevToolsHost();
374 // DevTools start in a separate process.
375 DevToolsWindow::ToggleDevToolsWindow(
376 devtools, true, DevToolsToggleAction::Inspect());
378 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
379 EXPECT_EQ(host_count, RenderProcessHostCount());
381 // close docked devtools
382 content::WindowedNotificationObserver close_observer(
383 content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
384 content::Source<content::WebContents>(
385 WebContents::FromRenderViewHost(devtools)));
386 chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Toggle());
387 close_observer.Wait();
390 // This class's goal is to close the browser window when a renderer process has
391 // crashed. It does so by monitoring WebContents for RenderProcessGone event and
392 // closing the passed in TabStripModel. This is used in the following test case.
393 class WindowDestroyer : public content::WebContentsObserver {
395 WindowDestroyer(content::WebContents* web_contents, TabStripModel* model)
396 : content::WebContentsObserver(web_contents),
397 tab_strip_model_(model) {
400 virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE {
401 // Wait for the window to be destroyed, which will ensure all other
402 // RenderViewHost objects are deleted before we return and proceed with
403 // the next iteration of notifications.
404 content::WindowedNotificationObserver observer(
405 chrome::NOTIFICATION_BROWSER_CLOSED,
406 content::NotificationService::AllSources());
407 tab_strip_model_->CloseAllTabs();
412 TabStripModel* tab_strip_model_;
414 DISALLOW_COPY_AND_ASSIGN(WindowDestroyer);
417 // Test to ensure that while iterating through all listeners in
418 // RenderProcessHost and invalidating them, we remove them properly and don't
419 // access already freed objects. See http://crbug.com/255524.
420 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,
421 CloseAllTabsDuringProcessDied) {
422 GURL url(chrome::kChromeUINewTabURL);
424 ui_test_utils::NavigateToURL(browser(), url);
425 ui_test_utils::NavigateToURLWithDisposition(
426 browser(), url, NEW_BACKGROUND_TAB,
427 ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
429 EXPECT_EQ(2, browser()->tab_strip_model()->count());
431 WebContents* wc1 = browser()->tab_strip_model()->GetWebContentsAt(0);
432 WebContents* wc2 = browser()->tab_strip_model()->GetWebContentsAt(1);
433 EXPECT_EQ(wc1->GetRenderProcessHost(), wc2->GetRenderProcessHost());
435 // Create an object that will close the window on a process crash.
436 WindowDestroyer destroyer(wc1, browser()->tab_strip_model());
438 // Use NOTIFICATION_BROWSER_CLOSED instead of NOTIFICATION_WINDOW_CLOSED,
439 // since the latter is not implemented on OSX and the test will timeout,
440 // causing it to fail.
441 content::WindowedNotificationObserver observer(
442 chrome::NOTIFICATION_BROWSER_CLOSED,
443 content::NotificationService::AllSources());
445 // Kill the renderer process, simulating a crash. This should the ProcessDied
446 // method to be called. Alternatively, RenderProcessHost::OnChannelError can
447 // be called to directly force a call to ProcessDied.
448 base::KillProcess(wc1->GetRenderProcessHost()->GetHandle(), -1, true);