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.
6 #include "base/strings/stringprintf.h"
7 #include "content/browser/frame_host/frame_tree.h"
8 #include "content/browser/frame_host/navigation_controller_impl.h"
9 #include "content/browser/frame_host/navigation_entry_impl.h"
10 #include "content/browser/web_contents/web_contents_impl.h"
11 #include "content/public/browser/render_view_host.h"
12 #include "content/public/browser/web_contents.h"
13 #include "content/public/browser/web_contents_observer.h"
14 #include "content/public/common/bindings_policy.h"
15 #include "content/public/common/url_constants.h"
16 #include "content/public/test/browser_test_utils.h"
17 #include "content/public/test/content_browser_test.h"
18 #include "content/public/test/content_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/test/content_browser_test_utils_internal.h"
23 #include "net/dns/mock_host_resolver.h"
24 #include "net/test/embedded_test_server/embedded_test_server.h"
28 class NavigationControllerBrowserTest
: public ContentBrowserTest
{
30 void SetUpOnMainThread() override
{
31 host_resolver()->AddRule("*", "127.0.0.1");
32 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
36 IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest
, LoadDataWithBaseURL
) {
37 const GURL
base_url("http://baseurl");
38 const GURL
history_url("http://historyurl");
39 const std::string data
= "<html><body>foo</body></html>";
41 const NavigationController
& controller
=
42 shell()->web_contents()->GetController();
43 // Load data. Blocks until it is done.
44 content::LoadDataWithBaseURL(shell(), history_url
, data
, base_url
);
46 // We should use history_url instead of the base_url as the original url of
47 // this navigation entry, because base_url is only used for resolving relative
48 // paths in the data, or enforcing same origin policy.
49 EXPECT_EQ(controller
.GetVisibleEntry()->GetOriginalRequestURL(), history_url
);
52 // The renderer uses the position in the history list as a clue to whether a
53 // navigation is stale. In the case where the entry limit is reached and the
54 // history list is pruned, make sure that there is no mismatch that would cause
55 // it to start incorrectly rejecting navigations as stale. See
56 // http://crbug.com/89798.
57 IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest
,
58 DontIgnoreBackAfterNavEntryLimit
) {
59 NavigationController
& controller
=
60 shell()->web_contents()->GetController();
62 const int kMaxEntryCount
=
63 static_cast<int>(NavigationControllerImpl::max_entry_count());
65 // Load up to the max count, all entries should be there.
66 for (int url_index
= 0; url_index
< kMaxEntryCount
; ++url_index
) {
67 GURL
url(base::StringPrintf("data:text/html,page%d", url_index
));
68 EXPECT_TRUE(NavigateToURL(shell(), url
));
71 EXPECT_EQ(controller
.GetEntryCount(), kMaxEntryCount
);
73 // Navigate twice more more.
74 for (int url_index
= kMaxEntryCount
;
75 url_index
< kMaxEntryCount
+ 2; ++url_index
) {
76 GURL
url(base::StringPrintf("data:text/html,page%d", url_index
));
77 EXPECT_TRUE(NavigateToURL(shell(), url
));
80 // We expect page0 and page1 to be gone.
81 EXPECT_EQ(kMaxEntryCount
, controller
.GetEntryCount());
82 EXPECT_EQ(GURL("data:text/html,page2"),
83 controller
.GetEntryAtIndex(0)->GetURL());
85 // Now try to go back. This should not hang.
86 ASSERT_TRUE(controller
.CanGoBack());
88 EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
90 // This should have successfully gone back.
91 EXPECT_EQ(GURL(base::StringPrintf("data:text/html,page%d", kMaxEntryCount
)),
92 controller
.GetLastCommittedEntry()->GetURL());
97 int RendererHistoryLength(Shell
* shell
) {
99 EXPECT_TRUE(ExecuteScriptAndExtractInt(
100 shell
->web_contents(),
101 "domAutomationController.send(history.length)",
106 // Similar to the ones from content_browser_test_utils.
107 bool NavigateToURLAndReplace(Shell
* shell
, const GURL
& url
) {
108 WebContents
* web_contents
= shell
->web_contents();
109 WaitForLoadStop(web_contents
);
110 TestNavigationObserver
same_tab_observer(web_contents
, 1);
111 NavigationController::LoadURLParams
params(url
);
112 params
.should_replace_current_entry
= true;
113 web_contents
->GetController().LoadURLWithParams(params
);
114 web_contents
->Focus();
115 same_tab_observer
.Wait();
116 if (!IsLastCommittedEntryOfPageType(web_contents
, PAGE_TYPE_NORMAL
))
118 return web_contents
->GetLastCommittedURL() == url
;
123 // When loading a new page to replace an old page in the history list, make sure
124 // that the browser and renderer agree, and that both get it right.
125 IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest
,
126 CorrectLengthWithCurrentItemReplacement
) {
127 NavigationController
& controller
=
128 shell()->web_contents()->GetController();
130 EXPECT_TRUE(NavigateToURL(shell(), GURL("data:text/html,page1")));
131 EXPECT_EQ(1, controller
.GetEntryCount());
132 EXPECT_EQ(1, RendererHistoryLength(shell()));
134 EXPECT_TRUE(NavigateToURLAndReplace(shell(), GURL("data:text/html,page2")));
135 EXPECT_EQ(1, controller
.GetEntryCount());
136 EXPECT_EQ(1, RendererHistoryLength(shell()));
138 // Note that there's no way to access the renderer's notion of the history
139 // offset via JavaScript. Checking just the history length, though, is enough;
140 // if the replacement failed, there would be a new history entry and thus an
144 // When spawning a new page from a WebUI page, make sure that the browser and
145 // renderer agree about the length of the history list, and that both get it
147 IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest
,
148 CorrectLengthWithNewTabNavigatingFromWebUI
) {
149 GURL
web_ui_page(std::string(kChromeUIScheme
) + "://" +
150 std::string(kChromeUIGpuHost
));
151 EXPECT_TRUE(NavigateToURL(shell(), web_ui_page
));
152 EXPECT_EQ(BINDINGS_POLICY_WEB_UI
,
153 shell()->web_contents()->GetRenderViewHost()->GetEnabledBindings());
155 ShellAddedObserver observer
;
156 std::string page_url
= embedded_test_server()->GetURL(
157 "/navigation_controller/simple_page_1.html").spec();
158 EXPECT_TRUE(ExecuteScript(shell()->web_contents(),
159 "window.open('" + page_url
+ "', '_blank')"));
160 Shell
* shell2
= observer
.GetShell();
161 WaitForLoadStop(shell2
->web_contents());
163 EXPECT_EQ(1, shell2
->web_contents()->GetController().GetEntryCount());
164 EXPECT_EQ(1, RendererHistoryLength(shell2
));
166 // Again, as above, there's no way to access the renderer's notion of the
167 // history offset via JavaScript. Checking just the history length, again,
168 // will have to suffice.
173 struct FrameNavigateParamsCapturer
: public WebContentsObserver
{
175 // Observes navigation for the specified |node|.
176 explicit FrameNavigateParamsCapturer(FrameTreeNode
* node
)
177 : WebContentsObserver(
178 node
->current_frame_host()->delegate()->GetAsWebContents()),
179 frame_tree_node_id_(node
->frame_tree_node_id()),
180 message_loop_runner_(new MessageLoopRunner
) {}
183 message_loop_runner_
->Run();
186 const FrameNavigateParams
& params() const {
190 const LoadCommittedDetails
& details() const {
195 void DidNavigateAnyFrame(RenderFrameHost
* render_frame_host
,
196 const LoadCommittedDetails
& details
,
197 const FrameNavigateParams
& params
) override
{
198 RenderFrameHostImpl
* rfh
=
199 static_cast<RenderFrameHostImpl
*>(render_frame_host
);
200 if (rfh
->frame_tree_node()->frame_tree_node_id() != frame_tree_node_id_
)
205 message_loop_runner_
->Quit();
208 // The id of the FrameTreeNode whose navigations to observe.
209 int frame_tree_node_id_
;
211 // The params of the last navigation.
212 FrameNavigateParams params_
;
214 // The details of the last navigation.
215 LoadCommittedDetails details_
;
217 // The MessageLoopRunner used to spin the message loop.
218 scoped_refptr
<MessageLoopRunner
> message_loop_runner_
;
221 struct LoadCommittedCapturer
: public WebContentsObserver
{
223 // Observes the load commit for the specified |node|.
224 explicit LoadCommittedCapturer(FrameTreeNode
* node
)
225 : WebContentsObserver(
226 node
->current_frame_host()->delegate()->GetAsWebContents()),
227 frame_tree_node_id_(node
->frame_tree_node_id()),
228 message_loop_runner_(new MessageLoopRunner
) {}
230 // Observes the load commit for the next created frame in the specified
232 explicit LoadCommittedCapturer(WebContents
* web_contents
)
233 : WebContentsObserver(web_contents
),
234 frame_tree_node_id_(0),
235 message_loop_runner_(new MessageLoopRunner
) {}
238 message_loop_runner_
->Run();
241 ui::PageTransition
transition_type() const {
242 return transition_type_
;
246 void RenderFrameCreated(RenderFrameHost
* render_frame_host
) override
{
247 // If this object was created with a specified tree frame node, there
248 // shouldn't be any frames being created.
249 DCHECK_EQ(0, frame_tree_node_id_
);
250 RenderFrameHostImpl
* rfh
=
251 static_cast<RenderFrameHostImpl
*>(render_frame_host
);
252 frame_tree_node_id_
= rfh
->frame_tree_node()->frame_tree_node_id();
255 void DidCommitProvisionalLoadForFrame(
256 RenderFrameHost
* render_frame_host
,
258 ui::PageTransition transition_type
) override
{
259 DCHECK_NE(0, frame_tree_node_id_
);
260 RenderFrameHostImpl
* rfh
=
261 static_cast<RenderFrameHostImpl
*>(render_frame_host
);
262 if (rfh
->frame_tree_node()->frame_tree_node_id() != frame_tree_node_id_
)
265 transition_type_
= transition_type
;
266 message_loop_runner_
->Quit();
269 // The id of the FrameTreeNode whose navigations to observe.
270 int frame_tree_node_id_
;
272 // The transition_type of the last navigation.
273 ui::PageTransition transition_type_
;
275 // The MessageLoopRunner used to spin the message loop.
276 scoped_refptr
<MessageLoopRunner
> message_loop_runner_
;
281 // Verify that the distinction between manual and auto subframes is properly set
282 // for subframe navigations. TODO(avi): It's rather bogus that the same info is
283 // in two different enums; http://crbug.com/453555.
284 IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest
,
285 ManualAndAutoSubframeNavigationClassification
) {
286 GURL
main_url(embedded_test_server()->GetURL(
287 "/navigation_controller/page_with_iframe.html"));
288 NavigateToURL(shell(), main_url
);
290 // It is safe to obtain the root frame tree node here, as it doesn't change.
291 FrameTreeNode
* root
=
292 static_cast<WebContentsImpl
*>(shell()->web_contents())->
293 GetFrameTree()->root();
295 ASSERT_EQ(1U, root
->child_count());
296 ASSERT_NE(nullptr, root
->child_at(0));
299 // Navigate the iframe to a new URL; expect a manual subframe transition.
300 FrameNavigateParamsCapturer
capturer(root
->child_at(0));
301 GURL
frame_url(embedded_test_server()->GetURL(
302 "/navigation_controller/simple_page_1.html"));
303 NavigateFrameToURL(root
->child_at(0), frame_url
);
305 EXPECT_EQ(ui::PAGE_TRANSITION_MANUAL_SUBFRAME
,
306 capturer
.params().transition
);
307 EXPECT_EQ(NAVIGATION_TYPE_NEW_SUBFRAME
, capturer
.details().type
);
311 // Do a history navigation; expect an auto subframe transition.
312 FrameNavigateParamsCapturer
capturer(root
->child_at(0));
313 shell()->web_contents()->GetController().GoBack();
315 EXPECT_EQ(ui::PAGE_TRANSITION_AUTO_SUBFRAME
, capturer
.params().transition
);
316 EXPECT_EQ(NAVIGATION_TYPE_AUTO_SUBFRAME
, capturer
.details().type
);
320 // Do a history navigation; expect an auto subframe transition.
321 FrameNavigateParamsCapturer
capturer(root
->child_at(0));
322 shell()->web_contents()->GetController().GoForward();
324 EXPECT_EQ(ui::PAGE_TRANSITION_AUTO_SUBFRAME
, capturer
.params().transition
);
325 EXPECT_EQ(NAVIGATION_TYPE_AUTO_SUBFRAME
, capturer
.details().type
);
329 // Navigate the iframe to a new URL; expect a manual subframe transition.
330 FrameNavigateParamsCapturer
capturer(root
->child_at(0));
331 GURL
frame_url(embedded_test_server()->GetURL(
332 "/navigation_controller/simple_page_2.html"));
333 NavigateFrameToURL(root
->child_at(0), frame_url
);
335 EXPECT_EQ(ui::PAGE_TRANSITION_MANUAL_SUBFRAME
,
336 capturer
.params().transition
);
337 EXPECT_EQ(NAVIGATION_TYPE_NEW_SUBFRAME
, capturer
.details().type
);
341 // Use location.assign(); expect a manual subframe transition.
342 FrameNavigateParamsCapturer
capturer(root
->child_at(0));
343 GURL
frame_url(embedded_test_server()->GetURL(
344 "/navigation_controller/simple_page_1.html"));
345 std::string script
= "location.assign('" + frame_url
.spec() + "')";
346 EXPECT_TRUE(content::ExecuteScript(root
->child_at(0)->current_frame_host(),
349 EXPECT_EQ(ui::PAGE_TRANSITION_MANUAL_SUBFRAME
,
350 capturer
.params().transition
);
351 EXPECT_EQ(NAVIGATION_TYPE_NEW_SUBFRAME
, capturer
.details().type
);
355 // Use location.replace(); expect an auto subframe transition. (Replacements
356 // aren't "navigation" so we only see the frame load committing.)
357 LoadCommittedCapturer
capturer(root
->child_at(0));
358 GURL
frame_url(embedded_test_server()->GetURL(
359 "/navigation_controller/simple_page_2.html"));
360 std::string script
= "location.replace('" + frame_url
.spec() + "')";
361 EXPECT_TRUE(content::ExecuteScript(root
->child_at(0)->current_frame_host(),
364 EXPECT_EQ(ui::PAGE_TRANSITION_AUTO_SUBFRAME
, capturer
.transition_type());
368 // Reload the subframe; expect an auto subframe transition. (Reloads aren't
369 // "navigation" so we only see the frame load committing.)
370 LoadCommittedCapturer
capturer(root
->child_at(0));
371 EXPECT_TRUE(content::ExecuteScript(root
->child_at(0)->current_frame_host(),
372 "location.reload()"));
374 EXPECT_EQ(ui::PAGE_TRANSITION_AUTO_SUBFRAME
, capturer
.transition_type());
378 // Create an iframe; expect an auto subframe transition. (Initial frame
379 // creation isn't "navigation" so we only see the frame load committing.)
380 LoadCommittedCapturer
capturer(shell()->web_contents());
381 GURL
frame_url(embedded_test_server()->GetURL(
382 "/navigation_controller/simple_page_1.html"));
383 std::string script
= "var iframe = document.createElement('iframe');"
384 "iframe.src = '" + frame_url
.spec() + "';"
385 "document.body.appendChild(iframe);";
386 EXPECT_TRUE(content::ExecuteScript(root
->current_frame_host(), script
));
388 EXPECT_EQ(ui::PAGE_TRANSITION_AUTO_SUBFRAME
, capturer
.transition_type());
392 } // namespace content