render_process_host_chrome_browsertest.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
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(content::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    WaitForLauncherThread();
75    return wc->GetRenderProcessHost()->GetHandle();
76  }
77
78  // Loads the given url in a new background tab and returns the handle of its
79  // renderer.
80  base::ProcessHandle OpenBackgroundTab(const GURL& page) {
81    ui_test_utils::NavigateToURLWithDisposition(browser(), page,
82        NEW_BACKGROUND_TAB, ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
83
84    TabStripModel* tab_strip = browser()->tab_strip_model();
85    WebContents* wc = tab_strip->GetWebContentsAt(
86        tab_strip->active_index() + 1);
87    CHECK(wc->GetVisibleURL() == page);
88
89    WaitForLauncherThread();
90    return wc->GetRenderProcessHost()->GetHandle();
91  }
92
93  // Ensures that the backgrounding / foregrounding gets a chance to run.
94  void WaitForLauncherThread() {
95    content::BrowserThread::PostTaskAndReply(
96        content::BrowserThread::PROCESS_LAUNCHER, FROM_HERE,
97        base::Bind(&base::DoNothing), base::MessageLoop::QuitClosure());
98    base::MessageLoop::current()->Run();
99  }
100
101  // When we hit the max number of renderers, verify that the way we do process
102  // sharing behaves correctly.  In particular, this test is verifying that even
103  // when we hit the max process limit, that renderers of each type will wind up
104  // in a process of that type, even if that means creating a new process.
105  void TestProcessOverflow() {
106    int tab_count = 1;
107    int host_count = 1;
108    WebContents* tab1 = NULL;
109    WebContents* tab2 = NULL;
110    content::RenderProcessHost* rph1 = NULL;
111    content::RenderProcessHost* rph2 = NULL;
112    content::RenderProcessHost* rph3 = NULL;
113
114    // Change the first tab to be the omnibox page (TYPE_WEBUI).
115    GURL omnibox(chrome::kChromeUIOmniboxURL);
116    ui_test_utils::NavigateToURL(browser(), omnibox);
117    EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
118    tab1 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
119    rph1 = tab1->GetRenderProcessHost();
120    EXPECT_EQ(omnibox, tab1->GetURL());
121    EXPECT_EQ(host_count, RenderProcessHostCount());
122
123    // Create a new TYPE_TABBED tab.  It should be in its own process.
124    GURL page1("data:text/html,hello world1");
125
126    ui_test_utils::WindowedTabAddedNotificationObserver observer1(
127        content::NotificationService::AllSources());
128    chrome::ShowSingletonTab(browser(), page1);
129    observer1.Wait();
130
131    tab_count++;
132    host_count++;
133    EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
134    tab1 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
135    rph2 = tab1->GetRenderProcessHost();
136    EXPECT_EQ(tab1->GetURL(), page1);
137    EXPECT_EQ(host_count, RenderProcessHostCount());
138    EXPECT_NE(rph1, rph2);
139
140    // Create another TYPE_TABBED tab.  It should share the previous process.
141    GURL page2("data:text/html,hello world2");
142    ui_test_utils::WindowedTabAddedNotificationObserver observer2(
143        content::NotificationService::AllSources());
144    chrome::ShowSingletonTab(browser(), page2);
145    observer2.Wait();
146    tab_count++;
147    EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
148    tab2 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
149    EXPECT_EQ(tab2->GetURL(), page2);
150    EXPECT_EQ(host_count, RenderProcessHostCount());
151    EXPECT_EQ(tab2->GetRenderProcessHost(), rph2);
152
153    // Create another TYPE_WEBUI tab.  It should share the process with omnibox.
154    // Note: intentionally create this tab after the TYPE_TABBED tabs to
155    // exercise bug 43448 where extension and WebUI tabs could get combined into
156    // normal renderers.
157    GURL history(chrome::kChromeUIHistoryURL);
158    ui_test_utils::WindowedTabAddedNotificationObserver observer3(
159        content::NotificationService::AllSources());
160    chrome::ShowSingletonTab(browser(), history);
161    observer3.Wait();
162    tab_count++;
163    EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
164    tab2 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
165    EXPECT_EQ(tab2->GetURL(), GURL(history));
166    EXPECT_EQ(host_count, RenderProcessHostCount());
167    EXPECT_EQ(tab2->GetRenderProcessHost(), rph1);
168
169    // Create a TYPE_EXTENSION tab.  It should be in its own process.
170    // (the bookmark manager is implemented as an extension)
171    GURL bookmarks(chrome::kChromeUIBookmarksURL);
172    ui_test_utils::WindowedTabAddedNotificationObserver observer4(
173        content::NotificationService::AllSources());
174    chrome::ShowSingletonTab(browser(), bookmarks);
175    observer4.Wait();
176    tab_count++;
177    host_count++;
178    EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
179    tab1 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
180    rph3 = tab1->GetRenderProcessHost();
181    EXPECT_EQ(tab1->GetURL(), bookmarks);
182    EXPECT_EQ(host_count, RenderProcessHostCount());
183    EXPECT_NE(rph1, rph3);
184    EXPECT_NE(rph2, rph3);
185  }
186};
187
188
189class ChromeRenderProcessHostTestWithCommandLine
190    : public ChromeRenderProcessHostTest {
191 protected:
192  virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
193    command_line->AppendSwitchASCII(switches::kRendererProcessLimit, "1");
194  }
195};
196
197IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, ProcessPerTab) {
198  // Set max renderers to 1 to force running out of processes.
199  content::RenderProcessHost::SetMaxRendererProcessCount(1);
200
201  CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
202  parsed_command_line.AppendSwitch(switches::kProcessPerTab);
203
204  int tab_count = 1;
205  int host_count = 1;
206
207  // Change the first tab to be the new tab page (TYPE_WEBUI).
208  GURL omnibox(chrome::kChromeUIOmniboxURL);
209  ui_test_utils::NavigateToURL(browser(), omnibox);
210  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
211  EXPECT_EQ(host_count, RenderProcessHostCount());
212
213  // Create a new TYPE_TABBED tab.  It should be in its own process.
214  GURL page1("data:text/html,hello world1");
215  ui_test_utils::WindowedTabAddedNotificationObserver observer1(
216      content::NotificationService::AllSources());
217  chrome::ShowSingletonTab(browser(), page1);
218  observer1.Wait();
219  tab_count++;
220  host_count++;
221  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
222  EXPECT_EQ(host_count, RenderProcessHostCount());
223
224  // Create another TYPE_TABBED tab.  It should share the previous process.
225  GURL page2("data:text/html,hello world2");
226  ui_test_utils::WindowedTabAddedNotificationObserver observer2(
227      content::NotificationService::AllSources());
228  chrome::ShowSingletonTab(browser(), page2);
229  observer2.Wait();
230  tab_count++;
231  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
232  EXPECT_EQ(host_count, RenderProcessHostCount());
233
234  // Create another omnibox tab.  It should share the process with the other
235  // WebUI.
236  ui_test_utils::NavigateToURLWithDisposition(
237      browser(), omnibox, NEW_FOREGROUND_TAB,
238      ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
239  tab_count++;
240  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
241  EXPECT_EQ(host_count, RenderProcessHostCount());
242
243  // Create another omnibox tab.  It should share the process with the other
244  // WebUI.
245  ui_test_utils::NavigateToURLWithDisposition(
246      browser(), omnibox, NEW_FOREGROUND_TAB,
247      ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
248  tab_count++;
249  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
250  EXPECT_EQ(host_count, RenderProcessHostCount());
251}
252
253// We don't change process priorities on Mac or Posix because the user lacks the
254// permission to raise a process' priority even after lowering it.
255#if defined(OS_WIN) || defined(OS_LINUX)
256IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, Backgrounding) {
257  if (!base::Process::CanBackgroundProcesses()) {
258    LOG(ERROR) << "Can't background processes";
259    return;
260  }
261  CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
262  parsed_command_line.AppendSwitch(switches::kProcessPerTab);
263
264  // Change the first tab to be the omnibox page (TYPE_WEBUI).
265  GURL omnibox(chrome::kChromeUIOmniboxURL);
266  ui_test_utils::NavigateToURL(browser(), omnibox);
267
268  // Create a new tab. It should be foreground.
269  GURL page1("data:text/html,hello world1");
270  base::ProcessHandle pid1 = ShowSingletonTab(page1);
271  EXPECT_FALSE(base::Process(pid1).IsProcessBackgrounded());
272
273  // Create another tab. It should be foreground, and the first tab should
274  // now be background.
275  GURL page2("data:text/html,hello world2");
276  base::ProcessHandle pid2 = ShowSingletonTab(page2);
277  EXPECT_NE(pid1, pid2);
278  EXPECT_TRUE(base::Process(pid1).IsProcessBackgrounded());
279  EXPECT_FALSE(base::Process(pid2).IsProcessBackgrounded());
280
281  // Load another tab in background. The renderer of the new tab should be
282  // backgrounded, while visibility of the other renderers should not change.
283  GURL page3("data:text/html,hello world3");
284  base::ProcessHandle pid3 = OpenBackgroundTab(page3);
285  EXPECT_NE(pid3, pid1);
286  EXPECT_NE(pid3, pid2);
287  EXPECT_TRUE(base::Process(pid1).IsProcessBackgrounded());
288  EXPECT_FALSE(base::Process(pid2).IsProcessBackgrounded());
289  EXPECT_TRUE(base::Process(pid3).IsProcessBackgrounded());
290
291  // Navigate back to the first page. Its renderer should be in foreground
292  // again while the other renderers should be backgrounded.
293  EXPECT_EQ(pid1, ShowSingletonTab(page1));
294  EXPECT_FALSE(base::Process(pid1).IsProcessBackgrounded());
295  EXPECT_TRUE(base::Process(pid2).IsProcessBackgrounded());
296  EXPECT_TRUE(base::Process(pid3).IsProcessBackgrounded());
297}
298#endif
299
300// TODO(nasko): crbug.com/173137
301#if defined(OS_WIN)
302#define MAYBE_ProcessOverflow DISABLED_ProcessOverflow
303#else
304#define MAYBE_ProcessOverflow ProcessOverflow
305#endif
306
307IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, MAYBE_ProcessOverflow) {
308  // Set max renderers to 1 to force running out of processes.
309  content::RenderProcessHost::SetMaxRendererProcessCount(1);
310  TestProcessOverflow();
311}
312
313// Variation of the ProcessOverflow test, which is driven through command line
314// parameter instead of direct function call into the class.
315IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTestWithCommandLine,
316                       ProcessOverflow) {
317  TestProcessOverflow();
318}
319
320// Ensure that DevTools opened to debug DevTools is launched in a separate
321// process when --process-per-tab is set. See crbug.com/69873.
322IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,
323                       DevToolsOnSelfInOwnProcessPPT) {
324#if defined(OS_WIN) && defined(USE_ASH)
325  // Disable this test in Metro+Ash for now (http://crbug.com/262796).
326  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests))
327    return;
328#endif
329
330  CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
331  parsed_command_line.AppendSwitch(switches::kProcessPerTab);
332
333  int tab_count = 1;
334  int host_count = 1;
335
336  GURL page1("data:text/html,hello world1");
337  ui_test_utils::WindowedTabAddedNotificationObserver observer1(
338      content::NotificationService::AllSources());
339  chrome::ShowSingletonTab(browser(), page1);
340  observer1.Wait();
341  tab_count++;
342  host_count++;
343  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
344  EXPECT_EQ(host_count, RenderProcessHostCount());
345
346  // DevTools start in docked mode (no new tab), in a separate process.
347  chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Inspect());
348  host_count++;
349  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
350  EXPECT_EQ(host_count, RenderProcessHostCount());
351
352  RenderViewHost* devtools = FindFirstDevToolsHost();
353  DCHECK(devtools);
354
355  // DevTools start in a separate process.
356  DevToolsWindow::OpenDevToolsWindow(devtools, DevToolsToggleAction::Inspect());
357  host_count++;
358  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
359  EXPECT_EQ(host_count, RenderProcessHostCount());
360
361  // close docked devtools
362  content::WindowedNotificationObserver close_observer(
363      content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
364      content::Source<WebContents>(WebContents::FromRenderViewHost(devtools)));
365
366  chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Toggle());
367  close_observer.Wait();
368}
369
370// Ensure that DevTools opened to debug DevTools is launched in a separate
371// process. See crbug.com/69873.
372IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,
373                       DevToolsOnSelfInOwnProcess) {
374#if defined(OS_WIN) && defined(USE_ASH)
375  // Disable this test in Metro+Ash for now (http://crbug.com/262796).
376  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests))
377    return;
378#endif
379
380  int tab_count = 1;
381  int host_count = 1;
382
383  GURL page1("data:text/html,hello world1");
384  ui_test_utils::WindowedTabAddedNotificationObserver observer1(
385      content::NotificationService::AllSources());
386  chrome::ShowSingletonTab(browser(), page1);
387  observer1.Wait();
388  tab_count++;
389  host_count++;
390  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
391  EXPECT_EQ(host_count, RenderProcessHostCount());
392
393  // DevTools start in docked mode (no new tab), in a separate process.
394  chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Inspect());
395  host_count++;
396  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
397  EXPECT_EQ(host_count, RenderProcessHostCount());
398
399  RenderViewHost* devtools = FindFirstDevToolsHost();
400  DCHECK(devtools);
401
402  // DevTools start in a separate process.
403  DevToolsWindow::OpenDevToolsWindow(devtools, DevToolsToggleAction::Inspect());
404  host_count++;
405  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
406  EXPECT_EQ(host_count, RenderProcessHostCount());
407
408  // close docked devtools
409  content::WindowedNotificationObserver close_observer(
410      content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
411      content::Source<content::WebContents>(
412          WebContents::FromRenderViewHost(devtools)));
413  chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Toggle());
414  close_observer.Wait();
415}
416
417// This class's goal is to close the browser window when a renderer process has
418// crashed. It does so by monitoring WebContents for RenderProcessGone event and
419// closing the passed in TabStripModel. This is used in the following test case.
420class WindowDestroyer : public content::WebContentsObserver {
421 public:
422  WindowDestroyer(content::WebContents* web_contents, TabStripModel* model)
423      : content::WebContentsObserver(web_contents),
424        tab_strip_model_(model) {
425  }
426
427  virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE {
428    // Wait for the window to be destroyed, which will ensure all other
429    // RenderViewHost objects are deleted before we return and proceed with
430    // the next iteration of notifications.
431    content::WindowedNotificationObserver observer(
432        chrome::NOTIFICATION_BROWSER_CLOSED,
433        content::NotificationService::AllSources());
434    tab_strip_model_->CloseAllTabs();
435    observer.Wait();
436  }
437
438 private:
439  TabStripModel* tab_strip_model_;
440
441  DISALLOW_COPY_AND_ASSIGN(WindowDestroyer);
442};
443
444// Test to ensure that while iterating through all listeners in
445// RenderProcessHost and invalidating them, we remove them properly and don't
446// access already freed objects. See http://crbug.com/255524.
447IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,
448                       CloseAllTabsDuringProcessDied) {
449  GURL url(chrome::kChromeUIOmniboxURL);
450
451  ui_test_utils::NavigateToURL(browser(), url);
452  ui_test_utils::NavigateToURLWithDisposition(
453      browser(), url, NEW_BACKGROUND_TAB,
454      ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
455
456  EXPECT_EQ(2, browser()->tab_strip_model()->count());
457
458  WebContents* wc1 = browser()->tab_strip_model()->GetWebContentsAt(0);
459  WebContents* wc2 = browser()->tab_strip_model()->GetWebContentsAt(1);
460  EXPECT_EQ(wc1->GetRenderProcessHost(), wc2->GetRenderProcessHost());
461
462  // Create an object that will close the window on a process crash.
463  WindowDestroyer destroyer(wc1, browser()->tab_strip_model());
464
465  // Use NOTIFICATION_BROWSER_CLOSED instead of NOTIFICATION_WINDOW_CLOSED,
466  // since the latter is not implemented on OSX and the test will timeout,
467  // causing it to fail.
468  content::WindowedNotificationObserver observer(
469      chrome::NOTIFICATION_BROWSER_CLOSED,
470      content::NotificationService::AllSources());
471
472  // Kill the renderer process, simulating a crash. This should the ProcessDied
473  // method to be called. Alternatively, RenderProcessHost::OnChannelError can
474  // be called to directly force a call to ProcessDied.
475  base::KillProcess(wc1->GetRenderProcessHost()->GetHandle(), -1, true);
476
477  observer.Wait();
478}
479