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