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 "content/browser/site_per_process_browsertest.h" 6 7#include "base/command_line.h" 8#include "base/strings/stringprintf.h" 9#include "content/browser/frame_host/cross_process_frame_connector.h" 10#include "content/browser/frame_host/frame_tree.h" 11#include "content/browser/frame_host/render_frame_proxy_host.h" 12#include "content/browser/frame_host/render_widget_host_view_child_frame.h" 13#include "content/browser/renderer_host/render_view_host_impl.h" 14#include "content/browser/web_contents/web_contents_impl.h" 15#include "content/public/browser/notification_observer.h" 16#include "content/public/browser/notification_service.h" 17#include "content/public/browser/notification_types.h" 18#include "content/public/browser/web_contents_observer.h" 19#include "content/public/common/content_switches.h" 20#include "content/public/test/browser_test_utils.h" 21#include "content/public/test/content_browser_test_utils.h" 22#include "content/public/test/test_utils.h" 23#include "content/shell/browser/shell.h" 24#include "content/test/content_browser_test_utils_internal.h" 25#include "net/dns/mock_host_resolver.h" 26 27namespace content { 28 29class SitePerProcessWebContentsObserver: public WebContentsObserver { 30 public: 31 explicit SitePerProcessWebContentsObserver(WebContents* web_contents) 32 : WebContentsObserver(web_contents), 33 navigation_succeeded_(false) {} 34 virtual ~SitePerProcessWebContentsObserver() {} 35 36 virtual void DidStartProvisionalLoadForFrame( 37 RenderFrameHost* render_frame_host, 38 const GURL& validated_url, 39 bool is_error_page, 40 bool is_iframe_srcdoc) OVERRIDE { 41 navigation_succeeded_ = false; 42 } 43 44 virtual void DidFailProvisionalLoad( 45 RenderFrameHost* render_frame_host, 46 const GURL& validated_url, 47 int error_code, 48 const base::string16& error_description) OVERRIDE { 49 navigation_url_ = validated_url; 50 navigation_succeeded_ = false; 51 } 52 53 virtual void DidCommitProvisionalLoadForFrame( 54 RenderFrameHost* render_frame_host, 55 const GURL& url, 56 ui::PageTransition transition_type) OVERRIDE { 57 navigation_url_ = url; 58 navigation_succeeded_ = true; 59 } 60 61 const GURL& navigation_url() const { 62 return navigation_url_; 63 } 64 65 int navigation_succeeded() const { return navigation_succeeded_; } 66 67 private: 68 GURL navigation_url_; 69 bool navigation_succeeded_; 70 71 DISALLOW_COPY_AND_ASSIGN(SitePerProcessWebContentsObserver); 72}; 73 74class RedirectNotificationObserver : public NotificationObserver { 75 public: 76 // Register to listen for notifications of the given type from either a 77 // specific source, or from all sources if |source| is 78 // NotificationService::AllSources(). 79 RedirectNotificationObserver(int notification_type, 80 const NotificationSource& source); 81 virtual ~RedirectNotificationObserver(); 82 83 // Wait until the specified notification occurs. If the notification was 84 // emitted between the construction of this object and this call then it 85 // returns immediately. 86 void Wait(); 87 88 // Returns NotificationService::AllSources() if we haven't observed a 89 // notification yet. 90 const NotificationSource& source() const { 91 return source_; 92 } 93 94 const NotificationDetails& details() const { 95 return details_; 96 } 97 98 // NotificationObserver: 99 virtual void Observe(int type, 100 const NotificationSource& source, 101 const NotificationDetails& details) OVERRIDE; 102 103 private: 104 bool seen_; 105 bool seen_twice_; 106 bool running_; 107 NotificationRegistrar registrar_; 108 109 NotificationSource source_; 110 NotificationDetails details_; 111 scoped_refptr<MessageLoopRunner> message_loop_runner_; 112 113 DISALLOW_COPY_AND_ASSIGN(RedirectNotificationObserver); 114}; 115 116RedirectNotificationObserver::RedirectNotificationObserver( 117 int notification_type, 118 const NotificationSource& source) 119 : seen_(false), 120 running_(false), 121 source_(NotificationService::AllSources()) { 122 registrar_.Add(this, notification_type, source); 123} 124 125RedirectNotificationObserver::~RedirectNotificationObserver() {} 126 127void RedirectNotificationObserver::Wait() { 128 if (seen_ && seen_twice_) 129 return; 130 131 running_ = true; 132 message_loop_runner_ = new MessageLoopRunner; 133 message_loop_runner_->Run(); 134 EXPECT_TRUE(seen_); 135} 136 137void RedirectNotificationObserver::Observe( 138 int type, 139 const NotificationSource& source, 140 const NotificationDetails& details) { 141 source_ = source; 142 details_ = details; 143 seen_twice_ = seen_; 144 seen_ = true; 145 if (!running_) 146 return; 147 148 message_loop_runner_->Quit(); 149 running_ = false; 150} 151 152// 153// SitePerProcessBrowserTest 154// 155 156SitePerProcessBrowserTest::SitePerProcessBrowserTest() { 157}; 158 159void SitePerProcessBrowserTest::StartFrameAtDataURL() { 160 std::string data_url_script = 161 "var iframes = document.getElementById('test');iframes.src=" 162 "'data:text/html,dataurl';"; 163 ASSERT_TRUE(ExecuteScript(shell()->web_contents(), data_url_script)); 164} 165 166bool SitePerProcessBrowserTest::NavigateIframeToURL(Shell* window, 167 const GURL& url, 168 std::string iframe_id) { 169 // TODO(creis): This should wait for LOAD_STOP, but cross-site subframe 170 // navigations generate extra DidStartLoading and DidStopLoading messages. 171 // Until we replace swappedout:// with frame proxies, we need to listen for 172 // something else. For now, we trigger NEW_SUBFRAME navigations and listen 173 // for commit. 174 std::string script = base::StringPrintf( 175 "setTimeout(\"" 176 "var iframes = document.getElementById('%s');iframes.src='%s';" 177 "\",0)", 178 iframe_id.c_str(), url.spec().c_str()); 179 WindowedNotificationObserver load_observer( 180 NOTIFICATION_NAV_ENTRY_COMMITTED, 181 Source<NavigationController>( 182 &window->web_contents()->GetController())); 183 bool result = ExecuteScript(window->web_contents(), script); 184 load_observer.Wait(); 185 return result; 186} 187 188void SitePerProcessBrowserTest::SetUpCommandLine(CommandLine* command_line) { 189 command_line->AppendSwitch(switches::kSitePerProcess); 190}; 191 192// It fails on ChromeOS and Android, so disabled while investigating. 193// http://crbug.com/399775 194#if defined(OS_ANDROID) || defined(OS_CHROMEOS) 195#define MAYBE_CrossSiteIframe DISABLED_CrossSiteIframe 196#else 197#define MAYBE_CrossSiteIframe CrossSiteIframe 198#endif 199IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, MAYBE_CrossSiteIframe) { 200 host_resolver()->AddRule("*", "127.0.0.1"); 201 ASSERT_TRUE(test_server()->Start()); 202 GURL main_url(test_server()->GetURL("files/site_per_process_main.html")); 203 NavigateToURL(shell(), main_url); 204 205 // It is safe to obtain the root frame tree node here, as it doesn't change. 206 FrameTreeNode* root = 207 static_cast<WebContentsImpl*>(shell()->web_contents())-> 208 GetFrameTree()->root(); 209 210 SitePerProcessWebContentsObserver observer(shell()->web_contents()); 211 212 // Load same-site page into iframe. 213 FrameTreeNode* child = root->child_at(0); 214 GURL http_url(test_server()->GetURL("files/title1.html")); 215 NavigateFrameToURL(child, http_url); 216 EXPECT_EQ(http_url, observer.navigation_url()); 217 EXPECT_TRUE(observer.navigation_succeeded()); 218 { 219 // There should be only one RenderWidgetHost when there are no 220 // cross-process iframes. 221 std::set<RenderWidgetHostView*> views_set = 222 static_cast<WebContentsImpl*>(shell()->web_contents()) 223 ->GetRenderWidgetHostViewsInTree(); 224 EXPECT_EQ(1U, views_set.size()); 225 } 226 RenderFrameProxyHost* proxy_to_parent = 227 child->render_manager()->GetRenderFrameProxyHost( 228 shell()->web_contents()->GetSiteInstance()); 229 EXPECT_FALSE(proxy_to_parent); 230 231 // These must stay in scope with replace_host. 232 GURL::Replacements replace_host; 233 std::string foo_com("foo.com"); 234 235 // Load cross-site page into iframe. 236 GURL cross_site_url(test_server()->GetURL("files/title2.html")); 237 replace_host.SetHostStr(foo_com); 238 cross_site_url = cross_site_url.ReplaceComponents(replace_host); 239 NavigateFrameToURL(root->child_at(0), cross_site_url); 240 EXPECT_EQ(cross_site_url, observer.navigation_url()); 241 EXPECT_TRUE(observer.navigation_succeeded()); 242 243 // Ensure that we have created a new process for the subframe. 244 ASSERT_EQ(1U, root->child_count()); 245 SiteInstance* site_instance = child->current_frame_host()->GetSiteInstance(); 246 RenderViewHost* rvh = child->current_frame_host()->render_view_host(); 247 RenderProcessHost* rph = child->current_frame_host()->GetProcess(); 248 EXPECT_NE(shell()->web_contents()->GetRenderViewHost(), rvh); 249 EXPECT_NE(shell()->web_contents()->GetSiteInstance(), site_instance); 250 EXPECT_NE(shell()->web_contents()->GetRenderProcessHost(), rph); 251 { 252 // There should be now two RenderWidgetHosts, one for each process 253 // rendering a frame. 254 std::set<RenderWidgetHostView*> views_set = 255 static_cast<WebContentsImpl*>(shell()->web_contents()) 256 ->GetRenderWidgetHostViewsInTree(); 257 EXPECT_EQ(2U, views_set.size()); 258 } 259 proxy_to_parent = child->render_manager()->GetProxyToParent(); 260 EXPECT_TRUE(proxy_to_parent); 261 EXPECT_TRUE(proxy_to_parent->cross_process_frame_connector()); 262 EXPECT_EQ( 263 rvh->GetView(), 264 proxy_to_parent->cross_process_frame_connector()->get_view_for_testing()); 265 266 // Load another cross-site page into the same iframe. 267 cross_site_url = test_server()->GetURL("files/title3.html"); 268 std::string bar_com("bar.com"); 269 replace_host.SetHostStr(bar_com); 270 cross_site_url = cross_site_url.ReplaceComponents(replace_host); 271 NavigateFrameToURL(root->child_at(0), cross_site_url); 272 EXPECT_EQ(cross_site_url, observer.navigation_url()); 273 EXPECT_TRUE(observer.navigation_succeeded()); 274 275 // Check again that a new process is created and is different from the 276 // top level one and the previous one. 277 ASSERT_EQ(1U, root->child_count()); 278 child = root->child_at(0); 279 EXPECT_NE(shell()->web_contents()->GetRenderViewHost(), 280 child->current_frame_host()->render_view_host()); 281 EXPECT_NE(rvh, child->current_frame_host()->render_view_host()); 282 EXPECT_NE(shell()->web_contents()->GetSiteInstance(), 283 child->current_frame_host()->GetSiteInstance()); 284 EXPECT_NE(site_instance, 285 child->current_frame_host()->GetSiteInstance()); 286 EXPECT_NE(shell()->web_contents()->GetRenderProcessHost(), 287 child->current_frame_host()->GetProcess()); 288 EXPECT_NE(rph, child->current_frame_host()->GetProcess()); 289 { 290 std::set<RenderWidgetHostView*> views_set = 291 static_cast<WebContentsImpl*>(shell()->web_contents()) 292 ->GetRenderWidgetHostViewsInTree(); 293 EXPECT_EQ(2U, views_set.size()); 294 } 295 EXPECT_EQ(proxy_to_parent, child->render_manager()->GetProxyToParent()); 296 EXPECT_TRUE(proxy_to_parent->cross_process_frame_connector()); 297 EXPECT_EQ( 298 child->current_frame_host()->render_view_host()->GetView(), 299 proxy_to_parent->cross_process_frame_connector()->get_view_for_testing()); 300} 301 302// Crash a subframe and ensures its children are cleared from the FrameTree. 303// See http://crbug.com/338508. 304// TODO(creis): Disabled for flakiness; see http://crbug.com/405582. 305// TODO(creis): Enable this on Android when we can kill the process there. 306IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, DISABLED_CrashSubframe) { 307 host_resolver()->AddRule("*", "127.0.0.1"); 308 ASSERT_TRUE(test_server()->Start()); 309 GURL main_url(test_server()->GetURL("files/site_per_process_main.html")); 310 NavigateToURL(shell(), main_url); 311 312 StartFrameAtDataURL(); 313 314 // These must stay in scope with replace_host. 315 GURL::Replacements replace_host; 316 std::string foo_com("foo.com"); 317 318 // Load cross-site page into iframe. 319 GURL cross_site_url(test_server()->GetURL("files/title2.html")); 320 replace_host.SetHostStr(foo_com); 321 cross_site_url = cross_site_url.ReplaceComponents(replace_host); 322 EXPECT_TRUE(NavigateIframeToURL(shell(), cross_site_url, "test")); 323 324 // Check the subframe process. 325 FrameTreeNode* root = 326 static_cast<WebContentsImpl*>(shell()->web_contents())-> 327 GetFrameTree()->root(); 328 ASSERT_EQ(1U, root->child_count()); 329 FrameTreeNode* child = root->child_at(0); 330 EXPECT_EQ(main_url, root->current_url()); 331 EXPECT_EQ(cross_site_url, child->current_url()); 332 333 EXPECT_TRUE( 334 child->current_frame_host()->render_view_host()->IsRenderViewLive()); 335 EXPECT_TRUE(child->current_frame_host()->IsRenderFrameLive()); 336 337 // Crash the subframe process. 338 RenderProcessHost* root_process = root->current_frame_host()->GetProcess(); 339 RenderProcessHost* child_process = child->current_frame_host()->GetProcess(); 340 { 341 RenderProcessHostWatcher crash_observer( 342 child_process, 343 RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); 344 base::KillProcess(child_process->GetHandle(), 0, false); 345 crash_observer.Wait(); 346 } 347 348 // Ensure that the child frame still exists but has been cleared. 349 EXPECT_EQ(1U, root->child_count()); 350 EXPECT_EQ(main_url, root->current_url()); 351 EXPECT_EQ(GURL(), child->current_url()); 352 353 EXPECT_FALSE( 354 child->current_frame_host()->render_view_host()->IsRenderViewLive()); 355 EXPECT_FALSE(child->current_frame_host()->IsRenderFrameLive()); 356 EXPECT_FALSE(child->current_frame_host()->render_frame_created_); 357 358 // Now crash the top-level page to clear the child frame. 359 { 360 RenderProcessHostWatcher crash_observer( 361 root_process, 362 RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); 363 base::KillProcess(root_process->GetHandle(), 0, false); 364 crash_observer.Wait(); 365 } 366 EXPECT_EQ(0U, root->child_count()); 367 EXPECT_EQ(GURL(), root->current_url()); 368} 369 370// TODO(nasko): Disable this test until out-of-process iframes is ready and the 371// security checks are back in place. 372// TODO(creis): Replace SpawnedTestServer with host_resolver to get test to run 373// on Android (http://crbug.com/187570). 374IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, 375 DISABLED_CrossSiteIframeRedirectOnce) { 376 ASSERT_TRUE(test_server()->Start()); 377 net::SpawnedTestServer https_server( 378 net::SpawnedTestServer::TYPE_HTTPS, 379 net::SpawnedTestServer::kLocalhost, 380 base::FilePath(FILE_PATH_LITERAL("content/test/data"))); 381 ASSERT_TRUE(https_server.Start()); 382 383 GURL main_url(test_server()->GetURL("files/site_per_process_main.html")); 384 GURL http_url(test_server()->GetURL("files/title1.html")); 385 GURL https_url(https_server.GetURL("files/title1.html")); 386 387 NavigateToURL(shell(), main_url); 388 389 SitePerProcessWebContentsObserver observer(shell()->web_contents()); 390 { 391 // Load cross-site client-redirect page into Iframe. 392 // Should be blocked. 393 GURL client_redirect_https_url(https_server.GetURL( 394 "client-redirect?files/title1.html")); 395 EXPECT_TRUE(NavigateIframeToURL(shell(), 396 client_redirect_https_url, "test")); 397 // DidFailProvisionalLoad when navigating to client_redirect_https_url. 398 EXPECT_EQ(observer.navigation_url(), client_redirect_https_url); 399 EXPECT_FALSE(observer.navigation_succeeded()); 400 } 401 402 { 403 // Load cross-site server-redirect page into Iframe, 404 // which redirects to same-site page. 405 GURL server_redirect_http_url(https_server.GetURL( 406 "server-redirect?" + http_url.spec())); 407 EXPECT_TRUE(NavigateIframeToURL(shell(), 408 server_redirect_http_url, "test")); 409 EXPECT_EQ(observer.navigation_url(), http_url); 410 EXPECT_TRUE(observer.navigation_succeeded()); 411 } 412 413 { 414 // Load cross-site server-redirect page into Iframe, 415 // which redirects to cross-site page. 416 GURL server_redirect_http_url(https_server.GetURL( 417 "server-redirect?files/title1.html")); 418 EXPECT_TRUE(NavigateIframeToURL(shell(), 419 server_redirect_http_url, "test")); 420 // DidFailProvisionalLoad when navigating to https_url. 421 EXPECT_EQ(observer.navigation_url(), https_url); 422 EXPECT_FALSE(observer.navigation_succeeded()); 423 } 424 425 { 426 // Load same-site server-redirect page into Iframe, 427 // which redirects to cross-site page. 428 GURL server_redirect_http_url(test_server()->GetURL( 429 "server-redirect?" + https_url.spec())); 430 EXPECT_TRUE(NavigateIframeToURL(shell(), 431 server_redirect_http_url, "test")); 432 433 EXPECT_EQ(observer.navigation_url(), https_url); 434 EXPECT_FALSE(observer.navigation_succeeded()); 435 } 436 437 { 438 // Load same-site client-redirect page into Iframe, 439 // which redirects to cross-site page. 440 GURL client_redirect_http_url(test_server()->GetURL( 441 "client-redirect?" + https_url.spec())); 442 443 RedirectNotificationObserver load_observer2( 444 NOTIFICATION_LOAD_STOP, 445 Source<NavigationController>( 446 &shell()->web_contents()->GetController())); 447 448 EXPECT_TRUE(NavigateIframeToURL(shell(), 449 client_redirect_http_url, "test")); 450 451 // Same-site Client-Redirect Page should be loaded successfully. 452 EXPECT_EQ(observer.navigation_url(), client_redirect_http_url); 453 EXPECT_TRUE(observer.navigation_succeeded()); 454 455 // Redirecting to Cross-site Page should be blocked. 456 load_observer2.Wait(); 457 EXPECT_EQ(observer.navigation_url(), https_url); 458 EXPECT_FALSE(observer.navigation_succeeded()); 459 } 460 461 { 462 // Load same-site server-redirect page into Iframe, 463 // which redirects to same-site page. 464 GURL server_redirect_http_url(test_server()->GetURL( 465 "server-redirect?files/title1.html")); 466 EXPECT_TRUE(NavigateIframeToURL(shell(), 467 server_redirect_http_url, "test")); 468 EXPECT_EQ(observer.navigation_url(), http_url); 469 EXPECT_TRUE(observer.navigation_succeeded()); 470 } 471 472 { 473 // Load same-site client-redirect page into Iframe, 474 // which redirects to same-site page. 475 GURL client_redirect_http_url(test_server()->GetURL( 476 "client-redirect?" + http_url.spec())); 477 RedirectNotificationObserver load_observer2( 478 NOTIFICATION_LOAD_STOP, 479 Source<NavigationController>( 480 &shell()->web_contents()->GetController())); 481 482 EXPECT_TRUE(NavigateIframeToURL(shell(), 483 client_redirect_http_url, "test")); 484 485 // Same-site Client-Redirect Page should be loaded successfully. 486 EXPECT_EQ(observer.navigation_url(), client_redirect_http_url); 487 EXPECT_TRUE(observer.navigation_succeeded()); 488 489 // Redirecting to Same-site Page should be loaded successfully. 490 load_observer2.Wait(); 491 EXPECT_EQ(observer.navigation_url(), http_url); 492 EXPECT_TRUE(observer.navigation_succeeded()); 493 } 494} 495 496// TODO(nasko): Disable this test until out-of-process iframes is ready and the 497// security checks are back in place. 498// TODO(creis): Replace SpawnedTestServer with host_resolver to get test to run 499// on Android (http://crbug.com/187570). 500IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, 501 DISABLED_CrossSiteIframeRedirectTwice) { 502 ASSERT_TRUE(test_server()->Start()); 503 net::SpawnedTestServer https_server( 504 net::SpawnedTestServer::TYPE_HTTPS, 505 net::SpawnedTestServer::kLocalhost, 506 base::FilePath(FILE_PATH_LITERAL("content/test/data"))); 507 ASSERT_TRUE(https_server.Start()); 508 509 GURL main_url(test_server()->GetURL("files/site_per_process_main.html")); 510 GURL http_url(test_server()->GetURL("files/title1.html")); 511 GURL https_url(https_server.GetURL("files/title1.html")); 512 513 NavigateToURL(shell(), main_url); 514 515 SitePerProcessWebContentsObserver observer(shell()->web_contents()); 516 { 517 // Load client-redirect page pointing to a cross-site client-redirect page, 518 // which eventually redirects back to same-site page. 519 GURL client_redirect_https_url(https_server.GetURL( 520 "client-redirect?" + http_url.spec())); 521 GURL client_redirect_http_url(test_server()->GetURL( 522 "client-redirect?" + client_redirect_https_url.spec())); 523 524 // We should wait until second client redirect get cancelled. 525 RedirectNotificationObserver load_observer2( 526 NOTIFICATION_LOAD_STOP, 527 Source<NavigationController>( 528 &shell()->web_contents()->GetController())); 529 530 EXPECT_TRUE(NavigateIframeToURL(shell(), client_redirect_http_url, "test")); 531 532 // DidFailProvisionalLoad when navigating to client_redirect_https_url. 533 load_observer2.Wait(); 534 EXPECT_EQ(observer.navigation_url(), client_redirect_https_url); 535 EXPECT_FALSE(observer.navigation_succeeded()); 536 } 537 538 { 539 // Load server-redirect page pointing to a cross-site server-redirect page, 540 // which eventually redirect back to same-site page. 541 GURL server_redirect_https_url(https_server.GetURL( 542 "server-redirect?" + http_url.spec())); 543 GURL server_redirect_http_url(test_server()->GetURL( 544 "server-redirect?" + server_redirect_https_url.spec())); 545 EXPECT_TRUE(NavigateIframeToURL(shell(), 546 server_redirect_http_url, "test")); 547 EXPECT_EQ(observer.navigation_url(), http_url); 548 EXPECT_TRUE(observer.navigation_succeeded()); 549 } 550 551 { 552 // Load server-redirect page pointing to a cross-site server-redirect page, 553 // which eventually redirects back to cross-site page. 554 GURL server_redirect_https_url(https_server.GetURL( 555 "server-redirect?" + https_url.spec())); 556 GURL server_redirect_http_url(test_server()->GetURL( 557 "server-redirect?" + server_redirect_https_url.spec())); 558 EXPECT_TRUE(NavigateIframeToURL(shell(), server_redirect_http_url, "test")); 559 560 // DidFailProvisionalLoad when navigating to https_url. 561 EXPECT_EQ(observer.navigation_url(), https_url); 562 EXPECT_FALSE(observer.navigation_succeeded()); 563 } 564 565 { 566 // Load server-redirect page pointing to a cross-site client-redirect page, 567 // which eventually redirects back to same-site page. 568 GURL client_redirect_http_url(https_server.GetURL( 569 "client-redirect?" + http_url.spec())); 570 GURL server_redirect_http_url(test_server()->GetURL( 571 "server-redirect?" + client_redirect_http_url.spec())); 572 EXPECT_TRUE(NavigateIframeToURL(shell(), server_redirect_http_url, "test")); 573 574 // DidFailProvisionalLoad when navigating to client_redirect_http_url. 575 EXPECT_EQ(observer.navigation_url(), client_redirect_http_url); 576 EXPECT_FALSE(observer.navigation_succeeded()); 577 } 578} 579 580} // namespace content 581