// Copyright 2018 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/shared_password_controller.h"

#import <stddef.h>

#import <algorithm>
#import <map>
#import <memory>
#import <string>
#import <utility>
#import <vector>

#import "base/apple/foundation_util.h"
#import "base/check_deref.h"
#import "base/check_op.h"
#import "base/containers/to_vector.h"
#import "base/debug/crash_logging.h"
#import "base/feature_list.h"
#import "base/functional/bind.h"
#import "base/memory/raw_ptr.h"
#import "base/metrics/histogram_functions.h"
#import "base/metrics/histogram_macros.h"
#import "base/not_fatal_until.h"
#import "base/scoped_multi_source_observation.h"
#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "base/types/expected_macros.h"
#import "base/values.h"
#import "components/autofill/core/browser/autofill_server_prediction.h"
#import "components/autofill/core/browser/filling/filling_product.h"
#import "components/autofill/core/browser/form_structure.h"
#import "components/autofill/core/browser/suggestions/suggestion_type.h"
#import "components/autofill/core/common/autofill_features.h"
#import "components/autofill/core/common/field_data_manager.h"
#import "components/autofill/core/common/form_data.h"
#import "components/autofill/core/common/password_form_fill_data.h"
#import "components/autofill/core/common/password_form_generation_data.h"
#import "components/autofill/core/common/password_generation_util.h"
#import "components/autofill/core/common/signatures.h"
#import "components/autofill/core/common/unique_ids.h"
#import "components/autofill/ios/browser/autofill_client_ios.h"
#import "components/autofill/ios/browser/autofill_driver_ios.h"
#import "components/autofill/ios/browser/autofill_driver_ios_factory.h"
#import "components/autofill/ios/browser/autofill_manager_observer_bridge.h"
#import "components/autofill/ios/browser/autofill_util.h"
#import "components/autofill/ios/browser/form_suggestion_provider_query.h"
#import "components/autofill/ios/browser/password_autofill_agent.h"
#import "components/autofill/ios/common/field_data_manager_factory_ios.h"
#import "components/autofill/ios/form_util/form_activity_observer_bridge.h"
#import "components/autofill/ios/form_util/form_activity_params.h"
#import "components/password_manager/core/browser/passkey_credential.h"
#import "components/password_manager/core/browser/password_bubble_experiment.h"
#import "components/password_manager/core/browser/password_feature_manager.h"
#import "components/password_manager/core/browser/password_generation_frame_helper.h"
#import "components/password_manager/core/browser/password_manager_client.h"
#import "components/password_manager/core/browser/password_manager_metrics_util.h"
#import "components/password_manager/core/common/password_manager_features.h"
#import "components/password_manager/ios/account_select_fill_data.h"
#import "components/password_manager/ios/constants.h"
#import "components/password_manager/ios/features.h"
#import "components/password_manager/ios/ios_password_manager_driver_factory.h"
#import "components/password_manager/ios/password_manager_ios_util.h"
#import "components/password_manager/ios/password_manager_java_script_feature.h"
#import "components/password_manager/ios/shared_password_controller+private.h"
#import "components/strings/grit/components_strings.h"
#import "components/webauthn/ios/ios_webauthn_credentials_delegate_factory.h"
#import "components/webauthn/ios/passkey_suggestion_utils.h"
#import "ios/web/common/url_scheme_util.h"
#import "ios/web/public/js_messaging/web_frame.h"
#import "ios/web/public/js_messaging/web_frames_manager.h"
#import "ios/web/public/js_messaging/web_frames_manager_observer_bridge.h"
#import "ios/web/public/navigation/navigation_context.h"
#import "ios/web/public/web_state.h"
#import "services/network/public/cpp/shared_url_loader_factory.h"
#import "ui/base/l10n/l10n_util_mac.h"
#import "url/gurl.h"

using autofill::AutofillManager;
using autofill::AutofillManagerObserverBridge;
using autofill::FieldDataManager;
using autofill::FieldGlobalId;
using autofill::FieldRendererId;
using autofill::FormActivityObserverBridge;
using autofill::FormData;
using autofill::FormGlobalId;
using autofill::FormRendererId;
using autofill::LocalFrameToken;
using autofill::PasswordFormGenerationData;
using autofill::password_generation::LogPasswordGenerationEvent;
using autofill::password_generation::PasswordGenerationType;
using base::SysNSStringToUTF16;
using base::SysNSStringToUTF8;
using base::SysUTF16ToNSString;
using base::SysUTF8ToNSString;
using l10n_util::GetNSString;
using l10n_util::GetNSStringF;
using password_manager::AccountSelectFillData;
using password_manager::FillData;
using password_manager::PasswordFormManagerForUI;
using password_manager::PasswordGenerationFrameHelper;
using password_manager::PasswordManagerClient;
using password_manager::PasswordManagerDriver;
using password_manager::PasswordManagerInterface;
using password_manager::WebAuthnCredentialsDelegate;
using password_manager::metrics_util::LogPasswordDropdownShown;
using password_manager::metrics_util::PasswordDropdownState;
using FieldType = autofill::FormActivityParams::FieldType;

namespace {

// Password is considered not generated when user edits it below 4 characters.
constexpr int kMinimumLengthForEditedPassword = 4;

// Histogram for recording the status of retrieving FillData.
constexpr char kFillDataRetrievalStatusHistogram[] =
    "PasswordManager.iOS.FillDataRetrievalStatus";

class PasswordAutofillAgentDelegateImpl
    : public autofill::PasswordAutofillAgentDelegate {
 public:
  ~PasswordAutofillAgentDelegateImpl() override = default;
  explicit PasswordAutofillAgentDelegateImpl(web::WebState* web_state)
      : web_state_(web_state) {}

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

  void DidFillField(web::WebFrame* frame,
                    std::optional<autofill::FormRendererId> form_id,
                    autofill::FieldRendererId field_id,
                    const std::u16string& field_value) override {
    auto* driver = IOSPasswordManagerDriverFactory::FromWebStateAndWebFrame(
        web_state_, frame);
    CHECK(driver);

    autofill::FieldDataManager* field_data_manager =
        autofill::FieldDataManagerFactoryIOS::FromWebFrame(frame);

    driver->GetPasswordManager()->UpdateStateOnUserInput(
        driver, *field_data_manager, form_id, field_id, field_value);
  }

 private:
  raw_ptr<web::WebState> web_state_;
};

AcceptedGeneratedPasswordSourceType DetermineGeneratedPasswordSource(
    bool proactive,
    bool manual) {
  // 'proactive' keeps track of whether the sheet was shown proactively and
  // 'manual' keeps track of whether the sheet was shown through manual
  // fallback (these are mutually exclusive events), so if both are false, it
  // means the sheet was shown through the "Suggest strong password" keyboard
  // accessory.
  return proactive ? AcceptedGeneratedPasswordSourceType::kProactiveBottomSheet
         : manual  ? AcceptedGeneratedPasswordSourceType::kManualFallback
                   : AcceptedGeneratedPasswordSourceType::kSuggestion;
}

// Returns a LocalFrameToken that uniquely identifies the `frame`. Returns an
// empty token if it can't be constructed (i.e. because the frame id isn't of
// the right length).
autofill::LocalFrameToken GetLocalFrameToken(web::WebFrame* frame) {
  CHECK(frame);
  return std::optional<autofill::LocalFrameToken>(
             autofill::DeserializeJavaScriptFrameId(frame->GetFrameId()))
      .value_or(autofill::LocalFrameToken());
}

}  // namespace

