summaryrefslogtreecommitdiffstats
path: root/chrome/renderer/password_autocomplete_manager_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/renderer/password_autocomplete_manager_unittest.cc')
-rw-r--r--chrome/renderer/password_autocomplete_manager_unittest.cc329
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