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/test/base/chrome_render_view_test.h"
11 #include "components/autofill/content/common/autofill_messages.h"
12 #include "components/autofill/content/renderer/autofill_agent.h"
13 #include "components/autofill/content/renderer/form_autofill_util.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"
23 using blink::WebDocument
;
24 using blink::WebElement
;
25 using blink::WebInputElement
;
27 using blink::WebString
;
31 class PasswordGenerationAgentTest
: public ChromeRenderViewTest
{
33 PasswordGenerationAgentTest() {}
35 void TearDown() override
{
37 ChromeRenderViewTest::TearDown();
40 void SetNotBlacklistedMessage(const char* form_str
) {
41 autofill::PasswordForm form
;
43 GURL(base::StringPrintf("data:text/html;charset=utf-8,%s", form_str
));
44 AutofillMsg_FormNotBlacklisted
msg(0, form
);
45 password_generation_
->OnMessageReceived(msg
);
48 // Sends a message that the |form_index| form on the page is valid for
50 void SetAccountCreationFormsDetectedMessage(int form_index
) {
51 WebDocument document
= GetMainFrame()->document();
52 blink::WebVector
<blink::WebFormElement
> web_forms
;
53 document
.forms(web_forms
);
55 autofill::FormData form_data
;
56 WebFormElementToFormData(web_forms
[form_index
],
57 blink::WebFormControlElement(),
61 NULL
/* FormFieldData */);
63 std::vector
<autofill::FormData
> forms
;
64 forms
.push_back(form_data
);
65 AutofillMsg_AccountCreationFormsDetected
msg(0, forms
);
66 password_generation_
->OnMessageReceived(msg
);
69 void ExpectPasswordGenerationAvailable(const char* element_id
,
71 WebDocument document
= GetMainFrame()->document();
73 document
.getElementById(WebString::fromUTF8(element_id
));
74 ASSERT_FALSE(element
.isNull());
76 base::StringPrintf("document.getElementById('%s').focus();",
79 ASSERT_EQ(1u, password_generation_
->messages().size());
80 EXPECT_EQ(AutofillHostMsg_ShowPasswordGenerationPopup::ID
,
81 password_generation_
->messages()[0]->type());
83 EXPECT_EQ(0u, password_generation_
->messages().size());
85 password_generation_
->clear_messages();
88 void LoadHTMLWithUserGesture(const char* html
) {
91 // Enable show-ime event when element is focused by indicating that a user
92 // gesture has been processed since load.
93 EXPECT_TRUE(SimulateElementClick("dummy"));
97 DISALLOW_COPY_AND_ASSIGN(PasswordGenerationAgentTest
);
100 const char kSigninFormHTML
[] =
101 "<FORM name = 'blah' action = 'http://www.random.com/'> "
102 " <INPUT type = 'text' id = 'username'/> "
103 " <INPUT type = 'password' id = 'password'/> "
104 " <INPUT type = 'button' id = 'dummy'/> "
105 " <INPUT type = 'submit' value = 'LOGIN' />"
108 const char kAccountCreationFormHTML
[] =
109 "<FORM name = 'blah' action = 'http://www.random.com/'> "
110 " <INPUT type = 'text' id = 'username'/> "
111 " <INPUT type = 'password' id = 'first_password' "
112 " autocomplete = 'off' size = 5/>"
113 " <INPUT type = 'password' id = 'second_password' size = 5/> "
114 " <INPUT type = 'text' id = 'address'/> "
115 " <INPUT type = 'button' id = 'dummy'/> "
116 " <INPUT type = 'submit' value = 'LOGIN' />"
119 const char kDisabledElementAccountCreationFormHTML
[] =
120 "<FORM name = 'blah' action = 'http://www.random.com/'> "
121 " <INPUT type = 'text' id = 'username'/> "
122 " <INPUT type = 'password' id = 'first_password' "
123 " autocomplete = 'off' size = 5/>"
124 " <INPUT type = 'password' id = 'second_password' size = 5/> "
125 " <INPUT type = 'text' id = 'address'/> "
126 " <INPUT type = 'text' id = 'disabled' disabled/> "
127 " <INPUT type = 'button' id = 'dummy'/> "
128 " <INPUT type = 'submit' value = 'LOGIN' />"
131 const char kHiddenPasswordAccountCreationFormHTML
[] =
132 "<FORM name = 'blah' action = 'http://www.random.com/'> "
133 " <INPUT type = 'text' id = 'username'/> "
134 " <INPUT type = 'password' id = 'first_password'/> "
135 " <INPUT type = 'password' id = 'second_password' style='display:none'/> "
136 " <INPUT type = 'button' id = 'dummy'/> "
137 " <INPUT type = 'submit' value = 'LOGIN' />"
140 const char kInvalidActionAccountCreationFormHTML
[] =
141 "<FORM name = 'blah' action = 'invalid'> "
142 " <INPUT type = 'text' id = 'username'/> "
143 " <INPUT type = 'password' id = 'first_password'/> "
144 " <INPUT type = 'password' id = 'second_password'/> "
145 " <INPUT type = 'button' id = 'dummy'/> "
146 " <INPUT type = 'submit' value = 'LOGIN' />"
149 const char kMultipleAccountCreationFormHTML
[] =
150 "<FORM name = 'login' action = 'http://www.random.com/'> "
151 " <INPUT type = 'text' id = 'random'/> "
152 " <INPUT type = 'text' id = 'username'/> "
153 " <INPUT type = 'password' id = 'password'/> "
154 " <INPUT type = 'button' id = 'dummy'/> "
155 " <INPUT type = 'submit' value = 'LOGIN' />"
157 "<FORM name = 'signup' action = 'http://www.random.com/signup'> "
158 " <INPUT type = 'text' id = 'username'/> "
159 " <INPUT type = 'password' id = 'first_password' "
160 " autocomplete = 'off' size = 5/>"
161 " <INPUT type = 'password' id = 'second_password' size = 5/> "
162 " <INPUT type = 'text' id = 'address'/> "
163 " <INPUT type = 'submit' value = 'LOGIN' />"
166 const char ChangeDetectionScript
[] =
168 " firstOnChangeCalled = false;"
169 " secondOnChangeCalled = false;"
170 " document.getElementById('first_password').onchange = function() {"
171 " firstOnChangeCalled = true;"
173 " document.getElementById('second_password').onchange = function() {"
174 " secondOnChangeCalled = true;"
178 TEST_F(PasswordGenerationAgentTest
, DetectionTest
) {
179 // Don't shown the icon for non account creation forms.
180 LoadHTMLWithUserGesture(kSigninFormHTML
);
181 ExpectPasswordGenerationAvailable("password", false);
183 // We don't show the decoration yet because the feature isn't enabled.
184 LoadHTMLWithUserGesture(kAccountCreationFormHTML
);
185 ExpectPasswordGenerationAvailable("first_password", false);
187 // Pretend like We have received message indicating site is not blacklisted,
188 // and we have received message indicating the form is classified as
189 // ACCOUNT_CREATION_FORM form Autofill server. We should show the icon.
190 LoadHTMLWithUserGesture(kAccountCreationFormHTML
);
191 SetNotBlacklistedMessage(kAccountCreationFormHTML
);
192 SetAccountCreationFormsDetectedMessage(0);
193 ExpectPasswordGenerationAvailable("first_password", true);
195 // Hidden fields are not treated differently.
196 LoadHTMLWithUserGesture(kHiddenPasswordAccountCreationFormHTML
);
197 SetNotBlacklistedMessage(kHiddenPasswordAccountCreationFormHTML
);
198 SetAccountCreationFormsDetectedMessage(0);
199 ExpectPasswordGenerationAvailable("first_password", true);
201 // This doesn't trigger because the form action is invalid.
202 LoadHTMLWithUserGesture(kInvalidActionAccountCreationFormHTML
);
203 SetNotBlacklistedMessage(kInvalidActionAccountCreationFormHTML
);
204 SetAccountCreationFormsDetectedMessage(0);
205 ExpectPasswordGenerationAvailable("first_password", false);
208 TEST_F(PasswordGenerationAgentTest
, FillTest
) {
209 // Make sure that we are enabled before loading HTML.
210 std::string html
= std::string(kAccountCreationFormHTML
) +
211 ChangeDetectionScript
;
212 LoadHTMLWithUserGesture(html
.c_str());
213 SetNotBlacklistedMessage(html
.c_str());
214 SetAccountCreationFormsDetectedMessage(0);
216 WebDocument document
= GetMainFrame()->document();
218 document
.getElementById(WebString::fromUTF8("first_password"));
219 ASSERT_FALSE(element
.isNull());
220 WebInputElement first_password_element
= element
.to
<WebInputElement
>();
221 element
= document
.getElementById(WebString::fromUTF8("second_password"));
222 ASSERT_FALSE(element
.isNull());
223 WebInputElement second_password_element
= element
.to
<WebInputElement
>();
225 // Both password fields should be empty.
226 EXPECT_TRUE(first_password_element
.value().isNull());
227 EXPECT_TRUE(second_password_element
.value().isNull());
229 base::string16 password
= base::ASCIIToUTF16("random_password");
230 AutofillMsg_GeneratedPasswordAccepted
msg(0, password
);
231 password_generation_
->OnMessageReceived(msg
);
233 // Password fields are filled out and set as being autofilled.
234 EXPECT_EQ(password
, first_password_element
.value());
235 EXPECT_EQ(password
, second_password_element
.value());
236 EXPECT_TRUE(first_password_element
.isAutofilled());
237 EXPECT_TRUE(second_password_element
.isAutofilled());
239 // Make sure onchange events are called.
240 int first_onchange_called
= -1;
241 int second_onchange_called
= -1;
243 ExecuteJavaScriptAndReturnIntValue(
244 base::ASCIIToUTF16("firstOnChangeCalled ? 1 : 0"),
245 &first_onchange_called
));
246 EXPECT_EQ(1, first_onchange_called
);
248 ExecuteJavaScriptAndReturnIntValue(
249 base::ASCIIToUTF16("secondOnChangeCalled ? 1 : 0"),
250 &second_onchange_called
));
251 EXPECT_EQ(1, second_onchange_called
);
253 // Focus moved to the next input field.
254 // TODO(zysxqn): Change this back to the address element once Bug 90224
255 // https://bugs.webkit.org/show_bug.cgi?id=90224 has been fixed.
256 element
= document
.getElementById(WebString::fromUTF8("first_password"));
257 ASSERT_FALSE(element
.isNull());
258 EXPECT_EQ(element
, document
.focusedElement());
261 TEST_F(PasswordGenerationAgentTest
, EditingTest
) {
262 LoadHTMLWithUserGesture(kAccountCreationFormHTML
);
263 SetNotBlacklistedMessage(kAccountCreationFormHTML
);
264 SetAccountCreationFormsDetectedMessage(0);
266 WebDocument document
= GetMainFrame()->document();
268 document
.getElementById(WebString::fromUTF8("first_password"));
269 ASSERT_FALSE(element
.isNull());
270 WebInputElement first_password_element
= element
.to
<WebInputElement
>();
271 element
= document
.getElementById(WebString::fromUTF8("second_password"));
272 ASSERT_FALSE(element
.isNull());
273 WebInputElement second_password_element
= element
.to
<WebInputElement
>();
275 base::string16 password
= base::ASCIIToUTF16("random_password");
276 AutofillMsg_GeneratedPasswordAccepted
msg(0, password
);
277 password_generation_
->OnMessageReceived(msg
);
279 // Passwords start out the same.
280 EXPECT_EQ(password
, first_password_element
.value());
281 EXPECT_EQ(password
, second_password_element
.value());
283 // After editing the first field they are still the same.
284 base::string16 edited_password
= base::ASCIIToUTF16("edited_password");
285 first_password_element
.setValue(edited_password
);
286 // Cast to WebAutofillClient where textFieldDidChange() is public.
287 static_cast<blink::WebAutofillClient
*>(autofill_agent_
)->textFieldDidChange(
288 first_password_element
);
289 // textFieldDidChange posts a task, so we need to wait until it's been
291 base::MessageLoop::current()->RunUntilIdle();
292 EXPECT_EQ(edited_password
, first_password_element
.value());
293 EXPECT_EQ(edited_password
, second_password_element
.value());
295 // Verify that password mirroring works correctly even when the password
297 base::string16 empty_password
;
298 first_password_element
.setValue(empty_password
);
299 // Cast to WebAutofillClient where textFieldDidChange() is public.
300 static_cast<blink::WebAutofillClient
*>(autofill_agent_
)->textFieldDidChange(
301 first_password_element
);
302 // textFieldDidChange posts a task, so we need to wait until it's been
304 base::MessageLoop::current()->RunUntilIdle();
305 EXPECT_EQ(empty_password
, first_password_element
.value());
306 EXPECT_EQ(empty_password
, second_password_element
.value());
309 TEST_F(PasswordGenerationAgentTest
, BlacklistedTest
) {
310 // Did not receive not blacklisted message. Don't show password generation
312 LoadHTMLWithUserGesture(kAccountCreationFormHTML
);
313 SetAccountCreationFormsDetectedMessage(0);
314 ExpectPasswordGenerationAvailable("first_password", false);
316 // Receive one not blacklisted message for non account creation form. Don't
317 // show password generation icon.
318 LoadHTMLWithUserGesture(kAccountCreationFormHTML
);
319 SetNotBlacklistedMessage(kSigninFormHTML
);
320 SetAccountCreationFormsDetectedMessage(0);
321 ExpectPasswordGenerationAvailable("first_password", false);
323 // Receive one not blacklisted message for account creation form. Show
324 // password generation icon.
325 LoadHTMLWithUserGesture(kAccountCreationFormHTML
);
326 SetNotBlacklistedMessage(kAccountCreationFormHTML
);
327 SetAccountCreationFormsDetectedMessage(0);
328 ExpectPasswordGenerationAvailable("first_password", true);
330 // Receive two not blacklisted messages, one is for account creation form and
331 // the other is not. Show password generation icon.
332 LoadHTMLWithUserGesture(kAccountCreationFormHTML
);
333 SetNotBlacklistedMessage(kAccountCreationFormHTML
);
334 SetNotBlacklistedMessage(kSigninFormHTML
);
335 SetAccountCreationFormsDetectedMessage(0);
336 ExpectPasswordGenerationAvailable("first_password", true);
339 TEST_F(PasswordGenerationAgentTest
, AccountCreationFormsDetectedTest
) {
340 // Did not receive account creation forms detected message. Don't show
341 // password generation icon.
342 LoadHTMLWithUserGesture(kAccountCreationFormHTML
);
343 SetNotBlacklistedMessage(kAccountCreationFormHTML
);
344 ExpectPasswordGenerationAvailable("first_password", false);
346 // Receive the account creation forms detected message. Show password
348 LoadHTMLWithUserGesture(kAccountCreationFormHTML
);
349 SetNotBlacklistedMessage(kAccountCreationFormHTML
);
350 SetAccountCreationFormsDetectedMessage(0);
351 ExpectPasswordGenerationAvailable("first_password", true);
354 TEST_F(PasswordGenerationAgentTest
, MaximumOfferSize
) {
355 base::HistogramTester histogram_tester
;
357 LoadHTMLWithUserGesture(kAccountCreationFormHTML
);
358 SetNotBlacklistedMessage(kAccountCreationFormHTML
);
359 SetAccountCreationFormsDetectedMessage(0);
360 ExpectPasswordGenerationAvailable("first_password", true);
362 WebDocument document
= GetMainFrame()->document();
364 document
.getElementById(WebString::fromUTF8("first_password"));
365 ASSERT_FALSE(element
.isNull());
366 WebInputElement first_password_element
= element
.to
<WebInputElement
>();
368 // Make a password just under maximum offer size.
369 first_password_element
.setValue(
371 std::string(password_generation_
->kMaximumOfferSize
- 1, 'a')));
372 // Cast to WebAutofillClient where textFieldDidChange() is public.
373 static_cast<blink::WebAutofillClient
*>(autofill_agent_
)->textFieldDidChange(
374 first_password_element
);
375 // textFieldDidChange posts a task, so we need to wait until it's been
377 base::MessageLoop::current()->RunUntilIdle();
378 // There should now be a message to show the UI.
379 ASSERT_EQ(1u, password_generation_
->messages().size());
380 EXPECT_EQ(AutofillHostMsg_ShowPasswordGenerationPopup::ID
,
381 password_generation_
->messages()[0]->type());
382 password_generation_
->clear_messages();
384 // Simulate a user typing a password just over maximum offer size.
385 first_password_element
.setValue(
387 std::string(password_generation_
->kMaximumOfferSize
+ 1, 'a')));
388 // Cast to WebAutofillClient where textFieldDidChange() is public.
389 static_cast<blink::WebAutofillClient
*>(autofill_agent_
)->textFieldDidChange(
390 first_password_element
);
391 // textFieldDidChange posts a task, so we need to wait until it's been
393 base::MessageLoop::current()->RunUntilIdle();
394 // There should now be a message to hide the UI.
395 ASSERT_EQ(1u, password_generation_
->messages().size());
396 EXPECT_EQ(AutofillHostMsg_HidePasswordGenerationPopup::ID
,
397 password_generation_
->messages()[0]->type());
398 password_generation_
->clear_messages();
400 // Simulate the user deleting characters. The generation popup should be shown
402 first_password_element
.setValue(
404 std::string(password_generation_
->kMaximumOfferSize
, 'a')));
405 // Cast to WebAutofillClient where textFieldDidChange() is public.
406 static_cast<blink::WebAutofillClient
*>(autofill_agent_
)->textFieldDidChange(
407 first_password_element
);
408 // textFieldDidChange posts a task, so we need to wait until it's been
410 base::MessageLoop::current()->RunUntilIdle();
411 // There should now be a message to show the UI.
412 ASSERT_EQ(1u, password_generation_
->messages().size());
413 EXPECT_EQ(AutofillHostMsg_ShowPasswordGenerationPopup::ID
,
414 password_generation_
->messages()[0]->type());
415 password_generation_
->clear_messages();
417 // Change focus. Bubble should be hidden, but that is handled by AutofilAgent,
418 // so no messages are sent.
419 ExecuteJavaScript("document.getElementById('username').focus();");
420 EXPECT_EQ(0u, password_generation_
->messages().size());
421 password_generation_
->clear_messages();
423 // Focusing the password field will bring up the generation UI again.
424 ExecuteJavaScript("document.getElementById('first_password').focus();");
425 ASSERT_EQ(1u, password_generation_
->messages().size());
426 EXPECT_EQ(AutofillHostMsg_ShowPasswordGenerationPopup::ID
,
427 password_generation_
->messages()[0]->type());
428 password_generation_
->clear_messages();
430 // Loading a different page triggers UMA stat upload. Verify that only one
431 // display event is sent even though
432 LoadHTMLWithUserGesture(kSigninFormHTML
);
434 histogram_tester
.ExpectBucketCount(
435 "PasswordGeneration.Event",
436 autofill::password_generation::GENERATION_POPUP_SHOWN
,
440 TEST_F(PasswordGenerationAgentTest
, DynamicFormTest
) {
441 LoadHTMLWithUserGesture(kSigninFormHTML
);
442 SetNotBlacklistedMessage(kSigninFormHTML
);
445 "var form = document.createElement('form');"
446 "var username = document.createElement('input');"
447 "username.type = 'text';"
448 "username.id = 'dynamic_username';"
449 "var first_password = document.createElement('input');"
450 "first_password.type = 'password';"
451 "first_password.id = 'first_password';"
452 "first_password.name = 'first_password';"
453 "var second_password = document.createElement('input');"
454 "second_password.type = 'password';"
455 "second_password.id = 'second_password';"
456 "second_password.name = 'second_password';"
457 "form.appendChild(username);"
458 "form.appendChild(first_password);"
459 "form.appendChild(second_password);"
460 "document.body.appendChild(form);");
461 ProcessPendingMessages();
463 // This needs to come after the DOM has been modified.
464 SetAccountCreationFormsDetectedMessage(1);
466 // TODO(gcasto): I'm slightly worried about flakes in this test where
467 // didAssociateFormControls() isn't called. If this turns out to be a problem
468 // adding a call to OnDynamicFormsSeen(GetMainFrame()) will fix it, though
469 // it will weaken the test.
470 ExpectPasswordGenerationAvailable("first_password", true);
473 TEST_F(PasswordGenerationAgentTest
, MultiplePasswordFormsTest
) {
474 // If two forms on the page looks like possible account creation forms, make
475 // sure to trigger on the one that is specified from Autofill.
476 LoadHTMLWithUserGesture(kMultipleAccountCreationFormHTML
);
477 SetNotBlacklistedMessage(kMultipleAccountCreationFormHTML
);
479 // Should trigger on the second form.
480 SetAccountCreationFormsDetectedMessage(1);
482 ExpectPasswordGenerationAvailable("password", false);
483 ExpectPasswordGenerationAvailable("first_password", true);
486 TEST_F(PasswordGenerationAgentTest
, MessagesAfterAccountSignupFormFound
) {
487 LoadHTMLWithUserGesture(kAccountCreationFormHTML
);
488 SetNotBlacklistedMessage(kAccountCreationFormHTML
);
489 SetAccountCreationFormsDetectedMessage(0);
491 // Generation should be enabled.
492 ExpectPasswordGenerationAvailable("first_password", true);
494 // Extra not blacklisted messages can be sent. Make sure that they are handled
495 // correctly (generation should still be available).
496 SetNotBlacklistedMessage(kAccountCreationFormHTML
);
498 // Need to focus another field first for verification to work.
499 ExpectPasswordGenerationAvailable("second_password", false);
500 ExpectPasswordGenerationAvailable("first_password", true);
503 // Losing focus should not trigger a password generation popup.
504 TEST_F(PasswordGenerationAgentTest
, BlurTest
) {
505 LoadHTMLWithUserGesture(kDisabledElementAccountCreationFormHTML
);
506 SetNotBlacklistedMessage(kDisabledElementAccountCreationFormHTML
);
507 SetAccountCreationFormsDetectedMessage(0);
509 // Focus on the first password field: password generation popup should show
511 ExpectPasswordGenerationAvailable("first_password", true);
513 // Remove focus from everywhere by clicking an unfocusable element: password
514 // generation popup should not show up.
515 EXPECT_TRUE(SimulateElementClick("disabled"));
516 EXPECT_EQ(0u, password_generation_
->messages().size());
519 } // namespace autofill