chromium/components/password_manager/ios/password_manager_java_script_feature.mm

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

#include "base/no_destructor.h"
#include "base/strings/sys_string_conversions.h"
#include "base/values.h"
#include "components/autofill/core/common/password_form_fill_data.h"
#include "components/autofill/ios/browser/autofill_util.h"
#import "components/autofill/ios/common/javascript_feature_util.h"
#import "components/autofill/ios/form_util/form_util_java_script_feature.h"
#include "components/password_manager/ios/account_select_fill_data.h"
#include "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 int64_t kJavaScriptExecutionTimeoutInSeconds = 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::Value::Dict 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::Value::Dict root_dict;
  root_dict.Set("origin", origin.spec());
  root_dict.Set("renderer_id", FormRendererIdToJsParameter(form_renderer_id));

  base::Value::List fieldList;

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

  base::Value::Dict 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::Value::Dict 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)},
          {web::java_script_features::GetCommonJavaScriptFeature(),
           web::java_script_features::GetMessageJavaScriptFeature(),
           autofill::FormUtilJavaScriptFeature::GetInstance()}) {}

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)),
                         base::Seconds(kJavaScriptExecutionTimeoutInSeconds));
}

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

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) {
  DCHECK(!callback.is_null());

  base::Value::Dict form_value = SerializeFillData(fill_data, fill_username);
  CallJavaScriptFunction(frame, "passwords.fillPasswordForm",
                         base::Value::List()
                             .Append(std::move(form_value))
                             .Append(username)
                             .Append(password),
                         std::move(callback),
                         base::Seconds(kJavaScriptExecutionTimeoutInSeconds));
}

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::Value::List()
          .Append(FormRendererIdToJsParameter(form_identifier))
          .Append(FieldRendererIdToJsParameter(new_password_identifier))
          .Append(FieldRendererIdToJsParameter(confirm_password_identifier))
          .Append(base::SysNSStringToUTF8(generated_password)),
      CreateBoolCallback(std::move(callback)),
      base::Seconds(kJavaScriptExecutionTimeoutInSeconds));
}

}  // namespace password_manager