// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/autofill/content/renderer/password_generation_agent.h"

#include <string.h>

#include <algorithm>
#include <array>
#include <memory>
#include <string>
#include <string_view>

#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "build/build_config.h"
#include "chrome/renderer/autofill/fake_mojo_password_manager_driver.h"
#include "chrome/renderer/autofill/fake_password_generation_driver.h"
#include "chrome/renderer/autofill/password_generation_test_utils.h"
#include "chrome/test/base/chrome_render_view_test.h"
#include "components/autofill/content/renderer/autofill_agent.h"
#include "components/autofill/content/renderer/form_autofill_util.h"
#include "components/autofill/content/renderer/test_password_autofill_agent.h"
#include "components/autofill/core/common/autofill_switches.h"
#include "components/autofill/core/common/form_data.h"
#include "components/autofill/core/common/form_field_data.h"
#include "components/autofill/core/common/password_generation_util.h"
#include "components/autofill/core/common/unique_ids.h"
#include "components/password_manager/core/common/password_manager_features.h"
#include "content/public/renderer/render_frame.h"
#include "mojo/public/cpp/bindings/associated_receiver_set.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/common/renderer_preferences/renderer_preferences.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_element.h"
#include "third_party/blink/public/web/web_frame_widget.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_view.h"
#include "ui/events/keycodes/keyboard_codes.h"

using ::autofill::mojom::FocusedFieldType;
using ::base::ASCIIToUTF16;
using ::blink::WebDocument;
using ::blink::WebElement;
using ::blink::WebInputElement;
using ::blink::WebNode;
using ::blink::WebString;
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::AtLeast;
using ::testing::AtMost;
using ::testing::Eq;

namespace autofill {
namespace {

// Utility method that tries to find a field in `form` whose `id_attribute`
// matches `id`. Returns nullptr if no such field exists.
const FormFieldData* FindFieldById(const FormData& form, std::string_view id) {
  auto it = std::ranges::find(form.fields(), base::UTF8ToUTF16(id),
                              &FormFieldData::id_attribute);
  return it != form.fields().end() ? &*it : nullptr;
}

class FakeContentAutofillDriver : public mojom::AutofillDriver {
 public:
  FakeContentAutofillDriver() = default;
  ~FakeContentAutofillDriver() override = default;

  void BindReceiver(
      mojo::PendingAssociatedReceiver<mojom::AutofillDriver> receiver) {
    receivers_.Add(this, std::move(receiver));
  }

  void SetUp() { forms_seen_run_loop_ = std::make_unique<base::RunLoop>(); }

  void WaitForFormsSeen() {
    forms_seen_run_loop_->Run();
    forms_seen_run_loop_ = std::make_unique<base::RunLoop>();
  }

 private:
  // mojom::AutofillDriver:
  void FormsSeen(const std::vector<FormData>& updated_forms,
                 const std::vector<FormRendererId>& removed_forms) override {
    forms_seen_run_loop_->Quit();
  }

  void FormSubmitted(const FormData& form,
                     mojom::SubmissionSource source) override {}

  void CaretMovedInFormField(const FormData& form,
                             FieldRendererId field_id,
                             const gfx::Rect& caret_bounds) override {}

  void TextFieldValueChanged(const FormData& form,
                             FieldRendererId field_id,
                             base::TimeTicks timestamp) override {}

  void TextFieldDidScroll(const FormData& form,
                          FieldRendererId field_id) override {}

  void SelectControlSelectionChanged(const FormData& form,
                                     FieldRendererId field_id) override {}

  void JavaScriptChangedAutofilledValue(
      const FormData& form,
      FieldRendererId field_id,
      const std::u16string& old_value) override {}

  void AskForValuesToFill(const FormData& form,
                          FieldRendererId field_id,
                          const gfx::Rect& caret_bounds,
                          AutofillSuggestionTriggerSource trigger_source,
                          const std::optional<PasswordSuggestionRequest>&
                              password_request) override {}

  void HidePopup() override {}

  void SuppressAutomaticRefills(const FillId& fill_id) override {}

  void RequestRefill(const FillId& fill_id) override {}

  void FocusOnNonFormField() override {}

  void FocusOnFormField(const FormData& form,
                        FieldRendererId field_id) override {}

  void DidAutofillForm(const FormData& form) override {}

  void DidEndTextFieldEditing() override {}

  void SelectFieldOptionsDidChange(const FormData& form,
                                   FieldRendererId field_id) override {}

  void FormWithEmailVerificationTokenSubmitted(
      const FormData& form,
      FieldRendererId field_id) override {}

  void DidDetectJavaScriptAutofill(
      const FormData& form,
      FieldRendererId trigger_field_id,
      std::vector<mojom::JavaScriptFieldModificationPtr> field_modifications)
      override {}

  std::unique_ptr<base::RunLoop> forms_seen_run_loop_;

  mojo::AssociatedReceiverSet<mojom::AutofillDriver> receivers_;
};

constexpr char kSigninFormHTML[] =
    "<FORM name = 'blah' action = 'http://www.random.com/'> "
    "  <INPUT type = 'text' id = 'username'/> "
    "  <INPUT type = 'password' id = 'password'/> "
    "  <INPUT type = 'button' id = 'dummy'/> "
    "  <INPUT type = 'submit' value = 'LOGIN' />"
    "</FORM>";

constexpr char kAccountCreationFormHTML[] =
    "<FORM id = 'blah' action = 'http://www.random.com/pa/th?q=1&p=3#first'> "
    "  <INPUT type = 'text' id = 'username'/> "
    "  <INPUT type = 'password' id = 'first_password' size = 5/>"
    "  <INPUT type = 'password' id = 'second_password' size = 5/> "
    "  <INPUT type = 'text' id = 'address'/> "
    "  <INPUT type = 'text' id = 'hidden' style='display: none;'/> "
    "  <INPUT type = 'button' id = 'dummy'/> "
    "  <INPUT type = 'submit' value = 'LOGIN' />"
    "</FORM>";

constexpr char kAccountCreationNoForm[] =
    "<INPUT type = 'text' id = 'username'/> "
    "<INPUT type = 'password' id = 'first_password' size = 5/>"
    "<INPUT type = 'password' id = 'second_password' size = 5/> "
    "<INPUT type = 'text' id = 'address'/> "
    "<INPUT type = 'text' id = 'hidden' style='display: none;'/> "
    "<INPUT type = 'button' id = 'dummy'/> "
    "<INPUT type = 'submit' value = 'LOGIN' />";

#if !BUILDFLAG(IS_ANDROID)
constexpr char kAccountCreationNoIds[] =
    "<FORM action = 'http://www.random.com/pa/th?q=1&p=3#first'> "
    "  <INPUT type = 'text'/> "
    "  <INPUT type = 'password' class='first_password'/>"
    "  <INPUT type = 'password' class='second_password'/> "
    "  <INPUT type = 'text'/> "
    "  <INPUT type = 'button' id = 'dummy'/> "
    "  <INPUT type = 'submit' value = 'LOGIN'/>"
    "</FORM>";
#endif  // !BUILDFLAG(IS_ANDROID)

constexpr char kDisabledElementAccountCreationFormHTML[] =
    "<FORM name = 'blah' action = 'http://www.random.com/'> "
    "  <INPUT type = 'text' id = 'username'/> "
    "  <INPUT type = 'password' id = 'first_password' "
    "         autocomplete = 'off' size = 5/>"
    "  <INPUT type = 'password' id = 'second_password' size = 5/> "
    "  <INPUT type = 'text' id = 'address'/> "
    "  <INPUT type = 'text' id = 'disabled' disabled/> "
    "  <INPUT type = 'button' id = 'dummy'/> "
    "  <INPUT type = 'submit' value = 'LOGIN' />"
    "</FORM>";

constexpr char kHiddenPasswordAccountCreationFormHTML[] =
    "<FORM name = 'blah' action = 'http://www.random.com/'> "
    "  <INPUT type = 'text' id = 'username'/> "
    "  <INPUT type = 'password' id = 'first_password'/> "
    "  <INPUT type = 'password' id = 'second_password' style='display:none'/> "
    "  <INPUT type = 'button' id = 'dummy'/> "
    "  <INPUT type = 'submit' value = 'LOGIN' />"
    "</FORM>";

constexpr char kMultipleAccountCreationFormHTML[] =
    "<FORM name = 'login' action = 'http://www.random.com/'> "
    "  <INPUT type = 'text' id = 'random'/> "
    "  <INPUT type = 'text' id = 'username'/> "
    "  <INPUT type = 'password' id = 'password'/> "
    "  <INPUT type = 'button' id = 'dummy'/> "
    "  <INPUT type = 'submit' value = 'LOGIN' />"
    "</FORM>"
    "<FORM name = 'signup' action = 'http://www.random.com/signup'> "
    "  <INPUT type = 'text' id = 'username'/> "
    "  <INPUT type = 'password' id = 'first_password' "
    "         autocomplete = 'off' size = 5/>"
    "  <INPUT type = 'password' id = 'second_password' size = 5/> "
    "  <INPUT type = 'text' id = 'address'/> "
    "  <INPUT type = 'submit' value = 'LOGIN' />"
    "</FORM>";

constexpr char kPasswordChangeFormHTML[] =
    "<FORM name = 'ChangeWithUsernameForm' action = 'http://www.bidule.com'> "
    "  <INPUT type = 'text' id = 'username'/> "
    "  <INPUT type = 'password' id = 'password'/> "
    "  <INPUT type = 'password' id = 'newpassword'/> "
    "  <INPUT type = 'password' id = 'confirmpassword'/> "
    "  <INPUT type = 'button' id = 'dummy'/> "
    "  <INPUT type = 'submit' value = 'Login'/> "
    "</FORM>";

constexpr char kPasswordFormAndSpanHTML[] =
    "<FORM name = 'blah' action = 'http://www.random.com/pa/th?q=1&p=3#first'>"
    "  <INPUT type = 'text' id = 'username'/> "
    "  <INPUT type = 'password' id = 'password'/> "
    "  <INPUT type = 'button' id = 'dummy'/> "
    "</FORM>"
    "<SPAN id='span'>Text to click on</SPAN>";

class PasswordGenerationAgentTest : public ChromeRenderViewTest {
 public:
  enum AutomaticGenerationStatus {
    kNotReported,
    kAvailable,
  };

