Home | History | Annotate | Download | only in renderer_host
      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/ui/browser.h"
      9 #include "chrome/browser/ui/browser_commands.h"
     10 #include "chrome/browser/ui/singleton_tabs.h"
     11 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     12 #include "chrome/common/chrome_switches.h"
     13 #include "chrome/common/url_constants.h"
     14 #include "chrome/test/base/in_process_browser_test.h"
     15 #include "chrome/test/base/test_switches.h"
     16 #include "chrome/test/base/ui_test_utils.h"
     17 #include "content/public/browser/notification_service.h"
     18 #include "content/public/browser/render_process_host.h"
     19 #include "content/public/browser/render_view_host.h"
     20 #include "content/public/browser/web_contents.h"
     21 #include "content/public/browser/web_contents_observer.h"
     22 
     23 using content::RenderViewHost;
     24 using content::RenderWidgetHost;
     25 using content::WebContents;
     26 
     27 namespace {
     28 
     29 int RenderProcessHostCount() {
     30   content::RenderProcessHost::iterator hosts =
     31       content::RenderProcessHost::AllHostsIterator();
     32   int count = 0;
     33   while (!hosts.IsAtEnd()) {
     34     if (hosts.GetCurrentValue()->HasConnection())
     35       count++;
     36     hosts.Advance();
     37   }
     38   return count;
     39 }
     40 
     41 RenderViewHost* FindFirstDevToolsHost() {
     42   RenderWidgetHost::List widgets = RenderWidgetHost::GetRenderWidgetHosts();
     43   for (size_t i = 0; i < widgets.size(); ++i) {
     44     if (!widgets[i]->GetProcess()->HasConnection())
     45       continue;
     46     if (!widgets[i]->IsRenderView())
     47       continue;
     48     RenderViewHost* host = RenderViewHost::From(widgets[i]);
     49     WebContents* contents = WebContents::FromRenderViewHost(host);
     50     GURL url = contents->GetURL();
     51     if (url.SchemeIs(chrome::kChromeDevToolsScheme))
     52       return host;
     53   }
     54   return NULL;
     55 }
     56 
     57 }  // namespace
     58 
     59 class ChromeRenderProcessHostTest : public InProcessBrowserTest {
     60  public:
     61   ChromeRenderProcessHostTest() {}
     62 
     63   // Show a tab, activating the current one if there is one, and wait for
     64   // the renderer process to be created or foregrounded, returning the process
     65   // handle.
     66   base::ProcessHandle ShowSingletonTab(const GURL& page) {
     67     chrome::ShowSingletonTab(browser(), page);
     68     WebContents* wc = browser()->tab_strip_model()->GetActiveWebContents();
     69     CHECK(wc->GetURL() == page);
     70 
     71     // Ensure that the backgrounding / foregrounding gets a chance to run.
     72     content::BrowserThread::PostTaskAndReply(
     73         content::BrowserThread::PROCESS_LAUNCHER, FROM_HERE,
     74         base::Bind(&base::DoNothing), base::MessageLoop::QuitClosure());
     75     base::MessageLoop::current()->Run();
     76 
     77     return wc->GetRenderProcessHost()->GetHandle();
     78   }
     79 
     80   // When we hit the max number of renderers, verify that the way we do process
     81   // sharing behaves correctly.  In particular, this test is verifying that even
     82   // when we hit the max process limit, that renderers of each type will wind up
     83   // in a process of that type, even if that means creating a new process.
     84   void TestProcessOverflow() {
     85     int tab_count = 1;
     86     int host_count = 1;
     87     WebContents* tab1 = NULL;
     88     WebContents* tab2 = NULL;
     89     content::RenderProcessHost* rph1 = NULL;
     90     content::RenderProcessHost* rph2 = NULL;
     91     content::RenderProcessHost* rph3 = NULL;
     92 
     93     // Change the first tab to be the new tab page (TYPE_WEBUI).
     94     GURL newtab(chrome::kChromeUINewTabURL);
     95     ui_test_utils::NavigateToURL(browser(), newtab);
     96     EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
     97     tab1 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
     98     rph1 = tab1->GetRenderProcessHost();
     99     EXPECT_EQ(tab1->GetURL(), newtab);
    100     EXPECT_EQ(host_count, RenderProcessHostCount());
    101 
    102     // Create a new TYPE_TABBED tab.  It should be in its own process.
    103     GURL page1("data:text/html,hello world1");
    104 
    105     ui_test_utils::WindowedTabAddedNotificationObserver observer1(
    106         content::NotificationService::AllSources());
    107     chrome::ShowSingletonTab(browser(), page1);
    108     observer1.Wait();
    109 
    110     tab_count++;
    111     host_count++;
    112     EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    113     tab1 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
    114     rph2 = tab1->GetRenderProcessHost();
    115     EXPECT_EQ(tab1->GetURL(), page1);
    116     EXPECT_EQ(host_count, RenderProcessHostCount());
    117     EXPECT_NE(rph1, rph2);
    118 
    119     // Create another TYPE_TABBED tab.  It should share the previous process.
    120     GURL page2("data:text/html,hello world2");
    121     ui_test_utils::WindowedTabAddedNotificationObserver observer2(
    122         content::NotificationService::AllSources());
    123     chrome::ShowSingletonTab(browser(), page2);
    124     observer2.Wait();
    125     tab_count++;
    126     EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    127     tab2 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
    128     EXPECT_EQ(tab2->GetURL(), page2);
    129     EXPECT_EQ(host_count, RenderProcessHostCount());
    130     EXPECT_EQ(tab2->GetRenderProcessHost(), rph2);
    131 
    132     // Create another TYPE_WEBUI tab.  It should share the process with newtab.
    133     // Note: intentionally create this tab after the TYPE_TABBED tabs to
    134     // exercise bug 43448 where extension and WebUI tabs could get combined into
    135     // normal renderers.
    136     GURL history(chrome::kChromeUIHistoryURL);
    137     ui_test_utils::WindowedTabAddedNotificationObserver observer3(
    138         content::NotificationService::AllSources());
    139     chrome::ShowSingletonTab(browser(), history);
    140     observer3.Wait();
    141     tab_count++;
    142     EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    143     tab2 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
    144     EXPECT_EQ(tab2->GetURL(), GURL(history));
    145     EXPECT_EQ(host_count, RenderProcessHostCount());
    146     EXPECT_EQ(tab2->GetRenderProcessHost(), rph1);
    147 
    148     // Create a TYPE_EXTENSION tab.  It should be in its own process.
    149     // (the bookmark manager is implemented as an extension)
    150     GURL bookmarks(chrome::kChromeUIBookmarksURL);
    151     ui_test_utils::WindowedTabAddedNotificationObserver observer4(
    152         content::NotificationService::AllSources());
    153     chrome::ShowSingletonTab(browser(), bookmarks);
    154     observer4.Wait();
    155     tab_count++;
    156     host_count++;
    157     EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    158     tab1 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
    159     rph3 = tab1->GetRenderProcessHost();
    160     EXPECT_EQ(tab1->GetURL(), bookmarks);
    161     EXPECT_EQ(host_count, RenderProcessHostCount());
    162     EXPECT_NE(rph1, rph3);
    163     EXPECT_NE(rph2, rph3);
    164   }
    165 };
    166 
    167 
    168 class ChromeRenderProcessHostTestWithCommandLine
    169     : public ChromeRenderProcessHostTest {
    170  protected:
    171   virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
    172     command_line->AppendSwitchASCII(switches::kRendererProcessLimit, "1");
    173   }
    174 };
    175 
    176 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, ProcessPerTab) {
    177   // Set max renderers to 1 to force running out of processes.
    178   content::RenderProcessHost::SetMaxRendererProcessCount(1);
    179 
    180   CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
    181   parsed_command_line.AppendSwitch(switches::kProcessPerTab);
    182 
    183   int tab_count = 1;
    184   int host_count = 1;
    185 
    186   // Change the first tab to be the new tab page (TYPE_WEBUI).
    187   GURL newtab(chrome::kChromeUINewTabURL);
    188   ui_test_utils::NavigateToURL(browser(), newtab);
    189   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    190   EXPECT_EQ(host_count, RenderProcessHostCount());
    191 
    192   // Create a new TYPE_TABBED tab.  It should be in its own process.
    193   GURL page1("data:text/html,hello world1");
    194   ui_test_utils::WindowedTabAddedNotificationObserver observer1(
    195       content::NotificationService::AllSources());
    196   chrome::ShowSingletonTab(browser(), page1);
    197   observer1.Wait();
    198   tab_count++;
    199   host_count++;
    200   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    201   EXPECT_EQ(host_count, RenderProcessHostCount());
    202 
    203   // Create another TYPE_TABBED tab.  It should share the previous process.
    204   GURL page2("data:text/html,hello world2");
    205   ui_test_utils::WindowedTabAddedNotificationObserver observer2(
    206       content::NotificationService::AllSources());
    207   chrome::ShowSingletonTab(browser(), page2);
    208   observer2.Wait();
    209   tab_count++;
    210   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    211   EXPECT_EQ(host_count, RenderProcessHostCount());
    212 
    213   // Create another new tab.  It should share the process with the other WebUI.
    214   ui_test_utils::WindowedTabAddedNotificationObserver observer3(
    215       content::NotificationService::AllSources());
    216   chrome::NewTab(browser());
    217   observer3.Wait();
    218   tab_count++;
    219   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    220   EXPECT_EQ(host_count, RenderProcessHostCount());
    221 
    222   // Create another new tab.  It should share the process with the other WebUI.
    223   ui_test_utils::WindowedTabAddedNotificationObserver observer4(
    224       content::NotificationService::AllSources());
    225   chrome::NewTab(browser());
    226   observer4.Wait();
    227   tab_count++;
    228   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    229   EXPECT_EQ(host_count, RenderProcessHostCount());
    230 }
    231 
    232 // We don't change process priorities on Mac or Posix because the user lacks the
    233 // permission to raise a process' priority even after lowering it.
    234 #if defined(OS_WIN) || defined(OS_LINUX)
    235 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, Backgrounding) {
    236   if (!base::Process::CanBackgroundProcesses()) {
    237     LOG(ERROR) << "Can't background processes";
    238     return;
    239   }
    240   CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
    241   parsed_command_line.AppendSwitch(switches::kProcessPerTab);
    242 
    243   // Change the first tab to be the new tab page (TYPE_WEBUI).
    244   GURL newtab(chrome::kChromeUINewTabURL);
    245   ui_test_utils::NavigateToURL(browser(), newtab);
    246 
    247   // Create a new tab. It should be foreground.
    248   GURL page1("data:text/html,hello world1");
    249   base::ProcessHandle pid1 = ShowSingletonTab(page1);
    250   EXPECT_FALSE(base::Process(pid1).IsProcessBackgrounded());
    251 
    252   // Create another tab. It should be foreground, and the first tab should
    253   // now be background.
    254   GURL page2("data:text/html,hello world2");
    255   base::ProcessHandle pid2 = ShowSingletonTab(page2);
    256   EXPECT_NE(pid1, pid2);
    257   EXPECT_TRUE(base::Process(pid1).IsProcessBackgrounded());
    258   EXPECT_FALSE(base::Process(pid2).IsProcessBackgrounded());
    259 
    260   // Navigate back to first page. It should be foreground again, and the second
    261   // tab should be background.
    262   EXPECT_EQ(pid1, ShowSingletonTab(page1));
    263   EXPECT_FALSE(base::Process(pid1).IsProcessBackgrounded());
    264   EXPECT_TRUE(base::Process(pid2).IsProcessBackgrounded());
    265 }
    266 #endif
    267 
    268 // TODO(nasko): crbug.com/173137
    269 #if defined(OS_WIN)
    270 #define MAYBE_ProcessOverflow DISABLED_ProcessOverflow
    271 #else
    272 #define MAYBE_ProcessOverflow ProcessOverflow
    273 #endif
    274 
    275 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, MAYBE_ProcessOverflow) {
    276   // Set max renderers to 1 to force running out of processes.
    277   content::RenderProcessHost::SetMaxRendererProcessCount(1);
    278   TestProcessOverflow();
    279 }
    280 
    281 // Variation of the ProcessOverflow test, which is driven through command line
    282 // parameter instead of direct function call into the class.
    283 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTestWithCommandLine,
    284                        ProcessOverflow) {
    285   TestProcessOverflow();
    286 }
    287 
    288 // Ensure that DevTools opened to debug DevTools is launched in a separate
    289 // process when --process-per-tab is set. See crbug.com/69873.
    290 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,
    291                        DevToolsOnSelfInOwnProcessPPT) {
    292 #if defined(OS_WIN) && defined(USE_ASH)
    293   // Disable this test in Metro+Ash for now (http://crbug.com/262796).
    294   if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests))
    295     return;
    296 #endif
    297 
    298   CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
    299   parsed_command_line.AppendSwitch(switches::kProcessPerTab);
    300 
    301   int tab_count = 1;
    302   int host_count = 1;
    303 
    304   GURL page1("data:text/html,hello world1");
    305   ui_test_utils::WindowedTabAddedNotificationObserver observer1(
    306       content::NotificationService::AllSources());
    307   chrome::ShowSingletonTab(browser(), page1);
    308   observer1.Wait();
    309   tab_count++;
    310   host_count++;
    311   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    312   EXPECT_EQ(host_count, RenderProcessHostCount());
    313 
    314   // DevTools start in docked mode (no new tab), in a separate process.
    315   chrome::ToggleDevToolsWindow(browser(), DEVTOOLS_TOGGLE_ACTION_INSPECT);
    316   host_count++;
    317   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    318   EXPECT_EQ(host_count, RenderProcessHostCount());
    319 
    320   RenderViewHost* devtools = FindFirstDevToolsHost();
    321   DCHECK(devtools);
    322 
    323   // DevTools start in a separate process.
    324   DevToolsWindow::ToggleDevToolsWindow(
    325       devtools, true, DEVTOOLS_TOGGLE_ACTION_INSPECT);
    326   host_count++;
    327   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    328   EXPECT_EQ(host_count, RenderProcessHostCount());
    329 }
    330 
    331 // Ensure that DevTools opened to debug DevTools is launched in a separate
    332 // process. See crbug.com/69873.
    333 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,
    334                        DevToolsOnSelfInOwnProcess) {
    335 #if defined(OS_WIN) && defined(USE_ASH)
    336   // Disable this test in Metro+Ash for now (http://crbug.com/262796).
    337   if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests))
    338     return;
    339 #endif
    340 
    341   int tab_count = 1;
    342   int host_count = 1;
    343 
    344   GURL page1("data:text/html,hello world1");
    345   ui_test_utils::WindowedTabAddedNotificationObserver observer1(
    346       content::NotificationService::AllSources());
    347   chrome::ShowSingletonTab(browser(), page1);
    348   observer1.Wait();
    349   tab_count++;
    350   host_count++;
    351   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    352   EXPECT_EQ(host_count, RenderProcessHostCount());
    353 
    354   // DevTools start in docked mode (no new tab), in a separate process.
    355   chrome::ToggleDevToolsWindow(browser(), DEVTOOLS_TOGGLE_ACTION_INSPECT);
    356   host_count++;
    357   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    358   EXPECT_EQ(host_count, RenderProcessHostCount());
    359 
    360   RenderViewHost* devtools = FindFirstDevToolsHost();
    361   DCHECK(devtools);
    362 
    363   // DevTools start in a separate process.
    364   DevToolsWindow::ToggleDevToolsWindow(
    365       devtools, true, DEVTOOLS_TOGGLE_ACTION_INSPECT);
    366   host_count++;
    367   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    368   EXPECT_EQ(host_count, RenderProcessHostCount());
    369 }
    370 
    371 // This class's goal is to close the browser window when a renderer process has
    372 // crashed. It does so by monitoring WebContents for RenderProcessGone event and
    373 // closing the passed in TabStripModel. This is used in the following test case.
    374 class WindowDestroyer : public content::WebContentsObserver {
    375  public:
    376   WindowDestroyer(content::WebContents* web_contents, TabStripModel* model)
    377       : content::WebContentsObserver(web_contents),
    378         tab_strip_model_(model) {
    379   }
    380 
    381   virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE {
    382     // Wait for the window to be destroyed, which will ensure all other
    383     // RenderViewHost objects are deleted before we return and proceed with
    384     // the next iteration of notifications.
    385     content::WindowedNotificationObserver observer(
    386         chrome::NOTIFICATION_BROWSER_CLOSED,
    387         content::NotificationService::AllSources());
    388     tab_strip_model_->CloseAllTabs();
    389     observer.Wait();
    390   }
    391 
    392  private:
    393   TabStripModel* tab_strip_model_;
    394 
    395   DISALLOW_COPY_AND_ASSIGN(WindowDestroyer);
    396 };
    397 
    398 // Test to ensure that while iterating through all listeners in
    399 // RenderProcessHost and invalidating them, we remove them properly and don't
    400 // access already freed objects. See http://crbug.com/255524.
    401 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,
    402                        CloseAllTabsDuringProcessDied) {
    403   GURL url(chrome::kChromeUINewTabURL);
    404 
    405   ui_test_utils::NavigateToURL(browser(), url);
    406   ui_test_utils::NavigateToURLWithDisposition(
    407       browser(), url, NEW_BACKGROUND_TAB,
    408       ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
    409 
    410   EXPECT_EQ(2, browser()->tab_strip_model()->count());
    411 
    412   WebContents* wc1 = browser()->tab_strip_model()->GetWebContentsAt(0);
    413   WebContents* wc2 = browser()->tab_strip_model()->GetWebContentsAt(1);
    414   EXPECT_EQ(wc1->GetRenderProcessHost(), wc2->GetRenderProcessHost());
    415 
    416   // Create an object that will close the window on a process crash.
    417   WindowDestroyer destroyer(wc1, browser()->tab_strip_model());
    418 
    419   // Use NOTIFICATION_BROWSER_CLOSED instead of NOTIFICATION_WINDOW_CLOSED,
    420   // since the latter is not implemented on OSX and the test will timeout,
    421   // causing it to fail.
    422   content::WindowedNotificationObserver observer(
    423       chrome::NOTIFICATION_BROWSER_CLOSED,
    424       content::NotificationService::AllSources());
    425 
    426   // Kill the renderer process, simulating a crash. This should the ProcessDied
    427   // method to be called. Alternatively, RenderProcessHost::OnChannelError can
    428   // be called to directly force a call to ProcessDied.
    429   base::KillProcess(wc1->GetRenderProcessHost()->GetHandle(), -1, true);
    430 
    431   observer.Wait();
    432 }
    433