chromium/ios/chrome/browser/passwords/model/password_controller_js_unittest.mm

// 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.

#import <Foundation/Foundation.h>

#import <string>

#import "base/json/json_string_value_serializer.h"
#import "base/strings/strcat.h"
#import "base/strings/sys_string_conversions.h"
#import "base/test/ios/wait_util.h"
#import "base/values.h"
#import "components/autofill/ios/browser/autofill_util.h"
#import "components/autofill/ios/form_util/form_util_java_script_feature.h"
#import "components/password_manager/ios/password_manager_java_script_feature.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/browser/web/model/chrome_web_client.h"
#import "ios/web/public/js_messaging/web_frame.h"
#import "ios/web/public/js_messaging/web_frames_manager.h"
#import "ios/web/public/test/js_test_util.h"
#import "ios/web/public/test/scoped_testing_web_client.h"
#import "ios/web/public/test/web_state_test_util.h"
#import "ios/web/public/test/web_task_environment.h"
#import "ios/web/public/web_state.h"
#import "testing/gmock/include/gmock/gmock.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"

using base::SysUTF8ToNSString;
using base::test::ios::kWaitForJSCompletionTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;
using ::testing::IsTrue;

// Unit tests for
// components/password_manager/ios/resources/password_controller.js
namespace {

// Serializes a dictionary value in a NSString.
NSString* SerializeDictValueToNSString(const base::Value::Dict& value) {
  std::string output;
  JSONStringValueSerializer serializer(&output);
  EXPECT_TRUE(serializer.Serialize(value));
  return base::SysUTF8ToNSString(output);
}

base::Value::Dict ParsedField(std::string renderer_id,
                              std::string control_type,
                              std::string identifier,
                              std::string value,
                              std::string label,
                              std::string name) {
  base::Value::Dict field = base::Value::Dict()
                                .Set("identifier", identifier)
                                .Set("name", name)
                                .Set("name_attribute", name)
                                .Set("id_attribute", "")
                                .Set("renderer_id", renderer_id)
                                .Set("form_control_type", control_type)
                                .Set("aria_label", "")
                                .Set("aria_description", "")
                                .Set("should_autocomplete", true)
                                .Set("is_focusable", true)
                                .Set("is_user_edited", true)
                                .Set("max_length", 524288)
                                .Set("is_checkable", false)
                                .Set("value", value)
                                .Set("label", label)
                                .Set("placeholder_attribute", "");
  return field;
}

// Returns the fill result payload when filling failed.
base::Value FillResultForFailure() {
  return base::Value(base::Value::Dict()
                         .Set("didAttemptFill", base::Value(false))
                         .Set("didFillUsername", base::Value(false))
                         .Set("didFillPassword", base::Value(false)));
}

// Returns the fill result payload when filling succeeded.
base::Value FillResultForSuccess(bool did_fill_username,
                                 bool did_fill_password) {
  return base::Value(
      base::Value::Dict()
          .Set("didAttemptFill", base::Value(true))
          .Set("didFillUsername", base::Value(did_fill_username))
          .Set("didFillPassword", base::Value(did_fill_password)));
}

std::unique_ptr<base::Value> ParseFormFillResult(id wk_result) {
  base::Value::Dict parsed_result;
  if (wk_result[@"didAttemptFill"]) {
    parsed_result.Set(
        "didAttemptFill",
        static_cast<bool>([wk_result[@"didAttemptFill"] boolValue]));
  }
  if (wk_result[@"didFillUsername"]) {
    parsed_result.Set(
        "didFillUsername",
        static_cast<bool>([wk_result[@"didFillUsername"] boolValue]));
  }
  if (wk_result[@"didFillPassword"]) {
    parsed_result.Set(
        "didFillPassword",
        static_cast<bool>([wk_result[@"didFillPassword"] boolValue]));
  }
  return std::make_unique<base::Value>(std::move(parsed_result));
}

// Text fixture to test password controller.
class PasswordControllerJsTest : public PlatformTest {
 public:
  PasswordControllerJsTest()
      : web_client_(std::make_unique<ChromeWebClient>()) {
    browser_state_ = TestChromeBrowserState::Builder().Build();

    web::WebState::CreateParams params(browser_state_.get());
    web_state_ = web::WebState::Create(params);
    web_state_->GetView();
    web_state_->SetKeepRenderProcessAlive(true);
  }

  bool SetUpUniqueIDs() {
    __block web::WebFrame* main_frame = nullptr;
    bool success =
        WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^bool {
          main_frame = GetMainWebFrame();
          return main_frame != nullptr;
        });
    if (!success) {
      return false;
    }
    DCHECK(main_frame);

    // Run password forms search to set up unique IDs.
    return FindPasswordForms() != nil;
  }

  web::WebFrame* GetMainWebFrame() {
    password_manager::PasswordManagerJavaScriptFeature* feature =
        password_manager::PasswordManagerJavaScriptFeature::GetInstance();
    return feature->GetWebFramesManager(web_state())->GetMainWebFrame();
  }

  // Finds all password forms in the window and returns for data as a JSON
  // string.
  NSString* FindPasswordForms() {
    web::WebFrame* main_frame = GetMainWebFrame();
    // Run password forms search to set up unique IDs.
    __block bool complete = false;
    __block NSString* result = nil;
    password_manager::PasswordManagerJavaScriptFeature::GetInstance()
        ->FindPasswordFormsInFrame(main_frame,
                                   base::BindOnce(^(NSString* forms) {
                                     result = forms;
                                     complete = true;
                                   }));

    EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^bool {
      return complete;
    }));

    return result;
  }

  std::string BaseUrl() { return web_state()->GetLastCommittedURL().spec(); }

  NSString* PageOrigin() { return SysUTF8ToNSString(BaseUrl() + "origin1"); }

  NSString* FormOrigin() { return SysUTF8ToNSString(BaseUrl() + "origin2"); }

  // Returns fill data for a form with prefilled values. Will leave the fill
  // data empty for the field if its renderer id is 0. Make sure that the names
  // used here correspond to the ones in the HTML content.
  base::Value::Dict FormFillData(int username_renderer_id,
                                 int password_renderer_id) {
    auto fill_data = base::Value::Dict()
                         .Set("action", base::SysNSStringToUTF8(PageOrigin()))
                         .Set("origin", base::SysNSStringToUTF8(FormOrigin()))
                         .Set("name", "login_form")
                         .Set("renderer_id", 1);

    auto fields = base::Value::List();

    if (username_renderer_id) {
      fields.Append(base::Value::Dict()
                        .Set("name", "username")
                        .Set("value", "username")
                        .Set("renderer_id", username_renderer_id));
    } else {
      fields.Append(base::Value::Dict()
                        .Set("name", "")
                        .Set("value", "")
                        .Set("renderer_id", 0));
    }

    if (password_renderer_id) {
      fields.Append(base::Value::Dict()
                        .Set("name", "password")
                        .Set("value", "password")
                        .Set("renderer_id", password_renderer_id));
    } else {
      fields.Append(base::Value::Dict()
                        .Set("name", "")
                        .Set("value", "")
                        .Set("renderer_id", 0));
    }

    fill_data.Set("fields", std::move(fields));
    return fill_data;
  }

 protected:
  id ExecuteJavaScript(NSString* script) {
    password_manager::PasswordManagerJavaScriptFeature* feature =
        password_manager::PasswordManagerJavaScriptFeature::GetInstance();
    return web::test::ExecuteJavaScriptForFeature(web_state(), script, feature);
  }

  web::WebState* web_state() { return web_state_.get(); }

  web::ScopedTestingWebClient web_client_;
  web::WebTaskEnvironment task_environment_;
  std::unique_ptr<TestChromeBrowserState> browser_state_;
  std::unique_ptr<web::WebState> web_state_;
};

