Home | History | Annotate | Download | only in browser
      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 "base/strings/stringprintf.h"
      7 #include "base/strings/utf_string_conversions.h"
      8 #include "content/browser/frame_host/frame_tree.h"
      9 #include "content/browser/renderer_host/render_view_host_impl.h"
     10 #include "content/browser/web_contents/web_contents_impl.h"
     11 #include "content/public/browser/navigation_entry.h"
     12 #include "content/public/browser/notification_observer.h"
     13 #include "content/public/browser/notification_service.h"
     14 #include "content/public/browser/notification_types.h"
     15 #include "content/public/browser/web_contents_observer.h"
     16 #include "content/public/common/content_switches.h"
     17 #include "content/public/common/url_constants.h"
     18 #include "content/public/test/browser_test_utils.h"
     19 #include "content/public/test/test_navigation_observer.h"
     20 #include "content/public/test/test_utils.h"
     21 #include "content/shell/browser/shell.h"
     22 #include "content/shell/browser/shell_content_browser_client.h"
     23 #include "content/test/content_browser_test.h"
     24 #include "content/test/content_browser_test_utils.h"
     25 #include "net/base/escape.h"
     26 #include "net/dns/mock_host_resolver.h"
     27 
     28 namespace content {
     29 
     30 class SitePerProcessWebContentsObserver: public WebContentsObserver {
     31  public:
     32   explicit SitePerProcessWebContentsObserver(WebContents* web_contents)
     33       : WebContentsObserver(web_contents),
     34         navigation_succeeded_(true) {}
     35   virtual ~SitePerProcessWebContentsObserver() {}
     36 
     37   virtual void DidFailProvisionalLoad(
     38       int64 frame_id,
     39       const base::string16& frame_unique_name,
     40       bool is_main_frame,
     41       const GURL& validated_url,
     42       int error_code,
     43       const base::string16& error_description,
     44       RenderViewHost* render_view_host) OVERRIDE {
     45     navigation_url_ = validated_url;
     46     navigation_succeeded_ = false;
     47   }
     48 
     49   virtual void DidCommitProvisionalLoadForFrame(
     50       int64 frame_id,
     51       const base::string16& frame_unique_name,
     52       bool is_main_frame,
     53       const GURL& url,
     54       PageTransition transition_type,
     55       RenderViewHost* render_view_host) OVERRIDE{
     56     navigation_url_ = url;
     57     navigation_succeeded_ = true;
     58   }
     59 
     60   const GURL& navigation_url() const {
     61     return navigation_url_;
     62   }
     63 
     64   int navigation_succeeded() const { return navigation_succeeded_; }
     65 
     66  private:
     67   GURL navigation_url_;
     68   bool navigation_succeeded_;
     69 
     70   DISALLOW_COPY_AND_ASSIGN(SitePerProcessWebContentsObserver);
     71 };
     72 
     73 class RedirectNotificationObserver : public NotificationObserver {
     74  public:
     75   // Register to listen for notifications of the given type from either a
     76   // specific source, or from all sources if |source| is
     77   // NotificationService::AllSources().
     78   RedirectNotificationObserver(int notification_type,
     79                                const NotificationSource& source);
     80   virtual ~RedirectNotificationObserver();
     81 
     82   // Wait until the specified notification occurs.  If the notification was
     83   // emitted between the construction of this object and this call then it
     84   // returns immediately.
     85   void Wait();
     86 
     87   // Returns NotificationService::AllSources() if we haven't observed a
     88   // notification yet.
     89   const NotificationSource& source() const {
     90     return source_;
     91   }
     92 
     93   const NotificationDetails& details() const {
     94     return details_;
     95   }
     96 
     97   // NotificationObserver:
     98   virtual void Observe(int type,
     99                        const NotificationSource& source,
    100                        const NotificationDetails& details) OVERRIDE;
    101 
    102  private:
    103   bool seen_;
    104   bool seen_twice_;
    105   bool running_;
    106   NotificationRegistrar registrar_;
    107 
    108   NotificationSource source_;
    109   NotificationDetails details_;
    110   scoped_refptr<MessageLoopRunner> message_loop_runner_;
    111 
    112   DISALLOW_COPY_AND_ASSIGN(RedirectNotificationObserver);
    113 };
    114 
    115 RedirectNotificationObserver::RedirectNotificationObserver(
    116     int notification_type,
    117     const NotificationSource& source)
    118     : seen_(false),
    119       running_(false),
    120       source_(NotificationService::AllSources()) {
    121   registrar_.Add(this, notification_type, source);
    122 }
    123 
    124 RedirectNotificationObserver::~RedirectNotificationObserver() {}
    125 
    126 void RedirectNotificationObserver::Wait() {
    127   if (seen_ && seen_twice_)
    128     return;
    129 
    130   running_ = true;
    131   message_loop_runner_ = new MessageLoopRunner;
    132   message_loop_runner_->Run();
    133   EXPECT_TRUE(seen_);
    134 }
    135 
    136 void RedirectNotificationObserver::Observe(
    137     int type,
    138     const NotificationSource& source,
    139     const NotificationDetails& details) {
    140   source_ = source;
    141   details_ = details;
    142   seen_twice_ = seen_;
    143   seen_ = true;
    144   if (!running_)
    145     return;
    146 
    147   message_loop_runner_->Quit();
    148   running_ = false;
    149 }
    150 
    151 class SitePerProcessBrowserTest : public ContentBrowserTest {
    152  protected:
    153   bool NavigateIframeToURL(Shell* window,
    154                            const GURL& url,
    155                            std::string iframe_id) {
    156     std::string script = base::StringPrintf(
    157         "var iframes = document.getElementById('%s');iframes.src='%s';",
    158         iframe_id.c_str(), url.spec().c_str());
    159     WindowedNotificationObserver load_observer(
    160         NOTIFICATION_LOAD_STOP,
    161         Source<NavigationController>(
    162             &shell()->web_contents()->GetController()));
    163     bool result = ExecuteScript(window->web_contents(), script);
    164     load_observer.Wait();
    165     return result;
    166   }
    167 
    168   void NavigateToURLContentInitiated(Shell* window,
    169                                      const GURL& url,
    170                                      bool should_replace_current_entry) {
    171     std::string script;
    172     if (should_replace_current_entry)
    173       script = base::StringPrintf("location.replace('%s')", url.spec().c_str());
    174     else
    175       script = base::StringPrintf("location.href = '%s'", url.spec().c_str());
    176     TestNavigationObserver load_observer(shell()->web_contents(), 1);
    177     bool result = ExecuteScript(window->web_contents(), script);
    178     EXPECT_TRUE(result);
    179     load_observer.Wait();
    180   }
    181 
    182   virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
    183     command_line->AppendSwitch(switches::kSitePerProcess);
    184   }
    185 };
    186 
    187 // TODO(nasko): Disable this test until out-of-process iframes is ready and the
    188 // security checks are back in place.
    189 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, DISABLED_CrossSiteIframe) {
    190   ASSERT_TRUE(test_server()->Start());
    191   net::SpawnedTestServer https_server(
    192       net::SpawnedTestServer::TYPE_HTTPS,
    193       net::SpawnedTestServer::kLocalhost,
    194       base::FilePath(FILE_PATH_LITERAL("content/test/data")));
    195   ASSERT_TRUE(https_server.Start());
    196   GURL main_url(test_server()->GetURL("files/site_per_process_main.html"));
    197 
    198   NavigateToURL(shell(), main_url);
    199 
    200   SitePerProcessWebContentsObserver observer(shell()->web_contents());
    201   {
    202     // Load same-site page into Iframe.
    203     GURL http_url(test_server()->GetURL("files/title1.html"));
    204     EXPECT_TRUE(NavigateIframeToURL(shell(), http_url, "test"));
    205     EXPECT_EQ(observer.navigation_url(), http_url);
    206     EXPECT_TRUE(observer.navigation_succeeded());
    207   }
    208 
    209   {
    210     // Load cross-site page into Iframe.
    211     GURL https_url(https_server.GetURL("files/title1.html"));
    212     EXPECT_TRUE(NavigateIframeToURL(shell(), https_url, "test"));
    213     EXPECT_EQ(observer.navigation_url(), https_url);
    214     EXPECT_FALSE(observer.navigation_succeeded());
    215   }
    216 }
    217 
    218 // TODO(nasko): Disable this test until out-of-process iframes is ready and the
    219 // security checks are back in place.
    220 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
    221                        DISABLED_CrossSiteIframeRedirectOnce) {
    222   ASSERT_TRUE(test_server()->Start());
    223   net::SpawnedTestServer https_server(
    224       net::SpawnedTestServer::TYPE_HTTPS,
    225       net::SpawnedTestServer::kLocalhost,
    226       base::FilePath(FILE_PATH_LITERAL("content/test/data")));
    227   ASSERT_TRUE(https_server.Start());
    228 
    229   GURL main_url(test_server()->GetURL("files/site_per_process_main.html"));
    230   GURL http_url(test_server()->GetURL("files/title1.html"));
    231   GURL https_url(https_server.GetURL("files/title1.html"));
    232 
    233   NavigateToURL(shell(), main_url);
    234 
    235   SitePerProcessWebContentsObserver observer(shell()->web_contents());
    236   {
    237     // Load cross-site client-redirect page into Iframe.
    238     // Should be blocked.
    239     GURL client_redirect_https_url(https_server.GetURL(
    240         "client-redirect?files/title1.html"));
    241     EXPECT_TRUE(NavigateIframeToURL(shell(),
    242                                     client_redirect_https_url, "test"));
    243     // DidFailProvisionalLoad when navigating to client_redirect_https_url.
    244     EXPECT_EQ(observer.navigation_url(), client_redirect_https_url);
    245     EXPECT_FALSE(observer.navigation_succeeded());
    246   }
    247 
    248   {
    249     // Load cross-site server-redirect page into Iframe,
    250     // which redirects to same-site page.
    251     GURL server_redirect_http_url(https_server.GetURL(
    252         "server-redirect?" + http_url.spec()));
    253     EXPECT_TRUE(NavigateIframeToURL(shell(),
    254                                     server_redirect_http_url, "test"));
    255     EXPECT_EQ(observer.navigation_url(), http_url);
    256     EXPECT_TRUE(observer.navigation_succeeded());
    257   }
    258 
    259   {
    260     // Load cross-site server-redirect page into Iframe,
    261     // which redirects to cross-site page.
    262     GURL server_redirect_http_url(https_server.GetURL(
    263         "server-redirect?files/title1.html"));
    264     EXPECT_TRUE(NavigateIframeToURL(shell(),
    265                                     server_redirect_http_url, "test"));
    266     // DidFailProvisionalLoad when navigating to https_url.
    267     EXPECT_EQ(observer.navigation_url(), https_url);
    268     EXPECT_FALSE(observer.navigation_succeeded());
    269   }
    270 
    271   {
    272     // Load same-site server-redirect page into Iframe,
    273     // which redirects to cross-site page.
    274     GURL server_redirect_http_url(test_server()->GetURL(
    275         "server-redirect?" + https_url.spec()));
    276     EXPECT_TRUE(NavigateIframeToURL(shell(),
    277                                     server_redirect_http_url, "test"));
    278 
    279     EXPECT_EQ(observer.navigation_url(), https_url);
    280     EXPECT_FALSE(observer.navigation_succeeded());
    281    }
    282 
    283   {
    284     // Load same-site client-redirect page into Iframe,
    285     // which redirects to cross-site page.
    286     GURL client_redirect_http_url(test_server()->GetURL(
    287         "client-redirect?" + https_url.spec()));
    288 
    289     RedirectNotificationObserver load_observer2(
    290         NOTIFICATION_LOAD_STOP,
    291         Source<NavigationController>(
    292             &shell()->web_contents()->GetController()));
    293 
    294     EXPECT_TRUE(NavigateIframeToURL(shell(),
    295                                     client_redirect_http_url, "test"));
    296 
    297     // Same-site Client-Redirect Page should be loaded successfully.
    298     EXPECT_EQ(observer.navigation_url(), client_redirect_http_url);
    299     EXPECT_TRUE(observer.navigation_succeeded());
    300 
    301     // Redirecting to Cross-site Page should be blocked.
    302     load_observer2.Wait();
    303     EXPECT_EQ(observer.navigation_url(), https_url);
    304     EXPECT_FALSE(observer.navigation_succeeded());
    305   }
    306 
    307   {
    308     // Load same-site server-redirect page into Iframe,
    309     // which redirects to same-site page.
    310     GURL server_redirect_http_url(test_server()->GetURL(
    311         "server-redirect?files/title1.html"));
    312     EXPECT_TRUE(NavigateIframeToURL(shell(),
    313                                     server_redirect_http_url, "test"));
    314     EXPECT_EQ(observer.navigation_url(), http_url);
    315     EXPECT_TRUE(observer.navigation_succeeded());
    316    }
    317 
    318   {
    319     // Load same-site client-redirect page into Iframe,
    320     // which redirects to same-site page.
    321     GURL client_redirect_http_url(test_server()->GetURL(
    322         "client-redirect?" + http_url.spec()));
    323     RedirectNotificationObserver load_observer2(
    324         NOTIFICATION_LOAD_STOP,
    325         Source<NavigationController>(
    326             &shell()->web_contents()->GetController()));
    327 
    328     EXPECT_TRUE(NavigateIframeToURL(shell(),
    329                                     client_redirect_http_url, "test"));
    330 
    331     // Same-site Client-Redirect Page should be loaded successfully.
    332     EXPECT_EQ(observer.navigation_url(), client_redirect_http_url);
    333     EXPECT_TRUE(observer.navigation_succeeded());
    334 
    335     // Redirecting to Same-site Page should be loaded successfully.
    336     load_observer2.Wait();
    337     EXPECT_EQ(observer.navigation_url(), http_url);
    338     EXPECT_TRUE(observer.navigation_succeeded());
    339   }
    340 }
    341 
    342 // TODO(nasko): Disable this test until out-of-process iframes is ready and the
    343 // security checks are back in place.
    344 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
    345                        DISABLED_CrossSiteIframeRedirectTwice) {
    346   ASSERT_TRUE(test_server()->Start());
    347   net::SpawnedTestServer https_server(
    348       net::SpawnedTestServer::TYPE_HTTPS,
    349       net::SpawnedTestServer::kLocalhost,
    350       base::FilePath(FILE_PATH_LITERAL("content/test/data")));
    351   ASSERT_TRUE(https_server.Start());
    352 
    353   GURL main_url(test_server()->GetURL("files/site_per_process_main.html"));
    354   GURL http_url(test_server()->GetURL("files/title1.html"));
    355   GURL https_url(https_server.GetURL("files/title1.html"));
    356 
    357   NavigateToURL(shell(), main_url);
    358 
    359   SitePerProcessWebContentsObserver observer(shell()->web_contents());
    360   {
    361     // Load client-redirect page pointing to a cross-site client-redirect page,
    362     // which eventually redirects back to same-site page.
    363     GURL client_redirect_https_url(https_server.GetURL(
    364         "client-redirect?" + http_url.spec()));
    365     GURL client_redirect_http_url(test_server()->GetURL(
    366         "client-redirect?" + client_redirect_https_url.spec()));
    367 
    368     // We should wait until second client redirect get cancelled.
    369     RedirectNotificationObserver load_observer2(
    370         NOTIFICATION_LOAD_STOP,
    371         Source<NavigationController>(
    372             &shell()->web_contents()->GetController()));
    373 
    374     EXPECT_TRUE(NavigateIframeToURL(shell(), client_redirect_http_url, "test"));
    375 
    376     // DidFailProvisionalLoad when navigating to client_redirect_https_url.
    377     load_observer2.Wait();
    378     EXPECT_EQ(observer.navigation_url(), client_redirect_https_url);
    379     EXPECT_FALSE(observer.navigation_succeeded());
    380   }
    381 
    382   {
    383     // Load server-redirect page pointing to a cross-site server-redirect page,
    384     // which eventually redirect back to same-site page.
    385     GURL server_redirect_https_url(https_server.GetURL(
    386         "server-redirect?" + http_url.spec()));
    387     GURL server_redirect_http_url(test_server()->GetURL(
    388         "server-redirect?" + server_redirect_https_url.spec()));
    389     EXPECT_TRUE(NavigateIframeToURL(shell(),
    390                                     server_redirect_http_url, "test"));
    391     EXPECT_EQ(observer.navigation_url(), http_url);
    392     EXPECT_TRUE(observer.navigation_succeeded());
    393   }
    394 
    395   {
    396     // Load server-redirect page pointing to a cross-site server-redirect page,
    397     // which eventually redirects back to cross-site page.
    398     GURL server_redirect_https_url(https_server.GetURL(
    399         "server-redirect?" + https_url.spec()));
    400     GURL server_redirect_http_url(test_server()->GetURL(
    401         "server-redirect?" + server_redirect_https_url.spec()));
    402     EXPECT_TRUE(NavigateIframeToURL(shell(), server_redirect_http_url, "test"));
    403 
    404     // DidFailProvisionalLoad when navigating to https_url.
    405     EXPECT_EQ(observer.navigation_url(), https_url);
    406     EXPECT_FALSE(observer.navigation_succeeded());
    407   }
    408 
    409   {
    410     // Load server-redirect page pointing to a cross-site client-redirect page,
    411     // which eventually redirects back to same-site page.
    412     GURL client_redirect_http_url(https_server.GetURL(
    413         "client-redirect?" + http_url.spec()));
    414     GURL server_redirect_http_url(test_server()->GetURL(
    415         "server-redirect?" + client_redirect_http_url.spec()));
    416     EXPECT_TRUE(NavigateIframeToURL(shell(), server_redirect_http_url, "test"));
    417 
    418     // DidFailProvisionalLoad when navigating to client_redirect_http_url.
    419     EXPECT_EQ(observer.navigation_url(), client_redirect_http_url);
    420     EXPECT_FALSE(observer.navigation_succeeded());
    421   }
    422 }
    423 
    424 // Ensures FrameTree correctly reflects page structure during navigations.
    425 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
    426                        FrameTreeShape) {
    427   host_resolver()->AddRule("*", "127.0.0.1");
    428   ASSERT_TRUE(test_server()->Start());
    429 
    430   GURL base_url = test_server()->GetURL("files/site_isolation/");
    431   GURL::Replacements replace_host;
    432   std::string host_str("A.com");  // Must stay in scope with replace_host.
    433   replace_host.SetHostStr(host_str);
    434   base_url = base_url.ReplaceComponents(replace_host);
    435 
    436   // Load doc without iframes. Verify FrameTree just has root.
    437   // Frame tree:
    438   //   Site-A Root
    439   NavigateToURL(shell(), base_url.Resolve("blank.html"));
    440   FrameTreeNode* root =
    441       static_cast<WebContentsImpl*>(shell()->web_contents())->
    442       GetFrameTree()->root();
    443   EXPECT_EQ(0U, root->child_count());
    444 
    445   // Add 2 same-site frames. Verify 3 nodes in tree with proper names.
    446   // Frame tree:
    447   //   Site-A Root -- Site-A frame1
    448   //              \-- Site-A frame2
    449   WindowedNotificationObserver observer1(
    450       content::NOTIFICATION_LOAD_STOP,
    451       content::Source<NavigationController>(
    452           &shell()->web_contents()->GetController()));
    453   NavigateToURL(shell(), base_url.Resolve("frames-X-X.html"));
    454   observer1.Wait();
    455   ASSERT_EQ(2U, root->child_count());
    456   EXPECT_EQ(0U, root->child_at(0)->child_count());
    457   EXPECT_EQ(0U, root->child_at(1)->child_count());
    458 }
    459 
    460 // TODO(ajwong): Talk with nasko and merge this functionality with
    461 // FrameTreeShape.
    462 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
    463                        FrameTreeShape2) {
    464   host_resolver()->AddRule("*", "127.0.0.1");
    465   ASSERT_TRUE(test_server()->Start());
    466 
    467   NavigateToURL(shell(),
    468                 test_server()->GetURL("files/frame_tree/top.html"));
    469 
    470   WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
    471   RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>(
    472       wc->GetRenderViewHost());
    473   FrameTreeNode* root = wc->GetFrameTree()->root();
    474 
    475   // Check that the root node is properly created with the frame id of the
    476   // initial navigation.
    477   ASSERT_EQ(3UL, root->child_count());
    478   EXPECT_EQ(std::string(), root->frame_name());
    479   EXPECT_EQ(rvh->main_frame_id(), root->frame_id());
    480 
    481   ASSERT_EQ(2UL, root->child_at(0)->child_count());
    482   EXPECT_STREQ("1-1-name", root->child_at(0)->frame_name().c_str());
    483 
    484   // Verify the deepest node exists and has the right name.
    485   ASSERT_EQ(2UL, root->child_at(2)->child_count());
    486   EXPECT_EQ(1UL, root->child_at(2)->child_at(1)->child_count());
    487   EXPECT_EQ(0UL, root->child_at(2)->child_at(1)->child_at(0)->child_count());
    488   EXPECT_STREQ("3-1-id",
    489       root->child_at(2)->child_at(1)->child_at(0)->frame_name().c_str());
    490 
    491   // Navigate to about:blank, which should leave only the root node of the frame
    492   // tree in the browser process.
    493   NavigateToURL(shell(), test_server()->GetURL("files/title1.html"));
    494 
    495   root = wc->GetFrameTree()->root();
    496   EXPECT_EQ(0UL, root->child_count());
    497   EXPECT_EQ(std::string(), root->frame_name());
    498   EXPECT_EQ(rvh->main_frame_id(), root->frame_id());
    499 }
    500 
    501 // Test that we can navigate away if the previous renderer doesn't clean up its
    502 // child frames.
    503 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, FrameTreeAfterCrash) {
    504   ASSERT_TRUE(test_server()->Start());
    505   NavigateToURL(shell(),
    506                 test_server()->GetURL("files/frame_tree/top.html"));
    507 
    508   // Crash the renderer so that it doesn't send any FrameDetached messages.
    509   WindowedNotificationObserver crash_observer(
    510       NOTIFICATION_RENDERER_PROCESS_CLOSED,
    511       NotificationService::AllSources());
    512   NavigateToURL(shell(), GURL(kChromeUICrashURL));
    513   crash_observer.Wait();
    514 
    515   // The frame tree should be cleared, and the frame ID should be reset.
    516   WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
    517   RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>(
    518       wc->GetRenderViewHost());
    519   FrameTreeNode* root = wc->GetFrameTree()->root();
    520   EXPECT_EQ(0UL, root->child_count());
    521   EXPECT_EQ(FrameTreeNode::kInvalidFrameId, root->frame_id());
    522   EXPECT_EQ(rvh->main_frame_id(), root->frame_id());
    523 
    524   // Navigate to a new URL.
    525   NavigateToURL(shell(), test_server()->GetURL("files/title1.html"));
    526 
    527   // The frame ID should now be set.
    528   EXPECT_EQ(0UL, root->child_count());
    529   EXPECT_NE(FrameTreeNode::kInvalidFrameId, root->frame_id());
    530   EXPECT_EQ(rvh->main_frame_id(), root->frame_id());
    531 }
    532 
    533 // Test that we can navigate away if the previous renderer doesn't clean up its
    534 // child frames.
    535 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, NavigateWithLeftoverFrames) {
    536   host_resolver()->AddRule("*", "127.0.0.1");
    537   ASSERT_TRUE(test_server()->Start());
    538 
    539   GURL base_url = test_server()->GetURL("files/site_isolation/");
    540   GURL::Replacements replace_host;
    541   std::string host_str("A.com");  // Must stay in scope with replace_host.
    542   replace_host.SetHostStr(host_str);
    543   base_url = base_url.ReplaceComponents(replace_host);
    544 
    545   NavigateToURL(shell(),
    546                 test_server()->GetURL("files/frame_tree/top.html"));
    547 
    548   // Hang the renderer so that it doesn't send any FrameDetached messages.
    549   // (This navigation will never complete, so don't wait for it.)
    550   shell()->LoadURL(GURL(kChromeUIHangURL));
    551 
    552   // Check that the frame tree still has children.
    553   WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
    554   FrameTreeNode* root = wc->GetFrameTree()->root();
    555   ASSERT_EQ(3UL, root->child_count());
    556 
    557   // Navigate to a new URL.  We use LoadURL because NavigateToURL will try to
    558   // wait for the previous navigation to stop.
    559   TestNavigationObserver tab_observer(wc, 1);
    560   shell()->LoadURL(base_url.Resolve("blank.html"));
    561   tab_observer.Wait();
    562 
    563   // The frame tree should now be cleared, and the frame ID should be valid.
    564   EXPECT_EQ(0UL, root->child_count());
    565   EXPECT_NE(FrameTreeNode::kInvalidFrameId, root->frame_id());
    566 }
    567 
    568 // Tests that the |should_replace_current_entry| flag persists correctly across
    569 // request transfers that began with a cross-process navigation.
    570 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
    571                        ReplaceEntryCrossProcessThenTranfers) {
    572   const NavigationController& controller =
    573       shell()->web_contents()->GetController();
    574   host_resolver()->AddRule("*", "127.0.0.1");
    575   ASSERT_TRUE(test_server()->Start());
    576 
    577   // These must all stay in scope with replace_host.
    578   GURL::Replacements replace_host;
    579   std::string a_com("A.com");
    580   std::string b_com("B.com");
    581 
    582   // Navigate to a starting URL, so there is a history entry to replace.
    583   GURL url1 = test_server()->GetURL("files/site_isolation/blank.html?1");
    584   NavigateToURL(shell(), url1);
    585 
    586   // Force all future navigations to transfer. Note that this includes same-site
    587   // navigiations which may cause double process swaps (via OpenURL and then via
    588   // transfer). This test intentionally exercises that case.
    589   ShellContentBrowserClient::SetSwapProcessesForRedirect(true);
    590 
    591   // Navigate to a page on A.com with entry replacement. This navigation is
    592   // cross-site, so the renderer will send it to the browser via OpenURL to give
    593   // to a new process. It will then be transferred into yet another process due
    594   // to the call above.
    595   GURL url2 = test_server()->GetURL("files/site_isolation/blank.html?2");
    596   replace_host.SetHostStr(a_com);
    597   url2 = url2.ReplaceComponents(replace_host);
    598   NavigateToURLContentInitiated(shell(), url2, true);
    599 
    600   // There should be one history entry. url2 should have replaced url1.
    601   EXPECT_TRUE(controller.GetPendingEntry() == NULL);
    602   EXPECT_EQ(1, controller.GetEntryCount());
    603   EXPECT_EQ(0, controller.GetCurrentEntryIndex());
    604   EXPECT_EQ(url2, controller.GetEntryAtIndex(0)->GetURL());
    605 
    606   // Now navigate as before to a page on B.com, but normally (without
    607   // replacement). This will still perform a double process-swap as above, via
    608   // OpenURL and then transfer.
    609   GURL url3 = test_server()->GetURL("files/site_isolation/blank.html?3");
    610   replace_host.SetHostStr(b_com);
    611   url3 = url3.ReplaceComponents(replace_host);
    612   NavigateToURLContentInitiated(shell(), url3, false);
    613 
    614   // There should be two history entries. url2 should have replaced url1. url2
    615   // should not have replaced url3.
    616   EXPECT_TRUE(controller.GetPendingEntry() == NULL);
    617   EXPECT_EQ(2, controller.GetEntryCount());
    618   EXPECT_EQ(1, controller.GetCurrentEntryIndex());
    619   EXPECT_EQ(url2, controller.GetEntryAtIndex(0)->GetURL());
    620   EXPECT_EQ(url3, controller.GetEntryAtIndex(1)->GetURL());
    621 }
    622 
    623 // Tests that the |should_replace_current_entry| flag persists correctly across
    624 // request transfers that began with a content-initiated in-process
    625 // navigation. This test is the same as the test above, except transfering from
    626 // in-process.
    627 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
    628                        ReplaceEntryInProcessThenTranfers) {
    629   const NavigationController& controller =
    630       shell()->web_contents()->GetController();
    631   ASSERT_TRUE(test_server()->Start());
    632 
    633   // Navigate to a starting URL, so there is a history entry to replace.
    634   GURL url = test_server()->GetURL("files/site_isolation/blank.html?1");
    635   NavigateToURL(shell(), url);
    636 
    637   // Force all future navigations to transfer. Note that this includes same-site
    638   // navigiations which may cause double process swaps (via OpenURL and then via
    639   // transfer). All navigations in this test are same-site, so it only swaps
    640   // processes via request transfer.
    641   ShellContentBrowserClient::SetSwapProcessesForRedirect(true);
    642 
    643   // Navigate in-process with entry replacement. It will then be transferred
    644   // into a new one due to the call above.
    645   GURL url2 = test_server()->GetURL("files/site_isolation/blank.html?2");
    646   NavigateToURLContentInitiated(shell(), url2, true);
    647 
    648   // There should be one history entry. url2 should have replaced url1.
    649   EXPECT_TRUE(controller.GetPendingEntry() == NULL);
    650   EXPECT_EQ(1, controller.GetEntryCount());
    651   EXPECT_EQ(0, controller.GetCurrentEntryIndex());
    652   EXPECT_EQ(url2, controller.GetEntryAtIndex(0)->GetURL());
    653 
    654   // Now navigate as before, but without replacement.
    655   GURL url3 = test_server()->GetURL("files/site_isolation/blank.html?3");
    656   NavigateToURLContentInitiated(shell(), url3, false);
    657 
    658   // There should be two history entries. url2 should have replaced url1. url2
    659   // should not have replaced url3.
    660   EXPECT_TRUE(controller.GetPendingEntry() == NULL);
    661   EXPECT_EQ(2, controller.GetEntryCount());
    662   EXPECT_EQ(1, controller.GetCurrentEntryIndex());
    663   EXPECT_EQ(url2, controller.GetEntryAtIndex(0)->GetURL());
    664   EXPECT_EQ(url3, controller.GetEntryAtIndex(1)->GetURL());
    665 }
    666 
    667 // Tests that the |should_replace_current_entry| flag persists correctly across
    668 // request transfers that cross processes twice from renderer policy.
    669 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
    670                        ReplaceEntryCrossProcessTwice) {
    671   const NavigationController& controller =
    672       shell()->web_contents()->GetController();
    673   host_resolver()->AddRule("*", "127.0.0.1");
    674   ASSERT_TRUE(test_server()->Start());
    675 
    676   // These must all stay in scope with replace_host.
    677   GURL::Replacements replace_host;
    678   std::string a_com("A.com");
    679   std::string b_com("B.com");
    680 
    681   // Navigate to a starting URL, so there is a history entry to replace.
    682   GURL url1 = test_server()->GetURL("files/site_isolation/blank.html?1");
    683   NavigateToURL(shell(), url1);
    684 
    685   // Navigate to a page on A.com which redirects to B.com with entry
    686   // replacement. This will switch processes via OpenURL twice. First to A.com,
    687   // and second in response to the server redirect to B.com. The second swap is
    688   // also renderer-initiated via OpenURL because decidePolicyForNavigation is
    689   // currently applied on redirects.
    690   GURL url2b = test_server()->GetURL("files/site_isolation/blank.html?2");
    691   replace_host.SetHostStr(b_com);
    692   url2b = url2b.ReplaceComponents(replace_host);
    693   GURL url2a = test_server()->GetURL(
    694       "server-redirect?" + net::EscapeQueryParamValue(url2b.spec(), false));
    695   replace_host.SetHostStr(a_com);
    696   url2a = url2a.ReplaceComponents(replace_host);
    697   NavigateToURLContentInitiated(shell(), url2a, true);
    698 
    699   // There should be one history entry. url2b should have replaced url1.
    700   EXPECT_TRUE(controller.GetPendingEntry() == NULL);
    701   EXPECT_EQ(1, controller.GetEntryCount());
    702   EXPECT_EQ(0, controller.GetCurrentEntryIndex());
    703   EXPECT_EQ(url2b, controller.GetEntryAtIndex(0)->GetURL());
    704 
    705   // Now repeat without replacement.
    706   GURL url3b = test_server()->GetURL("files/site_isolation/blank.html?3");
    707   replace_host.SetHostStr(b_com);
    708   url3b = url3b.ReplaceComponents(replace_host);
    709   GURL url3a = test_server()->GetURL(
    710       "server-redirect?" + net::EscapeQueryParamValue(url3b.spec(), false));
    711   replace_host.SetHostStr(a_com);
    712   url3a = url3a.ReplaceComponents(replace_host);
    713   NavigateToURLContentInitiated(shell(), url3a, false);
    714 
    715   // There should be two history entries. url2b should have replaced url1. url2b
    716   // should not have replaced url3b.
    717   EXPECT_TRUE(controller.GetPendingEntry() == NULL);
    718   EXPECT_EQ(2, controller.GetEntryCount());
    719   EXPECT_EQ(1, controller.GetCurrentEntryIndex());
    720   EXPECT_EQ(url2b, controller.GetEntryAtIndex(0)->GetURL());
    721   EXPECT_EQ(url3b, controller.GetEntryAtIndex(1)->GetURL());
    722 }
    723 
    724 }  // namespace content
    725