@interface SharedPasswordController ()

// Helper contains common password suggestion logic.
@property(nonatomic, readonly) PasswordSuggestionHelper* suggestionHelper;

// Tracks field when current password was generated.
@property(nonatomic) FieldGlobalId passwordGeneratedIdentifier;

- (BOOL)IsOffTheRecord;

// Tracks whether the potential generated password has been proactively shown
// or through the keyboard accessory
@property(nonatomic) BOOL proactivePasswordGeneration;

@end

@implementation SharedPasswordController {
  raw_ptr<PasswordManagerInterface, DanglingUntriaged> _passwordManager;

  // The WebState this instance is observing. Will be null after
  // -webStateDestroyed: has been called.
  raw_ptr<web::WebState> _webState;

  PasswordControllerDriverHelper* _driverHelper;

  // Bridge to observe WebState from Objective-C.
  std::unique_ptr<web::WebStateObserverBridge> _webStateObserverBridge;

  // Bridge to observe the web frames manager from Objective-C.
  std::unique_ptr<web::WebFramesManagerObserverBridge>
      _webFramesManagerObserverBridge;

  // Bridge to observe the AutofillManagers for this `_webState`.
  std::unique_ptr<AutofillManagerObserverBridge> _autofillManagerObserverBridge;

  std::unique_ptr<base::ScopedMultiSourceObservation<AutofillManager,
                                                     AutofillManager::Observer>>
      _autofillManagerObservation;

  // Bridge to observe form activity in |_webState|.
  std::unique_ptr<FormActivityObserverBridge> _formActivityObserverBridge;

  // Form data for password generation on this page.
  std::map<FormRendererId, PasswordFormGenerationData> _formGenerationData;

  // Identifier of the field that was last typed into.
  FieldRendererId _lastTypedfieldIdentifier;

  // The value that was last typed by the user.
  NSString* _lastTypedValue;

  // Identifier of the last focused form.
  FormRendererId _lastFocusedFormIdentifier;

  // Identifier of the last focused field.
  FieldRendererId _lastFocusedFieldIdentifier;

  // Last focused frame.
  raw_ptr<web::WebFrame, DanglingUntriaged> _lastFocusedFrame;

  // A refcounted object is stored here, because otherwise the driver can
  // be deleted with the frame, and the driver needs to be alive after the
  // frame deletion for submission detecting purposes.
  scoped_refptr<IOSPasswordManagerDriver> _lastSubmittedPasswordManagerDriver;

  // Delegate for the PasswordAutofillAgent that receives information from
  // Autofill.
  std::unique_ptr<PasswordAutofillAgentDelegateImpl> _agentDelegate;
}

- (instancetype)initWithWebState:(web::WebState*)webState
                         manager:(password_manager::PasswordManagerInterface*)
                                     passwordManager
                      formHelper:(PasswordFormHelper*)formHelper
                suggestionHelper:(PasswordSuggestionHelper*)suggestionHelper
                    driverHelper:(PasswordControllerDriverHelper*)driverHelper {
  self = [super init];
  if (self) {
    DCHECK(webState);
    IOSPasswordManagerDriverFactory::CreateForWebState(webState, self,
                                                       passwordManager);
    _agentDelegate =
        std::make_unique<PasswordAutofillAgentDelegateImpl>(webState);
    autofill::PasswordAutofillAgent::CreateForWebState(webState,
                                                       _agentDelegate.get());

    _webState = webState;
    _webStateObserverBridge =
        std::make_unique<web::WebStateObserverBridge>(self);
    _webState->AddObserver(_webStateObserverBridge.get());
    _webFramesManagerObserverBridge =
        std::make_unique<web::WebFramesManagerObserverBridge>(self);
    web::WebFramesManager* framesManager = [self webFramesManager];
    framesManager->AddObserver(_webFramesManagerObserverBridge.get());
    _autofillManagerObserverBridge =
        std::make_unique<AutofillManagerObserverBridge>(self);
    _autofillManagerObservation =
        std::make_unique<base::ScopedMultiSourceObservation<
            AutofillManager, AutofillManager::Observer>>(
            _autofillManagerObserverBridge.get());
    _formActivityObserverBridge =
        std::make_unique<FormActivityObserverBridge>(_webState, self);
    _formHelper = formHelper;
    _formHelper.delegate = self;
    _suggestionHelper = suggestionHelper;
    _suggestionHelper.delegate = self;
    _passwordManager = passwordManager;
    _driverHelper = driverHelper;
    _proactivePasswordGeneration = NO;
  }
  return self;
}

- (void)dealloc {
  if (_webState) {
    _webState->RemoveObserver(_webStateObserverBridge.get());
  }
}

- (password_manager::FillDataRetrievalResult)
    passwordFillDataForUsername:(NSString*)username
             isBackupCredential:(BOOL)isBackupCredential
                     forFrameId:(const std::string&)frameId {
  return [self.suggestionHelper passwordFillDataForUsername:username
                                         isBackupCredential:isBackupCredential
                                                 forFrameId:frameId];
}

- (BOOL)IsOffTheRecord {
  DCHECK(_delegate.passwordManagerClient);
  return _delegate.passwordManagerClient->IsOffTheRecord();
}

#pragma mark - PasswordGenerationProvider

- (void)triggerPasswordGeneration {
  LogPasswordGenerationEvent(
      autofill::password_generation::PASSWORD_GENERATION_CONTEXT_MENU_PRESSED);
  [self triggerPasswordGenerationForFormId:_lastFocusedFormIdentifier
                           fieldIdentifier:_lastFocusedFieldIdentifier
                                   inFrame:_lastFocusedFrame
                                 proactive:NO];
}

- (void)triggerPasswordGenerationForFormId:(FormRendererId)formIdentifier
                           fieldIdentifier:(FieldRendererId)fieldIdentifier
                                   inFrame:(web::WebFrame*)frame
                                 proactive:(BOOL)proactivePasswordGeneration {
  if (!fieldIdentifier) {
    return;
  }
  _proactivePasswordGeneration = proactivePasswordGeneration;
  if (proactivePasswordGeneration) {
    // Record the dropdown selection metrics for password generation here
    // because -didSelectSuggestion isn't called when triggering the proactive
    // password generation sheet.
    password_manager::metrics_util::LogPasswordSuggestionSelected(
        password_manager::metrics_util::PasswordDropdownSelectedOption::
            kGenerate,
        [self IsOffTheRecord]);
  }
  // This function is reached either by using manual fallback or proactive
  // generation. Therefore, if it is not proactive password generation, it is
  // manually triggered, hence how isManuallyTriggered is set in the following
  // call.
  [self generatePasswordForFormId:formIdentifier
                  fieldIdentifier:fieldIdentifier
                          inFrame:frame
              isManuallyTriggered:!_proactivePasswordGeneration];
}

