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/test_password_generation_agent.h"
14 #include "components/autofill/core/common/form_data.h"
15 #include "components/autofill/core/common/password_generation_util.h"
16 #include "testing/gtest/include/gtest/gtest.h"
17 #include "third_party/WebKit/public/platform/WebString.h"
18 #include "third_party/WebKit/public/web/WebDocument.h"
19 #include "third_party/WebKit/public/web/WebLocalFrame.h"
20 #include "third_party/WebKit/public/web/WebWidget.h"
22 using blink::WebDocument
;
23 using blink::WebElement
;
24 using blink::WebInputElement
;
26 using blink::WebString
;
30 class PasswordGenerationAgentTest
: public ChromeRenderViewTest
{
32 PasswordGenerationAgentTest() {}
34 void TearDown() override
{
36 ChromeRenderViewTest::TearDown();
39 void SetNotBlacklistedMessage(const char* form_str
) {
40 autofill::PasswordForm form
;
42 GURL(base::StringPrintf("data:text/html;charset=utf-8,%s", form_str
));
43 AutofillMsg_FormNotBlacklisted
msg(0, form
);
44 password_generation_
->OnMessageReceived(msg
);
47 void SetAccountCreationFormsDetectedMessage(const char* form_str
) {
48 autofill::FormData form
;
50 GURL(base::StringPrintf("data:text/html;charset=utf-8,%s", form_str
));
51 std::vector
<autofill::FormData
> forms
;
52 forms
.push_back(form
);
53 AutofillMsg_AccountCreationFormsDetected
msg(0, forms
);
54 password_generation_
->OnMessageReceived(msg
);
57 void ExpectPasswordGenerationAvailable(const char* element_id
,
59 WebDocument document
= GetMainFrame()->document();
61 document
.getElementById(WebString::fromUTF8(element_id
));
62 ASSERT_FALSE(element
.isNull());
64 base::StringPrintf("document.getElementById('%s').focus();",
67 ASSERT_EQ(1u, password_generation_
->messages().size());
68 EXPECT_EQ(AutofillHostMsg_ShowPasswordGenerationPopup::ID
,
69 password_generation_
->messages()[0]->type());
71 EXPECT_EQ(0u, password_generation_
->messages().size());
73 password_generation_
->clear_messages();
77 DISALLOW_COPY_AND_ASSIGN(PasswordGenerationAgentTest
);
80 const char kSigninFormHTML
[] =
81 "<FORM name = 'blah' action = 'http://www.random.com/'> "
82 " <INPUT type = 'text' id = 'username'/> "
83 " <INPUT type = 'password' id = 'password'/> "
84 " <INPUT type = 'submit' value = 'LOGIN' />"
87 const char kAccountCreationFormHTML
[] =
88 "<FORM name = 'blah' action = 'http://www.random.com/'> "
89 " <INPUT type = 'text' id = 'username'/> "
90 " <INPUT type = 'password' id = 'first_password' "
91 " autocomplete = 'off' size = 5/>"
92 " <INPUT type = 'password' id = 'second_password' size = 5/> "
93 " <INPUT type = 'text' id = 'address'/> "
94 " <INPUT type = 'submit' value = 'LOGIN' />"
97 const char kHiddenPasswordAccountCreationFormHTML
[] =
98 "<FORM name = 'blah' action = 'http://www.random.com/'> "
99 " <INPUT type = 'text' id = 'username'/> "
100 " <INPUT type = 'password' id = 'first_password'/> "
101 " <INPUT type = 'password' id = 'second_password' style='display:none'/> "
102 " <INPUT type = 'submit' value = 'LOGIN' />"
105 const char kInvalidActionAccountCreationFormHTML
[] =
106 "<FORM name = 'blah' action = 'invalid'> "
107 " <INPUT type = 'text' id = 'username'/> "
108 " <INPUT type = 'password' id = 'first_password'/> "
109 " <INPUT type = 'password' id = 'second_password'/> "
110 " <INPUT type = 'submit' value = 'LOGIN' />"
113 TEST_F(PasswordGenerationAgentTest
, DetectionTest
) {
114 // Don't shown the icon for non account creation forms.
115 LoadHTML(kSigninFormHTML
);
116 ExpectPasswordGenerationAvailable("password", false);
118 // We don't show the decoration yet because the feature isn't enabled.
119 LoadHTML(kAccountCreationFormHTML
);
120 ExpectPasswordGenerationAvailable("first_password", false);
122 // Pretend like We have received message indicating site is not blacklisted,
123 // and we have received message indicating the form is classified as
124 // ACCOUNT_CREATION_FORM form Autofill server. We should show the icon.
125 LoadHTML(kAccountCreationFormHTML
);
126 SetNotBlacklistedMessage(kAccountCreationFormHTML
);
127 SetAccountCreationFormsDetectedMessage(kAccountCreationFormHTML
);
128 ExpectPasswordGenerationAvailable("first_password", true);
130 // This doesn't trigger because hidden password fields are ignored.
131 LoadHTML(kHiddenPasswordAccountCreationFormHTML
);
132 SetNotBlacklistedMessage(kHiddenPasswordAccountCreationFormHTML
);
133 SetAccountCreationFormsDetectedMessage(
134 kHiddenPasswordAccountCreationFormHTML
);
135 ExpectPasswordGenerationAvailable("first_password", false);
137 // This doesn't trigger because the form action is invalid.
138 LoadHTML(kInvalidActionAccountCreationFormHTML
);
139 SetNotBlacklistedMessage(kInvalidActionAccountCreationFormHTML
);
140 SetAccountCreationFormsDetectedMessage(kInvalidActionAccountCreationFormHTML
);
141 ExpectPasswordGenerationAvailable("first_password", false);
144 TEST_F(PasswordGenerationAgentTest
, FillTest
) {
145 // Make sure that we are enabled before loading HTML.
146 LoadHTML(kAccountCreationFormHTML
);
148 WebDocument document
= GetMainFrame()->document();
150 document
.getElementById(WebString::fromUTF8("first_password"));
151 ASSERT_FALSE(element
.isNull());
152 WebInputElement first_password_element
= element
.to
<WebInputElement
>();
153 element
= document
.getElementById(WebString::fromUTF8("second_password"));
154 ASSERT_FALSE(element
.isNull());
155 WebInputElement second_password_element
= element
.to
<WebInputElement
>();
157 // Both password fields should be empty.
158 EXPECT_TRUE(first_password_element
.value().isNull());
159 EXPECT_TRUE(second_password_element
.value().isNull());
161 base::string16 password
= base::ASCIIToUTF16("random_password");
162 AutofillMsg_GeneratedPasswordAccepted
msg(0, password
);
163 password_generation_
->OnMessageReceived(msg
);
165 // Password fields are filled out and set as being autofilled.
166 EXPECT_EQ(password
, first_password_element
.value());
167 EXPECT_EQ(password
, second_password_element
.value());
168 EXPECT_TRUE(first_password_element
.isAutofilled());
169 EXPECT_TRUE(second_password_element
.isAutofilled());
171 // Focus moved to the next input field.
172 // TODO(zysxqn): Change this back to the address element once Bug 90224
173 // https://bugs.webkit.org/show_bug.cgi?id=90224 has been fixed.
174 element
= document
.getElementById(WebString::fromUTF8("first_password"));
175 ASSERT_FALSE(element
.isNull());
176 EXPECT_EQ(element
, document
.focusedElement());
179 TEST_F(PasswordGenerationAgentTest
, EditingTest
) {
180 LoadHTML(kAccountCreationFormHTML
);
181 SetNotBlacklistedMessage(kAccountCreationFormHTML
);
182 SetAccountCreationFormsDetectedMessage(kAccountCreationFormHTML
);
184 WebDocument document
= GetMainFrame()->document();
186 document
.getElementById(WebString::fromUTF8("first_password"));
187 ASSERT_FALSE(element
.isNull());
188 WebInputElement first_password_element
= element
.to
<WebInputElement
>();
189 element
= document
.getElementById(WebString::fromUTF8("second_password"));
190 ASSERT_FALSE(element
.isNull());
191 WebInputElement second_password_element
= element
.to
<WebInputElement
>();
193 base::string16 password
= base::ASCIIToUTF16("random_password");
194 AutofillMsg_GeneratedPasswordAccepted
msg(0, password
);
195 password_generation_
->OnMessageReceived(msg
);
197 // Passwords start out the same.
198 EXPECT_EQ(password
, first_password_element
.value());
199 EXPECT_EQ(password
, second_password_element
.value());
201 // After editing the first field they are still the same.
202 base::string16 edited_password
= base::ASCIIToUTF16("edited_password");
203 first_password_element
.setValue(edited_password
);
204 // Cast to WebAutofillClient where textFieldDidChange() is public.
205 static_cast<blink::WebAutofillClient
*>(autofill_agent_
)->textFieldDidChange(
206 first_password_element
);
207 // textFieldDidChange posts a task, so we need to wait until it's been
209 base::MessageLoop::current()->RunUntilIdle();
210 EXPECT_EQ(edited_password
, first_password_element
.value());
211 EXPECT_EQ(edited_password
, second_password_element
.value());
213 // Verify that password mirroring works correctly even when the password
215 base::string16 empty_password
;
216 first_password_element
.setValue(empty_password
);
217 // Cast to WebAutofillClient where textFieldDidChange() is public.
218 static_cast<blink::WebAutofillClient
*>(autofill_agent_
)->textFieldDidChange(
219 first_password_element
);
220 // textFieldDidChange posts a task, so we need to wait until it's been
222 base::MessageLoop::current()->RunUntilIdle();
223 EXPECT_EQ(empty_password
, first_password_element
.value());
224 EXPECT_EQ(empty_password
, second_password_element
.value());
227 TEST_F(PasswordGenerationAgentTest
, BlacklistedTest
) {
228 // Did not receive not blacklisted message. Don't show password generation
230 LoadHTML(kAccountCreationFormHTML
);
231 SetAccountCreationFormsDetectedMessage(kAccountCreationFormHTML
);
232 ExpectPasswordGenerationAvailable("first_password", false);
234 // Receive one not blacklisted message for non account creation form. Don't
235 // show password generation icon.
236 LoadHTML(kAccountCreationFormHTML
);
237 SetNotBlacklistedMessage(kSigninFormHTML
);
238 SetAccountCreationFormsDetectedMessage(kAccountCreationFormHTML
);
239 ExpectPasswordGenerationAvailable("first_password", false);
241 // Receive one not blackliste message for account creation form. Show password
243 LoadHTML(kAccountCreationFormHTML
);
244 SetNotBlacklistedMessage(kAccountCreationFormHTML
);
245 SetAccountCreationFormsDetectedMessage(kAccountCreationFormHTML
);
246 ExpectPasswordGenerationAvailable("first_password", true);
248 // Receive two not blacklisted messages, one is for account creation form and
249 // the other is not. Show password generation icon.
250 LoadHTML(kAccountCreationFormHTML
);
251 SetNotBlacklistedMessage(kAccountCreationFormHTML
);
252 SetNotBlacklistedMessage(kSigninFormHTML
);
253 SetAccountCreationFormsDetectedMessage(kAccountCreationFormHTML
);
254 ExpectPasswordGenerationAvailable("first_password", true);
257 TEST_F(PasswordGenerationAgentTest
, AccountCreationFormsDetectedTest
) {
258 // Did not receive account creation forms detected messege. Don't show
259 // password generation icon.
260 LoadHTML(kAccountCreationFormHTML
);
261 SetNotBlacklistedMessage(kAccountCreationFormHTML
);
262 ExpectPasswordGenerationAvailable("first_password", false);
264 // Receive the account creation forms detected message. Show password
266 LoadHTML(kAccountCreationFormHTML
);
267 SetNotBlacklistedMessage(kAccountCreationFormHTML
);
268 SetAccountCreationFormsDetectedMessage(kAccountCreationFormHTML
);
269 ExpectPasswordGenerationAvailable("first_password", true);
272 TEST_F(PasswordGenerationAgentTest
, MaximumOfferSize
) {
273 base::HistogramTester histogram_tester
;
275 LoadHTML(kAccountCreationFormHTML
);
276 SetNotBlacklistedMessage(kAccountCreationFormHTML
);
277 SetAccountCreationFormsDetectedMessage(kAccountCreationFormHTML
);
278 ExpectPasswordGenerationAvailable("first_password", true);
280 WebDocument document
= GetMainFrame()->document();
282 document
.getElementById(WebString::fromUTF8("first_password"));
283 ASSERT_FALSE(element
.isNull());
284 WebInputElement first_password_element
= element
.to
<WebInputElement
>();
286 // Make a password just under maximum offer size.
287 first_password_element
.setValue(
289 std::string(password_generation_
->kMaximumOfferSize
- 1, 'a')));
290 // Cast to WebAutofillClient where textFieldDidChange() is public.
291 static_cast<blink::WebAutofillClient
*>(autofill_agent_
)->textFieldDidChange(
292 first_password_element
);
293 // textFieldDidChange posts a task, so we need to wait until it's been
295 base::MessageLoop::current()->RunUntilIdle();
296 // There should now be a message to show the UI.
297 ASSERT_EQ(1u, password_generation_
->messages().size());
298 EXPECT_EQ(AutofillHostMsg_ShowPasswordGenerationPopup::ID
,
299 password_generation_
->messages()[0]->type());
300 password_generation_
->clear_messages();
302 // Simulate a user typing a password just over maximum offer size.
303 first_password_element
.setValue(
305 std::string(password_generation_
->kMaximumOfferSize
+ 1, 'a')));
306 // Cast to WebAutofillClient where textFieldDidChange() is public.
307 static_cast<blink::WebAutofillClient
*>(autofill_agent_
)->textFieldDidChange(
308 first_password_element
);
309 // textFieldDidChange posts a task, so we need to wait until it's been
311 base::MessageLoop::current()->RunUntilIdle();
312 // There should now be a message to hide the UI.
313 ASSERT_EQ(1u, password_generation_
->messages().size());
314 EXPECT_EQ(AutofillHostMsg_HidePasswordGenerationPopup::ID
,
315 password_generation_
->messages()[0]->type());
316 password_generation_
->clear_messages();
318 // Simulate the user deleting characters. The generation popup should be shown
320 first_password_element
.setValue(
322 std::string(password_generation_
->kMaximumOfferSize
, 'a')));
323 // Cast to WebAutofillClient where textFieldDidChange() is public.
324 static_cast<blink::WebAutofillClient
*>(autofill_agent_
)->textFieldDidChange(
325 first_password_element
);
326 // textFieldDidChange posts a task, so we need to wait until it's been
328 base::MessageLoop::current()->RunUntilIdle();
329 // There should now be a message to show the UI.
330 ASSERT_EQ(1u, password_generation_
->messages().size());
331 EXPECT_EQ(AutofillHostMsg_ShowPasswordGenerationPopup::ID
,
332 password_generation_
->messages()[0]->type());
333 password_generation_
->clear_messages();
335 // Change focus. Bubble should be hidden, but that is handled by AutofilAgent,
336 // so no messages are sent.
337 ExecuteJavaScript("document.getElementById('username').focus();");
338 EXPECT_EQ(0u, password_generation_
->messages().size());
339 password_generation_
->clear_messages();
341 // Focusing the password field will bring up the generation UI again.
342 ExecuteJavaScript("document.getElementById('first_password').focus();");
343 EXPECT_EQ(1u, password_generation_
->messages().size());
344 EXPECT_EQ(AutofillHostMsg_ShowPasswordGenerationPopup::ID
,
345 password_generation_
->messages()[0]->type());
346 password_generation_
->clear_messages();
348 // Loading a different page triggers UMA stat upload. Verify that only one
349 // display event is sent even though
350 LoadHTML(kSigninFormHTML
);
352 histogram_tester
.ExpectBucketCount(
353 "PasswordGeneration.Event",
354 autofill::password_generation::GENERATION_POPUP_SHOWN
,
358 TEST_F(PasswordGenerationAgentTest
, DynamicFormTest
) {
359 LoadHTML(kSigninFormHTML
);
360 SetNotBlacklistedMessage(kSigninFormHTML
);
361 SetAccountCreationFormsDetectedMessage(kSigninFormHTML
);
364 "var form = document.createElement('form');"
365 "var username = document.createElement('input');"
366 "username.type = 'text';"
367 "username.id = 'dynamic_username';"
368 "var first_password = document.createElement('input');"
369 "first_password.type = 'password';"
370 "first_password.id = 'first_password';"
371 "first_password.name = 'first_password';"
372 "var second_password = document.createElement('input');"
373 "second_password.type = 'password';"
374 "second_password.id = 'second_password';"
375 "second_password.name = 'second_password';"
376 "form.appendChild(username);"
377 "form.appendChild(first_password);"
378 "form.appendChild(second_password);"
379 "document.body.appendChild(form);");
380 ProcessPendingMessages();
381 // TODO(gcasto): I'm slighty worried about flakes in this test where
382 // didAssociateFormControls() isn't called. If this turns out to be a problem
383 // adding a call to OnDynamicFormsSeen(GetMainFrame()) will fix it, though
384 // it will weaken the test.
385 ExpectPasswordGenerationAvailable("first_password", true);
388 } // namespace autofill