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.
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"
30 class SitePerProcessWebContentsObserver
: public WebContentsObserver
{
32 explicit SitePerProcessWebContentsObserver(WebContents
* web_contents
)
33 : WebContentsObserver(web_contents
),
34 navigation_succeeded_(false) {}
35 virtual ~SitePerProcessWebContentsObserver() {}
37 virtual void DidStartProvisionalLoadForFrame(
39 int64 parent_frame_id
,
41 const GURL
& validated_url
,
43 bool is_iframe_srcdoc
,
44 RenderViewHost
* render_view_host
) OVERRIDE
{
45 navigation_succeeded_
= false;
48 virtual void DidFailProvisionalLoad(
50 const base::string16
& frame_unique_name
,
52 const GURL
& validated_url
,
54 const base::string16
& error_description
,
55 RenderViewHost
* render_view_host
) OVERRIDE
{
56 navigation_url_
= validated_url
;
57 navigation_succeeded_
= false;
60 virtual void DidCommitProvisionalLoadForFrame(
62 const base::string16
& frame_unique_name
,
65 PageTransition transition_type
,
66 RenderViewHost
* render_view_host
) OVERRIDE
{
67 navigation_url_
= url
;
68 navigation_succeeded_
= true;
71 const GURL
& navigation_url() const {
72 return navigation_url_
;
75 int navigation_succeeded() const { return navigation_succeeded_
; }
79 bool navigation_succeeded_
;
81 DISALLOW_COPY_AND_ASSIGN(SitePerProcessWebContentsObserver
);
84 class RedirectNotificationObserver
: public NotificationObserver
{
86 // Register to listen for notifications of the given type from either a
87 // specific source, or from all sources if |source| is
88 // NotificationService::AllSources().
89 RedirectNotificationObserver(int notification_type
,
90 const NotificationSource
& source
);
91 virtual ~RedirectNotificationObserver();
93 // Wait until the specified notification occurs. If the notification was
94 // emitted between the construction of this object and this call then it
95 // returns immediately.
98 // Returns NotificationService::AllSources() if we haven't observed a
100 const NotificationSource
& source() const {
104 const NotificationDetails
& details() const {
108 // NotificationObserver:
109 virtual void Observe(int type
,
110 const NotificationSource
& source
,
111 const NotificationDetails
& details
) OVERRIDE
;
117 NotificationRegistrar registrar_
;
119 NotificationSource source_
;
120 NotificationDetails details_
;
121 scoped_refptr
<MessageLoopRunner
> message_loop_runner_
;
123 DISALLOW_COPY_AND_ASSIGN(RedirectNotificationObserver
);
126 RedirectNotificationObserver::RedirectNotificationObserver(
127 int notification_type
,
128 const NotificationSource
& source
)
131 source_(NotificationService::AllSources()) {
132 registrar_
.Add(this, notification_type
, source
);
135 RedirectNotificationObserver::~RedirectNotificationObserver() {}
137 void RedirectNotificationObserver::Wait() {
138 if (seen_
&& seen_twice_
)
142 message_loop_runner_
= new MessageLoopRunner
;
143 message_loop_runner_
->Run();
147 void RedirectNotificationObserver::Observe(
149 const NotificationSource
& source
,
150 const NotificationDetails
& details
) {
158 message_loop_runner_
->Quit();
162 class SitePerProcessBrowserTest
: public ContentBrowserTest
{
164 bool NavigateIframeToURL(Shell
* window
,
166 std::string iframe_id
) {
167 std::string script
= base::StringPrintf(
168 "var iframes = document.getElementById('%s');iframes.src='%s';",
169 iframe_id
.c_str(), url
.spec().c_str());
170 WindowedNotificationObserver
load_observer(
171 NOTIFICATION_LOAD_STOP
,
172 Source
<NavigationController
>(
173 &shell()->web_contents()->GetController()));
174 bool result
= ExecuteScript(window
->web_contents(), script
);
175 load_observer
.Wait();
179 void NavigateToURLContentInitiated(Shell
* window
,
181 bool should_replace_current_entry
) {
183 if (should_replace_current_entry
)
184 script
= base::StringPrintf("location.replace('%s')", url
.spec().c_str());
186 script
= base::StringPrintf("location.href = '%s'", url
.spec().c_str());
187 TestNavigationObserver
load_observer(shell()->web_contents(), 1);
188 bool result
= ExecuteScript(window
->web_contents(), script
);
190 load_observer
.Wait();
193 virtual void SetUpCommandLine(CommandLine
* command_line
) OVERRIDE
{
194 command_line
->AppendSwitch(switches::kSitePerProcess
);
198 // Ensure that we can complete a cross-process subframe navigation.
199 // TODO(nasko): Disable this test for now, since enabling swapping out of
200 // RenderFrameHosts causes this to break. Fix this test once
201 // didFailProvisionalLoad is moved from RenderView to RenderFrame.
202 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest
, DISABLED_CrossSiteIframe
) {
203 ASSERT_TRUE(test_server()->Start());
204 net::SpawnedTestServer
https_server(
205 net::SpawnedTestServer::TYPE_HTTPS
,
206 net::SpawnedTestServer::kLocalhost
,
207 base::FilePath(FILE_PATH_LITERAL("content/test/data")));
208 ASSERT_TRUE(https_server
.Start());
209 GURL
main_url(test_server()->GetURL("files/site_per_process_main.html"));
211 NavigateToURL(shell(), main_url
);
213 SitePerProcessWebContentsObserver
observer(shell()->web_contents());
215 // Load same-site page into iframe.
216 GURL
http_url(test_server()->GetURL("files/title1.html"));
217 EXPECT_TRUE(NavigateIframeToURL(shell(), http_url
, "test"));
218 EXPECT_EQ(observer
.navigation_url(), http_url
);
219 EXPECT_TRUE(observer
.navigation_succeeded());
221 // Load cross-site page into iframe.
222 GURL
https_url(https_server
.GetURL("files/title1.html"));
223 EXPECT_TRUE(NavigateIframeToURL(shell(), https_url
, "test"));
224 EXPECT_EQ(observer
.navigation_url(), https_url
);
225 EXPECT_TRUE(observer
.navigation_succeeded());
227 // Ensure that we have created a new process for the subframe.
228 FrameTreeNode
* root
=
229 static_cast<WebContentsImpl
*>(shell()->web_contents())->
230 GetFrameTree()->root();
231 ASSERT_EQ(1U, root
->child_count());
232 FrameTreeNode
* child
= root
->child_at(0);
233 EXPECT_NE(shell()->web_contents()->GetRenderViewHost(),
234 child
->current_frame_host()->render_view_host());
235 EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
236 child
->current_frame_host()->render_view_host()->GetSiteInstance());
237 EXPECT_NE(shell()->web_contents()->GetRenderProcessHost(),
238 child
->current_frame_host()->GetProcess());
241 // TODO(nasko): Disable this test until out-of-process iframes is ready and the
242 // security checks are back in place.
243 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest
,
244 DISABLED_CrossSiteIframeRedirectOnce
) {
245 ASSERT_TRUE(test_server()->Start());
246 net::SpawnedTestServer
https_server(
247 net::SpawnedTestServer::TYPE_HTTPS
,
248 net::SpawnedTestServer::kLocalhost
,
249 base::FilePath(FILE_PATH_LITERAL("content/test/data")));
250 ASSERT_TRUE(https_server
.Start());
252 GURL
main_url(test_server()->GetURL("files/site_per_process_main.html"));
253 GURL
http_url(test_server()->GetURL("files/title1.html"));
254 GURL
https_url(https_server
.GetURL("files/title1.html"));
256 NavigateToURL(shell(), main_url
);
258 SitePerProcessWebContentsObserver
observer(shell()->web_contents());
260 // Load cross-site client-redirect page into Iframe.
261 // Should be blocked.
262 GURL
client_redirect_https_url(https_server
.GetURL(
263 "client-redirect?files/title1.html"));
264 EXPECT_TRUE(NavigateIframeToURL(shell(),
265 client_redirect_https_url
, "test"));
266 // DidFailProvisionalLoad when navigating to client_redirect_https_url.
267 EXPECT_EQ(observer
.navigation_url(), client_redirect_https_url
);
268 EXPECT_FALSE(observer
.navigation_succeeded());
272 // Load cross-site server-redirect page into Iframe,
273 // which redirects to same-site page.
274 GURL
server_redirect_http_url(https_server
.GetURL(
275 "server-redirect?" + http_url
.spec()));
276 EXPECT_TRUE(NavigateIframeToURL(shell(),
277 server_redirect_http_url
, "test"));
278 EXPECT_EQ(observer
.navigation_url(), http_url
);
279 EXPECT_TRUE(observer
.navigation_succeeded());
283 // Load cross-site server-redirect page into Iframe,
284 // which redirects to cross-site page.
285 GURL
server_redirect_http_url(https_server
.GetURL(
286 "server-redirect?files/title1.html"));
287 EXPECT_TRUE(NavigateIframeToURL(shell(),
288 server_redirect_http_url
, "test"));
289 // DidFailProvisionalLoad when navigating to https_url.
290 EXPECT_EQ(observer
.navigation_url(), https_url
);
291 EXPECT_FALSE(observer
.navigation_succeeded());
295 // Load same-site server-redirect page into Iframe,
296 // which redirects to cross-site page.
297 GURL
server_redirect_http_url(test_server()->GetURL(
298 "server-redirect?" + https_url
.spec()));
299 EXPECT_TRUE(NavigateIframeToURL(shell(),
300 server_redirect_http_url
, "test"));
302 EXPECT_EQ(observer
.navigation_url(), https_url
);
303 EXPECT_FALSE(observer
.navigation_succeeded());
307 // Load same-site client-redirect page into Iframe,
308 // which redirects to cross-site page.
309 GURL
client_redirect_http_url(test_server()->GetURL(
310 "client-redirect?" + https_url
.spec()));
312 RedirectNotificationObserver
load_observer2(
313 NOTIFICATION_LOAD_STOP
,
314 Source
<NavigationController
>(
315 &shell()->web_contents()->GetController()));
317 EXPECT_TRUE(NavigateIframeToURL(shell(),
318 client_redirect_http_url
, "test"));
320 // Same-site Client-Redirect Page should be loaded successfully.
321 EXPECT_EQ(observer
.navigation_url(), client_redirect_http_url
);
322 EXPECT_TRUE(observer
.navigation_succeeded());
324 // Redirecting to Cross-site Page should be blocked.
325 load_observer2
.Wait();
326 EXPECT_EQ(observer
.navigation_url(), https_url
);
327 EXPECT_FALSE(observer
.navigation_succeeded());
331 // Load same-site server-redirect page into Iframe,
332 // which redirects to same-site page.
333 GURL
server_redirect_http_url(test_server()->GetURL(
334 "server-redirect?files/title1.html"));
335 EXPECT_TRUE(NavigateIframeToURL(shell(),
336 server_redirect_http_url
, "test"));
337 EXPECT_EQ(observer
.navigation_url(), http_url
);
338 EXPECT_TRUE(observer
.navigation_succeeded());
342 // Load same-site client-redirect page into Iframe,
343 // which redirects to same-site page.
344 GURL
client_redirect_http_url(test_server()->GetURL(
345 "client-redirect?" + http_url
.spec()));
346 RedirectNotificationObserver
load_observer2(
347 NOTIFICATION_LOAD_STOP
,
348 Source
<NavigationController
>(
349 &shell()->web_contents()->GetController()));
351 EXPECT_TRUE(NavigateIframeToURL(shell(),
352 client_redirect_http_url
, "test"));
354 // Same-site Client-Redirect Page should be loaded successfully.
355 EXPECT_EQ(observer
.navigation_url(), client_redirect_http_url
);
356 EXPECT_TRUE(observer
.navigation_succeeded());
358 // Redirecting to Same-site Page should be loaded successfully.
359 load_observer2
.Wait();
360 EXPECT_EQ(observer
.navigation_url(), http_url
);
361 EXPECT_TRUE(observer
.navigation_succeeded());
365 // TODO(nasko): Disable this test until out-of-process iframes is ready and the
366 // security checks are back in place.
367 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest
,
368 DISABLED_CrossSiteIframeRedirectTwice
) {
369 ASSERT_TRUE(test_server()->Start());
370 net::SpawnedTestServer
https_server(
371 net::SpawnedTestServer::TYPE_HTTPS
,
372 net::SpawnedTestServer::kLocalhost
,
373 base::FilePath(FILE_PATH_LITERAL("content/test/data")));
374 ASSERT_TRUE(https_server
.Start());
376 GURL
main_url(test_server()->GetURL("files/site_per_process_main.html"));
377 GURL
http_url(test_server()->GetURL("files/title1.html"));
378 GURL
https_url(https_server
.GetURL("files/title1.html"));
380 NavigateToURL(shell(), main_url
);
382 SitePerProcessWebContentsObserver
observer(shell()->web_contents());
384 // Load client-redirect page pointing to a cross-site client-redirect page,
385 // which eventually redirects back to same-site page.
386 GURL
client_redirect_https_url(https_server
.GetURL(
387 "client-redirect?" + http_url
.spec()));
388 GURL
client_redirect_http_url(test_server()->GetURL(
389 "client-redirect?" + client_redirect_https_url
.spec()));
391 // We should wait until second client redirect get cancelled.
392 RedirectNotificationObserver
load_observer2(
393 NOTIFICATION_LOAD_STOP
,
394 Source
<NavigationController
>(
395 &shell()->web_contents()->GetController()));
397 EXPECT_TRUE(NavigateIframeToURL(shell(), client_redirect_http_url
, "test"));
399 // DidFailProvisionalLoad when navigating to client_redirect_https_url.
400 load_observer2
.Wait();
401 EXPECT_EQ(observer
.navigation_url(), client_redirect_https_url
);
402 EXPECT_FALSE(observer
.navigation_succeeded());
406 // Load server-redirect page pointing to a cross-site server-redirect page,
407 // which eventually redirect back to same-site page.
408 GURL
server_redirect_https_url(https_server
.GetURL(
409 "server-redirect?" + http_url
.spec()));
410 GURL
server_redirect_http_url(test_server()->GetURL(
411 "server-redirect?" + server_redirect_https_url
.spec()));
412 EXPECT_TRUE(NavigateIframeToURL(shell(),
413 server_redirect_http_url
, "test"));
414 EXPECT_EQ(observer
.navigation_url(), http_url
);
415 EXPECT_TRUE(observer
.navigation_succeeded());
419 // Load server-redirect page pointing to a cross-site server-redirect page,
420 // which eventually redirects back to cross-site page.
421 GURL
server_redirect_https_url(https_server
.GetURL(
422 "server-redirect?" + https_url
.spec()));
423 GURL
server_redirect_http_url(test_server()->GetURL(
424 "server-redirect?" + server_redirect_https_url
.spec()));
425 EXPECT_TRUE(NavigateIframeToURL(shell(), server_redirect_http_url
, "test"));
427 // DidFailProvisionalLoad when navigating to https_url.
428 EXPECT_EQ(observer
.navigation_url(), https_url
);
429 EXPECT_FALSE(observer
.navigation_succeeded());
433 // Load server-redirect page pointing to a cross-site client-redirect page,
434 // which eventually redirects back to same-site page.
435 GURL
client_redirect_http_url(https_server
.GetURL(
436 "client-redirect?" + http_url
.spec()));
437 GURL
server_redirect_http_url(test_server()->GetURL(
438 "server-redirect?" + client_redirect_http_url
.spec()));
439 EXPECT_TRUE(NavigateIframeToURL(shell(), server_redirect_http_url
, "test"));
441 // DidFailProvisionalLoad when navigating to client_redirect_http_url.
442 EXPECT_EQ(observer
.navigation_url(), client_redirect_http_url
);
443 EXPECT_FALSE(observer
.navigation_succeeded());
447 // Tests that the |should_replace_current_entry| flag persists correctly across
448 // request transfers that began with a cross-process navigation.
449 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest
,
450 ReplaceEntryCrossProcessThenTranfers
) {
451 const NavigationController
& controller
=
452 shell()->web_contents()->GetController();
453 host_resolver()->AddRule("*", "127.0.0.1");
454 ASSERT_TRUE(test_server()->Start());
456 // These must all stay in scope with replace_host.
457 GURL::Replacements replace_host
;
458 std::string
a_com("A.com");
459 std::string
b_com("B.com");
461 // Navigate to a starting URL, so there is a history entry to replace.
462 GURL url1
= test_server()->GetURL("files/site_isolation/blank.html?1");
463 NavigateToURL(shell(), url1
);
465 // Force all future navigations to transfer. Note that this includes same-site
466 // navigiations which may cause double process swaps (via OpenURL and then via
467 // transfer). This test intentionally exercises that case.
468 ShellContentBrowserClient::SetSwapProcessesForRedirect(true);
470 // Navigate to a page on A.com with entry replacement. This navigation is
471 // cross-site, so the renderer will send it to the browser via OpenURL to give
472 // to a new process. It will then be transferred into yet another process due
473 // to the call above.
474 GURL url2
= test_server()->GetURL("files/site_isolation/blank.html?2");
475 replace_host
.SetHostStr(a_com
);
476 url2
= url2
.ReplaceComponents(replace_host
);
477 NavigateToURLContentInitiated(shell(), url2
, true);
479 // There should be one history entry. url2 should have replaced url1.
480 EXPECT_TRUE(controller
.GetPendingEntry() == NULL
);
481 EXPECT_EQ(1, controller
.GetEntryCount());
482 EXPECT_EQ(0, controller
.GetCurrentEntryIndex());
483 EXPECT_EQ(url2
, controller
.GetEntryAtIndex(0)->GetURL());
485 // Now navigate as before to a page on B.com, but normally (without
486 // replacement). This will still perform a double process-swap as above, via
487 // OpenURL and then transfer.
488 GURL url3
= test_server()->GetURL("files/site_isolation/blank.html?3");
489 replace_host
.SetHostStr(b_com
);
490 url3
= url3
.ReplaceComponents(replace_host
);
491 NavigateToURLContentInitiated(shell(), url3
, false);
493 // There should be two history entries. url2 should have replaced url1. url2
494 // should not have replaced url3.
495 EXPECT_TRUE(controller
.GetPendingEntry() == NULL
);
496 EXPECT_EQ(2, controller
.GetEntryCount());
497 EXPECT_EQ(1, controller
.GetCurrentEntryIndex());
498 EXPECT_EQ(url2
, controller
.GetEntryAtIndex(0)->GetURL());
499 EXPECT_EQ(url3
, controller
.GetEntryAtIndex(1)->GetURL());
502 // Tests that the |should_replace_current_entry| flag persists correctly across
503 // request transfers that began with a content-initiated in-process
504 // navigation. This test is the same as the test above, except transfering from
506 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest
,
507 ReplaceEntryInProcessThenTranfers
) {
508 const NavigationController
& controller
=
509 shell()->web_contents()->GetController();
510 ASSERT_TRUE(test_server()->Start());
512 // Navigate to a starting URL, so there is a history entry to replace.
513 GURL url
= test_server()->GetURL("files/site_isolation/blank.html?1");
514 NavigateToURL(shell(), url
);
516 // Force all future navigations to transfer. Note that this includes same-site
517 // navigiations which may cause double process swaps (via OpenURL and then via
518 // transfer). All navigations in this test are same-site, so it only swaps
519 // processes via request transfer.
520 ShellContentBrowserClient::SetSwapProcessesForRedirect(true);
522 // Navigate in-process with entry replacement. It will then be transferred
523 // into a new one due to the call above.
524 GURL url2
= test_server()->GetURL("files/site_isolation/blank.html?2");
525 NavigateToURLContentInitiated(shell(), url2
, true);
527 // There should be one history entry. url2 should have replaced url1.
528 EXPECT_TRUE(controller
.GetPendingEntry() == NULL
);
529 EXPECT_EQ(1, controller
.GetEntryCount());
530 EXPECT_EQ(0, controller
.GetCurrentEntryIndex());
531 EXPECT_EQ(url2
, controller
.GetEntryAtIndex(0)->GetURL());
533 // Now navigate as before, but without replacement.
534 GURL url3
= test_server()->GetURL("files/site_isolation/blank.html?3");
535 NavigateToURLContentInitiated(shell(), url3
, false);
537 // There should be two history entries. url2 should have replaced url1. url2
538 // should not have replaced url3.
539 EXPECT_TRUE(controller
.GetPendingEntry() == NULL
);
540 EXPECT_EQ(2, controller
.GetEntryCount());
541 EXPECT_EQ(1, controller
.GetCurrentEntryIndex());
542 EXPECT_EQ(url2
, controller
.GetEntryAtIndex(0)->GetURL());
543 EXPECT_EQ(url3
, controller
.GetEntryAtIndex(1)->GetURL());
546 // Tests that the |should_replace_current_entry| flag persists correctly across
547 // request transfers that cross processes twice from renderer policy.
548 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest
,
549 ReplaceEntryCrossProcessTwice
) {
550 const NavigationController
& controller
=
551 shell()->web_contents()->GetController();
552 host_resolver()->AddRule("*", "127.0.0.1");
553 ASSERT_TRUE(test_server()->Start());
555 // These must all stay in scope with replace_host.
556 GURL::Replacements replace_host
;
557 std::string
a_com("A.com");
558 std::string
b_com("B.com");
560 // Navigate to a starting URL, so there is a history entry to replace.
561 GURL url1
= test_server()->GetURL("files/site_isolation/blank.html?1");
562 NavigateToURL(shell(), url1
);
564 // Navigate to a page on A.com which redirects to B.com with entry
565 // replacement. This will switch processes via OpenURL twice. First to A.com,
566 // and second in response to the server redirect to B.com. The second swap is
567 // also renderer-initiated via OpenURL because decidePolicyForNavigation is
568 // currently applied on redirects.
569 GURL url2b
= test_server()->GetURL("files/site_isolation/blank.html?2");
570 replace_host
.SetHostStr(b_com
);
571 url2b
= url2b
.ReplaceComponents(replace_host
);
572 GURL url2a
= test_server()->GetURL(
573 "server-redirect?" + net::EscapeQueryParamValue(url2b
.spec(), false));
574 replace_host
.SetHostStr(a_com
);
575 url2a
= url2a
.ReplaceComponents(replace_host
);
576 NavigateToURLContentInitiated(shell(), url2a
, true);
578 // There should be one history entry. url2b should have replaced url1.
579 EXPECT_TRUE(controller
.GetPendingEntry() == NULL
);
580 EXPECT_EQ(1, controller
.GetEntryCount());
581 EXPECT_EQ(0, controller
.GetCurrentEntryIndex());
582 EXPECT_EQ(url2b
, controller
.GetEntryAtIndex(0)->GetURL());
584 // Now repeat without replacement.
585 GURL url3b
= test_server()->GetURL("files/site_isolation/blank.html?3");
586 replace_host
.SetHostStr(b_com
);
587 url3b
= url3b
.ReplaceComponents(replace_host
);
588 GURL url3a
= test_server()->GetURL(
589 "server-redirect?" + net::EscapeQueryParamValue(url3b
.spec(), false));
590 replace_host
.SetHostStr(a_com
);
591 url3a
= url3a
.ReplaceComponents(replace_host
);
592 NavigateToURLContentInitiated(shell(), url3a
, false);
594 // There should be two history entries. url2b should have replaced url1. url2b
595 // should not have replaced url3b.
596 EXPECT_TRUE(controller
.GetPendingEntry() == NULL
);
597 EXPECT_EQ(2, controller
.GetEntryCount());
598 EXPECT_EQ(1, controller
.GetCurrentEntryIndex());
599 EXPECT_EQ(url2b
, controller
.GetEntryAtIndex(0)->GetURL());
600 EXPECT_EQ(url3b
, controller
.GetEntryAtIndex(1)->GetURL());
603 } // namespace content