// IDs used in the Username and Password <input> elements.
NSString* const kEmailInputID = @"Email";
NSString* const kPasswordInputID = @"Passwd";

// Fake username and password used for sign-in.
NSString* const kUsername = @"[email protected]";
NSString* const kPassword = @"super!secret";

// Returns an autoreleased string of an HTML form that is similar to the
// Google Accounts sign in form. `email` may be nil if the form does not
// need to be pre-filled with the username. Use `isReadOnly` flag to indicate
// if the email field should be read-only.
NSString* GAIASignInForm(NSString* formAction,
                         NSString* email,
                         BOOL isReadOnly,
                         BOOL isDisabled) {
  return [NSString
      stringWithFormat:
          @"<html><body>"
           "<form novalidate action=\"%@\" "
           "id=\"gaia_loginform\">"
           "  <input name=\"GALX\" type=\"hidden\" value=\"abcdefghij\">"
           "  <input name=\"service\" type=\"hidden\" value=\"mail\">"
           "  <input id=\"%@\" name=\"Email\" type=\"email\" value=\"%@\" %@ "
           "%@>"
           "  <input id=\"%@\" name=\"Passwd\" type=\"password\" "
           "    placeholder=\"Password\">"
           "</form></body></html>",
          formAction, kEmailInputID, email ? email : @"",
          isReadOnly ? @"readonly" : @"", isDisabled ? @"disabled" : @"",
          kPasswordInputID];
}

// Returns an autoreleased string of JSON for a parsed form.
NSString* GAIASignInFormData(NSString* formOrigin, NSString* formName) {
  return
      [NSString stringWithFormat:
                    @"{"
                     "  \"origin\":\"%@\","
                     "  \"name\":\"%@\","
                     "  \"renderer_id\":1,"
                     "  \"fields\":["
                     "    {\"name\":\"%@\", \"value\":\"\", \"renderer_id\":2},"
                     "    {\"name\":\"%@\",\"value\":\"\", \"renderer_id\":3},"
                     "  ]"
                     "}",
                    formOrigin, formName, kEmailInputID, kPasswordInputID];
}

// Tests that an attempt to fill in credentials when the username field is
// read-only succeeds, but by only filling the password while skipping filling
// the username.
TEST_F(PasswordControllerJsTest,
       FillPasswordFormWithPrefilledUsername_SucceedsWhenReadOnly) {
  const std::string origin = "https://accounts.google.com/ServiceLoginAuth";
  NSString* const formOrigin = [NSString stringWithUTF8String:origin.c_str()];
  NSString* const formName = @"gaia_loginform";
  NSString* const username2 = @"[email protected]";
  web::test::LoadHtml(GAIASignInForm(formOrigin, kUsername, /*isReadOnly=*/YES,
                                     /*isDisabled=*/NO),
                      GURL(origin), web_state());
  ASSERT_TRUE(SetUpUniqueIDs());

  // Expect the attempt to succeeds where the username is skipped and the
  // password filled.
  auto expected_result =
      base::Value(base::Value::Dict()
                      .Set("didAttemptFill", base::Value(true))
                      .Set("didFillUsername", base::Value(false))
                      .Set("didFillPassword", base::Value(true)));

  auto result = ParseFormFillResult(ExecuteJavaScript([NSString
      stringWithFormat:@"__gCrWeb.passwords.fillPasswordForm(%@, '%@', '%@')",
                       GAIASignInFormData(formOrigin, formName), username2,
                       kPassword]));
  // Expect success but without filling the username field.
  EXPECT_EQ(FillResultForSuccess(/*did_fill_username=*/false,
                                 /*did_fill_password=*/true),
            *result);

  // Verifies that the sign-in form has been filled with username/password.
  NSString* email_js = [NSString
      stringWithFormat:@"document.getElementById('%@').value", kEmailInputID];
  EXPECT_NSEQ(kUsername, ExecuteJavaScript(email_js));

  NSString* password_js =
      [NSString stringWithFormat:@"document.getElementById('%@').value",
                                 kPasswordInputID];
  EXPECT_NSEQ(kPassword, ExecuteJavaScript(password_js));
}

// Loads a page with a password form containing a username value already.
// Checks that an attempt to fill in credentials with a different username
// succeeds, as long as the field is editable.
TEST_F(
    PasswordControllerJsTest,
    FillPasswordFormWithPrefilledUsername_SucceedsByOverridingUsernameWhenEditable) {
  const std::string origin = "https://accounts.google.com/ServiceLoginAuth";
  NSString* const formOrigin = [NSString stringWithUTF8String:origin.c_str()];
  NSString* const formName = @"gaia_loginform";
  NSString* const username2 = @"[email protected]";
  web::test::LoadHtml(GAIASignInForm(formOrigin, kUsername, /*isReadOnly=*/NO,
                                     /*isDisabled=*/NO),
                      GURL(origin), web_state());
  ASSERT_TRUE(SetUpUniqueIDs());

  auto result = ParseFormFillResult(ExecuteJavaScript([NSString
      stringWithFormat:@"__gCrWeb.passwords.fillPasswordForm(%@, '%@', '%@')",
                       GAIASignInFormData(formOrigin, formName), username2,
                       kPassword]));
  // Expect success with both fields filled.
  EXPECT_EQ(FillResultForSuccess(/*did_fill_username=*/true,
                                 /*did_fill_password=*/true),
            *result);

  // Verifies that the sign-in form has been filled with the new username
  // and password.

  NSString* email_js = [NSString
      stringWithFormat:@"document.getElementById('%@').value", kEmailInputID];
  EXPECT_NSEQ(username2, ExecuteJavaScript(email_js));

  NSString* password_js =
      [NSString stringWithFormat:@"document.getElementById('%@').value",
                                 kPasswordInputID];
  EXPECT_NSEQ(kPassword, ExecuteJavaScript(password_js));
}

