OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011 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/string_util.h" | |
6 #include "base/utf_string_conversions.h" | |
7 #include "chrome/common/autofill_messages.h" | |
8 #include "chrome/renderer/autofill/autofill_agent.h" | |
9 #include "chrome/renderer/autofill/password_autofill_manager.h" | |
10 #include "chrome/test/base/render_view_test.h" | |
11 #include "testing/gtest/include/gtest/gtest.h" | |
12 #include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h" | |
13 #include "third_party/WebKit/Source/WebKit/chromium/public/WebElement.h" | |
14 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFormElement.h" | |
15 #include "third_party/WebKit/Source/WebKit/chromium/public/WebInputElement.h" | |
16 #include "third_party/WebKit/Source/WebKit/chromium/public/WebNode.h" | |
17 #include "third_party/WebKit/Source/WebKit/chromium/public/WebString.h" | |
18 #include "third_party/WebKit/Source/WebKit/chromium/public/WebVector.h" | |
19 #include "ui/base/keycodes/keyboard_codes.h" | |
20 #include "webkit/glue/form_data.h" | |
21 #include "webkit/glue/form_field.h" | |
22 | |
23 using webkit_glue::FormField; | |
24 using webkit_glue::PasswordFormFillData; | |
25 using webkit_glue::PasswordForm; | |
26 using webkit_glue::PasswordFormDomManager; | |
27 using WebKit::WebDocument; | |
28 using WebKit::WebElement; | |
29 using WebKit::WebFrame; | |
30 using WebKit::WebInputElement; | |
31 using WebKit::WebString; | |
32 | |
33 namespace { | |
34 | |
35 // The name of the username/password element in the form. | |
36 const char* const kUsernameName = "username"; | |
37 const char* const kPasswordName = "password"; | |
38 | |
39 const char* const kAliceUsername = "alice"; | |
40 const char* const kAlicePassword = "password"; | |
41 const char* const kBobUsername = "bob"; | |
42 const char* const kBobPassword = "secret"; | |
43 const char* const kCarolUsername = "Carol"; | |
44 const char* const kCarolPassword = "test"; | |
45 | |
46 | |
47 const char* const kFormHTML = | |
48 "<FORM name='LoginTestForm' action='http://www.bidule.com'>" | |
49 " <INPUT type='text' id='username'/>" | |
50 " <INPUT type='password' id='password'/>" | |
51 " <INPUT type='submit' value='Login'/>" | |
52 "</FORM>"; | |
53 | |
54 } // namespace | |
55 | |
56 namespace autofill { | |
57 | |
58 class PasswordAutofillManagerTest : public RenderViewTest { | |
59 public: | |
60 PasswordAutofillManagerTest() { | |
61 } | |
62 | |
63 // Simulates the fill password form message being sent to the renderer. | |
64 // We use that so we don't have to make RenderView::OnFillPasswordForm() | |
65 // protected. | |
66 void SimulateOnFillPasswordForm( | |
67 const PasswordFormFillData& fill_data) { | |
68 AutofillMsg_FillPasswordForm msg(0, fill_data); | |
69 password_autofill_->OnMessageReceived(msg); | |
70 } | |
71 | |
72 virtual void SetUp() { | |
73 RenderViewTest::SetUp(); | |
74 | |
75 // Add a preferred login and an additional login to the FillData. | |
76 username1_ = ASCIIToUTF16(kAliceUsername); | |
77 password1_ = ASCIIToUTF16(kAlicePassword); | |
78 username2_ = ASCIIToUTF16(kBobUsername); | |
79 password2_ = ASCIIToUTF16(kBobPassword); | |
80 username3_ = ASCIIToUTF16(kCarolUsername); | |
81 password3_ = ASCIIToUTF16(kCarolPassword); | |
82 | |
83 fill_data_.basic_data.fields.push_back( | |
84 FormField(string16(), ASCIIToUTF16(kUsernameName), | |
85 username1_, string16(), 0, false)); | |
86 fill_data_.basic_data.fields.push_back( | |
87 FormField(string16(), ASCIIToUTF16(kPasswordName), | |
88 password1_, string16(), 0, false)); | |
89 fill_data_.additional_logins[username2_] = password2_; | |
90 fill_data_.additional_logins[username3_] = password3_; | |
91 | |
92 // We need to set the origin so it matches the frame URL and the action so | |
93 // it matches the form action, otherwise we won't autocomplete. | |
94 std::string origin("data:text/html;charset=utf-8,"); | |
95 origin += kFormHTML; | |
96 fill_data_.basic_data.origin = GURL(origin); | |
97 fill_data_.basic_data.action = GURL("http://www.bidule.com"); | |
98 | |
99 LoadHTML(kFormHTML); | |
100 | |
101 // Now retrieves the input elements so the test can access them. | |
102 WebDocument document = GetMainFrame()->document(); | |
103 WebElement element = | |
104 document.getElementById(WebString::fromUTF8(kUsernameName)); | |
105 ASSERT_FALSE(element.isNull()); | |
106 username_element_ = element.to<WebKit::WebInputElement>(); | |
107 element = document.getElementById(WebString::fromUTF8(kPasswordName)); | |
108 ASSERT_FALSE(element.isNull()); | |
109 password_element_ = element.to<WebKit::WebInputElement>(); | |
110 } | |
111 | |
112 void ClearUsernameAndPasswordFields() { | |
113 username_element_.setValue(""); | |
114 username_element_.setAutofilled(false); | |
115 password_element_.setValue(""); | |
116 password_element_.setAutofilled(false); | |
117 } | |
118 | |
119 void SimulateUsernameChange(const std::string& username, | |
120 bool move_caret_to_end) { | |
121 username_element_.setValue(WebString::fromUTF8(username)); | |
122 if (move_caret_to_end) | |
123 username_element_.setSelectionRange(username.length(), username.length()); | |
124 autofill_agent_->textFieldDidChange(username_element_); | |
125 // Processing is delayed because of a WebKit bug, see | |
126 // PasswordAutocompleteManager::TextDidChangeInTextField() for details. | |
127 MessageLoop::current()->RunAllPending(); | |
128 } | |
129 | |
130 void SimulateKeyDownEvent(const WebInputElement& element, | |
131 ui::KeyboardCode key_code) { | |
132 WebKit::WebKeyboardEvent key_event; | |
133 key_event.windowsKeyCode = key_code; | |
134 autofill_agent_->textFieldDidReceiveKeyDown(element, key_event); | |
135 } | |
136 | |
137 void CheckTextFieldsState(const std::string& username, | |
138 bool username_autofilled, | |
139 const std::string& password, | |
140 bool password_autofilled) { | |
141 EXPECT_EQ(username, | |
142 static_cast<std::string>(username_element_.value().utf8())); | |
143 EXPECT_EQ(username_autofilled, username_element_.isAutofilled()); | |
144 EXPECT_EQ(password, | |
145 static_cast<std::string>(password_element_.value().utf8())); | |
146 EXPECT_EQ(password_autofilled, password_element_.isAutofilled()); | |
147 } | |
148 | |
149 void CheckUsernameSelection(int start, int end) { | |
150 EXPECT_EQ(start, username_element_.selectionStart()); | |
151 EXPECT_EQ(end, username_element_.selectionEnd()); | |
152 } | |
153 | |
154 string16 username1_; | |
155 string16 username2_; | |
156 string16 username3_; | |
157 string16 password1_; | |
158 string16 password2_; | |
159 string16 password3_; | |
160 PasswordFormFillData fill_data_; | |
161 | |
162 WebInputElement username_element_; | |
163 WebInputElement password_element_; | |
164 | |
165 private: | |
166 DISALLOW_COPY_AND_ASSIGN(PasswordAutofillManagerTest); | |
167 }; | |
168 | |
169 } | |
170 | |
171 using autofill::PasswordAutofillManagerTest; | |
172 | |
173 // Tests that the password login is autocompleted as expected when the browser | |
174 // sends back the password info. | |
175 TEST_F(PasswordAutofillManagerTest, InitialAutocomplete) { | |
176 /* | |
177 * Right now we are not sending the message to the browser because we are | |
178 * loading a data URL and the security origin canAccessPasswordManager() | |
179 * returns false. May be we should mock URL loading to cirmcuvent this? | |
180 TODO(jcivelli): find a way to make the security origin not deny access to the | |
181 password manager and then reenable this code. | |
182 | |
183 // The form has been loaded, we should have sent the browser a message about | |
184 // the form. | |
185 const IPC::Message* msg = render_thread_.sink().GetFirstMessageMatching( | |
186 AutofillHostMsg_PasswordFormsFound::ID); | |
187 ASSERT_TRUE(msg != NULL); | |
188 | |
189 Tuple1<std::vector<PasswordForm> > forms; | |
190 AutofillHostMsg_PasswordFormsFound::Read(msg, &forms); | |
191 ASSERT_EQ(1U, forms.a.size()); | |
192 PasswordForm password_form = forms.a[0]; | |
193 EXPECT_EQ(PasswordForm::SCHEME_HTML, password_form.scheme); | |
194 EXPECT_EQ(ASCIIToUTF16(kUsernameName), password_form.username_element); | |
195 EXPECT_EQ(ASCIIToUTF16(kPasswordName), password_form.password_element); | |
196 */ | |
197 | |
198 // Simulate the browser sending back the login info, it triggers the | |
199 // autocomplete. | |
200 SimulateOnFillPasswordForm(fill_data_); | |
201 | |
202 // The username and password should have been autocompleted. | |
203 CheckTextFieldsState(kAliceUsername, true, kAlicePassword, true); | |
204 } | |
205 | |
206 // Tests that we correctly fill forms having an empty 'action' attribute. | |
207 TEST_F(PasswordAutofillManagerTest, InitialAutocompleteForEmptyAction) { | |
208 const char kEmptyActionFormHTML[] = | |
209 "<FORM name='LoginTestForm'>" | |
210 " <INPUT type='text' id='username'/>" | |
211 " <INPUT type='password' id='password'/>" | |
212 " <INPUT type='submit' value='Login'/>" | |
213 "</FORM>"; | |
214 LoadHTML(kEmptyActionFormHTML); | |
215 | |
216 // Retrieve the input elements so the test can access them. | |
217 WebDocument document = GetMainFrame()->document(); | |
218 WebElement element = | |
219 document.getElementById(WebString::fromUTF8(kUsernameName)); | |
220 ASSERT_FALSE(element.isNull()); | |
221 username_element_ = element.to<WebKit::WebInputElement>(); | |
222 element = document.getElementById(WebString::fromUTF8(kPasswordName)); | |
223 ASSERT_FALSE(element.isNull()); | |
224 password_element_ = element.to<WebKit::WebInputElement>(); | |
225 | |
226 // Set the expected form origin and action URLs. | |
227 std::string origin("data:text/html;charset=utf-8,"); | |
228 origin += kEmptyActionFormHTML; | |
229 fill_data_.basic_data.origin = GURL(origin); | |
230 fill_data_.basic_data.action = GURL(origin); | |
231 | |
232 // Simulate the browser sending back the login info, it triggers the | |
233 // autocomplete. | |
234 SimulateOnFillPasswordForm(fill_data_); | |
235 | |
236 // The username and password should have been autocompleted. | |
237 CheckTextFieldsState(kAliceUsername, true, kAlicePassword, true); | |
238 } | |
239 | |
240 // Tests that changing the username does not fill a read-only password field. | |
241 TEST_F(PasswordAutofillManagerTest, NoInitialAutocompleteForReadOnly) { | |
242 password_element_.setAttribute(WebString::fromUTF8("readonly"), | |
243 WebString::fromUTF8("true")); | |
244 | |
245 // Simulate the browser sending back the login info, it triggers the | |
246 // autocomplete. | |
247 SimulateOnFillPasswordForm(fill_data_); | |
248 | |
249 // Only the username should have been autocompleted. | |
250 // TODO(jcivelli): may be we should not event fill the username? | |
251 CheckTextFieldsState(kAliceUsername, true, "", false); | |
252 } | |
253 | |
254 // Tests that having a non-matching username precludes the autocomplete. | |
255 TEST_F(PasswordAutofillManagerTest, NoInitialAutocompleteForFilledField) { | |
256 username_element_.setValue(WebString::fromUTF8("bogus")); | |
257 | |
258 // Simulate the browser sending back the login info, it triggers the | |
259 // autocomplete. | |
260 SimulateOnFillPasswordForm(fill_data_); | |
261 | |
262 // Neither field should be autocompleted. | |
263 CheckTextFieldsState("bogus", false, "", false); | |
264 } | |
265 | |
266 // Tests that having a matching username does not preclude the autocomplete. | |
267 TEST_F(PasswordAutofillManagerTest, InitialAutocompleteForMatchingFilledField) { | |
268 username_element_.setValue(WebString::fromUTF8(kAliceUsername)); | |
269 | |
270 // Simulate the browser sending back the login info, it triggers the | |
271 // autocomplete. | |
272 SimulateOnFillPasswordForm(fill_data_); | |
273 | |
274 // The username and password should have been autocompleted. | |
275 CheckTextFieldsState(kAliceUsername, true, kAlicePassword, true); | |
276 } | |
277 | |
278 // Tests that editing the password clears the autocompleted password field. | |
279 TEST_F(PasswordAutofillManagerTest, PasswordClearOnEdit) { | |
280 // Simulate the browser sending back the login info, it triggers the | |
281 // autocomplete. | |
282 SimulateOnFillPasswordForm(fill_data_); | |
283 | |
284 // Simulate the user changing the username to some unknown username. | |
285 SimulateUsernameChange("alicia", true); | |
286 | |
287 // The password should have been cleared. | |
288 CheckTextFieldsState("alicia", false, "", false); | |
289 } | |
290 | |
291 // Tests that we only autocomplete on focus lost and with a full username match | |
292 // when |wait_for_username| is true. | |
293 TEST_F(PasswordAutofillManagerTest, WaitUsername) { | |
294 // Simulate the browser sending back the login info. | |
295 fill_data_.wait_for_username = true; | |
296 SimulateOnFillPasswordForm(fill_data_); | |
297 | |
298 // No auto-fill should have taken place. | |
299 CheckTextFieldsState("", false, "", false); | |
300 | |
301 // No autocomplete should happen when text is entered in the username. | |
302 SimulateUsernameChange("a", true); | |
303 CheckTextFieldsState("a", false, "", false); | |
304 SimulateUsernameChange("al", true); | |
305 CheckTextFieldsState("al", false, "", false); | |
306 SimulateUsernameChange(kAliceUsername, true); | |
307 CheckTextFieldsState(kAliceUsername, false, "", false); | |
308 | |
309 // Autocomplete should happen only when the username textfield is blurred with | |
310 // a full match. | |
311 username_element_.setValue("a"); | |
312 autofill_agent_->textFieldDidEndEditing(username_element_); | |
313 CheckTextFieldsState("a", false, "", false); | |
314 username_element_.setValue("al"); | |
315 autofill_agent_->textFieldDidEndEditing(username_element_); | |
316 CheckTextFieldsState("al", false, "", false); | |
317 username_element_.setValue("alices"); | |
318 autofill_agent_->textFieldDidEndEditing(username_element_); | |
319 CheckTextFieldsState("alices", false, "", false); | |
320 username_element_.setValue(ASCIIToUTF16(kAliceUsername)); | |
321 autofill_agent_->textFieldDidEndEditing(username_element_); | |
322 CheckTextFieldsState(kAliceUsername, true, kAlicePassword, true); | |
323 } | |
324 | |
325 // Tests that inline autocompletion works properly. | |
326 TEST_F(PasswordAutofillManagerTest, InlineAutocomplete) { | |
327 // Simulate the browser sending back the login info. | |
328 SimulateOnFillPasswordForm(fill_data_); | |
329 | |
330 // Clear the text fields to start fresh. | |
331 ClearUsernameAndPasswordFields(); | |
332 | |
333 // Simulate the user typing in the first letter of 'alice', a stored username. | |
334 SimulateUsernameChange("a", true); | |
335 // Both the username and password text fields should reflect selection of the | |
336 // stored login. | |
337 CheckTextFieldsState(kAliceUsername, true, kAlicePassword, true); | |
338 // And the selection should have been set to 'lice', the last 4 letters. | |
339 CheckUsernameSelection(1, 5); | |
340 | |
341 // Now the user types the next letter of the same username, 'l'. | |
342 SimulateUsernameChange("al", true); | |
343 // Now the fields should have the same value, but the selection should have a | |
344 // different start value. | |
345 CheckTextFieldsState(kAliceUsername, true, kAlicePassword, true); | |
346 CheckUsernameSelection(2, 5); | |
347 | |
348 // Test that deleting does not trigger autocomplete. | |
349 SimulateKeyDownEvent(username_element_, ui::VKEY_BACK); | |
350 SimulateUsernameChange("alic", true); | |
351 CheckTextFieldsState("alic", false, "", false); | |
352 CheckUsernameSelection(4, 4); // No selection. | |
353 // Reset the last pressed key to something other than backspace. | |
354 SimulateKeyDownEvent(username_element_, ui::VKEY_A); | |
355 | |
356 // Now lets say the user goes astray from the stored username and types the | |
357 // letter 'f', spelling 'alf'. We don't know alf (that's just sad), so in | |
358 // practice the username should no longer be 'alice' and the selected range | |
359 // should be empty. | |
360 SimulateUsernameChange("alf", true); | |
361 CheckTextFieldsState("alf", false, "", false); | |
362 CheckUsernameSelection(3, 3); // No selection. | |
363 | |
364 // Ok, so now the user removes all the text and enters the letter 'b'. | |
365 SimulateUsernameChange("b", true); | |
366 // The username and password fields should match the 'bob' entry. | |
367 CheckTextFieldsState(kBobUsername, true, kBobPassword, true); | |
368 CheckUsernameSelection(1, 3); | |
369 | |
370 // Then, the user again removes all the text and types an uppercase 'C'. | |
371 SimulateUsernameChange("C", true); | |
372 // The username and password fields should match the 'Carol' entry. | |
373 CheckTextFieldsState(kCarolUsername, true, kCarolPassword, true); | |
374 CheckUsernameSelection(1, 5); | |
375 // Finally, the user removes all the text and types a lowercase 'c'. We only | |
376 // want case-sensitive autocompletion, so the username and the selected range | |
377 // should be empty. | |
378 SimulateUsernameChange("c", true); | |
379 CheckTextFieldsState("c", false, "", false); | |
380 CheckUsernameSelection(1, 1); | |
381 } | |
382 | |
383 // Tests that accepting an item in the suggestion drop-down works. | |
384 TEST_F(PasswordAutofillManagerTest, SuggestionAccept) { | |
385 // Simulate the browser sending back the login info. | |
386 SimulateOnFillPasswordForm(fill_data_); | |
387 | |
388 // Clear the text fields to start fresh. | |
389 ClearUsernameAndPasswordFields(); | |
390 | |
391 // To simulate accepting an item in the suggestion drop-down we just mimic | |
392 // what the WebView does: it sets the element value then calls | |
393 // didAcceptAutofillSuggestion on the renderer. | |
394 autofill_agent_->didAcceptAutofillSuggestion(username_element_, | |
395 ASCIIToUTF16(kAliceUsername), | |
396 WebKit::WebString(), | |
397 0, | |
398 0); | |
399 // Autocomplete should have kicked in. | |
400 CheckTextFieldsState(kAliceUsername, true, kAlicePassword, true); | |
401 } | |
402 | |
403 // Tests that selecting an item in the suggestion drop-down no-ops. | |
404 TEST_F(PasswordAutofillManagerTest, SuggestionSelect) { | |
405 // Simulate the browser sending back the login info. | |
406 SimulateOnFillPasswordForm(fill_data_); | |
407 | |
408 // Clear the text fields to start fresh. | |
409 ClearUsernameAndPasswordFields(); | |
410 | |
411 // To simulate accepting an item in the suggestion drop-down we just mimic | |
412 // what the WebView does: it sets the element value then calls | |
413 // didSelectAutofillSuggestion on the renderer. | |
414 autofill_agent_->didSelectAutofillSuggestion(username_element_, | |
415 ASCIIToUTF16(kAliceUsername), | |
416 WebKit::WebString(), | |
417 0); | |
418 // Autocomplete should not have kicked in. | |
419 CheckTextFieldsState("", false, "", false); | |
420 } | |
OLD | NEW |