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:
SitePerProcessWebContentsObserver(WebContents * web_contents)32 explicit SitePerProcessWebContentsObserver(WebContents* web_contents)
33 : WebContentsObserver(web_contents),
34 navigation_succeeded_(true) {}
~SitePerProcessWebContentsObserver()35 virtual ~SitePerProcessWebContentsObserver() {}
36
DidFailProvisionalLoad(int64 frame_id,const base::string16 & frame_unique_name,bool is_main_frame,const GURL & validated_url,int error_code,const base::string16 & error_description,RenderViewHost * render_view_host)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
DidCommitProvisionalLoadForFrame(int64 frame_id,const base::string16 & frame_unique_name,bool is_main_frame,const GURL & url,PageTransition transition_type,RenderViewHost * render_view_host)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
navigation_url() const60 const GURL& navigation_url() const {
61 return navigation_url_;
62 }
63
navigation_succeeded() const64 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.
source() const89 const NotificationSource& source() const {
90 return source_;
91 }
92
details() const93 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
RedirectNotificationObserver(int notification_type,const NotificationSource & source)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
~RedirectNotificationObserver()124 RedirectNotificationObserver::~RedirectNotificationObserver() {}
125
Wait()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
Observe(int type,const NotificationSource & source,const NotificationDetails & details)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:
NavigateIframeToURL(Shell * window,const GURL & url,std::string iframe_id)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
NavigateToURLContentInitiated(Shell * window,const GURL & url,bool should_replace_current_entry)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
SetUpCommandLine(CommandLine * command_line)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.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,DISABLED_CrossSiteIframe)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.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,DISABLED_CrossSiteIframeRedirectOnce)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.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,DISABLED_CrossSiteIframeRedirectTwice)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.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,FrameTreeShape)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.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,FrameTreeShape2)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.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,FrameTreeAfterCrash)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.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,NavigateWithLeftoverFrames)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.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,ReplaceEntryCrossProcessThenTranfers)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.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,ReplaceEntryInProcessThenTranfers)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.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,ReplaceEntryCrossProcessTwice)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