// 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 <UIKit/UIKit.h>

#import <memory>
#import <vector>

#import "base/apple/foundation_util.h"
#import "base/ios/ios_util.h"
#import "base/memory/ptr_util.h"
#import "base/strings/utf_string_conversions.h"
#import "base/task/thread_pool/thread_pool_instance.h"
#import "base/test/ios/wait_util.h"
#import "base/test/metrics/histogram_tester.h"
#import "base/test/scoped_feature_list.h"
#import "base/test/test_future.h"
#import "base/types/id_type.h"
#import "base/uuid.h"
#import "components/autofill/core/browser/data_manager/addresses/address_data_manager.h"
#import "components/autofill/core/browser/data_manager/addresses/address_data_manager_test_api.h"
#import "components/autofill/core/browser/data_manager/payments/payments_data_manager.h"
#import "components/autofill/core/browser/data_manager/personal_data_manager.h"
#import "components/autofill/core/browser/data_manager/personal_data_manager_test_utils.h"
#import "components/autofill/core/browser/data_model/addresses/autofill_i18n_api.h"
#import "components/autofill/core/browser/form_structure.h"
#import "components/autofill/core/browser/foundations/autofill_manager_test_api.h"
#import "components/autofill/core/browser/foundations/browser_autofill_manager.h"
#import "components/autofill/core/browser/foundations/test_autofill_manager_waiter.h"
#import "components/autofill/core/browser/geo/alternative_state_name_map_updater.h"
#import "components/autofill/core/browser/metrics/autofill_metrics.h"
#import "components/autofill/core/browser/webdata/autocomplete/autocomplete_entry.h"
#import "components/autofill/core/common/autofill_clock.h"
#import "components/autofill/core/common/autofill_features.h"
#import "components/autofill/core/common/autofill_test_utils.h"
#import "components/autofill/core/common/field_data_manager.h"
#import "components/autofill/core/common/form_data.h"
#import "components/autofill/core/common/mojom/autofill_types.mojom-shared.h"
#import "components/autofill/core/common/unique_ids.h"
#import "components/autofill/ios/browser/autofill_agent.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_java_script_feature.h"
#import "components/autofill/ios/browser/form_suggestion.h"
#import "components/autofill/ios/browser/test_autofill_client_ios.h"
#import "components/autofill/ios/browser/test_autofill_manager_injector.h"
#import "components/autofill/ios/common/field_data_manager_factory_ios.h"
#import "components/autofill/ios/form_util/form_activity_tab_helper.h"
#import "components/infobars/core/confirm_infobar_delegate.h"
#import "components/infobars/core/infobar.h"
#import "components/infobars/core/infobar_manager.h"
#import "components/keyed_service/core/service_access_type.h"
#import "components/password_manager/core/browser/password_manager_test_utils.h"
#import "components/password_manager/core/browser/password_store/mock_password_store_interface.h"
#import "ios/chrome/browser/autofill/form_input_accessory/coordinator/form_input_accessory_mediator.h"
#import "ios/chrome/browser/autofill/model/bottom_sheet/autofill_bottom_sheet_tab_helper.h"
#import "ios/chrome/browser/autofill/model/bottom_sheet/save_card_bottom_sheet_model.h"
#import "ios/chrome/browser/autofill/model/features.h"
#import "ios/chrome/browser/autofill/model/form_suggestion_controller.h"
#import "ios/chrome/browser/autofill/model/personal_data_manager_factory.h"
#import "ios/chrome/browser/autofill/ui_bundled/chrome_autofill_client_ios.h"
#import "ios/chrome/browser/infobars/model/infobar_manager_impl.h"
#import "ios/chrome/browser/passwords/model/ios_chrome_profile_password_store_factory.h"
#import "ios/chrome/browser/passwords/model/password_controller.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/browser/shared/public/commands/autofill_commands.h"
#import "ios/chrome/browser/web/model/chrome_web_client.h"
#import "ios/chrome/browser/webdata_services/model/web_data_service_factory.h"
#import "ios/chrome/test/ios_chrome_scoped_testing_local_state.h"
#import "ios/web/public/js_messaging/web_frame.h"
#import "ios/web/public/js_messaging/web_frames_manager.h"
#import "ios/web/public/navigation/navigation_item.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web/public/test/scoped_testing_web_client.h"
#import "ios/web/public/test/task_observer_util.h"
#import "ios/web/public/test/web_state_test_util.h"
#import "ios/web/public/test/web_task_environment.h"
#import "ios/web/public/web_state.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/OCMock/OCMockMacros.h"

using base::test::ScopedFeatureList;

// Real FormSuggestionController is wrapped to register the addition of
// suggestions.
@interface TestSuggestionController : FormSuggestionController

@property(nonatomic, copy) NSArray* suggestions;
@property(nonatomic, assign) BOOL suggestionRetrievalComplete;
@property(nonatomic, assign) BOOL suggestionRetrievalStarted;

@end

@implementation TestSuggestionController

@synthesize suggestions = _suggestions;
@synthesize suggestionRetrievalComplete = _suggestionRetrievalComplete;

- (void)retrieveSuggestionsForForm:(const autofill::FormActivityParams&)params
                          webState:(web::WebState*)webState
          accessoryViewUpdateBlock:
              (FormSuggestionsReadyCompletion)accessoryViewUpdateBlock {
  self.suggestionRetrievalStarted = YES;

  __weak __typeof(self) weakSelf = self;
  FormSuggestionsReadyCompletion wrappedBlock =
      ^(NSArray<FormSuggestion*>* suggestions,
        id<FormInputSuggestionsProvider> provider) {
        // This is the key change: update the test's state regardless of whether
        // the controller is stateful or stateless.
        weakSelf.suggestions = suggestions;
        weakSelf.suggestionRetrievalComplete = YES;

        // Call the original completion block to ensure the mediator's logic
        // still runs.
        if (accessoryViewUpdateBlock) {
          accessoryViewUpdateBlock(suggestions, provider);
        }
      };

  [super retrieveSuggestionsForForm:params
                           webState:webState
           accessoryViewUpdateBlock:wrappedBlock];
}

// -updateKeyboardWithSuggestions: is only called in the stateful path.
// The new wrapped block above handles the stateless path.
- (void)updateKeyboardWithSuggestions:(NSArray*)suggestions {
  self.suggestions = suggestions;
  self.suggestionRetrievalComplete = YES;
}

// -onNoSuggestionsAvailable is only called in the stateful path.
- (void)onNoSuggestionsAvailable {
  self.suggestionRetrievalComplete = YES;
}

- (void)resetSuggestionAvailable {
  self.suggestionRetrievalComplete = NO;
  self.suggestionRetrievalStarted = NO;
  self.suggestions = nil;
}

@end