// Loads a page with a password form containing a disabled input with username
// value already. Checks that an attempt to fill in credentials succeeds, and
// the password is filled while the skipping filling the username.
TEST_F(
    PasswordControllerJsTest,
    FillPasswordFormWithPrefilledUsername_SucceedsWhenUsernameMatchesAndIsDisabled) {
  const std::string origin = "https://accounts.google.com/ServiceLoginAuth";
  NSString* const formOrigin = [NSString stringWithUTF8String:origin.c_str()];
  NSString* const formName = @"gaia_loginform";
  NSString* const username2 = @"[email protected]";
  web::test::LoadHtml(GAIASignInForm(formOrigin, kUsername, /*isReadOnly=*/NO,
                                     /*isDisabled=*/YES),
                      GURL(origin), web_state());
  ASSERT_TRUE(SetUpUniqueIDs());

  auto result = ParseFormFillResult(ExecuteJavaScript([NSString
      stringWithFormat:@"__gCrWeb.passwords.fillPasswordForm(%@, '%@', '%@')",
                       GAIASignInFormData(formOrigin, formName), username2,
                       kPassword]));
  // Expect success without filling the username field.
  EXPECT_EQ(FillResultForSuccess(/*did_fill_username=*/false,
                                 /*did_fill_password=*/true),
            *result);

  // Verifies that the sign-in form has been filled with password and username
  // value remained the same.
  NSString* email_js = [NSString
      stringWithFormat:@"document.getElementById('%@').value", kEmailInputID];
  EXPECT_NSEQ(kUsername, ExecuteJavaScript(email_js));

  NSString* password_js =
      [NSString stringWithFormat:@"document.getElementById('%@').value",
                                 kPasswordInputID];
  EXPECT_NSEQ(kPassword, ExecuteJavaScript(password_js));
}

// Check that one password form is identified and serialized correctly.
TEST_F(PasswordControllerJsTest, GetPasswordForms_SingleFrameAndSingleForm) {
  web::test::LoadHtml(
      @"<html><body>"
       "<form action='/generic_submit' method='post' name='login_form'>"
       "  Name: <input type='text' name='username'>"
       "  Password: <input type='password' name='password'>"
       "  <input type='submit' value='Submit'>"
       "</form>"
       "</body></html>",
      web_state());
  ASSERT_TRUE(SetUpUniqueIDs());

  auto expected_form =
      base::Value::Dict()
          .Set("name", "login_form")
          .Set("origin", BaseUrl())
          .Set("action", base::StrCat({BaseUrl(), "generic_submit"}))
          .Set("name_attribute", "login_form")
          .Set("id_attribute", "")
          .Set("renderer_id", "1")
          .Set("frame_id", GetMainWebFrame()->GetFrameId());
  base::Value::Dict expected_username_field =
      ParsedField(/*renderer_id=*/"2", /*contole_type=*/"text",
                  /*identifier=*/"username", /*value=*/"",
                  /*label=*/"Name:", /*name=*/"username");
  base::Value::Dict expected_password_field = ParsedField(
      /*renderer_id=*/"3", /*contole_type=*/"password",
      /*identifier=*/"password", /*value=*/"",
      /*label=*/"Password:", /*name=*/"password");
  auto expected_fields = base::Value::List()
                             .Append(std::move(expected_username_field))
                             .Append(std::move(expected_password_field));
  expected_form.Set("fields", std::move(expected_fields));
  base::Value::List expected_results =
      base::Value::List().Append(std::move(expected_form));

  std::unique_ptr<base::Value> results =
      autofill::ParseJson(FindPasswordForms());
  ASSERT_TRUE(results);

  EXPECT_EQ(expected_results, *results);
}

// Check that multiple password forms are identified and parsed & serialized
// correctly.
TEST_F(PasswordControllerJsTest, GetPasswordForms_SingleFrameAndMultipleForms) {
  web::test::LoadHtml(@"<html><body>"
                       "<form action='/generic_submit1' name='login_form1'>"
                       "  Name: <input type='text' name='username'>"
                       "  Password: <input type='password' name='password'>"
                       "  <input type='submit' value='Submit'>"
                       "</form>"
                       "<form action='/generic_submit2' name='login_form2'>"
                       "  Name: <input type='text' name='username2'>"
                       "  Password: <input type='password' name='password2'>"
                       "  <input type='submit' value='Submit'>"
                       "</form>"
                       "</body></html>",
                      web_state());
  ASSERT_TRUE(SetUpUniqueIDs());

  // Build expected parsed forms results.
  base::Value::List expected_results = base::Value::List();
  // Set expected form 1.
  {
    auto expected_form =
        base::Value::Dict()
            .Set("name", "login_form1")
            .Set("origin", BaseUrl())
            .Set("action", base::StrCat({BaseUrl(), "generic_submit1"}))
            .Set("name_attribute", "login_form1")
            .Set("id_attribute", "")
            .Set("renderer_id", "1")
            .Set("frame_id", GetMainWebFrame()->GetFrameId());

    base::Value::Dict expected_username_field =
        ParsedField(/*renderer_id=*/"2", /*contole_type=*/"text",
                    /*identifier=*/"username", /*value=*/"",
                    /*label=*/"Name:", /*name=*/"username");
    base::Value::Dict expected_password_field = ParsedField(
        /*renderer_id=*/"3", /*contole_type=*/"password",
        /*identifier=*/"password", /*value=*/"",
        /*label=*/"Password:", /*name=*/"password");
    auto expected_fields = base::Value::List()
                               .Append(std::move(expected_username_field))
                               .Append(std::move(expected_password_field));
    expected_form.Set("fields", std::move(expected_fields));

    expected_results.Append(std::move(expected_form));
  }
  // Set expected form 2.
  {
    auto expected_form =
        base::Value::Dict()
            .Set("name", "login_form2")
            .Set("origin", BaseUrl())
            .Set("action", base::StrCat({BaseUrl(), "generic_submit2"}))
            .Set("name_attribute", "login_form2")
            .Set("id_attribute", "")
            .Set("renderer_id", "4")
            .Set("frame_id", GetMainWebFrame()->GetFrameId());
    base::Value::Dict expected_username_field =
        ParsedField(/*renderer_id=*/"5", /*contole_type=*/"text",
                    /*identifier=*/"username2", /*value=*/"",
                    /*label=*/"Name:", /*name=*/"username2");
    base::Value::Dict expected_password_field = ParsedField(
        /*renderer_id=*/"6", /*contole_type=*/"password",
        /*identifier=*/"password2", /*value=*/"",
        /*label=*/"Password:", /*name=*/"password2");
    auto expected_fields = base::Value::List()
                               .Append(std::move(expected_username_field))
                               .Append(std::move(expected_password_field));
    expected_form.Set("fields", std::move(expected_fields));

    expected_results.Append(std::move(expected_form));
  }

  std::unique_ptr<base::Value> results =
      autofill::ParseJson(FindPasswordForms());
  ASSERT_TRUE(results);

  EXPECT_EQ(expected_results, *results);
}

