chromium/components/autofill/ios/browser/autofill_agent.mm

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

#import "components/autofill/ios/browser/autofill_agent.h"

#import <UIKit/UIKit.h>

#import <cstdint>
#import <memory>
#import <optional>
#import <string>
#import <tuple>
#import <utility>

#import "base/apple/foundation_util.h"
#import "base/containers/map_util.h"
#import "base/feature_list.h"
#import "base/format_macros.h"
#import "base/functional/bind.h"
#import "base/json/json_reader.h"
#import "base/json/json_writer.h"
#import "base/memory/raw_ptr.h"
#import "base/memory/weak_ptr.h"
#import "base/metrics/field_trial.h"
#import "base/metrics/histogram_functions.h"
#import "base/ranges/algorithm.h"
#import "base/strings/string_number_conversions.h"
#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "base/time/time.h"
#import "base/types/cxx23_to_underlying.h"
#import "base/uuid.h"
#import "base/values.h"
#import "build/branding_buildflags.h"
#import "components/autofill/core/browser/autofill_field.h"
#import "components/autofill/core/browser/browser_autofill_manager.h"
#import "components/autofill/core/browser/data_model/autofill_profile.h"
#import "components/autofill/core/browser/data_model/credit_card.h"
#import "components/autofill/core/browser/filling_product.h"
#import "components/autofill/core/browser/metrics/autofill_metrics.h"
#import "components/autofill/core/browser/ui/suggestion.h"
#import "components/autofill/core/browser/ui/suggestion_type.h"
#import "components/autofill/core/common/autofill_constants.h"
#import "components/autofill/core/common/autofill_features.h"
#import "components/autofill/core/common/autofill_payments_features.h"
#import "components/autofill/core/common/autofill_prefs.h"
#import "components/autofill/core/common/autofill_util.h"
#import "components/autofill/core/common/field_data_manager.h"
#import "components/autofill/core/common/form_data.h"
#import "components/autofill/core/common/form_data_predictions.h"
#import "components/autofill/core/common/form_field_data.h"
#import "components/autofill/core/common/unique_ids.h"
#import "components/autofill/ios/browser/autofill_driver_ios.h"
#import "components/autofill/ios/browser/autofill_java_script_feature.h"
#import "components/autofill/ios/browser/autofill_util.h"
#import "components/autofill/ios/browser/form_suggestion.h"
#import "components/autofill/ios/browser/form_suggestion_provider.h"
#import "components/autofill/ios/browser/password_autofill_agent.h"
#import "components/autofill/ios/common/features.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/autofill/ios/form_util/form_handlers_java_script_feature.h"
#import "components/autofill/ios/form_util/form_util_java_script_feature.h"
#import "components/feature_engagement/public/feature_constants.h"
#import "components/grit/components_resources.h"
#import "components/plus_addresses/features.h"
#import "components/prefs/ios/pref_observer_bridge.h"
#import "components/prefs/pref_change_registrar.h"
#import "components/prefs/pref_service.h"
#import "components/ukm/ios/ukm_url_recorder.h"
#import "ios/chrome/browser/shared/public/features/features.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 "ios/web/public/web_state_observer_bridge.h"
#import "services/metrics/public/cpp/ukm_builders.h"
#import "third_party/abseil-cpp/absl/types/variant.h"
#import "ui/base/resource/resource_bundle.h"
#import "ui/gfx/geometry/rect.h"
#import "ui/gfx/image/image.h"
#import "url/gurl.h"

using autofill::AutofillJavaScriptFeature;
using autofill::FieldDataManager;
using autofill::FieldDataManagerFactoryIOS;
using autofill::FieldGlobalId;
using autofill::FieldRendererId;
using autofill::FormData;
using autofill::FormFieldData;
using autofill::FormGlobalId;
using autofill::FormHandlersJavaScriptFeature;
using autofill::FormRendererId;
using autofill::FormUtilJavaScriptFeature;
using autofill::FieldPropertiesFlags::kAutofilledOnUserTrigger;
using base::NumberToString;
using base::SysNSStringToUTF16;
using base::SysNSStringToUTF8;
using base::SysUTF16ToNSString;
using base::SysUTF8ToNSString;

namespace {

using FormDataVector = std::vector<FormData>;
// Maps each field id to their respective host form id. This is needed as the
// information linking the fields to their host form is lost between the moment
// of filling and when receiving the filling response.
using FieldToFormLookupMap = std::map<FieldRendererId, FormRendererId>;

// Contains the data for doing filling.
struct AutofillData {
  std::string frameID;
  base::Value::Dict payload;
  FieldToFormLookupMap fieldToFormLookupMap;
};

// The type of the completion handler callback for
// |fetchFormsWithName:completionHandler|
using FetchFormsCompletionHandler =
    base::OnceCallback<void(BOOL, const FormDataVector&)>;

// Delay for setting an utterance to be queued, it is required to ensure that
// standard announcements have already been started and thus would not interrupt
// the enqueued utterance.
constexpr base::TimeDelta kA11yAnnouncementQueueDelay = base::Seconds(1);

// The correct icon size to use in suggestions. Used to ensure images are scaled
// appropriately.
constexpr CGFloat kSuggestionIconWidth = 32;

bool ContainsFocusableField(const FormData& form, FieldRendererId field_id) {
  auto it =
      base::ranges::find(form.fields(), field_id, &FormFieldData::renderer_id);
  return it != form.fields().end() && it->is_focusable();
}

}  // namespace

@interface AutofillAgent () <CRWWebStateObserver,
                             CRWWebFramesManagerObserver,
                             FormActivityObserver,
                             PrefObserverDelegate> {
  // The WebState this instance is observing. Will be null after
  // -webStateDestroyed: has been called.
  raw_ptr<web::WebState> _webState;

  // Bridge to observe the web state 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;

  // The pref service for which this agent was created.
  raw_ptr<PrefService> _prefService;

  // The unique renderer ID of the most recent autocomplete field;
  // tracks the currently-focused form element in order to force filling of
  // the currently selected form element, even if it's non-empty.
  FieldRendererId _pendingAutocompleteFieldID;

  // Suggestions state:
  // The most recent form suggestions.
  NSArray* _mostRecentSuggestions;

  // The completion to inform FormSuggestionController that a user selection
  // has been handled.
  SuggestionHandledCompletion _suggestionHandledCompletion;

  // The completion to inform FormSuggestionController that suggestions are
  // available for a given form and field.
  SuggestionsAvailableCompletion _suggestionsAvailableCompletion;

  // The text entered by the user into the active field.
  NSString* _typedValue;

  // Delegate for the most recent suggestions.
  // The reference is weak because a weak pointer is sent to our
  // BrowserAutofillManagerDelegate.
  base::WeakPtr<autofill::AutofillSuggestionDelegate> _suggestionDelegate;

  // The autofill data that needs to be sent when the |webState_| is shown.
  std::optional<AutofillData> _pendingFormData;

  // Bridge to listen to pref changes.
  std::unique_ptr<PrefObserverBridge> _prefObserverBridge;
  // Registrar for pref changes notifications.
  PrefChangeRegistrar _prefChangeRegistrar;

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

  // ID of the last Autofill query made. Used to discard outdated suggestions.
  FieldGlobalId _lastQueriedFieldID;
}

