1 // Copyright 2014 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 "base/command_line.h"
6 #include "base/run_loop.h"
7 #include "base/strings/stringprintf.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "base/test/metrics/histogram_tester.h"
10 #include "build/build_config.h"
11 #include "chrome/browser/password_manager/chrome_password_manager_client.h"
12 #include "chrome/browser/password_manager/password_manager_interactive_test_base.h"
13 #include "chrome/browser/password_manager/password_store_factory.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/ui/browser.h"
16 #include "chrome/browser/ui/passwords/password_generation_popup_observer.h"
17 #include "chrome/browser/ui/tabs/tab_strip_model.h"
18 #include "chrome/test/base/in_process_browser_test.h"
19 #include "chrome/test/base/ui_test_utils.h"
20 #include "components/autofill/core/browser/autofill_test_utils.h"
21 #include "components/autofill/core/common/autofill_features.h"
22 #include "components/password_manager/core/browser/password_form_manager.h"
23 #include "components/password_manager/core/browser/password_generation_frame_helper.h"
24 #include "components/password_manager/core/browser/password_manager_util.h"
25 #include "components/password_manager/core/browser/test_password_store.h"
26 #include "content/public/browser/render_view_host.h"
27 #include "content/public/browser/render_widget_host.h"
28 #include "content/public/browser/web_contents.h"
29 #include "content/public/test/browser_test.h"
30 #include "content/public/test/browser_test_utils.h"
31 #include "net/test/embedded_test_server/embedded_test_server.h"
32 #include "testing/gtest/include/gtest/gtest.h"
33 #include "ui/events/base_event_utils.h"
34 #include "ui/events/keycodes/keyboard_codes.h"
35 
36 namespace {
37 
38 class TestPopupObserver : public PasswordGenerationPopupObserver {
39  public:
40   enum class GenerationPopup {
41     kShown,
42     kHidden,
43   };
44 
45   TestPopupObserver() = default;
46   ~TestPopupObserver() = default;
47 
OnPopupShown(PasswordGenerationPopupController::GenerationUIState state)48   void OnPopupShown(
49       PasswordGenerationPopupController::GenerationUIState state) override {
50     popup_showing_ = GenerationPopup::kShown;
51     state_ = state;
52     MaybeQuitRunLoop();
53   }
54 
OnPopupHidden()55   void OnPopupHidden() override {
56     popup_showing_ = GenerationPopup::kHidden;
57     MaybeQuitRunLoop();
58   }
59 
popup_showing() const60   bool popup_showing() const {
61     return popup_showing_ == GenerationPopup::kShown;
62   }
state() const63   PasswordGenerationPopupController::GenerationUIState state() const {
64     return state_;
65   }
66 
67   // Waits until the popup is in specified status.
WaitForStatus(GenerationPopup status)68   void WaitForStatus(GenerationPopup status) {
69     if (status == popup_showing_)
70       return;
71     base::RunLoop run_loop;
72     run_loop_ = &run_loop;
73     run_loop_->Run();
74     EXPECT_EQ(popup_showing_, status);
75   }
76 
77   // Waits until the popup is either shown or hidden.
WaitForStatusChange()78   void WaitForStatusChange() {
79     base::RunLoop run_loop;
80     run_loop_ = &run_loop;
81     run_loop_->Run();
82   }
83 
84  private:
MaybeQuitRunLoop()85   void MaybeQuitRunLoop() {
86     if (run_loop_) {
87       run_loop_->Quit();
88       run_loop_ = nullptr;
89     }
90   }
91 
92   // The loop to be stopped after the popup state change.
93   base::RunLoop* run_loop_ = nullptr;
94   GenerationPopup popup_showing_ = GenerationPopup::kHidden;
95   PasswordGenerationPopupController::GenerationUIState state_ =
96       PasswordGenerationPopupController::kOfferGeneration;
97 
98   DISALLOW_COPY_AND_ASSIGN(TestPopupObserver);
99 };
100 
101 enum ReturnCodes {  // Possible results of the JavaScript code.
102   RETURN_CODE_OK,
103   RETURN_CODE_NO_ELEMENT,
104   RETURN_CODE_INVALID,
105 };
106 
107 }  // namespace
108 
109 class PasswordGenerationInteractiveTest
110     : public PasswordManagerInteractiveTestBase {
111  public:
SetUpOnMainThread()112   void SetUpOnMainThread() override {
113     PasswordManagerBrowserTestBase::SetUpOnMainThread();
114     // Disable Autofill requesting access to AddressBook data. This will cause
115     // the tests to hang on Mac.
116     autofill::test::DisableSystemServices(browser()->profile()->GetPrefs());
117 
118     // Set observer for popup.
119     ChromePasswordManagerClient* client =
120         ChromePasswordManagerClient::FromWebContents(WebContents());
121     client->SetTestObserver(&observer_);
122     // The base class should enable password generation.
123     ASSERT_TRUE(client->GetPasswordFeatureManager()->IsGenerationEnabled());
124     password_manager::PasswordFormManager::
125         set_wait_for_server_predictions_for_filling(false);
126 
127     NavigateToFile("/password/signup_form_new_password.html");
128   }
129 
TearDownOnMainThread()130   void TearDownOnMainThread() override {
131     PasswordManagerBrowserTestBase::TearDownOnMainThread();
132 
133     autofill::test::ReenableSystemServices();
134   }
135 
136   // Waits until the value of the field with id |field_id| becomes non-empty.
WaitForNonEmptyFieldValue(const std::string & field_id)137   void WaitForNonEmptyFieldValue(const std::string& field_id) {
138     const std::string script = base::StringPrintf(
139         "element = document.getElementById('%s');"
140         "if (!element) {"
141         "  setTimeout(window.domAutomationController.send(%d), 0);"
142         "}"
143         "if (element.value) {"
144         "  setTimeout(window.domAutomationController.send(%d), 0); "
145         "} else {"
146         "  element.onchange = function() {"
147         "    if (element.value) {"
148         "      window.domAutomationController.send(%d);"
149         "    }"
150         "  }"
151         "}",
152         field_id.c_str(), RETURN_CODE_NO_ELEMENT, RETURN_CODE_OK,
153         RETURN_CODE_OK);
154     int return_value = RETURN_CODE_INVALID;
155     ASSERT_TRUE(content::ExecuteScriptWithoutUserGestureAndExtractInt(
156         RenderFrameHost(), script, &return_value));
157     EXPECT_EQ(RETURN_CODE_OK, return_value);
158   }
159 
GetFocusedElement()160   std::string GetFocusedElement() {
161     std::string focused_element;
162     EXPECT_TRUE(content::ExecuteScriptAndExtractString(
163         WebContents(),
164         "window.domAutomationController.send("
165         "    document.activeElement.id)",
166         &focused_element));
167     return focused_element;
168   }
169 
FocusPasswordField()170   void FocusPasswordField() {
171     ASSERT_TRUE(content::ExecuteScript(
172         WebContents(), "document.getElementById('password_field').focus()"));
173   }
174 
FocusUsernameField()175   void FocusUsernameField() {
176     ASSERT_TRUE(content::ExecuteScript(
177         WebContents(), "document.getElementById('username_field').focus();"));
178   }
179 
SendKeyToPopup(ui::KeyboardCode key)180   void SendKeyToPopup(ui::KeyboardCode key) {
181     content::NativeWebKeyboardEvent event(
182         blink::WebKeyboardEvent::Type::kRawKeyDown,
183         blink::WebInputEvent::kNoModifiers,
184         blink::WebInputEvent::GetStaticTimeStampForTests());
185     event.windows_key_code = key;
186     WebContents()
187         ->GetMainFrame()
188         ->GetRenderViewHost()
189         ->GetWidget()
190         ->ForwardKeyboardEvent(event);
191   }
192 
GenerationPopupShowing()193   bool GenerationPopupShowing() {
194     return observer_.popup_showing() &&
195            observer_.state() ==
196                PasswordGenerationPopupController::kOfferGeneration;
197   }
198 
EditingPopupShowing()199   bool EditingPopupShowing() {
200     return observer_.popup_showing() &&
201            observer_.state() ==
202                PasswordGenerationPopupController::kEditGeneratedPassword;
203   }
204 
WaitForStatus(TestPopupObserver::GenerationPopup status)205   void WaitForStatus(TestPopupObserver::GenerationPopup status) {
206     observer_.WaitForStatus(status);
207   }
208 
WaitForPopupStatusChange()209   void WaitForPopupStatusChange() { observer_.WaitForStatusChange(); }
210 
211  private:
212   TestPopupObserver observer_;
213 };
214 
IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,PopupShownAndPasswordSelected)215 IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,
216                        PopupShownAndPasswordSelected) {
217   FocusPasswordField();
218   EXPECT_TRUE(GenerationPopupShowing());
219   base::HistogramTester histogram_tester;
220   SendKeyToPopup(ui::VKEY_DOWN);
221   SendKeyToPopup(ui::VKEY_RETURN);
222 
223   // Selecting the password should fill the field and move focus to the
224   // submit button.
225   WaitForNonEmptyFieldValue("password_field");
226   EXPECT_FALSE(GenerationPopupShowing());
227   EXPECT_FALSE(EditingPopupShowing());
228   EXPECT_EQ("input_submit_button", GetFocusedElement());
229 
230   // Re-focusing the password field should show the editing popup.
231   FocusPasswordField();
232   EXPECT_TRUE(EditingPopupShowing());
233 
234   // The metrics are recorded when the form manager is destroyed. Closing the
235   // tab enforces it.
236   CloseAllBrowsers();
237   histogram_tester.ExpectUniqueSample(
238       "PasswordGeneration.UserDecision",
239       password_manager::PasswordFormMetricsRecorder::GeneratedPasswordStatus::
240           kPasswordAccepted,
241       1);
242 }
243 
IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,PopupShownAutomaticallyAndPasswordErased)244 IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,
245                        PopupShownAutomaticallyAndPasswordErased) {
246   FocusPasswordField();
247   EXPECT_TRUE(GenerationPopupShowing());
248   SendKeyToPopup(ui::VKEY_DOWN);
249   SendKeyToPopup(ui::VKEY_RETURN);
250 
251   // Wait until the password is filled.
252   WaitForNonEmptyFieldValue("password_field");
253 
254   // Re-focusing the password field should show the editing popup.
255   FocusPasswordField();
256   EXPECT_TRUE(EditingPopupShowing());
257 
258   // Delete the password. The generation prompt should be visible.
259   base::HistogramTester histogram_tester;
260   SimulateUserDeletingFieldContent("password_field");
261   WaitForPopupStatusChange();
262   EXPECT_FALSE(EditingPopupShowing());
263   EXPECT_TRUE(GenerationPopupShowing());
264 
265   // The metrics are recorded on navigation when the frame is destroyed.
266   NavigateToFile("/password/done.html");
267   histogram_tester.ExpectUniqueSample(
268       "PasswordGeneration.UserDecision",
269       password_manager::PasswordFormMetricsRecorder::GeneratedPasswordStatus::
270           kPasswordDeleted,
271       1);
272 }
273 
IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,PopupShownManuallyAndPasswordErased)274 IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,
275                        PopupShownManuallyAndPasswordErased) {
276   NavigateToFile("/password/password_form.html");
277   FocusPasswordField();
278   EXPECT_FALSE(GenerationPopupShowing());
279   // The same flow happens when user generates a password from the context menu.
280   password_manager_util::UserTriggeredManualGenerationFromContextMenu(
281       ChromePasswordManagerClient::FromWebContents(WebContents()));
282   WaitForStatus(TestPopupObserver::GenerationPopup::kShown);
283   EXPECT_TRUE(GenerationPopupShowing());
284   SendKeyToPopup(ui::VKEY_DOWN);
285   SendKeyToPopup(ui::VKEY_RETURN);
286 
287   // Wait until the password is filled.
288   WaitForNonEmptyFieldValue("password_field");
289 
290   // Re-focusing the password field should show the editing popup.
291   FocusPasswordField();
292   EXPECT_TRUE(EditingPopupShowing());
293 
294   // Delete the password. The generation prompt should not be visible.
295   SimulateUserDeletingFieldContent("password_field");
296   WaitForStatus(TestPopupObserver::GenerationPopup::kHidden);
297   EXPECT_FALSE(EditingPopupShowing());
298   EXPECT_FALSE(GenerationPopupShowing());
299 }
300 
IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,DISABLED_PopupShownAndDismissed)301 IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,
302                        DISABLED_PopupShownAndDismissed) {
303   FocusPasswordField();
304   EXPECT_TRUE(GenerationPopupShowing());
305 
306   FocusUsernameField();
307 
308   // Popup is dismissed.
309   WaitForStatus(TestPopupObserver::GenerationPopup::kHidden);
310 }
311 
IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,PopupShownAndDismissedByKeyPress)312 IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,
313                        PopupShownAndDismissedByKeyPress) {
314   FocusPasswordField();
315   EXPECT_TRUE(GenerationPopupShowing());
316 
317   SendKeyToPopup(ui::VKEY_ESCAPE);
318 
319   // Popup is dismissed.
320   EXPECT_FALSE(GenerationPopupShowing());
321 }
322 
IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,PopupShownAndDismissedByScrolling)323 IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,
324                        PopupShownAndDismissedByScrolling) {
325   FocusPasswordField();
326   EXPECT_TRUE(GenerationPopupShowing());
327 
328   ASSERT_TRUE(
329       content::ExecuteScript(WebContents(), "window.scrollTo(100, 0);"));
330 
331   EXPECT_FALSE(GenerationPopupShowing());
332 }
333 
IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,GenerationTriggeredInIFrame)334 IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,
335                        GenerationTriggeredInIFrame) {
336   NavigateToFile("/password/framed_signup_form.html");
337 
338   // Execute the script in the context of the iframe so that it kinda receives a
339   // user gesture.
340   std::vector<content::RenderFrameHost*> frames = WebContents()->GetAllFrames();
341   ASSERT_EQ(2u, frames.size());
342   ASSERT_TRUE(frames[0] == RenderFrameHost());
343 
344   std::string focus_script =
345       "document.getElementById('password_field').focus();";
346 
347   ASSERT_TRUE(content::ExecuteScript(frames[1], focus_script));
348   EXPECT_TRUE(GenerationPopupShowing());
349 }
350 
IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,GenerationTriggeredOnTap)351 IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,
352                        GenerationTriggeredOnTap) {
353   // Tap in the middle of the field.
354   ASSERT_TRUE(content::ExecuteScriptWithoutUserGesture(
355       RenderFrameHost(),
356       "var submitRect = document.getElementById('password_field')"
357       ".getBoundingClientRect();"));
358   double y;
359   ASSERT_TRUE(content::ExecuteScriptWithoutUserGestureAndExtractDouble(
360       RenderFrameHost(),
361       "window.domAutomationController.send((submitRect.top +"
362       "submitRect.bottom) / 2);",
363       &y));
364   double x;
365   EXPECT_TRUE(content::ExecuteScriptWithoutUserGestureAndExtractDouble(
366       RenderFrameHost(),
367       "window.domAutomationController.send((submitRect.left + submitRect.right)"
368       "/ 2);",
369       &x));
370   content::SimulateTapAt(WebContents(),
371                          gfx::Point(static_cast<int>(x), static_cast<int>(y)));
372   WaitForStatus(TestPopupObserver::GenerationPopup::kShown);
373 }
374 
IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,GenerationTriggeredOnClick)375 IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,
376                        GenerationTriggeredOnClick) {
377   // Tap in the middle of the field.
378   ASSERT_TRUE(content::ExecuteScriptWithoutUserGesture(
379       RenderFrameHost(),
380       "var submitRect = document.getElementById('password_field')"
381       ".getBoundingClientRect();"));
382   double y;
383   ASSERT_TRUE(content::ExecuteScriptWithoutUserGestureAndExtractDouble(
384       RenderFrameHost(),
385       "window.domAutomationController.send((submitRect.top +"
386       "submitRect.bottom) / 2);",
387       &y));
388   double x;
389   EXPECT_TRUE(content::ExecuteScriptWithoutUserGestureAndExtractDouble(
390       RenderFrameHost(),
391       "window.domAutomationController.send((submitRect.left + submitRect.right)"
392       "/ 2);",
393       &x));
394   content::SimulateMouseClickAt(
395       WebContents(), 0, blink::WebMouseEvent::Button::kLeft,
396       gfx::Point(static_cast<int>(x), static_cast<int>(y)));
397   WaitForStatus(TestPopupObserver::GenerationPopup::kShown);
398 }
399 
400 // https://crbug.com/791389
IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,DISABLED_AutoSavingGeneratedPassword)401 IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,
402                        DISABLED_AutoSavingGeneratedPassword) {
403   scoped_refptr<password_manager::TestPasswordStore> password_store =
404       static_cast<password_manager::TestPasswordStore*>(
405           PasswordStoreFactory::GetForProfile(
406               browser()->profile(), ServiceAccessType::IMPLICIT_ACCESS).get());
407 
408   FocusPasswordField();
409   EXPECT_TRUE(GenerationPopupShowing());
410   SendKeyToPopup(ui::VKEY_DOWN);
411   SendKeyToPopup(ui::VKEY_RETURN);
412 
413   // Change username.
414   FocusUsernameField();
415   content::SimulateKeyPress(WebContents(), ui::DomKey::FromCharacter('U'),
416                             ui::DomCode::US_U, ui::VKEY_U, false, false, false,
417                             false);
418   content::SimulateKeyPress(WebContents(), ui::DomKey::FromCharacter('N'),
419                             ui::DomCode::US_N, ui::VKEY_N, false, false, false,
420                             false);
421 
422   // Submit form.
423   NavigationObserver observer(WebContents());
424   std::string submit_script =
425       "document.getElementById('input_submit_button').click()";
426   ASSERT_TRUE(content::ExecuteScript(WebContents(), submit_script));
427   observer.Wait();
428 
429   WaitForPasswordStore();
430   EXPECT_FALSE(password_store->IsEmpty());
431 
432   // Make sure the username is correct.
433   password_manager::TestPasswordStore::PasswordMap stored_passwords =
434       password_store->stored_passwords();
435   EXPECT_EQ(1u, stored_passwords.size());
436   EXPECT_EQ(1u, stored_passwords.begin()->second.size());
437   EXPECT_EQ(base::UTF8ToUTF16("UN"),
438             (stored_passwords.begin()->second)[0].username_value);
439 }
440 
441 // Verify that navigating away closes the popup.
IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,NavigatingAwayClosesPopup)442 IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,
443                        NavigatingAwayClosesPopup) {
444   // Open popup.
445   FocusPasswordField();
446   EXPECT_TRUE(GenerationPopupShowing());
447 
448   // Simulate navigating to a different page.
449   NavigateToFile("/password/signup_form.html");
450 
451   // Check that popup is dismissed.
452   EXPECT_FALSE(GenerationPopupShowing());
453 }
454