// Copyright 2013 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 #include "base/command_line.h" #include "base/metrics/histogram_samples.h" #include "base/metrics/statistics_recorder.h" #include "base/strings/stringprintf.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/infobars/infobar_service.h" #include "chrome/browser/password_manager/password_store_factory.h" #include "chrome/browser/password_manager/test_password_store_service.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/common/chrome_version_info.h" #include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/test_switches.h" #include "chrome/test/base/ui_test_utils.h" #include "components/autofill/core/browser/autofill_test_utils.h" #include "components/infobars/core/confirm_infobar_delegate.h" #include "components/infobars/core/infobar.h" #include "components/infobars/core/infobar_manager.h" #include "components/password_manager/core/browser/test_password_store.h" #include "components/password_manager/core/common/password_manager_switches.h" #include "content/public/browser/render_view_host.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/test_utils.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "net/url_request/test_url_fetcher_factory.h" #include "testing/gmock/include/gmock/gmock.h" #include "third_party/WebKit/public/web/WebInputEvent.h" #include "ui/events/keycodes/keyboard_codes.h" #include "ui/gfx/geometry/point.h" // NavigationObserver --------------------------------------------------------- namespace { // Observer that waits for navigation to complete and for the password infobar // to be shown. class NavigationObserver : public content::WebContentsObserver, public infobars::InfoBarManager::Observer { public: explicit NavigationObserver(content::WebContents* web_contents) : content::WebContentsObserver(web_contents), message_loop_runner_(new content::MessageLoopRunner), infobar_shown_(false), infobar_removed_(false), should_automatically_accept_infobar_(true), infobar_service_(InfoBarService::FromWebContents(web_contents)) { infobar_service_->AddObserver(this); } virtual ~NavigationObserver() { if (infobar_service_) infobar_service_->RemoveObserver(this); } // Normally Wait() will not return until a main frame navigation occurs. // If a path is set, Wait() will return after this path has been seen, // regardless of the frame that navigated. Useful for multi-frame pages. void SetPathToWaitFor(const std::string& path) { wait_for_path_ = path; } // content::WebContentsObserver: virtual void DidFinishLoad( int64 frame_id, const GURL& validated_url, bool is_main_frame, content::RenderViewHost* render_view_host) OVERRIDE { if (!wait_for_path_.empty()) { if (validated_url.path() == wait_for_path_) message_loop_runner_->Quit(); } else if (is_main_frame) { message_loop_runner_->Quit(); } } bool infobar_shown() const { return infobar_shown_; } bool infobar_removed() const { return infobar_removed_; } void disable_should_automatically_accept_infobar() { should_automatically_accept_infobar_ = false; } void Wait() { message_loop_runner_->Run(); } private: // infobars::InfoBarManager::Observer: virtual void OnInfoBarAdded(infobars::InfoBar* infobar) OVERRIDE { if (should_automatically_accept_infobar_) { infobar_service_->infobar_at(0)->delegate()-> AsConfirmInfoBarDelegate()->Accept(); } infobar_shown_ = true; } virtual void OnInfoBarRemoved(infobars::InfoBar* infobar, bool animate) OVERRIDE { infobar_removed_ = true; } virtual void OnManagerShuttingDown( infobars::InfoBarManager* manager) OVERRIDE { ASSERT_EQ(infobar_service_, manager); infobar_service_->RemoveObserver(this); infobar_service_ = NULL; } std::string wait_for_path_; scoped_refptr message_loop_runner_; bool infobar_shown_; bool infobar_removed_; // If |should_automatically_accept_infobar_| is true, then whenever the test // sees an infobar added, it will click its accepting button. Default = true. bool should_automatically_accept_infobar_; InfoBarService* infobar_service_; DISALLOW_COPY_AND_ASSIGN(NavigationObserver); }; } // namespace // PasswordManagerBrowserTest ------------------------------------------------- class PasswordManagerBrowserTest : public InProcessBrowserTest { public: PasswordManagerBrowserTest() {} virtual ~PasswordManagerBrowserTest() {} // InProcessBrowserTest: virtual void SetUpOnMainThread() OVERRIDE { // Use TestPasswordStore to remove a possible race. Normally the // PasswordStore does its database manipulation on the DB thread, which // creates a possible race during navigation. Specifically the // PasswordManager will ignore any forms in a page if the load from the // PasswordStore has not completed. PasswordStoreFactory::GetInstance()->SetTestingFactory( browser()->profile(), TestPasswordStoreService::Build); } protected: content::WebContents* WebContents() { return browser()->tab_strip_model()->GetActiveWebContents(); } content::RenderViewHost* RenderViewHost() { return WebContents()->GetRenderViewHost(); } // Wrapper around ui_test_utils::NavigateToURL that waits until // DidFinishLoad() fires. Normally this function returns after // DidStopLoading(), which caused flakiness as the NavigationObserver // would sometimes see the DidFinishLoad event from a previous navigation and // return immediately. void NavigateToFile(const std::string& path) { if (!embedded_test_server()->Started()) ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); ASSERT_FALSE(CommandLine::ForCurrentProcess()->HasSwitch( password_manager::switches::kEnableAutomaticPasswordSaving)); NavigationObserver observer(WebContents()); GURL url = embedded_test_server()->GetURL(path); ui_test_utils::NavigateToURL(browser(), url); observer.Wait(); } // Waits until the "value" attribute of the HTML element with |element_id| is // equal to |expected_value|. If the current value is not as expected, this // waits until the "change" event is fired for the element. This also // guarantees that once the real value matches the expected, the JavaScript // event loop is spun to allow all other possible events to take place. void WaitForElementValue(const std::string& element_id, const std::string& expected_value); // Checks that the current "value" attribute of the HTML element with // |element_id| is equal to |expected_value|. void CheckElementValue(const std::string& element_id, const std::string& expected_value); private: DISALLOW_COPY_AND_ASSIGN(PasswordManagerBrowserTest); }; void PasswordManagerBrowserTest::WaitForElementValue( const std::string& element_id, const std::string& expected_value) { enum ReturnCodes { // Possible results of the JavaScript code. RETURN_CODE_OK, RETURN_CODE_NO_ELEMENT, RETURN_CODE_WRONG_VALUE, RETURN_CODE_INVALID, }; const std::string value_check_function = base::StringPrintf( "function valueCheck() {" " var element = document.getElementById('%s');" " return element && element.value == '%s';" "}", element_id.c_str(), expected_value.c_str()); const std::string script = value_check_function + base::StringPrintf( "if (valueCheck()) {" " /* Spin the event loop with setTimeout. */" " setTimeout(window.domAutomationController.send(%d), 0);" "} else {" " var element = document.getElementById('%s');" " if (!element)" " window.domAutomationController.send(%d);" " element.onchange = function() {" " if (valueCheck()) {" " /* Spin the event loop with setTimeout. */" " setTimeout(window.domAutomationController.send(%d), 0);" " } else {" " window.domAutomationController.send(%d);" " }" " };" "}", RETURN_CODE_OK, element_id.c_str(), RETURN_CODE_NO_ELEMENT, RETURN_CODE_OK, RETURN_CODE_WRONG_VALUE); int return_value = RETURN_CODE_INVALID; ASSERT_TRUE(content::ExecuteScriptAndExtractInt( RenderViewHost(), script, &return_value)); EXPECT_EQ(RETURN_CODE_OK, return_value) << "element_id = " << element_id << ", expected_value = " << expected_value; } void PasswordManagerBrowserTest::CheckElementValue( const std::string& element_id, const std::string& expected_value) { const std::string value_check_script = base::StringPrintf( "var element = document.getElementById('%s');" "window.domAutomationController.send(element && element.value == '%s');", element_id.c_str(), expected_value.c_str()); bool return_value = false; ASSERT_TRUE(content::ExecuteScriptAndExtractBool( RenderViewHost(), value_check_script, &return_value)); EXPECT_TRUE(return_value) << "element_id = " << element_id << ", expected_value = " << expected_value; } // Actual tests --------------------------------------------------------------- IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, PromptForNormalSubmit) { NavigateToFile("/password/password_form.html"); // Fill a form and submit through a button. Nothing // special. NavigationObserver observer(WebContents()); std::string fill_and_submit = "document.getElementById('username_field').value = 'temp';" "document.getElementById('password_field').value = 'random';" "document.getElementById('input_submit_button').click()"; ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit)); observer.Wait(); EXPECT_TRUE(observer.infobar_shown()); } IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, PromptForSubmitWithInPageNavigation) { NavigateToFile("/password/password_navigate_before_submit.html"); // Fill a form and submit through a button. Nothing // special. The form does an in-page navigation before submitting. NavigationObserver observer(WebContents()); std::string fill_and_submit = "document.getElementById('username_field').value = 'temp';" "document.getElementById('password_field').value = 'random';" "document.getElementById('input_submit_button').click()"; ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit)); observer.Wait(); EXPECT_TRUE(observer.infobar_shown()); } IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, LoginSuccessWithUnrelatedForm) { // Log in, see a form on the landing page. That form is not related to the // login form (=has a different action), so we should offer saving the // password. NavigateToFile("/password/password_form.html"); NavigationObserver observer(WebContents()); std::string fill_and_submit = "document.getElementById('username_unrelated').value = 'temp';" "document.getElementById('password_unrelated').value = 'random';" "document.getElementById('submit_unrelated').click()"; ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit)); observer.Wait(); EXPECT_TRUE(observer.infobar_shown()); } IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, LoginFailed) { // Log in, see a form on the landing page. That form is not related to the // login form (=has a different action), so we should offer saving the // password. NavigateToFile("/password/password_form.html"); NavigationObserver observer(WebContents()); std::string fill_and_submit = "document.getElementById('username_failed').value = 'temp';" "document.getElementById('password_failed').value = 'random';" "document.getElementById('submit_failed').click()"; ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit)); observer.Wait(); EXPECT_FALSE(observer.infobar_shown()); } IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, Redirects) { NavigateToFile("/password/password_form.html"); // Fill a form and submit through a button. The form // points to a redirection page. NavigationObserver observer(WebContents()); std::string fill_and_submit = "document.getElementById('username_redirect').value = 'temp';" "document.getElementById('password_redirect').value = 'random';" "document.getElementById('submit_redirect').click()"; ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit)); observer.disable_should_automatically_accept_infobar(); observer.Wait(); EXPECT_TRUE(observer.infobar_shown()); // The redirection page now redirects via Javascript. We check that the // infobar stays. ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), "window.location.href = 'done.html';")); observer.Wait(); EXPECT_FALSE(observer.infobar_removed()); } IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, PromptForSubmitUsingJavaScript) { NavigateToFile("/password/password_form.html"); // Fill a form and submit using