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
28namespace content {
29
30class 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
73class 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
115RedirectNotificationObserver::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
124RedirectNotificationObserver::~RedirectNotificationObserver() {}
125
126void 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
136void 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
151class 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.
189IN_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.
220IN_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.
344IN_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.
425IN_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.
462IN_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.
503IN_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.
535IN_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.
570IN_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.
627IN_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.
669IN_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