// Test serializing of password forms by directly calling the javascript code.
TEST_F(PasswordControllerJsTest, GetPasswordForms_DirectJsCall) {
  web::test::LoadHtml(@"<html><body>"
                       "<form name='login_form' action='/generic_submit'>"
                       "  Name: <input type='text' name='username'>"
                       "  Password: <input type='password' name='password'>"
                       "  <input type='submit' value='Submit'>"
                       "</form>"
                       "</body></html>",
                      web_state());
  ASSERT_TRUE(SetUpUniqueIDs());

  auto expected_form =
      base::Value::Dict()
          .Set("name", "login_form")
          .Set("origin", BaseUrl())
          .Set("action", base::StrCat({BaseUrl(), "generic_submit"}))
          .Set("name_attribute", "login_form")
          .Set("id_attribute", "")
          .Set("renderer_id", "1")
          .Set("frame_id", GetMainWebFrame()->GetFrameId())
          .Set("fields", base::Value::List());

  base::Value::Dict expected_username_field =
      ParsedField(/*renderer_id=*/"2", /*contole_type=*/"text",
                  /*identifier=*/"username", /*value=*/"",
                  /*label=*/"Name:", /*name=*/"username");
  base::Value::Dict expected_password_field = ParsedField(
      /*renderer_id=*/"3", /*contole_type=*/"password",
      /*identifier=*/"password", /*value=*/"",
      /*label=*/"Password:", /*name=*/"password");
  auto expected_fields = base::Value::List()
                             .Append(std::move(expected_username_field))
                             .Append(std::move(expected_password_field));
  expected_form.Set("fields", std::move(expected_fields));

  NSString* parameter = @"window.document.getElementsByTagName('form')[0]";

  std::unique_ptr<base::Value> results = autofill::ParseJson(ExecuteJavaScript(
      [NSString stringWithFormat:@"__gCrWeb.stringify(__gCrWeb.passwords."
                                 @"getPasswordFormData(%@, window))",
                                 parameter]));
  ASSERT_TRUE(results);

  EXPECT_EQ(expected_form, *results);
}

// Check that if a form action is not set then the action is parsed to the
// current url.
TEST_F(PasswordControllerJsTest, GetPasswordForms_FormActionIsNotSet) {
  web::test::LoadHtml(@"<html><body>"
                       "<form name='login_form'>"
                       "  Name: <input type='text' name='username'>"
                       "  Password: <input type='password' name='password'>"
                       "  <input type='submit' value='Submit'>"
                       "</form>"
                       "</body></html>",
                      web_state());
  ASSERT_TRUE(SetUpUniqueIDs());

  auto expected_form = base::Value::Dict()
                           .Set("name", "login_form")
                           .Set("origin", BaseUrl())
                           .Set("action", BaseUrl())
                           .Set("name_attribute", "login_form")
                           .Set("id_attribute", "")
                           .Set("renderer_id", "1")
                           .Set("frame_id", GetMainWebFrame()->GetFrameId());
  base::Value::Dict expected_username_field =
      ParsedField(/*renderer_id=*/"2", /*contole_type=*/"text",
                  /*identifier=*/"username", /*value=*/"",
                  /*label=*/"Name:", /*name=*/"username");
  base::Value::Dict expected_password_field = ParsedField(
      /*renderer_id=*/"3", /*contole_type=*/"password",
      /*identifier=*/"password", /*value=*/"",
      /*label=*/"Password:", /*name=*/"password");
  auto expected_fields = base::Value::List()
                             .Append(std::move(expected_username_field))
                             .Append(std::move(expected_password_field));
  expected_form.Set("fields", std::move(expected_fields));
  base::Value::List expected_results =
      base::Value::List().Append(std::move(expected_form));

  std::unique_ptr<base::Value> results =
      autofill::ParseJson(FindPasswordForms());
  ASSERT_TRUE(results);

  EXPECT_EQ(expected_results, *results);
}

// Check that a single username form can be extracted when its input has a
// "username" autocomplete value.
TEST_F(PasswordControllerJsTest,
       GetPasswordForms_UsernameField_WithUsernameAutocompleteAttribute) {
  web::test::LoadHtml(@"<html><body>"
                       "<form name='login_form' action='/generic_submit'>"
                       "  Name: <input type='text' name='username' "
                       "autocomplete='username'>"
                       "</form>"
                       "</body></html>",
                      web_state());
  ASSERT_TRUE(SetUpUniqueIDs());

  auto expected_form =
      base::Value::Dict()
          .Set("name", "login_form")
          .Set("origin", BaseUrl())
          .Set("action", base::StrCat({BaseUrl(), "generic_submit"}))
          .Set("name_attribute", "login_form")
          .Set("id_attribute", "")
          .Set("renderer_id", "1")
          .Set("frame_id", GetMainWebFrame()->GetFrameId());
  base::Value::Dict expected_username_field =
      ParsedField(/*renderer_id=*/"2", /*contole_type=*/"text",
                  /*identifier=*/"username", /*value=*/"",
                  /*label=*/"Name:", /*name=*/"username");
  // Set the autocomplete_attribute key here which is only set if there is an
  // autocomplete attribute in the input field.
  expected_username_field.Set("autocomplete_attribute", "username");
  auto expected_fields =
      base::Value::List().Append(std::move(expected_username_field));
  expected_form.Set("fields", std::move(expected_fields));
  base::Value::List expected_results =
      base::Value::List().Append(std::move(expected_form));

  std::unique_ptr<base::Value> results =
      autofill::ParseJson(FindPasswordForms());
  ASSERT_TRUE(results);

  EXPECT_EQ(expected_results, *results);
}

// Check that the form can be extracted when one of its input has a "webauthn"
// autocomplete value.
TEST_F(PasswordControllerJsTest,
       GetPasswordForms_UsernameField_WithWebauthnAutocompleteAttribute) {
  web::test::LoadHtml(@"<html><body>"
                       "<form name='login_form' action='/generic_submit'>"
                       "  Name: <input type='text' name='username' "
                       "autocomplete='webauthn'>"
                       "</form>"
                       "</body></html>",
                      web_state());
  ASSERT_TRUE(SetUpUniqueIDs());

  auto expected_form =
      base::Value::Dict()
          .Set("name", "login_form")
          .Set("origin", BaseUrl())
          .Set("action", base::StrCat({BaseUrl(), "generic_submit"}))
          .Set("name_attribute", "login_form")
          .Set("id_attribute", "")
          .Set("renderer_id", "1")
          .Set("frame_id", GetMainWebFrame()->GetFrameId());
  base::Value::Dict expected_username_field =
      ParsedField(/*renderer_id=*/"2", /*contole_type=*/"text",
                  /*identifier=*/"username", /*value=*/"",
                  /*label=*/"Name:", /*name=*/"username");
  // Set the autocomplete_attribute field here which is only set if there is
  // an autocomplete attribute in the field.
  expected_username_field.Set("autocomplete_attribute", "webauthn");
  auto expected_fields =
      base::Value::List().Append(std::move(expected_username_field));
  expected_form.Set("fields", std::move(expected_fields));
  base::Value::List expected_results =
      base::Value::List().Append(std::move(expected_form));

  std::unique_ptr<base::Value> results =
      autofill::ParseJson(FindPasswordForms());
  ASSERT_TRUE(results);

  EXPECT_EQ(expected_results, *results);
}

// Check that a form that isn't identified as a password form is rejected.
TEST_F(PasswordControllerJsTest,
       GetPasswordForms_UsernameField_NonPwdFormRejected) {
  web::test::LoadHtml(@"<html><body>"
                       "<form name='np' id='np1' action='/generic_submit'>"
                       "  Name: <input type='text' name='name' "
                       "autocomplete='address'>"
                       "</form>"
                       "</body></html>",
                      web_state());
  ASSERT_TRUE(SetUpUniqueIDs());

  std::string mainFrameID = GetMainWebFrame()->GetFrameId();
  NSString* expected_result = [NSString stringWithFormat:@"[]"];

  std::unique_ptr<base::Value> expected_result_json =
      autofill::ParseJson(expected_result);
  ASSERT_TRUE(expected_result);

  std::unique_ptr<base::Value> result_json =
      autofill::ParseJson(FindPasswordForms());
  ASSERT_TRUE(result_json);

  EXPECT_EQ(*expected_result_json, *result_json);
}

