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