// 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 "chrome/browser/chrome_notification_types.h" #include "chrome/browser/infobars/confirm_infobar_delegate.h" #include "chrome/browser/infobars/infobar.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.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/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 "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" #include "content/public/browser/notification_service.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 "ui/events/keycodes/keyboard_codes.h" // NavigationObserver --------------------------------------------------------- namespace { // Observer that waits for navigation to complete and for the password infobar // to be shown. class NavigationObserver : public content::NotificationObserver, public content::WebContentsObserver { 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)) { registrar_.Add(this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_ADDED, content::Source(infobar_service_)); registrar_.Add(this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED, content::Source(infobar_service_)); } virtual ~NavigationObserver() {} // 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::NotificationObserver: virtual void Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) OVERRIDE { switch (type) { case chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_ADDED: if (should_automatically_accept_infobar_) { infobar_service_->infobar_at(0) ->delegate() ->AsConfirmInfoBarDelegate() ->Accept(); } infobar_shown_ = true; return; case chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED: infobar_removed_ = true; return; default: NOTREACHED(); return; } } // 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: 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_; content::NotificationRegistrar registrar_; 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()); NavigationObserver observer(WebContents()); GURL url = embedded_test_server()->GetURL(path); ui_test_utils::NavigateToURL(browser(), url); observer.Wait(); } private: DISALLOW_COPY_AND_ASSIGN(PasswordManagerBrowserTest); }; // 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