Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / content / browser / site_per_process_browsertest.cc
index 1523ec5..f2e09e1 100644 (file)
@@ -6,12 +6,15 @@
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "content/browser/frame_host/frame_tree.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
 #include "content/browser/renderer_host/render_view_host_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/notification_types.h"
+#include "content/public/browser/resource_dispatcher_host_delegate.h"
+#include "content/public/browser/resource_throttle.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/url_constants.h"
 #include "content/public/test/test_utils.h"
 #include "content/shell/browser/shell.h"
 #include "content/shell/browser/shell_content_browser_client.h"
+#include "content/shell/browser/shell_resource_dispatcher_host_delegate.h"
 #include "content/test/content_browser_test.h"
 #include "content/test/content_browser_test_utils.h"
 #include "net/base/escape.h"
 #include "net/dns/mock_host_resolver.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_status.h"
 
 namespace content {
 
@@ -159,18 +165,200 @@ void RedirectNotificationObserver::Observe(
   running_ = false;
 }
 
+// Tracks a single request for a specified URL, and allows waiting until the
+// request is destroyed, and then inspecting whether it completed successfully.
+class TrackingResourceDispatcherHostDelegate
+    : public ShellResourceDispatcherHostDelegate {
+ public:
+  TrackingResourceDispatcherHostDelegate() : throttle_created_(false) {
+  }
+
+  virtual void RequestBeginning(
+      net::URLRequest* request,
+      ResourceContext* resource_context,
+      appcache::AppCacheService* appcache_service,
+      ResourceType::Type resource_type,
+      int child_id,
+      int route_id,
+      ScopedVector<ResourceThrottle>* throttles) OVERRIDE {
+    CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+    ShellResourceDispatcherHostDelegate::RequestBeginning(
+        request, resource_context, appcache_service, resource_type, child_id,
+        route_id, throttles);
+    // Expect only a single request for the tracked url.
+    ASSERT_FALSE(throttle_created_);
+    // If this is a request for the tracked URL, add a throttle to track it.
+    if (request->url() == tracked_url_)
+      throttles->push_back(new TrackingThrottle(request, this));
+  }
+
+  // Starts tracking a URL.  The request for previously tracked URL, if any,
+  // must have been made and deleted before calling this function.
+  void SetTrackedURL(const GURL& tracked_url) {
+    CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+    // Should not currently be tracking any URL.
+    ASSERT_FALSE(run_loop_);
+
+    // Create a RunLoop that will be stopped once the request for the tracked
+    // URL has been destroyed, to allow tracking the URL while also waiting for
+    // other events.
+    run_loop_.reset(new base::RunLoop());
+
+    BrowserThread::PostTask(
+        BrowserThread::IO, FROM_HERE,
+        base::Bind(
+            &TrackingResourceDispatcherHostDelegate::SetTrackedURLOnIOThread,
+            base::Unretained(this),
+            tracked_url));
+  }
+
+  // Waits until the tracked URL has been requests, and the request for it has
+  // been destroyed.
+  bool WaitForTrackedURLAndGetCompleted() {
+    CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+    run_loop_->Run();
+    run_loop_.reset();
+    return tracked_request_completed_;
+  }
+
+ private:
+  // ResourceThrottle attached to request for the tracked URL.  On destruction,
+  // passes the final URLRequestStatus back to the delegate.
+  class TrackingThrottle : public ResourceThrottle {
+   public:
+    TrackingThrottle(net::URLRequest* request,
+                     TrackingResourceDispatcherHostDelegate* tracker)
+        : request_(request), tracker_(tracker) {
+    }
+
+    virtual ~TrackingThrottle() {
+      // If the request is deleted without being cancelled, its status will
+      // indicate it succeeded, so have to check if the request is still pending
+      // as well.
+      tracker_->OnTrackedRequestDestroyed(
+          !request_->is_pending() && request_->status().is_success());
+    }
+
+    // ResourceThrottle implementation:
+    virtual const char* GetNameForLogging() const OVERRIDE {
+      return "TrackingThrottle";
+    }
+
+   private:
+    net::URLRequest* request_;
+    TrackingResourceDispatcherHostDelegate* tracker_;
+
+    DISALLOW_COPY_AND_ASSIGN(TrackingThrottle);
+  };
+
+  void SetTrackedURLOnIOThread(const GURL& tracked_url) {
+    CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+    throttle_created_ = false;
+    tracked_url_ = tracked_url;
+  }
+
+  void OnTrackedRequestDestroyed(bool completed) {
+    CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+    tracked_request_completed_ = completed;
+    tracked_url_ = GURL();
+
+    BrowserThread::PostTask(
+        BrowserThread::UI, FROM_HERE, run_loop_->QuitClosure());
+  }
+
+  // These live on the IO thread.
+  GURL tracked_url_;
+  bool throttle_created_;
+
+  // This is created and destroyed on the UI thread, but stopped on the IO
+  // thread.
+  scoped_ptr<base::RunLoop> run_loop_;
+
+  // Set on the IO thread while |run_loop_| is non-NULL, read on the UI thread
+  // after deleting run_loop_.
+  bool tracked_request_completed_;
+
+  DISALLOW_COPY_AND_ASSIGN(TrackingResourceDispatcherHostDelegate);
+};
+
+// WebContentsDelegate that fails to open a URL when there's a request that
+// needs to be transferred between renderers.
+class NoTransferRequestDelegate : public WebContentsDelegate {
+ public:
+  NoTransferRequestDelegate() {}
+
+  virtual WebContents* OpenURLFromTab(WebContents* source,
+                                      const OpenURLParams& params) OVERRIDE {
+    bool is_transfer =
+        (params.transferred_global_request_id != GlobalRequestID());
+    if (is_transfer)
+      return NULL;
+    NavigationController::LoadURLParams load_url_params(params.url);
+    load_url_params.referrer = params.referrer;
+    load_url_params.frame_tree_node_id = params.frame_tree_node_id;
+    load_url_params.transition_type = params.transition;
+    load_url_params.extra_headers = params.extra_headers;
+    load_url_params.should_replace_current_entry =
+        params.should_replace_current_entry;
+    load_url_params.is_renderer_initiated = true;
+    source->GetController().LoadURLWithParams(load_url_params);
+    return source;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(NoTransferRequestDelegate);
+};
+
 class SitePerProcessBrowserTest : public ContentBrowserTest {
+ public:
+  SitePerProcessBrowserTest() : old_delegate_(NULL) {
+  }
+
+  // ContentBrowserTest implementation:
+  virtual void SetUpOnMainThread() OVERRIDE {
+    BrowserThread::PostTask(
+        BrowserThread::IO, FROM_HERE,
+        base::Bind(
+            &SitePerProcessBrowserTest::InjectResourceDisptcherHostDelegate,
+            base::Unretained(this)));
+  }
+
+  virtual void TearDownOnMainThread() OVERRIDE {
+    BrowserThread::PostTask(
+        BrowserThread::IO, FROM_HERE,
+        base::Bind(
+            &SitePerProcessBrowserTest::RestoreResourceDisptcherHostDelegate,
+            base::Unretained(this)));
+  }
+
  protected:
+  // Start at a data URL so each extra navigation creates a navigation entry.
+  // (The first navigation will silently be classified as AUTO_SUBFRAME.)
+  // TODO(creis): This won't be necessary when we can wait for LOAD_STOP.
+  void StartFrameAtDataURL() {
+    std::string data_url_script =
+      "var iframes = document.getElementById('test');iframes.src="
+      "'data:text/html,dataurl';";
+    ASSERT_TRUE(ExecuteScript(shell()->web_contents(), data_url_script));
+  }
+
   bool NavigateIframeToURL(Shell* window,
                            const GURL& url,
                            std::string iframe_id) {
+    // TODO(creis): This should wait for LOAD_STOP, but cross-site subframe
+    // navigations generate extra DidStartLoading and DidStopLoading messages.
+    // Until we replace swappedout:// with frame proxies, we need to listen for
+    // something else.  For now, we trigger NEW_SUBFRAME navigations and listen
+    // for commit.
     std::string script = base::StringPrintf(
-        "var iframes = document.getElementById('%s');iframes.src='%s';",
+        "setTimeout(\""
+        "var iframes = document.getElementById('%s');iframes.src='%s';"
+        "\",0)",
         iframe_id.c_str(), url.spec().c_str());
     WindowedNotificationObserver load_observer(
-        NOTIFICATION_LOAD_STOP,
+        NOTIFICATION_NAV_ENTRY_COMMITTED,
         Source<NavigationController>(
-            &shell()->web_contents()->GetController()));
+            &window->web_contents()->GetController()));
     bool result = ExecuteScript(window->web_contents(), script);
     load_observer.Wait();
     return result;
@@ -178,7 +366,8 @@ class SitePerProcessBrowserTest : public ContentBrowserTest {
 
   void NavigateToURLContentInitiated(Shell* window,
                                      const GURL& url,
-                                     bool should_replace_current_entry) {
+                                     bool should_replace_current_entry,
+                                     bool should_wait_for_navigation) {
     std::string script;
     if (should_replace_current_entry)
       script = base::StringPrintf("location.replace('%s')", url.spec().c_str());
@@ -187,41 +376,65 @@ class SitePerProcessBrowserTest : public ContentBrowserTest {
     TestNavigationObserver load_observer(shell()->web_contents(), 1);
     bool result = ExecuteScript(window->web_contents(), script);
     EXPECT_TRUE(result);
-    load_observer.Wait();
+    if (should_wait_for_navigation)
+      load_observer.Wait();
   }
 
   virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
     command_line->AppendSwitch(switches::kSitePerProcess);
+
+    // TODO(creis): Remove this when GTK is no longer a supported platform.
+    command_line->AppendSwitch(switches::kForceCompositingMode);
+  }
+
+  void InjectResourceDisptcherHostDelegate() {
+    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+    old_delegate_ = ResourceDispatcherHostImpl::Get()->delegate();
+    ResourceDispatcherHostImpl::Get()->SetDelegate(&tracking_delegate_);
   }
+
+  void RestoreResourceDisptcherHostDelegate() {
+    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+    ResourceDispatcherHostImpl::Get()->SetDelegate(old_delegate_);
+    old_delegate_ = NULL;
+  }
+
+  TrackingResourceDispatcherHostDelegate& tracking_delegate() {
+    return tracking_delegate_;
+  }
+
+ private:
+  TrackingResourceDispatcherHostDelegate tracking_delegate_;
+  ResourceDispatcherHostDelegate* old_delegate_;
 };
 
 // Ensure that we can complete a cross-process subframe navigation.
-// TODO(nasko): Disable this test for now, since enabling swapping out of
-// RenderFrameHosts causes this to break. Fix this test once
-// didFailProvisionalLoad is moved from RenderView to RenderFrame.
-IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, DISABLED_CrossSiteIframe) {
+IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, CrossSiteIframe) {
+  host_resolver()->AddRule("*", "127.0.0.1");
   ASSERT_TRUE(test_server()->Start());
-  net::SpawnedTestServer https_server(
-      net::SpawnedTestServer::TYPE_HTTPS,
-      net::SpawnedTestServer::kLocalhost,
-      base::FilePath(FILE_PATH_LITERAL("content/test/data")));
-  ASSERT_TRUE(https_server.Start());
   GURL main_url(test_server()->GetURL("files/site_per_process_main.html"));
-
   NavigateToURL(shell(), main_url);
 
+  StartFrameAtDataURL();
+
   SitePerProcessWebContentsObserver observer(shell()->web_contents());
 
   // Load same-site page into iframe.
   GURL http_url(test_server()->GetURL("files/title1.html"));
   EXPECT_TRUE(NavigateIframeToURL(shell(), http_url, "test"));
-  EXPECT_EQ(observer.navigation_url(), http_url);
+  EXPECT_EQ(http_url, observer.navigation_url());
   EXPECT_TRUE(observer.navigation_succeeded());
 
+  // These must stay in scope with replace_host.
+  GURL::Replacements replace_host;
+  std::string foo_com("foo.com");
+
   // Load cross-site page into iframe.
-  GURL https_url(https_server.GetURL("files/title1.html"));
-  EXPECT_TRUE(NavigateIframeToURL(shell(), https_url, "test"));
-  EXPECT_EQ(observer.navigation_url(), https_url);
+  GURL cross_site_url(test_server()->GetURL("files/title2.html"));
+  replace_host.SetHostStr(foo_com);
+  cross_site_url = cross_site_url.ReplaceComponents(replace_host);
+  EXPECT_TRUE(NavigateIframeToURL(shell(), cross_site_url, "test"));
+  EXPECT_EQ(cross_site_url, observer.navigation_url());
   EXPECT_TRUE(observer.navigation_succeeded());
 
   // Ensure that we have created a new process for the subframe.
@@ -238,8 +451,72 @@ IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, DISABLED_CrossSiteIframe) {
             child->current_frame_host()->GetProcess());
 }
 