  PasswordGenerationAgentTest() = default;

  PasswordGenerationAgentTest(const PasswordGenerationAgentTest&) = delete;
  PasswordGenerationAgentTest& operator=(const PasswordGenerationAgentTest&) =
      delete;

  // ChromeRenderViewTest:
  void RegisterMainFrameRemoteInterfaces() override;
  void SetUp() override;
  void TearDown() override;

  void LoadHTMLWithUserGesture(const char* html);
  WebElement GetElementById(std::string_view element_id);
  WebInputElement GetInputElementById(std::string_view element_id);
  void FocusField(const char* element_id);

  void ExpectAutomaticGenerationAvailable(const char* element_id,
                                          AutomaticGenerationStatus available);
  void ExpectGenerationElementLostFocus(const char* new_element_id);
  void ExpectEditingPopupOnFieldFocus(const char* element_id);
  void SelectGenerationFallbackAndExpect(bool available);
  void ExpectAttribute(const WebElement& element,
                       std::string_view attribute,
                       std::string_view expected_value);
  void CheckPreviewedValue(const WebInputElement& element,
                           const std::u16string& value);

  void BindAutofillDriver(mojo::ScopedInterfaceEndpointHandle handle);
  void BindPasswordManagerDriver(mojo::ScopedInterfaceEndpointHandle handle);
  void BindPasswordManagerClient(mojo::ScopedInterfaceEndpointHandle handle);

  // Callback for TriggeredGeneratePassword.
  MOCK_METHOD1(
      TriggeredGeneratePasswordReply,
      void(
          const std::optional<password_generation::PasswordGenerationUIData>&));

  FakeContentAutofillDriver fake_autofill_driver_;
  testing::NiceMock<FakeMojoPasswordManagerDriver> fake_driver_;
  testing::StrictMock<FakePasswordGenerationDriver> fake_pw_client_;

  int called_inform_about_user_input_count_ = 0;
  std::optional<FormData> form_data_maybe_submitted_;
  mojom::FocusedFieldType last_focused_field_type_ =
      mojom::FocusedFieldType::kUnknown;
};

void PasswordGenerationAgentTest::RegisterMainFrameRemoteInterfaces() {
  // Because the test cases only involve the main frame in this test,
  // the fake password client is only used for the main frame.
  blink::AssociatedInterfaceProvider* remote_associated_interfaces =
      GetMainRenderFrame()->GetRemoteAssociatedInterfaces();
  remote_associated_interfaces->OverrideBinderForTesting(
      mojom::AutofillDriver::Name_,
      base::BindRepeating(&PasswordGenerationAgentTest::BindAutofillDriver,
                          base::Unretained(this)));
  remote_associated_interfaces->OverrideBinderForTesting(
      mojom::PasswordGenerationDriver::Name_,
      base::BindRepeating(
          &PasswordGenerationAgentTest::BindPasswordManagerClient,
          base::Unretained(this)));
  remote_associated_interfaces->OverrideBinderForTesting(
      mojom::PasswordManagerDriver::Name_,
      base::BindRepeating(
          &PasswordGenerationAgentTest::BindPasswordManagerDriver,
          base::Unretained(this)));

  ON_CALL(fake_driver_, InformAboutUserInput)
      .WillByDefault([this](const FormData& form_data) {
        called_inform_about_user_input_count_++;
        form_data_maybe_submitted_ = form_data;
      });

  ON_CALL(fake_driver_, FocusedInputChanged)
      .WillByDefault([this](FieldRendererId focused_field_id,
                            mojom::FocusedFieldType focused_field_type) {
        last_focused_field_type_ = focused_field_type;
      });
}

void PasswordGenerationAgentTest::SetUp() {
  ChromeRenderViewTest::SetUp();
  fake_autofill_driver_.SetUp();

  // TODO(crbug.com/41401202): Remove workaround preventing non-test classes to
  // bind fake_driver_ or fake_pw_client_.
  password_autofill_agent_->unsafe_driver();
  password_generation_->RequestPasswordManagerClientForTesting();
  base::RunLoop().RunUntilIdle();  // Executes binding the interfaces.
  // Reject all requests to bind driver/client to anything but the test class:
  GetMainRenderFrame()
      ->GetRemoteAssociatedInterfaces()
      ->OverrideBinderForTesting(
          mojom::PasswordGenerationDriver::Name_,
          base::BindRepeating([](mojo::ScopedInterfaceEndpointHandle handle) {
            handle.reset();
          }));
  GetMainRenderFrame()
      ->GetRemoteAssociatedInterfaces()
      ->OverrideBinderForTesting(
          mojom::PasswordManagerDriver::Name_,
          base::BindRepeating([](mojo::ScopedInterfaceEndpointHandle handle) {
            handle.reset();
          }));

  // Necessary for focus changes to work correctly and dispatch blur events
  // when a field was previously focused.
  GetWebFrameWidget()->SetFocus(true);
}

void PasswordGenerationAgentTest::TearDown() {
  // Unloading the document may trigger the event.
#if !BUILDFLAG(IS_ANDROID)
  EXPECT_CALL(fake_pw_client_, GenerationElementLostFocus()).Times(AtMost(1));
#endif  // !BUILDFLAG(IS_ANDROID)
  ChromeRenderViewTest::TearDown();
}

void PasswordGenerationAgentTest::LoadHTMLWithUserGesture(const char* html) {
  LoadHTML(html);

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

WebElement PasswordGenerationAgentTest::GetElementById(
    std::string_view element_id) {
  WebDocument document = GetMainFrame()->GetDocument();
  WebElement element =
      document.GetElementById(blink::WebString::FromUtf8(element_id));
  CHECK(element);
  return element;
}

WebInputElement PasswordGenerationAgentTest::GetInputElementById(
    std::string_view element_id) {
  WebInputElement input_element =
      GetElementById(element_id).To<WebInputElement>();
  CHECK(input_element);
  return input_element;
}

void PasswordGenerationAgentTest::FocusField(const char* element_id) {
  ASSERT_TRUE(SimulateElementClick(element_id));
#if BUILDFLAG(IS_ANDROID)
  // TODO(crbug.com/40820173): On Android, the JS above doesn't trigger the
  // method below.
  GetMainFrame()->AutofillClient()->DidCompleteFocusChangeInFrame();
#endif  // BUILDFLAG(IS_ANDROID)
}

void PasswordGenerationAgentTest::ExpectAutomaticGenerationAvailable(
    const char* element_id,
    AutomaticGenerationStatus status) {
  SCOPED_TRACE(testing::Message()
               << "element_id = " << element_id << " available = " << status);
  if (status == kNotReported) {
    EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable).Times(0);
  } else {
    // TODO(crbug.com/40279043): Expect the call precisely once.
    EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable)
        .Times(testing::AtLeast(1));
  }

  FocusField(element_id);
  base::RunLoop().RunUntilIdle();
  testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);

  // Check that aria-autocomplete attribute is set correctly.
  if (status == kAvailable) {
    WebElement element = GetElementById(element_id);
    ExpectAttribute(element, "aria-autocomplete", "list");
  }
}

void PasswordGenerationAgentTest::ExpectGenerationElementLostFocus(
    const char* new_element_id) {
#if !BUILDFLAG(IS_ANDROID)
  EXPECT_CALL(fake_pw_client_, GenerationElementLostFocus());
#endif  // !BUILDFLAG(IS_ANDROID)
  FocusField(new_element_id);
  base::RunLoop().RunUntilIdle();
  testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);
}

void PasswordGenerationAgentTest::ExpectEditingPopupOnFieldFocus(
    const char* new_element_id) {
#if !BUILDFLAG(IS_ANDROID)
  EXPECT_CALL(fake_pw_client_, ShowPasswordEditingPopup).Times(AtLeast(1));
#endif  // !BUILDFLAG(IS_ANDROID)
  FocusField(new_element_id);
  base::RunLoop().RunUntilIdle();
  testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);
}

