| 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 |