1 // Copyright 2014 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"
7 #include "base/macros.h"
8 #include "base/memory/weak_ptr.h"
9 #include "base/test/histogram_tester.h"
10 #include "base/time/time.h"
11 #include "content/browser/frame_host/navigation_controller_impl.h"
12 #include "content/browser/frame_host/navigation_entry_impl.h"
13 #include "content/browser/frame_host/navigation_request.h"
14 #include "content/browser/frame_host/navigation_request_info.h"
15 #include "content/browser/frame_host/navigator.h"
16 #include "content/browser/frame_host/navigator_impl.h"
17 #include "content/browser/frame_host/render_frame_host_manager.h"
18 #include "content/browser/loader/navigation_url_loader.h"
19 #include "content/browser/loader/navigation_url_loader_delegate.h"
20 #include "content/browser/loader/navigation_url_loader_factory.h"
21 #include "content/browser/site_instance_impl.h"
22 #include "content/browser/streams/stream.h"
23 #include "content/browser/streams/stream_registry.h"
24 #include "content/common/navigation_params.h"
25 #include "content/public/browser/stream_handle.h"
26 #include "content/public/common/content_switches.h"
27 #include "content/public/common/url_constants.h"
28 #include "content/public/common/url_utils.h"
29 #include "content/test/test_render_frame_host.h"
30 #include "content/test/test_web_contents.h"
31 #include "net/base/load_flags.h"
32 #include "net/http/http_response_headers.h"
33 #include "net/url_request/redirect_info.h"
34 #include "ui/base/page_transition_types.h"
35 #include "url/url_constants.h"
41 class TestNavigationURLLoader
42 : public NavigationURLLoader
,
43 public base::SupportsWeakPtr
<TestNavigationURLLoader
> {
45 TestNavigationURLLoader(const CommonNavigationParams
& common_params
,
46 scoped_ptr
<NavigationRequestInfo
> request_info
,
47 NavigationURLLoaderDelegate
* delegate
)
48 : common_params_(common_params
),
49 request_info_(request_info
.Pass()),
54 // NavigationURLLoader implementation.
55 void FollowRedirect() override
{ redirect_count_
++; }
57 const CommonNavigationParams
& common_params() const { return common_params_
; }
58 NavigationRequestInfo
* request_info() const { return request_info_
.get(); }
60 void CallOnRequestRedirected(
61 const net::RedirectInfo
& redirect_info
,
62 const scoped_refptr
<ResourceResponse
>& response
) {
63 delegate_
->OnRequestRedirected(redirect_info
, response
);
66 void CallOnResponseStarted(
67 const scoped_refptr
<ResourceResponse
>& response
,
68 scoped_ptr
<StreamHandle
> body
) {
69 delegate_
->OnResponseStarted(response
, body
.Pass());
72 int redirect_count() { return redirect_count_
; }
75 CommonNavigationParams common_params_
;
76 scoped_ptr
<NavigationRequestInfo
> request_info_
;
77 NavigationURLLoaderDelegate
* delegate_
;
81 class TestNavigationURLLoaderFactory
: public NavigationURLLoaderFactory
{
83 // NavigationURLLoaderFactory implementation.
84 scoped_ptr
<NavigationURLLoader
> CreateLoader(
85 BrowserContext
* browser_context
,
86 int64 frame_tree_node_id
,
87 const CommonNavigationParams
& common_params
,
88 scoped_ptr
<NavigationRequestInfo
> request_info
,
89 ResourceRequestBody
* request_body
,
90 NavigationURLLoaderDelegate
* delegate
) override
{
91 return scoped_ptr
<NavigationURLLoader
>(new TestNavigationURLLoader(
92 common_params
, request_info
.Pass(), delegate
));
98 class NavigatorTest
: public RenderViewHostImplTestHarness
{
100 NavigatorTest() : stream_registry_(new StreamRegistry
) {}
102 void SetUp() override
{
103 RenderViewHostImplTestHarness::SetUp();
104 loader_factory_
.reset(new TestNavigationURLLoaderFactory
);
105 NavigationURLLoader::SetFactoryForTesting(loader_factory_
.get());
108 void TearDown() override
{
109 NavigationURLLoader::SetFactoryForTesting(nullptr);
110 loader_factory_
.reset();
111 RenderViewHostImplTestHarness::TearDown();
114 NavigationRequest
* GetNavigationRequestForFrameTreeNode(
115 FrameTreeNode
* frame_tree_node
) const {
116 NavigatorImpl
* navigator
=
117 static_cast<NavigatorImpl
*>(frame_tree_node
->navigator());
118 return navigator
->navigation_request_map_
.get(
119 frame_tree_node
->frame_tree_node_id());
122 TestNavigationURLLoader
* GetLoaderForNavigationRequest(
123 NavigationRequest
* request
) const {
124 return static_cast<TestNavigationURLLoader
*>(request
->loader_for_testing());
127 void EnableBrowserSideNavigation() {
128 CommandLine::ForCurrentProcess()->AppendSwitch(
129 switches::kEnableBrowserSideNavigation
);
132 void SendRequestNavigation(FrameTreeNode
* node
,
134 SendRequestNavigationWithParameters(
135 node
, url
, Referrer(), ui::PAGE_TRANSITION_LINK
,
136 NavigationController::NO_RELOAD
);
139 void SendRequestNavigationWithParameters(
142 const Referrer
& referrer
,
143 ui::PageTransition transition_type
,
144 NavigationController::ReloadType reload_type
) {
145 scoped_ptr
<NavigationEntryImpl
> entry(
146 NavigationEntryImpl::FromNavigationEntry(
147 NavigationController::CreateNavigationEntry(
153 controller().GetBrowserContext())));
154 static_cast<NavigatorImpl
*>(node
->navigator())->RequestNavigation(
155 node
, *entry
, reload_type
, base::TimeTicks::Now());
158 scoped_ptr
<StreamHandle
> MakeEmptyStream() {
159 GURL
url(std::string(url::kBlobScheme
) + "://" + base::GenerateGUID());
160 scoped_refptr
<Stream
> stream(new Stream(stream_registry_
.get(), NULL
, url
));
162 return stream
->CreateHandle();
166 scoped_ptr
<StreamRegistry
> stream_registry_
;
167 scoped_ptr
<TestNavigationURLLoaderFactory
> loader_factory_
;
170 // PlzNavigate: Test that a proper NavigationRequest is created by
172 // Note that all PlzNavigate methods on the browser side require the use of the
173 // flag kEnableBrowserSideNavigation.
174 TEST_F(NavigatorTest
, BrowserSideNavigationBeginNavigation
) {
175 const GURL
kUrl1("http://www.google.com/");
176 const GURL
kUrl2("http://www.chromium.org/");
177 const GURL
kUrl3("http://www.gmail.com/");
179 contents()->NavigateAndCommit(kUrl1
);
181 EnableBrowserSideNavigation();
184 FrameTreeNode
* root
= contents()->GetFrameTree()->root();
185 TestRenderFrameHost
* subframe_rfh
= static_cast<TestRenderFrameHost
*>(
186 contents()->GetFrameTree()->AddFrame(
187 root
, root
->current_frame_host()->GetProcess()->GetID(), 14,
189 EXPECT_TRUE(subframe_rfh
);
191 FrameTreeNode
* subframe_node
= subframe_rfh
->frame_tree_node();
192 SendRequestNavigation(subframe_rfh
->frame_tree_node(), kUrl2
);
193 // There is no previous renderer in the subframe, so BeginNavigation is
195 NavigationRequest
* subframe_request
=
196 GetNavigationRequestForFrameTreeNode(subframe_node
);
197 TestNavigationURLLoader
* subframe_loader
=
198 GetLoaderForNavigationRequest(subframe_request
);
199 ASSERT_TRUE(subframe_request
);
200 EXPECT_EQ(kUrl2
, subframe_request
->common_params().url
);
201 EXPECT_EQ(kUrl2
, subframe_loader
->common_params().url
);
202 // First party for cookies url should be that of the main frame.
203 EXPECT_EQ(kUrl1
, subframe_loader
->request_info()->first_party_for_cookies
);
204 EXPECT_FALSE(subframe_loader
->request_info()->is_main_frame
);
205 EXPECT_TRUE(subframe_loader
->request_info()->parent_is_main_frame
);
207 SendRequestNavigation(root
, kUrl3
);
208 // Simulate a BeginNavigation IPC on the main frame.
209 contents()->GetMainFrame()->SendBeginNavigationWithURL(kUrl3
);
210 NavigationRequest
* main_request
= GetNavigationRequestForFrameTreeNode(root
);
211 TestNavigationURLLoader
* main_loader
=
212 GetLoaderForNavigationRequest(main_request
);
213 ASSERT_TRUE(main_request
);
214 EXPECT_EQ(kUrl3
, main_request
->common_params().url
);
215 EXPECT_EQ(kUrl3
, main_loader
->common_params().url
);
216 EXPECT_EQ(kUrl3
, main_loader
->request_info()->first_party_for_cookies
);
217 EXPECT_TRUE(main_loader
->request_info()->is_main_frame
);
218 EXPECT_FALSE(main_loader
->request_info()->parent_is_main_frame
);
221 // PlzNavigate: Test that RequestNavigation creates a NavigationRequest and that
222 // RenderFrameHost is not modified when the navigation commits.
223 TEST_F(NavigatorTest
, BrowserSideNavigationRequestNavigationNoLiveRenderer
) {
224 const GURL
kUrl("http://www.google.com/");
226 EnableBrowserSideNavigation();
227 EXPECT_FALSE(main_test_rfh()->render_view_host()->IsRenderViewLive());
228 FrameTreeNode
* node
= main_test_rfh()->frame_tree_node();
229 SendRequestNavigation(node
, kUrl
);
230 NavigationRequest
* main_request
= GetNavigationRequestForFrameTreeNode(node
);
231 // A NavigationRequest should have been generated.
232 EXPECT_TRUE(main_request
!= NULL
);
233 RenderFrameHostImpl
* rfh
= main_test_rfh();
235 // Now return the response without any redirects. This will cause the
236 // navigation to commit at the same URL.
237 scoped_refptr
<ResourceResponse
> response(new ResourceResponse
);
238 GetLoaderForNavigationRequest(main_request
)->CallOnResponseStarted(
239 response
, MakeEmptyStream());
240 main_request
= GetNavigationRequestForFrameTreeNode(node
);
242 // The main RFH should not have been changed, and the renderer should have
244 EXPECT_EQ(rfh
, main_test_rfh());
245 EXPECT_TRUE(main_test_rfh()->IsRenderFrameLive());
246 EXPECT_TRUE(main_test_rfh()->render_view_host()->IsRenderViewLive());
249 // PlzNavigate: Test that commiting an HTTP 204 or HTTP 205 response cancels the
251 TEST_F(NavigatorTest
, BrowserSideNavigationNoContent
) {
252 const GURL
kUrl1("http://www.chromium.org/");
253 const GURL
kUrl2("http://www.google.com/");
256 contents()->NavigateAndCommit(kUrl1
);
257 RenderFrameHostImpl
* rfh
= main_test_rfh();
258 EXPECT_EQ(RenderFrameHostImpl::STATE_DEFAULT
, rfh
->rfh_state());
259 FrameTreeNode
* node
= main_test_rfh()->frame_tree_node();
261 EnableBrowserSideNavigation();
263 // Navigate to a different site.
264 SendRequestNavigation(node
, kUrl2
);
265 main_test_rfh()->SendBeginNavigationWithURL(kUrl2
);
266 NavigationRequest
* main_request
= GetNavigationRequestForFrameTreeNode(node
);
267 ASSERT_TRUE(main_request
);
269 // Commit an HTTP 204 response.
270 scoped_refptr
<ResourceResponse
> response(new ResourceResponse
);
271 const char kNoContentHeaders
[] = "HTTP/1.1 204 No Content\0\0";
272 response
->head
.headers
= new net::HttpResponseHeaders(
273 std::string(kNoContentHeaders
, arraysize(kNoContentHeaders
)));
274 GetLoaderForNavigationRequest(main_request
)->CallOnResponseStarted(
275 response
, MakeEmptyStream());
277 // There should be no pending RenderFrameHost; the navigation was aborted.
278 EXPECT_FALSE(GetNavigationRequestForFrameTreeNode(node
));
279 EXPECT_FALSE(node
->render_manager()->pending_frame_host());
281 // Now, repeat the test with 205 Reset Content.
283 // Navigate to a different site again.
284 SendRequestNavigation(node
, kUrl2
);
285 main_test_rfh()->SendBeginNavigationWithURL(kUrl2
);
286 main_request
= GetNavigationRequestForFrameTreeNode(node
);
287 ASSERT_TRUE(main_request
);
289 // Commit an HTTP 205 response.
290 response
= new ResourceResponse
;
291 const char kResetContentHeaders
[] = "HTTP/1.1 205 Reset Content\0\0";
292 response
->head
.headers
= new net::HttpResponseHeaders(
293 std::string(kResetContentHeaders
, arraysize(kResetContentHeaders
)));
294 GetLoaderForNavigationRequest(main_request
)->CallOnResponseStarted(
295 response
, MakeEmptyStream());
297 // There should be no pending RenderFrameHost; the navigation was aborted.
298 EXPECT_FALSE(GetNavigationRequestForFrameTreeNode(node
));
299 EXPECT_FALSE(node
->render_manager()->pending_frame_host());
302 // PlzNavigate: Test that a new RenderFrameHost is created when doing a cross
304 TEST_F(NavigatorTest
, BrowserSideNavigationCrossSiteNavigation
) {
305 const GURL
kUrl1("http://www.chromium.org/");
306 const GURL
kUrl2("http://www.google.com/");
308 contents()->NavigateAndCommit(kUrl1
);
309 RenderFrameHostImpl
* rfh
= main_test_rfh();
310 EXPECT_EQ(RenderFrameHostImpl::STATE_DEFAULT
, rfh
->rfh_state());
311 FrameTreeNode
* node
= main_test_rfh()->frame_tree_node();
313 EnableBrowserSideNavigation();
315 // Navigate to a different site.
316 SendRequestNavigation(node
, kUrl2
);
317 main_test_rfh()->SendBeginNavigationWithURL(kUrl2
);
318 NavigationRequest
* main_request
= GetNavigationRequestForFrameTreeNode(node
);
319 ASSERT_TRUE(main_request
);
321 scoped_refptr
<ResourceResponse
> response(new ResourceResponse
);
322 GetLoaderForNavigationRequest(main_request
)->CallOnResponseStarted(
323 response
, MakeEmptyStream());
324 RenderFrameHostImpl
* pending_rfh
=
325 node
->render_manager()->pending_frame_host();
326 ASSERT_TRUE(pending_rfh
);
327 EXPECT_NE(pending_rfh
, rfh
);
328 EXPECT_TRUE(pending_rfh
->IsRenderFrameLive());
329 EXPECT_TRUE(pending_rfh
->render_view_host()->IsRenderViewLive());
332 // PlzNavigate: Test that redirects are followed.
333 TEST_F(NavigatorTest
, BrowserSideNavigationRedirectCrossSite
) {
334 const GURL
kUrl1("http://www.chromium.org/");
335 const GURL
kUrl2("http://www.google.com/");
337 contents()->NavigateAndCommit(kUrl1
);
338 RenderFrameHostImpl
* rfh
= main_test_rfh();
339 EXPECT_EQ(RenderFrameHostImpl::STATE_DEFAULT
, rfh
->rfh_state());
340 FrameTreeNode
* node
= main_test_rfh()->frame_tree_node();
342 EnableBrowserSideNavigation();
344 // Navigate to a URL on the same site.
345 SendRequestNavigation(node
, kUrl1
);
346 main_test_rfh()->SendBeginNavigationWithURL(kUrl1
);
347 NavigationRequest
* main_request
= GetNavigationRequestForFrameTreeNode(node
);
348 ASSERT_TRUE(main_request
);
350 // It then redirects to another site.
351 net::RedirectInfo redirect_info
;
352 redirect_info
.status_code
= 302;
353 redirect_info
.new_method
= "GET";
354 redirect_info
.new_url
= kUrl2
;
355 redirect_info
.new_first_party_for_cookies
= kUrl2
;
356 scoped_refptr
<ResourceResponse
> response(new ResourceResponse
);
357 GetLoaderForNavigationRequest(main_request
)->CallOnRequestRedirected(
358 redirect_info
, response
);
360 // The redirect should have been followed.
361 EXPECT_EQ(1, GetLoaderForNavigationRequest(main_request
)->redirect_count());
364 response
= new ResourceResponse
;
365 GetLoaderForNavigationRequest(main_request
)->CallOnResponseStarted(
366 response
, MakeEmptyStream());
367 RenderFrameHostImpl
* pending_rfh
=
368 node
->render_manager()->pending_frame_host();
369 ASSERT_TRUE(pending_rfh
);
370 EXPECT_NE(pending_rfh
, rfh
);
371 EXPECT_TRUE(pending_rfh
->IsRenderFrameLive());
372 EXPECT_TRUE(pending_rfh
->render_view_host()->IsRenderViewLive());
375 // PlzNavigate: Test that a navigation is cancelled if another request has been
376 // issued in the meantime.
377 TEST_F(NavigatorTest
, BrowserSideNavigationReplacePendingNavigation
) {
378 const GURL
kUrl0("http://www.wikipedia.org/");
379 const GURL kUrl0_site
= SiteInstance::GetSiteForURL(browser_context(), kUrl0
);
380 const GURL
kUrl1("http://www.chromium.org/");
381 const GURL
kUrl2("http://www.google.com/");
382 const GURL kUrl2_site
= SiteInstance::GetSiteForURL(browser_context(), kUrl2
);
385 contents()->NavigateAndCommit(kUrl0
);
386 FrameTreeNode
* node
= main_test_rfh()->frame_tree_node();
387 EnableBrowserSideNavigation();
388 EXPECT_EQ(kUrl0_site
, main_test_rfh()->GetSiteInstance()->GetSiteURL());
390 // Request navigation to the 1st URL.
391 SendRequestNavigation(node
, kUrl1
);
392 main_test_rfh()->SendBeginNavigationWithURL(kUrl1
);
393 NavigationRequest
* request1
= GetNavigationRequestForFrameTreeNode(node
);
394 ASSERT_TRUE(request1
);
395 EXPECT_EQ(kUrl1
, request1
->common_params().url
);
396 base::WeakPtr
<TestNavigationURLLoader
> loader1
=
397 GetLoaderForNavigationRequest(request1
)->AsWeakPtr();
399 // Request navigation to the 2nd URL; the NavigationRequest must have been
400 // replaced by a new one with a different URL.
401 SendRequestNavigation(node
, kUrl2
);
402 main_test_rfh()->SendBeginNavigationWithURL(kUrl2
);
403 NavigationRequest
* request2
= GetNavigationRequestForFrameTreeNode(node
);
404 ASSERT_TRUE(request2
);
405 EXPECT_EQ(kUrl2
, request2
->common_params().url
);
407 // Confirm that the first loader got destroyed.
408 EXPECT_FALSE(loader1
);
410 // Confirm that the commit corresponds to the new request.
411 scoped_refptr
<ResourceResponse
> response(new ResourceResponse
);
412 GetLoaderForNavigationRequest(request2
)->CallOnResponseStarted(
413 response
, MakeEmptyStream());
414 RenderFrameHostImpl
* pending_rfh
=
415 node
->render_manager()->pending_frame_host();
416 ASSERT_TRUE(pending_rfh
);
417 EXPECT_EQ(kUrl2_site
, pending_rfh
->GetSiteInstance()->GetSiteURL());
420 // PlzNavigate: Tests that the navigation histograms are correctly tracked both
421 // when PlzNavigate is enabled and disabled, and also ignores in-tab renderer
422 // initiated navigation for the non-enabled case.
423 // Note: the related histogram, Navigation.TimeToURLJobStart, cannot be tracked
424 // by this test as the IO thread is not running.
425 TEST_F(NavigatorTest
, BrowserSideNavigationHistogramTest
) {
426 const GURL
kUrl0("http://www.google.com/");
427 const GURL
kUrl1("http://www.chromium.org/");
428 base::HistogramTester histo_tester
;
430 // Performs a "normal" non-PlzNavigate navigation
431 contents()->NavigateAndCommit(kUrl0
);
432 histo_tester
.ExpectTotalCount("Navigation.TimeToCommit", 1);
434 // Performs an in-tab renderer initiated navigation
435 int32 new_page_id
= 1 + contents()->GetMaxPageIDForSiteInstance(
436 main_test_rfh()->GetSiteInstance());
437 main_test_rfh()->SendNavigate(new_page_id
, kUrl0
);
438 histo_tester
.ExpectTotalCount("Navigation.TimeToCommit", 1);
440 // Performs a PlzNavigate navigation
441 EnableBrowserSideNavigation();
442 contents()->NavigateAndCommit(kUrl1
);
443 histo_tester
.ExpectTotalCount("Navigation.TimeToCommit", 2);
446 // PlzNavigate: Test that a reload navigation is properly signaled to the
447 // renderer when the navigation can commit.
448 TEST_F(NavigatorTest
, BrowserSideNavigationReload
) {
449 const GURL
kUrl("http://www.google.com/");
450 contents()->NavigateAndCommit(kUrl
);
452 EnableBrowserSideNavigation();
453 FrameTreeNode
* node
= main_test_rfh()->frame_tree_node();
454 SendRequestNavigationWithParameters(
455 node
, kUrl
, Referrer(), ui::PAGE_TRANSITION_LINK
,
456 NavigationController::RELOAD
);
457 contents()->GetMainFrame()->SendBeginNavigationWithURL(kUrl
);
458 // A NavigationRequest should have been generated.
459 NavigationRequest
* main_request
=
460 GetNavigationRequestForFrameTreeNode(node
);
461 ASSERT_TRUE(main_request
!= NULL
);
462 EXPECT_EQ(FrameMsg_Navigate_Type::RELOAD
,
463 main_request
->common_params().navigation_type
);
464 int page_id
= contents()->GetMaxPageIDForSiteInstance(
465 main_test_rfh()->GetSiteInstance()) + 1;
466 main_test_rfh()->SendNavigate(page_id
, kUrl
);
468 // Now do a shift+reload.
469 SendRequestNavigationWithParameters(
470 node
, kUrl
, Referrer(), ui::PAGE_TRANSITION_LINK
,
471 NavigationController::RELOAD_IGNORING_CACHE
);
472 contents()->GetMainFrame()->SendBeginNavigationWithURL(kUrl
);
473 // A NavigationRequest should have been generated.
474 main_request
= GetNavigationRequestForFrameTreeNode(node
);
475 ASSERT_TRUE(main_request
!= NULL
);
476 EXPECT_EQ(FrameMsg_Navigate_Type::RELOAD_IGNORING_CACHE
,
477 main_request
->common_params().navigation_type
);
480 } // namespace content