namespace autofill {

namespace {

// The profile-type form used by tests.
NSString* const kProfileFormHtml =
    @"<form action='/submit' method='post'>"
     "Name <input type='text' name='name'>"
     "Address <input type='text' name='address'>"
     "City <input type='text' name='city'>"
     "State <input type='text' name='state'>"
     "Zip <input type='text' name='zip'>"
     "<input type='submit' id='submit' value='Submit'>"
     "</form>";

// A minimal form with a name.
NSString* const kMinimalFormWithNameHtml = @"<form id='form1'>"
                                            "<input name='name'>"
                                            "<input name='address'>"
                                            "<input name='city'>"
                                            "</form>";

// The key/value-type form used by tests.
NSString* const kKeyValueFormHtml =
    @"<form action='/submit' method='post'>"
     "Greeting <input id='greeting' type='text' name='greeting'>"
     "Dummy field <input id='dummy' type='text' name='dummy'>"
     "<input type='submit' id='submit' value='Submit'>"
     "</form>";

// The credit card-type form used by tests.
NSString* const kCreditCardFormHtml =
    @"<form id='form' action='/submit' method='post'>"
     "Name on card: <input id='name' type='text' name='name'>"
     "Credit card number: <input id='CCNo' type='text' name='CCNo'>"
     "Expiry Month: <input id='CCExpiresMonth' type='text' "
     "name='CCExpiresMonth'>"
     "Expiry Year: <input id='CCExpiresYear' type='text' name='CCExpiresYear'>"
     "<input type='submit' id='submit' value='Submit'>"
     "</form>";

// An HTML page without a card-type form.
static NSString* kNoCreditCardFormHtml =
    @"<form><input type=\"text\" autofocus autocomplete=\"username\"></form>";

// A credit card-type form with the autofocus attribute (which is detected at
// page load).
NSString* const kCreditCardAutofocusFormHtml =
    @"<form><input type=\"text\" autofocus autocomplete=\"cc-number\"></form>";

// A profile-type formless form. The fields are not inside a form element.
NSString* const kProfileFormlessHtml =
    @"<div id='div'>"
     "Name <input id='name' type='text' name='name'>"
     "Address <input id='address' type='text' name='address'>"
     "City <input id='city' type='text' name='city'>"
     "State <input id='state' type='text' name='state'>"
     "Zip <input id='zip' type='text' name='zip'>"
     "<input type='submit' id='submit' value='Submit'>"
     "</div>";

using ::testing::AllOf;
using ::testing::AssertionFailure;
using ::testing::AssertionResult;
using ::testing::AssertionSuccess;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::IsEmpty;
using ::testing::IsTrue;
using ::testing::Property;
using ::testing::SizeIs;
using ::testing::UnorderedElementsAre;

// FAIL if a field with the supplied `name` and `fieldType` is not present on
// the `form`.
void CheckField(const FormStructure& form,
                FieldType fieldType,
                const char* name) {
  for (const auto& field : form) {
    if (field->heuristic_type() == fieldType) {
      EXPECT_EQ(base::UTF8ToUTF16(name), field->name());
      return;
    }
  }
  FAIL() << "Missing field " << name;
}

// Forces rendering of a UIView. This is used in tests to make sure that UIKit
// optimizations don't have the views return the previous values (such as
// zoomScale).
void ForceViewRendering(UIView* view) {
  EXPECT_TRUE(view);
  CALayer* layer = view.layer;
  EXPECT_TRUE(layer);
  const CGFloat kArbitraryNonZeroPositiveValue = 19;
  const CGSize arbitraryNonEmptyArea = CGSizeMake(
      kArbitraryNonZeroPositiveValue, kArbitraryNonZeroPositiveValue);
  UIGraphicsBeginImageContext(arbitraryNonEmptyArea);
  CGContext* context = UIGraphicsGetCurrentContext();
  EXPECT_TRUE(context);
  [layer renderInContext:context];
  UIGraphicsEndImageContext();
}

// Returns a matcher to verify a child frame in the FormData.
auto ChildFrameMatcher(int expected_predecessor) {
  const auto valid_token_matcher = ::testing::Field(
      &FrameTokenWithPredecessor::token,
      ::testing::VariantWith<RemoteFrameToken>(::testing::IsTrue()));
  const auto predecessor_matcher =
      ::testing::Field(&FrameTokenWithPredecessor::predecessor,
                       testing::Eq(expected_predecessor));
  return AllOf(valid_token_matcher, predecessor_matcher);
}

// Text fixture to test autofill.
class AutofillControllerTest : public PlatformTest {
 public:
  AutofillControllerTest() : web_client_(std::make_unique<ChromeWebClient>()) {
    TestProfileIOS::Builder builder;
    // TODO(crbug.com/533826510): Remove this override when
    // kAutofillEnableBottomSheetScanCardAndFill is cleaned up. These credit
    // card import tests need to be updated to support the save-and-fill flow.
    scoped_feature_list_2_.InitWithFeatures(
        /*enabled_features=*/{kStatelessFormSuggestionController},
        /*disabled_features=*/{
            autofill::features::kAutofillEnableBottomSheetScanCardAndFill});

    builder.AddTestingFactory(
        IOSChromeProfilePasswordStoreFactory::GetInstance(),
        base::BindOnce(
            &password_manager::BuildPasswordStoreInterface<
                ProfileIOS, password_manager::MockPasswordStoreInterface>));
    // Profile import requires a PersonalDataManager which itself needs the
    // WebDataService; this is not initialized on a TestProfileIOS by
    // default.
    builder.AddTestingFactory(ios::WebDataServiceFactory::GetInstance(),
                              ios::WebDataServiceFactory::GetDefaultFactory());
    profile_ = std::move(builder).Build();

    web::WebState::CreateParams params(profile_.get());
    web_state_ = web::WebState::Create(params);
    web_state_->GetView();
    web_state_->SetKeepRenderProcessAlive(true);
  }

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

  ~AutofillControllerTest() override {}

 protected:
  class TestAutofillManager : public BrowserAutofillManager {
   public:
    explicit TestAutofillManager(AutofillDriverIOS* driver)
        : BrowserAutofillManager(driver) {}

    TestAutofillManagerWaiter& waiter() { return waiter_; }

   private:
    TestAutofillManagerWaiter waiter_{*this,
                                      {AutofillManagerEvent::kFormsSeen}};
  };

  void SetUp() override;
  void TearDown() override;

  void SetUpForSuggestions(NSString* data, size_t expected_number_of_forms);

  // Adds key value data to the Personal Data Manager and loads test page.
  void SetUpKeyValueData();

  // Blocks until suggestion retrieval has completed.
  // If `wait_for_trigger` is yes, wait for the call to
  // `retrieveSuggestionsForForm` to avoid considering a former call.
  void WaitForSuggestionRetrieval(BOOL wait_for_trigger);
  void ResetWaitForSuggestionRetrieval();

  // Loads the page and wait until the initial form processing has been done.
  // This processing must find `expected_size` forms.
  [[nodiscard]] bool LoadHtmlAndWaitForFormFetched(
      NSString* html,
      size_t expected_number_of_forms,
      size_t expected_number_of_calls = 1);

  void LoadHtmlAndInitRendererIds(NSString* html);

  // Fails if the specified metric was not registered the given number of times.
  void ExpectMetric(const std::string& histogram_name, int sum);

  TestSuggestionController* suggestion_controller() {
    return suggestion_controller_;
  }

  void WaitForCondition(ConditionBlock condition);

  // Focuses the field with 'field_id' and dispatches a focus event.
  void FocusElement(NSString* field_id);

  // Simulates a text input event by focusing the field with 'field_id' and
  // dispatching a TextEvent with value 'field_value'.
  void SimulateTextInputEvent(NSString* field_id, NSString* field_value);

  void LoadAndFillCreditCardForm();

  // Returns the AutofillManager for the main frame.
  BrowserAutofillManager* autofill_manager_for_main_frame() {
    web::WebFramesManager* frames_manager =
        AutofillJavaScriptFeature::GetInstance()->GetWebFramesManager(
            web_state());
    web::WebFrame* main_frame = frames_manager->GetMainWebFrame();
    return &AutofillDriverIOS::FromWebStateAndWebFrame(web_state(), main_frame)
                ->GetAutofillManager();
  }

  PrefService* local_state() {
    return GetApplicationContext()->GetLocalState();
  }

 protected:
  web::WebState* web_state() { return web_state_.get(); }