#pragma mark - CRWWebStateObserver

- (void)webState:(web::WebState*)webState
    didFinishNavigation:(web::NavigationContext*)navigation {
  DCHECK_EQ(_webState, webState);

  if (!navigation->HasCommitted() || navigation->IsSameDocument()) {
    return;
  }

  if (!webState->GetLastCommittedURLIfTrusted()) {
    return;
  }

  // Clear per-page state.
  [self.suggestionHelper resetForNewPage];

  // This FieldDataManager info is for forms that were present on a page
  // before navigation, therefore not the current driver is needed, but the
  // last submitted one.
  if (_lastSubmittedPasswordManagerDriver) {
    _passwordManager->PropagateFieldDataManagerInfo(
        _lastSubmittedPasswordManagerDriver->field_data_manager(),
        _lastSubmittedPasswordManagerDriver.get());
  }
  // On non-iOS platforms navigations initiated by link click are excluded from
  // navigations which might be form submssions. On iOS there is no easy way to
  // check that the navigation is link initiated, so it is skipped. It should
  // not be so important since it is unlikely that the user clicks on a link
  // after filling password form w/o submitting it.
  _passwordManager->DidNavigateMainFrame(
      /*form_may_be_submitted=*/navigation->IsRendererInitiated());
  if (_lastSubmittedPasswordManagerDriver) {
    _lastSubmittedPasswordManagerDriver->field_data_manager().ClearData();
  }
}

- (void)webState:(web::WebState*)webState didLoadPageWithSuccess:(BOOL)success {
  DCHECK_EQ(_webState, webState);

  // Retrieve the identity of the page. In case the page might be malicous,
  // returns early.
  std::optional<GURL> pageURL = webState->GetLastCommittedURLIfTrusted();
  if (!pageURL) {
    return;
  }

  if (!web::UrlHasWebScheme(*pageURL)) {
    return;
  }

  if (!webState->ContentIsHTML()) {
    // If the current page is not HTML, it does not contain any HTML forms.
    web::WebFramesManager* framesManager = [self webFramesManager];
    web::WebFrame* mainFrame = framesManager->GetMainWebFrame();
    [self didFinishPasswordFormExtraction:std::vector<FormData>()
                    triggeredByFormChange:false
                                  inFrame:mainFrame];
  }
}

- (void)webFramesManager:(web::WebFramesManager*)webFramesManager
    frameBecameAvailable:(web::WebFrame*)webFrame {
  DCHECK(webFrame);
  auto* driver =
      autofill::AutofillDriverIOS::FromWebStateAndWebFrame(_webState, webFrame);
  if (driver) {
    _autofillManagerObservation->AddObservation(&driver->GetAutofillManager());
  }

  if (_webState->ContentIsHTML()) {
    [self findPasswordFormsAndSendToPasswordStoreForFormChange:false
                                                       inFrame:webFrame];
  }
}

// Track detaching iframes.
- (void)webFramesManager:(web::WebFramesManager*)webFramesManager
    frameBecameUnavailable:(const std::string&)frameId {
  // No need to try to detect submissions when the webState is being destroyed.
  if (_webState->IsBeingDestroyed()) {
    return;
  }

  [_suggestionHelper cleanupForFrameId:frameId];

  web::WebFramesManager* framesManager = [self webFramesManager];
  web::WebFrame* webFrame = framesManager->GetFrameWithId(frameId);
  if (!webFrame) {
    return;
  }

  // Avoid keeping a pointer to a destroyed frame.
  if (webFrame == _lastFocusedFrame) {
    _lastFocusedFrame = nullptr;
  }

  // Main frame becomes unavailable, submission detection will happen after the
  // the new main frame is loaded.
  if (webFrame->IsMainFrame()) {
    return;
  }

  // Casting is safe, as this code is run on iOS Chrome & WebView only.
  auto* driver = static_cast<IOSPasswordManagerDriver*>(
      [_driverHelper PasswordManagerDriver:webFrame]);

  _passwordManager->OnIframeDetach(frameId, driver,
                                   driver->field_data_manager());
}

- (void)webStateDestroyed:(web::WebState*)webState {
  DCHECK_EQ(_webState, webState);
  if (_webState) {
    _webState->RemoveObserver(_webStateObserverBridge.get());
    _webStateObserverBridge.reset();
    web::WebFramesManager* framesManager = [self webFramesManager];
    framesManager->RemoveObserver(_webFramesManagerObserverBridge.get());
    _webFramesManagerObserverBridge.reset();
    _formActivityObserverBridge.reset();
    _webState = nullptr;
  }
  _formGenerationData.clear();
  _isPasswordGenerated = NO;
  _lastTypedfieldIdentifier = FieldRendererId();
  _lastTypedValue = nil;
  _lastFocusedFormIdentifier = FormRendererId();
  _lastFocusedFieldIdentifier = FieldRendererId();
  _lastFocusedFrame = nullptr;
  _driverHelper = nil;
  _passwordManager = nullptr;
  _lastSubmittedPasswordManagerDriver = nullptr;
  _agentDelegate.reset();
}

#pragma mark - AutofillManagerObserver

- (void)onAutofillManagerStateChanged:(AutofillManager&)manager
                                 from:(AutofillManager::LifecycleState)oldState
                                   to:(AutofillManager::LifecycleState)
                                          newState {
  using enum autofill::AutofillManager::LifecycleState;
  switch (newState) {
    case kInactive:
    case kActive:
    case kPendingReset:
      break;
    case kPendingDeletion:
      _autofillManagerObservation->RemoveObservation(&manager);
      break;
  }
}

- (void)onFieldTypesDetermined:(AutofillManager&)manager
                       forForm:(FormGlobalId)formId
                    fromSource:
                        (AutofillManager::Observer::FieldTypeSource)source
          smallFormsWereParsed:(bool)small_forms_were_parsed {
  const autofill::FormStructure* form_structure =
      manager.FindCachedFormById(formId);
  if (!form_structure) {
    return;
  }
  FormData form_data = form_structure->ToFormData();

  // Split the browser form into renderer forms.
  // The cast is safe: every AutofillClient on iOS is an AutofillClientIOS.
  const autofill::AutofillDriverRouter& router =
      static_cast<autofill::AutofillClientIOS&>(manager.client())
          .GetAutofillDriverFactory()
          .router();
  std::vector<FormData> renderer_forms = router.GetRendererForms(form_data);

  // Process predictions for each renderer form.
  web::WebFramesManager* webFramesManager = [self webFramesManager];
  for (const FormData& renderer_form : renderer_forms) {
    web::WebFrame* child_frame =
        webFramesManager->GetFrameWithId(renderer_form.host_frame().ToString());
    if (!child_frame) {
      continue;
    }
    [self propagatePredictionsToPasswordManagerFrom:manager
                                        forFormData:renderer_form
                                       globalFormId:formId
                                            inFrame:*child_frame
                                         fromSource:source];
  }
}

#pragma mark - FormSuggestionProvider

