chromium/components/password_manager/ios/shared_password_controller.mm

// 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/feature_list.h"
#import "base/functional/bind.h"
#import "base/memory/raw_ptr.h"
#import "base/metrics/histogram_macros.h"
#import "base/ranges/algorithm.h"
#import "base/scoped_multi_source_observation.h"
#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "base/values.h"
#import "components/autofill/core/browser/filling_product.h"
#import "components/autofill/core/browser/form_structure.h"
#import "components/autofill/core/browser/ui/suggestion_type.h"
#import "components/autofill/core/common/autofill_features.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_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/form_util/form_activity_observer_bridge.h"
#import "components/autofill/ios/form_util/form_activity_params.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/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 "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::FieldRendererId;
using autofill::FormActivityObserverBridge;
using autofill::FormData;
using autofill::FormGlobalId;
using autofill::FormRendererId;
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::JsonStringToFormData;
using password_manager::PasswordFormManagerForUI;
using password_manager::PasswordGenerationFrameHelper;
using password_manager::PasswordManagerClient;
using password_manager::PasswordManagerDriver;
using password_manager::PasswordManagerInterface;
using password_manager::metrics_util::LogPasswordDropdownShown;
using password_manager::metrics_util::PasswordDropdownState;

namespace {

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

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);
    driver->GetPasswordManager()->UpdateStateOnUserInput(driver, form_id,
                                                         field_id, field_value);
  }

 private:
  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;
}

}  // namespace

NSString* const kPasswordFormSuggestionSuffix = @" ••••••••";

@interface SharedPasswordController ()

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

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

// Tracks current potential generated password until accepted or rejected.
@property(nonatomic, copy) NSString* generatedPotentialPassword;

- (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> _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> _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());
  }
}

- (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;
  // 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;
  }
  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;
  _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)form
                    fromSource:
                        (AutofillManager::Observer::FieldTypeSource)source {
  // Heuristics predictions are not relevant to PWM because it runs its own
  // heuristics - only server predictions are.
  if (source ==
      AutofillManager::Observer::FieldTypeSource::kHeuristicsOrAutocomplete) {
    return;
  }

  autofill::FormStructure* form_structure = manager.FindCachedFormById(form);
  if (!form_structure) {
    return;
  }
  autofill::FormDataAndServerPredictions forms_and_predictions =
      autofill::GetFormDataAndServerPredictions(*form_structure);

  if (base::FeatureList::IsEnabled(
          autofill::features::kAutofillAcrossIframesIos)) {
    // Process the predictions for each renderer form that composes the browser
    // form when Autofill across frames is enabled.

    // Split the browser form into renderer forms.
    const autofill::AutofillDriverRouter& router =
        autofill::AutofillDriverIOSFactory::FromWebState(_webState)->router();
    std::vector<FormData> renderer_forms =
        router.GetRendererForms(forms_and_predictions.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;
      }
      _passwordManager->ProcessAutofillPredictions(
          IOSPasswordManagerDriverFactory::FromWebStateAndWebFrame(_webState,
                                                                   child_frame),
          renderer_form, forms_and_predictions.predictions);
    }
  } else {
    auto& driver = static_cast<autofill::AutofillDriverIOS&>(manager.driver());
    web::WebFrame* frame = driver.web_frame();
    if (!frame) {
      return;
    }
    // `GetFormDataAndServerPredictions` returns the same number of `FormData`
    // as `FormStructure` that are passed to it, i.e. one in this case.
    // Therefore take the front.
    _passwordManager->ProcessAutofillPredictions(
        IOSPasswordManagerDriverFactory::FromWebStateAndWebFrame(_webState,
                                                                 frame),
        forms_and_predictions.form_data, forms_and_predictions.predictions);
  }
}

#pragma mark - FormSuggestionProvider