void PasswordGenerationAgentTest::SelectGenerationFallbackAndExpect(
    bool available) {
  if (available) {
    EXPECT_CALL(*this,
                TriggeredGeneratePasswordReply(testing::Ne(std::nullopt)));
  } else {
    EXPECT_CALL(*this,
                TriggeredGeneratePasswordReply(testing::Eq(std::nullopt)));
  }
  password_generation_->TriggeredGeneratePassword(base::BindOnce(
      &PasswordGenerationAgentTest::TriggeredGeneratePasswordReply,
      base::Unretained(this)));
  testing::Mock::VerifyAndClearExpectations(this);
}

void PasswordGenerationAgentTest::ExpectAttribute(
    const WebElement& element,
    std::string_view attribute,
    std::string_view expected_value) {
  WebString actual_value =
      element.GetAttribute(blink::WebString::FromUtf8(attribute));
  ASSERT_FALSE(actual_value.IsNull());
  EXPECT_EQ(expected_value, actual_value.Ascii());
}

void PasswordGenerationAgentTest::CheckPreviewedValue(
    const WebInputElement& element,
    const std::u16string& value) {
  EXPECT_EQ(value, element.SuggestedValue().Utf16());
  EXPECT_EQ(element.GetAutofillState(), blink::WebAutofillState::kPreviewed);
}

void PasswordGenerationAgentTest::BindAutofillDriver(
    mojo::ScopedInterfaceEndpointHandle handle) {
  fake_autofill_driver_.BindReceiver(
      mojo::PendingAssociatedReceiver<mojom::AutofillDriver>(
          std::move(handle)));
}

void PasswordGenerationAgentTest::BindPasswordManagerDriver(
    mojo::ScopedInterfaceEndpointHandle handle) {
  fake_driver_.BindReceiver(
      mojo::PendingAssociatedReceiver<mojom::PasswordManagerDriver>(
          std::move(handle)));
}

void PasswordGenerationAgentTest::BindPasswordManagerClient(
    mojo::ScopedInterfaceEndpointHandle handle) {
  fake_pw_client_.BindReceiver(
      mojo::PendingAssociatedReceiver<mojom::PasswordGenerationDriver>(
          std::move(handle)));
}

// Tests HTML forms' annotations (e.g. signatures, visibility). The parser's
// annotations are tested in PasswordManagerBrowserTest.ParserAnnotations.
class PasswordGenerationAgentTestForHtmlAnnotation
    : public PasswordGenerationAgentTest {
 public:
  PasswordGenerationAgentTestForHtmlAnnotation() = default;

  PasswordGenerationAgentTestForHtmlAnnotation(
      const PasswordGenerationAgentTestForHtmlAnnotation&) = delete;
  PasswordGenerationAgentTestForHtmlAnnotation& operator=(
      const PasswordGenerationAgentTestForHtmlAnnotation&) = delete;

  void SetUp() override {
    base::CommandLine::ForCurrentProcess()->AppendSwitch(
        switches::kShowAutofillSignatures);
    PasswordGenerationAgentTest::SetUp();
  }

  void TestAnnotateForm(bool has_form_tag);
};

void PasswordGenerationAgentTestForHtmlAnnotation::TestAnnotateForm(
    bool has_form_tag) {
  SCOPED_TRACE(testing::Message() << "has_form_tag = " << has_form_tag);
  const char* kHtmlForm =
      has_form_tag ? kAccountCreationFormHTML : kAccountCreationNoForm;
  LoadHTMLWithUserGesture(kHtmlForm);
  SetFoundFormEligibleForGeneration(
      password_generation_, GetMainFrame()->GetDocument(),
      /*new_password_id=*/"first_password", /*confirm_password_id=*/nullptr);
  ExpectAutomaticGenerationAvailable("first_password", kAvailable);
  WebDocument document = GetMainFrame()->GetDocument();

  const char* kFormSignature =
      has_form_tag ? "17583149382422306905" : "17876980055561084954";
  if (has_form_tag) {
    // Check the form signature is set in the <form> tag.
    WebElement form_element = GetElementById("blah");
    ExpectAttribute(form_element, "form_signature", kFormSignature);
  }

  // Check field signatures and form signature are set in the <input>s.
  WebElement username_element = GetElementById("username");
  ExpectAttribute(username_element, "field_signature", "239111655");
  ExpectAttribute(username_element, "form_signature", kFormSignature);
  ExpectAttribute(username_element, "visibility_annotation", "true");

  WebElement password_element = GetElementById("first_password");
  ExpectAttribute(password_element, "field_signature", "3933215845");
  ExpectAttribute(password_element, "form_signature", kFormSignature);
  ExpectAttribute(password_element, "visibility_annotation", "true");
  ExpectAttribute(password_element, "password_creation_field", "1");

  WebElement hidden_element = GetElementById("hidden");
  ExpectAttribute(hidden_element, "visibility_annotation", "false");
}

TEST_F(PasswordGenerationAgentTest, HiddenSecondPasswordDetectionTest) {
  // Hidden fields are not treated differently.
  LoadHTMLWithUserGesture(kHiddenPasswordAccountCreationFormHTML);
  SetFoundFormEligibleForGeneration(
      password_generation_, GetMainFrame()->GetDocument(),
      /*new_password_id=*/"first_password", /*confirm_password_id=*/nullptr);
  ExpectAutomaticGenerationAvailable("first_password", kAvailable);
}

TEST_F(PasswordGenerationAgentTest, DetectionTestNoForm) {
  LoadHTMLWithUserGesture(kAccountCreationNoForm);
  SetFoundFormEligibleForGeneration(
      password_generation_, GetMainFrame()->GetDocument(),
      /*new_password_id=*/"first_password", /*confirm_password_id=*/nullptr);

  ExpectAutomaticGenerationAvailable("first_password", kAvailable);
  ExpectGenerationElementLostFocus("second_password");
}

TEST_F(PasswordGenerationAgentTest, FillTest) {
  // Add event listeners for password fields.
  std::vector<std::u16string> variables_to_check;
  std::string events_registration_script =
      CreateScriptToRegisterListeners("first_password", &variables_to_check) +
      CreateScriptToRegisterListeners("second_password", &variables_to_check);

  // Make sure that we are enabled before loading HTML.
  std::string html =
      std::string(kAccountCreationFormHTML) + events_registration_script;
  // Begin with no gesture and therefore no focused element.
  LoadHTMLWithUserGesture(html.c_str());
  WebDocument document = GetMainFrame()->GetDocument();
  SetFoundFormEligibleForGeneration(password_generation_,
                                    GetMainFrame()->GetDocument(),
                                    /*new_password_id=*/"first_password",
                                    /*confirm_password_id=*/"second_password");
  ExpectAutomaticGenerationAvailable("first_password", kAvailable);

  WebInputElement first_password_element =
      GetInputElementById("first_password");
  WebInputElement second_password_element =
      GetInputElementById("second_password");

  // Both password fields should be empty.
  EXPECT_TRUE(first_password_element.Value().IsNull());
  EXPECT_TRUE(second_password_element.Value().IsNull());

  std::u16string password = u"random_password";
  EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword(_, Eq(password)));

  password_generation_->GeneratedPasswordAccepted(password);
  password_generation_->FocusNextFieldAfterPasswords();

  // Password fields are filled out and set as being autofilled.
  EXPECT_EQ(password, first_password_element.Value().Utf16());
  EXPECT_EQ(password, second_password_element.Value().Utf16());
  EXPECT_TRUE(first_password_element.IsAutofilled());
  EXPECT_TRUE(second_password_element.IsAutofilled());

  // Make sure all events are called.
  for (const std::u16string& variable : variables_to_check) {
    int value;
    EXPECT_TRUE(ExecuteJavaScriptAndReturnIntValue(variable, &value));
    EXPECT_EQ(1, value) << variable;
  }

  // Check that focus returns to previously focused element.
  WebInputElement element = GetInputElementById("address");
  EXPECT_EQ(element, document.FocusedElement());
}