- (void)checkIfSuggestionsAvailableForForm:
            (FormSuggestionProviderQuery*)formQuery
                            hasUserGesture:(BOOL)hasUserGesture
                                  webState:(web::WebState*)webState
                         completionHandler:
                             (SuggestionsAvailableCompletion)completion {
  SCOPED_CRASH_KEY_BOOL("Bug40072712", "spc_isPwdGen",
                        self.isPasswordGenerated);
  SCOPED_CRASH_KEY_NUMBER("Bug40072712", "spc_pwdGenId",
                          self.passwordGeneratedIdentifier.renderer_id.value());

  DCHECK_EQ(_webState, webState);
  if (!webState->GetLastCommittedURLIfTrusted()) {
    completion(NO);
    return;
  }

  password_manager::PasswordManagerJavaScriptFeature* feature =
      password_manager::PasswordManagerJavaScriptFeature::GetInstance();
  web::WebFrame* frame = feature->GetWebFramesManager(webState)->GetFrameWithId(
      SysNSStringToUTF8(formQuery.frameID));

  // Clicking on a password form field from a different form on the same page
  // triggers displaying the on-screen keyboard. When the keyboard is
  // displayed, FormInputAccessoryMediator uses the cached parameters from the
  // previous clicked field in the previous password form. Getting the frame
  // from this previous frame id will result in a null frame pointer, hence
  // the check below.
  if (!frame) {
    completion(NO);
    return;
  }

  BOOL isPasswordField = [self.suggestionHelper isPasswordFieldOnForm:formQuery
                                                             webFrame:frame];

  [self.suggestionHelper
      checkIfSuggestionsAvailableForForm:formQuery
                       completionHandler:^(BOOL suggestionsAvailable) {
                         // Always display "Show All..." for password fields.
                         completion(isPasswordField || suggestionsAvailable);
                       }];

  if (self.isPasswordGenerated &&
      (formQuery.type == ActivityType::kInput ||
       formQuery.type == ActivityType::kKeyUp) &&
      self.passwordGeneratedIdentifier ==
          FieldGlobalId{GetLocalFrameToken(frame), formQuery.fieldRendererID}) {
    // On other platforms, when the user clicks on generation field, we show
    // password in clear text. And the user has the possibility to edit it. On
    // iOS, it's harder to do (it's probably bad idea to change field type from
    // password to text). The decision was to give everything to the automatic
    // flow and avoid the manual flow, for a cleaner and simpler UI.
    if (formQuery.typedValue.length < kMinimumLengthForEditedPassword) {
      self.isPasswordGenerated = NO;
      LogPasswordGenerationEvent(
          autofill::password_generation::PASSWORD_DELETED);
      self.passwordGeneratedIdentifier = FieldGlobalId();
      _passwordManager->OnPasswordNoLongerGenerated();
    } else {
      // Inject updated value to possibly update confirmation field.
      [self injectGeneratedPasswordForFormId:formQuery.formRendererID
                                     inFrame:frame
                           generatedPassword:formQuery.typedValue
                           completionHandler:nil];
    }
  }

  if (formQuery.fieldRendererID != _lastTypedfieldIdentifier ||
      ![formQuery.typedValue isEqual:_lastTypedValue]) {
    // This method is called multiple times for the same user keystroke. Inform
    // only once the keystroke.
    _lastTypedfieldIdentifier = formQuery.fieldRendererID;
    _lastTypedValue = formQuery.typedValue;

    if (formQuery.type == ActivityType::kInput ||
        formQuery.type == ActivityType::kKeyUp) {
      [self.formHelper updateFieldDataOnUserInput:formQuery.fieldRendererID
                                          inFrame:frame
                                       inputValue:formQuery.typedValue];
      autofill::FieldDataManager* fieldDataManager =
          autofill::FieldDataManagerFactoryIOS::FromWebFrame(frame);
      _passwordManager->UpdateStateOnUserInput(
          [_driverHelper PasswordManagerDriver:frame], *fieldDataManager,
          formQuery.formRendererID, formQuery.fieldRendererID,
          SysNSStringToUTF16(formQuery.typedValue));
    }
  }
}

- (void)retrieveSuggestionsForForm:(FormSuggestionProviderQuery*)formQuery
                          webState:(web::WebState*)webState
                 completionHandler:(SuggestionsReadyCompletion)completion {
  DCHECK_EQ(_webState, webState);
  if (!webState->GetLastCommittedURLIfTrusted()) {
    completion({}, self);
    return;
  }

  const std::string frameId = SysNSStringToUTF8(formQuery.frameID);
  web::WebFramesManager* framesManager = [self webFramesManager];
  web::WebFrame* frame = framesManager->GetFrameWithId(frameId);

  if (frame == nullptr) {
    completion({}, self);
    return;
  }

  // Retrieve passkey and password suggestions.
  NSArray<FormSuggestion*>* passkeySuggestions =
      [self retrievePasskeySuggestionsForFrame:frame];
  NSArray<FormSuggestion*>* passwordSuggestions =
      [self retrievePasswordSuggestionsForFormQuery:formQuery frame:frame];

  NSMutableArray<FormSuggestion*>* suggestions = [[NSMutableArray alloc]
      initWithArray:webauthn::MergePasskeyAndPasswordSuggestions(
                        passkeySuggestions, passwordSuggestions)];

  // Add a password generation suggestion when appropriate.
  if (!formQuery.onlyPassword &&
      [self canGeneratePasswordForForm:formQuery.formRendererID
                       fieldIdentifier:formQuery.fieldRendererID
                             fieldType:formQuery.fieldType
                               inFrame:frame]) {
    NSString* suggestPassword = GetNSString(IDS_IOS_SUGGEST_PASSWORD);
    FormSuggestion* suggestion = [FormSuggestion
        suggestionWithValue:suggestPassword
         displayDescription:nil
                       icon:nil
                       type:autofill::SuggestionType::kGeneratePasswordEntry
                    payload:autofill::Suggestion::Payload()
             requiresReauth:NO];

    [suggestions addObject:suggestion];
  }

  std::vector<autofill::Suggestion> suggestions_vector;
  for (FormSuggestion* suggestion in suggestions) {
    // LogPasswordDropdownShown requires types only.
    suggestions_vector.emplace_back(suggestion.type);
  }
  LogPasswordDropdownShown(suggestions_vector);

  completion(suggestions, self);
}

