Add a WebContents API for initializing the renderer without navigating

I added prepare_renderer flag in WebContents::CreateParams.
So now we can create WebContents with renderer process and RenderFrame
without navigation to about:blank or something like that.
It is need to avoid hack with navigation and to save resources.

BUG=521729
[email protected], [email protected]

Review-Url: https://codereview.chromium.org/1970253002
Cr-Commit-Position: refs/heads/master@{#395413}
diff --git a/chrome/browser/ui/browser_navigator_browsertest.cc b/chrome/browser/ui/browser_navigator_browsertest.cc
index 429519a..93c12211 100644
--- a/chrome/browser/ui/browser_navigator_browsertest.cc
+++ b/chrome/browser/ui/browser_navigator_browsertest.cc
@@ -30,7 +30,10 @@
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
 #include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_test_utils.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 
 using content::WebContents;
@@ -126,8 +129,9 @@
   return browser;
 }
 
-WebContents* BrowserNavigatorTest::CreateWebContents() {
+WebContents* BrowserNavigatorTest::CreateWebContents(bool initialize_renderer) {
   content::WebContents::CreateParams create_params(browser()->profile());
+  create_params.initialize_renderer = initialize_renderer;
   content::WebContents* base_web_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
   if (base_web_contents) {
@@ -700,10 +704,10 @@
 IN_PROC_BROWSER_TEST_F(BrowserNavigatorTest, TargetContents_ForegroundTab) {
   chrome::NavigateParams params(MakeNavigateParams());
   params.disposition = NEW_FOREGROUND_TAB;
-  params.target_contents = CreateWebContents();
+  params.target_contents = CreateWebContents(false);
   chrome::Navigate(&params);
 
-  // Navigate() should have opened the contents in a new foreground in the
+  // Navigate() should have opened the contents in a new foreground tab in the
   // current Browser.
   EXPECT_EQ(browser(), params.browser);
   EXPECT_EQ(browser()->tab_strip_model()->GetActiveWebContents(),
@@ -719,7 +723,7 @@
 IN_PROC_BROWSER_TEST_F(BrowserNavigatorTest, DISABLED_TargetContents_Popup) {
   chrome::NavigateParams params(MakeNavigateParams());
   params.disposition = NEW_POPUP;
-  params.target_contents = CreateWebContents();
+  params.target_contents = CreateWebContents(false);
   params.window_bounds = gfx::Rect(10, 10, 500, 500);
   chrome::Navigate(&params);
 
@@ -753,6 +757,46 @@
 }
 #endif
 
+// This test checks that we can create WebContents with renderer process and
+// RenderFrame without navigating it.
+IN_PROC_BROWSER_TEST_F(BrowserNavigatorTest,
+                       CreateWebContentsWithRendererProcess) {
+  chrome::NavigateParams params(MakeNavigateParams());
+  params.disposition = NEW_FOREGROUND_TAB;
+  params.target_contents = CreateWebContents(true);
+  ASSERT_TRUE(params.target_contents);
+
+  // There is no navigation (to about:blank or something like that).
+  EXPECT_FALSE(params.target_contents->IsLoading());
+
+  ASSERT_TRUE(params.target_contents->GetMainFrame());
+  EXPECT_TRUE(params.target_contents->GetMainFrame()->IsRenderFrameLive());
+  EXPECT_TRUE(
+      params.target_contents->GetController().IsInitialBlankNavigation());
+  int renderer_id = params.target_contents->GetRenderProcessHost()->GetID();
+
+  // We should have one window, with one tab of WebContents differ from
+  // params.target_contents.
+  EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
+  EXPECT_EQ(1, browser()->tab_strip_model()->count());
+  EXPECT_NE(browser()->tab_strip_model()->GetActiveWebContents(),
+            params.target_contents);
+
+  chrome::Navigate(&params);
+
+  // Navigate() should have opened the contents in a new foreground tab in the
+  // current Browser, without changing the renderer process of target_contents.
+  EXPECT_EQ(browser(), params.browser);
+  EXPECT_EQ(browser()->tab_strip_model()->GetActiveWebContents(),
+            params.target_contents);
+  EXPECT_EQ(renderer_id,
+            params.target_contents->GetRenderProcessHost()->GetID());
+
+  // We should have one window, with two tabs.
+  EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
+  EXPECT_EQ(2, browser()->tab_strip_model()->count());
+}
+
 // This tests adding a tab at a specific index.
 IN_PROC_BROWSER_TEST_F(BrowserNavigatorTest, Tabstrip_InsertAtIndex) {
   // This is not meant to be a comprehensive test of whether or not the tab
diff --git a/chrome/browser/ui/browser_navigator_browsertest.h b/chrome/browser/ui/browser_navigator_browsertest.h
index eb089862..4d5a998 100644
--- a/chrome/browser/ui/browser_navigator_browsertest.h
+++ b/chrome/browser/ui/browser_navigator_browsertest.h
@@ -36,7 +36,7 @@
   Browser* CreateEmptyBrowserForType(Browser::Type type, Profile* profile);
   Browser* CreateEmptyBrowserForApp(Profile* profile);
 
-  content::WebContents* CreateWebContents();
+  content::WebContents* CreateWebContents(bool initialize_renderer);
 
   void RunSuppressTest(WindowOpenDisposition disposition);
   void RunUseNonIncognitoWindowTest(const GURL& url);
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 33f0093..13947aab1 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -1584,6 +1584,13 @@
     GetRenderManager()->current_frame_host()->SetRenderFrameCreated(true);
   }
 
+  // Create the renderer process in advance if requested.
+  if (params.initialize_renderer) {
+    if (!GetRenderManager()->current_frame_host()->IsRenderFrameLive()) {
+      GetRenderManager()->InitRenderView(GetRenderViewHost(), nullptr);
+    }
+  }
+
   // Ensure that observers are notified of the creation of this WebContents's
   // main RenderFrameHost. It must be done here for main frames, since the
   // NotifySwappedFromRenderManager expects view_ to already be created and that
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index ef56b7d..6eedb76 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -757,7 +757,7 @@
                                  const gfx::RectF& active_rect);
 #endif
 
-private:
+ private:
   friend class WebContentsObserver;
   friend class WebContents;  // To implement factory methods.
 
diff --git a/content/browser/web_contents/web_contents_impl_browsertest.cc b/content/browser/web_contents/web_contents_impl_browsertest.cc
index 55e00c56..b6593d3f 100644
--- a/content/browser/web_contents/web_contents_impl_browsertest.cc
+++ b/content/browser/web_contents/web_contents_impl_browsertest.cc
@@ -1011,4 +1011,83 @@
   wc->SetJavaScriptDialogManagerForTesting(nullptr);
 }
 
+IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
+                       CreateWebContentsWithRendererProcess) {
+  GURL url("http://c.com/title3.html");
+  ASSERT_TRUE(embedded_test_server()->Start());
+  WebContents* base_web_contents = shell()->web_contents();
+  ASSERT_TRUE(base_web_contents);
+
+  WebContents::CreateParams create_params(
+      base_web_contents->GetBrowserContext());
+  create_params.initialize_renderer = true;
+  create_params.initial_size =
+      base_web_contents->GetContainerBounds().size();
+  std::unique_ptr<WebContents> web_contents(WebContents::Create(create_params));
+  ASSERT_TRUE(web_contents);
+
+  // There is no navigation (to about:blank or something like that).
+  EXPECT_FALSE(web_contents->IsLoading());
+
+  ASSERT_TRUE(web_contents->GetMainFrame());
+  EXPECT_TRUE(web_contents->GetMainFrame()->IsRenderFrameLive());
+  EXPECT_TRUE(web_contents->GetController().IsInitialBlankNavigation());
+  int renderer_id = web_contents->GetRenderProcessHost()->GetID();
+
+  TestNavigationObserver same_tab_observer(web_contents.get(), 1);
+  NavigationController::LoadURLParams params(url);
+  params.transition_type = ui::PageTransitionFromInt(
+      ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
+  web_contents->GetController().LoadURLWithParams(params);
+  same_tab_observer.Wait();
+
+  // Check that pre-warmed process is used.
+  EXPECT_EQ(renderer_id, web_contents->GetRenderProcessHost()->GetID());
+  EXPECT_EQ(1, web_contents->GetController().GetEntryCount());
+  NavigationEntry* entry =
+      web_contents->GetController().GetLastCommittedEntry();
+  ASSERT_TRUE(entry);
+  EXPECT_EQ(url, entry->GetURL());
+}
+
+IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
+                       NavigatingToWebUIDoesNotUsePreWarmedProcess) {
+  GURL web_ui_url(std::string(kChromeUIScheme) + "://" +
+                  std::string(kChromeUIGpuHost));
+  ASSERT_TRUE(embedded_test_server()->Start());
+  WebContents* base_web_contents = shell()->web_contents();
+  ASSERT_TRUE(base_web_contents);
+
+  WebContents::CreateParams create_params(
+      base_web_contents->GetBrowserContext());
+  create_params.initialize_renderer = true;
+  create_params.initial_size =
+      base_web_contents->GetContainerBounds().size();
+  std::unique_ptr<WebContents> web_contents(WebContents::Create(create_params));
+  ASSERT_TRUE(web_contents);
+
+  // There is no navigation (to about:blank or something like that).
+  EXPECT_FALSE(web_contents->IsLoading());
+
+  ASSERT_TRUE(web_contents->GetMainFrame());
+  EXPECT_TRUE(web_contents->GetMainFrame()->IsRenderFrameLive());
+  EXPECT_TRUE(web_contents->GetController().IsInitialBlankNavigation());
+  int renderer_id = web_contents->GetRenderProcessHost()->GetID();
+
+  TestNavigationObserver same_tab_observer(web_contents.get(), 1);
+  NavigationController::LoadURLParams params(web_ui_url);
+  params.transition_type = ui::PageTransitionFromInt(
+      ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
+  web_contents->GetController().LoadURLWithParams(params);
+  same_tab_observer.Wait();
+
+  // Check that pre-warmed process isn't used.
+  EXPECT_NE(renderer_id, web_contents->GetRenderProcessHost()->GetID());
+  EXPECT_EQ(1, web_contents->GetController().GetEntryCount());
+  NavigationEntry* entry =
+      web_contents->GetController().GetLastCommittedEntry();
+  ASSERT_TRUE(entry);
+  EXPECT_EQ(web_ui_url, entry->GetURL());
+}
+
 }  // namespace content
diff --git a/content/public/browser/web_contents.cc b/content/public/browser/web_contents.cc
index 69190cf..fa0afb5 100644
--- a/content/public/browser/web_contents.cc
+++ b/content/public/browser/web_contents.cc
@@ -28,7 +28,9 @@
       initially_hidden(false),
       guest_delegate(nullptr),
       context(nullptr),
-      renderer_initiated_creation(false) {}
+      renderer_initiated_creation(false),
+      initialize_renderer(false) {
+}
 
 WebContents::CreateParams::CreateParams(const CreateParams& other) = default;
 
diff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h
index 6aa00e6..90b647e 100644
--- a/content/public/browser/web_contents.h
+++ b/content/public/browser/web_contents.h
@@ -149,6 +149,14 @@
     // RenderFrame, have already been created on the renderer side, and
     // WebContents construction should take this into account.
     bool renderer_initiated_creation;
+
+    // True if the WebContents should create its renderer process and main
+    // RenderFrame before the first navigation. This is useful to reduce
+    // the latency of the first navigation in cases where it might
+    // not happen right away.
+    // Note that the pre-created renderer process may not be used if the first
+    // navigation requires a dedicated or privileged process, such as a WebUI.
+    bool initialize_renderer;
   };
 
   // Creates a new WebContents.