// Checks that a touchend event from a button which contains in a password form
// works as a submission indicator for this password form.
TEST_F(PasswordControllerJsTest, TouchendAsSubmissionIndicator) {
  web::test::LoadHtml(@"<html><body>"
                       "<form name='login_form' id='login_form'>"
                       "  Name: <input type='text' name='username'>"
                       "  Password: <input type='password' name='password'>"
                       "  <button id='submit_button' value='Submit'>"
                       "</form>"
                       "</body></html>",
                      web_state());
  ASSERT_TRUE(SetUpUniqueIDs());

  // Call __gCrWeb.passwords.findPasswordForms in order to set an event handler
  // on the button touchend event.
  FindPasswordForms();

  // Replace __gCrWeb.common.sendWebKitMessage with mock method for checking of
  // call arguments.
  ExecuteJavaScript(
      @"var submittedFormData = null;"
       "var submittedFormMessageCalls = 0;"
       "__gCrWeb.common.sendWebKitMessage = function(messageName, messageData) "
       "{"
       "  if (messageName == 'PasswordFormSubmitButtonClick') {"
       "    submittedFormData = messageData;"
       "    submittedFormMessageCalls++;"
       "  }"
       "}");

  // Simulate touchend event on the button.
  ExecuteJavaScript(
      @"document.getElementsByName('username')[0].value = 'user1';"
       "document.getElementsByName('password')[0].value = 'password1';"
       "var e = new UIEvent('touchend');"
       "document.getElementsByTagName('button')[0].dispatchEvent(e);");

  // Check that there was only 1 call for sendWebKitMessage.
  EXPECT_NSEQ(@1, ExecuteJavaScript(@"submittedFormMessageCalls"));

  auto expected_form = base::Value::Dict()
                           .Set("name", "login_form")
                           .Set("origin", BaseUrl())
                           .Set("action", BaseUrl())
                           .Set("name_attribute", "login_form")
                           .Set("id_attribute", "login_form")
                           .Set("renderer_id", "1")
                           .Set("frame_id", GetMainWebFrame()->GetFrameId());
  base::Value::Dict expected_username_field = ParsedField(
      /*renderer_id=*/"2", /*contole_type=*/"text",
      /*identifier=*/"username", /*value=*/"user1",
      /*label=*/"Name:", /*name=*/"username");
  base::Value::Dict expected_password_field = ParsedField(
      /*renderer_id=*/"3", /*contole_type=*/"password",
      /*identifier=*/"password", /*value=*/"password1",
      /*label=*/"Password:", /*name=*/"password");
  auto expected_fields = base::Value::List()
                             .Append(std::move(expected_username_field))
                             .Append(std::move(expected_password_field));
  expected_form.Set("fields", std::move(expected_fields));

  std::unique_ptr<base::Value> results = autofill::ParseJson(
      ExecuteJavaScript(@"__gCrWeb.stringify(submittedFormData)"));
  ASSERT_TRUE(results);

  // Check that sendWebKitMessage was called with the correct argument.
  EXPECT_EQ(expected_form, *results);
}

// Check that a form is filled if url of a page and url in form fill data are
// different only in paths.
TEST_F(PasswordControllerJsTest, OriginsAreDifferentInPaths) {
  web::test::LoadHtml(
      @"<html><body>"
       "<form name='login_form' action='action1'>"
       "  Name: <input type='text' name='name' id='name'>"
       "  Password: <input type='password' name='password' id='password'>"
       "  <input type='submit' value='Submit'>"
       "</form>"
       "</body></html>",
      web_state());
  ASSERT_TRUE(SetUpUniqueIDs());

  NSString* form_fill_data = SerializeDictValueToNSString(
      FormFillData(/*username_renderer_id=*/2, /*password_renderer_id=*/3));
  auto result = ParseFormFillResult(ExecuteJavaScript([NSString
      stringWithFormat:@"__gCrWeb.passwords.fillPasswordForm(%@, '%@', '%@')",
                       form_fill_data, kUsername, kPassword]));
  // Expect success with both fields filled.
  EXPECT_EQ(FillResultForSuccess(/*did_fill_username=*/true,
                                 /*did_fill_password=*/true),
            *result);

  // Verifies that the sign-in form has been filled with username/password.
  EXPECT_NSEQ(kUsername,
              ExecuteJavaScript(@"document.getElementById('name').value"));
  EXPECT_NSEQ(kPassword,
              ExecuteJavaScript(@"document.getElementById('password').value"));
}

// Check that when instructed to fill a form named "bar", a form named "foo"
// is not filled with generated password.
TEST_F(PasswordControllerJsTest,
       FillPasswordFormWithGeneratedPassword_FailsWhenFormNotFound) {
  web::test::LoadHtml(@"<html>"
                       "  <body>"
                       "    <form name=\"foo\">"
                       "      <input type=\"password\" id=\"ps1\" name=\"ps\">"
                       "    </form>"
                       "  </body"
                       "</html>",
                      web_state());
  ASSERT_TRUE(SetUpUniqueIDs());

  uint32_t formIdentifier = 404;
  uint32_t newPasswordIdentifier = 2;
  EXPECT_NSEQ(
      @NO, ExecuteJavaScript([NSString
               stringWithFormat:
                   @"__gCrWeb.passwords."
                   @"fillPasswordFormWithGeneratedPassword(%d, %d, %d, '%@')",
                   formIdentifier, newPasswordIdentifier, 0, kPassword]));
}

// Check that filling a form without password fields fails.
TEST_F(PasswordControllerJsTest,
       FillPasswordFormWithGeneratedPassword_FailsWhenNoPasswordFields) {
  web::test::LoadHtml(@"<html>"
                       "  <body>"
                       "    <form name=\"foo\">"
                       "      <input type=\"text\" name=\"user\">"
                       "      <input type=\"submit\" name=\"go\">"
                       "    </form>"
                       "  </body"
                       "</html>",
                      web_state());
  ASSERT_TRUE(SetUpUniqueIDs());

  uint32_t formIdentifier = 1;
  uint32_t const newPasswordIdentifier = 4;
  uint32_t const confirmPasswordIdentifier = 5;
  EXPECT_NSEQ(
      @NO, ExecuteJavaScript([NSString
               stringWithFormat:
                   @"__gCrWeb.passwords."
                   @"fillPasswordFormWithGeneratedPassword(%d, %d, %d, '%@')",
                   formIdentifier, newPasswordIdentifier,
                   confirmPasswordIdentifier, kPassword]));
}

