// 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::PasswordFormFillData;
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 PasswordFormFillData& 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_;
  PasswordFormFillData 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