// Copyright 2017 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 <memory>

#include "chrome/test/base/chrome_render_view_test.h"
#include "components/autofill/content/renderer/autofill_agent.h"
#include "content/public/renderer/render_view.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/web_size.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_input_element.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/public/web/web_settings.h"
#include "third_party/blink/public/web/web_view.h"
#include "third_party/blink/public/web/web_widget.h"
#include "ui/events/keycodes/keyboard_codes.h"

namespace autofill {

class FormControlClickDetectionTest : public ChromeRenderViewTest {
 protected:
  void SetUp() override {
    ChromeRenderViewTest::SetUp();
    // Must be set before loading HTML.
    view_->GetWebView()->SetDefaultPageScaleLimits(1, 4);

    LoadHTML(
        "<form>"
        "  <input type='text' id='text_1'></input><br>"
        "  <input type='text' id='text_2'></input><br>"
        "  <textarea  id='textarea_1'></textarea><br>"
        "  <textarea  id='textarea_2'></textarea><br>"
        "  <input type='button' id='button'></input><br>"
        "  <input type='button' id='button_2' disabled></input><br>"
        "</form>");
    GetWebWidget()->Resize(blink::WebSize(500, 500));
    GetWebWidget()->SetFocus(true);
    blink::WebDocument document = GetMainFrame()->GetDocument();
    text_ = document.GetElementById("text_1");
    textarea_ = document.GetElementById("textarea_1");
    ASSERT_FALSE(text_.IsNull());
    ASSERT_FALSE(textarea_.IsNull());

    // Enable show-ime event when element is focused by indicating that a user
    // gesture has been processed since load.
    EXPECT_TRUE(SimulateElementClick("button"));
  }

  void TearDown() override {
    text_.Reset();
    textarea_.Reset();
    ChromeRenderViewTest::TearDown();
  }

  void ClearAutofillAgentTestState() {
    autofill_agent_->last_clicked_form_control_element_for_testing_ =
        blink::WebFormControlElement();
    autofill_agent_
        ->last_clicked_form_control_element_was_focused_for_testing_ = false;
  }

  const blink::WebFormControlElement& last_clicked_form_control_element()
      const {
    return autofill_agent_->last_clicked_form_control_element_for_testing_;
  }

  bool last_clicked_form_control_element_was_focused() const {
    return autofill_agent_
        ->last_clicked_form_control_element_was_focused_for_testing_;
  }

  bool form_control_element_clicked_called() const {
    return !last_clicked_form_control_element().IsNull();
  }

