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.
7 #include "base/memory/scoped_ptr.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "base/test/histogram_tester.h"
10 #include "chrome/renderer/autofill/password_generation_test_utils.h"
11 #include "chrome/test/base/chrome_render_view_test.h"
12 #include "components/autofill/content/common/autofill_messages.h"
13 #include "components/autofill/content/renderer/autofill_agent.h"
14 #include "components/autofill/content/renderer/test_password_generation_agent.h"
15 #include "components/autofill/core/common/form_data.h"
16 #include "components/autofill/core/common/password_generation_util.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18 #include "third_party/WebKit/public/platform/WebString.h"
19 #include "third_party/WebKit/public/web/WebDocument.h"
20 #include "third_party/WebKit/public/web/WebLocalFrame.h"
21 #include "third_party/WebKit/public/web/WebWidget.h"
22 #include "ui/events/keycodes/keyboard_codes.h"
24 using blink::WebDocument
;
25 using blink::WebElement
;
26 using blink::WebInputElement
;
28 using blink::WebString
;
32 class PasswordGenerationAgentTest
: public ChromeRenderViewTest
{
34 PasswordGenerationAgentTest() {}
36 void TearDown() override
{
38 ChromeRenderViewTest::TearDown();
41 void LoadHTMLWithUserGesture(const char* html
) {
44 // Enable show-ime event when element is focused by indicating that a user
45 // gesture has been processed since load.
46 EXPECT_TRUE(SimulateElementClick("dummy"));
49 void FocusField(const char* element_id
) {
50 WebDocument document
= GetMainFrame()->document();
51 blink::WebElement element
=
52 document
.getElementById(blink::WebString::fromUTF8(element_id
));
53 ASSERT_FALSE(element
.isNull());
54 ExecuteJavaScriptForTests(
55 base::StringPrintf("document.getElementById('%s').focus();",
59 void ExpectGenerationAvailable(const char* element_id
,
61 FocusField(element_id
);
62 ExpectPasswordGenerationAvailable(password_generation_
,
67 DISALLOW_COPY_AND_ASSIGN(PasswordGenerationAgentTest
);
70 const char kSigninFormHTML
[] =
71 "<FORM name = 'blah' action = 'http://www.random.com/'> "
72 " <INPUT type = 'text' id = 'username'/> "
73 " <INPUT type = 'password' id = 'password'/> "
74 " <INPUT type = 'button' id = 'dummy'/> "
75 " <INPUT type = 'submit' value = 'LOGIN' />"
78 const char kAccountCreationFormHTML
[] =
79 "<FORM name = 'blah' action = 'http://www.random.com/'> "
80 " <INPUT type = 'text' id = 'username'/> "
81 " <INPUT type = 'password' id = 'first_password' "
82 " autocomplete = 'off' size = 5/>"
83 " <INPUT type = 'password' id = 'second_password' size = 5/> "
84 " <INPUT type = 'text' id = 'address'/> "
85 " <INPUT type = 'button' id = 'dummy'/> "
86 " <INPUT type = 'submit' value = 'LOGIN' />"
89 const char kDisabledElementAccountCreationFormHTML
[] =
90 "<FORM name = 'blah' action = 'http://www.random.com/'> "
91 " <INPUT type = 'text' id = 'username'/> "
92 " <INPUT type = 'password' id = 'first_password' "
93 " autocomplete = 'off' size = 5/>"
94 " <INPUT type = 'password' id = 'second_password' size = 5/> "
95 " <INPUT type = 'text' id = 'address'/> "
96 " <INPUT type = 'text' id = 'disabled' disabled/> "
97 " <INPUT type = 'button' id = 'dummy'/> "
98 " <INPUT type = 'submit' value = 'LOGIN' />"
101 const char kHiddenPasswordAccountCreationFormHTML
[] =
102 "<FORM name = 'blah' action = 'http://www.random.com/'> "
103 " <INPUT type = 'text' id = 'username'/> "
104 " <INPUT type = 'password' id = 'first_password'/> "
105 " <INPUT type = 'password' id = 'second_password' style='display:none'/> "
106 " <INPUT type = 'button' id = 'dummy'/> "
107 " <INPUT type = 'submit' value = 'LOGIN' />"
110 const char kInvalidActionAccountCreationFormHTML
[] =
111 "<FORM name = 'blah' action = 'invalid'> "
112 " <INPUT type = 'text' id = 'username'/> "
113 " <INPUT type = 'password' id = 'first_password'/> "
114 " <INPUT type = 'password' id = 'second_password'/> "
115 " <INPUT type = 'button' id = 'dummy'/> "
116 " <INPUT type = 'submit' value = 'LOGIN' />"
119 const char kMultipleAccountCreationFormHTML
[] =
120 "<FORM name = 'login' action = 'http://www.random.com/'> "
121 " <INPUT type = 'text' id = 'random'/> "
122 " <INPUT type = 'text' id = 'username'/> "
123 " <INPUT type = 'password' id = 'password'/> "
124 " <INPUT type = 'button' id = 'dummy'/> "
125 " <INPUT type = 'submit' value = 'LOGIN' />"
127 "<FORM name = 'signup' action = 'http://www.random.com/signup'> "
128 " <INPUT type = 'text' id = 'username'/> "
129 " <INPUT type = 'password' id = 'first_password' "
130 " autocomplete = 'off' size = 5/>"
131 " <INPUT type = 'password' id = 'second_password' size = 5/> "
132 " <INPUT type = 'text' id = 'address'/> "
133 " <INPUT type = 'submit' value = 'LOGIN' />"
136 const char kBothAutocompleteAttributesFormHTML
[] =
137 "<FORM name = 'blah' action = 'http://www.random.com/'> "
138 " <INPUT type = 'text' autocomplete='username' id = 'username'/> "
139 " <INPUT type = 'password' id = 'first_password' "
140 " autocomplete = 'new-password' size = 5/>"
141 " <INPUT type = 'password' id = 'second_password' size = 5/> "
142 " <INPUT type = 'button' id = 'dummy'/> "
143 " <INPUT type = 'submit' value = 'LOGIN' />"
146 const char kUsernameAutocompleteAttributeFormHTML
[] =
147 "<FORM name = 'blah' action = 'http://www.random.com/'> "
148 " <INPUT type = 'text' autocomplete='username' id = 'username'/> "
149 " <INPUT type = 'password' id = 'first_password' size = 5/>"
150 " <INPUT type = 'password' id = 'second_password' size = 5/> "
151 " <INPUT type = 'button' id = 'dummy'/> "
152 " <INPUT type = 'submit' value = 'LOGIN' />"
155 const char kNewPasswordAutocompleteAttributeFormHTML
[] =
156 "<FORM name = 'blah' action = 'http://www.random.com/'> "
157 " <INPUT type = 'text' id = 'username'/> "
158 " <INPUT type = 'password' id = 'first_password' "
159 " autocomplete='new-password' size = 5/>"
160 " <INPUT type = 'password' id = 'second_password' size = 5/> "
161 " <INPUT type = 'button' id = 'dummy'/> "
162 " <INPUT type = 'submit' value = 'LOGIN' />"
165 const char ChangeDetectionScript
[] =
167 " firstOnChangeCalled = false;"
168 " secondOnChangeCalled = false;"
169 " document.getElementById('first_password').onchange = function() {"
170 " firstOnChangeCalled = true;"
172 " document.getElementById('second_password').onchange = function() {"
173 " secondOnChangeCalled = true;"
177 TEST_F(PasswordGenerationAgentTest
, DetectionTest
) {
178 // Don't shown the icon for non account creation forms.
179 LoadHTMLWithUserGesture(kSigninFormHTML
);
180 ExpectGenerationAvailable("password", false);
182 // We don't show the decoration yet because the feature isn't enabled.
183 LoadHTMLWithUserGesture(kAccountCreationFormHTML
);
184 ExpectGenerationAvailable("first_password", false);
186 // Pretend like We have received message indicating site is not blacklisted,
187 // and we have received message indicating the form is classified as
188 // ACCOUNT_CREATION_FORM form Autofill server. We should show the icon.
189 LoadHTMLWithUserGesture(kAccountCreationFormHTML
);
190 SetNotBlacklistedMessage(password_generation_
, kAccountCreationFormHTML
);
191 SetAccountCreationFormsDetectedMessage(password_generation_
,
192 GetMainFrame()->document(),
194 ExpectGenerationAvailable("first_password", true);
196 // Hidden fields are not treated differently.
197 LoadHTMLWithUserGesture(kHiddenPasswordAccountCreationFormHTML
);
198 SetNotBlacklistedMessage(password_generation_
,
199 kHiddenPasswordAccountCreationFormHTML
);
200 SetAccountCreationFormsDetectedMessage(password_generation_
,
201 GetMainFrame()->document(),
203 ExpectGenerationAvailable("first_password", true);
205 // This doesn't trigger because the form action is invalid.
206 LoadHTMLWithUserGesture(kInvalidActionAccountCreationFormHTML
);
207 SetNotBlacklistedMessage(password_generation_
,
208 kInvalidActionAccountCreationFormHTML
);
209 SetAccountCreationFormsDetectedMessage(password_generation_
,
210 GetMainFrame()->document(),
212 ExpectGenerationAvailable("first_password", false);
215 TEST_F(PasswordGenerationAgentTest
, FillTest
) {
216 // Make sure that we are enabled before loading HTML.
217 std::string html
= std::string(kAccountCreationFormHTML
) +
218 ChangeDetectionScript
;
219 LoadHTMLWithUserGesture(html
.c_str());
220 SetNotBlacklistedMessage(password_generation_
, html
.c_str());
221 SetAccountCreationFormsDetectedMessage(password_generation_
,
222 GetMainFrame()->document(),
225 WebDocument document
= GetMainFrame()->document();
227 document
.getElementById(WebString::fromUTF8("first_password"));
228 ASSERT_FALSE(element
.isNull());
229 WebInputElement first_password_element
= element
.to
<WebInputElement
>();
230 element
= document
.getElementById(WebString::fromUTF8("second_password"));
231 ASSERT_FALSE(element
.isNull());
232 WebInputElement second_password_element
= element
.to
<WebInputElement
>();
234 // Both password fields should be empty.
235 EXPECT_TRUE(first_password_element
.value().isNull());
236 EXPECT_TRUE(second_password_element
.value().isNull());
238 base::string16 password
= base::ASCIIToUTF16("random_password");
239 AutofillMsg_GeneratedPasswordAccepted
msg(0, password
);
240 password_generation_
->OnMessageReceived(msg
);
242 // Password fields are filled out and set as being autofilled.
243 EXPECT_EQ(password
, first_password_element
.value());
244 EXPECT_EQ(password
, second_password_element
.value());
245 EXPECT_TRUE(first_password_element
.isAutofilled());
246 EXPECT_TRUE(second_password_element
.isAutofilled());
248 // Make sure onchange events are called.
249 int first_onchange_called
= -1;
250 int second_onchange_called
= -1;
252 ExecuteJavaScriptAndReturnIntValue(
253 base::ASCIIToUTF16("firstOnChangeCalled ? 1 : 0"),
254 &first_onchange_called
));
255 EXPECT_EQ(1, first_onchange_called
);
257 ExecuteJavaScriptAndReturnIntValue(
258 base::ASCIIToUTF16("secondOnChangeCalled ? 1 : 0"),
259 &second_onchange_called
));
260 EXPECT_EQ(1, second_onchange_called
);
262 // Focus moved to the next input field.
263 // TODO(zysxqn): Change this back to the address element once Bug 90224
264 // https://bugs.webkit.org/show_bug.cgi?id=90224 has been fixed.
265 element
= document
.getElementById(WebString::fromUTF8("first_password"));
266 ASSERT_FALSE(element
.isNull());
267 EXPECT_EQ(element
, document
.focusedElement());
270 TEST_F(PasswordGenerationAgentTest
, EditingTest
) {
271 LoadHTMLWithUserGesture(kAccountCreationFormHTML
);
272 SetNotBlacklistedMessage(password_generation_
, kAccountCreationFormHTML
);
273 SetAccountCreationFormsDetectedMessage(password_generation_
,
274 GetMainFrame()->document(),
277 WebDocument document
= GetMainFrame()->document();
279 document
.getElementById(WebString::fromUTF8("first_password"));
280 ASSERT_FALSE(element
.isNull());
281 WebInputElement first_password_element
= element
.to
<WebInputElement
>();
282 element
= document
.getElementById(WebString::fromUTF8("second_password"));
283 ASSERT_FALSE(element
.isNull());
284 WebInputElement second_password_element
= element
.to
<WebInputElement
>();
286 base::string16 password
= base::ASCIIToUTF16("random_password");
287 AutofillMsg_GeneratedPasswordAccepted
msg(0, password
);
288 password_generation_
->OnMessageReceived(msg
);
290 // Passwords start out the same.
291 EXPECT_EQ(password
, first_password_element
.value());
292 EXPECT_EQ(password
, second_password_element
.value());
294 // After editing the first field they are still the same.
295 std::string edited_password_ascii
= "edited_password";
296 SimulateUserInputChangeForElement(&first_password_element
,
297 edited_password_ascii
);
298 base::string16 edited_password
= base::ASCIIToUTF16(edited_password_ascii
);
299 EXPECT_EQ(edited_password
, first_password_element
.value());
300 EXPECT_EQ(edited_password
, second_password_element
.value());
302 // Clear any uninteresting sent messages.
303 password_generation_
->clear_messages();
305 // Verify that password mirroring works correctly even when the password
307 SimulateUserInputChangeForElement(&first_password_element
, std::string());
308 EXPECT_EQ(base::string16(), first_password_element
.value());
309 EXPECT_EQ(base::string16(), second_password_element
.value());
311 // Should have notified the browser that the password is no longer generated
312 // and trigger generation again.
313 ASSERT_EQ(2u, password_generation_
->messages().size());
314 EXPECT_EQ(AutofillHostMsg_PasswordNoLongerGenerated::ID
,
315 password_generation_
->messages()[0]->type());
316 EXPECT_EQ(AutofillHostMsg_ShowPasswordGenerationPopup::ID
,
317 password_generation_
->messages()[1]->type());
320 TEST_F(PasswordGenerationAgentTest
, BlacklistedTest
) {
321 // Did not receive not blacklisted message. Don't show password generation
323 LoadHTMLWithUserGesture(kAccountCreationFormHTML
);
324 SetAccountCreationFormsDetectedMessage(password_generation_
,
325 GetMainFrame()->document(),
327 ExpectGenerationAvailable("first_password", false);
329 // Receive one not blacklisted message for non account creation form. Don't
330 // show password generation icon.
331 LoadHTMLWithUserGesture(kAccountCreationFormHTML
);
332 SetNotBlacklistedMessage(password_generation_
, kSigninFormHTML
);
333 SetAccountCreationFormsDetectedMessage(password_generation_
,
334 GetMainFrame()->document(),
336 ExpectGenerationAvailable("first_password", false);
338 // Receive one not blacklisted message for account creation form. Show
339 // password generation icon.
340 LoadHTMLWithUserGesture(kAccountCreationFormHTML
);
341 SetNotBlacklistedMessage(password_generation_
, kAccountCreationFormHTML
);
342 SetAccountCreationFormsDetectedMessage(password_generation_
,
343 GetMainFrame()->document(),
345 ExpectGenerationAvailable("first_password", true);
347 // Receive two not blacklisted messages, one is for account creation form and
348 // the other is not. Show password generation icon.
349 LoadHTMLWithUserGesture(kAccountCreationFormHTML
);
350 SetNotBlacklistedMessage(password_generation_
, kAccountCreationFormHTML
);
351 SetNotBlacklistedMessage(password_generation_
, kSigninFormHTML
);
352 SetAccountCreationFormsDetectedMessage(password_generation_
,
353 GetMainFrame()->document(),
355 ExpectGenerationAvailable("first_password", true);
358 TEST_F(PasswordGenerationAgentTest
, AccountCreationFormsDetectedTest
) {
359 // Did not receive account creation forms detected message. Don't show
360 // password generation icon.
361 LoadHTMLWithUserGesture(kAccountCreationFormHTML
);
362 SetNotBlacklistedMessage(password_generation_
, kAccountCreationFormHTML
);
363 ExpectGenerationAvailable("first_password", false);
365 // Receive the account creation forms detected message. Show password
367 LoadHTMLWithUserGesture(kAccountCreationFormHTML
);
368 SetNotBlacklistedMessage(password_generation_
, kAccountCreationFormHTML
);
369 SetAccountCreationFormsDetectedMessage(password_generation_
,
370 GetMainFrame()->document(),
372 ExpectGenerationAvailable("first_password", true);
375 TEST_F(PasswordGenerationAgentTest
, MaximumOfferSize
) {
376 base::HistogramTester histogram_tester
;
378 LoadHTMLWithUserGesture(kAccountCreationFormHTML
);
379 SetNotBlacklistedMessage(password_generation_
, kAccountCreationFormHTML
);
380 SetAccountCreationFormsDetectedMessage(password_generation_
,
381 GetMainFrame()->document(),
383 ExpectGenerationAvailable("first_password", true);
385 WebDocument document
= GetMainFrame()->document();
387 document
.getElementById(WebString::fromUTF8("first_password"));
388 ASSERT_FALSE(element
.isNull());
389 WebInputElement first_password_element
= element
.to
<WebInputElement
>();
391 // Make a password just under maximum offer size.
392 SimulateUserInputChangeForElement(
393 &first_password_element
,
394 std::string(password_generation_
->kMaximumOfferSize
- 1, 'a'));
395 // There should now be a message to show the UI.
396 ASSERT_EQ(1u, password_generation_
->messages().size());
397 EXPECT_EQ(AutofillHostMsg_ShowPasswordGenerationPopup::ID
,
398 password_generation_
->messages()[0]->type());
399 password_generation_
->clear_messages();
401 // Simulate a user typing a password just over maximum offer size.
402 SimulateUserTypingASCIICharacter('a', false);
403 SimulateUserTypingASCIICharacter('a', true);
404 // There should now be a message to hide the UI.
405 ASSERT_EQ(1u, password_generation_
->messages().size());
406 EXPECT_EQ(AutofillHostMsg_HidePasswordGenerationPopup::ID
,
407 password_generation_
->messages()[0]->type());
408 password_generation_
->clear_messages();
410 // Simulate the user deleting characters. The generation popup should be shown
412 SimulateUserTypingASCIICharacter(ui::VKEY_BACK
, true);
413 // There should now be a message to show the UI.
414 ASSERT_EQ(1u, password_generation_
->messages().size());
415 EXPECT_EQ(AutofillHostMsg_ShowPasswordGenerationPopup::ID
,
416 password_generation_
->messages()[0]->type());
417 password_generation_
->clear_messages();
419 // Change focus. Bubble should be hidden, but that is handled by AutofilAgent,
420 // so no messages are sent.
421 ExecuteJavaScriptForTests("document.getElementById('username').focus();");
422 EXPECT_EQ(0u, password_generation_
->messages().size());
423 password_generation_
->clear_messages();
425 // Focusing the password field will bring up the generation UI again.
426 ExecuteJavaScriptForTests(
427 "document.getElementById('first_password').focus();");
428 ASSERT_EQ(1u, password_generation_
->messages().size());
429 EXPECT_EQ(AutofillHostMsg_ShowPasswordGenerationPopup::ID
,
430 password_generation_
->messages()[0]->type());
431 password_generation_
->clear_messages();
433 // Loading a different page triggers UMA stat upload. Verify that only one
434 // display event is sent even though
435 LoadHTMLWithUserGesture(kSigninFormHTML
);
437 histogram_tester
.ExpectBucketCount(
438 "PasswordGeneration.Event",
439 autofill::password_generation::GENERATION_POPUP_SHOWN
,
443 TEST_F(PasswordGenerationAgentTest
, DynamicFormTest
) {
444 LoadHTMLWithUserGesture(kSigninFormHTML
);
445 SetNotBlacklistedMessage(password_generation_
, kSigninFormHTML
);
447 ExecuteJavaScriptForTests(
448 "var form = document.createElement('form');"
449 "var username = document.createElement('input');"
450 "username.type = 'text';"
451 "username.id = 'dynamic_username';"
452 "var first_password = document.createElement('input');"
453 "first_password.type = 'password';"
454 "first_password.id = 'first_password';"
455 "first_password.name = 'first_password';"
456 "var second_password = document.createElement('input');"
457 "second_password.type = 'password';"
458 "second_password.id = 'second_password';"
459 "second_password.name = 'second_password';"
460 "form.appendChild(username);"
461 "form.appendChild(first_password);"
462 "form.appendChild(second_password);"
463 "document.body.appendChild(form);");
464 ProcessPendingMessages();
466 // This needs to come after the DOM has been modified.
467 SetAccountCreationFormsDetectedMessage(password_generation_
,
468 GetMainFrame()->document(),
471 // TODO(gcasto): I'm slightly worried about flakes in this test where
472 // didAssociateFormControls() isn't called. If this turns out to be a problem
473 // adding a call to OnDynamicFormsSeen(GetMainFrame()) will fix it, though
474 // it will weaken the test.
475 ExpectGenerationAvailable("first_password", true);
478 TEST_F(PasswordGenerationAgentTest
, MultiplePasswordFormsTest
) {
479 // If two forms on the page looks like possible account creation forms, make
480 // sure to trigger on the one that is specified from Autofill.
481 LoadHTMLWithUserGesture(kMultipleAccountCreationFormHTML
);
482 SetNotBlacklistedMessage(password_generation_
,
483 kMultipleAccountCreationFormHTML
);
485 // Should trigger on the second form.
486 SetAccountCreationFormsDetectedMessage(password_generation_
,
487 GetMainFrame()->document(),
490 ExpectGenerationAvailable("password", false);
491 ExpectGenerationAvailable("first_password", true);
494 TEST_F(PasswordGenerationAgentTest
, MessagesAfterAccountSignupFormFound
) {
495 LoadHTMLWithUserGesture(kAccountCreationFormHTML
);
496 SetNotBlacklistedMessage(password_generation_
, kAccountCreationFormHTML
);
497 SetAccountCreationFormsDetectedMessage(password_generation_
,
498 GetMainFrame()->document(),
501 // Generation should be enabled.
502 ExpectGenerationAvailable("first_password", true);
504 // Extra not blacklisted messages can be sent. Make sure that they are handled
505 // correctly (generation should still be available).
506 SetNotBlacklistedMessage(password_generation_
, kAccountCreationFormHTML
);
508 // Need to focus another field first for verification to work.
509 ExpectGenerationAvailable("second_password", false);
510 ExpectGenerationAvailable("first_password", true);
513 // Losing focus should not trigger a password generation popup.
514 TEST_F(PasswordGenerationAgentTest
, BlurTest
) {
515 LoadHTMLWithUserGesture(kDisabledElementAccountCreationFormHTML
);
516 SetNotBlacklistedMessage(password_generation_
,
517 kDisabledElementAccountCreationFormHTML
);
518 SetAccountCreationFormsDetectedMessage(password_generation_
,
519 GetMainFrame()->document(),
522 // Focus on the first password field: password generation popup should show
524 ExpectGenerationAvailable("first_password", true);
526 // Remove focus from everywhere by clicking an unfocusable element: password
527 // generation popup should not show up.
528 EXPECT_TRUE(SimulateElementClick("disabled"));
529 EXPECT_EQ(0u, password_generation_
->messages().size());
532 TEST_F(PasswordGenerationAgentTest
, AutocompleteAttributesTest
) {
533 // Verify that autocomplete attributes can override Autofill to enable
535 LoadHTMLWithUserGesture(kBothAutocompleteAttributesFormHTML
);
536 SetNotBlacklistedMessage(password_generation_
,
537 kBothAutocompleteAttributesFormHTML
);
539 ExpectGenerationAvailable("first_password", true);
541 // Only setting one of the two attributes doesn't trigger generation.
542 LoadHTMLWithUserGesture(kUsernameAutocompleteAttributeFormHTML
);
543 SetNotBlacklistedMessage(password_generation_
,
544 kUsernameAutocompleteAttributeFormHTML
);
546 ExpectGenerationAvailable("first_password", false);
548 LoadHTMLWithUserGesture(kNewPasswordAutocompleteAttributeFormHTML
);
549 SetNotBlacklistedMessage(password_generation_
,
550 kNewPasswordAutocompleteAttributeFormHTML
);
552 ExpectGenerationAvailable("first_password", false);
555 } // namespace autofill