- (void)didSelectSuggestion:(FormSuggestion*)suggestion
                    atIndex:(NSInteger)index
                       form:(NSString*)formName
             formRendererID:(FormRendererId)formRendererID
            fieldIdentifier:(NSString*)fieldIdentifier
            fieldRendererID:(FieldRendererId)fieldRendererID
                    frameID:(NSString*)frameID
          completionHandler:(SuggestionHandledCompletion)completion {
  const std::string frameId = SysNSStringToUTF8(frameID);
  web::WebFramesManager* framesManager = [self webFramesManager];
  web::WebFrame* frame = framesManager->GetFrameWithId(frameId);
  if (!frame) {
    completion();
    return;
  }

  switch (suggestion.type) {
    case autofill::SuggestionType::kAllSavedPasswordsEntry: {
      completion();
      password_manager::metrics_util::LogPasswordSuggestionSelected(
          password_manager::metrics_util::PasswordDropdownSelectedOption::
              kShowAll,
          [self IsOffTheRecord]);
      return;
    }
    case autofill::SuggestionType::kGeneratePasswordEntry: {
      // Don't call completion because current suggestion state should remain
      // whether user injects a generated password or cancels.
      _proactivePasswordGeneration = NO;
      [self generatePasswordForFormId:formRendererID
                      fieldIdentifier:fieldRendererID
                              inFrame:frame
                  isManuallyTriggered:NO];
      password_manager::metrics_util::LogPasswordSuggestionSelected(
          password_manager::metrics_util::PasswordDropdownSelectedOption::
              kGenerate,
          [self IsOffTheRecord]);
      return;
    }
    case autofill::SuggestionType::kWebauthnCredential: {
      WebAuthnCredentialsDelegate* webAuthnCredentialsDelegate =
          [self retrieveWebAuthnCredentialsDelegateForFrame:frame];
      CHECK(webAuthnCredentialsDelegate);

      webAuthnCredentialsDelegate->SelectPasskey(
          webauthn::GetPasskeySuggestionEncodedCredentialId(suggestion),
          base::BindOnce(completion));
      return;
    }
    default: {
      password_manager::metrics_util::LogPasswordSuggestionSelected(
          password_manager::metrics_util::PasswordDropdownSelectedOption::
              kPassword,
          [self IsOffTheRecord]);
      NSString* username = suggestion.value;
      bool stateless = base::FeatureList::IsEnabled(
          password_manager::features::kIOSStatelessFillDataFlow);
      bool isBackupCredential =
          suggestion.type == autofill::SuggestionType::kBackupPasswordEntry;

      ASSIGN_OR_RETURN(
          password_manager::FillDataRetrievalResult fill_data_result,
          (stateless && suggestion.params.has_value())
              ? [self.suggestionHelper
                    passwordFillDataForUsername:username
                             isBackupCredential:isBackupCredential
                        likelyRealPasswordField:
                            suggestion.metadata.likely_from_real_password_field
                                 formIdentifier:suggestion.params
                                                    ->form_renderer_id
                                fieldIdentifier:suggestion.params
                                                    ->field_renderer_id
                                        frameId:suggestion.params->frame_id]
              : [self.suggestionHelper
                    passwordFillDataForUsername:username
                             isBackupCredential:isBackupCredential
                                     forFrameId:frameId],
          [completion](auto e) {
            base::UmaHistogramEnumeration(kFillDataRetrievalStatusHistogram, e);
            completion();
            return;
          });

      base::UmaHistogramEnumeration(
          kFillDataRetrievalStatusHistogram,
          password_manager::FillDataRetrievalStatus::kSuccess);

      BOOL triggerSubmission =
          suggestion.metadata.should_trigger_submission &&
          suggestion.metadata.accepts_auto_submit &&
          password_manager::features::kAutoSubmissionTypeParam.Get() ==
              password_manager::features::AutoSubmissionType::kScriptSubmit;

      [self.formHelper fillPasswordFormWithFillData:*fill_data_result.value()
                                            inFrame:frame
                                   triggeredOnField:fieldRendererID
                                  triggerSubmission:triggerSubmission
                                  completionHandler:^(BOOL success) {
                                    completion();
                                  }];
      break;
    }
  }

  [_delegate sharedPasswordController:self didAcceptSuggestion:suggestion];
}

- (SuggestionProviderType)type {
  return SuggestionProviderTypePassword;
}

- (autofill::FillingProduct)mainFillingProduct {
  return autofill::FillingProduct::kPassword;
}

#pragma mark - PasswordManagerDriverDelegate

- (const GURL&)lastCommittedURL {
  return _webState ? _webState->GetLastCommittedURL() : GURL::EmptyGURL();
}

- (void)processPasswordFormFillData:
            (const autofill::PasswordFormFillData&)formData
                         forFrameId:(const std::string&)frameId
                        isMainFrame:(BOOL)isMainFrame
                  forSecurityOrigin:(const url::Origin&)origin {
  // Biometric auth is always enabled on iOS so wait_for_username is
  // specifically set to prevent filling without user confirmation.
  DCHECK(formData.wait_for_username);
  [self.suggestionHelper processWithPasswordFormFillData:formData
                                              forFrameId:frameId
                                             isMainFrame:isMainFrame
                                       forSecurityOrigin:origin];
}

- (void)onNoSavedCredentialsWithFrameId:(const std::string&)frameId {
  [self.suggestionHelper processWithNoSavedCredentialsWithFrameId:frameId];
  [self detachListenersForBottomSheet:frameId];
}

- (void)scrollAndCheckViewAreaVisible:(autofill::FieldRendererId)fieldId
                           forFrameId:(const std::string&)frameId
                    completionHandler:
                        (void (^)(BOOL visible))completionHandler {
  web::WebFrame* webFrame = [self webFramesManager]->GetFrameWithId(frameId);
  if (!webFrame) {
    completionHandler(NO);
    return;
  }

  auto boolCallback = base::BindOnce(
      [](void (^handler)(BOOL), BOOL visible) { handler(visible); },
      completionHandler);

  password_manager::PasswordManagerJavaScriptFeature::GetInstance()
      ->ScrollAndCheckViewAreaVisible(webFrame, fieldId,
                                      std::move(boolCallback));
}

- (void)fillField:(autofill::FieldRendererId)fieldId
            withValue:(const std::u16string&)value
           forFrameId:(const std::string&)frameId
    completionHandler:(void (^)(BOOL success))completionHandler {
  web::WebFrame* webFrame = [self webFramesManager]->GetFrameWithId(frameId);
  if (!webFrame) {
    completionHandler(NO);
    return;
  }

  auto boolCallback = base::BindOnce(
      [](void (^handler)(BOOL), BOOL success) { handler(success); },
      completionHandler);

  password_manager::PasswordManagerJavaScriptFeature::GetInstance()->FillField(
      webFrame, fieldId, value, std::move(boolCallback));
}

- (void)formEligibleForGenerationFound:(const PasswordFormGenerationData&)form {
  _formGenerationData[form.form_renderer_id] = form;
}

- (void)attachListenersForPasswordGenerationFields:
            (const PasswordFormGenerationData&)form
                                        forFrameId:(const std::string&)frameId {
  const std::vector<autofill::FieldRendererId> rendererIds = {
      form.new_password_renderer_id};
  [self.delegate attachListenersForPasswordGenerationBottomSheet:rendererIds
                                                      forFrameId:frameId];
}

#pragma mark - PasswordFormHelperDelegate

- (void)formHelper:(PasswordFormHelper*)formHelper
     didSubmitForm:(const FormData&)form
           inFrame:(web::WebFrame*)frame {
  DCHECK(frame);

  IOSPasswordManagerDriver* driver =
      [_driverHelper PasswordManagerDriver:frame];
  if (frame->IsMainFrame()) {
    _passwordManager->OnPasswordFormSubmitted(driver, form);
  } else {
    // Show a save prompt immediately because for iframes it is very hard to
    // figure out correctness of password forms submission.
    _passwordManager->OnSubframeFormSubmission(driver, form);
  }
}