+// Crash a subframe and ensures its children are cleared from the FrameTree.
+// See http://crbug.com/338508.
+// TODO(creis): Enable this on Android when we can kill the process there.
+#if defined(OS_ANDROID)
+#define MAYBE_CrashSubframe DISABLED_CrashSubframe
+#else
+#define MAYBE_CrashSubframe CrashSubframe
+#endif
+IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, MAYBE_CrashSubframe) {
+  host_resolver()->AddRule("*", "127.0.0.1");
+  ASSERT_TRUE(test_server()->Start());
+  GURL main_url(test_server()->GetURL("files/site_per_process_main.html"));
+  NavigateToURL(shell(), main_url);
+
+  StartFrameAtDataURL();
+
+  // These must stay in scope with replace_host.
+  GURL::Replacements replace_host;
+  std::string foo_com("foo.com");
+
+  // Load cross-site page into iframe.
+  GURL cross_site_url(test_server()->GetURL("files/title2.html"));
+  replace_host.SetHostStr(foo_com);
+  cross_site_url = cross_site_url.ReplaceComponents(replace_host);
+  EXPECT_TRUE(NavigateIframeToURL(shell(), cross_site_url, "test"));
+
+  // Check the subframe process.
+  FrameTreeNode* root =
+      static_cast<WebContentsImpl*>(shell()->web_contents())->
+          GetFrameTree()->root();
+  ASSERT_EQ(1U, root->child_count());
+  FrameTreeNode* child = root->child_at(0);
+  EXPECT_NE(FrameTreeNode::kInvalidFrameId, root->frame_id());
+  EXPECT_NE(FrameTreeNode::kInvalidFrameId, root->child_at(0)->frame_id());
+
+  // Crash the subframe process.
+  RenderProcessHost* root_process = root->current_frame_host()->GetProcess();
+  RenderProcessHost* child_process = child->current_frame_host()->GetProcess();
+  {
+    RenderProcessHostWatcher crash_observer(
+        child_process,
+        RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
+    base::KillProcess(child_process->GetHandle(), 0, false);
+    crash_observer.Wait();
+  }
+
+  // Ensure that the child frame still exists but has been cleared.
+  EXPECT_EQ(1U, root->child_count());
+  EXPECT_EQ(FrameTreeNode::kInvalidFrameId, root->child_at(0)->frame_id());
+
+  // Now crash the top-level page to clear the child frame.
+  {
+    RenderProcessHostWatcher crash_observer(
+        root_process,
+        RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
+    base::KillProcess(root_process->GetHandle(), 0, false);
+    crash_observer.Wait();
+  }
+  EXPECT_EQ(0U, root->child_count());
+  EXPECT_EQ(FrameTreeNode::kInvalidFrameId, root->frame_id());
+}
+
 // TODO(nasko): Disable this test until out-of-process iframes is ready and the
 // security checks are back in place.
+// TODO(creis): Replace SpawnedTestServer with host_resolver to get test to run
+// on Android (http://crbug.com/187570).
 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
                        DISABLED_CrossSiteIframeRedirectOnce) {
   ASSERT_TRUE(test_server()->Start());
@@ -364,6 +641,8 @@ IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
 
 // TODO(nasko): Disable this test until out-of-process iframes is ready and the
 // security checks are back in place.
+// TODO(creis): Replace SpawnedTestServer with host_resolver to get test to run
+// on Android (http://crbug.com/187570).
 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
                        DISABLED_CrossSiteIframeRedirectTwice) {
   ASSERT_TRUE(test_server()->Start());
@@ -447,7 +726,7 @@ IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
 // Tests that the |should_replace_current_entry| flag persists correctly across
 // request transfers that began with a cross-process navigation.
 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
-                       ReplaceEntryCrossProcessThenTranfers) {
+                       ReplaceEntryCrossProcessThenTransfer) {
   const NavigationController& controller =
       shell()->web_contents()->GetController();
   host_resolver()->AddRule("*", "127.0.0.1");
@@ -474,13 +753,18 @@ IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
   GURL url2 = test_server()->GetURL("files/site_isolation/blank.html?2");
   replace_host.SetHostStr(a_com);
   url2 = url2.ReplaceComponents(replace_host);
-  NavigateToURLContentInitiated(shell(), url2, true);
+  // Used to make sure the request for url2 succeeds, and there was only one of
+  // them.
+  tracking_delegate().SetTrackedURL(url2);
+  NavigateToURLContentInitiated(shell(), url2, true, true);
 
   // There should be one history entry. url2 should have replaced url1.
   EXPECT_TRUE(controller.GetPendingEntry() == NULL);
   EXPECT_EQ(1, controller.GetEntryCount());
   EXPECT_EQ(0, controller.GetCurrentEntryIndex());
   EXPECT_EQ(url2, controller.GetEntryAtIndex(0)->GetURL());
+  // Make sure the request succeeded.
+  EXPECT_TRUE(tracking_delegate().WaitForTrackedURLAndGetCompleted());
 
   // Now navigate as before to a page on B.com, but normally (without
   // replacement). This will still perform a double process-swap as above, via
@@ -488,7 +772,10 @@ IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
   GURL url3 = test_server()->GetURL("files/site_isolation/blank.html?3");
   replace_host.SetHostStr(b_com);
   url3 = url3.ReplaceComponents(replace_host);
-  NavigateToURLContentInitiated(shell(), url3, false);
+  // Used to make sure the request for url3 succeeds, and there was only one of
+  // them.
+  tracking_delegate().SetTrackedURL(url3);
+  NavigateToURLContentInitiated(shell(), url3, false, true);
 
   // There should be two history entries. url2 should have replaced url1. url2
   // should not have replaced url3.
@@ -497,6 +784,9 @@ IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
   EXPECT_EQ(1, controller.GetCurrentEntryIndex());
   EXPECT_EQ(url2, controller.GetEntryAtIndex(0)->GetURL());
   EXPECT_EQ(url3, controller.GetEntryAtIndex(1)->GetURL());
+
+  // Make sure the request succeeded.
+  EXPECT_TRUE(tracking_delegate().WaitForTrackedURLAndGetCompleted());
 }
 
 // Tests that the |should_replace_current_entry| flag persists correctly across
@@ -522,7 +812,7 @@ IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
   // Navigate in-process with entry replacement. It will then be transferred
   // into a new one due to the call above.
   GURL url2 = test_server()->GetURL("files/site_isolation/blank.html?2");
-  NavigateToURLContentInitiated(shell(), url2, true);
+  NavigateToURLContentInitiated(shell(), url2, true, true);
 
   // There should be one history entry. url2 should have replaced url1.
   EXPECT_TRUE(controller.GetPendingEntry() == NULL);
@@ -532,7 +822,7 @@ IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
 
   // Now navigate as before, but without replacement.
   GURL url3 = test_server()->GetURL("files/site_isolation/blank.html?3");
-  NavigateToURLContentInitiated(shell(), url3, false);
+  NavigateToURLContentInitiated(shell(), url3, false, true);
 
   // There should be two history entries. url2 should have replaced url1. url2
   // should not have replaced url3.
@@ -573,7 +863,7 @@ IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
       "server-redirect?" + net::EscapeQueryParamValue(url2b.spec(), false));
   replace_host.SetHostStr(a_com);
   url2a = url2a.ReplaceComponents(replace_host);
