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. 4 5 #include <string.h> 6 7 #include "base/memory/scoped_ptr.h" 8 #include "base/strings/utf_string_conversions.h" 9 #include "chrome/test/base/chrome_render_view_test.h" 10 #include "components/autofill/content/common/autofill_messages.h" 11 #include "components/autofill/content/renderer/autofill_agent.h" 12 #include "components/autofill/content/renderer/test_password_generation_agent.h" 13 #include "components/autofill/core/common/form_data.h" 14 #include "testing/gtest/include/gtest/gtest.h" 15 #include "third_party/WebKit/public/platform/WebString.h" 16 #include "third_party/WebKit/public/web/WebDocument.h" 17 #include "third_party/WebKit/public/web/WebLocalFrame.h" 18 #include "third_party/WebKit/public/web/WebWidget.h" 19 20 using blink::WebDocument; 21 using blink::WebElement; 22 using blink::WebInputElement; 23 using blink::WebNode; 24 using blink::WebString; 25 26 namespace autofill { 27 28 class PasswordGenerationAgentTest : public ChromeRenderViewTest { 29 public: 30 PasswordGenerationAgentTest() {} 31 32 virtual void TearDown() { 33 LoadHTML(""); 34 ChromeRenderViewTest::TearDown(); 35 } 36 37 void SetNotBlacklistedMessage(const char* form_str) { 38 autofill::PasswordForm form; 39 form.origin = 40 GURL(base::StringPrintf("data:text/html;charset=utf-8,%s", form_str)); 41 AutofillMsg_FormNotBlacklisted msg(0, form); 42 password_generation_->OnMessageReceived(msg); 43 } 44 45 void SetAccountCreationFormsDetectedMessage(const char* form_str) { 46 autofill::FormData form; 47 form.origin = 48 GURL(base::StringPrintf("data:text/html;charset=utf-8,%s", form_str)); 49 std::vector<autofill::FormData> forms; 50 forms.push_back(form); 51 AutofillMsg_AccountCreationFormsDetected msg(0, forms); 52 password_generation_->OnMessageReceived(msg); 53 } 54 55 void ExpectPasswordGenerationAvailable(const char* element_id, 56 bool available) { 57 WebDocument document = GetMainFrame()->document(); 58 WebElement element = 59 document.getElementById(WebString::fromUTF8(element_id)); 60 ASSERT_FALSE(element.isNull()); 61 ExecuteJavaScript( 62 base::StringPrintf("document.getElementById('%s').focus();", 63 element_id).c_str()); 64 if (available) { 65 ASSERT_EQ(1u, password_generation_->messages().size()); 66 EXPECT_EQ(AutofillHostMsg_ShowPasswordGenerationPopup::ID, 67 password_generation_->messages()[0]->type()); 68 } else { 69 EXPECT_EQ(0u, password_generation_->messages().size()); 70 } 71 password_generation_->clear_messages(); 72 } 73 74 private: 75 DISALLOW_COPY_AND_ASSIGN(PasswordGenerationAgentTest); 76 }; 77 78 const char kSigninFormHTML[] = 79 "<FORM name = 'blah' action = 'http://www.random.com/'> " 80 " <INPUT type = 'text' id = 'username'/> " 81 " <INPUT type = 'password' id = 'password'/> " 82 " <INPUT type = 'submit' value = 'LOGIN' />" 83 "</FORM>"; 84 85 const char kAccountCreationFormHTML[] = 86 "<FORM name = 'blah' action = 'http://www.random.com/'> " 87 " <INPUT type = 'text' id = 'username'/> " 88 " <INPUT type = 'password' id = 'first_password' " 89 " autocomplete = 'off' size = 5/>" 90 " <INPUT type = 'password' id = 'second_password' size = 5/> " 91 " <INPUT type = 'text' id = 'address'/> " 92 " <INPUT type = 'submit' value = 'LOGIN' />" 93 "</FORM>"; 94 95 const char kHiddenPasswordAccountCreationFormHTML[] = 96 "<FORM name = 'blah' action = 'http://www.random.com/'> " 97 " <INPUT type = 'text' id = 'username'/> " 98 " <INPUT type = 'password' id = 'first_password'/> " 99 " <INPUT type = 'password' id = 'second_password' style='display:none'/> " 100 " <INPUT type = 'submit' value = 'LOGIN' />" 101 "</FORM>"; 102 103 const char kInvalidActionAccountCreationFormHTML[] = 104 "<FORM name = 'blah' action = 'invalid'> " 105 " <INPUT type = 'text' id = 'username'/> " 106 " <INPUT type = 'password' id = 'first_password'/> " 107 " <INPUT type = 'password' id = 'second_password'/> " 108 " <INPUT type = 'submit' value = 'LOGIN' />" 109 "</FORM>"; 110 111 TEST_F(PasswordGenerationAgentTest, DetectionTest) { 112 // Don't shown the icon for non account creation forms. 113 LoadHTML(kSigninFormHTML); 114 ExpectPasswordGenerationAvailable("password", false); 115 116 // We don't show the decoration yet because the feature isn't enabled. 117 LoadHTML(kAccountCreationFormHTML); 118 ExpectPasswordGenerationAvailable("first_password", false); 119 120 // Pretend like We have received message indicating site is not blacklisted, 121 // and we have received message indicating the form is classified as 122 // ACCOUNT_CREATION_FORM form Autofill server. We should show the icon. 123 LoadHTML(kAccountCreationFormHTML); 124 SetNotBlacklistedMessage(kAccountCreationFormHTML); 125 SetAccountCreationFormsDetectedMessage(kAccountCreationFormHTML); 126 ExpectPasswordGenerationAvailable("first_password", true); 127 128 // This doesn't trigger because hidden password fields are ignored. 129 LoadHTML(kHiddenPasswordAccountCreationFormHTML); 130 SetNotBlacklistedMessage(kHiddenPasswordAccountCreationFormHTML); 131 SetAccountCreationFormsDetectedMessage( 132 kHiddenPasswordAccountCreationFormHTML); 133 ExpectPasswordGenerationAvailable("first_password", false); 134 135 // This doesn't trigger because the form action is invalid. 136 LoadHTML(kInvalidActionAccountCreationFormHTML); 137 SetNotBlacklistedMessage(kInvalidActionAccountCreationFormHTML); 138 SetAccountCreationFormsDetectedMessage(kInvalidActionAccountCreationFormHTML); 139 ExpectPasswordGenerationAvailable("first_password", false); 140 } 141 142 TEST_F(PasswordGenerationAgentTest, FillTest) { 143 // Make sure that we are enabled before loading HTML. 144 LoadHTML(kAccountCreationFormHTML); 145 146 WebDocument document = GetMainFrame()->document(); 147 WebElement element = 148 document.getElementById(WebString::fromUTF8("first_password")); 149 ASSERT_FALSE(element.isNull()); 150 WebInputElement first_password_element = element.to<WebInputElement>(); 151 element = document.getElementById(WebString::fromUTF8("second_password")); 152 ASSERT_FALSE(element.isNull()); 153 WebInputElement second_password_element = element.to<WebInputElement>(); 154 155 // Both password fields should be empty. 156 EXPECT_TRUE(first_password_element.value().isNull()); 157 EXPECT_TRUE(second_password_element.value().isNull()); 158 159 base::string16 password = base::ASCIIToUTF16("random_password"); 160 AutofillMsg_GeneratedPasswordAccepted msg(0, password); 161 password_generation_->OnMessageReceived(msg); 162 163 // Password fields are filled out and set as being autofilled. 164 EXPECT_EQ(password, first_password_element.value()); 165 EXPECT_EQ(password, second_password_element.value()); 166 EXPECT_TRUE(first_password_element.isAutofilled()); 167 EXPECT_TRUE(second_password_element.isAutofilled()); 168 169 // Focus moved to the next input field. 170 // TODO(zysxqn): Change this back to the address element once Bug 90224 171 // https://bugs.webkit.org/show_bug.cgi?id=90224 has been fixed. 172 element = document.getElementById(WebString::fromUTF8("first_password")); 173 ASSERT_FALSE(element.isNull()); 174 EXPECT_EQ(element, document.focusedElement()); 175 } 176 177 TEST_F(PasswordGenerationAgentTest, EditingTest) { 178 LoadHTML(kAccountCreationFormHTML); 179 SetNotBlacklistedMessage(kAccountCreationFormHTML); 180 SetAccountCreationFormsDetectedMessage(kAccountCreationFormHTML); 181 182 WebDocument document = GetMainFrame()->document(); 183 WebElement element = 184 document.getElementById(WebString::fromUTF8("first_password")); 185 ASSERT_FALSE(element.isNull()); 186 WebInputElement first_password_element = element.to<WebInputElement>(); 187 element = document.getElementById(WebString::fromUTF8("second_password")); 188 ASSERT_FALSE(element.isNull()); 189 WebInputElement second_password_element = element.to<WebInputElement>(); 190 191 base::string16 password = base::ASCIIToUTF16("random_password"); 192 AutofillMsg_GeneratedPasswordAccepted msg(0, password); 193 password_generation_->OnMessageReceived(msg); 194 195 // Passwords start out the same. 196 EXPECT_EQ(password, first_password_element.value()); 197 EXPECT_EQ(password, second_password_element.value()); 198 199 // After editing the first field they are still the same. 200 base::string16 edited_password = base::ASCIIToUTF16("edited_password"); 201 first_password_element.setValue(edited_password); 202 // Cast to WebAutofillClient where textFieldDidChange() is public. 203 static_cast<blink::WebAutofillClient*>(autofill_agent_)->textFieldDidChange( 204 first_password_element); 205 // textFieldDidChange posts a task, so we need to wait until it's been 206 // processed. 207 base::MessageLoop::current()->RunUntilIdle(); 208 209 EXPECT_EQ(edited_password, first_password_element.value()); 210 EXPECT_EQ(edited_password, second_password_element.value()); 211 } 212 213 TEST_F(PasswordGenerationAgentTest, BlacklistedTest) { 214 // Did not receive not blacklisted message. Don't show password generation 215 // icon. 216 LoadHTML(kAccountCreationFormHTML); 217 SetAccountCreationFormsDetectedMessage(kAccountCreationFormHTML); 218 ExpectPasswordGenerationAvailable("first_password", false); 219 220 // Receive one not blacklisted message for non account creation form. Don't 221 // show password generation icon. 222 LoadHTML(kAccountCreationFormHTML); 223 SetNotBlacklistedMessage(kSigninFormHTML); 224 SetAccountCreationFormsDetectedMessage(kAccountCreationFormHTML); 225 ExpectPasswordGenerationAvailable("first_password", false); 226 227 // Receive one not blackliste message for account creation form. Show password 228 // generation icon. 229 LoadHTML(kAccountCreationFormHTML); 230 SetNotBlacklistedMessage(kAccountCreationFormHTML); 231 SetAccountCreationFormsDetectedMessage(kAccountCreationFormHTML); 232 ExpectPasswordGenerationAvailable("first_password", true); 233 234 // Receive two not blacklisted messages, one is for account creation form and 235 // the other is not. Show password generation icon. 236 LoadHTML(kAccountCreationFormHTML); 237 SetNotBlacklistedMessage(kAccountCreationFormHTML); 238 SetNotBlacklistedMessage(kSigninFormHTML); 239 SetAccountCreationFormsDetectedMessage(kAccountCreationFormHTML); 240 ExpectPasswordGenerationAvailable("first_password", true); 241 } 242 243 TEST_F(PasswordGenerationAgentTest, AccountCreationFormsDetectedTest) { 244 // Did not receive account creation forms detected messege. Don't show 245 // password generation icon. 246 LoadHTML(kAccountCreationFormHTML); 247 SetNotBlacklistedMessage(kAccountCreationFormHTML); 248 ExpectPasswordGenerationAvailable("first_password", false); 249 250 // Receive the account creation forms detected message. Show password 251 // generation icon. 252 LoadHTML(kAccountCreationFormHTML); 253 SetNotBlacklistedMessage(kAccountCreationFormHTML); 254 SetAccountCreationFormsDetectedMessage(kAccountCreationFormHTML); 255 ExpectPasswordGenerationAvailable("first_password", true); 256 } 257 258 TEST_F(PasswordGenerationAgentTest, MaximumOfferSize) { 259 LoadHTML(kAccountCreationFormHTML); 260 SetNotBlacklistedMessage(kAccountCreationFormHTML); 261 SetAccountCreationFormsDetectedMessage(kAccountCreationFormHTML); 262 ExpectPasswordGenerationAvailable("first_password", true); 263 264 WebDocument document = GetMainFrame()->document(); 265 WebElement element = 266 document.getElementById(WebString::fromUTF8("first_password")); 267 ASSERT_FALSE(element.isNull()); 268 WebInputElement first_password_element = element.to<WebInputElement>(); 269 270 // Make a password just under maximum offer size. 271 first_password_element.setValue( 272 base::ASCIIToUTF16( 273 std::string(password_generation_->kMaximumOfferSize - 1, 'a'))); 274 // Cast to WebAutofillClient where textFieldDidChange() is public. 275 static_cast<blink::WebAutofillClient*>(autofill_agent_)->textFieldDidChange( 276 first_password_element); 277 // textFieldDidChange posts a task, so we need to wait until it's been 278 // processed. 279 base::MessageLoop::current()->RunUntilIdle(); 280 // There should now be a message to show the UI. 281 ASSERT_EQ(1u, password_generation_->messages().size()); 282 EXPECT_EQ(AutofillHostMsg_ShowPasswordGenerationPopup::ID, 283 password_generation_->messages()[0]->type()); 284 password_generation_->clear_messages(); 285 286 // Simulate a user typing a password just over maximum offer size. 287 first_password_element.setValue( 288 base::ASCIIToUTF16( 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 294 // processed. 295 base::MessageLoop::current()->RunUntilIdle(); 296 // There should now be a message to hide the UI. 297 ASSERT_EQ(1u, password_generation_->messages().size()); 298 EXPECT_EQ(AutofillHostMsg_HidePasswordGenerationPopup::ID, 299 password_generation_->messages()[0]->type()); 300 password_generation_->clear_messages(); 301 302 // Simulate the user deleting characters. The generation popup should be shown 303 // again. 304 first_password_element.setValue( 305 base::ASCIIToUTF16( 306 std::string(password_generation_->kMaximumOfferSize, 'a'))); 307 // Cast to WebAutofillClient where textFieldDidChange() is public. 308 static_cast<blink::WebAutofillClient*>(autofill_agent_)->textFieldDidChange( 309 first_password_element); 310 // textFieldDidChange posts a task, so we need to wait until it's been 311 // processed. 312 base::MessageLoop::current()->RunUntilIdle(); 313 // There should now be a message to show the UI. 314 ASSERT_EQ(1u, password_generation_->messages().size()); 315 EXPECT_EQ(AutofillHostMsg_ShowPasswordGenerationPopup::ID, 316 password_generation_->messages()[0]->type()); 317 password_generation_->clear_messages(); 318 319 // Change focus. Bubble should be hidden, but that is handled by AutofilAgent, 320 // so no messages are sent. 321 ExecuteJavaScript("document.getElementById('username').focus();"); 322 EXPECT_EQ(0u, password_generation_->messages().size()); 323 password_generation_->clear_messages(); 324 325 // Focusing the password field will bring up the generation UI again. 326 ExecuteJavaScript("document.getElementById('first_password').focus();"); 327 EXPECT_EQ(1u, password_generation_->messages().size()); 328 EXPECT_EQ(AutofillHostMsg_ShowPasswordGenerationPopup::ID, 329 password_generation_->messages()[0]->type()); 330 password_generation_->clear_messages(); 331 } 332 333 } // namespace autofill 334