@end

@implementation AutofillAgent

- (instancetype)initWithPrefService:(PrefService*)prefService
                           webState:(web::WebState*)webState {
  DCHECK(prefService);
  DCHECK(webState);
  self = [super init];
  if (self) {
    _webState = webState;
    _webStateObserverBridge =
        std::make_unique<web::WebStateObserverBridge>(self);
    _webState->AddObserver(_webStateObserverBridge.get());
    _webFramesManagerObserverBridge =
        std::make_unique<web::WebFramesManagerObserverBridge>(self);
    web::WebFramesManager* framesManager =
        AutofillJavaScriptFeature::GetInstance()->GetWebFramesManager(
            _webState);
    framesManager->AddObserver(_webFramesManagerObserverBridge.get());
    _formActivityObserverBridge =
        std::make_unique<autofill::FormActivityObserverBridge>(_webState, self);
    _prefService = prefService;
    _prefObserverBridge = std::make_unique<PrefObserverBridge>(self);
    _prefChangeRegistrar.Init(prefService);
    _prefObserverBridge->ObserveChangesForPreference(
        autofill::prefs::kAutofillCreditCardEnabled, &_prefChangeRegistrar);
    _prefObserverBridge->ObserveChangesForPreference(
        autofill::prefs::kAutofillProfileEnabled, &_prefChangeRegistrar);
  }
  return self;
}

- (void)dealloc {
  if (_webState) {
    [self webStateDestroyed:_webState];
  }
}

#pragma mark - FormSuggestionProvider

- (void)checkIfSuggestionsAvailableForForm:
            (FormSuggestionProviderQuery*)formQuery
                            hasUserGesture:(BOOL)hasUserGesture
                                  webState:(web::WebState*)webState
                         completionHandler:
                             (SuggestionsAvailableCompletion)completion {
  DCHECK_EQ(_webState, webState);

  if (![self isAutofillEnabled]) {
    completion(NO);
    return;
  }

  // Check for suggestions if the form activity is initiated by the user.
  if (!hasUserGesture) {
    completion(NO);
    return;
  }

  web::WebFramesManager* frames_manager =
      AutofillJavaScriptFeature::GetInstance()->GetWebFramesManager(_webState);
  web::WebFrame* frame =
      frames_manager->GetFrameWithId(SysNSStringToUTF8(formQuery.frameID));
  if (!frame) {
    completion(NO);
    return;
  }

  const auto callback = [](AutofillAgent* agent,
                           FormSuggestionProviderQuery* formQuery,
                           base::WeakPtr<web::WebFrame> frame,
                           base::WeakPtr<web::WebState> webState,
                           SuggestionsAvailableCompletion completion,
                           BOOL success, const FormDataVector& forms) {
    if (success && forms.size() == 1) {
      // Once the active form and field are extracted, send a query to the
      // BrowserAutofillManager for suggestions.
      [agent queryAutofillForForm:forms[0]
                  fieldIdentifier:formQuery.fieldRendererID
                             type:formQuery.type
                       typedValue:formQuery.typedValue
                            frame:frame
                         webState:webState
                completionHandler:completion];
    }
  };

  // Re-extract the active form and field only. All forms with at least one
  // input element are considered because key/value suggestions are offered
  // even on short forms.
  [self fetchFormsFiltered:YES
                  withName:SysNSStringToUTF16(formQuery.formName)
                   inFrame:frame
         completionHandler:base::BindOnce(callback, self, formQuery,
                                          frame->AsWeakPtr(),
                                          webState->GetWeakPtr(), completion)];
}