#pragma mark - PasswordSuggestionHelperDelegate

- (void)suggestionHelperShouldTriggerFormExtraction:
            (PasswordSuggestionHelper*)suggestionHelper
                                            inFrame:(web::WebFrame*)frame {
  [self findPasswordFormsAndSendToPasswordStoreForFormChange:false
                                                     inFrame:frame];
}

- (void)attachListenersForBottomSheet:
            (const std::vector<autofill::FieldRendererId>&)rendererIds
                           forFrameId:(const std::string&)frameId {
  [self.delegate attachListenersForBottomSheet:rendererIds forFrameId:frameId];
}

- (void)detachListenersForBottomSheet:(const std::string&)frameId {
  [self.delegate detachListenersForBottomSheet:frameId];
}

#pragma mark - Private methods

- (void)didFinishPasswordFormExtraction:(const std::vector<FormData>&)forms
                  triggeredByFormChange:(BOOL)triggeredByFormChange
                                inFrame:(web::WebFrame*)frame {
  // Do nothing if |self| has been detached.
  if (!_passwordManager) {
    return;
  }
  IOSPasswordManagerDriver* driver =
      [_driverHelper PasswordManagerDriver:frame];
  if (!forms.empty()) {
    // Invoke the password manager callback to autofill password forms
    // on the loaded page.
    _passwordManager->OnPasswordFormsParsed(driver, forms);
  } else if (frame) {
    [self onNoSavedCredentialsWithFrameId:frame->GetFrameId()];
  }
  // Invoke the password manager callback to check if password was
  // accepted or rejected. If accepted, infobar is presented. If
  // rejected, the provisionally saved password is deleted. On Chrome
  // w/ a renderer, it is the renderer who calls OnPasswordFormsParsed()
  // and OnPasswordFormsRendered(). Bling has to improvised a bit on the
  // ordering of these two calls.
  // Only check for form submissions if forms are not being parsed due to
  // added elements to the form.
  if (!triggeredByFormChange) {
    _passwordManager->OnPasswordFormsRendered(driver, forms);
  }
}

- (void)findPasswordFormsAndSendToPasswordStoreForFormChange:
            (BOOL)triggeredByFormChange
                                                     inFrame:
                                                         (web::WebFrame*)frame {
  // Read all password forms from the page and send them to the password
  // manager.
  __weak SharedPasswordController* weakSelf = self;
  auto completionHandler = ^(const std::vector<FormData>& forms) {
    [weakSelf didFinishPasswordFormExtraction:forms
                        triggeredByFormChange:triggeredByFormChange
                                      inFrame:frame];
  };

  [self.formHelper findPasswordFormsInFrame:frame
                          completionHandler:completionHandler];
}

- (BOOL)canGeneratePasswordForForm:(FormRendererId)formIdentifier
                   fieldIdentifier:(FieldRendererId)fieldIdentifier
                         fieldType:(FieldType)fieldType
                           inFrame:(web::WebFrame*)frame {
  if (![_driverHelper PasswordGenerationHelper:frame]->IsGenerationEnabled(
          /*log_debug_data*/ true)) {
    return NO;
  }
  if (fieldType != FieldType::kObfuscated) {
    return NO;
  }
  const PasswordFormGenerationData* generationData =
      [self formForGenerationFromFormID:formIdentifier];
  if (!generationData) {
    return NO;
  }

  FieldRendererId newPasswordIdentifier =
      generationData->new_password_renderer_id;
  if (fieldIdentifier == newPasswordIdentifier) {
    return YES;
  }

  // Don't show password generation if the field is 'confirm password'.
  return NO;
}

- (const PasswordFormGenerationData*)formForGenerationFromFormID:
    (FormRendererId)formIdentifier {
  if (_formGenerationData.find(formIdentifier) != _formGenerationData.end()) {
    return &_formGenerationData[formIdentifier];
  }
  return nullptr;
}

- (void)formDataCompletionForForm:(const autofill::FormData&)form
               formSignatureFound:(BOOL)found
              isManuallyTriggered:(BOOL)isManuallyTriggered
                   formIdentifier:(FormRendererId)formIdentifier
                  fieldIdentifier:(FieldRendererId)fieldIdentifier
                            frame:(base::WeakPtr<web::WebFrame>)weakFrame {
  if (!_webState) {
    // Stop here if the '_webState' was deleted before handling the decision for
    // the generated password. Doing anything further is unsafe.
    return;
  }
  web::WebFrame* frame = weakFrame.get();
  if (!frame) {
    // The frame has been destroyed, probably due to a
    // navigation or closing the tab, ignore the event.
    return;
  }

  autofill::FormSignature formSignature =
      found ? CalculateFormSignature(form) : autofill::FormSignature(0);
  autofill::FieldSignature fieldSignature = autofill::FieldSignature(0);
  uint64_t maxLength = 0;

  if (found) {
    for (const autofill::FormFieldData& field : form.fields()) {
      if (field.renderer_id() == fieldIdentifier) {
        fieldSignature = CalculateFieldSignatureForField(field);
        maxLength = field.max_length();
        break;
      }
    }
  }

  std::u16string generatedPassword =
      [_driverHelper PasswordGenerationHelper:frame]->GeneratePassword(
          frame->GetSecurityOrigin().GetURL(),
          isManuallyTriggered ? PasswordGenerationType::kManual
                              : PasswordGenerationType::kAutomatic,
          formSignature, fieldSignature, maxLength);

  base::UmaHistogramBoolean(
      "PasswordGeneration.iOS.GeneratedPasswordIsEmpty.BeforeParsing",
      generatedPassword.empty());

  NSString* generatedPotentialPassword = SysUTF16ToNSString(generatedPassword);

  __weak SharedPasswordController* weakSelf = self;
  [self.delegate sharedPasswordController:self
           showGeneratedPotentialPassword:generatedPotentialPassword
                                proactive:self.proactivePasswordGeneration
                                    frame:weakFrame
                          decisionHandler:^(BOOL accept) {
                            [weakSelf
                                onGeneratedPasswordAccepted:
                                    generatedPotentialPassword
                                                 isAccepted:accept
                                        isManuallyTriggered:isManuallyTriggered
                                             formIdentifier:formIdentifier
                                                      frame:weakFrame];
                          }];
}

- (void)onGeneratedPasswordAccepted:(NSString*)generatedPassword
                         isAccepted:(BOOL)accepted
                isManuallyTriggered:(BOOL)isManuallyTriggered
                     formIdentifier:(FormRendererId)formIdentifier
                              frame:(base::WeakPtr<web::WebFrame>)weakFrame {
  if (!_webState) {
    // Stop here if the '_webState' was deleted before handling the decision for
    // the generated password. Doing anything further is unsafe.
    return;
  }
  web::WebFrame* frame = weakFrame.get();
  if (!frame) {
    // The frame has been destroyed, probably due to a
    // navigation or closing the tab, ignore the event.
    return;
  }

  [self logUserChoice:accepted
            proactive:self.proactivePasswordGeneration
               manual:isManuallyTriggered];
  bool generatedPasswordEmpty = generatedPassword.length == 0;
  if (accepted) {
    // Log "on accepted" metrics which doesn't necessarily mean that the
    // generated password can be injected.
    LogPasswordGenerationEvent(
        autofill::password_generation::PASSWORD_ACCEPTED);
    base::UmaHistogramBoolean(
        "PasswordGeneration.iOS.AcceptedGeneratedPasswordIsEmpty",
        generatedPasswordEmpty);
  }
  if (accepted && !generatedPasswordEmpty) {
    // Only inject the generated password if not empty, password manager will
    // trigger a crash otherwise.
    [self injectGeneratedPasswordForFormId:formIdentifier
                                   inFrame:frame
                         generatedPassword:generatedPassword
                         completionHandler:nil];
  } else {
    [self cancelPasswordGeneration];
  }
}