TEST_F(PasswordGenerationAgentTest, EditingTest) {
  LoadHTMLWithUserGesture(kAccountCreationFormHTML);
  SetFoundFormEligibleForGeneration(password_generation_,
                                    GetMainFrame()->GetDocument(),
                                    /*new_password_id=*/"first_password",
                                    /*confirm_password_id=*/"second_password");
  ExpectAutomaticGenerationAvailable("first_password", kAvailable);

  WebInputElement first_password_element =
      GetInputElementById("first_password");
  WebInputElement second_password_element =
      GetInputElementById("second_password");

  std::u16string password = u"random_password";
  EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword(_, Eq(password)));

  password_generation_->GeneratedPasswordAccepted(password);
  base::RunLoop().RunUntilIdle();
  testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);

  // Passwords start out the same.
  EXPECT_EQ(password, first_password_element.Value().Utf16());
  EXPECT_EQ(password, second_password_element.Value().Utf16());

  // After editing the first field they are still the same.
  std::u16string appended_chars = u"123";
  std::u16string edited_password = password + appended_chars;
  ExpectEditingPopupOnFieldFocus("first_password");
  EXPECT_CALL(fake_pw_client_,
              PresaveGeneratedPassword(_, Eq(edited_password)));
  SimulateUserTypingKeyCode(ui::VKEY_END, /*flush_message_loop=*/false);
  for (size_t i = 0; i < appended_chars.size(); i++) {
    SimulateUserTypingAsciiCharacter(appended_chars[i],
                                     /*flush_message_loop=*/false);
  }
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(edited_password, first_password_element.Value().Utf16());
  EXPECT_EQ(edited_password, second_password_element.Value().Utf16());
  EXPECT_TRUE(first_password_element.IsAutofilled());
  EXPECT_TRUE(second_password_element.IsAutofilled());
  ASSERT_TRUE(form_data_maybe_submitted_.has_value());
  EXPECT_THAT(FindFieldById(*form_data_maybe_submitted_, "first_password"),
              testing::Property(&FormFieldData::value, edited_password));
  EXPECT_THAT(FindFieldById(*form_data_maybe_submitted_, "second_password"),
              testing::Property(&FormFieldData::value, edited_password));
  base::RunLoop().RunUntilIdle();
  testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);

  // Verify that password mirroring works correctly even when the password
  // is deleted.
  EXPECT_CALL(fake_pw_client_, PasswordNoLongerGenerated);
  EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable);
  SimulateUserInputChangeForElement(first_password_element, std::string());
  EXPECT_EQ(std::u16string(), first_password_element.Value().Utf16());
  EXPECT_EQ(std::u16string(), second_password_element.Value().Utf16());
  EXPECT_FALSE(first_password_element.IsAutofilled());
  EXPECT_FALSE(second_password_element.IsAutofilled());
  EXPECT_FALSE(first_password_element.ShouldRevealPassword());
  EXPECT_FALSE(second_password_element.ShouldRevealPassword());
  ASSERT_TRUE(form_data_maybe_submitted_.has_value());
  EXPECT_THAT(FindFieldById(*form_data_maybe_submitted_, "first_password"),
              testing::Property(&FormFieldData::value, std::u16string()));
  EXPECT_THAT(FindFieldById(*form_data_maybe_submitted_, "second_password"),
              testing::Property(&FormFieldData::value, std::u16string()));
}

TEST_F(PasswordGenerationAgentTest, EditingEventsTest) {
  LoadHTMLWithUserGesture(kAccountCreationFormHTML);
  SetFoundFormEligibleForGeneration(
      password_generation_, GetMainFrame()->GetDocument(),
      /*new_password_id=*/"first_password", /*confirm_password_id=*/nullptr);

  // Generate password.
  ExpectAutomaticGenerationAvailable("first_password", kAvailable);
  std::u16string password = u"random_password";
  EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword(_, Eq(password)));
  password_generation_->GeneratedPasswordAccepted(password);
  fake_pw_client_.Flush();
  testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);

  // Start removing characters one by one and observe the events sent to the
  // browser.
  ExpectEditingPopupOnFieldFocus("first_password");
  SimulateUserTypingKeyCode(ui::VKEY_END, true);
  fake_pw_client_.Flush();
  testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);
  size_t max_chars_to_delete_before_editing =
      password.length() -
      PasswordGenerationAgent::kMinimumLengthForEditedPassword;
  for (size_t i = 0; i < max_chars_to_delete_before_editing; ++i) {
    password.erase(password.end() - 1);
    EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword(_, Eq(password)));
    SimulateUserTypingKeyCode(ui::VKEY_BACK, true);
    fake_pw_client_.Flush();
    fake_driver_.Flush();
    EXPECT_EQ(FocusedFieldType::kFillablePasswordField,
              last_focused_field_type_);
    testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);
  }

  // Delete one more character and move back to the generation state.
  EXPECT_CALL(fake_pw_client_, PasswordNoLongerGenerated);
  EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable);
  SimulateUserTypingKeyCode(ui::VKEY_BACK, true);
  fake_pw_client_.Flush();
  // Last focused element shouldn't change while editing.
  fake_driver_.Flush();
  EXPECT_EQ(FocusedFieldType::kFillablePasswordField, last_focused_field_type_);
}

TEST_F(PasswordGenerationAgentTest, UnblocklistedMultipleTest) {
  // Receive two not blocklisted messages, one is for account creation form and
  // the other is not. Show password generation icon.
  LoadHTMLWithUserGesture(kAccountCreationFormHTML);
  SetFoundFormEligibleForGeneration(
      password_generation_, GetMainFrame()->GetDocument(),
      /*new_password_id=*/"first_password", /*confirm_password_id=*/nullptr);
  ExpectAutomaticGenerationAvailable("first_password", kAvailable);
}

TEST_F(PasswordGenerationAgentTest, AccountCreationFormsDetectedTest) {
  // Did not receive account creation forms detected message. Don't show
  // password generation icon.
  LoadHTMLWithUserGesture(kAccountCreationFormHTML);

  // Receive the account creation forms detected message. Show password
  // generation icon.
  LoadHTMLWithUserGesture(kAccountCreationFormHTML);
  SetFoundFormEligibleForGeneration(
      password_generation_, GetMainFrame()->GetDocument(),
      /*new_password_id=*/"first_password", /*confirm_password_id=*/nullptr);
  ExpectAutomaticGenerationAvailable("first_password", kAvailable);
}

TEST_F(PasswordGenerationAgentTest, MaximumCharsForGenerationOffer) {
  base::HistogramTester histogram_tester;

  LoadHTMLWithUserGesture(kAccountCreationFormHTML);
  SetFoundFormEligibleForGeneration(
      password_generation_, GetMainFrame()->GetDocument(),
      /*new_password_id=*/"first_password", /*confirm_password_id=*/nullptr);
  // There should now be a message to show the UI.
  ExpectAutomaticGenerationAvailable("first_password", kAvailable);

  // Simulate the user typing a character. The popup should disappear.
#if !BUILDFLAG(IS_ANDROID)
  EXPECT_CALL(fake_pw_client_, PasswordGenerationRejectedByTyping);
#endif  // !BUILDFLAG(IS_ANDROID)
  WebInputElement first_password_element =
      GetInputElementById("first_password");
  SimulateUserInputChangeForElement(first_password_element, "a");
  testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);

  // Simulate the user deleting a character. The generation popup should be
  // shown again.
  EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable);
  SimulateUserTypingKeyCode(ui::VKEY_BACK, true);
  fake_pw_client_.Flush();
  testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);

  // Change focus. Bubble should be hidden.
  ExpectGenerationElementLostFocus("username");
  fake_pw_client_.Flush();

  // Focusing the password field will bring up the generation UI again.
  EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable).Times(AtLeast(1));
  FocusField("first_password");
  fake_pw_client_.Flush();
  testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);

  // Loading a different page triggers UMA stat upload. Verify that only one
  // display event is sent.
#if !BUILDFLAG(IS_ANDROID)
  EXPECT_CALL(fake_pw_client_, GenerationElementLostFocus());
#endif  // !BUILDFLAG(IS_ANDROID)
  LoadHTMLWithUserGesture(kSigninFormHTML);
  fake_pw_client_.Flush();

  histogram_tester.ExpectBucketCount(
      "PasswordGeneration.Event", password_generation::GENERATION_POPUP_SHOWN,
      1);
}

TEST_F(PasswordGenerationAgentTest, MinimumLengthForEditedPassword) {
  LoadHTMLWithUserGesture(kAccountCreationFormHTML);
  SetFoundFormEligibleForGeneration(
      password_generation_, GetMainFrame()->GetDocument(),
      /*new_password_id=*/"first_password", /*confirm_password_id=*/nullptr);
  ExpectAutomaticGenerationAvailable("first_password", kAvailable);

  // Generate a new password.
  std::u16string password = u"random_password";

  EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword(_, Eq(password)));
  password_generation_->GeneratedPasswordAccepted(password);
  fake_pw_client_.Flush();
  testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);

  ExpectEditingPopupOnFieldFocus("first_password");

  // Delete most of the password.
  EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable).Times(0);
  SimulateUserTypingKeyCode(ui::VKEY_END, false);
  size_t max_chars_to_delete =
      password.length() -
      PasswordGenerationAgent::kMinimumLengthForEditedPassword;
  EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword)
      .Times(testing::AtLeast(1));
  for (size_t i = 0; i < max_chars_to_delete; ++i)
    SimulateUserTypingKeyCode(ui::VKEY_BACK, false);
  fake_pw_client_.Flush();
  testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);

  // Delete one more character. The state should move to offering generation.
  EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable);
  EXPECT_CALL(fake_pw_client_, PasswordNoLongerGenerated(testing::_));
  SimulateUserTypingKeyCode(ui::VKEY_BACK, true);
  fake_pw_client_.Flush();
  testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);

  // The first password field is still non empty. The second one should be
  // cleared.
  WebInputElement first_password_element =
      GetInputElementById("first_password");
  WebInputElement second_password_element =
      GetInputElementById("second_password");
  EXPECT_NE(std::u16string(), first_password_element.Value().Utf16());
  EXPECT_EQ(std::u16string(), second_password_element.Value().Utf16());
}

