| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/bind.h" |
| #include "base/strings/stringprintf.h" |
| #include "content/browser/frame_host/frame_tree.h" |
| #include "content/browser/frame_host/navigation_controller_impl.h" |
| #include "content/browser/frame_host/navigation_entry_impl.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_observer.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/content_browser_test.h" |
| #include "content/public/test/content_browser_test_utils.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "content/public/test/test_utils.h" |
| #include "content/shell/browser/shell.h" |
| #include "content/test/content_browser_test_utils_internal.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| |
| namespace content { |
| |
| class NavigationControllerBrowserTest : public ContentBrowserTest { |
| protected: |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, LoadDataWithBaseURL) { |
| const GURL base_url("http://baseurl"); |
| const GURL history_url("http://historyurl"); |
| const std::string data = "<html><body>foo</body></html>"; |
| |
| const NavigationController& controller = |
| shell()->web_contents()->GetController(); |
| // Load data. Blocks until it is done. |
| content::LoadDataWithBaseURL(shell(), history_url, data, base_url); |
| |
| // We should use history_url instead of the base_url as the original url of |
| // this navigation entry, because base_url is only used for resolving relative |
| // paths in the data, or enforcing same origin policy. |
| EXPECT_EQ(controller.GetVisibleEntry()->GetOriginalRequestURL(), history_url); |
| } |
| |
| // The renderer uses the position in the history list as a clue to whether a |
| // navigation is stale. In the case where the entry limit is reached and the |
| // history list is pruned, make sure that there is no mismatch that would cause |
| // it to start incorrectly rejecting navigations as stale. See |
| // http://crbug.com/89798. |
| IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, |
| DontIgnoreBackAfterNavEntryLimit) { |
| NavigationController& controller = |
| shell()->web_contents()->GetController(); |
| |
| const int kMaxEntryCount = |
| static_cast<int>(NavigationControllerImpl::max_entry_count()); |
| |
| // Load up to the max count, all entries should be there. |
| for (int url_index = 0; url_index < kMaxEntryCount; ++url_index) { |
| GURL url(base::StringPrintf("data:text/html,page%d", url_index)); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| } |
| |
| EXPECT_EQ(controller.GetEntryCount(), kMaxEntryCount); |
| |
| // Navigate twice more more. |
| for (int url_index = kMaxEntryCount; |
| url_index < kMaxEntryCount + 2; ++url_index) { |
| GURL url(base::StringPrintf("data:text/html,page%d", url_index)); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| } |
| |
| // We expect http://www.a.com/0 and /1 to be gone. |
| EXPECT_EQ(kMaxEntryCount, controller.GetEntryCount()); |
| EXPECT_EQ(GURL("data:text/html,page2"), |
| controller.GetEntryAtIndex(0)->GetURL()); |
| |
| // Now try to go back. This should not hang. |
| ASSERT_TRUE(controller.CanGoBack()); |
| controller.GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| |
| // This should have successfully gone back. |
| EXPECT_EQ(GURL(base::StringPrintf("data:text/html,page%d", kMaxEntryCount)), |
| controller.GetLastCommittedEntry()->GetURL()); |
| } |
| |
| namespace { |
| |
| int RendererHistoryLength(Shell* shell) { |
| int value = 0; |
| EXPECT_TRUE(ExecuteScriptAndExtractInt( |
| shell->web_contents(), |
| "domAutomationController.send(history.length)", |
| &value)); |
| return value; |
| } |
| |
| // Similar to the ones from content_browser_test_utils. |
| bool NavigateToURLAndReplace(Shell* shell, const GURL& url) { |
| WebContents* web_contents = shell->web_contents(); |
| WaitForLoadStop(web_contents); |
| TestNavigationObserver same_tab_observer(web_contents, 1); |
| NavigationController::LoadURLParams params(url); |
| params.should_replace_current_entry = true; |
| web_contents->GetController().LoadURLWithParams(params); |
| web_contents->Focus(); |
| same_tab_observer.Wait(); |
| if (!IsLastCommittedEntryOfPageType(web_contents, PAGE_TYPE_NORMAL)) |
| return false; |
| return web_contents->GetLastCommittedURL() == url; |
| } |
| |
| } // namespace |
| |
| // When loading a new page to replace an old page in the history list, make sure |
| // that the browser and renderer agree, and that both get it right. |
| IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, |
| CorrectLengthWithCurrentItemReplacement) { |
| NavigationController& controller = |
| shell()->web_contents()->GetController(); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), GURL("data:text/html,page1"))); |
| EXPECT_EQ(1, controller.GetEntryCount()); |
| EXPECT_EQ(1, RendererHistoryLength(shell())); |
| |
| EXPECT_TRUE(NavigateToURLAndReplace(shell(), GURL("data:text/html,page2"))); |
| EXPECT_EQ(1, controller.GetEntryCount()); |
| EXPECT_EQ(1, RendererHistoryLength(shell())); |
| |
| // Note that there's no way to access the renderer's notion of the history |
| // offset via JavaScript. Checking just the history length, though, is enough; |
| // if the replacement failed, there would be a new history entry and thus an |
| // incorrect length. |
| } |
| |
| namespace { |
| |
| struct FrameNavigateParamsCapturer : public WebContentsObserver { |
| public: |
| explicit FrameNavigateParamsCapturer(FrameTreeNode* node) |
| : WebContentsObserver( |
| node->current_frame_host()->delegate()->GetAsWebContents()), |
| frame_tree_node_id_(node->frame_tree_node_id()), |
| message_loop_runner_(new MessageLoopRunner) {} |
| |
| void Wait() { |
| message_loop_runner_->Run(); |
| } |
| |
| const FrameNavigateParams& params() const { |
| return params_; |
| } |
| |
| const LoadCommittedDetails& details() const { |
| return details_; |
| } |
| |
| private: |
| void DidNavigateAnyFrame(RenderFrameHost* render_frame_host, |
| const LoadCommittedDetails& details, |
| const FrameNavigateParams& params) override { |
| RenderFrameHostImpl* rfh = |
| static_cast<RenderFrameHostImpl*>(render_frame_host); |
| if (rfh->frame_tree_node()->frame_tree_node_id() != frame_tree_node_id_) |
| return; |
| |
| params_ = params; |
| details_ = details; |
| message_loop_runner_->Quit(); |
| } |
| |
| // The id of the FrameTreeNode whose navigations to observe. |
| int frame_tree_node_id_; |
| |
| // The params of the last navigation. |
| FrameNavigateParams params_; |
| |
| // The details of the last navigation. |
| LoadCommittedDetails details_; |
| |
| // The MessageLoopRunner used to spin the message loop. |
| scoped_refptr<MessageLoopRunner> message_loop_runner_; |
| }; |
| |
| } // namespace |
| |
| // Verify that the distinction between manual and auto subframes is properly set |
| // for subframe navigations. TODO(avi): It's rather bogus that the same info is |
| // in two different enums; http://crbug.com/453555. |
| IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, |
| ManualAndAutoSubframeNavigationClassification) { |
| GURL main_url( |
| embedded_test_server()->GetURL("/frame_tree/page_with_one_frame.html")); |
| NavigateToURL(shell(), main_url); |
| |
| // It is safe to obtain the root frame tree node here, as it doesn't change. |
| FrameTreeNode* root = |
| static_cast<WebContentsImpl*>(shell()->web_contents())-> |
| GetFrameTree()->root(); |
| |
| ASSERT_EQ(1U, root->child_count()); |
| ASSERT_NE(nullptr, root->child_at(0)); |
| |
| { |
| // Navigate the iframe to a new URL; expect a manual subframe transition. |
| FrameNavigateParamsCapturer capturer(root->child_at(0)); |
| GURL frame_url( |
| embedded_test_server()->GetURL("/frame_tree/2-1.html")); |
| NavigateFrameToURL(root->child_at(0), frame_url); |
| capturer.Wait(); |
| EXPECT_EQ(ui::PAGE_TRANSITION_MANUAL_SUBFRAME, |
| capturer.params().transition); |
| EXPECT_EQ(NAVIGATION_TYPE_NEW_SUBFRAME, capturer.details().type); |
| } |
| |
| { |
| // History navigations should result in an auto subframe transition. |
| FrameNavigateParamsCapturer capturer(root->child_at(0)); |
| shell()->web_contents()->GetController().GoBack(); |
| capturer.Wait(); |
| EXPECT_EQ(ui::PAGE_TRANSITION_AUTO_SUBFRAME, capturer.params().transition); |
| EXPECT_EQ(NAVIGATION_TYPE_AUTO_SUBFRAME, capturer.details().type); |
| } |
| |
| { |
| // History navigations should result in an auto subframe transition. |
| FrameNavigateParamsCapturer capturer(root->child_at(0)); |
| shell()->web_contents()->GetController().GoForward(); |
| capturer.Wait(); |
| EXPECT_EQ(ui::PAGE_TRANSITION_AUTO_SUBFRAME, capturer.params().transition); |
| EXPECT_EQ(NAVIGATION_TYPE_AUTO_SUBFRAME, capturer.details().type); |
| } |
| |
| { |
| // Navigate the iframe to a new URL; expect a manual subframe transition. |
| FrameNavigateParamsCapturer capturer(root->child_at(0)); |
| GURL frame_url( |
| embedded_test_server()->GetURL("/frame_tree/2-3.html")); |
| NavigateFrameToURL(root->child_at(0), frame_url); |
| capturer.Wait(); |
| EXPECT_EQ(ui::PAGE_TRANSITION_MANUAL_SUBFRAME, |
| capturer.params().transition); |
| EXPECT_EQ(NAVIGATION_TYPE_NEW_SUBFRAME, capturer.details().type); |
| } |
| } |
| |
| } // namespace content |