  web::ScopedTestingWebClient web_client_;
  web::WebTaskEnvironment task_environment_;
  test::AutofillUnitTestEnvironment autofill_test_environment_{
      {.disable_server_communication = true}};
  IOSChromeScopedTestingLocalState scoped_testing_local_state_;
  std::unique_ptr<TestProfileIOS> profile_;
  std::unique_ptr<web::WebState> web_state_;
  bool processed_a_task_ = false;
  // Histogram tester for these tests.
  std::unique_ptr<base::HistogramTester> histogram_tester_;
  raw_ptr<AutofillBottomSheetTabHelper> bottomsheet_tab_helper_;
  id<AutofillCommands> autofill_commands_handler_;
  ScopedFeatureList scoped_feature_list_2_;

 private:
  std::unique_ptr<AutofillClient> autofill_client_;

  AutofillAgent* autofill_agent_;

  std::unique_ptr<TestAutofillManagerInjector<TestAutofillManager>>
      autofill_manager_injector_;

  // Retrieves suggestions according to form events.
  TestSuggestionController* suggestion_controller_;

  // Retrieves accessory views according to form events.
  FormInputAccessoryMediator* accessory_mediator_;

  PasswordController* passwordController_;
};

void AutofillControllerTest::SetUp() {
  PlatformTest::SetUp();

  // Create a PasswordController instance that will handle set up for renderer
  // ids.
  passwordController_ =
      [[PasswordController alloc] initWithWebState:web_state()];

  autofill_agent_ =
      [[AutofillAgent alloc] initWithPrefService:profile_->GetPrefs()
                                        webState:web_state()];
  suggestion_controller_ =
      [[TestSuggestionController alloc] initWithWebState:web_state()
                                               providers:@[ autofill_agent_ ]];

  InfoBarManagerImpl::CreateForWebState(web_state());
  infobars::InfoBarManager* infobar_manager =
      InfoBarManagerImpl::FromWebState(web_state());
  autofill_client_ =
      std::make_unique<WithFakedFromWebState<ChromeAutofillClientIOS>>(
          profile_.get(), web_state(), infobar_manager, autofill_agent_);

  autofill_manager_injector_ =
      std::make_unique<TestAutofillManagerInjector<TestAutofillManager>>(
          web_state());

  accessory_mediator_ =
      [[FormInputAccessoryMediator alloc] initWithConsumer:nil
                                                   handler:nil
                                              webStateList:nullptr
                                       personalDataManager:nullptr
                                      profilePasswordStore:nullptr
                                      accountPasswordStore:nullptr
                                      securityAlertHandler:nil
                                    reauthenticationModule:nil
                                         engagementTracker:nil];

  [accessory_mediator_ injectWebState:web_state()];
  [accessory_mediator_ injectProvider:suggestion_controller_];

  AutofillBottomSheetTabHelper::CreateForWebState(web_state());
  bottomsheet_tab_helper_ =
      AutofillBottomSheetTabHelper::FromWebState(web_state_.get());
  autofill_commands_handler_ = OCMProtocolMock(@protocol(AutofillCommands));
  bottomsheet_tab_helper_->SetAutofillBottomSheetHandler(
      autofill_commands_handler_);

  autofill::FormActivityTabHelper::GetOrCreateForWebState(web_state())
      ->SetForceSubmittedByUserForTesting(true);

  histogram_tester_ = std::make_unique<base::HistogramTester>();
}

void AutofillControllerTest::TearDown() {
  @autoreleasepool {
    [accessory_mediator_ disconnect];
    [suggestion_controller_ detachFromWebState];

    autofill_manager_injector_.reset();

    bottomsheet_tab_helper_ = nullptr;
    autofill_agent_ = nil;
  }

  web::test::WaitForBackgroundTasks();
  web_state_.reset();
}

void AutofillControllerTest::ResetWaitForSuggestionRetrieval() {
  [suggestion_controller() resetSuggestionAvailable];
}

void AutofillControllerTest::WaitForSuggestionRetrieval(BOOL wait_for_trigger) {
  // Wait for the message queue to ensure that JS events fired in the tests
  // trigger TestSuggestionController's retrieveSuggestionsForFormNamed: method
  // and set suggestionRetrievalComplete to NO.
  if (wait_for_trigger) {
    WaitForCondition(^bool {
      return suggestion_controller().suggestionRetrievalStarted;
    });
  }
  // Now we can wait for suggestionRetrievalComplete to be set to YES.
  WaitForCondition(^bool {
    return suggestion_controller().suggestionRetrievalComplete;
  });
}

bool AutofillControllerTest::LoadHtmlAndWaitForFormFetched(
    NSString* html,
    size_t expected_number_of_forms,
    size_t expected_number_of_calls) {
  web::test::LoadHtml(html, web_state());
  TestAutofillManager* autofill_manager =
      autofill_manager_injector_->GetForMainFrame();
  return autofill_manager->waiter().Wait(expected_number_of_calls) &&
         test_api(*autofill_manager).form_structures().size() ==
             expected_number_of_forms;
}

void AutofillControllerTest::ExpectMetric(const std::string& histogram_name,
                                          int sum) {
  histogram_tester_->ExpectBucketCount(histogram_name, sum, 1);
}

void AutofillControllerTest::WaitForCondition(ConditionBlock condition) {
  ASSERT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(base::Seconds(1000),
                                                           true, condition));
}

void AutofillControllerTest::FocusElement(NSString* field_id) {
  web::test::ExecuteJavaScript(
      [NSString
          stringWithFormat:
              @"var el = document.getElementById('%@') || "
              @"document.forms[0]['%@'];"
              @"if (el) { el.focus(); el.dispatchEvent(new Event('focus', "
              @"{bubbles: true})); }",
              field_id, field_id],
      web_state());
}

void AutofillControllerTest::SimulateTextInputEvent(NSString* field_id,
                                                    NSString* field_value) {
  FocusElement(field_id);
  web::test::ExecuteJavaScript(
      [NSString
          stringWithFormat:
              @"var el = document.getElementById('%@') || "
              @"document.forms[0]['%@'];"
              @"var event = document.createEvent('TextEvent');"
              @"event.initTextEvent('textInput', true, true, window, '%@');"
              @"if (el) { el.dispatchEvent(event); }",
              field_id, field_id, field_value],
      web_state());
}

void AutofillControllerTest::LoadAndFillCreditCardForm() {
  ASSERT_TRUE(LoadHtmlAndWaitForFormFetched(kCreditCardFormHtml, 1));

  // Simulate entering a credit card in the form.
  SimulateTextInputEvent(/*field_id=*/@"name", /*field_value=*/@"Superman");
  SimulateTextInputEvent(/*field_id=*/@"CCNo",
                         /*field_value=*/@"4000-4444-4444-4444");
  SimulateTextInputEvent(/*field_id=*/@"CCExpiresMonth", /*field_value=*/@"11");
  SimulateTextInputEvent(/*field_id=*/@"CCExpiresYear",
                         /*field_value=*/@"2999");
}

// Checks that viewing an HTML page containing a form results in the form being
// registered as a FormStructure by the BrowserAutofillManager.
TEST_F(AutofillControllerTest, ReadForm) {
  ASSERT_TRUE(LoadHtmlAndWaitForFormFetched(kProfileFormHtml, 1));
  web::WebFramesManager* frames_manager =
      AutofillJavaScriptFeature::GetInstance()->GetWebFramesManager(
          web_state());
  web::WebFrame* main_frame = frames_manager->GetMainWebFrame();
  BrowserAutofillManager& autofill_manager =
      AutofillDriverIOS::FromWebStateAndWebFrame(web_state(), main_frame)
          ->GetAutofillManager();
  const FormStructure& form =
      *test_api(autofill_manager).form_structures().front();
  CheckField(form, NAME_FULL, "name");
  CheckField(form, ADDRESS_HOME_LINE1, "address");
  CheckField(form, ADDRESS_HOME_CITY, "city");
  CheckField(form, ADDRESS_HOME_STATE, "state");
  CheckField(form, ADDRESS_HOME_ZIP, "zip");
  ExpectMetric("Autofill.IsEnabled.PageLoad", 1);
}

// Checks that when autofill across iframes is enabled the child frames are
// carried over for their parent form.
TEST_F(AutofillControllerTest, ReadForm_WithChildFrames) {
  // A form with iframes and inputs where some of the iframes have predecessors.
  NSString* const test_page =
      @"<form id='form1'>"
       "<iframe></iframe>"
       "Name <input id='name' type='text' name='name' />"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "Address <input type='text' name='address'>"
       "City <input type='text' name='city'>"
       "<iframe></iframe>"
       "State <input type='text' name='state'>"
       "</form>";

  ASSERT_TRUE(LoadHtmlAndWaitForFormFetched(test_page,
                                            /*expected_number_of_forms=*/1,
                                            /*expected_number_of_calls=*/5));

  // Verify that the child frames are present in the form data.
  std::vector<FormData> form_data = base::ToVector(
      test_api(*autofill_manager_for_main_frame()).form_structures(),
      &FormStructure::ToFormData);
  EXPECT_THAT(
      form_data,
      ElementsAre(AllOf(
          Property(&FormData::renderer_id, IsTrue()),
          Property(&FormData::child_frames,
                   ElementsAre(ChildFrameMatcher(-1), ChildFrameMatcher(0),
                               ChildFrameMatcher(0), ChildFrameMatcher(2))))));
}

// Checks that when autofill across iframes is enabled the child frames are
// carried over for their synthetic form.
TEST_F(AutofillControllerTest, ReadForm_WithChildFrames_Synthetic) {
  // A syntethic form with iframes and inputs where some of the iframes have
  // predecessors.
  NSString* const test_page =
      @"<html><body><div id='div'>"
       "<iframe></iframe>"
       "Name <input id='name' type='text' name='name' />"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "Address <input type='text' name='address'>"
       "City <input type='text' name='city'>"
       "<iframe></iframe>"
       "State <input type='text' name='state'>"
       "</div></html></body>";

  ASSERT_TRUE(LoadHtmlAndWaitForFormFetched(test_page,
                                            /*expected_number_of_forms=*/1,
                                            /*expected_number_of_calls=*/3));

  // Verify that the child frames are present in the form data.
  std::vector<FormData> form_data = base::ToVector(
      test_api(*autofill_manager_for_main_frame()).form_structures(),
      &FormStructure::ToFormData);
  EXPECT_THAT(
      form_data,
      ElementsAre(AllOf(
          Property(&FormData::renderer_id, ::testing::IsFalse()),
          Property(&FormData::child_frames,
                   ElementsAre(ChildFrameMatcher(-1), ChildFrameMatcher(0),
                               ChildFrameMatcher(0), ChildFrameMatcher(2))))));
}

// Checks that with autofill across iframes and throttling enabled, the child
// frames will stop being extracted for forms once the limit of frames is
// reached.
TEST_F(AutofillControllerTest,
       ReadForm_WithChildFrames_Throttling_AcrossForms) {
  // A form with iframes and inputs where some of the iframes have predecessors.
  NSString* const test_page =
      @"<form id='form1'>"
       "<!-- 20 frames, just a the limit -->"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "Name <input id='name' type='text' name='name' />"
       "Address <input type='text' name='address'>"
       "City <input type='text' name='city'>"
       "State <input type='text' name='state'>"
       "</form>"
       "<form id='form2'>"
       "<!-- Frame limit busted -->"
       "<iframe></iframe>"
       "Name <input id='name' type='text' name='name' />"
       "Address <input type='text' name='address'>"
       "City <input type='text' name='city'>"
       "State <input type='text' name='state'>"
       "</form>"
       "<form id='form3'>"
       "<!-- Frame limit busted -->"
       "<iframe></iframe>"
       "Name <input id='name' type='text' name='name' />"
       "Address <input type='text' name='address'>"
       "City <input type='text' name='city'>"
       "State <input type='text' name='state'>"
       "</form>";

  ASSERT_TRUE(LoadHtmlAndWaitForFormFetched(test_page,
                                            /*expected_number_of_forms=*/3));

  // Verify that the form data is correctly filled with the child frames data
  // by respecting the child frames limit, where the first form has its 20 child
  // frames then the follow up forms don't have any child frames.
  std::vector<FormData> form_data = base::ToVector(
      test_api(*autofill_manager_for_main_frame()).form_structures(),
      &FormStructure::ToFormData);
  auto form1_matcher = AllOf(Property(&FormData::renderer_id, IsTrue()),
                             Property(&FormData::child_frames, SizeIs(20)));
  auto following_forms_matcher =
      AllOf(Property(&FormData::renderer_id, IsTrue()),
            Property(&FormData::child_frames, IsEmpty()));
  EXPECT_THAT(form_data, ElementsAre(form1_matcher, following_forms_matcher,
                                     following_forms_matcher));
}

// Checks that with autofill across iframes and throttling enabled, the child
// frames won't be extracted for the syntethic forms once the limit of frames is
// reached.
TEST_F(AutofillControllerTest,
       ReadForm_WithChildFrames_Throttling_AcrossForms_Synthetic) {
  // A form with iframes and inputs where some of the iframes have predecessors.
  NSString* const test_page =
      @"<form id='form1'>"
       "<!-- 4 iframes, below the per-form limit -->"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "Name <input id='name' type='text' name='name' />"
       "Address <input type='text' name='address'>"
       "City <input type='text' name='city'>"
       "State <input type='text' name='state'>"
       "</form>"
       "<!-- 17 frames in the synthetic form, just above the xform limit -->"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "Name <input id='name' type='text' name='name' />"
       "Address <input type='text' name='address'>"
       "City <input type='text' name='city'>"
       "State <input type='text' name='state'>";

  ASSERT_TRUE(LoadHtmlAndWaitForFormFetched(test_page,
                                            /*expected_number_of_forms=*/2));

  // Verify that the form data is correctly filled with the child frames data
  // by respecting the child frames limit, where the first form has its 4 child
  // frames then the follow up synthetic form hasn't any child frame because it
  // busted the xform limit.
  std::vector<FormData> form_data = base::ToVector(
      test_api(*autofill_manager_for_main_frame()).form_structures(),
      &FormStructure::ToFormData);
  auto form1_matcher = AllOf(Property(&FormData::renderer_id, IsTrue()),
                             Property(&FormData::child_frames, SizeIs(4)));
  auto synthetic_form_matcher =
      AllOf(Property(&FormData::renderer_id, testing::Eq(FormRendererId(0))),
            Property(&FormData::child_frames, IsEmpty()));
  EXPECT_THAT(form_data, ElementsAre(synthetic_form_matcher, form1_matcher));
}

// Checks that with autofill across iframes and throttling enabled, the child
// frames will not be extracted on a form that exceeds the limit of child
// frames.
TEST_F(AutofillControllerTest, ReadForm_WithChildFrames_Throttling_SingleForm) {
  // A form with iframes and inputs where some of the iframes have predecessors.
  NSString* const test_page =
      @"<form id='form1'>"
       "<!-- 21 frames, just above the limit -->"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "Name <input id='name' type='text' name='name' />"
       "Address <input type='text' name='address'>"
       "City <input type='text' name='city'>"
       "State <input type='text' name='state'>"
       "</form>";

  ASSERT_TRUE(LoadHtmlAndWaitForFormFetched(test_page,
                                            /*expected_number_of_forms=*/1));

  // Verify that the form data doesn't have child frames when the form exceeds
  // the child frame limit.
  std::vector<FormData> form_data = base::ToVector(
      test_api(*autofill_manager_for_main_frame()).form_structures(),
      &FormStructure::ToFormData);
  auto form_matcher = AllOf(Property(&FormData::renderer_id, IsTrue()),
                            Property(&FormData::child_frames, IsEmpty()));
  EXPECT_THAT(form_data, ElementsAre(form_matcher));
}

// Checks that with autofill across iframes and throttling enabled, the child
// frames will not be extracted on a synthetic form that exceeds the limit of
// child frames.
TEST_F(AutofillControllerTest,
       ReadForm_WithChildFrames_Throttling_SingleForm_Synthetic) {
  // A synthetic form with too many child frames exceeding the limit.
  NSString* const test_page =
      @"<html><body><div id='div'>"
       "<!-- 21 frames in synthetic form, just above the limit -->"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "<iframe></iframe>"
       "Name <input id='name' type='text' name='name' />"
       "Address <input type='text' name='address'>"
       "City <input type='text' name='city'>"
       "State <input type='text' name='state'>"
       "</div></html></body>";

  ASSERT_TRUE(LoadHtmlAndWaitForFormFetched(test_page,
                                            /*expected_number_of_forms=*/1));

  // Verify that the synthetic form data doesn't have child frames when the form
  // exceeds the child frame limit.
  std::vector<FormData> form_data = base::ToVector(
      test_api(*autofill_manager_for_main_frame()).form_structures(),
      &FormStructure::ToFormData);
  auto form_matcher =
      AllOf(Property(&FormData::renderer_id, Eq(FormRendererId(0))),
            Property(&FormData::child_frames, IsEmpty()));
  EXPECT_THAT(form_data, UnorderedElementsAre(form_matcher));
}

// Checks that viewing an HTML page containing a form with an 'id' results in
// the form being registered as a FormStructure by the BrowserAutofillManager,
// and the name is correctly set.
TEST_F(AutofillControllerTest, ReadFormName) {
  ASSERT_TRUE(LoadHtmlAndWaitForFormFetched(kMinimalFormWithNameHtml, 1));
  web::WebFramesManager* frames_manager =
      AutofillJavaScriptFeature::GetInstance()->GetWebFramesManager(
          web_state());
  web::WebFrame* main_frame = frames_manager->GetMainWebFrame();
  BrowserAutofillManager& autofill_manager =
      AutofillDriverIOS::FromWebStateAndWebFrame(web_state(), main_frame)
          ->GetAutofillManager();
  const FormStructure& form =
      *test_api(autofill_manager).form_structures().front();
  EXPECT_EQ(u"form1", form.ToFormData().name());
}

// Checks that an HTML page containing a profile-type form which is submitted
// with scripts (simulating user form submission) results in a profile being
// successfully imported into the PersonalDataManager.
TEST_F(AutofillControllerTest, ProfileImport) {
  PersonalDataManager* personal_data_manager =
      PersonalDataManagerFactory::GetForProfile(profile_.get());
  test_api(personal_data_manager->address_data_manager())
      .set_auto_accept_address_imports(true);
  // Check there are no registered profiles already.
  EXPECT_EQ(0U,
            personal_data_manager->address_data_manager().GetProfiles().size());
  ASSERT_TRUE(LoadHtmlAndWaitForFormFetched(kProfileFormHtml, 1));
  web::test::ExecuteJavaScript(
      @"document.forms[0].name.value = 'Homer Simpson'", web_state());
  web::test::ExecuteJavaScript(
      @"document.forms[0].address.value = '123 Main Street'", web_state());
  web::test::ExecuteJavaScript(@"document.forms[0].city.value = 'Springfield'",
                               web_state());
  web::test::ExecuteJavaScript(@"document.forms[0].state.value = 'IL'",
                               web_state());
  web::test::ExecuteJavaScript(@"document.forms[0].zip.value = '55123'",
                               web_state());
  web::test::ExecuteJavaScript(@"submit.click()", web_state());
  WaitForCondition(^bool {
    return personal_data_manager->address_data_manager().GetProfiles().size();
  });
  const std::vector<const AutofillProfile*>& profiles =
      personal_data_manager->address_data_manager().GetProfiles();
  if (profiles.size() != 1) {
    FAIL() << "Not exactly one profile found after attempted import";
  }
  const AutofillProfile& profile = *profiles[0];
  EXPECT_EQ(u"Homer Simpson", profile.GetInfo(NAME_FULL, "en-US"));
  EXPECT_EQ(u"123 Main Street", profile.GetInfo(ADDRESS_HOME_LINE1, "en-US"));
  EXPECT_EQ(u"Springfield", profile.GetInfo(ADDRESS_HOME_CITY, "en-US"));
  EXPECT_EQ(u"IL", profile.GetInfo(ADDRESS_HOME_STATE, "en-US"));
  EXPECT_EQ(u"55123", profile.GetInfo(ADDRESS_HOME_ZIP, "en-US"));
}

void AutofillControllerTest::SetUpForSuggestions(
    NSString* data,
    size_t expected_number_of_forms) {
  PersonalDataManager* personal_data_manager =
      PersonalDataManagerFactory::GetForProfile(profile_.get());
  AutofillProfile profile(i18n_model_definition::kLegacyHierarchyCountryCode);
  profile.SetRawInfo(NAME_FULL, u"Homer Simpson");
  profile.SetRawInfo(ADDRESS_HOME_LINE1, u"123 Main Street");
  profile.SetRawInfo(ADDRESS_HOME_CITY, u"Springfield");
  profile.SetRawInfo(ADDRESS_HOME_STATE, u"IL");
  profile.SetRawInfo(ADDRESS_HOME_ZIP, u"55123");
  EXPECT_EQ(0U,
            personal_data_manager->address_data_manager().GetProfiles().size());
  PersonalDataChangedWaiter waiter(*personal_data_manager);
  personal_data_manager->address_data_manager().AddProfile(profile);
  std::move(waiter).Wait();
  EXPECT_EQ(1U,
            personal_data_manager->address_data_manager().GetProfiles().size());

  ASSERT_TRUE(LoadHtmlAndWaitForFormFetched(data, expected_number_of_forms));
  web::test::WaitForBackgroundTasks();
}

// Checks that focusing on a text element of a profile-type form will result in
// suggestions being sent to the AutofillAgent, once data has been loaded into a
// test data manager.
TEST_F(AutofillControllerTest, ProfileSuggestions) {
  if (@available(iOS 16.3, *)) {
    // TODO(crbug.com/40064372): Re-enable when fixed on iOS16.3+.
    return;
  }

  SetUpForSuggestions(kProfileFormHtml, 1);
  ForceViewRendering(web_state()->GetView());
  ResetWaitForSuggestionRetrieval();
  web::test::ExecuteJavaScript(@"document.forms[0].name.focus()", web_state());
  WaitForSuggestionRetrieval(/*wait_for_trigger=*/YES);
  ExpectMetric("Autofill.SuggestionsCount.Address", 1);
  EXPECT_EQ(1U, [suggestion_controller() suggestions].count);
  FormSuggestion* suggestion = [suggestion_controller() suggestions][0];
  EXPECT_NSEQ(@"Homer Simpson", suggestion.value);
}

// Tests that the system is able to offer suggestions for an anonymous form when
// there is another anonymous form on the page.
TEST_F(AutofillControllerTest, ProfileSuggestionsTwoAnonymousForms) {
  if (@available(iOS 16.3, *)) {
    // TODO(crbug.com/40064372): Re-enable when fixed on iOS16.3+.
    return;
  }

  SetUpForSuggestions(
      [NSString stringWithFormat:@"%@%@", kProfileFormHtml, kProfileFormHtml],
      2);
  ForceViewRendering(web_state()->GetView());
  ResetWaitForSuggestionRetrieval();
  web::test::ExecuteJavaScript(@"document.forms[0].name.focus()", web_state());
  WaitForSuggestionRetrieval(/*wait_for_trigger=*/YES);
  ExpectMetric("Autofill.SuggestionsCount.Address", 1);
  EXPECT_EQ(1U, [suggestion_controller() suggestions].count);
  FormSuggestion* suggestion = [suggestion_controller() suggestions][0];
  EXPECT_NSEQ(@"Homer Simpson", suggestion.value);
}

// Checks that focusing on a select element in a profile-type form will result
// in suggestions being sent to the AutofillAgent, once data has been loaded
// into a test data manager.
TEST_F(AutofillControllerTest, ProfileSuggestionsFromSelectField) {
  if (@available(iOS 16.3, *)) {
    // TODO(crbug.com/40064372): Re-enable when fixed on iOS16.3+.
    return;
  }

  SetUpForSuggestions(kProfileFormHtml, 1);
  ForceViewRendering(web_state()->GetView());
  ResetWaitForSuggestionRetrieval();
  web::test::ExecuteJavaScript(@"document.forms[0].state.focus()", web_state());
  WaitForSuggestionRetrieval(/*wait_for_trigger=*/YES);
  ExpectMetric("Autofill.SuggestionsCount.Address", 1);
  EXPECT_EQ(1U, [suggestion_controller() suggestions].count);
  FormSuggestion* suggestion = [suggestion_controller() suggestions][0];
  EXPECT_NSEQ(@"IL", suggestion.value);
}

// Checks that multiple profiles will offer a matching number of suggestions.
TEST_F(AutofillControllerTest, MultipleProfileSuggestions) {
  if (@available(iOS 16.3, *)) {
    // TODO(crbug.com/40064372): Re-enable when fixed on iOS16.3+.
    return;
  }

  PersonalDataManager* personal_data_manager =
      PersonalDataManagerFactory::GetForProfile(profile_.get());
  personal_data_manager->SetSyncServiceForTest(nullptr);

  AutofillProfile profile(i18n_model_definition::kLegacyHierarchyCountryCode);
  profile.SetRawInfo(NAME_FULL, u"Homer Simpson");
  profile.SetRawInfo(ADDRESS_HOME_LINE1, u"123 Main Street");
  profile.SetRawInfo(ADDRESS_HOME_CITY, u"Springfield");
  profile.SetRawInfo(ADDRESS_HOME_STATE, u"IL");
  profile.SetRawInfo(ADDRESS_HOME_ZIP, u"55123");

  AutofillProfile profile2(i18n_model_definition::kLegacyHierarchyCountryCode);
  profile2.SetRawInfo(NAME_FULL, u"Larry Page");
  profile2.SetRawInfo(ADDRESS_HOME_LINE1, u"1600 Amphitheatre Parkway");
  profile2.SetRawInfo(ADDRESS_HOME_CITY, u"Mountain View");
  profile2.SetRawInfo(ADDRESS_HOME_STATE, u"CA");
  profile2.SetRawInfo(ADDRESS_HOME_ZIP, u"94043");

  EXPECT_EQ(0U,
            personal_data_manager->address_data_manager().GetProfiles().size());
  PersonalDataChangedWaiter waiter(*personal_data_manager);
  personal_data_manager->address_data_manager().AddProfile(profile);
  personal_data_manager->address_data_manager().AddProfile(profile2);
  std::move(waiter).Wait();
  EXPECT_EQ(2U,
            personal_data_manager->address_data_manager().GetProfiles().size());

  EXPECT_TRUE(LoadHtmlAndWaitForFormFetched(kProfileFormHtml, 1));
  ForceViewRendering(web_state()->GetView());
  ResetWaitForSuggestionRetrieval();
  web::test::ExecuteJavaScript(@"document.forms[0].name.focus()", web_state());
  WaitForSuggestionRetrieval(/*wait_for_trigger=*/YES);
  ExpectMetric("Autofill.SuggestionsCount.Address", 2);
  EXPECT_EQ(2U, [suggestion_controller() suggestions].count);
}

// Check that an HTML page containing a key/value type form which is submitted
// with scripts (simulating user form submission) results in data being
// successfully registered.
TEST_F(AutofillControllerTest, KeyValueImport) {
  ASSERT_TRUE(LoadHtmlAndWaitForFormFetched(kKeyValueFormHtml, 1));
  web::test::ExecuteJavaScript(@"document.forms[0].greeting.value = 'Hello'",
                               web_state());
  scoped_refptr<AutofillWebDataService> web_data_service =
      ios::WebDataServiceFactory::GetAutofillWebDataForProfile(
          profile_.get(), ServiceAccessType::EXPLICIT_ACCESS);

  {
    base::test::TestFuture<WebDataServiceBase::Handle,
                           std::unique_ptr<WDTypedResult>>
        future;
    web_data_service->GetFormValuesForElementName(u"greeting", std::u16string(),
                                                  /*limit=*/1,
                                                  future.GetCallback());
    base::ThreadPoolInstance::Get()->FlushForTesting();
    web::test::WaitForBackgroundTasks();
    std::vector<AutocompleteEntry> result =
        static_cast<WDResult<std::vector<AutocompleteEntry>>&>(*future.Get<1>())
            .GetValue();
    // No value should be returned before anything is loaded via form
    // submission.
    EXPECT_THAT(result, IsEmpty());
  }

  web::test::ExecuteJavaScript(@"submit.click()", web_state());

  {
    base::test::TestFuture<WebDataServiceBase::Handle,
                           std::unique_ptr<WDTypedResult>>
        future;
    web_data_service->GetFormValuesForElementName(u"greeting", std::u16string(),
                                                  /*limit=*/1,
                                                  future.GetCallback());
    base::ThreadPoolInstance::Get()->FlushForTesting();
    web::test::WaitForBackgroundTasks();
    std::vector<AutocompleteEntry> result =
        static_cast<WDResult<std::vector<AutocompleteEntry>>&>(*future.Get<1>())
            .GetValue();
    // One result should be returned, matching the filled value.
    EXPECT_THAT(result, ElementsAre(Property(
                            &AutocompleteEntry::key,
                            Property(&AutocompleteKey::value, u"Hello"))));
  }
}

void AutofillControllerTest::SetUpKeyValueData() {
  scoped_refptr<AutofillWebDataService> web_data_service =
      ios::WebDataServiceFactory::GetAutofillWebDataForProfile(
          profile_.get(), ServiceAccessType::EXPLICIT_ACCESS);
  // Load value into database.
  std::vector<FormFieldData> values;
  FormFieldData fieldData;
  fieldData.set_name(u"greeting");
  fieldData.set_value(u"Bonjour");
  values.push_back(fieldData);
  web_data_service->AddFormFields(values);

  // Load test page.
  ASSERT_TRUE(LoadHtmlAndWaitForFormFetched(kKeyValueFormHtml, 1));
  web::test::WaitForBackgroundTasks();
}

// Checks that focusing on an element of a key/value type form then typing the
// first letter of a suggestion will result in suggestions being sent to the
// AutofillAgent, once data has been loaded into a test data manager.
TEST_F(AutofillControllerTest, KeyValueSuggestions) {
  SetUpKeyValueData();
  ResetWaitForSuggestionRetrieval();
  // Focus element.
  web::test::ExecuteJavaScript(@"document.forms[0].greeting.value='B'",
                               web_state());
  FocusElement(@"greeting");
  WaitForSuggestionRetrieval(/*wait_for_trigger=*/YES);
  EXPECT_EQ(1U, [suggestion_controller() suggestions].count);
  FormSuggestion* suggestion = [suggestion_controller() suggestions][0];
  EXPECT_NSEQ(@"Bonjour", suggestion.value);
}

// Checks that typing events (simulated in script) result in suggestions. Note
// that the field is not explicitly focused before typing starts; this can
// happen in practice and should not result in a crash or incorrect behavior.
TEST_F(AutofillControllerTest, KeyValueTypedSuggestions) {
  SetUpKeyValueData();
  ResetWaitForSuggestionRetrieval();
  FocusElement(@"greeting");
  WaitForSuggestionRetrieval(/*wait_for_trigger=*/YES);
  ResetWaitForSuggestionRetrieval();
  SimulateTextInputEvent(/*field_id=*/@"greeting", /*field_value=*/@"B");

  WaitForSuggestionRetrieval(/*wait_for_trigger=*/YES);
  EXPECT_EQ(1U, [suggestion_controller() suggestions].count);
  FormSuggestion* suggestion = [suggestion_controller() suggestions][0];
  EXPECT_NSEQ(@"Bonjour", suggestion.value);
}

// Checks that focusing on and typing on one field, then changing focus before
// typing again, result in suggestions.
TEST_F(AutofillControllerTest, KeyValueFocusChange) {
  SetUpKeyValueData();

  // Focus the dummy field and confirm no suggestions are presented.
  ResetWaitForSuggestionRetrieval();
  FocusElement(@"dummy");
  WaitForSuggestionRetrieval(/*wait_for_trigger=*/YES);
  ASSERT_EQ(0U, [suggestion_controller() suggestions].count);
  ResetWaitForSuggestionRetrieval();

  // Enter 'B' in the dummy field and confirm no suggestions are presented.
  SimulateTextInputEvent(/*field_id=*/@"dummy", /*field_value=*/@"B");

  WaitForSuggestionRetrieval(/*wait_for_trigger=*/YES);
  ASSERT_EQ(0U, [suggestion_controller() suggestions].count);
  ResetWaitForSuggestionRetrieval();

  // Enter 'B' in the greeting field and confirm that one suggestion ("Bonjour")
  // is presented.
  FocusElement(@"greeting");
  WaitForSuggestionRetrieval(/*wait_for_trigger=*/YES);
  ResetWaitForSuggestionRetrieval();
  web::test::ExecuteJavaScript(
      @"var event = document.createEvent('TextEvent');", web_state());
  web::test::ExecuteJavaScript(
      @"event.initTextEvent('textInput', true, true, window, 'B');",
      web_state());
  web::test::ExecuteJavaScript(
      @"document.forms[0].greeting.dispatchEvent(event);", web_state());
  WaitForSuggestionRetrieval(/*wait_for_trigger=*/YES);

  ASSERT_EQ(1U, [suggestion_controller() suggestions].count);
  FormSuggestion* suggestion = [suggestion_controller() suggestions][0];
  EXPECT_NSEQ(@"Bonjour", suggestion.value);
}

// Checks that an HTML page containing a credit card-type form which is
// submitted with scripts (simulating user form submission) results in a credit
// card being successfully imported into the PersonalDataManager.
TEST_F(AutofillControllerTest, CreditCardImport) {
  PersonalDataManager* personal_data_manager =
      PersonalDataManagerFactory::GetForProfile(profile_.get());
  personal_data_manager->SetSyncServiceForTest(nullptr);

  // Check there are no registered profiles already.
  EXPECT_EQ(
      0U,
      personal_data_manager->payments_data_manager().GetCreditCards().size());

  LoadAndFillCreditCardForm();

  __block bool save_card_bottomsheet_shown = false;
  OCMStub([autofill_commands_handler_
              showSaveCardBottomSheetOnOriginWebState:web_state()])
      .andDo(^(NSInvocation* invocation) {
        save_card_bottomsheet_shown = true;
      });
  web::test::ExecuteJavaScript(@"submit.click()", web_state());
  WaitForCondition(^bool() {
    return save_card_bottomsheet_shown;
  });
  ASSERT_TRUE(save_card_bottomsheet_shown);

  {
    // This call cause a modification of the PersonalDataManager, so wait until
    // the asynchronous task completes in addition to waiting for the UI update.
    PersonalDataChangedWaiter waiter(*personal_data_manager);
    bottomsheet_tab_helper_->GetSaveCardBottomSheetModel()->OnAccepted();
    std::move(waiter).Wait();
  }

  const std::vector<const CreditCard*>& credit_cards =
      personal_data_manager->payments_data_manager().GetCreditCards();
  ASSERT_EQ(1U, credit_cards.size());
  const CreditCard& credit_card = *credit_cards[0];
  EXPECT_EQ(u"Superman", credit_card.GetInfo(CREDIT_CARD_NAME_FULL, "en-US"));
  EXPECT_EQ(u"4000444444444444",
            credit_card.GetInfo(CREDIT_CARD_NUMBER, "en-US"));
  EXPECT_EQ(u"11", credit_card.GetInfo(CREDIT_CARD_EXP_MONTH, "en-US"));
  EXPECT_EQ(u"2999",
            credit_card.GetInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR, "en-US"));