TEST_F(PasswordGenerationAgentTest, DynamicFormTest) {
  LoadHTMLWithUserGesture(kSigninFormHTML);
  fake_autofill_driver_.WaitForFormsSeen();

  ExecuteJavaScriptForTests(
      "var form = document.createElement('form');"
      "form.action='http://www.random.com';"
      "var username = document.createElement('input');"
      "username.type = 'text';"
      "username.id = 'dynamic_username';"
      "var first_password = document.createElement('input');"
      "first_password.type = 'password';"
      "first_password.id = 'first_password';"
      "first_password.name = 'first_password';"
      "var second_password = document.createElement('input');"
      "second_password.type = 'password';"
      "second_password.id = 'second_password';"
      "second_password.name = 'second_password';"
      "form.appendChild(username);"
      "form.appendChild(first_password);"
      "form.appendChild(second_password);"
      "document.body.appendChild(form);");
  fake_autofill_driver_.WaitForFormsSeen();

  // This needs to come after the DOM has been modified.
  SetFoundFormEligibleForGeneration(
      password_generation_, GetMainFrame()->GetDocument(),
      /*new_password_id=*/"first_password", /*confirm_password_id=*/nullptr);

  // TODO(gcasto): I'm slightly worried about flakes in this test where
  // didAssociateFormControls() isn't called. If this turns out to be a problem
  // adding a call to OnDynamicFormsSeen(GetMainFrame()) will fix it, though
  // it will weaken the test.
  ExpectAutomaticGenerationAvailable("first_password", kAvailable);
}

// Losing focus should not trigger a password generation popup.
TEST_F(PasswordGenerationAgentTest, BlurTest) {
  LoadHTMLWithUserGesture(kDisabledElementAccountCreationFormHTML);
  SetFoundFormEligibleForGeneration(
      password_generation_, GetMainFrame()->GetDocument(),
      /*new_password_id=*/"first_password", /*confirm_password_id=*/nullptr);

  // Focus on the first password field: password generation popup should show
  // up.
  ExpectAutomaticGenerationAvailable("first_password", kAvailable);

  // Remove focus from everywhere by clicking an unfocusable element: password
  // generation popup should not show up.
  ExpectGenerationElementLostFocus("disabled");
}

TEST_F(PasswordGenerationAgentTest, ChangePasswordFormDetectionTest) {
  // Verify that generation is shown on correct field after message receiving.
  LoadHTMLWithUserGesture(kPasswordChangeFormHTML);
  ExpectAutomaticGenerationAvailable("password", kNotReported);
  ExpectAutomaticGenerationAvailable("newpassword", kNotReported);
  ExpectAutomaticGenerationAvailable("confirmpassword", kNotReported);

  SetFoundFormEligibleForGeneration(password_generation_,
                                    GetMainFrame()->GetDocument(),
                                    /*new_password_id=*/"newpassword",
                                    /*confirm_password_id=*/"confirmpassword");
  ExpectAutomaticGenerationAvailable("password", kNotReported);
  ExpectAutomaticGenerationAvailable("newpassword", kAvailable);
  ExpectGenerationElementLostFocus("confirmpassword");
}

// These tests are for the right-click menu and it is not applicable to Android.
#if !BUILDFLAG(IS_ANDROID)
TEST_F(PasswordGenerationAgentTest, DesktopContextMenuGenerationInFormTest) {
  LoadHTMLWithUserGesture(kSigninFormHTML);
  WebInputElement first_password_element = GetInputElementById("password");
  EXPECT_TRUE(first_password_element.Value().IsNull());
  SimulateElementRightClick("password");
  SelectGenerationFallbackAndExpect(true);
  EXPECT_TRUE(first_password_element.Value().IsNull());

  // Re-focusing a password field for which manual generation was requested
  // should not re-trigger generation.
  ExpectAutomaticGenerationAvailable("password", kNotReported);
}

TEST_F(PasswordGenerationAgentTest, DesktopContextMenuGenerationNoFormTest) {
  LoadHTMLWithUserGesture(kAccountCreationNoForm);
  SimulateElementRightClick("first_password");
  SelectGenerationFallbackAndExpect(true);
}

TEST_F(PasswordGenerationAgentTest,
       DesktopContextMenuGenerationDoesntSuppressAutomatic) {
  LoadHTMLWithUserGesture(kAccountCreationFormHTML);
  SetFoundFormEligibleForGeneration(
      password_generation_, GetMainFrame()->GetDocument(),
      /*new_password_id=*/"first_password", /*confirm_password_id=*/nullptr);
  ExpectAutomaticGenerationAvailable("first_password", kAvailable);
  // The browser may show a standard password dropdown with the "Generate"
  // option. In this case manual generation is triggered.
  SelectGenerationFallbackAndExpect(true);

  // Move the focus away to somewhere.
  ExpectGenerationElementLostFocus("address");

  // Moving the focus back should trigger the automatic generation again.
  ExpectAutomaticGenerationAvailable("first_password", kAvailable);
}

TEST_F(PasswordGenerationAgentTest, DesktopContextMenuGenerationNoIds) {
  LoadHTMLWithUserGesture(kAccountCreationNoIds);
  WebDocument document = GetMainFrame()->GetDocument();

  ExecuteJavaScriptForTests(
      "document.getElementsByClassName('first_password')[0].focus();");
  WebInputElement first_password_element =
      document.FocusedElement().To<WebInputElement>();
  ASSERT_TRUE(first_password_element);
  SelectGenerationFallbackAndExpect(true);

  // Simulate that the user accepts a generated password.
  std::u16string password = u"random_password";
  EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword(_, Eq(password)));
  password_generation_->GeneratedPasswordAccepted(password);

  // Check that the first password field is autofilled with the generated
  // password.
  EXPECT_EQ(password, first_password_element.Value().Utf16());
  EXPECT_TRUE(first_password_element.IsAutofilled());

  // Check that the second password field is autofilled with the generated
  // password (since it is chosen as a confirmation password field).
  ExecuteJavaScriptForTests(
      "document.getElementsByClassName('second_password')[0].focus();");
  WebInputElement second_password_element =
      document.FocusedElement().To<WebInputElement>();
  ASSERT_TRUE(second_password_element);
  EXPECT_EQ(password, second_password_element.Value().Utf16());
  EXPECT_TRUE(second_password_element.IsAutofilled());
}

TEST_F(PasswordGenerationAgentTest,
       DesktopContextMenuGeneration_NoFocusedElement) {
  // Checks the fallback doesn't cause a crash just in case no password element
  // had focus so far.
  LoadHTMLWithUserGesture(kAccountCreationFormHTML);
  SelectGenerationFallbackAndExpect(false);
}

// Test corner case when password field becomes readonly the moment you focus it
// and later becomes normal again.
TEST_F(PasswordGenerationAgentTest,
       DesktopContextMenuManualGenerationOnReadonly) {
  LoadHTMLWithUserGesture(kSigninFormHTML);
  ExecuteJavaScriptForTests(
      "document.getElementsByClassName('first_password')[0].setAttribute('"
      "readonly', 'true');");
  SimulateElementRightClick("password");

  ExecuteJavaScriptForTests(
      "document.getElementsByClassName('first_password')[0].removeAttribute('"
      "readonly');");

  SelectGenerationFallbackAndExpect(true);
}

// Tests that manual generation can be triggered on a text field, if heuristics
// signal that the field is a password field.
TEST_F(PasswordGenerationAgentTest,
       ManualGenerationIsTriggeredOnTextFieldWithHeuristic) {
  // The field type is text, but has password mention in the id/name attribute.
  LoadHTML(
      R"(
      <input type="text" id="username-field" name="username-field">
      <input type="text" id="password-field" name="password-field">
    )");

  SimulateElementRightClick("password-field");
  SelectGenerationFallbackAndExpect(true);

  // Simulate that the user accepts a generated password.
  std::u16string password = u"random_password";
  EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword(_, Eq(password)));
  password_generation_->GeneratedPasswordAccepted(password);

  WebInputElement password_element = GetInputElementById("password-field");
  // Check that the password field is autofilled with the generated
  // password.
  EXPECT_EQ(password, password_element.Value().Utf16());
  EXPECT_TRUE(password_element.IsAutofilled());
}

#endif  // !BUILDFLAG(IS_ANDROID)

TEST_F(PasswordGenerationAgentTest, PresavingGeneratedPassword) {
  const struct {
    const char* form;
    const char* generation_element;
  } kTestCases[] = {{kAccountCreationFormHTML, "first_password"},
                    {kAccountCreationNoForm, "first_password"},
                    {kPasswordChangeFormHTML, "newpassword"}};
  for (auto& test_case : kTestCases) {
    SCOPED_TRACE(testing::Message("form: ") << test_case.form);
    LoadHTMLWithUserGesture(test_case.form);
    FocusField(test_case.generation_element);
    SelectGenerationFallbackAndExpect(true);

    std::u16string password = u"random_password";
    EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword(_, Eq(password)));
    password_generation_->GeneratedPasswordAccepted(password);
    base::RunLoop().RunUntilIdle();

    ExpectEditingPopupOnFieldFocus(test_case.generation_element);
    EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword);
    SimulateUserTypingAsciiCharacter('a', true);
    base::RunLoop().RunUntilIdle();

    ExpectGenerationElementLostFocus("username");
    EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword);
    SimulateUserTypingAsciiCharacter('X', true);
    base::RunLoop().RunUntilIdle();
    testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);

    ExpectEditingPopupOnFieldFocus(test_case.generation_element);
    SimulateUserTypingKeyCode(ui::VKEY_END, false);
    EXPECT_CALL(fake_pw_client_, PasswordNoLongerGenerated(testing::_));
    for (size_t i = 0; i < password.length(); ++i)
      SimulateUserTypingKeyCode(ui::VKEY_BACK, false);
    SimulateUserTypingKeyCode(ui::VKEY_BACK, true);
    base::RunLoop().RunUntilIdle();

    EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword).Times(0);
    ExpectGenerationElementLostFocus("username");
    SimulateUserTypingAsciiCharacter('Y', true);
    base::RunLoop().RunUntilIdle();
    testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);
  }
}