- (void)checkIfSuggestionsAvailableForForm:
            (FormSuggestionProviderQuery*)formQuery
                            hasUserGesture:(BOOL)hasUserGesture
                                  webState:(web::WebState*)webState
                         completionHandler:
                             (SuggestionsAvailableCompletion)completion {
  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 isEqual:@"input"] ||
       [formQuery.type isEqual:@"keyup"]) &&
      formQuery.fieldRendererID == self.passwordGeneratedIdentifier) {
    // 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 = FieldRendererId();
      _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 isEqual:@"input"] ||
        [formQuery.type isEqual:@"keyup"]) {
      [self.formHelper updateFieldDataOnUserInput:formQuery.fieldRendererID
                                          inFrame:frame
                                       inputValue:formQuery.typedValue];
      _passwordManager->UpdateStateOnUserInput(
          [_driverHelper PasswordManagerDriver:frame], 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;
  }
  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;
    }
    DCHECK(self.delegate.passwordManagerClient);
    NSString* value = [rawSuggestion.value
        stringByAppendingString:kPasswordFormSuggestionSuffix];
    FormSuggestion* suggestion =
        [FormSuggestion suggestionWithValue:value
                         displayDescription:rawSuggestion.displayDescription
                                       icon:nil
                                       type:rawSuggestion.type
                          backendIdentifier:nil
                             requiresReauth:YES
                 acceptanceA11yAnnouncement:nil
                                   metadata:rawSuggestion.metadata];
    [suggestions addObject:suggestion];
  }
  std::optional<PasswordDropdownState> suggestionState;
  if (suggestions.count) {
    suggestionState = PasswordDropdownState::kStandard;
  }

  if ([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
          backendIdentifier:nil
             requiresReauth:NO];

    [suggestions addObject:suggestion];
    suggestionState = PasswordDropdownState::kStandardGenerate;
  }

  if (suggestionState) {
    LogPasswordDropdownShown(*suggestionState);
  }

  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::LogPasswordDropdownItemSelected(
          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::LogPasswordDropdownItemSelected(
          password_manager::metrics_util::PasswordDropdownSelectedOption::
              kGenerate,
          [self IsOffTheRecord]);
      return;
    }
    default: {
      password_manager::metrics_util::LogPasswordDropdownItemSelected(
          password_manager::metrics_util::PasswordDropdownSelectedOption::
              kPassword,
          [self IsOffTheRecord]);
      DCHECK([suggestion.value hasSuffix:kPasswordFormSuggestionSuffix]);
      NSString* username = [suggestion.value
          substringToIndex:suggestion.value.length -
                           kPasswordFormSuggestionSuffix.length];
      std::unique_ptr<password_manager::FillData> fillData =
          [self.suggestionHelper passwordFillDataForUsername:username
                                                  forFrameId:frameId];

      if (!fillData) {
        completion();
        return;
      }

      [self.formHelper fillPasswordFormWithFillData:*fillData
                                            inFrame:frame
                                   triggeredOnField:fieldRendererID
                                  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 GURL&)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)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:(NSString*)fieldType
                           inFrame:(web::WebFrame*)frame {
  if (![_driverHelper PasswordGenerationHelper:frame]->IsGenerationEnabled(
          /*log_debug_data*/ true)) {
    return NO;
  }
  if (![fieldType isEqual:kObfuscatedFieldType]) {
    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(
          [self lastCommittedURL],
          isManuallyTriggered ? PasswordGenerationType::kManual
                              : PasswordGenerationType::kAutomatic,
          formSignature, fieldSignature, maxLength);

  self.generatedPotentialPassword = SysUTF16ToNSString(generatedPassword);

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

- (void)onPasswordGenerationAccepted:(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];
  if (accepted) {
    LogPasswordGenerationEvent(
        autofill::password_generation::PASSWORD_ACCEPTED);

    __weak SharedPasswordController* weakSelf = self;
    [self injectGeneratedPasswordForFormId:formIdentifier
                                   inFrame:frame
                         generatedPassword:self.generatedPotentialPassword
                         completionHandler:^{
                           [weakSelf clearGeneratedPotentialPassword];
                         }];
  } else {
    [self cancelPasswordGeneration];
  }
}

- (void)clearGeneratedPotentialPassword {
  self.generatedPotentialPassword = nil;
}

- (void)cancelPasswordGeneration {
  [self clearGeneratedPotentialPassword];
  _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];
  if (!generationData) {
    return;
  }
  FieldRendererId newPasswordUniqueId =
      generationData->new_password_renderer_id;
  FieldRendererId confirmPasswordUniqueId =
      generationData->confirmation_password_renderer_id;

  __weak SharedPasswordController* weakSelf = self;
  auto generatedPasswordInjected = ^(BOOL success) {
    if (success) {
      [weakSelf onFilledPasswordForm:formIdentifier
               withGeneratedPassword:generatedPassword
                    passwordUniqueId:newPasswordUniqueId
                             inFrame:frame];
    }
    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:(web::WebFrame*)frame {
  __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 presaveGeneratedPassword:generatedPassword
                              formData:form
                               inFrame:frame];
  };

  [self.formHelper extractPasswordFormData:formIdentifier
                                   inFrame:frame
                         completionHandler:passwordPresaved];
  self.isPasswordGenerated = YES;
  self.passwordGeneratedIdentifier = newPasswordUniqueId;
}

- (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);
}

#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 == "input" || params.type == "change") {
    _lastSubmittedPasswordManagerDriver =
        IOSPasswordManagerDriverFactory::GetRetainableDriver(_webState, frame);
  }

  if (params.type == "focus") {
    _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 == "form_changed") {
    [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