- (void)cancelPasswordGeneration {
  _passwordManager->OnPasswordNoLongerGenerated();
}

- (void)generatePasswordForFormId:(FormRendererId)formIdentifier
                  fieldIdentifier:(FieldRendererId)fieldIdentifier
                          inFrame:(web::WebFrame*)frame
              isManuallyTriggered:(BOOL)isManuallyTriggered {
  const autofill::PasswordFormGenerationData* generationData =
      [self formForGenerationFromFormID:formIdentifier];
  if (!isManuallyTriggered && !generationData) {
    return;
  }

  BOOL shouldUpdateGenerationData =
      !generationData ||
      generationData->new_password_renderer_id != fieldIdentifier;
  if (isManuallyTriggered && shouldUpdateGenerationData) {
    PasswordFormGenerationData newGenerationData = {
        .form_renderer_id = formIdentifier,
        .new_password_renderer_id = fieldIdentifier,
    };
    [self formEligibleForGenerationFound:newGenerationData];
  }

  __weak SharedPasswordController* weakSelf = self;
  base::WeakPtr<web::WebFrame> weakFrame = frame->AsWeakPtr();
  [self.formHelper
      extractPasswordFormData:formIdentifier
                      inFrame:frame
            completionHandler:^(BOOL found, const autofill::FormData& form) {
              [weakSelf formDataCompletionForForm:form
                               formSignatureFound:found
                              isManuallyTriggered:isManuallyTriggered
                                   formIdentifier:formIdentifier
                                  fieldIdentifier:fieldIdentifier
                                            frame:weakFrame];
            }];

  IOSPasswordManagerDriver* driver =
      [_driverHelper PasswordManagerDriver:frame];
  _passwordManager->SetGenerationElementAndTypeForForm(
      driver, formIdentifier, fieldIdentifier,
      isManuallyTriggered ? PasswordGenerationType::kManual
                          : PasswordGenerationType::kAutomatic);
}

- (void)injectGeneratedPasswordForFormId:(FormRendererId)formIdentifier
                                 inFrame:(web::WebFrame*)frame
                       generatedPassword:(NSString*)generatedPassword
                       completionHandler:(void (^)())completionHandler {
  const autofill::PasswordFormGenerationData* generationData =
      [self formForGenerationFromFormID:formIdentifier];
  CHECK_GT(generatedPassword.length, 0u);
  if (!generationData) {
    return;
  }
  FieldRendererId newPasswordUniqueId =
      generationData->new_password_renderer_id;
  FieldRendererId confirmPasswordUniqueId =
      generationData->confirmation_password_renderer_id;

  __weak SharedPasswordController* weakSelf = self;
  base::WeakPtr<web::WebFrame> weakFrame = frame->AsWeakPtr();
  auto generatedPasswordInjected = ^(BOOL success) {
    if (success) {
      [weakSelf onFilledPasswordForm:formIdentifier
               withGeneratedPassword:generatedPassword
                    passwordUniqueId:newPasswordUniqueId
                             inFrame:weakFrame];
    }
    if (completionHandler) {
      completionHandler();
    }
  };

  [self.formHelper fillPasswordForm:formIdentifier
                            inFrame:frame
              newPasswordIdentifier:newPasswordUniqueId
          confirmPasswordIdentifier:confirmPasswordUniqueId
                  generatedPassword:generatedPassword
                  completionHandler:generatedPasswordInjected];
}

- (void)onFilledPasswordForm:(FormRendererId)formIdentifier
       withGeneratedPassword:(NSString*)generatedPassword
            passwordUniqueId:(FieldRendererId)newPasswordUniqueId
                     inFrame:(base::WeakPtr<web::WebFrame>)weakFrame {
  __weak SharedPasswordController* weakSelf = self;
  auto passwordPresaved = ^(BOOL found, const autofill::FormData& form) {
    // If the form isn't found, it disappeared between the call to
    // [self.formHelper fillPasswordForm:newPasswordIdentifier:...]
    // and here. There isn't much that can be done.
    if (!found) {
      return;
    }

    [weakSelf didExtractGeneratedPasswordForm:form
                        withGeneratedPassword:generatedPassword
                                newPasswordID:newPasswordUniqueId
                                      inFrame:weakFrame];
  };

  if (!weakFrame) {
    return;
  }

  [self.formHelper extractPasswordFormData:formIdentifier
                                   inFrame:weakFrame.get()
                         completionHandler:passwordPresaved];
}
- (void)didExtractGeneratedPasswordForm:(const autofill::FormData&)form
                  withGeneratedPassword:(NSString*)password
                          newPasswordID:(FieldRendererId)newPasswordID
                                inFrame:
                                    (base::WeakPtr<web::WebFrame>)weakFrame {
  web::WebFrame* frame = weakFrame.get();
  if (!frame) {
    return;
  }
  FieldGlobalId newPasswordGlobalId = {GetLocalFrameToken(frame),
                                       newPasswordID};
  if (!form.FindFieldByGlobalId(newPasswordGlobalId)) {
    // The form that was filled with the generated password should contain the
    // new password field that was intended to fill. Otherwise, we are in an
    // undesired state hence abort presaving the generated password.
    return;
  }
  [self presaveGeneratedPassword:password formData:form inFrame:frame];
  self.isPasswordGenerated = YES;
  self.passwordGeneratedIdentifier = {GetLocalFrameToken(frame), newPasswordID};
}

- (void)presaveGeneratedPassword:(NSString*)generatedPassword
                        formData:(const autofill::FormData&)formData
                         inFrame:(web::WebFrame*)frame {
  if (!_passwordManager) {
    return;
  }

  _passwordManager->OnPresaveGeneratedPassword(
      [_driverHelper PasswordManagerDriver:frame], formData,
      SysNSStringToUTF16(generatedPassword));
}

- (web::WebFramesManager*)webFramesManager {
  return password_manager::PasswordManagerJavaScriptFeature::GetInstance()
      ->GetWebFramesManager(_webState);
}