TEST_F(PasswordGenerationAgentTest, FallbackForSaving) {
  LoadHTMLWithUserGesture(kAccountCreationFormHTML);
  FocusField("first_password");
  SelectGenerationFallbackAndExpect(true);
  EXPECT_EQ(0, called_inform_about_user_input_count_);
  std::u16string password = u"random_password";
  EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword(_, Eq(password)))
      .WillOnce(testing::InvokeWithoutArgs([this]() {
        // Make sure that generation event was propagated to the browser before
        // the fallback showing. Otherwise, the fallback for saving provides a
        // save bubble instead of a confirmation bubble.
        EXPECT_EQ(0, called_inform_about_user_input_count_);
      }));
  password_generation_->GeneratedPasswordAccepted(password);
  fake_driver_.Flush();
  // Two fallback requests are expected because generation changes either new
  // password and confirmation fields.
  EXPECT_EQ(2, called_inform_about_user_input_count_);
}

TEST_F(PasswordGenerationAgentTest, AcceptAfterNavigation) {
  LoadHTMLWithUserGesture(kAccountCreationFormHTML);
  SetFoundFormEligibleForGeneration(
      password_generation_, GetMainFrame()->GetDocument(),
      /*new_password_id=*/"first_password", /*confirm_password_id=*/nullptr);
  ExpectAutomaticGenerationAvailable("first_password", kAvailable);

  // Navigation happens. Then browser UI accepts the generated password. It
  // should not crash.
#if !BUILDFLAG(IS_ANDROID)
  EXPECT_CALL(fake_pw_client_, GenerationElementLostFocus());
#endif  // !BUILDFLAG(IS_ANDROID)
  LoadHTMLWithUserGesture(kSigninFormHTML);
  EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword).Times(0);
  password_generation_->GeneratedPasswordAccepted(u"random_password");
}

TEST_F(PasswordGenerationAgentTest, RevealPassword) {
  // Checks that revealed password is masked when the field lost focus.
  // Test cases: user click on another input field and on non-focusable element.
  LoadHTMLWithUserGesture(kPasswordFormAndSpanHTML);
  SetFoundFormEligibleForGeneration(
      password_generation_, GetMainFrame()->GetDocument(),
      /*new_password_id=*/"password", /*confirm_password_id=*/nullptr);
  const char* kGenerationElementId = "password";
  const char* kSpanId = "span";
  const char* kTextFieldId = "username";

  ExpectAutomaticGenerationAvailable(kGenerationElementId, kAvailable);
  std::u16string password = u"long_pwd";
  EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword(_, Eq(password)));
  password_generation_->GeneratedPasswordAccepted(password);

  for (bool clickOnInputField : {false, true}) {
    SCOPED_TRACE(testing::Message("clickOnInputField = ") << clickOnInputField);
    // Click on the generation field to reveal the password value.
    ExpectEditingPopupOnFieldFocus(kGenerationElementId);
    fake_pw_client_.Flush();

    WebInputElement input = GetInputElementById(kGenerationElementId);
    EXPECT_TRUE(input.ShouldRevealPassword());

    // Click on another HTML element.
    const char* const click_target_name =
        clickOnInputField ? kTextFieldId : kSpanId;
    ExpectGenerationElementLostFocus(click_target_name);
    EXPECT_FALSE(input.ShouldRevealPassword());
    fake_pw_client_.Flush();
    testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);
  }
}

TEST_F(PasswordGenerationAgentTest,
       DoesNotResetGenerationWhenJavascriptClearedTheField) {
  LoadHTMLWithUserGesture(kAccountCreationFormHTML);
  SetFoundFormEligibleForGeneration(
      password_generation_, GetMainFrame()->GetDocument(),
      /*new_password_id=*/"first_password", /*confirm_password_id=*/nullptr);

  const char kGenerationElementId[] = "first_password";
  ExpectAutomaticGenerationAvailable(kGenerationElementId, kAvailable);
  std::u16string password = u"long_pwd";
  EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword(_, Eq(password)));
  password_generation_->GeneratedPasswordAccepted(password);

  // Should not reset the password generation as this is not drived by user.
  // Some websites might clear the user data right before submission.
  EXPECT_CALL(fake_pw_client_, PasswordNoLongerGenerated).Times(0);
  EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable).Times(0);
  ExecuteJavaScriptForTests(
      "document.getElementById('first_password').value = '';");
  base::RunLoop().RunUntilIdle();
}

TEST_F(PasswordGenerationAgentTest,
       ResetsGenerationWhenUserFocusesFieldClearedByJavascript) {
  LoadHTMLWithUserGesture(kAccountCreationFormHTML);
  SetFoundFormEligibleForGeneration(
      password_generation_, GetMainFrame()->GetDocument(),
      /*new_password_id=*/"first_password", /*confirm_password_id=*/nullptr);

  const char kGenerationElementId[] = "first_password";
  ExpectAutomaticGenerationAvailable(kGenerationElementId, kAvailable);
  std::u16string password = u"long_pwd";
  EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword(_, Eq(password)));
  password_generation_->GeneratedPasswordAccepted(password);

  // Should not reset the password generation as this is not drived by user.
  // Some websites might clear the user data right before submission.
  EXPECT_CALL(fake_pw_client_, PasswordNoLongerGenerated(testing::_)).Times(0);
  EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable).Times(0);
  ExecuteJavaScriptForTests(
      "document.getElementById('first_password').value = '';");
  base::RunLoop().RunUntilIdle();

  // Should reset the password generation now, when user focuses an empty
  // password field. Now we are sure that the form with the previously generated
  // password won't be submitted.
  EXPECT_CALL(fake_pw_client_, PasswordNoLongerGenerated).Times(AtLeast(1));
  EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable).Times(AtLeast(1));
  FocusField("first_password");
}

TEST_F(PasswordGenerationAgentTest, JavascriptClearedThePassword_TypeUsername) {
  LoadHTMLWithUserGesture(kAccountCreationFormHTML);
  constexpr char kGenerationElementId[] = "first_password";
  SetFoundFormEligibleForGeneration(password_generation_,
                                    GetMainFrame()->GetDocument(),
                                    /*new_password_id=*/kGenerationElementId,
                                    /*confirm_password_id=*/nullptr);

  ExpectAutomaticGenerationAvailable(kGenerationElementId, kAvailable);
  std::u16string password = u"long_pwd";
  EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword(_, Eq(password)));
  password_generation_->GeneratedPasswordAccepted(password);

  // Edit some other field.
  EXPECT_CALL(fake_pw_client_, PasswordNoLongerGenerated(testing::_));
#if !BUILDFLAG(IS_ANDROID)
  EXPECT_CALL(fake_pw_client_, GenerationElementLostFocus());
#endif  // !BUILDFLAG(IS_ANDROID)
  ExecuteJavaScriptForTests(
      "document.getElementById('first_password').value = '';");
  FocusField("username");
  SimulateUserTypingAsciiCharacter('a', true);
}

TEST_F(PasswordGenerationAgentTest, GenerationFallback_NoFocusedElement) {
  // Checks the fallback doesn't cause a crash just in case no password element
  // had focus so far.
  LoadHTMLWithUserGesture(kAccountCreationFormHTML);
  SelectGenerationFallbackAndExpect(false);
}

TEST_F(PasswordGenerationAgentTest, AutofillToGenerationField) {
  LoadHTMLWithUserGesture(kAccountCreationFormHTML);
  SetFoundFormEligibleForGeneration(
      password_generation_, GetMainFrame()->GetDocument(),
      /*new_password_id=*/"first_password", /*confirm_password_id=*/nullptr);
  ExpectAutomaticGenerationAvailable("first_password", kAvailable);

  WebDocument document = GetMainFrame()->GetDocument();
  const WebInputElement input_element = GetInputElementById("first_password");
  // Since password isn't generated (just suitable field was detected),
  // `OnFieldAutofilled` wouldn't trigger any actions.
  EXPECT_CALL(fake_pw_client_, PasswordNoLongerGenerated(testing::_)).Times(0);
  password_generation_->OnFieldAutofilled(input_element);
}

TEST_F(PasswordGenerationAgentTestForHtmlAnnotation, AnnotateForm) {
  TestAnnotateForm(true);
}

TEST_F(PasswordGenerationAgentTestForHtmlAnnotation, AnnotateNoForm) {
  TestAnnotateForm(false);
}