  histogram_tester_->ExpectUniqueSample(
      /*name=*/kAutofillSubmissionDetectionSourceHistogram,
      /*sample=*/mojom::SubmissionSource::FORM_SUBMISSION,
      /*expected_count=*/1);
}

// Checks that an HTML page containing a credit card-type form which is
// submitted with scripts (simulating form removal) results in a credit
// card being successfully imported into the PersonalDataManager.
TEST_F(AutofillControllerTest, CreditCardImportAfterFormRemoval) {
  PersonalDataManager* personal_data_manager =
      PersonalDataManagerFactory::GetForProfile(profile_.get());
  personal_data_manager->SetSyncServiceForTest(nullptr);

  // Check there are no registered profiles already.
  EXPECT_EQ(
      0U,
      personal_data_manager->payments_data_manager().GetCreditCards().size());

  LoadAndFillCreditCardForm();

  __block bool save_card_bottomsheet_shown = false;
  OCMStub([autofill_commands_handler_
              showSaveCardBottomSheetOnOriginWebState:web_state()])
      .andDo(^(NSInvocation* invocation) {
        save_card_bottomsheet_shown = true;
      });

  // Deleting the form should be detected as a submission because it had user
  // input. Adding a delay is necessary or the event above might not be
  // dispatched.
  web::test::ExecuteJavaScript(@"setTimeout(function(){"
                               @"   document.forms[0].remove();"
                               @"}, 30);",
                               web_state());
  WaitForCondition(^bool() {
    return save_card_bottomsheet_shown;
  });
  ASSERT_TRUE(save_card_bottomsheet_shown);

  {
    // This call cause a modification of the PersonalDataManager, so wait until
    // the asynchronous task completes in addition to waiting for the UI update.
    PersonalDataChangedWaiter waiter(*personal_data_manager);
    bottomsheet_tab_helper_->GetSaveCardBottomSheetModel()->OnAccepted();
    std::move(waiter).Wait();
  }

  const std::vector<const CreditCard*>& credit_cards =
      personal_data_manager->payments_data_manager().GetCreditCards();
  ASSERT_EQ(1U, credit_cards.size());
  const CreditCard& credit_card = *credit_cards[0];
  EXPECT_EQ(u"Superman", credit_card.GetInfo(CREDIT_CARD_NAME_FULL, "en-US"));
  EXPECT_EQ(u"4000444444444444",
            credit_card.GetInfo(CREDIT_CARD_NUMBER, "en-US"));
  EXPECT_EQ(u"11", credit_card.GetInfo(CREDIT_CARD_EXP_MONTH, "en-US"));
  EXPECT_EQ(u"2999",
            credit_card.GetInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR, "en-US"));

  histogram_tester_->ExpectUniqueSample(
      /*name=*/kAutofillSubmissionDetectionSourceHistogram,
      /*sample=*/mojom::SubmissionSource::XHR_SUCCEEDED,
      /*expected_count=*/1);
}