- (void)retrieveSuggestionsForForm:(FormSuggestionProviderQuery*)formQuery
                          webState:(web::WebState*)webState
                 completionHandler:(SuggestionsReadyCompletion)completion {
  DCHECK(_mostRecentSuggestions) << "Requestor should have called "
                                 << "|checkIfSuggestionsAvailableForForm:"
                                    "webState:completionHandler:|.";
  completion(_mostRecentSuggestions, 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 {
  [[UIDevice currentDevice] playInputClick];
  DCHECK(completion);
  _suggestionHandledCompletion = [completion copy];

  if (suggestion.acceptanceA11yAnnouncement != nil) {
    // The announcement is done asyncronously with certain delay to make sure
    // it is not interrupted by (almost) immediate standard announcements.
    dispatch_after(
        dispatch_time(DISPATCH_TIME_NOW,
                      kA11yAnnouncementQueueDelay.InNanoseconds()),
        dispatch_get_main_queue(), ^{
          // Queueing flag allows to preserve standard announcements,
          // they are conveyed first and then announce this message.
          // This is a tradeoff as there is no control over the
          // standard utterances (they are interrupting) and it is
          // not desirable to interrupt them. Hence acceptance
          // announcement is done after standard ones (which takes
          // seconds).
          NSAttributedString* message = [[NSAttributedString alloc]
              initWithString:suggestion.acceptanceA11yAnnouncement
                  attributes:@{
                    UIAccessibilitySpeechAttributeQueueAnnouncement : @YES
                  }];
          UIAccessibilityPostNotification(
              UIAccessibilityAnnouncementNotification, message);
        });
  }

  if (suggestion.type == autofill::SuggestionType::kAddressEntry ||
      suggestion.type == autofill::SuggestionType::kCreditCardEntry ||
      suggestion.type == autofill::SuggestionType::kCreateNewPlusAddress ||
      (base::FeatureList::IsEnabled(
           autofill::features::kAutofillEnableVirtualCards) &&
       suggestion.type == autofill::SuggestionType::kVirtualCreditCardEntry)) {
    _pendingAutocompleteFieldID = fieldRendererID;
    if (_suggestionDelegate) {
      autofill::Suggestion autofill_suggestion;
      autofill_suggestion.main_text.value =
          SysNSStringToUTF16(suggestion.value);
      autofill_suggestion.type = suggestion.type;
      if (!suggestion.backendIdentifier.length) {
        autofill_suggestion.payload = autofill::Suggestion::BackendId();
      } else {
        autofill_suggestion.payload =
            autofill::Suggestion::BackendId(autofill::Suggestion::Guid(
                SysNSStringToUTF8(suggestion.backendIdentifier)));
      }

      _suggestionDelegate->DidAcceptSuggestion(autofill_suggestion,
                                               {static_cast<int>(index), 0});
    }
    return;
  }

  web::WebFramesManager* frames_manager =
      AutofillJavaScriptFeature::GetInstance()->GetWebFramesManager(_webState);
  web::WebFrame* frame =
      frames_manager->GetFrameWithId(SysNSStringToUTF8(frameID));
  if (!frame) {
    // The frame no longer exists, so the field can not be filled.
    if (SuggestionHandledCompletion c =
            std::exchange(_suggestionHandledCompletion, nil)) {
      c();
    }
    return;
  }

  if (suggestion.type == autofill::SuggestionType::kAutocompleteEntry ||
      suggestion.type == autofill::SuggestionType::kFillExistingPlusAddress) {
    // FormSuggestion is a simple, single value that can be filled out now.
    [self fillField:SysNSStringToUTF8(fieldIdentifier)
        fieldRendererID:fieldRendererID
         formRendererID:formRendererID
               formName:SysNSStringToUTF8(formName)
                  value:SysNSStringToUTF16(suggestion.value)
                inFrame:frame];
  } else if (suggestion.type == autofill::SuggestionType::kUndoOrClear) {
    const auto callback = [](__weak AutofillAgent* agent,
                             base::WeakPtr<web::WebFrame> frame,
                             FormRendererId formId,
                             SuggestionHandledCompletion completion,
                             NSString* jsonString) {
      if (frame) {
        [agent onDidClearFields:jsonString inFrame:frame.get() inForm:formId];
      }
      // Only run the completion if set as it isn't impossible that the provided
      // completion is nil.
      if (completion) {
        completion();
      }
    };

    __weak __typeof(self) weakSelf = self;
    AutofillJavaScriptFeature::GetInstance()->ClearAutofilledFieldsForForm(
        frame, formRendererID, fieldRendererID,
        base::BindOnce(callback, weakSelf, frame->AsWeakPtr(), formRendererID,
                       std::exchange(_suggestionHandledCompletion, nil)));

  } else if (suggestion.type == autofill::SuggestionType::kShowAccountCards) {
    autofill::BrowserAutofillManager* autofillManager =
        [self autofillManagerFromWebState:_webState webFrame:frame];
    if (autofillManager) {
      autofillManager->OnUserAcceptedCardsFromAccountOption();
    }
  } else {
    NOTREACHED_IN_MIGRATION()
        << "unknown identifier " << base::to_underlying(suggestion.type);
  }
}

- (SuggestionProviderType)type {
  return SuggestionProviderTypeAutofill;
}

- (autofill::FillingProduct)mainFillingProduct {
  return _suggestionDelegate ? _suggestionDelegate->GetMainFillingProduct()
                             : autofill::FillingProduct::kNone;
}

#pragma mark - AutofillDriverIOSBridge

- (void)fillData:(const std::vector<autofill::FormFieldData::FillData>&)data
         inFrame:(web::WebFrame*)frame {
  base::Value::Dict fieldsData;
  FieldToFormLookupMap fieldToFormLookupMap;

  for (const auto& field : data) {
    // Skip empty fields and those that are not autofilled.
    if (field.value.empty() || !field.is_autofilled) {
      continue;
    }

    base::Value::Dict fieldData;
    fieldData.Set("value", field.value);
    fieldData.Set("section", field.section.ToString());
    fieldData.Set("hostFormId", static_cast<int>(*field.host_form_id));
    fieldsData.Set(NumberToString(*field.renderer_id), std::move(fieldData));

    fieldToFormLookupMap[field.renderer_id] = field.host_form_id;
  }
  auto payload = base::Value::Dict().Set("fields", std::move(fieldsData));
  AutofillData autofillData = {
      .frameID = frame ? frame->GetFrameId() : "",
      .payload = std::move(payload),
      .fieldToFormLookupMap = std::move(fieldToFormLookupMap)};

  // Store the form data when WebState is not visible, to send it as soon as it
  // becomes visible again, e.g., when the CVC unmask prompt is showing.
  if (!_webState->IsVisible()) {
    _pendingFormData = std::move(autofillData);
  } else {
    [self sendData:std::move(autofillData) toFrame:frame];
  }
}

// Similar to `fillField`, but does not rely on `FillActiveFormField`, opting
// instead to find and fill a specific field in `frame` with `value`. In other
// words, `field` need not be `document.activeElement`.
- (void)fillSpecificFormField:(const FieldRendererId&)field
                    withValue:(const std::u16string)value
                      inFrame:(web::WebFrame*)frame {
  base::Value::Dict data;
  data.Set("renderer_id", static_cast<int>(field.value()));
  data.Set("value", value);

  const auto callback =
      [](__weak AutofillAgent* agent, SuggestionHandledCompletion completion,
         FieldRendererId fieldId, base::WeakPtr<web::WebFrame> frame,
         const std::u16string& value, BOOL success) {
        if (success && frame) {
          [agent onDidFillField:fieldId
                           form:std::nullopt
                          frame:frame.get()
                          value:value];
        }
        // Only run the completion if set as it isn't impossible that the
        // provided completion is nil.
        if (completion) {
          completion();
        }
      };

  __weak __typeof(self) weakSelf = self;
  AutofillJavaScriptFeature::GetInstance()->FillSpecificFormField(
      frame, std::move(data),
      base::BindOnce(callback, weakSelf,
                     std::exchange(_suggestionHandledCompletion, nil), field,
                     frame->AsWeakPtr(), value));
}

- (void)handleParsedForms:
            (const std::vector<
                raw_ptr<autofill::FormStructure, VectorExperimental>>&)forms
                  inFrame:(web::WebFrame*)frame {
}

- (void)fillFormDataPredictions:
            (const std::vector<autofill::FormDataPredictions>&)forms
                        inFrame:(web::WebFrame*)frame {
  if (!base::FeatureList::IsEnabled(
          autofill::features::test::kAutofillShowTypePredictions)) {
    return;
  }

  base::Value::Dict predictionData;
  for (const auto& form : forms) {
    base::Value::Dict fieldData;
    DCHECK(form.fields.size() == form.data.fields().size());
    for (size_t i = 0; i < form.fields.size(); i++) {
      fieldData.Set(NumberToString(form.data.fields()[i].renderer_id().value()),
                    base::Value(form.fields[i].overall_type));
    }
    predictionData.Set(base::UTF16ToUTF8(form.data.name()),
                       std::move(fieldData));
  }
  AutofillJavaScriptFeature::GetInstance()->FillPredictionData(
      frame, std::move(predictionData));
}

- (void)scanFormsInWebState:(web::WebState*)webState
                    inFrame:(web::WebFrame*)webFrame {
  __weak __typeof(self) weakSelf = self;
  const auto callback = [](__weak AutofillAgent* agent,
                           base::WeakPtr<web::WebFrame> frame, BOOL success,
                           const FormDataVector& forms) {
    if (!success || forms.empty()) {
      return;
    }
    [agent notifyFormsSeen:forms inFrame:frame.get()];
  };
  // The document has now been fully loaded. Scan for forms to be extracted.
  [self fetchFormsFiltered:NO
                  withName:std::u16string()
                   inFrame:webFrame
         completionHandler:base::BindOnce(callback, weakSelf,
                                          webFrame->AsWeakPtr())];
}

#pragma mark - AutofillClientIOSBridge

- (void)showAutofillPopup:
            (const std::vector<autofill::Suggestion>&)popup_suggestions
       suggestionDelegate:
           (const base::WeakPtr<autofill::AutofillSuggestionDelegate>&)
               delegate {
  // Convert the suggestions into an NSArray for the keyboard.
  NSMutableArray<FormSuggestion*>* suggestions = [[NSMutableArray alloc] init];
  for (auto popup_suggestion : popup_suggestions) {
    // In the Chromium implementation the identifiers represent rows on the
    // drop down of options. These include elements that aren't relevant to us
    // such as separators ... see blink::WebAutofillClient::MenuItemIDSeparator
    // for example. We can't include that enum because it's from WebKit, but
    // fortunately almost all the entries we are interested in (profile or
    // autofill entries) are zero or positive. Negative entries we are
    // interested in is autofill::SuggestionType::kUndoOrClear, used to show the
    // "clear form" button.
    // TODO(crbug.com/40266549): Replace Clear Form with Undo
    NSString* value = nil;
    NSString* minorValue = nil;
    NSString* displayDescription = nil;
    UIImage* icon = nil;

    if (popup_suggestion.type == autofill::SuggestionType::kAutocompleteEntry ||
        popup_suggestion.type == autofill::SuggestionType::kAddressEntry ||
        popup_suggestion.type == autofill::SuggestionType::kCreditCardEntry ||
        (base::FeatureList::IsEnabled(
             autofill::features::kAutofillEnableVirtualCards) &&
         popup_suggestion.type ==
             autofill::SuggestionType::kVirtualCreditCardEntry)) {
      // Filter out any key/value suggestions if the user hasn't typed yet.
      if (popup_suggestion.type ==
              autofill::SuggestionType::kAutocompleteEntry &&
          _typedValue.length == 0) {
        continue;
      }
      // Value will contain the text to be filled in the selected element while
      // displayDescription will contain a summary of the data to be filled in
      // the other elements.
      value = SysUTF16ToNSString(popup_suggestion.main_text.value);

      if (base::FeatureList::IsEnabled(
              autofill::features::kAutofillEnableVirtualCards) &&
          (!popup_suggestion.minor_text.value.empty())) {
        // For Virtual Cards, the main_text is just "Virtual card" so we need to
        // include the minor_text (which is the card name + last 4 digits ||
        // card holder's name) as the minorValue.
        minorValue = SysUTF16ToNSString(popup_suggestion.minor_text.value);
      }

      if (!popup_suggestion.labels.empty() &&
          !popup_suggestion.labels.front().empty()) {
        DCHECK_EQ(popup_suggestion.labels.size(), 1U);
        DCHECK_EQ(popup_suggestion.labels[0].size(), 1U);
        displayDescription =
            SysUTF16ToNSString(popup_suggestion.labels[0][0].value);
      }

      // Only show icon for credit card suggestions.
      if (delegate && delegate->GetMainFillingProduct() ==
                          autofill::FillingProduct::kCreditCard) {
        icon = [self createIcon:popup_suggestion];
      }
    } else if (popup_suggestion.type ==
               autofill::SuggestionType::kUndoOrClear) {
      // Show the "clear form" button.
      // TODO(crbug.com/40266549): Replace Clear Form with Undo once this
      // changes
      value = SysUTF16ToNSString(popup_suggestion.main_text.value);
    } else if (popup_suggestion.type ==
               autofill::SuggestionType::kShowAccountCards) {
      // Show opt-in for showing cards from account.
      value = SysUTF16ToNSString(popup_suggestion.main_text.value);
    } else if (popup_suggestion.type ==
                   autofill::SuggestionType::kFillExistingPlusAddress ||
               popup_suggestion.type ==
                   autofill::SuggestionType::kCreateNewPlusAddress) {
      // Show any plus_address suggestions.
      value = SysUTF16ToNSString(popup_suggestion.main_text.value);
      if (!popup_suggestion.labels.empty() &&
          !popup_suggestion.labels.front().empty() &&
          IsKeyboardAccessoryUpgradeEnabled()) {
        displayDescription =
            SysUTF16ToNSString(popup_suggestion.labels[0][0].value);
      }
    }

    if (!value)
      continue;

    NSString* acceptanceA11yAnnouncement =
        popup_suggestion.acceptance_a11y_announcement.has_value()
            ? SysUTF16ToNSString(*popup_suggestion.acceptance_a11y_announcement)
            : nil;

    FormSuggestion* suggestion = [FormSuggestion
               suggestionWithValue:value
                        minorValue:minorValue
                displayDescription:displayDescription
                              icon:icon
                              type:popup_suggestion.type
                 backendIdentifier:SysUTF8ToNSString(
                                       popup_suggestion
                                           .GetBackendId<
                                               autofill::Suggestion::Guid>()
                                           .value())
                    requiresReauth:NO
        acceptanceA11yAnnouncement:acceptanceA11yAnnouncement];

    suggestion.featureForIPH = SuggestionFeatureForIPH::kUnknown;
    if (popup_suggestion.feature_for_iph ==
        &feature_engagement::
            kIPHAutofillExternalAccountProfileSuggestionFeature) {
      suggestion.featureForIPH =
          SuggestionFeatureForIPH::kAutofillExternalAccountProfile;
    } else if (popup_suggestion.feature_for_iph ==
               &feature_engagement::kIPHPlusAddressCreateSuggestionFeature) {
      suggestion.featureForIPH = SuggestionFeatureForIPH::kPlusAddressCreation;
    }

    // Put "clear form" entry at the front of the suggestions.
    if (popup_suggestion.type == autofill::SuggestionType::kUndoOrClear) {
      [suggestions insertObject:suggestion atIndex:0];
    } else {
      [suggestions addObject:suggestion];
    }
  }

  [self onSuggestionsReady:suggestions suggestionDelegate:delegate];

  if (delegate)
    delegate->OnSuggestionsShown();
}

- (void)hideAutofillPopup {
  [self
      onSuggestionsReady:@[]
      suggestionDelegate:base::WeakPtr<autofill::AutofillSuggestionDelegate>()];
}

- (bool)isLastQueriedField:(FieldGlobalId)fieldID {
  return fieldID == _lastQueriedFieldID;
}

#pragma mark - CRWWebStateObserver

- (void)webStateWasShown:(web::WebState*)webState {
  DCHECK_EQ(_webState, webState);
  if (!_pendingFormData) {
    return;
  }

  // The frameID cannot be empty.
  const std::string& frameID = _pendingFormData->frameID;
  CHECK(!frameID.empty());
  web::WebFramesManager* frames_manager =
      AutofillJavaScriptFeature::GetInstance()->GetWebFramesManager(_webState);
  web::WebFrame* frame = frames_manager->GetFrameWithId(frameID);
  [self sendData:std::move(*_pendingFormData) toFrame:frame];
  _pendingFormData.reset();
}

- (void)webState:(web::WebState*)webState didLoadPageWithSuccess:(BOOL)success {
  DCHECK_EQ(_webState, webState);
  if (![self isAutofillEnabled])
    return;

  [self processPage:webState];
}

- (void)webStateDestroyed:(web::WebState*)webState {
  DCHECK_EQ(_webState, webState);
  if (_webState) {
    _formActivityObserverBridge.reset();
    _webState->RemoveObserver(_webStateObserverBridge.get());
    _webStateObserverBridge.reset();
    web::WebFramesManager* framesManager =
        AutofillJavaScriptFeature::GetInstance()->GetWebFramesManager(
            _webState);
    framesManager->RemoveObserver(_webFramesManagerObserverBridge.get());
    _webFramesManagerObserverBridge.reset();
    _webState = nullptr;
  }
  // Do not wait for deallocation. Remove all observers here.
  _prefChangeRegistrar.RemoveAll();
}

#pragma mark - CRWWebFramesManagerObserver

- (void)webFramesManager:(web::WebFramesManager*)webFramesManager
    frameBecameAvailable:(web::WebFrame*)webFrame {
  DCHECK(_webState);
  DCHECK(webFrame);
  if (![self isAutofillEnabled] || _webState->IsLoading()) {
    return;
  }
  if (webFrame->IsMainFrame()) {
    [self processPage:_webState];
    return;
  }
  // Check that the main frame has already been processed.
  if (!webFramesManager->GetMainWebFrame()) {
    return;
  }
  auto* main_driver = autofill::AutofillDriverIOS::FromWebStateAndWebFrame(
      _webState, webFramesManager->GetMainWebFrame());
  DLOG_IF(WARNING, !main_driver) << "No AutofillDriverIOS found for WebFrame";
  if (!main_driver || !main_driver->is_processed()) {
    return;
  }
  [self processFrame:webFrame inWebState:_webState];
}

#pragma mark - FormActivityObserver

- (void)webState:(web::WebState*)webState
    didRegisterFormActivity:(const autofill::FormActivityParams&)params
                    inFrame:(web::WebFrame*)frame {
  DCHECK_EQ(_webState, webState);
  if (![self isAutofillEnabled])
    return;

  if (!frame) {
    return;
  }

  // Return early if the page is not processed yet.
  auto* driver =
      autofill::AutofillDriverIOS::FromWebStateAndWebFrame(webState, frame);
  DLOG_IF(WARNING, !driver) << "No AutofillDriverIOS found for WebFrame";
  if (!driver || !driver->is_processed()) {
    return;
  }

  // Return early if |params| is not complete.
  if (params.input_missing)
    return;

  // If the event is a form_changed, then the event concerns the whole page and
  // not a particular form. The whole document's forms need to be extracted to
  // find the new forms.
  if (params.type == "form_changed") {
    [self scanFormsInWebState:webState inFrame:frame];
    return;
  }

  // We are only interested in 'input' events in order to notify the autofill
  // manager for metrics purposes.
  if (params.type != "input" ||
      (params.field_type != "text" && params.field_type != "password")) {
    return;
  }

  // The completion block is executed asynchronously, thus it cannot refer
  // directly to `params.field_identifier` (as params is passed by reference
  // and may have been destroyed by the point the block is executed).
  __weak __typeof(self) weakSelf = self;
  const auto callback =
      [](__weak AutofillAgent* agent, base::WeakPtr<web::WebFrame> frame,
         FieldRendererId fieldId, BOOL success, const FormDataVector& forms) {
        [agent onFormsFetched:success
                    formsData:forms
                     webFrame:frame
              fieldIdentifier:fieldId];
      };

  // Extract the active form and field only.
  [self
      fetchFormsFiltered:YES
                withName:base::UTF8ToUTF16(params.form_name)
                 inFrame:frame
       completionHandler:base::BindOnce(callback, weakSelf, frame->AsWeakPtr(),
                                        params.field_renderer_id)];
}

- (void)webState:(web::WebState*)webState
    didSubmitDocumentWithFormNamed:(const std::string&)formName
                          withData:(const std::string&)formData
                    hasUserGesture:(BOOL)hasUserGesture
                           inFrame:(web::WebFrame*)frame {
  if (![self isAutofillEnabled] || !frame) {
    return;
  }

  auto* driver =
      autofill::AutofillDriverIOS::FromWebStateAndWebFrame(webState, frame);
  if (!driver) {
    return;
  }

  FieldDataManager* fieldDataManager =
      FieldDataManagerFactoryIOS::FromWebFrame(frame);

  FormDataVector forms;

  bool success = autofill::ExtractFormsData(
      base::SysUTF8ToNSString(formData), true, base::UTF8ToUTF16(formName),
      webState->GetLastCommittedURL(), frame->GetSecurityOrigin(),
      *fieldDataManager, frame->GetFrameId(), &forms);

  if (!success || forms.empty()) {
    return;
  }

  // Exactly one form should be extracted.
  DCHECK_EQ(1U, forms.size());
  FormData form = forms[0];
  driver->FormSubmitted(form,
                        /*known_success=*/false,
                        autofill::mojom::SubmissionSource::FORM_SUBMISSION);
}

- (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.";

  autofill::AutofillDriverIOS* autofillDriver =
      autofill::AutofillDriverIOS::FromWebStateAndWebFrame(webState, frame);
  if (!autofillDriver) {
    return;
  }

  autofillDriver->FormsRemoved(params.removed_forms,
                               params.removed_unowned_fields);
}

#pragma mark - PrefObserverDelegate

- (void)onPreferenceChanged:(const std::string&)preferenceName {
  // Processing the page can be needed here if Autofill is enabled in settings
  // when the page is already loaded.
  if ([self isAutofillEnabled])
    [self processPage:_webState];
}

#pragma mark - Private methods

// Returns whether Autofill is enabled by checking if Autofill is turned on and
// if the current URL has a web scheme and the page content is HTML.
- (BOOL)isAutofillEnabled {
  if (!autofill::prefs::IsAutofillProfileEnabled(_prefService) &&
      !autofill::prefs::IsAutofillPaymentMethodsEnabled(_prefService)) {
    return NO;
  }

  // Only web URLs are supported by Autofill.
  return web::UrlHasWebScheme(_webState->GetLastCommittedURL()) &&
         _webState->ContentIsHTML();
}

// Fills a field identified with |fieldIdentifier| on the form named
// |formName| in |frame| using |value| then move the cursor.
// TODO(crbug.com/41284261): |dataString| ends up at fillFormField() in
// autofill_controller.js. fillFormField() expects an AutofillFormFieldData
// object, which |dataString| is not because 'form' is not a specified member of
// AutofillFormFieldData. fillFormField() also expects members 'max_length' and
// 'is_checked' to exist.
- (void)fillField:(const std::string&)fieldIdentifier
    fieldRendererID:(FieldRendererId)fieldRendererID
     formRendererID:(FormRendererId)formRendererID
           formName:(const std::string&)formName
              value:(const std::u16string)value
            inFrame:(web::WebFrame*)frame {
  base::Value::Dict data;
  data.Set("renderer_id", static_cast<int>(fieldRendererID.value()));
  data.Set("identifier", fieldIdentifier);
  data.Set("form", formName);
  data.Set("value", value);

  DCHECK(_suggestionHandledCompletion);

  const auto callback = [](__weak AutofillAgent* agent,
                           SuggestionHandledCompletion completion,
                           FieldRendererId fieldId,
                           std::optional<FormRendererId> formId,
                           base::WeakPtr<web::WebFrame> frame,
                           const std::u16string& value, BOOL success) {
    if (success && frame) {
      [agent onDidFillField:fieldId form:formId frame:frame.get() value:value];
    }
    // Only run the completion if set as it isn't impossible that the provided
    // completion is nil.
    if (completion) {
      completion();
    }
  };

  __weak __typeof(self) weakSelf = self;
  AutofillJavaScriptFeature::GetInstance()->FillActiveFormField(
      frame, std::move(data),
      base::BindOnce(
          callback, weakSelf, std::exchange(_suggestionHandledCompletion, nil),
          fieldRendererID, formRendererID, frame->AsWeakPtr(), value));
}

// Called when did fill a specific field.
- (void)onDidFillField:(FieldRendererId)fieldID
                  form:(std::optional<FormRendererId>)formID
                 frame:(web::WebFrame*)frame
                 value:(const std::u16string&)value {
  [self updateFieldManagerForSpecificField:fieldID
                                   inFrame:frame
                                 withValue:value];
  [self notifyAboutValueChangeOnField:fieldID
                               inForm:formID
                                frame:frame
                            withValue:value];
}

// Called when did fill multiple fields and received results serialized in a
// JSON string.
- (void)onDidFillWithResults:(NSString*)resultsAsJsonStr
                     inFrame:(web::WebFrame*)frame
        fieldToFormLookupMap:(const FieldToFormLookupMap&)fieldToFormLookupMap {
  std::map<uint32_t, std::u16string> fillingResults;
  if (autofill::ExtractFillingResults(resultsAsJsonStr, &fillingResults)) {
    [self updateFieldManagerWithFillingResults:fillingResults inFrame:frame];
    [self notifyAboutFormFillingResults:fillingResults
                                inFrame:frame
                   fieldToFormLookupMap:fieldToFormLookupMap];
  }

  [self recordFormFillingSuccessMetrics:!fillingResults.empty()];
}

// Called when did clear fields.
- (void)onDidClearFields:(NSString*)clearedFieldsAsJsonStr
                 inFrame:(web::WebFrame*)frame
                  inForm:(FormRendererId)formID {
  const auto clearedIDs =
      autofill::ExtractIDs<FieldRendererId>(clearedFieldsAsJsonStr);
  if (!clearedIDs) {
    return;
  }

  [self updateFieldManagerForClearedIDs:*clearedIDs inFrame:frame];
  [self notifyAboutClearedFields:*clearedIDs inFrame:frame inForm:formID];
}

// Updates field managers with filling results.
- (void)updateFieldManagerWithFillingResults:
            (const std::map<uint32_t, std::u16string>&)fillingResults
                                     inFrame:(web::WebFrame*)frame {
  for (auto& fillData : fillingResults) {
    [self updateFieldManagerForSpecificField:FieldRendererId(fillData.first)
                                     inFrame:frame
                                   withValue:fillData.second];
  }
}

- (void)updateFieldManagerForSpecificField:(FieldRendererId)fieldRendererID
                                   inFrame:(web::WebFrame*)frame
                                 withValue:(const std::u16string&)value {
  FieldDataManagerFactoryIOS::FromWebFrame(frame)->UpdateFieldDataMap(
      fieldRendererID, value, kAutofilledOnUserTrigger);
}

// Updates field managers for cleared fields.
- (void)updateFieldManagerForClearedIDs:
            (const std::set<FieldRendererId>&)clearedFields
                                inFrame:(web::WebFrame*)frame {
  for (const auto fieldID : clearedFields) {
    [self updateFieldManagerForSpecificField:fieldID
                                     inFrame:frame
                                   withValue:u""];
  }
}

// Notifies the PasswordAutofillAgent that the value of a field has changed.
- (void)notifyAboutValueChangeOnField:(FieldRendererId)fieldID
                               inForm:(std::optional<FormRendererId>)formID
                                frame:(web::WebFrame*)frame
                            withValue:(const std::u16string&)value {
  CHECK(frame);

  autofill::PasswordAutofillAgent* agent =
      autofill::PasswordAutofillAgent::FromWebState(_webState);
  agent->DidFillField(frame, formID, fieldID, value);
}

// Notifies that form filling results were received.
- (void)notifyAboutFormFillingResults:
            (const std::map<uint32_t, std::u16string>&)fillingResults
                              inFrame:(web::WebFrame*)frame
                 fieldToFormLookupMap:
                     (const FieldToFormLookupMap&)fieldToFormLookupMap {
  CHECK(frame);

  for (auto& fillData : fillingResults) {
    FieldRendererId fieldID = FieldRendererId(fillData.first);
    if (const FormRendererId* formID =
            base::FindOrNull(fieldToFormLookupMap, fieldID)) {
      [self notifyAboutValueChangeOnField:fieldID
                                   inForm:*formID
                                    frame:frame
                                withValue:fillData.second];
    }
  }
}

// Notifies that fields were cleared.
- (void)notifyAboutClearedFields:(const std::set<FieldRendererId>&)clearedFields
                         inFrame:(web::WebFrame*)frame
                          inForm:(FormRendererId)formID {
  CHECK(frame);

  for (auto fieldID : clearedFields) {
    [self notifyAboutValueChangeOnField:fieldID
                                 inForm:formID
                                  frame:frame
                              withValue:u""];
  }
}

// Sends the the |data| to |frame| to actually fill the data.
- (void)sendData:(AutofillData)data toFrame:(web::WebFrame*)frame {
  DCHECK(_webState->IsVisible());
  __weak __typeof(self) weakSelf = self;
  const auto callback =
      [](__weak AutofillAgent* agent, base::WeakPtr<web::WebFrame> frame,
         SuggestionHandledCompletion completion,
         const FieldToFormLookupMap& map, NSString* jsonString) {
        if (frame) {
          [agent onDidFillWithResults:jsonString
                              inFrame:frame.get()
                 fieldToFormLookupMap:map];
        }

        // Only run the completion if set as it isn't impossible that the
        // provided completion is nil.
        if (completion) {
          completion();
        }
      };
  AutofillJavaScriptFeature::GetInstance()->FillForm(
      frame, std::move(data.payload), _pendingAutocompleteFieldID,
      base::BindOnce(callback, weakSelf, frame->AsWeakPtr(),
                     std::exchange(_suggestionHandledCompletion, nil),
                     std::move(data.fieldToFormLookupMap)));
}

// Helper method used to implement the aynchronous completion block of
// -webState:didRegisterFormActivity:inFrame:. Due to the asynchronous
// invocation, WebState* and WebFrame* may both have been destroyed, so
// the method needs to check for those edge cases.
- (void)onFormsFetched:(BOOL)success
             formsData:(const FormDataVector&)forms
              webFrame:(base::WeakPtr<web::WebFrame>)webFrame
       fieldIdentifier:(FieldRendererId)fieldIdentifier {
  if (!success || forms.size() != 1 || !_webState || !webFrame) {
    return;
  }

  auto* driver = autofill::AutofillDriverIOS::FromWebStateAndWebFrame(
      _webState, webFrame.get());
  if (!driver) {
    return;
  }
  const FormData& form = forms[0];
  if (!ContainsFocusableField(form, fieldIdentifier)) {
    return;
  }
  driver->TextFieldDidChange(form, {form.host_frame(), fieldIdentifier},
                             base::TimeTicks::Now());
}

// Helper method to create icons for payment cards.
- (UIImage*)createIcon:(autofill::Suggestion)popup_suggestion {
  // If available, the custom icon for the card is preferred over the
  // generic network icon. The network icon may also be missing, in
  // which case we do not set an icon at all.
  if (auto* custom_icon =
          absl::get_if<gfx::Image>(&popup_suggestion.custom_icon);
      custom_icon && !custom_icon->IsEmpty()) {
    UIImage* icon = custom_icon->ToUIImage();

    // On iOS, the keyboard accessory wants smaller icons than the default
    // 40x24 size, so we resize them to 32x20, if the provided icon is
    // larger than that.
    if (icon && (icon.size.width > kSuggestionIconWidth)) {
      // For a simple image resize, we can keep the same underlying image
      // and only adjust the ratio.
      CGFloat ratio = icon.size.width / kSuggestionIconWidth;
      return [UIImage imageWithCGImage:[icon CGImage]
                                 scale:icon.scale * ratio
                           orientation:icon.imageOrientation];
    }
    return icon;
  } else if (popup_suggestion.icon != autofill::Suggestion::Icon::kNoIcon) {
    const int resourceID =
        autofill::CreditCard::IconResourceId(popup_suggestion.icon);
    return ui::ResourceBundle::GetSharedInstance()
        .GetNativeImageNamed(resourceID)
        .ToUIImage();
  }
  return nil;
}

// Returns the autofill manager associated with a web::WebState instance.
// Returns nullptr if there is no autofill manager associated anymore, this can
// happen when |close| has been called on the |webState|. Also returns nullptr
// if -webStateDestroyed: has been called.
- (autofill::BrowserAutofillManager*)
    autofillManagerFromWebState:(web::WebState*)webState
                       webFrame:(web::WebFrame*)webFrame {
  if (!webState || !_webStateObserverBridge) {
    return nullptr;
  }
  auto* driver =
      autofill::AutofillDriverIOS::FromWebStateAndWebFrame(webState, webFrame);
  DLOG_IF(WARNING, !driver) << "No AutofillDriverIOS found for WebFrame";
  if (!driver) {
    return nullptr;
  }
  return &driver->GetAutofillManager();
}

// Notifies the autofill manager when forms are detected on a page.
- (void)notifyFormsSeen:(const FormDataVector&)updatedForms
                inFrame:(web::WebFrame*)frame {
  auto* driver =
      autofill::AutofillDriverIOS::FromWebStateAndWebFrame(_webState, frame);
  if (!driver) {
    return;
  }

  DCHECK(!updatedForms.empty());

  driver->FormsSeen(/*updated_forms=*/updatedForms, /*removed_forms=*/{});
}

// Invokes the form extraction script in |frame| and loads the output into the
// format expected by the BrowserAutofillManager.
// If |filtered| is NO, all forms are extracted.
// If |filtered| is YES,
//   - if |formName| is non-empty, only a form of that name is extracted.
//   - if |formName| is empty, unowned fields are extracted.
// Only forms with at least |requiredFieldsCount| fields are extracted.
// Calls |completionHandler| with a success BOOL of YES and the form data that
// was extracted.
// Calls |completionHandler| with NO if the forms could not be extracted.
// |completionHandler| cannot be nil.
- (void)fetchFormsFiltered:(BOOL)filtered
                  withName:(const std::u16string&)formName
                   inFrame:(web::WebFrame*)frame
         completionHandler:(FetchFormsCompletionHandler)completionHandler {
  DCHECK(completionHandler);

  // Necessary so the values can be used inside a block.
  GURL pageURL = _webState->GetLastCommittedURL();
  GURL frameOrigin =
      frame ? frame->GetSecurityOrigin() : pageURL.DeprecatedGetOriginAsURL();

  const scoped_refptr<FieldDataManager> fieldDataManager =
      FieldDataManagerFactoryIOS::GetRetainable(frame);
  const auto callback = [](FetchFormsCompletionHandler completion,
                           BOOL filtered, const std::u16string& formName,
                           const GURL& pageURL, const GURL& frameOrigin,
                           scoped_refptr<FieldDataManager> fieldDataManager,
                           const std::string& frame_id, NSString* formJSON) {
    std::vector<FormData> formData;
    bool success = autofill::ExtractFormsData(
        formJSON, filtered, formName, pageURL, frameOrigin, *fieldDataManager,
        frame_id, &formData);
    std::move(completion).Run(success, formData);
  };
  AutofillJavaScriptFeature::GetInstance()->FetchForms(
      frame, base::BindOnce(callback, std::move(completionHandler), filtered,
                            formName, pageURL, frameOrigin, fieldDataManager,
                            frame->GetFrameId()));
}

- (void)onSuggestionsReady:(NSArray<FormSuggestion*>*)suggestions
        suggestionDelegate:
            (const base::WeakPtr<autofill::AutofillSuggestionDelegate>&)
                delegate {
  _suggestionDelegate = delegate;
  _mostRecentSuggestions = suggestions;
  if (SuggestionsAvailableCompletion completion =
          std::exchange(_suggestionsAvailableCompletion, nil)) {
    completion([_mostRecentSuggestions count] > 0);
  }
}

// Sends a request to BrowserAutofillManager to retrieve suggestions for the
// specified form and field.
- (void)queryAutofillForForm:(const FormData&)form
             fieldIdentifier:(FieldRendererId)fieldIdentifier
                        type:(NSString*)type
                  typedValue:(NSString*)typedValue
                       frame:(base::WeakPtr<web::WebFrame>)frame
                    webState:(base::WeakPtr<web::WebState>)webState
           completionHandler:(SuggestionsAvailableCompletion)completion {
  if (!frame || !webState) {
    completion(NO);
    return;
  }

  // Save the completion and go look for suggestions.
  _suggestionsAvailableCompletion = [completion copy];
  _typedValue = typedValue;

  // Query the BrowserAutofillManager for suggestions. Results will arrive in
  // -showAutofillPopup:suggestionDelegate:.
  if (!ContainsFocusableField(form, fieldIdentifier)) {
    return;
  }
  _lastQueriedFieldID = {form.host_frame(), fieldIdentifier};
  auto* driver = autofill::AutofillDriverIOS::FromWebStateAndWebFrame(
      _webState, frame.get());
  DLOG_IF(WARNING, !driver) << "No AutofillDriverIOS found for WebFrame";
  if (!driver) {
    return;
  }
  driver->AskForValuesToFill(form, _lastQueriedFieldID);
}

- (void)processPage:(web::WebState*)webState {
  web::WebFramesManager* frames_manager =
      AutofillJavaScriptFeature::GetInstance()->GetWebFramesManager(webState);
  if (!frames_manager->GetMainWebFrame()) {
    return;
  }
  [self processFrame:frames_manager->GetMainWebFrame() inWebState:webState];
  for (auto* frame : frames_manager->GetAllWebFrames()) {
    if (frame->IsMainFrame()) {
      continue;
    }
    [self processFrame:frame inWebState:webState];
  }
}

- (void)processFrame:(web::WebFrame*)frame inWebState:(web::WebState*)webState {
  if (!frame) {
    return;
  }

  autofill::AutofillDriverIOS* driver =
      autofill::AutofillDriverIOS::FromWebStateAndWebFrame(webState, frame);
  DLOG_IF(WARNING, !driver) << "No AutofillDriverIOS found for WebFrame";
  // This process is only done once.
  if (!driver || driver->is_processed()) {
    return;
  }
  driver->set_processed(true);

  FormUtilJavaScriptFeature::GetInstance()->SetAutofillAcrossIframes(
      frame, base::FeatureList::IsEnabled(
                 autofill::features::kAutofillAcrossIframesIos));

  FormUtilJavaScriptFeature::GetInstance()->SetAutofillIsolatedContentWorld(
      frame,
      base::FeatureList::IsEnabled(kAutofillIsolatedWorldForJavascriptIos));

  if (frame->IsMainFrame()) {
    _suggestionDelegate.reset();
    _suggestionsAvailableCompletion = nil;
    _suggestionHandledCompletion = nil;
    _mostRecentSuggestions = nil;
    _typedValue = nil;
  }

  FormHandlersJavaScriptFeature* formHandlerFeature =
      FormHandlersJavaScriptFeature::GetInstance();

  // Use a delay of 200ms when tracking form mutations to reduce the
  // communication overhead (as mutations are likely to come in batch).
  constexpr int kMutationTrackingEnabledDelayInMs = 200;
  formHandlerFeature->TrackFormMutations(frame,
                                         kMutationTrackingEnabledDelayInMs);

  formHandlerFeature->ToggleTrackingUserEditedFields(
      frame,
      /*track_user_edited_fields=*/true);

  [self scanFormsInWebState:webState inFrame:frame];
}

// Records if the renderer was able to fill the Autofill-provided values in a
// form or formless fields.
- (void)recordFormFillingSuccessMetrics:(BOOL)success {
  base::UmaHistogramBoolean(/*name=*/"Autofill.FormFillSuccessIOS",
                            /*sample=*/success);

  ukm::SourceId source_id = ukm::GetSourceIdForWebStateDocument(_webState);
  ukm::builders::Autofill_FormFillSuccessIOS(source_id)
      .SetFormFillSuccess(success)
      .Record(ukm::UkmRecorder::Get());
}

@end