- (void)propagatePredictionsToPasswordManagerFrom:(AutofillManager&)manager
                                      forFormData:(FormData)form
                                     globalFormId:(FormGlobalId)globalFormId
                                          inFrame:(web::WebFrame&)frame
                                       fromSource:(AutofillManager::Observer::
                                                       FieldTypeSource)source {
  PasswordManagerDriver* driver =
      IOSPasswordManagerDriverFactory::FromWebStateAndWebFrame(_webState,
                                                               &frame);
  std::vector<autofill::FieldGlobalId> field_ids =
      base::ToVector(form.fields(), &autofill::FormFieldData::global_id);
  switch (source) {
    case AutofillManager::Observer::FieldTypeSource::kAutofillServer:
    case AutofillManager::Observer::FieldTypeSource::kAutofillAiModel:
      _passwordManager->ProcessAutofillPredictions(
          CHECK_DEREF(driver), form,
          manager.GetServerPredictionsForForm(globalFormId, field_ids));
      break;
    case AutofillManager::Observer::FieldTypeSource::kHeuristicsOrAutocomplete:
      if (base::FeatureList::IsEnabled(
              password_manager::features::
                  kApplyClientsideModelPredictionsForPasswordTypes)) {
        _passwordManager->ProcessClassificationModelPredictions(
            driver, form,
            manager.GetHeuristicPredictionForForm(
                autofill::HeuristicSource::kPasswordManagerMachineLearning,
                globalFormId, field_ids));
      }
      break;
  }
}

// Retrieves the WebAuthnCredentialsDelegate from the PasswordManagerClient
// for the given `frame`.
- (WebAuthnCredentialsDelegate*)retrieveWebAuthnCredentialsDelegateForFrame:
    (web::WebFrame*)frame {
  return webauthn::IOSWebAuthnCredentialsDelegateFactory::GetFactory(_webState)
      ->GetDelegateForFrameId(frame->GetFrameId());
}

// Retrieves passkey suggestions for the provided `frame`.
- (NSArray<FormSuggestion*>*)retrievePasskeySuggestionsForFrame:
    (web::WebFrame*)frame {
  WebAuthnCredentialsDelegate* webAuthnCredentialsDelegate =
      [self retrieveWebAuthnCredentialsDelegateForFrame:frame];
  if (!webAuthnCredentialsDelegate) {
    // No WebAuthnCredentialsDelegate means that passkeys are not supported.
    return @[];
  }

  base::expected<const std::vector<password_manager::PasskeyCredential>*,
                 WebAuthnCredentialsDelegate::PasskeysUnavailableReason>
      passkeys = webAuthnCredentialsDelegate->GetPasskeys();
  if (!passkeys.has_value()) {
    // An unset `passkeys` vector means that no passkey request has been
    // received.
    return @[];
  }

  return webauthn::FormSuggestionsFromPasskeyCredentials(**passkeys);
}

// Retrieves password suggestions for the provided `formQuery` and `frame`.
- (NSArray<FormSuggestion*>*)
    retrievePasswordSuggestionsForFormQuery:
        (FormSuggestionProviderQuery*)formQuery
                                      frame:(web::WebFrame*)frame {
  NSArray<FormSuggestion*>* rawSuggestions =
      [self.suggestionHelper retrieveSuggestionsWithForm:formQuery];

  NSMutableArray<FormSuggestion*>* suggestions = [NSMutableArray array];
  bool isPasswordField = [self.suggestionHelper isPasswordFieldOnForm:formQuery
                                                             webFrame:frame];
  for (FormSuggestion* rawSuggestion in rawSuggestions) {
    // 1) If this is a focus event or the field is empty show all suggestions.
    // Otherwise:
    // 2) If this is a username field then show only credentials with matching
    // prefixes.
    // 3) If this is a password field then show suggestions only if
    // the field is empty.
    if (![formQuery hasFocusType] && formQuery.typedValue.length > 0 &&
        (isPasswordField ||
         ![rawSuggestion.value hasPrefix:formQuery.typedValue])) {
      continue;
    }

    FormSuggestion* suggestion =
        [FormSuggestion suggestionWithValue:rawSuggestion.value
                         displayDescription:rawSuggestion.displayDescription
                                       icon:nil
                                       type:rawSuggestion.type
                                    payload:autofill::Suggestion::Payload()
                             requiresReauth:YES
                 acceptanceA11yAnnouncement:nil
                                   metadata:rawSuggestion.metadata];
    [suggestions addObject:suggestion];
  }
  return suggestions;
}

#pragma mark - FormActivityObserver

- (void)webState:(web::WebState*)webState
    didRegisterFormActivity:(const autofill::FormActivityParams&)params
                    inFrame:(web::WebFrame*)frame {
  DCHECK_EQ(_webState, webState);

  std::optional<GURL> pageURL = webState->GetLastCommittedURLIfTrusted();
  if (!pageURL || !frame || params.input_missing) {
    _lastFocusedFormIdentifier = FormRendererId();
    _lastFocusedFieldIdentifier = FieldRendererId();
    _lastFocusedFrame = nullptr;
    return;
  }

  if (params.type == ActivityType::kInput ||
      params.type == ActivityType::kChange) {
    _lastSubmittedPasswordManagerDriver =
        IOSPasswordManagerDriverFactory::GetRetainableDriver(_webState, frame);
  }

  if (params.type == ActivityType::kFocus) {
    _lastFocusedFormIdentifier = params.form_renderer_id;
    _lastFocusedFieldIdentifier = params.field_renderer_id;
    _lastFocusedFrame = frame;
  }

  // If there's a change in password forms on a page, they should be parsed
  // again.
  if (params.type == ActivityType::kFormChanged) {
    [self findPasswordFormsAndSendToPasswordStoreForFormChange:true
                                                       inFrame:frame];
  }
}

// If the form was removed, PasswordManager should be informed to decide
// whether the form was submitted.
- (void)webState:(web::WebState*)webState
    didRegisterFormRemoval:(const autofill::FormRemovalParams&)params
                   inFrame:(web::WebFrame*)frame {
  CHECK_EQ(_webState, webState);
  CHECK(!params.removed_forms.empty() || !params.removed_unowned_fields.empty())
      << "Invalid params. Form removal events with missing input should have "
         "been filtered out by FormActivityTabHelper.";

  auto* driver = static_cast<IOSPasswordManagerDriver*>(
      [_driverHelper PasswordManagerDriver:frame]);

  _passwordManager->OnPasswordFormsRemoved(
      driver, driver->field_data_manager(),
      /*removed_forms=*/params.removed_forms,
      /*removed_unowned_fields=*/params.removed_unowned_fields);
}

- (void)logUserChoice:(BOOL)accept
            proactive:(BOOL)proactive
               manual:(BOOL)manual {
  if (accept) {
    if (proactive) {
      // Suggested generated password accepted on proactive bottom sheet.
      base::UmaHistogramEnumeration(
          "PasswordManager.TouchToFill.PasswordGeneration.UserChoice",
          password_manager::metrics_util::GenerationDialogChoice::kAccepted);
    }
    base::UmaHistogramEnumeration(
        "PasswordGeneration.iOS.AcceptedGeneratedPasswordSource",
        DetermineGeneratedPasswordSource(proactive, manual));
  } else if (proactive) {
    // Suggested generated password is not accepted from proactive bottom sheet.
    base::UmaHistogramEnumeration(
        "PasswordManager.TouchToFill."
        "PasswordGeneration.UserChoice",
        password_manager::metrics_util::GenerationDialogChoice::kRejected);
  }
}

@end