TEST_F(PasswordGenerationAgentTest, PasswordUnmaskedUntilCompleteDeletion) {
  LoadHTMLWithUserGesture(kAccountCreationFormHTML);
  SetFoundFormEligibleForGeneration(
      password_generation_, GetMainFrame()->GetDocument(),
      /*new_password_id=*/"first_password", /*confirm_password_id=*/nullptr);

  constexpr char kGenerationElementId[] = "first_password";

  // Generate a new password.
  ExpectAutomaticGenerationAvailable(kGenerationElementId, kAvailable);
  std::u16string password = u"random_password";
  EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword(_, Eq(password)));
  password_generation_->GeneratedPasswordAccepted(password);
  fake_pw_client_.Flush();
  testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);

  // Delete characters of the generated password until only
  // `kMinimumLengthForEditedPassword` - 1 chars remain.
  ExpectEditingPopupOnFieldFocus(kGenerationElementId);
  SimulateUserTypingKeyCode(ui::VKEY_END, false);
  EXPECT_CALL(fake_pw_client_, PasswordNoLongerGenerated(testing::_));
  EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable);
  size_t max_chars_to_delete =
      password.length() -
      PasswordGenerationAgent::kMinimumLengthForEditedPassword + 1;
  for (size_t i = 0; i < max_chars_to_delete; ++i)
    SimulateUserTypingKeyCode(ui::VKEY_BACK, false);
  base::RunLoop().RunUntilIdle();
  fake_pw_client_.Flush();
  // The remaining characters no longer count as a generated password, so
  // generation should be offered again.

  // Check that the characters remain unmasked.
  WebDocument document = GetMainFrame()->GetDocument();
  WebInputElement input = GetInputElementById(kGenerationElementId);
  EXPECT_TRUE(input.ShouldRevealPassword());

  // Delete the rest of the characters. The field should now mask new
  // characters. Due to implementation details it's possible to get pings about
  // password generation available.
  EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable).Times(AnyNumber());
  for (size_t i = 0;
       i < PasswordGenerationAgent::kMinimumLengthForEditedPassword; ++i)
    SimulateUserTypingKeyCode(ui::VKEY_BACK, false);
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(input.ShouldRevealPassword());
}

TEST_F(PasswordGenerationAgentTest, ShortPasswordMaskedAfterChangingFocus) {
  LoadHTMLWithUserGesture(kPasswordFormAndSpanHTML);
  constexpr char kGenerationElementId[] = "password";
  SetFoundFormEligibleForGeneration(password_generation_,
                                    GetMainFrame()->GetDocument(),
                                    /*new_password_id=*/kGenerationElementId,
                                    /*confirm_password_id=*/nullptr);

  // Generate a new password.
  ExpectAutomaticGenerationAvailable(kGenerationElementId, kAvailable);
  std::u16string password = u"random_password";
  EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword(_, Eq(password)));
  password_generation_->GeneratedPasswordAccepted(password);
  fake_pw_client_.Flush();
  testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);

  // Delete characters of the generated password until only
  // `kMinimumLengthForEditedPassword` - 1 chars remain.
  ExpectEditingPopupOnFieldFocus(kGenerationElementId);
  EXPECT_CALL(fake_pw_client_, PasswordNoLongerGenerated(testing::_));
  size_t max_chars_to_delete =
      password.length() -
      PasswordGenerationAgent::kMinimumLengthForEditedPassword + 1;
  EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable);
  for (size_t i = 0; i < max_chars_to_delete; ++i)
    SimulateUserTypingKeyCode(ui::VKEY_BACK, false);
  // The remaining characters no longer count as a generated password, so
  // generation should be offered again.
  base::RunLoop().RunUntilIdle();
  fake_pw_client_.Flush();
  testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);

  // Check that the characters remain unmasked.
  WebInputElement input = GetInputElementById(kGenerationElementId);
  EXPECT_TRUE(input.ShouldRevealPassword());

  // Focus another element on the page. The password should be masked.
  ExpectGenerationElementLostFocus("span");
  EXPECT_FALSE(input.ShouldRevealPassword());

  // Focus the password field again. As the remaining characters are not
  // a generated password, they should remain masked.
  ExpectAutomaticGenerationAvailable(kGenerationElementId, kAvailable);
  EXPECT_FALSE(input.ShouldRevealPassword());
}

TEST_F(PasswordGenerationAgentTest, GenerationAvailableByRendererIds) {
  LoadHTMLWithUserGesture(kMultipleAccountCreationFormHTML);

  constexpr auto kPasswordElementsIds = std::to_array<const char*>({
      "password",
      "first_password",
      "second_password",
  });

  WebDocument document = GetMainFrame()->GetDocument();
  std::vector<WebInputElement> password_elements;
  for (const char* id : kPasswordElementsIds) {
    WebInputElement input = GetInputElementById(id);
    password_elements.push_back(input);
  }

  // Simulate that the browser informs about eligible for generation form.
  // Check that generation is available only on new password field of this form.
  PasswordFormGenerationData generation_data;
  generation_data.new_password_renderer_id =
      form_util::GetFieldRendererId(password_elements[0]);

  password_generation_->FoundFormEligibleForGeneration(generation_data);
  ExpectAutomaticGenerationAvailable(kPasswordElementsIds[0], kAvailable);
  ExpectGenerationElementLostFocus(kPasswordElementsIds[1]);
  ExpectAutomaticGenerationAvailable(kPasswordElementsIds[2], kNotReported);

  // Simulate that the browser informs about the second eligible for generation
  // form. Check that generation is available on both forms.
  generation_data.new_password_renderer_id =
      form_util::GetFieldRendererId(password_elements[2]);
  password_generation_->FoundFormEligibleForGeneration(generation_data);
  ExpectAutomaticGenerationAvailable(kPasswordElementsIds[0], kAvailable);
  ExpectGenerationElementLostFocus(kPasswordElementsIds[1]);
  ExpectAutomaticGenerationAvailable(kPasswordElementsIds[2], kAvailable);
}

TEST_F(PasswordGenerationAgentTest, SuggestionPreviewedAndClearedTest) {
  LoadHTMLWithUserGesture(kAccountCreationFormHTML);
  WebDocument document = GetMainFrame()->GetDocument();
  SetFoundFormEligibleForGeneration(password_generation_,
                                    GetMainFrame()->GetDocument(),
                                    /*new_password_id=*/"first_password",
                                    /*confirm_password_id=*/"second_password");
  ExpectAutomaticGenerationAvailable("first_password", kAvailable);

  WebInputElement first_password_element =
      GetInputElementById("first_password");
  WebInputElement second_password_element =
      GetInputElementById("second_password");

  std::u16string password = u"random_password";
  password_generation_->PreviewGenerationSuggestion(password);

  // Both password fields should have suggested values.
  CheckPreviewedValue(first_password_element, password);
  CheckPreviewedValue(second_password_element, password);

  // Previewed suggestions should be successfully cleared upon request.
  password_generation_->ClearPreviewedForm();
  EXPECT_TRUE(first_password_element.SuggestedValue().IsNull());
  EXPECT_TRUE(second_password_element.SuggestedValue().IsNull());
  EXPECT_EQ(first_password_element.GetAutofillState(),
            blink::WebAutofillState::kNotFilled);
  EXPECT_EQ(second_password_element.GetAutofillState(),
            blink::WebAutofillState::kNotFilled);
}

TEST_F(PasswordGenerationAgentTest, SuggestionPreviewedAndFilledTest) {
  LoadHTMLWithUserGesture(kAccountCreationFormHTML);
  WebDocument document = GetMainFrame()->GetDocument();
  SetFoundFormEligibleForGeneration(password_generation_,
                                    GetMainFrame()->GetDocument(),
                                    /*new_password_id=*/"first_password",
                                    /*confirm_password_id=*/"second_password");
  ExpectAutomaticGenerationAvailable("first_password", kAvailable);

  WebInputElement first_password_element =
      GetInputElementById("first_password");
  WebInputElement second_password_element =
      GetInputElementById("second_password");

  std::u16string password = u"random_password";
  password_generation_->PreviewGenerationSuggestion(password);

  // Both password fields should have suggested values.
  CheckPreviewedValue(first_password_element, password);
  CheckPreviewedValue(second_password_element, password);

  // Previewed suggestion should be successfully cleared when the
  // suggestion is accepted.
  EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword(_, Eq(password)));
  password_generation_->GeneratedPasswordAccepted(password);

  EXPECT_TRUE(first_password_element.SuggestedValue().IsNull());
  EXPECT_TRUE(second_password_element.SuggestedValue().IsNull());
  EXPECT_EQ(first_password_element.GetAutofillState(),
            blink::WebAutofillState::kAutofilled);
  EXPECT_EQ(second_password_element.GetAutofillState(),
            blink::WebAutofillState::kAutofilled);
}

TEST_F(PasswordGenerationAgentTest, AdvancesFocusToNextFieldAfterPasswords) {
  constexpr char kGenerationElementId[] = "first_password";
  LoadHTMLWithUserGesture(kAccountCreationFormHTML);
  SetFoundFormEligibleForGeneration(password_generation_,
                                    GetMainFrame()->GetDocument(),
                                    /*new_password_id=*/kGenerationElementId,
                                    /*confirm_password_id=*/"second_password");
  ExpectAutomaticGenerationAvailable(kGenerationElementId, kAvailable);

  password_generation_->FocusNextFieldAfterPasswords();
  WebDocument document = GetMainFrame()->GetDocument();
  EXPECT_EQ(document.FocusedElement(), GetElementById("address"));
}

