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

#import "components/password_manager/ios/password_manager_java_script_feature.h"

#import "base/check.h"
#import "base/feature_list.h"
#import "base/functional/callback_helpers.h"
#import "base/no_destructor.h"
#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "base/values.h"
#import "components/autofill/core/common/password_form_fill_data.h"
#import "components/autofill/ios/browser/autofill_util.h"
#import "components/autofill/ios/common/autofill_optimization_features.h"
#import "components/autofill/ios/common/javascript_feature_util.h"
#import "components/password_manager/ios/account_select_fill_data.h"
#import "components/password_manager/ios/password_manager_tab_helper.h"
#import "ios/web/public/js_messaging/java_script_feature_util.h"

using autofill::CreateBoolCallback;
using autofill::CreateStringCallback;

namespace password_manager {

namespace {
constexpr char kScriptName[] = "password_controller";
constexpr char FormSubmittedHandlerName[] = "PasswordFormSubmitButtonClick";

// The timeout for any JavaScript call in this file.
constexpr base::TimeDelta kJavaScriptExecutionTimeout = base::Seconds(5);

// Converts FormRendererId to int value that can be used in Javascript methods.
int FormRendererIdToJsParameter(autofill::FormRendererId form_id) {
  return form_id.value();
}

// Converts FieldRendererId to int value that can be used in Javascript methods.
int FieldRendererIdToJsParameter(autofill::FieldRendererId field_id) {
  return field_id.value();
}

base::DictValue SerializeFillData(const GURL& origin,
                                  autofill::FormRendererId form_renderer_id,
                                  autofill::FieldRendererId username_element,
                                  const std::u16string& username_value,
                                  autofill::FieldRendererId password_element,
                                  const std::u16string& password_value) {
  base::DictValue root_dict;
  root_dict.Set("origin", origin.spec());
  root_dict.Set("renderer_id", FormRendererIdToJsParameter(form_renderer_id));

  base::ListValue fieldList;

  base::DictValue usernameField;
  usernameField.Set("renderer_id",
                    FieldRendererIdToJsParameter(username_element));
  usernameField.Set("value", username_value);
  fieldList.Append(std::move(usernameField));

  base::DictValue passwordField;
  passwordField.Set("renderer_id", static_cast<int>(password_element.value()));
  passwordField.Set("value", password_value);
  fieldList.Append(std::move(passwordField));

  root_dict.Set("fields", std::move(fieldList));

  return root_dict;
}

// Serializes |fill_data| so it can be used by the JS side of
// PasswordController. Includes both username and password data if
// |fill_username|, and only password data otherwise.
base::DictValue SerializeFillData(const password_manager::FillData& fill_data,
                                  BOOL fill_username) {
  return SerializeFillData(fill_data.origin, fill_data.form_id,
                           fill_username ? fill_data.username_element_id
                                         : autofill::FieldRendererId(),
                           fill_data.username_value,
                           fill_data.password_element_id,
                           fill_data.password_value);
}

}  // namespace

// static
PasswordManagerJavaScriptFeature*
PasswordManagerJavaScriptFeature::GetInstance() {
  static base::NoDestructor<PasswordManagerJavaScriptFeature> instance;
  return instance.get();
}

PasswordManagerJavaScriptFeature::PasswordManagerJavaScriptFeature()
    : web::JavaScriptFeature(
          ContentWorldForAutofillJavascriptFeatures(),
          {FeatureScript::CreateWithFilename(
              kScriptName,
              FeatureScript::InjectionTime::kDocumentStart,
              FeatureScript::TargetFrames::kAllFrames,
              FeatureScript::ReinjectionBehavior::kInjectOncePerWindow,
              base::BindRepeating(
                  []() -> FeatureScript::PlaceholderReplacements {
                    return @{
                      @"window."
                      @"gCrWebPlaceholderAutofillOptimizationFormSearch" :
                              base::FeatureList::IsEnabled(
                                  autofill::features::
                                      kAutofillOptimizationFormSearchIos)
                          ? @"true"
                          : @"false",
                    };
                  }))}) {}

PasswordManagerJavaScriptFeature::~PasswordManagerJavaScriptFeature() = default;

void PasswordManagerJavaScriptFeature::FindPasswordFormsInFrame(
    web::WebFrame* frame,
    base::OnceCallback<void(NSString*)> callback) {
  DCHECK(!callback.is_null());
  CallJavaScriptFunction(frame, "passwords.findPasswordForms", {},
                         CreateStringCallback(std::move(callback)),
                         kJavaScriptExecutionTimeout);
}

void PasswordManagerJavaScriptFeature::ExtractForm(
    web::WebFrame* frame,
    autofill::FormRendererId form_identifier,
    base::OnceCallback<void(NSString*)> callback) {
  DCHECK(!callback.is_null());
  CallJavaScriptFunction(
      frame, "passwords.getPasswordFormDataAsString",
      base::ListValue().Append(FormRendererIdToJsParameter(form_identifier)),
      CreateStringCallback(std::move(callback)), kJavaScriptExecutionTimeout);
}

void PasswordManagerJavaScriptFeature::FillPasswordForm(
    web::WebFrame* frame,
    const password_manager::FillData& fill_data,
    BOOL fill_username,
    const std::string& username,
    const std::string& password,
    base::OnceCallback<void(const base::Value*)> callback,
    BOOL trigger_submission,
    bool fallback_to_keystroke) {
  DCHECK(!callback.is_null());

  base::DictValue form_value = SerializeFillData(fill_data, fill_username);
  CallJavaScriptFunction(frame,
                         trigger_submission
                             ? "passwords.fillPasswordFormAndSubmit"
                             : "passwords.fillPasswordForm",
                         base::ListValue()
                             .Append(std::move(form_value))
                             .Append(username)
                             .Append(password)
                             .Append(fallback_to_keystroke),
                         std::move(callback), kJavaScriptExecutionTimeout);
}

std::optional<std::string>
PasswordManagerJavaScriptFeature::GetScriptMessageHandlerName() const {
  return FormSubmittedHandlerName;
}

void PasswordManagerJavaScriptFeature::ScriptMessageReceived(
    web::WebState* web_state,
    const web::ScriptMessage& message) {
  PasswordManagerTabHelper::GetOrCreateForWebState(web_state)
      ->ScriptMessageReceived(message);
}

void PasswordManagerJavaScriptFeature::FillPasswordForm(
    web::WebFrame* frame,
    autofill::FormRendererId form_identifier,
    autofill::FieldRendererId new_password_identifier,
    autofill::FieldRendererId confirm_password_identifier,
    NSString* generated_password,
    base::OnceCallback<void(BOOL)> callback) {
  DCHECK(!callback.is_null());
  CallJavaScriptFunction(
      frame, "passwords.fillPasswordFormWithGeneratedPassword",
      base::ListValue()
          .Append(FormRendererIdToJsParameter(form_identifier))
          .Append(FieldRendererIdToJsParameter(new_password_identifier))
          .Append(FieldRendererIdToJsParameter(confirm_password_identifier))
          .Append(base::SysNSStringToUTF8(generated_password)),
      CreateBoolCallback(std::move(callback)), kJavaScriptExecutionTimeout);
}

void PasswordManagerJavaScriptFeature::SubmitForm(
    web::WebFrame* frame,
    autofill::FormRendererId form_identifier,
    autofill::FieldRendererId password_identifier,
    bool fallback_to_keystroke) {
  CallJavaScriptFunction(
      frame, "passwords.submitPasswordForm",
      base::ListValue()
          .Append(FormRendererIdToJsParameter(form_identifier))
          .Append(FieldRendererIdToJsParameter(password_identifier))
          .Append(fallback_to_keystroke));
}

void PasswordManagerJavaScriptFeature::PreventKeyboardOnElement(
    web::WebFrame* frame,
    autofill::FieldRendererId field_identifier,
    base::OnceCallback<void(BOOL)> callback) {
  CHECK(callback);
  CallJavaScriptFunction(
      frame, "passwords.preventKeyboardOnElement",
      base::ListValue().Append(FieldRendererIdToJsParameter(field_identifier)),
      autofill::CreateBoolCallback(std::move(callback)),
      kJavaScriptExecutionTimeout);
}

void PasswordManagerJavaScriptFeature::RestoreKeyboardOnElement(
    web::WebFrame* frame,
    autofill::FieldRendererId field_identifier,
    base::OnceCallback<void(BOOL)> callback) {
  CHECK(callback);
  CallJavaScriptFunction(
      frame, "passwords.restoreKeyboardOnElement",
      base::ListValue().Append(FieldRendererIdToJsParameter(field_identifier)),
      autofill::CreateBoolCallback(std::move(callback)),
      kJavaScriptExecutionTimeout);
}

void PasswordManagerJavaScriptFeature::FocusElement(
    web::WebFrame* frame,
    autofill::FieldRendererId field_identifier,
    base::OnceCallback<void(BOOL)> callback) {
  CHECK(callback);
  CallJavaScriptFunction(
      frame, "passwords.focusElement",
      base::ListValue().Append(FieldRendererIdToJsParameter(field_identifier)),
      autofill::CreateBoolCallback(std::move(callback)),
      kJavaScriptExecutionTimeout);
}

void PasswordManagerJavaScriptFeature::ScrollAndCheckViewAreaVisible(
    web::WebFrame* frame,
    autofill::FieldRendererId field_identifier,
    base::OnceCallback<void(bool)> callback) {
  CHECK(callback);
  CallJavaScriptFunction(
      frame, "passwords.scrollAndCheckViewAreaVisible",
      base::ListValue().Append(FieldRendererIdToJsParameter(field_identifier)),
      autofill::CreateBoolCallback(std::move(callback)),
      kJavaScriptExecutionTimeout);
}

void PasswordManagerJavaScriptFeature::FillField(
    web::WebFrame* frame,
    autofill::FieldRendererId field_identifier,
    const std::u16string& value,
    base::OnceCallback<void(bool)> callback) {
  CHECK(callback);
  CallJavaScriptFunction(
      frame, "passwords.fillField",
      base::ListValue()
          .Append(FieldRendererIdToJsParameter(field_identifier))
          .Append(base::UTF16ToUTF8(value)),
      autofill::CreateBoolCallback(std::move(callback)),
      kJavaScriptExecutionTimeout);
}

void PasswordManagerJavaScriptFeature::SetUpRendererKeystrokeShield(
    web::WebFrame* frame,
    autofill::FieldRendererId field_identifier,
    base::OnceCallback<void(BOOL)> callback) {
  CHECK(callback);
  CallJavaScriptFunction(
      frame, "passwords.setUpRendererKeystrokeShield",
      base::ListValue().Append(FieldRendererIdToJsParameter(field_identifier)),
      autofill::CreateBoolCallback(std::move(callback)),
      kJavaScriptExecutionTimeout);
}

void PasswordManagerJavaScriptFeature::RemoveRendererKeystrokeShield(
    web::WebFrame* frame,
    base::OnceClosure callback) {
  CHECK(callback);
  CallJavaScriptFunction(
      frame, "passwords.removeRendererKeystrokeShield", base::ListValue(),
      base::IgnoreArgs<const base::Value*>(std::move(callback)),
      kJavaScriptExecutionTimeout);
}

}  // namespace password_manager
