diff options
Diffstat (limited to 'chrome/renderer/password_autocomplete_manager_unittest.cc')
-rw-r--r-- | chrome/renderer/password_autocomplete_manager_unittest.cc | 329 |
1 files changed, 329 insertions, 0 deletions
diff --git a/chrome/renderer/password_autocomplete_manager_unittest.cc b/chrome/renderer/password_autocomplete_manager_unittest.cc new file mode 100644 index 0000000..fc32332 --- /dev/null +++ b/chrome/renderer/password_autocomplete_manager_unittest.cc @@ -0,0 +1,329 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/keyboard_codes.h" +#include "base/string_util.h" +#include "chrome/renderer/password_autocomplete_manager.h" +#include "chrome/test/render_view_test.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/WebKit/chromium/public/WebDocument.h" +#include "third_party/WebKit/WebKit/chromium/public/WebElement.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFormElement.h" +#include "third_party/WebKit/WebKit/chromium/public/WebInputElement.h" +#include "third_party/WebKit/WebKit/chromium/public/WebNode.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebVector.h" +#include "webkit/glue/form_data.h" +#include "webkit/glue/form_field.h" + +using webkit_glue::FormField; +using webkit_glue::PasswordForm; +using webkit_glue::PasswordFormDomManager; +using WebKit::WebDocument; +using WebKit::WebElement; +using WebKit::WebFrame; +using WebKit::WebInputElement; +using WebKit::WebString; + +namespace { + +// The name of the username/password element in the form. +const char* const kUsernameName = "username"; +const char* const kPasswordName = "password"; + +const char* const kAliceUsername = "alice"; +const char* const kAlicePassword = "password"; +const char* const kBobUsername = "bob"; +const char* const kBobPassword = "secret"; + +const char* const kFormHTML = + "<FORM name='LoginTestForm' action='http://www.bidule.com'>" + " <INPUT type='text' id='username'/>" + " <INPUT type='password' id='password'/>" + " <INPUT type='submit' value='Login'/>" + "</FORM>"; + +class PasswordAutocompleteManagerTest : public RenderViewTest { + public: + PasswordAutocompleteManagerTest() { + } + + // Simulates the fill password form message being sent to the renderer. + // We use that so we don't have to make RenderView::OnFillPasswordForm() + // protected. + void SimulateOnFillPasswordForm( + const PasswordFormDomManager::FillData& fill_data) { + ViewMsg_FillPasswordForm msg(0, fill_data); + view_->OnMessageReceived(msg); + } + + virtual void SetUp() { + RenderViewTest::SetUp(); + + // Add a preferred login and an additional login to the FillData. + username1_ = ASCIIToUTF16(kAliceUsername); + password1_ = ASCIIToUTF16(kAlicePassword); + username2_ = ASCIIToUTF16(kBobUsername); + password2_ = ASCIIToUTF16(kBobPassword); + + fill_data_.basic_data.fields.push_back( + FormField(string16(), ASCIIToUTF16(kUsernameName), + username1_, string16(), 0)); + fill_data_.basic_data.fields.push_back( + FormField(string16(), ASCIIToUTF16(kPasswordName), + password1_, string16(), 0)); + fill_data_.additional_logins[username2_] = password2_; + + // We need to set the origin so it matches the frame URL and the action so + // it matches the form action, otherwise we won't autocomplete. + std::string origin("data:text/html;charset=utf-8,"); + origin += kFormHTML; + fill_data_.basic_data.origin = GURL(origin); + fill_data_.basic_data.action = GURL("http://www.bidule.com"); + + LoadHTML(kFormHTML); + + // Now retrieves the input elements so the test can access them. + WebDocument document = GetMainFrame()->document(); + WebElement element = + document.getElementById(WebString::fromUTF8(kUsernameName)); + ASSERT_FALSE(element.isNull()); + username_element_ = element.to<WebKit::WebInputElement>(); + element = document.getElementById(WebString::fromUTF8(kPasswordName)); + ASSERT_FALSE(element.isNull()); + password_element_ = element.to<WebKit::WebInputElement>(); + } + + void ClearUsernameAndPasswordFields() { + username_element_.setValue(""); + username_element_.setAutofilled(false); + password_element_.setValue(""); + password_element_.setAutofilled(false); + } + + void SimulateUsernameChange(const std::string& username, + bool move_caret_to_end) { + username_element_.setValue(WebString::fromUTF8(username)); + if (move_caret_to_end) + username_element_.setSelectionRange(username.length(), username.length()); + view_->textFieldDidChange(username_element_); + // Processing is delayed because of a WebKit bug, see + // PasswordAutocompleteManager::TextDidChangeInTextField() for details. + MessageLoop::current()->RunAllPending(); + } + + void SimulateKeyDownEvent(const WebInputElement& element, + base::KeyboardCode key_code) { + WebKit::WebKeyboardEvent key_event; + key_event.windowsKeyCode = key_code; + view_->textFieldDidReceiveKeyDown(element, key_event); + } + + void CheckTextFieldsState(const std::string& username, + bool username_autofilled, + const std::string& password, + bool password_autofilled) { + EXPECT_EQ(username, + static_cast<std::string>(username_element_.value().utf8())); + EXPECT_EQ(username_autofilled, username_element_.isAutofilled()); + EXPECT_EQ(password, + static_cast<std::string>(password_element_.value().utf8())); + EXPECT_EQ(password_autofilled, password_element_.isAutofilled()); + } + + void CheckUsernameSelection(int start, int end) { + EXPECT_EQ(start, username_element_.selectionStart()); + EXPECT_EQ(end, username_element_.selectionEnd()); + } + + string16 username1_; + string16 username2_; + string16 password1_; + string16 password2_; + PasswordFormDomManager::FillData fill_data_; + + WebInputElement username_element_; + WebInputElement password_element_; + + private: + DISALLOW_COPY_AND_ASSIGN(PasswordAutocompleteManagerTest); +}; + +#if defined(WEBKIT_BUG_41283_IS_FIXED) +#define MAYBE_InitialAutocomplete InitialAutocomplete +#define MAYBE_NoInitialAutocompleteForReadOnly NoInitialAutocompleteForReadOnly +#define MAYBE_PasswordClearOnEdit PasswordClearOnEdit +#define MAYBE_WaitUsername WaitUsername +#define MAYBE_InlineAutocomplete InlineAutocomplete +#define MAYBE_SuggestionSelect SuggestionSelect +#else +#define MAYBE_InitialAutocomplete DISABLED_InitialAutocomplete +#define MAYBE_NoInitialAutocompleteForReadOnly \ + DISABLED_NoInitialAutocompleteForReadOnly +#define MAYBE_PasswordClearOnEdit DISABLED_PasswordClearOnEdit +#define MAYBE_WaitUsername DISABLED_WaitUsername +#define MAYBE_InlineAutocomplete DISABLED_InlineAutocomplete +#define MAYBE_SuggestionSelect DISABLED_SuggestionSelect +#endif + +// Tests that the password login is autocompleted as expected when the browser +// sends back the password info. +TEST_F(PasswordAutocompleteManagerTest, MAYBE_InitialAutocomplete) { + /* + * Right now we are not sending the message to the browser because we are + * loading a data URL and the security origin canAccessPasswordManager() + * returns false. May be we should mock URL loading to cirmcuvent this? + TODO(jcivelli): find a way to make the security origin not deny access to the + password manager and then reenable this code. + + // The form has been loaded, we should have sent the browser a message about + // the form. + const IPC::Message* msg = render_thread_.sink().GetFirstMessageMatching( + ViewHostMsg_PasswordFormsFound::ID); + ASSERT_TRUE(msg != NULL); + + Tuple1<std::vector<PasswordForm> > forms; + ViewHostMsg_PasswordFormsFound::Read(msg, &forms); + ASSERT_EQ(1U, forms.a.size()); + PasswordForm password_form = forms.a[0]; + EXPECT_EQ(PasswordForm::SCHEME_HTML, password_form.scheme); + EXPECT_EQ(ASCIIToUTF16(kUsernameName), password_form.username_element); + EXPECT_EQ(ASCIIToUTF16(kPasswordName), password_form.password_element); + */ + + // Simulate the browser sending back the login info, it triggers the + // autocomplete. + SimulateOnFillPasswordForm(fill_data_); + + // The username and password should have been autocompleted. + CheckTextFieldsState(kAliceUsername, true, kAlicePassword, true); +} + +// Tests that changing the username does not fill a read-only password field. +TEST_F(PasswordAutocompleteManagerTest, + MAYBE_NoInitialAutocompleteForReadOnly) { + password_element_.setAttribute(WebString::fromUTF8("readonly"), + WebString::fromUTF8("true")); + + // Simulate the browser sending back the login info, it triggers the + // autocompleted. + SimulateOnFillPasswordForm(fill_data_); + + // Only the username should have been autocompleted. + // TODO(jcivelli): may be we should not event fill the username? + CheckTextFieldsState(kAliceUsername, true, "", false); +} + +// Tests that editing the password clears the autocompleted password field. +TEST_F(PasswordAutocompleteManagerTest, MAYBE_PasswordClearOnEdit) { + // Simulate the browser sending back the login info, it triggers the + // autocomplete. + SimulateOnFillPasswordForm(fill_data_); + + // Simulate the user changing the username to some unknown username. + SimulateUsernameChange("alicia", true); + + // The password should have been cleared. + CheckTextFieldsState("alicia", false, "", false); +} + +// Tests that we only autocomplete on focus lost and with a full username match +// when |wait_for_username| is true. +TEST_F(PasswordAutocompleteManagerTest, MAYBE_WaitUsername) { + // Simulate the browser sending back the login info. + fill_data_.wait_for_username = true; + SimulateOnFillPasswordForm(fill_data_); + + // No auto-fill should have taken place. + CheckTextFieldsState("", false, "", false); + + // No autocomplete should happen when text is entered in the username. + SimulateUsernameChange("a", true); + CheckTextFieldsState("a", false, "", false); + SimulateUsernameChange("al", true); + CheckTextFieldsState("al", false, "", false); + SimulateUsernameChange(kAliceUsername, true); + CheckTextFieldsState(kAliceUsername, false, "", false); + + // Autocomplete should happen only when the username textfield is blurred with + // a full match. + username_element_.setValue("a"); + view_->textFieldDidEndEditing(username_element_); + CheckTextFieldsState("a", false, "", false); + username_element_.setValue("al"); + view_->textFieldDidEndEditing(username_element_); + CheckTextFieldsState("al", false, "", false); + username_element_.setValue("alices"); + view_->textFieldDidEndEditing(username_element_); + CheckTextFieldsState("alices", false, "", false); + username_element_.setValue(ASCIIToUTF16(kAliceUsername)); + view_->textFieldDidEndEditing(username_element_); + CheckTextFieldsState(kAliceUsername, true, kAlicePassword, true); +} + +// Tests that inline autocompletion works properly. +TEST_F(PasswordAutocompleteManagerTest, MAYBE_InlineAutocomplete) { + // Simulate the browser sending back the login info. + SimulateOnFillPasswordForm(fill_data_); + + // Clear the textfields to start fresh. + ClearUsernameAndPasswordFields(); + + // Simulate the user typing in the first letter of 'alice', a stored username. + SimulateUsernameChange("a", true); + // Both the username and password textfields should reflect selection of the + // stored login. + CheckTextFieldsState(kAliceUsername, true, kAlicePassword, true); + // And the selection should have been set to 'lice', the last 4 letters. + CheckUsernameSelection(1, 5); + + // Now the user types the next letter of the same username, 'l'. + SimulateUsernameChange("al", true); + // Now the fields should have the same value, but the selection should have a + // different start value. + CheckTextFieldsState(kAliceUsername, true, kAlicePassword, true); + CheckUsernameSelection(2, 5); + + // Test that deleting does not trigger autocomplete. + SimulateKeyDownEvent(username_element_, base::VKEY_BACK); + SimulateUsernameChange("alic", true); + CheckTextFieldsState("alic", false, "", false); + CheckUsernameSelection(4, 4); // No selection. + // Reset the last pressed key to something other than backspace. + SimulateKeyDownEvent(username_element_, base::VKEY_A); + + // Now lets say the user goes astray from the stored username and types the + // letter 'f', spelling 'alf'. We don't know alf (that's just sad), so in + // practice the username should no longer be 'alice' and the selected range + // should be empty. + SimulateUsernameChange("alf", true); + CheckTextFieldsState("alf", false, "", false); + CheckUsernameSelection(3, 3); // No selection. + + // Ok, so now the user removes all the text and enters the letter 'b'. + SimulateUsernameChange("b", true); + // The username and password fields should match the 'bob' entry. + CheckTextFieldsState(kBobUsername, true, kBobPassword, true); + CheckUsernameSelection(1, 3); +} + +// Tests that selecting and item in the suggestion drop-down works. +TEST_F(PasswordAutocompleteManagerTest, MAYBE_SuggestionSelect) { + // Simulate the browser sending back the login info. + SimulateOnFillPasswordForm(fill_data_); + + // Clear the textfields to start fresh. + ClearUsernameAndPasswordFields(); + + // To simulate a selection in the suggestion drop-down we just mimick what the + // WebView does: it sets the element value then calls + // didAcceptAutocompleteSuggestion on the renderer. + username_element_.setValue(ASCIIToUTF16(kAliceUsername)); + view_->didAcceptAutocompleteSuggestion(username_element_); + + // Autocomplete should have kicked in. + CheckTextFieldsState(kAliceUsername, true, kAlicePassword, true); +} + +} // namespace |