  blink::WebElement text_;
  blink::WebElement textarea_;
};

// Tests that a clicked input call is properly handled by AutofillAgent.
TEST_F(FormControlClickDetectionTest, InputClicked) {
  ClearAutofillAgentTestState();
  EXPECT_NE(text_, text_.GetDocument().FocusedElement());
  // Click the text field once.
  EXPECT_TRUE(SimulateElementClick("text_1"));
  EXPECT_TRUE(form_control_element_clicked_called());
  EXPECT_FALSE(last_clicked_form_control_element_was_focused());
  EXPECT_EQ(text_, last_clicked_form_control_element());
  ClearAutofillAgentTestState();

  // Click the text field again and verify that AutofillAgent knows about its
  // focus.
  EXPECT_TRUE(SimulateElementClick("text_1"));
  EXPECT_TRUE(form_control_element_clicked_called());
  EXPECT_TRUE(last_clicked_form_control_element_was_focused());
  EXPECT_EQ(text_, last_clicked_form_control_element());
  ClearAutofillAgentTestState();

  // Click the button, no notification should happen (this is not a text-input).
  EXPECT_TRUE(SimulateElementClick("button"));
  EXPECT_FALSE(form_control_element_clicked_called());
}

// Tests that AutofillAgent ignores a right click.
TEST_F(FormControlClickDetectionTest, InputRightClicked) {
  ClearAutofillAgentTestState();
  EXPECT_NE(text_, text_.GetDocument().FocusedElement());
  // Right click the text field once.
  EXPECT_TRUE(SimulateElementRightClick("text_1"));
  EXPECT_FALSE(form_control_element_clicked_called());
  EXPECT_FALSE(last_clicked_form_control_element_was_focused());
  EXPECT_NE(text_, last_clicked_form_control_element());
}

TEST_F(FormControlClickDetectionTest, InputFocusedAndClicked) {
  ClearAutofillAgentTestState();
  // Focus the text field without a click.
  ExecuteJavaScriptForTests("document.getElementById('text_1').focus();");
  EXPECT_FALSE(form_control_element_clicked_called());
  ClearAutofillAgentTestState();

  // Click the focused text field to test that was_focused_ is set correctly.
  EXPECT_TRUE(SimulateElementClick("text_1"));
  EXPECT_TRUE(form_control_element_clicked_called());
  EXPECT_TRUE(last_clicked_form_control_element_was_focused());
  EXPECT_EQ(text_, last_clicked_form_control_element());
}

// Tests that AutofillAgent accepts form clicks for a textarea element which is
// clicked.
TEST_F(FormControlClickDetectionTest, TextAreaClicked) {
  ClearAutofillAgentTestState();
  // Click the textarea field once.
  EXPECT_TRUE(SimulateElementClick("textarea_1"));
  EXPECT_TRUE(form_control_element_clicked_called());
  EXPECT_FALSE(last_clicked_form_control_element_was_focused());
  EXPECT_EQ(textarea_, last_clicked_form_control_element());
  ClearAutofillAgentTestState();

  // Click the text field again and verify that AutofillAgent knows about its
  // focus.
  EXPECT_TRUE(SimulateElementClick("textarea_1"));
  EXPECT_TRUE(form_control_element_clicked_called());
  EXPECT_TRUE(last_clicked_form_control_element_was_focused());
  EXPECT_EQ(textarea_, last_clicked_form_control_element());
  ClearAutofillAgentTestState();

  // Click the button, no notification should happen (this is not a text-input).
  EXPECT_TRUE(SimulateElementClick("button"));
  EXPECT_FALSE(form_control_element_clicked_called());
}

TEST_F(FormControlClickDetectionTest, TextAreaFocusedAndClicked) {
  ClearAutofillAgentTestState();
  // Focus the textarea without a click.
  ExecuteJavaScriptForTests("document.getElementById('textarea_1').focus();");
  EXPECT_FALSE(form_control_element_clicked_called());
  ClearAutofillAgentTestState();

  // Click the text field again and verify that AutofillAgent knows about its
  // focus.
  EXPECT_TRUE(SimulateElementClick("textarea_1"));
  EXPECT_TRUE(form_control_element_clicked_called());
  EXPECT_TRUE(last_clicked_form_control_element_was_focused());
  EXPECT_EQ(textarea_, last_clicked_form_control_element());
  ClearAutofillAgentTestState();
}

TEST_F(FormControlClickDetectionTest, ScaledTextareaClicked) {
  ClearAutofillAgentTestState();
  EXPECT_NE(textarea_, textarea_.GetDocument().FocusedElement());
  view_->GetWebView()->SetPageScaleFactor(3);
  view_->GetWebView()->SetVisualViewportOffset(gfx::PointF(50, 50));

  // Click textarea_1.
  SimulatePointClick(gfx::Point(30, 30));
  EXPECT_TRUE(form_control_element_clicked_called());
  EXPECT_FALSE(last_clicked_form_control_element_was_focused());
  EXPECT_EQ(textarea_, last_clicked_form_control_element());
}

TEST_F(FormControlClickDetectionTest, ScaledTextareaTapped) {
  ClearAutofillAgentTestState();
  EXPECT_NE(textarea_, textarea_.GetDocument().FocusedElement());
  view_->GetWebView()->SetPageScaleFactor(3);
  view_->GetWebView()->SetVisualViewportOffset(gfx::PointF(50, 50));

  // Tap textarea_1.
  SimulateRectTap(gfx::Rect(30, 30, 30, 30));
  EXPECT_TRUE(form_control_element_clicked_called());
  EXPECT_FALSE(last_clicked_form_control_element_was_focused());
  EXPECT_EQ(textarea_, last_clicked_form_control_element());
}

TEST_F(FormControlClickDetectionTest, DisabledInputClickedNoEvent) {
  ClearAutofillAgentTestState();
  EXPECT_NE(text_, text_.GetDocument().FocusedElement());
  // Click the text field once.
  EXPECT_TRUE(SimulateElementClick("text_1"));
  EXPECT_TRUE(form_control_element_clicked_called());
  EXPECT_FALSE(last_clicked_form_control_element_was_focused());
  EXPECT_EQ(text_, last_clicked_form_control_element());
  ClearAutofillAgentTestState();

  // Click the disabled element.
  EXPECT_TRUE(SimulateElementClick("button_2"));
  EXPECT_FALSE(form_control_element_clicked_called());
}

TEST_F(FormControlClickDetectionTest,
       ClickDisabledInputDoesNotResetClickCounter) {
  EXPECT_NE(text_, text_.GetDocument().FocusedElement());
  // Click the text field once.
  EXPECT_TRUE(SimulateElementClick("text_1"));
  EXPECT_TRUE(form_control_element_clicked_called());
  EXPECT_FALSE(last_clicked_form_control_element_was_focused());
  EXPECT_EQ(text_, last_clicked_form_control_element());
  ClearAutofillAgentTestState();

  // Click the disabled element and focus should change.
  EXPECT_TRUE(SimulateElementClick("button_2"));
  EXPECT_FALSE(form_control_element_clicked_called());
  ClearAutofillAgentTestState();

  // Click the text field second time and verify it has lost
  // focus already.
  EXPECT_TRUE(SimulateElementClick("text_1"));
  EXPECT_TRUE(form_control_element_clicked_called());
  EXPECT_FALSE(last_clicked_form_control_element_was_focused());
  EXPECT_EQ(text_, last_clicked_form_control_element());
}

TEST_F(FormControlClickDetectionTest, TapNearEdgeIsPageClick) {
  EXPECT_NE(text_, text_.GetDocument().FocusedElement());
  // Tap outside of element bounds, but tap width is overlapping the field.
  gfx::Rect element_bounds = GetElementBounds("text_1");
  SimulateRectTap(element_bounds -
                  gfx::Vector2d(element_bounds.width() / 2 + 1, 0));
  EXPECT_TRUE(form_control_element_clicked_called());
  EXPECT_FALSE(last_clicked_form_control_element_was_focused());
  EXPECT_EQ(text_, last_clicked_form_control_element());
}

}  // namespace autofill
