password_manager_browsertest.cc revision 58537e28ecd584eab876aee8be7156509866d23a
1// Copyright 2013 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. 4 5#include <string> 6 7#include "base/command_line.h" 8#include "chrome/browser/chrome_notification_types.h" 9#include "chrome/browser/infobars/confirm_infobar_delegate.h" 10#include "chrome/browser/infobars/infobar_service.h" 11#include "chrome/browser/password_manager/password_store_factory.h" 12#include "chrome/browser/password_manager/test_password_store.h" 13#include "chrome/browser/ui/browser.h" 14#include "chrome/browser/ui/tabs/tab_strip_model.h" 15#include "chrome/test/base/in_process_browser_test.h" 16#include "chrome/test/base/test_switches.h" 17#include "chrome/test/base/ui_test_utils.h" 18#include "content/public/browser/notification_observer.h" 19#include "content/public/browser/notification_registrar.h" 20#include "content/public/browser/notification_service.h" 21#include "content/public/browser/render_view_host.h" 22#include "content/public/browser/web_contents.h" 23#include "content/public/browser/web_contents_observer.h" 24#include "content/public/test/browser_test_utils.h" 25#include "content/public/test/test_utils.h" 26#include "net/test/embedded_test_server/embedded_test_server.h" 27#include "testing/gmock/include/gmock/gmock.h" 28#include "ui/base/keycodes/keyboard_codes.h" 29 30 31// NavigationObserver --------------------------------------------------------- 32 33namespace { 34 35// Observer that waits for navigation to complete and for the password infobar 36// to be shown. 37class NavigationObserver : public content::NotificationObserver, 38 public content::WebContentsObserver { 39 public: 40 explicit NavigationObserver(content::WebContents* web_contents) 41 : content::WebContentsObserver(web_contents), 42 message_loop_runner_(new content::MessageLoopRunner), 43 infobar_shown_(false), 44 infobar_service_(InfoBarService::FromWebContents(web_contents)) { 45 registrar_.Add(this, 46 chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_ADDED, 47 content::Source<InfoBarService>(infobar_service_)); 48 } 49 50 virtual ~NavigationObserver() {} 51 52 // Normally Wait() will not return until a main frame navigation occurs. 53 // If a path is set, Wait() will return after this path has been seen, 54 // regardless of the frame that navigated. Useful for multi-frame pages. 55 void SetPathToWaitFor(const std::string& path) { 56 wait_for_path_ = path; 57 } 58 59 // content::NotificationObserver: 60 virtual void Observe(int type, 61 const content::NotificationSource& source, 62 const content::NotificationDetails& details) OVERRIDE { 63 infobar_service_->infobar_at(0)->AsConfirmInfoBarDelegate()->Accept(); 64 infobar_shown_ = true; 65 } 66 67 // content::WebContentsObserver: 68 virtual void DidFinishLoad( 69 int64 frame_id, 70 const GURL& validated_url, 71 bool is_main_frame, 72 content::RenderViewHost* render_view_host) OVERRIDE { 73 if (!wait_for_path_.empty()) { 74 if (validated_url.path() == wait_for_path_) 75 message_loop_runner_->Quit(); 76 } else if (is_main_frame) { 77 message_loop_runner_->Quit(); 78 } 79 } 80 81 bool infobar_shown() const { return infobar_shown_; } 82 83 void Wait() { 84 message_loop_runner_->Run(); 85 } 86 87 private: 88 std::string wait_for_path_; 89 scoped_refptr<content::MessageLoopRunner> message_loop_runner_; 90 bool infobar_shown_; 91 content::NotificationRegistrar registrar_; 92 InfoBarService* infobar_service_; 93 94 DISALLOW_COPY_AND_ASSIGN(NavigationObserver); 95}; 96 97} // namespace 98 99 100// PasswordManagerBrowserTest ------------------------------------------------- 101 102class PasswordManagerBrowserTest : public InProcessBrowserTest { 103 public: 104 PasswordManagerBrowserTest() {} 105 virtual ~PasswordManagerBrowserTest() {} 106 107 // InProcessBrowserTest: 108 virtual void SetUpOnMainThread() OVERRIDE { 109 // Use TestPasswordStore to remove a possible race. Normally the 110 // PasswordStore does its database manipulation on the DB thread, which 111 // creates a possible race during navigation. Specifically the 112 // PasswordManager will ignore any forms in a page if the load from the 113 // PasswordStore has not completed. 114 PasswordStoreFactory::GetInstance()->SetTestingFactory( 115 browser()->profile(), &TestPasswordStore::Create); 116 } 117 118 protected: 119 content::WebContents* WebContents() { 120 return browser()->tab_strip_model()->GetActiveWebContents(); 121 } 122 123 content::RenderViewHost* RenderViewHost() { 124 return WebContents()->GetRenderViewHost(); 125 } 126 127 // Wrapper around ui_test_utils::NavigateToURL that waits until 128 // DidFinishLoad() fires. Normally this function returns after 129 // DidStopLoading(), which caused flakiness as the NavigationObserver 130 // would sometimes see the DidFinishLoad event from a previous navigation and 131 // return immediately. 132 void NavigateToFile(const std::string& path) { 133 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); 134 135 NavigationObserver observer(WebContents()); 136 GURL url = embedded_test_server()->GetURL(path); 137 ui_test_utils::NavigateToURL(browser(), url); 138 observer.Wait(); 139 } 140 141 private: 142 DISALLOW_COPY_AND_ASSIGN(PasswordManagerBrowserTest); 143}; 144 145// Actual tests --------------------------------------------------------------- 146IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, 147 PromptForNormalSubmit) { 148 NavigateToFile("/password/password_form.html"); 149 150 // Fill a form and submit through a <input type="submit"> button. Nothing 151 // special. 152 NavigationObserver observer(WebContents()); 153 std::string fill_and_submit = 154 "document.getElementById('username_field').value = 'temp';" 155 "document.getElementById('password_field').value = 'random';" 156 "document.getElementById('input_submit_button').click()"; 157 ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit)); 158 observer.Wait(); 159 EXPECT_TRUE(observer.infobar_shown()); 160} 161 162IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, 163 PromptForSubmitUsingJavaScript) { 164 NavigateToFile("/password/password_form.html"); 165 166 // Fill a form and submit using <button> that calls submit() on the form. 167 // This should work regardless of the type of element, as long as submit() is 168 // called. 169 NavigationObserver observer(WebContents()); 170 std::string fill_and_submit = 171 "document.getElementById('username_field').value = 'temp';" 172 "document.getElementById('password_field').value = 'random';" 173 "document.getElementById('submit_button').click()"; 174 ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit)); 175 observer.Wait(); 176 EXPECT_TRUE(observer.infobar_shown()); 177} 178 179IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, NoPromptForNavigation) { 180 NavigateToFile("/password/password_form.html"); 181 182 // Don't fill the password form, just navigate away. Shouldn't prompt. 183 NavigationObserver observer(WebContents()); 184 ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), 185 "window.location.href = 'done.html';")); 186 observer.Wait(); 187 EXPECT_FALSE(observer.infobar_shown()); 188} 189 190IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, 191 NoPromptForSubFrameNavigation) { 192 NavigateToFile("/password/multi_frames.html"); 193 194 // If you are filling out a password form in one frame and a different frame 195 // navigates, this should not trigger the infobar. 196 NavigationObserver observer(WebContents()); 197 observer.SetPathToWaitFor("/password/done.html"); 198 std::string fill = 199 "var first_frame = document.getElementById('first_frame');" 200 "var frame_doc = first_frame.contentDocument;" 201 "frame_doc.getElementById('username_field').value = 'temp';" 202 "frame_doc.getElementById('password_field').value = 'random';"; 203 std::string navigate_frame = 204 "var second_iframe = document.getElementById('second_frame');" 205 "second_iframe.contentWindow.location.href = 'done.html';"; 206 207 ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill)); 208 ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), navigate_frame)); 209 observer.Wait(); 210 EXPECT_FALSE(observer.infobar_shown()); 211} 212 213IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, 214 PromptAfterSubmitWithSubFrameNavigation) { 215 NavigateToFile("/password/multi_frames.html"); 216 217 // Make sure that we prompt to save password even if a sub-frame navigation 218 // happens first. 219 NavigationObserver observer(WebContents()); 220 observer.SetPathToWaitFor("/password/done.html"); 221 std::string navigate_frame = 222 "var second_iframe = document.getElementById('second_frame');" 223 "second_iframe.contentWindow.location.href = 'other.html';"; 224 std::string fill_and_submit = 225 "var first_frame = document.getElementById('first_frame');" 226 "var frame_doc = first_frame.contentDocument;" 227 "frame_doc.getElementById('username_field').value = 'temp';" 228 "frame_doc.getElementById('password_field').value = 'random';" 229 "frame_doc.getElementById('input_submit_button').click();"; 230 231 ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), navigate_frame)); 232 ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit)); 233 observer.Wait(); 234 EXPECT_TRUE(observer.infobar_shown()); 235} 236 237IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, 238 PromptForXHRSubmit) { 239#if defined(OS_WIN) && defined(USE_ASH) 240 // Disable this test in Metro+Ash for now (http://crbug.com/262796). 241 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests)) 242 return; 243#endif 244 NavigateToFile("/password/password_xhr_submit.html"); 245 246 // Verify that we show the save password prompt if a form returns false 247 // in its onsubmit handler but instead logs in/navigates via XHR. 248 // Note that calling 'submit()' on a form with javascript doesn't call 249 // the onsubmit handler, so we click the submit button instead. 250 NavigationObserver observer(WebContents()); 251 std::string fill_and_submit = 252 "document.getElementById('username_field').value = 'temp';" 253 "document.getElementById('password_field').value = 'random';" 254 "document.getElementById('submit_button').click()"; 255 ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit)); 256 observer.Wait(); 257 EXPECT_TRUE(observer.infobar_shown()); 258} 259 260IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, NoPromptForOtherXHR) { 261 NavigateToFile("/password/password_xhr_submit.html"); 262 263 // Verify that if random XHR navigation occurs, we don't try and save the 264 // password. 265 // 266 // We may want to change this functionality in the future to account for 267 // cases where the element that users click on isn't a submit button. 268 NavigationObserver observer(WebContents()); 269 std::string fill_and_navigate = 270 "document.getElementById('username_field').value = 'temp';" 271 "document.getElementById('password_field').value = 'random';" 272 "send_xhr()"; 273 ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_navigate)); 274 observer.Wait(); 275 EXPECT_FALSE(observer.infobar_shown()); 276} 277