// Check that a matching and complete password form is successfully filled
// with the generated password.
TEST_F(PasswordControllerJsTest,
       FillPasswordFormWithGeneratedPassword_SucceedsWhenFieldsFilled) {
  web::test::LoadHtml(@"<html>"
                       "  <body>"
                       "    <form name=\"foo\">"
                       "      <input type=\"text\" id=\"user\" name=\"user\">"
                       "      <input type=\"password\" id=\"ps1\" name=\"ps1\">"
                       "      <input type=\"password\" id=\"ps2\" name=\"ps2\">"
                       "      <input type=\"submit\" name=\"go\">"
                       "    </form>"
                       "  </body"
                       "</html>",
                      web_state());
  ASSERT_TRUE(SetUpUniqueIDs());

  uint32_t formIdentifier = 1;
  uint32_t const newPasswordIdentifier = 3;
  uint32_t const confirmPasswordIdentifier = 4;
  EXPECT_NSEQ(
      @YES, ExecuteJavaScript([NSString
                stringWithFormat:
                    @"__gCrWeb.passwords."
                    @"fillPasswordFormWithGeneratedPassword(%u, %u, %u, '%@')",
                    formIdentifier, newPasswordIdentifier,
                    confirmPasswordIdentifier, kPassword]));
  EXPECT_NSEQ(
      @YES,
      ExecuteJavaScript([NSString
          stringWithFormat:@"document.getElementById('ps1').value == '%@'",
                           kPassword]));
  EXPECT_NSEQ(
      @YES,
      ExecuteJavaScript([NSString
          stringWithFormat:@"document.getElementById('ps2').value == '%@'",
                           kPassword]));
  EXPECT_NSEQ(
      @NO,
      ExecuteJavaScript([NSString
          stringWithFormat:@"document.getElementById('user').value == '%@'",
                           kPassword]));
}

// Check that a matching and complete password field is successfully filled
// with the generated password and that confirm field is untouched.
TEST_F(
    PasswordControllerJsTest,
    FillPasswordFormWithGeneratedPassword_SucceedsWhenOnlyNewPasswordFilled) {
  web::test::LoadHtml(@"<html>"
                       "  <body>"
                       "    <form name=\"foo\">"
                       "      <input type=\"text\" id=\"user\" name=\"user\">"
                       "      <input type=\"password\" id=\"ps1\" name=\"ps1\">"
                       "      <input type=\"password\" id=\"ps2\" name=\"ps2\">"
                       "      <input type=\"submit\" name=\"go\">"
                       "    </form>"
                       "  </body"
                       "</html>",
                      web_state());
  ASSERT_TRUE(SetUpUniqueIDs());

  uint32_t formIdentifier = 1;
  uint32_t const newPasswordIdentifier = 3;
  EXPECT_NSEQ(
      @YES, ExecuteJavaScript([NSString
                stringWithFormat:
                    @"__gCrWeb.passwords."
                    @"fillPasswordFormWithGeneratedPassword(%u, %u, %u, '%@')",
                    formIdentifier, newPasswordIdentifier, 0, kPassword]));
  EXPECT_NSEQ(
      @YES,
      ExecuteJavaScript([NSString
          stringWithFormat:@"document.getElementById('ps1').value == '%@'",
                           kPassword]));
  EXPECT_NSEQ(
      @YES,
      ExecuteJavaScript([NSString
          stringWithFormat:@"document.getElementById('ps2').value == '%@'",
                           @""]));
  EXPECT_NSEQ(
      @NO,
      ExecuteJavaScript([NSString
          stringWithFormat:@"document.getElementById('user').value == '%@'",
                           kPassword]));
}

// Check that a matching and complete confirm password field is successfully
// filled with the generated password and that new password field is untouched.
TEST_F(
    PasswordControllerJsTest,
    FillPasswordFormWithGeneratedPassword_FailsWhenOnlyConfirmPasswordFilled) {
  web::test::LoadHtml(@"<html>"
                       "  <body>"
                       "    <form name=\"foo\">"
                       "      <input type=\"text\" id=\"user\" name=\"user\">"
                       "      <input type=\"password\" id=\"ps1\" name=\"ps1\">"
                       "      <input type=\"password\" id=\"ps2\" name=\"ps2\">"
                       "      <input type=\"submit\" name=\"go\">"
                       "    </form>"
                       "  </body"
                       "</html>",
                      web_state());
  ASSERT_TRUE(SetUpUniqueIDs());

  uint32_t formIdentifier = 1;
  uint32_t const confirmPasswordIdentifier = 4;
  EXPECT_NSEQ(
      @NO, ExecuteJavaScript([NSString
               stringWithFormat:
                   @"__gCrWeb.passwords."
                   @"fillPasswordFormWithGeneratedPassword(%u, %u, %u, '%@')",
                   formIdentifier, 0, confirmPasswordIdentifier, kPassword]));
  EXPECT_NSEQ(@YES,
              ExecuteJavaScript(@"document.getElementById('ps1').value == ''"));
  EXPECT_NSEQ(@YES,
              ExecuteJavaScript(@"document.getElementById('ps2').value == ''"));
  EXPECT_NSEQ(
      @YES, ExecuteJavaScript(@"document.getElementById('user').value == ''"));
}

// Check that unknown or null identifiers are handled gracefully.
TEST_F(
    PasswordControllerJsTest,
    FillPasswordFormWithGeneratedPassword_SucceedsOnUnknownOrNullIdentifiers) {
  web::test::LoadHtml(@"<html>"
                       "  <body>"
                       "    <form name=\"foo\">"
                       "      <input type=\"text\" id=\"user\" name=\"user\">"
                       "      <input type=\"password\" id=\"ps1\" name=\"ps1\">"
                       "      <input type=\"password\" id=\"ps2\" name=\"ps2\">"
                       "      <input type=\"submit\" name=\"go\">"
                       "    </form>"
                       "  </body"
                       "</html>",
                      web_state());
  ASSERT_TRUE(SetUpUniqueIDs());

  uint32_t formIdentifier = 1;
  EXPECT_NSEQ(@NO, ExecuteJavaScript([NSString
                       stringWithFormat:@"__gCrWeb.passwords."
                                        @"fillPasswordFormWithGeneratedPasswo"
                                        @"rd(%u, '%@', null, '%@')",
                                        formIdentifier, @"hello", kPassword]));
  EXPECT_NSEQ(@YES,
              ExecuteJavaScript(@"document.getElementById('ps1').value == ''"));
  EXPECT_NSEQ(@YES,
              ExecuteJavaScript(@"document.getElementById('ps2').value == ''"));
  EXPECT_NSEQ(
      @NO,
      ExecuteJavaScript([NSString
          stringWithFormat:@"document.getElementById('user').value == '%@'",
                           kPassword]));
}

