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 "content/browser/frame_host/cross_process_frame_connector.h" 8 #include "content/browser/frame_host/frame_tree.h" 9 #include "content/browser/frame_host/render_frame_proxy_host.h" 10 #include "content/browser/frame_host/render_widget_host_view_child_frame.h" 11 #include "content/browser/renderer_host/render_view_host_impl.h" 12 #include "content/browser/web_contents/web_contents_impl.h" 13 #include "content/public/browser/notification_observer.h" 14 #include "content/public/browser/notification_service.h" 15 #include "content/public/browser/notification_types.h" 16 #include "content/public/browser/web_contents_observer.h" 17 #include "content/public/common/content_switches.h" 18 #include "content/public/test/browser_test_utils.h" 19 #include "content/public/test/content_browser_test.h" 20 #include "content/public/test/content_browser_test_utils.h" 21 #include "content/public/test/test_utils.h" 22 #include "content/shell/browser/shell.h" 23 #include "content/test/content_browser_test_utils_internal.h" 24 #include "net/dns/mock_host_resolver.h" 25 #include "url/gurl.h" 26 27 namespace content { 28 29 class 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 int64 frame_id, 38 int64 parent_frame_id, 39 bool is_main_frame, 40 const GURL& validated_url, 41 bool is_error_page, 42 bool is_iframe_srcdoc, 43 RenderViewHost* render_view_host) OVERRIDE { 44 navigation_succeeded_ = false; 45 } 46 47 virtual void DidFailProvisionalLoad( 48 int64 frame_id, 49 const base::string16& frame_unique_name, 50 bool is_main_frame, 51 const GURL& validated_url, 52 int error_code, 53 const base::string16& error_description, 54 RenderViewHost* render_view_host) OVERRIDE { 55 navigation_url_ = validated_url; 56 navigation_succeeded_ = false; 57 } 58 59 virtual void DidCommitProvisionalLoadForFrame( 60 int64 frame_id, 61 const base::string16& frame_unique_name, 62 bool is_main_frame, 63 const GURL& url, 64 PageTransition transition_type, 65 RenderViewHost* render_view_host) OVERRIDE{ 66 navigation_url_ = url; 67 navigation_succeeded_ = true; 68 } 69 70 const GURL& navigation_url() const { 71 return navigation_url_; 72 } 73 74 int navigation_succeeded() const { return navigation_succeeded_; } 75 76 private: 77 GURL navigation_url_; 78 bool navigation_succeeded_; 79 80 DISALLOW_COPY_AND_ASSIGN(SitePerProcessWebContentsObserver); 81 }; 82 83 class RedirectNotificationObserver : public NotificationObserver { 84 public: 85 // Register to listen for notifications of the given type from either a 86 // specific source, or from all sources if |source| is 87 // NotificationService::AllSources(). 88 RedirectNotificationObserver(int notification_type, 89 const NotificationSource& source); 90 virtual ~RedirectNotificationObserver(); 91 92 // Wait until the specified notification occurs. If the notification was 93 // emitted between the construction of this object and this call then it 94 // returns immediately. 95 void Wait(); 96 97 // Returns NotificationService::AllSources() if we haven't observed a 98 // notification yet. 99 const NotificationSource& source() const { 100 return source_; 101 } 102 103 const NotificationDetails& details() const { 104 return details_; 105 } 106 107 // NotificationObserver: 108 virtual void Observe(int type, 109 const NotificationSource& source, 110 const NotificationDetails& details) OVERRIDE; 111 112 private: 113 bool seen_; 114 bool seen_twice_; 115 bool running_; 116 NotificationRegistrar registrar_; 117 118 NotificationSource source_; 119 NotificationDetails details_; 120 scoped_refptr<MessageLoopRunner> message_loop_runner_; 121 122 DISALLOW_COPY_AND_ASSIGN(RedirectNotificationObserver); 123 }; 124 125 RedirectNotificationObserver::RedirectNotificationObserver( 126 int notification_type, 127 const NotificationSource& source) 128 : seen_(false), 129 running_(false), 130 source_(NotificationService::AllSources()) { 131 registrar_.Add(this, notification_type, source); 132 } 133 134 RedirectNotificationObserver::~RedirectNotificationObserver() {} 135 136 void RedirectNotificationObserver::Wait() { 137 if (seen_ && seen_twice_) 138 return; 139 140 running_ = true; 141 message_loop_runner_ = new MessageLoopRunner; 142 message_loop_runner_->Run(); 143 EXPECT_TRUE(seen_); 144 } 145 146 void RedirectNotificationObserver::Observe( 147 int type, 148 const NotificationSource& source, 149 const NotificationDetails& details) { 150 source_ = source; 151 details_ = details; 152 seen_twice_ = seen_; 153 seen_ = true; 154 if (!running_) 155 return; 156 157 message_loop_runner_->Quit(); 158 running_ = false; 159 } 160 161 class SitePerProcessBrowserTest : public ContentBrowserTest { 162 public: 163 SitePerProcessBrowserTest() {} 164 165 protected: 166 // Start at a data URL so each extra navigation creates a navigation entry. 167 // (The first navigation will silently be classified as AUTO_SUBFRAME.) 168 // TODO(creis): This won't be necessary when we can wait for LOAD_STOP. 169 void StartFrameAtDataURL() { 170 std::string data_url_script = 171 "var iframes = document.getElementById('test');iframes.src=" 172 "'data:text/html,dataurl';"; 173 ASSERT_TRUE(ExecuteScript(shell()->web_contents(), data_url_script)); 174 } 175 176 bool NavigateIframeToURL(Shell* window, 177 const GURL& url, 178 std::string iframe_id) { 179 // TODO(creis): This should wait for LOAD_STOP, but cross-site subframe 180 // navigations generate extra DidStartLoading and DidStopLoading messages. 181 // Until we replace swappedout:// with frame proxies, we need to listen for 182 // something else. For now, we trigger NEW_SUBFRAME navigations and listen 183 // for commit. 184 std::string script = base::StringPrintf( 185 "setTimeout(\"" 186 "var iframes = document.getElementById('%s');iframes.src='%s';" 187 "\",0)", 188 iframe_id.c_str(), url.spec().c_str()); 189 WindowedNotificationObserver load_observer( 190 NOTIFICATION_NAV_ENTRY_COMMITTED, 191 Source<NavigationController>( 192 &window->web_contents()->GetController())); 193 bool result = ExecuteScript(window->web_contents(), script); 194 load_observer.Wait(); 195 return result; 196 } 197 198 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { 199 command_line->AppendSwitch(switches::kSitePerProcess); 200 } 201 }; 202 203 // Ensure that we can complete a cross-process subframe navigation. 204 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, CrossSiteIframe) { 205 host_resolver()->AddRule("*", "127.0.0.1"); 206 ASSERT_TRUE(test_server()->Start()); 207 GURL main_url(test_server()->GetURL("files/site_per_process_main.html")); 208 NavigateToURL(shell(), main_url); 209 210 // It is safe to obtain the root frame tree node here, as it doesn't change. 211 FrameTreeNode* root = 212 static_cast<WebContentsImpl*>(shell()->web_contents())-> 213 GetFrameTree()->root(); 214 215 SitePerProcessWebContentsObserver observer(shell()->web_contents()); 216 217 // Load same-site page into iframe. 218 FrameTreeNode* child = root->child_at(0); 219 GURL http_url(test_server()->GetURL("files/title1.html")); 220 NavigateFrameToURL(child, http_url); 221 EXPECT_EQ(http_url, observer.navigation_url()); 222 EXPECT_TRUE(observer.navigation_succeeded()); 223 { 224 // There should be only one RenderWidgetHost when there are no 225 // cross-process iframes. 226 std::set<RenderWidgetHostView*> views_set = 227 static_cast<WebContentsImpl*>(shell()->web_contents()) 228 ->GetRenderWidgetHostViewsInTree(); 229 EXPECT_EQ(1U, views_set.size()); 230 } 231 RenderFrameProxyHost* proxy_to_parent = 232 child->render_manager()->GetRenderFrameProxyHost( 233 shell()->web_contents()->GetSiteInstance()); 234 EXPECT_FALSE(proxy_to_parent); 235 236 // These must stay in scope with replace_host. 237 GURL::Replacements replace_host; 238 std::string foo_com("foo.com"); 239 240 // Load cross-site page into iframe. 241 GURL cross_site_url(test_server()->GetURL("files/title2.html")); 242 replace_host.SetHostStr(foo_com); 243 cross_site_url = cross_site_url.ReplaceComponents(replace_host); 244 NavigateFrameToURL(root->child_at(0), cross_site_url); 245 EXPECT_EQ(cross_site_url, observer.navigation_url()); 246 EXPECT_TRUE(observer.navigation_succeeded()); 247 248 // Ensure that we have created a new process for the subframe. 249 ASSERT_EQ(1U, root->child_count()); 250 SiteInstance* site_instance = child->current_frame_host()->GetSiteInstance(); 251 RenderViewHost* rvh = child->current_frame_host()->render_view_host(); 252 RenderProcessHost* rph = child->current_frame_host()->GetProcess(); 253 EXPECT_NE(shell()->web_contents()->GetRenderViewHost(), rvh); 254 EXPECT_NE(shell()->web_contents()->GetSiteInstance(), site_instance); 255 EXPECT_NE(shell()->web_contents()->GetRenderProcessHost(), rph); 256 { 257 // There should be now two RenderWidgetHosts, one for each process 258 // rendering a frame. 259 std::set<RenderWidgetHostView*> views_set = 260 static_cast<WebContentsImpl*>(shell()->web_contents()) 261 ->GetRenderWidgetHostViewsInTree(); 262 EXPECT_EQ(2U, views_set.size()); 263 } 264 proxy_to_parent = child->render_manager()->GetProxyToParent(); 265 EXPECT_TRUE(proxy_to_parent); 266 EXPECT_TRUE(proxy_to_parent->cross_process_frame_connector()); 267 EXPECT_EQ( 268 rvh->GetView(), 269 proxy_to_parent->cross_process_frame_connector()->get_view_for_testing()); 270 271 // Load another cross-site page into the same iframe. 272 cross_site_url = test_server()->GetURL("files/title3.html"); 273 std::string bar_com("bar.com"); 274 replace_host.SetHostStr(bar_com); 275 cross_site_url = cross_site_url.ReplaceComponents(replace_host); 276 NavigateFrameToURL(root->child_at(0), cross_site_url); 277 EXPECT_EQ(cross_site_url, observer.navigation_url()); 278 EXPECT_TRUE(observer.navigation_succeeded()); 279 280 // Check again that a new process is created and is different from the 281 // top level one and the previous one. 282 ASSERT_EQ(1U, root->child_count()); 283 child = root->child_at(0); 284 EXPECT_NE(shell()->web_contents()->GetRenderViewHost(), 285 child->current_frame_host()->render_view_host()); 286 EXPECT_NE(rvh, child->current_frame_host()->render_view_host()); 287 EXPECT_NE(shell()->web_contents()->GetSiteInstance(), 288 child->current_frame_host()->GetSiteInstance()); 289 EXPECT_NE(site_instance, 290 child->current_frame_host()->GetSiteInstance()); 291 EXPECT_NE(shell()->web_contents()->GetRenderProcessHost(), 292 child->current_frame_host()->GetProcess()); 293 EXPECT_NE(rph, child->current_frame_host()->GetProcess()); 294 { 295 std::set<RenderWidgetHostView*> views_set = 296 static_cast<WebContentsImpl*>(shell()->web_contents()) 297 ->GetRenderWidgetHostViewsInTree(); 298 EXPECT_EQ(2U, views_set.size()); 299 } 300 EXPECT_EQ(proxy_to_parent, child->render_manager()->GetProxyToParent()); 301 EXPECT_TRUE(proxy_to_parent->cross_process_frame_connector()); 302 EXPECT_EQ( 303 child->current_frame_host()->render_view_host()->GetView(), 304 proxy_to_parent->cross_process_frame_connector()->get_view_for_testing()); 305 } 306 307 // Crash a subframe and ensures its children are cleared from the FrameTree. 308 // See http://crbug.com/338508. 309 // TODO(creis): Enable this on Android when we can kill the process there. 310 #if defined(OS_ANDROID) 311 #define MAYBE_CrashSubframe DISABLED_CrashSubframe 312 #else 313 #define MAYBE_CrashSubframe CrashSubframe 314 #endif 315 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, MAYBE_CrashSubframe) { 316 host_resolver()->AddRule("*", "127.0.0.1"); 317 ASSERT_TRUE(test_server()->Start()); 318 GURL main_url(test_server()->GetURL("files/site_per_process_main.html")); 319 NavigateToURL(shell(), main_url); 320 321 StartFrameAtDataURL(); 322 323 // These must stay in scope with replace_host. 324 GURL::Replacements replace_host; 325 std::string foo_com("foo.com"); 326 327 // Load cross-site page into iframe. 328 GURL cross_site_url(test_server()->GetURL("files/title2.html")); 329 replace_host.SetHostStr(foo_com); 330 cross_site_url = cross_site_url.ReplaceComponents(replace_host); 331 EXPECT_TRUE(NavigateIframeToURL(shell(), cross_site_url, "test")); 332 333 // Check the subframe process. 334 FrameTreeNode* root = 335 static_cast<WebContentsImpl*>(shell()->web_contents())-> 336 GetFrameTree()->root(); 337 ASSERT_EQ(1U, root->child_count()); 338 FrameTreeNode* child = root->child_at(0); 339 EXPECT_EQ(main_url, root->current_url()); 340 EXPECT_EQ(cross_site_url, child->current_url()); 341 342 // Crash the subframe process. 343 RenderProcessHost* root_process = root->current_frame_host()->GetProcess(); 344 RenderProcessHost* child_process = child->current_frame_host()->GetProcess(); 345 { 346 RenderProcessHostWatcher crash_observer( 347 child_process, 348 RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); 349 base::KillProcess(child_process->GetHandle(), 0, false); 350 crash_observer.Wait(); 351 } 352 353 // Ensure that the child frame still exists but has been cleared. 354 EXPECT_EQ(1U, root->child_count()); 355 EXPECT_EQ(main_url, root->current_url()); 356 EXPECT_EQ(GURL(), child->current_url()); 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). 374 IN_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). 500 IN_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