render_process_host_chrome_browsertest.cc revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
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.
4
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"
24
25using content::RenderViewHost;
26using content::RenderWidgetHost;
27using content::WebContents;
28
29namespace {
30
31int RenderProcessHostCount() {
32  content::RenderProcessHost::iterator hosts =
33      content::RenderProcessHost::AllHostsIterator();
34  int count = 0;
35  while (!hosts.IsAtEnd()) {
36    if (hosts.GetCurrentValue()->HasConnection())
37      count++;
38    hosts.Advance();
39  }
40  return count;
41}
42
43RenderViewHost* FindFirstDevToolsHost() {
44  scoped_ptr<content::RenderWidgetHostIterator> widgets(
45      RenderWidgetHost::GetRenderWidgetHosts());
46  while (content::RenderWidgetHost* widget = widgets->GetNextHost()) {
47    if (!widget->GetProcess()->HasConnection())
48      continue;
49    if (!widget->IsRenderView())
50      continue;
51    RenderViewHost* host = RenderViewHost::From(widget);
52    WebContents* contents = WebContents::FromRenderViewHost(host);
53    GURL url = contents->GetURL();
54    if (url.SchemeIs(chrome::kChromeDevToolsScheme))
55      return host;
56  }
57  return NULL;
58}
59
60}  // namespace
61
62class ChromeRenderProcessHostTest : public InProcessBrowserTest {
63 public:
64  ChromeRenderProcessHostTest() {}
65
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
68  // handle.
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);
73
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();
79
80    return wc->GetRenderProcessHost()->GetHandle();
81  }
82
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() {
88    int tab_count = 1;
89    int host_count = 1;
90    WebContents* tab1 = NULL;
91    WebContents* tab2 = NULL;
92    content::RenderProcessHost* rph1 = NULL;
93    content::RenderProcessHost* rph2 = NULL;
94    content::RenderProcessHost* rph3 = NULL;
95
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());
104
105    // Create a new TYPE_TABBED tab.  It should be in its own process.
106    GURL page1("data:text/html,hello world1");
107
108    ui_test_utils::WindowedTabAddedNotificationObserver observer1(
109        content::NotificationService::AllSources());
110    chrome::ShowSingletonTab(browser(), page1);
111    observer1.Wait();
112
113    tab_count++;
114    host_count++;
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);
121
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);
127    observer2.Wait();
128    tab_count++;
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);
134
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
138    // normal renderers.
139    GURL history(chrome::kChromeUIHistoryURL);
140    ui_test_utils::WindowedTabAddedNotificationObserver observer3(
141        content::NotificationService::AllSources());
142    chrome::ShowSingletonTab(browser(), history);
143    observer3.Wait();
144    tab_count++;
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);
150
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);
157    observer4.Wait();
158    tab_count++;
159    host_count++;
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);
167  }
168};
169
170
171class ChromeRenderProcessHostTestWithCommandLine
172    : public ChromeRenderProcessHostTest {
173 protected:
174  virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
175    command_line->AppendSwitchASCII(switches::kRendererProcessLimit, "1");
176  }
177};
178
179IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, ProcessPerTab) {
180  // Set max renderers to 1 to force running out of processes.
181  content::RenderProcessHost::SetMaxRendererProcessCount(1);
182
183  CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
184  parsed_command_line.AppendSwitch(switches::kProcessPerTab);
185
186  int tab_count = 1;
187  int host_count = 1;
188
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());
194
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);
200  observer1.Wait();
201  tab_count++;
202  host_count++;
203  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
204  EXPECT_EQ(host_count, RenderProcessHostCount());
205
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);
211  observer2.Wait();
212  tab_count++;
213  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
214  EXPECT_EQ(host_count, RenderProcessHostCount());
215
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());
220  observer3.Wait();
221  tab_count++;
222  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
223  EXPECT_EQ(host_count, RenderProcessHostCount());
224
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());
229  observer4.Wait();
230  tab_count++;
231  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
232  EXPECT_EQ(host_count, RenderProcessHostCount());
233}
234
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)
238IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, Backgrounding) {
239  if (!base::Process::CanBackgroundProcesses()) {
240    LOG(ERROR) << "Can't background processes";
241    return;
242  }
243  CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
244  parsed_command_line.AppendSwitch(switches::kProcessPerTab);
245
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);
249
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());
254
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());
262
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());
268}
269#endif
270
271// TODO(nasko): crbug.com/173137
272#if defined(OS_WIN)
273#define MAYBE_ProcessOverflow DISABLED_ProcessOverflow
274#else
275#define MAYBE_ProcessOverflow ProcessOverflow
276#endif
277
278IN_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();
282}
283
284// Variation of the ProcessOverflow test, which is driven through command line
285// parameter instead of direct function call into the class.
286IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTestWithCommandLine,
287                       ProcessOverflow) {
288  TestProcessOverflow();
289}
290
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.
293IN_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))
298    return;
299#endif
300
301  CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
302  parsed_command_line.AppendSwitch(switches::kProcessPerTab);
303
304  int tab_count = 1;
305  int host_count = 1;
306
307  GURL page1("data:text/html,hello world1");
308  ui_test_utils::WindowedTabAddedNotificationObserver observer1(
309      content::NotificationService::AllSources());
310  chrome::ShowSingletonTab(browser(), page1);
311  observer1.Wait();
312  tab_count++;
313  host_count++;
314  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
315  EXPECT_EQ(host_count, RenderProcessHostCount());
316
317  // DevTools start in docked mode (no new tab), in a separate process.
318  chrome::ToggleDevToolsWindow(browser(), DEVTOOLS_TOGGLE_ACTION_INSPECT);
319  host_count++;
320  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
321  EXPECT_EQ(host_count, RenderProcessHostCount());
322
323  RenderViewHost* devtools = FindFirstDevToolsHost();
324  DCHECK(devtools);
325
326  // DevTools start in a separate process.
327  DevToolsWindow::ToggleDevToolsWindow(
328      devtools, true, DEVTOOLS_TOGGLE_ACTION_INSPECT);
329  host_count++;
330  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
331  EXPECT_EQ(host_count, RenderProcessHostCount());
332
333  // close docked devtools
334  content::WindowedNotificationObserver close_observer(
335      content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
336      content::Source<WebContents>(WebContents::FromRenderViewHost(devtools)));
337
338  chrome::ToggleDevToolsWindow(browser(), DEVTOOLS_TOGGLE_ACTION_TOGGLE);
339  close_observer.Wait();
340}
341
342// Ensure that DevTools opened to debug DevTools is launched in a separate
343// process. See crbug.com/69873.
344IN_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))
349    return;
350#endif
351
352  int tab_count = 1;
353  int host_count = 1;
354
355  GURL page1("data:text/html,hello world1");
356  ui_test_utils::WindowedTabAddedNotificationObserver observer1(
357      content::NotificationService::AllSources());
358  chrome::ShowSingletonTab(browser(), page1);
359  observer1.Wait();
360  tab_count++;
361  host_count++;
362  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
363  EXPECT_EQ(host_count, RenderProcessHostCount());
364
365  // DevTools start in docked mode (no new tab), in a separate process.
366  chrome::ToggleDevToolsWindow(browser(), DEVTOOLS_TOGGLE_ACTION_INSPECT);
367  host_count++;
368  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
369  EXPECT_EQ(host_count, RenderProcessHostCount());
370
371  RenderViewHost* devtools = FindFirstDevToolsHost();
372  DCHECK(devtools);
373
374  // DevTools start in a separate process.
375  DevToolsWindow::ToggleDevToolsWindow(
376      devtools, true, DEVTOOLS_TOGGLE_ACTION_INSPECT);
377  host_count++;
378  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
379  EXPECT_EQ(host_count, RenderProcessHostCount());
380
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(), DEVTOOLS_TOGGLE_ACTION_TOGGLE);
387  close_observer.Wait();
388}
389
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.
393class WindowDestroyer : public content::WebContentsObserver {
394 public:
395  WindowDestroyer(content::WebContents* web_contents, TabStripModel* model)
396      : content::WebContentsObserver(web_contents),
397        tab_strip_model_(model) {
398  }
399
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();
408    observer.Wait();
409  }
410
411 private:
412  TabStripModel* tab_strip_model_;
413
414  DISALLOW_COPY_AND_ASSIGN(WindowDestroyer);
415};
416
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.
420IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,
421                       CloseAllTabsDuringProcessDied) {
422  GURL url(chrome::kChromeUINewTabURL);
423
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);
428
429  EXPECT_EQ(2, browser()->tab_strip_model()->count());
430
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());
434
435  // Create an object that will close the window on a process crash.
436  WindowDestroyer destroyer(wc1, browser()->tab_strip_model());
437
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());
444
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);
449
450  observer.Wait();
451}
452