// Checks that an HTML page containing a credit card-type form which is
// submitted with scripts (simulating form removal) results in a credit
// card being successfully imported into the PersonalDataManager. The test
// verifies that the imported card includes the lastest known field values for
// the submitted form.
TEST_F(AutofillControllerTest,
       CreditCardImportWithFieldDataManagerValuesAfterFormRemoval) {
  PersonalDataManager* personal_data_manager =
      PersonalDataManagerFactory::GetForProfile(profile_.get());
  personal_data_manager->SetSyncServiceForTest(nullptr);

  // Check there are no registered profiles already.
  EXPECT_EQ(
      0U,
      personal_data_manager->payments_data_manager().GetCreditCards().size());

  LoadAndFillCreditCardForm();

  // Update the form fields in `FieldDataManager`.
  // When detecting a submission, the imported credit card should include the
  // latest values in `FieldDataManager`.
  auto* frames_manager =
      AutofillJavaScriptFeature::GetInstance()->GetWebFramesManager(
          web_state());
  auto* main_frame = frames_manager->GetMainWebFrame();
  auto* fieldDataManager = FieldDataManagerFactoryIOS::FromWebFrame(main_frame);
  // Name.
  fieldDataManager->UpdateFieldDataMap(FieldRendererId(2), u"Chuck",
                                       FieldPropertiesFlags::kAutofilled);
  // CCNo.
  fieldDataManager->UpdateFieldDataMap(FieldRendererId(3),
                                       u"5425-2334-3010-9903",
                                       FieldPropertiesFlags::kAutofilled);
  // CCExpiresMonth.
  fieldDataManager->UpdateFieldDataMap(FieldRendererId(4), u"12",
                                       FieldPropertiesFlags::kAutofilled);
  // CCExpiresYear.
  fieldDataManager->UpdateFieldDataMap(FieldRendererId(5), u"2998",
                                       FieldPropertiesFlags::kAutofilled);

  __block bool save_card_bottomsheet_shown = false;
  OCMStub([autofill_commands_handler_
              showSaveCardBottomSheetOnOriginWebState:web_state()])
      .andDo(^(NSInvocation* invocation) {
        save_card_bottomsheet_shown = true;
      });

  // Deleting the form should be detected as a submission because it had user
  // input. Adding a delay is necessary or the event above might not be
  // dispatched.
  web::test::ExecuteJavaScript(@"setTimeout(function(){"
                               @"   document.forms[0].remove();"
                               @"}, 30);",
                               web_state());

  WaitForCondition(^bool() {
    return save_card_bottomsheet_shown;
  });
  ASSERT_TRUE(save_card_bottomsheet_shown);

  {
    // This call cause a modification of the PersonalDataManager, so wait until
    // the asynchronous task completes in addition to waiting for the UI update.
    PersonalDataChangedWaiter waiter(*personal_data_manager);
    bottomsheet_tab_helper_->GetSaveCardBottomSheetModel()->OnAccepted();
    std::move(waiter).Wait();
  }

  const std::vector<const CreditCard*>& credit_cards =
      personal_data_manager->payments_data_manager().GetCreditCards();
  ASSERT_EQ(1U, credit_cards.size());
  const CreditCard& credit_card = *credit_cards[0];

  EXPECT_EQ(u"Chuck", credit_card.GetInfo(CREDIT_CARD_NAME_FULL, "en-US"));
  EXPECT_EQ(u"5425233430109903",
            credit_card.GetInfo(CREDIT_CARD_NUMBER, "en-US"));
  EXPECT_EQ(u"12", credit_card.GetInfo(CREDIT_CARD_EXP_MONTH, "en-US"));
  EXPECT_EQ(u"2998",
            credit_card.GetInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR, "en-US"));
}