// Check that a matching and complete password form is successfully filled
// with the generated password.
TEST_F(PasswordControllerJsTest,
       FillPasswordFormWithGeneratedPassword_SucceedsOutsideFormTag) {
  web::test::LoadHtml(@"<html>"
                       "  <body>"
                       "    <input type=\"text\" id=\"user\" name=\"user\">"
                       "    <input type=\"password\" id=\"ps1\" name=\"ps1\">"
                       "    <input type=\"password\" id=\"ps2\" name=\"ps2\">"
                       "    <input type=\"submit\" name=\"go\">"
                       "  </body>"
                       "</html>",
                      web_state());
  ASSERT_TRUE(SetUpUniqueIDs());

  uint32_t const newPasswordIdentifier = 2;
  uint32_t const confirmPasswordIdentifier = 3;
  EXPECT_NSEQ(
      @YES,
      ExecuteJavaScript([NSString
          stringWithFormat:
              @"__gCrWeb.passwords."
              @"fillPasswordFormWithGeneratedPassword(0, %u, %u, '%@')",
              newPasswordIdentifier, confirmPasswordIdentifier, kPassword]));
  EXPECT_NSEQ(
      @YES,
      ExecuteJavaScript([NSString
          stringWithFormat:@"document.getElementById('ps1').value == '%@'",
                           kPassword]));
  EXPECT_NSEQ(
      @YES,
      ExecuteJavaScript([NSString
          stringWithFormat:@"document.getElementById('ps2').value == '%@'",
                           kPassword]));
  EXPECT_NSEQ(
      @NO,
      ExecuteJavaScript([NSString
          stringWithFormat:@"document.getElementById('user').value == '%@'",
                           kPassword]));
}

// Check that a form with only a password field (i.e. w/o username) is filled.
TEST_F(PasswordControllerJsTest, FillPasswordField_Alone) {
  web::test::LoadHtml(
      @"<html><body>"
       "<form name='login_form' action='action1'>"
       "  Password: <input type='password' name='password' id='password'>"
       "</form>"
       "</body></html>",
      web_state());
  ASSERT_TRUE(SetUpUniqueIDs());

  // Expect the attempt to succeeds where only the password is filled.
  NSString* form_fill_data = SerializeDictValueToNSString(
      FormFillData(/*username_renderer_id=*/0, /*password_renderer_id=*/2));
  auto result = ParseFormFillResult(ExecuteJavaScript([NSString
      stringWithFormat:@"__gCrWeb.passwords.fillPasswordForm(%@, '', '%@')",
                       form_fill_data, kPassword]));
  // Expect success without filling the username field.
  EXPECT_EQ(FillResultForSuccess(/*did_fill_username=*/false,
                                 /*did_fill_password=*/true),
            *result);

  // Verifies that the sign-in form has been filled with `kPassword`.
  EXPECT_NSEQ(kPassword,
              ExecuteJavaScript(@"document.getElementById('password').value"));
}

// Check that no fill attempt is made if the password input field is disabled.
TEST_F(PasswordControllerJsTest, FillPasswordField_InputDisabled) {
  web::test::LoadHtml(@"<html><body>"
                       "<form name='login_form' action='action1'>"
                       "  Password: <input type='password' name='password' "
                       "id='password' disabled>"
                       "</form>"
                       "</body></html>",
                      web_state());
  ASSERT_TRUE(SetUpUniqueIDs());

  NSString* form_fill_data = SerializeDictValueToNSString(
      FormFillData(/*username_renderer_id=*/0, /*password_renderer_id=*/2));
  auto result = ParseFormFillResult(ExecuteJavaScript([NSString
      stringWithFormat:@"__gCrWeb.passwords.fillPasswordForm(%@, '', '%@')",
                       form_fill_data, kPassword]));
  // Expect fill to fail.
  EXPECT_EQ(FillResultForFailure(), *result);

  // Verifies that the password field isn't filled.
  EXPECT_NSEQ(@"",
              ExecuteJavaScript(@"document.getElementById('password').value"));
}

// Check that no fill attempt is made if the password input field isn't of
// password type.
TEST_F(PasswordControllerJsTest, FillPasswordField_NotPasswordInput) {
  web::test::LoadHtml(
      @"<html><body>"
       "<form name='login_form' action='action1'>"
       "  Password: <input type='text' name='password' id='password'>"
       "</form>"
       "</body></html>",
      web_state());
  ASSERT_TRUE(SetUpUniqueIDs());

  NSString* form_fill_data = SerializeDictValueToNSString(
      FormFillData(/*username_renderer_id=*/0, /*password_renderer_id=*/2));
  auto result = ParseFormFillResult(ExecuteJavaScript([NSString
      stringWithFormat:@"__gCrWeb.passwords.fillPasswordForm(%@, '', '%@')",
                       form_fill_data, kPassword]));
  // Expect fill to fail.
  EXPECT_EQ(FillResultForFailure(), *result);

  // Verifies that the password field isn't filled.
  EXPECT_NSEQ(@"",
              ExecuteJavaScript(@"document.getElementById('password').value"));
}

// Check that no fill attempt is made if the password field ID doesn't match.
TEST_F(PasswordControllerJsTest, FillPasswordField_NoMatchForID) {
  web::test::LoadHtml(@"<html><body>"
                       "<form name='login_form' action='action1'>"
                       "  Password: "
                       "<input type='password' name='password' id='password'>"
                       "</form>"
                       "</body></html>",
                      web_state());
  ASSERT_TRUE(SetUpUniqueIDs());

  NSString* form_fill_data = SerializeDictValueToNSString(
      FormFillData(/*username_renderer_id=*/0, /*password_renderer_id=*/3));
  auto result = ParseFormFillResult(ExecuteJavaScript([NSString
      stringWithFormat:@"__gCrWeb.passwords.fillPasswordForm(%@, '', '%@')",
                       form_fill_data, kPassword]));
  // Expect fill to fail.
  EXPECT_EQ(FillResultForFailure(), *result);

  // Verifies that the password field isn't filled.
  EXPECT_NSEQ(@"",
              ExecuteJavaScript(@"document.getElementById('password').value"));
}

// Check that filling aborts if the username input field isn't of text type.
TEST_F(PasswordControllerJsTest, FillUsernameField_NonText) {
  web::test::LoadHtml(
      @"<html><body>"
       "<form name='login_form' action='action1'>"
       "  Username: <input type='radio' name='username' id='username'>"
       "  Password: <input type='password' name='password' id='password'>"
       "</form>"
       "</body></html>",
      web_state());
  ASSERT_TRUE(SetUpUniqueIDs());

  NSString* form_fill_data = SerializeDictValueToNSString(
      FormFillData(/*username_renderer_id=*/2, /*password_renderer_id=*/0));
  auto result = ParseFormFillResult(ExecuteJavaScript([NSString
      stringWithFormat:@"__gCrWeb.passwords.fillPasswordForm(%@, '%@', '')",
                       form_fill_data, kUsername]));
  EXPECT_EQ(FillResultForFailure(), *result);

  // Verifies that the input value is still the default value for the type.
  EXPECT_NSEQ(@"on",
              ExecuteJavaScript(@"document.getElementById('username').value"));
}

// Check that the username field isn't filled if disabled in a single username
// form.
TEST_F(PasswordControllerJsTest,
       SingleUsername_FillUsernameField_InputDisabled) {
  web::test::LoadHtml(
      @"<html><body>"
       "<form name='login_form' action='action1'>"
       "  Username: <input type='text' name='username' id='username' disabled>"
       "</form>"
       "</body></html>",
      web_state());

  ASSERT_TRUE(SetUpUniqueIDs());

  NSString* form_fill_data = SerializeDictValueToNSString(
      FormFillData(/*username_renderer_id=*/2, /*password_renderer_id=*/0));
  auto result = ParseFormFillResult(ExecuteJavaScript([NSString
      stringWithFormat:@"__gCrWeb.passwords.fillPasswordForm(%@, '%@', '')",
                       form_fill_data, kUsername]));
  // Expect fill to succeeds despite no fields being filled.
  EXPECT_EQ(FillResultForSuccess(/*did_fill_username=*/false,
                                 /*did_fill_password=*/false),
            *result);

  // Verifies that the username field was filled.
  EXPECT_NSEQ(@"",
              ExecuteJavaScript(@"document.getElementById('username').value"));
}