-  NavigateToURLContentInitiated(shell(), url2a, true);
+  NavigateToURLContentInitiated(shell(), url2a, true, true);
 
   // There should be one history entry. url2b should have replaced url1.
   EXPECT_TRUE(controller.GetPendingEntry() == NULL);
@@ -589,7 +879,7 @@ IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
       "server-redirect?" + net::EscapeQueryParamValue(url3b.spec(), false));
   replace_host.SetHostStr(a_com);
   url3a = url3a.ReplaceComponents(replace_host);
-  NavigateToURLContentInitiated(shell(), url3a, false);
+  NavigateToURLContentInitiated(shell(), url3a, false, true);
 
   // There should be two history entries. url2b should have replaced url1. url2b
   // should not have replaced url3b.
@@ -600,4 +890,54 @@ IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
   EXPECT_EQ(url3b, controller.GetEntryAtIndex(1)->GetURL());
 }
 
+// Tests that the request is destroyed when a cross process navigation is
+// cancelled.
+IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, NoLeakOnCrossSiteCancel) {
+  const NavigationController& controller =
+      shell()->web_contents()->GetController();
+  host_resolver()->AddRule("*", "127.0.0.1");
+  ASSERT_TRUE(test_server()->Start());
+
+  // These must all stay in scope with replace_host.
+  GURL::Replacements replace_host;
+  std::string a_com("A.com");
+  std::string b_com("B.com");
+
+  // Navigate to a starting URL, so there is a history entry to replace.
+  GURL url1 = test_server()->GetURL("files/site_isolation/blank.html?1");
+  NavigateToURL(shell(), url1);
+
+  // Force all future navigations to transfer.
+  ShellContentBrowserClient::SetSwapProcessesForRedirect(true);
+
+  NoTransferRequestDelegate no_transfer_request_delegate;
+  WebContentsDelegate* old_delegate = shell()->web_contents()->GetDelegate();
+  shell()->web_contents()->SetDelegate(&no_transfer_request_delegate);
+
+  // Navigate to a page on A.com with entry replacement. This navigation is
+  // cross-site, so the renderer will send it to the browser via OpenURL to give
+  // to a new process. It will then be transferred into yet another process due
+  // to the call above.
+  GURL url2 = test_server()->GetURL("files/site_isolation/blank.html?2");
+  replace_host.SetHostStr(a_com);
+  url2 = url2.ReplaceComponents(replace_host);
+  // Used to make sure the second request is cancelled, and there is only one
+  // request for url2.
+  tracking_delegate().SetTrackedURL(url2);
+
+  // Don't wait for the navigation to complete, since that never happens in
+  // this case.
+  NavigateToURLContentInitiated(shell(), url2, false, false);
+
+  // There should be one history entry, with url1.
+  EXPECT_EQ(1, controller.GetEntryCount());
+  EXPECT_EQ(0, controller.GetCurrentEntryIndex());
+  EXPECT_EQ(url1, controller.GetEntryAtIndex(0)->GetURL());
+
+  // Make sure the request for url2 did not complete.
+  EXPECT_FALSE(tracking_delegate().WaitForTrackedURLAndGetCompleted());
+
+  shell()->web_contents()->SetDelegate(old_delegate);
+}
+
 }  // namespace content