// Copyright (c) 2012 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/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "content/public/browser/navigation_controller.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_types.h" #include "content/public/browser/web_contents.h" #include "content/public/common/url_constants.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_utils.h" #include "content/shell/browser/shell.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "net/test/embedded_test_server/http_request.h" #include "net/test/embedded_test_server/http_response.h" #include "testing/gtest/include/gtest/gtest.h" namespace content { namespace { // Handles |request| by serving a response with title set to request contents. scoped_ptr<net::test_server::HttpResponse> HandleEchoTitleRequest( const std::string& echotitle_path, const net::test_server::HttpRequest& request) { if (!StartsWithASCII(request.relative_url, echotitle_path, true)) return scoped_ptr<net::test_server::HttpResponse>(); scoped_ptr<net::test_server::BasicHttpResponse> http_response( new net::test_server::BasicHttpResponse); http_response->set_code(net::HTTP_OK); http_response->set_content( base::StringPrintf( "<html><head><title>%s</title></head></html>", request.content.c_str())); return http_response.PassAs<net::test_server::HttpResponse>(); } } // namespace class SessionHistoryTest : public ContentBrowserTest { protected: SessionHistoryTest() {} virtual void SetUpOnMainThread() OVERRIDE { ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); embedded_test_server()->RegisterRequestHandler( base::Bind(&HandleEchoTitleRequest, "/echotitle")); NavigateToURL(shell(), GURL(url::kAboutBlankURL)); } // Simulate clicking a link. Only works on the frames.html testserver page. void ClickLink(std::string node_id) { GURL url("javascript:clickLink('" + node_id + "')"); NavigateToURL(shell(), url); } // Simulate filling in form data. Only works on the frames.html page with // subframe = form.html, and on form.html itself. void FillForm(std::string node_id, std::string value) { GURL url("javascript:fillForm('" + node_id + "', '" + value + "')"); // This will return immediately, but since the JS executes synchronously // on the renderer, it will complete before the next navigate message is // processed. NavigateToURL(shell(), url); } // Simulate submitting a form. Only works on the frames.html page with // subframe = form.html, and on form.html itself. void SubmitForm(std::string node_id) { GURL url("javascript:submitForm('" + node_id + "')"); NavigateToURL(shell(), url); } // Navigate session history using history.go(distance). void JavascriptGo(std::string distance) { GURL url("javascript:history.go('" + distance + "')"); NavigateToURL(shell(), url); } std::string GetTabTitle() { return base::UTF16ToASCII(shell()->web_contents()->GetTitle()); } GURL GetTabURL() { return shell()->web_contents()->GetLastCommittedURL(); } GURL GetURL(const std::string file) { return embedded_test_server()->GetURL( std::string("/session_history/") + file); } void NavigateAndCheckTitle(const char* filename, const std::string& expected_title) { base::string16 expected_title16(base::ASCIIToUTF16(expected_title)); TitleWatcher title_watcher(shell()->web_contents(), expected_title16); NavigateToURL(shell(), GetURL(filename)); ASSERT_EQ(expected_title16, title_watcher.WaitAndGetTitle()); } bool CanGoBack() { return shell()->web_contents()->GetController().CanGoBack(); } bool CanGoForward() { return shell()->web_contents()->GetController().CanGoForward(); } void GoBack() { WindowedNotificationObserver load_stop_observer( NOTIFICATION_LOAD_STOP, NotificationService::AllSources()); shell()->web_contents()->GetController().GoBack(); load_stop_observer.Wait(); } void GoForward() { WindowedNotificationObserver load_stop_observer( NOTIFICATION_LOAD_STOP, NotificationService::AllSources()); shell()->web_contents()->GetController().GoForward(); load_stop_observer.Wait(); } }; // If this flakes, use http://crbug.com/61619 on windows and // http://crbug.com/102094 on mac. IN_PROC_BROWSER_TEST_F(SessionHistoryTest, BasicBackForward) { ASSERT_FALSE(CanGoBack()); ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("bot1.html", "bot1")); ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("bot2.html", "bot2")); ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("bot3.html", "bot3")); // history is [blank, bot1, bot2, *bot3] GoBack(); EXPECT_EQ("bot2", GetTabTitle()); GoBack(); EXPECT_EQ("bot1", GetTabTitle()); GoForward(); EXPECT_EQ("bot2", GetTabTitle()); GoBack(); EXPECT_EQ("bot1", GetTabTitle()); ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("bot3.html", "bot3")); // history is [blank, bot1, *bot3] ASSERT_FALSE(CanGoForward()); EXPECT_EQ("bot3", GetTabTitle()); GoBack(); EXPECT_EQ("bot1", GetTabTitle()); GoBack(); EXPECT_EQ(std::string(url::kAboutBlankURL), GetTabTitle()); ASSERT_FALSE(CanGoBack()); EXPECT_EQ(std::string(url::kAboutBlankURL), GetTabTitle()); GoForward(); EXPECT_EQ("bot1", GetTabTitle()); GoForward(); EXPECT_EQ("bot3", GetTabTitle()); } // Test that back/forward works when navigating in subframes. // If this flakes, use http://crbug.com/48833 IN_PROC_BROWSER_TEST_F(SessionHistoryTest, FrameBackForward) { ASSERT_FALSE(CanGoBack()); ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("frames.html", "bot1")); ClickLink("abot2"); EXPECT_EQ("bot2", GetTabTitle()); GURL frames(GetURL("frames.html")); EXPECT_EQ(frames, GetTabURL()); ClickLink("abot3"); EXPECT_EQ("bot3", GetTabTitle()); EXPECT_EQ(frames, GetTabURL()); // history is [blank, bot1, bot2, *bot3] GoBack(); EXPECT_EQ("bot2", GetTabTitle()); EXPECT_EQ(frames, GetTabURL()); GoBack(); EXPECT_EQ("bot1", GetTabTitle()); EXPECT_EQ(frames, GetTabURL()); GoBack(); EXPECT_EQ(std::string(url::kAboutBlankURL), GetTabTitle()); EXPECT_EQ(GURL(url::kAboutBlankURL), GetTabURL()); GoForward(); EXPECT_EQ("bot1", GetTabTitle()); EXPECT_EQ(frames, GetTabURL()); GoForward(); EXPECT_EQ("bot2", GetTabTitle()); EXPECT_EQ(frames, GetTabURL()); ClickLink("abot1"); EXPECT_EQ("bot1", GetTabTitle()); EXPECT_EQ(frames, GetTabURL()); // history is [blank, bot1, bot2, *bot1] ASSERT_FALSE(CanGoForward()); EXPECT_EQ("bot1", GetTabTitle()); EXPECT_EQ(frames, GetTabURL()); GoBack(); EXPECT_EQ("bot2", GetTabTitle()); EXPECT_EQ(frames, GetTabURL()); GoBack(); EXPECT_EQ("bot1", GetTabTitle()); EXPECT_EQ(frames, GetTabURL()); } // Test that back/forward preserves POST data and document state in subframes. // If this flakes use http://crbug.com/61619 IN_PROC_BROWSER_TEST_F(SessionHistoryTest, FrameFormBackForward) { ASSERT_FALSE(CanGoBack()); ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("frames.html", "bot1")); ClickLink("aform"); EXPECT_EQ("form", GetTabTitle()); GURL frames(GetURL("frames.html")); EXPECT_EQ(frames, GetTabURL()); SubmitForm("isubmit"); EXPECT_EQ("text=&select=a", GetTabTitle()); EXPECT_EQ(frames, GetTabURL()); GoBack(); EXPECT_EQ("form", GetTabTitle()); EXPECT_EQ(frames, GetTabURL()); // history is [blank, bot1, *form, post] ClickLink("abot2"); EXPECT_EQ("bot2", GetTabTitle()); EXPECT_EQ(frames, GetTabURL()); // history is [blank, bot1, form, *bot2] GoBack(); EXPECT_EQ("form", GetTabTitle()); EXPECT_EQ(frames, GetTabURL()); SubmitForm("isubmit"); EXPECT_EQ("text=&select=a", GetTabTitle()); EXPECT_EQ(frames, GetTabURL()); // history is [blank, bot1, form, *post] // TODO(mpcomplete): reenable this when WebKit bug 10199 is fixed: // "returning to a POST result within a frame does a GET instead of a POST" ClickLink("abot2"); EXPECT_EQ("bot2", GetTabTitle()); EXPECT_EQ(frames, GetTabURL()); GoBack(); EXPECT_EQ("text=&select=a", GetTabTitle()); EXPECT_EQ(frames, GetTabURL()); } // TODO(mpcomplete): enable this when Bug 734372 is fixed: // "Doing a session history navigation does not restore newly-created subframe // document state" // Test that back/forward preserves POST data and document state when navigating // across frames (ie, from frame -> nonframe). // Hangs, see http://crbug.com/45058. IN_PROC_BROWSER_TEST_F(SessionHistoryTest, CrossFrameFormBackForward) { ASSERT_FALSE(CanGoBack()); GURL frames(GetURL("frames.html")); ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("frames.html", "bot1")); ClickLink("aform"); EXPECT_EQ("form", GetTabTitle()); EXPECT_EQ(frames, GetTabURL()); SubmitForm("isubmit"); EXPECT_EQ("text=&select=a", GetTabTitle()); EXPECT_EQ(frames, GetTabURL()); GoBack(); EXPECT_EQ("form", GetTabTitle()); EXPECT_EQ(frames, GetTabURL()); // history is [blank, bot1, *form, post] ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("bot2.html", "bot2")); // history is [blank, bot1, form, *bot2] GoBack(); EXPECT_EQ("bot1", GetTabTitle()); EXPECT_EQ(frames, GetTabURL()); SubmitForm("isubmit"); EXPECT_EQ("text=&select=a", GetTabTitle()); EXPECT_EQ(frames, GetTabURL()); } // Test that back/forward entries are created for reference fragment // navigations. Bug 730379. // If this flakes use http://crbug.com/61619. IN_PROC_BROWSER_TEST_F(SessionHistoryTest, FragmentBackForward) { embedded_test_server()->RegisterRequestHandler( base::Bind(&HandleEchoTitleRequest, "/echotitle")); ASSERT_FALSE(CanGoBack()); GURL fragment(GetURL("fragment.html")); ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("fragment.html", "fragment")); ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("fragment.html#a", "fragment")); ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("fragment.html#b", "fragment")); ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("fragment.html#c", "fragment")); // history is [blank, fragment, fragment#a, fragment#b, *fragment#c] GoBack(); EXPECT_EQ(GetURL("fragment.html#b"), GetTabURL()); GoBack(); EXPECT_EQ(GetURL("fragment.html#a"), GetTabURL()); GoBack(); EXPECT_EQ(GetURL("fragment.html"), GetTabURL()); GoForward(); EXPECT_EQ(GetURL("fragment.html#a"), GetTabURL()); ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("bot3.html", "bot3")); // history is [blank, fragment, fragment#a, bot3] ASSERT_FALSE(CanGoForward()); EXPECT_EQ(GetURL("bot3.html"), GetTabURL()); GoBack(); EXPECT_EQ(GetURL("fragment.html#a"), GetTabURL()); GoBack(); EXPECT_EQ(GetURL("fragment.html"), GetTabURL()); } // Test that the javascript window.history object works. // NOTE: history.go(N) does not do anything if N is outside the bounds of the // back/forward list (such as trigger our start/stop loading events). This // means the test will hang if it attempts to navigate too far forward or back, // since we'll be waiting forever for a load stop event. // // TODO(brettw) bug 50648: fix flakyness. This test seems like it was failing // about 1/4 of the time on Vista by failing to execute JavascriptGo (see bug). IN_PROC_BROWSER_TEST_F(SessionHistoryTest, JavascriptHistory) { ASSERT_FALSE(CanGoBack()); ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("bot1.html", "bot1")); ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("bot2.html", "bot2")); ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("bot3.html", "bot3")); // history is [blank, bot1, bot2, *bot3] JavascriptGo("-1"); EXPECT_EQ("bot2", GetTabTitle()); JavascriptGo("-1"); EXPECT_EQ("bot1", GetTabTitle()); JavascriptGo("1"); EXPECT_EQ("bot2", GetTabTitle()); JavascriptGo("-1"); EXPECT_EQ("bot1", GetTabTitle()); JavascriptGo("2"); EXPECT_EQ("bot3", GetTabTitle()); // history is [blank, bot1, bot2, *bot3] JavascriptGo("-3"); EXPECT_EQ(std::string(url::kAboutBlankURL), GetTabTitle()); ASSERT_FALSE(CanGoBack()); EXPECT_EQ(std::string(url::kAboutBlankURL), GetTabTitle()); JavascriptGo("1"); EXPECT_EQ("bot1", GetTabTitle()); ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("bot3.html", "bot3")); // history is [blank, bot1, *bot3] ASSERT_FALSE(CanGoForward()); EXPECT_EQ("bot3", GetTabTitle()); JavascriptGo("-1"); EXPECT_EQ("bot1", GetTabTitle()); JavascriptGo("-1"); EXPECT_EQ(std::string(url::kAboutBlankURL), GetTabTitle()); ASSERT_FALSE(CanGoBack()); EXPECT_EQ(std::string(url::kAboutBlankURL), GetTabTitle()); JavascriptGo("1"); EXPECT_EQ("bot1", GetTabTitle()); JavascriptGo("1"); EXPECT_EQ("bot3", GetTabTitle()); // TODO(creis): Test that JavaScript history navigations work across tab // types. For example, load about:network in a tab, then a real page, then // try to go back and forward with JavaScript. Bug 1136715. // (Hard to test right now, because pages like about:network cause the // TabProxy to hang. This is because they do not appear to use the // NotificationService.) } // This test is failing consistently. See http://crbug.com/22560 IN_PROC_BROWSER_TEST_F(SessionHistoryTest, LocationReplace) { // Test that using location.replace doesn't leave the title of the old page // visible. ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle( "replace.html?bot1.html", "bot1")); } IN_PROC_BROWSER_TEST_F(SessionHistoryTest, LocationChangeInSubframe) { ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle( "location_redirect.html", "Default Title")); NavigateToURL(shell(), GURL("javascript:void(frames[0].navigate())")); EXPECT_EQ("foo", GetTabTitle()); GoBack(); EXPECT_EQ("Default Title", GetTabTitle()); } // http://code.google.com/p/chromium/issues/detail?id=56267 IN_PROC_BROWSER_TEST_F(SessionHistoryTest, HistoryLength) { int length; ASSERT_TRUE(ExecuteScriptAndExtractInt( shell()->web_contents(), "domAutomationController.send(history.length)", &length)); EXPECT_EQ(1, length); NavigateToURL(shell(), GetURL("title1.html")); ASSERT_TRUE(ExecuteScriptAndExtractInt( shell()->web_contents(), "domAutomationController.send(history.length)", &length)); EXPECT_EQ(2, length); // Now test that history.length is updated when the navigation is committed. NavigateToURL(shell(), GetURL("record_length.html")); ASSERT_TRUE(ExecuteScriptAndExtractInt( shell()->web_contents(), "domAutomationController.send(history.length)", &length)); EXPECT_EQ(3, length); GoBack(); GoBack(); // Ensure history.length is properly truncated. NavigateToURL(shell(), GetURL("title2.html")); ASSERT_TRUE(ExecuteScriptAndExtractInt( shell()->web_contents(), "domAutomationController.send(history.length)", &length)); EXPECT_EQ(2, length); } } // namespace content