// Check that the username field isn't filled if readonly in a single username
// form.
TEST_F(PasswordControllerJsTest, SingleUsername_FillUsernameField_ReadOnly) {
  web::test::LoadHtml(
      @"<html><body>"
       "<form name='login_form' action='action1'>"
       "  Username: <input type='text' name='username' id='username' readonly>"
       "</form>"
       "</body></html>",
      web_state());

  ASSERT_TRUE(SetUpUniqueIDs());

  NSString* form_fill_data = SerializeDictValueToNSString(
      FormFillData(/*username_renderer_id=*/2, /*password_renderer_id=*/0));
  auto result = ParseFormFillResult(ExecuteJavaScript([NSString
      stringWithFormat:@"__gCrWeb.passwords.fillPasswordForm(%@, '%@', '')",
                       form_fill_data, kUsername]));
  // Expect fill to succeeds despite no fields being filled.
  EXPECT_EQ(FillResultForSuccess(/*did_fill_username=*/false,
                                 /*did_fill_password=*/false),
            *result);

  // Verifies that the username field was filled.
  EXPECT_NSEQ(@"",
              ExecuteJavaScript(@"document.getElementById('username').value"));
}

// Check that the username can be filled in single username form.
TEST_F(PasswordControllerJsTest, SingleUsername_FillUsernameField) {
  web::test::LoadHtml(
      @"<html><body>"
       "<form name='login_form' action='action1'>"
       "  Username: <input type='text' name='username' id='username'>"
       "</form>"
       "</body></html>",
      web_state());

  ASSERT_TRUE(SetUpUniqueIDs());

  NSString* form_fill_data = SerializeDictValueToNSString(
      FormFillData(/*username_renderer_id=*/2, /*password_renderer_id=*/0));
  auto result = ParseFormFillResult(ExecuteJavaScript([NSString
      stringWithFormat:@"__gCrWeb.passwords.fillPasswordForm(%@, '%@', '')",
                       form_fill_data, kUsername]));
  EXPECT_EQ(FillResultForSuccess(/*did_fill_username=*/true,
                                 /*did_fill_password=*/false),
            *result);

  // Verifies that the username field was filled.
  EXPECT_NSEQ(kUsername,
              ExecuteJavaScript(@"document.getElementById('username').value"));
}

// Check that filling is skipped alltogether when the password input to fill is
// missing, and this, even if the username input can be filled. Partial filling
// isn't an option in that case.
TEST_F(PasswordControllerJsTest, FillUsernameAndPassword_MissingPasswordInput) {
  web::test::LoadHtml(
      @"<html><body>"
       "<form name='login_form' action='action1'>"
       "  Username: <input type='text' name='username' id='username'>"
       "</form>"
       "</body></html>",
      web_state());

  ASSERT_TRUE(SetUpUniqueIDs());

  NSString* form_fill_data = SerializeDictValueToNSString(
      FormFillData(/*username_renderer_id=*/2, /*password_renderer_id=*/3));
  auto result = ParseFormFillResult(ExecuteJavaScript([NSString
      stringWithFormat:@"__gCrWeb.passwords.fillPasswordForm(%@, '%@', '%@')",
                       form_fill_data, kUsername, kPassword]));
  EXPECT_EQ(FillResultForFailure(), *result);

  // Verifies that the username fields wasn't filled.
  EXPECT_NSEQ(@"",
              ExecuteJavaScript(@"document.getElementById('username').value"));
}

// Check that filling is skipped alltogether when the username input to fill is
// missing, and this, even if the password input can be filled. Partial filling
// isn't an option in that case.
TEST_F(PasswordControllerJsTest, FillUsernameAndPassword_MissingUsernameInput) {
  web::test::LoadHtml(
      @"<html><body>"
       "<form name='login_form' action='action1'>"
       "  Password: <input type='password' name='password' id='password'>"
       "</form>"
       "</body></html>",
      web_state());

  ASSERT_TRUE(SetUpUniqueIDs());

  NSString* form_fill_data = SerializeDictValueToNSString(
      FormFillData(/*username_renderer_id=*/3, /*password_renderer_id=*/2));
  auto result = ParseFormFillResult(ExecuteJavaScript([NSString
      stringWithFormat:@"__gCrWeb.passwords.fillPasswordForm(%@, '%@', '%@')",
                       form_fill_data, kUsername, kPassword]));
  EXPECT_EQ(FillResultForFailure(), *result);

  // Verifies that the password field wasn't filled.
  EXPECT_NSEQ(@"",
              ExecuteJavaScript(@"document.getElementById('password').value"));
}

// Check that password form outside the <form> tag is extracted correctly.
TEST_F(PasswordControllerJsTest, ExtractFormOutsideTheFormTag) {
  web::test::LoadHtml(@"<html><body>"
                       "  Name: <input type='text' name='username'>"
                       "  Password: <input type='password' name='password'>"
                       "  <input type='submit' value='Submit'>"
                       "</body></html>",
                      web_state());
  ASSERT_TRUE(SetUpUniqueIDs());

  auto expected_form = base::Value::Dict()
                           .Set("name", "")
                           .Set("origin", BaseUrl())
                           .Set("action", "");
  base::Value::Dict expected_username_field =
      ParsedField(/*renderer_id=*/"1", /*contole_type=*/"text",
                  /*identifier=*/"gChrome~field~~INPUT~0", /*value=*/"",
                  /*label=*/"Name:", /*name=*/"username");
  base::Value::Dict expected_password_field = ParsedField(
      /*renderer_id=*/"2", /*contole_type=*/"password",
      /*identifier=*/"gChrome~field~~INPUT~1", /*value=*/"",
      /*label=*/"Password:", /*name=*/"password");
  auto expected_fields = base::Value::List()
                             .Append(std::move(expected_username_field))
                             .Append(std::move(expected_password_field));
  expected_form.Set("fields", std::move(expected_fields));

  std::unique_ptr<base::Value> results = autofill::ParseJson(
      ExecuteJavaScript(@"__gCrWeb.passwords.getPasswordFormDataAsString(0)"));
  ASSERT_TRUE(results);
  // Verify that the returned `results` correspond to a dictionary with
  // key/value pairs.
  ASSERT_TRUE(results->is_dict());

  base::Value::Dict& results_content = results->GetDict();

  // Verify that there is the "frame_id" key in the returned `results`.
  const std::string* results_frame_id = results_content.FindString("frame_id");
  ASSERT_TRUE(results_frame_id);
  ASSERT_THAT(autofill::DeserializeJavaScriptFrameId(*results_frame_id),
              IsTrue());

  // Remove the key as it was already verified to make the expected results and
  // the actual results comparable, since the frame_id is randomly generated.
  results_content.Remove("frame_id");

  EXPECT_EQ(expected_form, *results);
}

}  // namespace