// Checks that an HTML page containing a profile-type formless form which is
// submitted with scripts (simulating form removal) results in a profile being
// successfully imported into the PersonalDataManager.
TEST_F(AutofillControllerTest, ProfileImportAfterFormlessFormRemoval) {
  PersonalDataManager* personal_data_manager =
      PersonalDataManagerFactory::GetForProfile(profile_.get());
  test_api(personal_data_manager->address_data_manager())
      .set_auto_accept_address_imports(true);
  // Check there are no registered profiles already.
  EXPECT_EQ(0U,
            personal_data_manager->address_data_manager().GetProfiles().size());
  ASSERT_TRUE(LoadHtmlAndWaitForFormFetched(kProfileFormlessHtml, 1));

  // Simulate entering a profile in the fields.
  SimulateTextInputEvent(/*field_id=*/@"name",
                         /*field_value=*/@"Homer Simpson");
  SimulateTextInputEvent(/*field_id=*/@"address",
                         /*field_value=*/@"123 Main Street");
  SimulateTextInputEvent(/*field_id=*/@"city", /*field_value=*/@"Springfield");
  SimulateTextInputEvent(/*field_id=*/@"state", /*field_value=*/@"IL");
  SimulateTextInputEvent(/*field_id=*/@"zip", /*field_value=*/@"55123");

  // Deleting the form should be detected as a submission because it had user
  // input. Adding a delay is necessary or the event above might not be
  // dispatched.
  web::test::ExecuteJavaScript(@"setTimeout(function(){"
                               @"   document.getElementById('div').remove();"
                               @"}, 30);",
                               web_state());

  WaitForCondition(^bool {
    return personal_data_manager->address_data_manager().GetProfiles().size();
  });
  const std::vector<const AutofillProfile*>& profiles =
      personal_data_manager->address_data_manager().GetProfiles();
  if (profiles.size() != 1) {
    FAIL() << "Not exactly one profile found after attempted import";
  }
  const AutofillProfile& profile = *profiles[0];
  EXPECT_EQ(u"Homer Simpson", profile.GetInfo(NAME_FULL, "en-US"));
  EXPECT_EQ(u"123 Main Street", profile.GetInfo(ADDRESS_HOME_LINE1, "en-US"));
  EXPECT_EQ(u"Springfield", profile.GetInfo(ADDRESS_HOME_CITY, "en-US"));
  EXPECT_EQ(u"IL", profile.GetInfo(ADDRESS_HOME_STATE, "en-US"));
  EXPECT_EQ(u"55123", profile.GetInfo(ADDRESS_HOME_ZIP, "en-US"));

  histogram_tester_->ExpectUniqueSample(
      /*name=*/kAutofillSubmissionDetectionSourceHistogram,
      /*sample=*/mojom::SubmissionSource::XHR_SUCCEEDED,
      /*expected_count=*/1);
}

}  // namespace

}  // namespace autofill