TEST_F(PasswordGenerationAgentTest,
       MasksPasswordAfterReplacingTextBySelectingAll) {
  LoadHTMLWithUserGesture(kAccountCreationFormHTML);
  SetFoundFormEligibleForGeneration(password_generation_,
                                    GetMainFrame()->GetDocument(),
                                    /*new_password_id=*/"first_password",
                                    /*confirm_password_id=*/"second_password");

  WebInputElement first_password_element =
      GetInputElementById("first_password");
  WebInputElement second_password_element =
      GetInputElementById("second_password");

  // Generate password.
  ExpectAutomaticGenerationAvailable("first_password", kAvailable);
  std::u16string password = u"random_password";
  EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword(_, Eq(password)));
  password_generation_->GeneratedPasswordAccepted(password);
  fake_pw_client_.Flush();
  testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);

  // Verify that values in both fields are mirrored.
  EXPECT_EQ(first_password_element.Value().Utf16(), password);
  EXPECT_EQ(second_password_element.Value().Utf16(), password);

  // Focus first password field, select all characters and type a letter.
  ExpectEditingPopupOnFieldFocus("first_password");
  first_password_element.SetSelectionRange(0, password.length());
  fake_pw_client_.Flush();
  testing::Mock::VerifyAndClearExpectations(&fake_pw_client_);
#if !BUILDFLAG(IS_ANDROID)
  EXPECT_CALL(fake_pw_client_, PasswordGenerationRejectedByTyping);
#endif  // !BUILDFLAG(IS_ANDROID)
  EXPECT_CALL(fake_pw_client_, PasswordNoLongerGenerated);
  SimulateUserTypingAsciiCharacter('X', /*flush_message_loop=*/true);

  // First field should contain the typed letter, second field should be empty
  // since the password is no longer generated. Both fields should be masked.
  EXPECT_EQ(first_password_element.Value().Utf16(), u"X");
  EXPECT_TRUE(second_password_element.Value().Utf16().empty());
  EXPECT_FALSE(first_password_element.ShouldRevealPassword());
  EXPECT_FALSE(second_password_element.ShouldRevealPassword());
}

TEST_F(PasswordGenerationAgentTest,
       MasksPasswordAfterTypingWithGenerationPopupVisible) {
  LoadHTMLWithUserGesture(kAccountCreationFormHTML);
  SetFoundFormEligibleForGeneration(password_generation_,
                                    GetMainFrame()->GetDocument(),
                                    /*new_password_id=*/"first_password",
                                    /*confirm_password_id=*/"second_password");
  ExpectAutomaticGenerationAvailable("first_password", kAvailable);

  // Type a character. This should result in popup being hidden and the password
  // field masked.
#if !BUILDFLAG(IS_ANDROID)
  EXPECT_CALL(fake_pw_client_, PasswordGenerationRejectedByTyping);
#endif  // !BUILDFLAG(IS_ANDROID)
  SimulateUserTypingAsciiCharacter('X', /*flush_message_loop=*/true);

  // First field should contain the typed letter, second field should be empty.
  // Both fields should be masked.
  WebInputElement first_password_element =
      GetInputElementById("first_password");
  WebInputElement second_password_element =
      GetInputElementById("second_password");
  EXPECT_EQ(first_password_element.Value().Utf16(), u"X");
  EXPECT_TRUE(second_password_element.Value().Utf16().empty());
  EXPECT_FALSE(first_password_element.ShouldRevealPassword());
  EXPECT_FALSE(second_password_element.ShouldRevealPassword());
}

// Tests that automatic generation is suggested after the field has become a
// password field at least once.
TEST_F(PasswordGenerationAgentTest, AutomaticSuggestionOnHasBeenPasswordField) {
  LoadHTML(
      R"(
      <input type="text" id="username-field" name="username-field">
      <input type="password" id="password-field" name="password-field">
    )");
  constexpr char kPasswordElementId[] = "password-field";

  WebInputElement password_field = GetInputElementById(kPasswordElementId);

  // Simulate parser finding the field to trigger generation on.
  SetFoundFormEligibleForGeneration(
      password_generation_, GetMainFrame()->GetDocument(),
      /*new_password_id=*/kPasswordElementId, /*confirm_password_id=*/nullptr);

  // User clicks on reveal password button, changing the field type to
  // text.
  password_field.SetAttribute("type", "text");

  // Automatic generation should still be available.
  ExpectAutomaticGenerationAvailable(kPasswordElementId, kAvailable);
}

// Regression test for UAF in GeneratedPasswordAccepted() when JS re-enters
// MaybeCreateCurrentGenerationItem() while it loops over
// `current_generation_item_`'s password elements.
class PasswordGenerationAgentReentrantUafTest
    : public PasswordGenerationAgentTest {
 public:
  void RegisterMainFrameRemoteInterfaces() override {
    // On Android Chrome (default flags) a JS .focus() during transient user
    // activation synchronously routes through
    // AutofillAgent::FocusedElementChanged to
    // ShowPasswordGenerationSuggestions(). On desktop this path is disabled
    // (focus_requires_scroll = true). This fixture flips uses_platform_autofill
    // so that the AutofillAgent under test is created with
    // focus_requires_scroll = false, enabling the same synchronous path.
    blink::RendererPreferences prefs =
        GetMainRenderFrame()->GetWebView()->GetRendererPreferences();
    prefs.uses_platform_autofill = true;
    GetMainRenderFrame()->GetWebView()->SetRendererPreferences(prefs);
    PasswordGenerationAgentTest::RegisterMainFrameRemoteInterfaces();
  }
};

// Regression test for UAF in GeneratedPasswordAccepted(). The test passes if it
// doesn't crash.
TEST_F(PasswordGenerationAgentReentrantUafTest,
       GeneratedPasswordAcceptedReentrantFree) {
  // Allow any driver/client mojo calls during the re-entrant chain; the
  // crash happens before they're flushed but TearDown may still see them.
  EXPECT_CALL(fake_pw_client_, AutomaticGenerationAvailable).Times(AnyNumber());
  EXPECT_CALL(fake_pw_client_, PasswordNoLongerGenerated).Times(AnyNumber());
  EXPECT_CALL(fake_pw_client_, PresaveGeneratedPassword).Times(AnyNumber());
#if !BUILDFLAG(IS_ANDROID)
  EXPECT_CALL(fake_pw_client_, GenerationElementLostFocus()).Times(AnyNumber());
  EXPECT_CALL(fake_pw_client_, FrameWasScrolled()).Times(AnyNumber());
  EXPECT_CALL(fake_pw_client_, ShowPasswordEditingPopup).Times(AnyNumber());
  EXPECT_CALL(fake_pw_client_, PasswordGenerationRejectedByTyping())
      .Times(AnyNumber());
#endif

  LoadHTMLWithUserGesture(R"(
    <form id=f1 action=http://www.random.com/a>
      <input type=text id=userA>
      <input type=password id=pwA autocomplete=new-password>
    </form>
    <form id=f2 action=http://www.random.com/b>
      <input type=text id=userB>
      <input type=password id=pwB autocomplete=new-password>
    </form>
    <div id=txt tabindex=0>x</div>
    <input type=button id=dummy>)");

  // Mark both password fields as generation-eligible (simulates two
  // FoundFormEligibleForGeneration messages from the browser).
  SetFoundFormEligibleForGeneration(password_generation_,
                                    GetMainFrame()->GetDocument(),
                                    /*new_password_id=*/"pwA",
                                    /*confirm_password_id=*/nullptr);
  SetFoundFormEligibleForGeneration(password_generation_,
                                    GetMainFrame()->GetDocument(),
                                    /*new_password_id=*/"pwB",
                                    /*confirm_password_id=*/nullptr);

  // Click pwA: grants transient user activation and (via the
  // FocusedElementChanged -> ShowSuggestions path) creates
  // current_generation_item_ for pwA.
  ASSERT_TRUE(SimulateElementClick("pwA"));

  // Install an `input` handler on pwA. SetAutofillValue() inside the
  // GeneratedPasswordAccepted() loop dispatches `input` synchronously, so
  // this runs while the loop holds a reference into
  // current_generation_item_->password_elements_.
  //   1. pwA.value = ''  -> generation_element_.Value().length() == 0
  //   2. txt.focus()     -> moves focus off pwA so step 3 is a real change
  //   3. pwA.focus()     -> ShowPasswordGenerationSuggestions(pwA)
  //                         -> PasswordNoLongerGenerated()
  //                         -> password_is_generated_ = false  (disarm)
  //   4. pwB.focus()     -> MaybeCreateCurrentGenerationItem(pwB)
  //                         -> password_is_generated_ guard falls through
  //                         -> current_generation_item_ reassigned
  //                         -> old password_elements_ buffer freed
  ExecuteJavaScriptForTests(R"(
      var ran = false;
      document.getElementById('pwA').addEventListener('input', function() {
        if (ran) return; ran = true;
        document.getElementById('pwA').value = '';
        document.getElementById('txt').focus();
        document.getElementById('pwA').focus();
        document.getElementById('pwB').focus();
      });)");

  // GeneratedPasswordAccepted() contains the critical loop. In that loop,
  // SetAutofillValue(pwA) dispatches the input event above; on return
  // `current_generation_item->password_elements` and `password_element` have
  // been freed.
  password_generation_->GeneratedPasswordAccepted(u"random_password");
  SUCCEED() << "The test doesn't crash in the previous statement. ASAN bots "
               "would detect a UAF in the previous statement.";
}

}  // namespace
}  // namespace autofill
