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

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

#import <string>
#import <vector>

#import "base/containers/contains.h"
#import "base/containers/flat_set.h"
#import "base/strings/strcat.h"
#import "base/strings/stringprintf.h"
#import "base/strings/utf_string_conversions.h"
#import "base/test/ios/wait_util.h"
#import "base/test/scoped_feature_list.h"
#import "base/time/time.h"
#import "base/types/id_type.h"
#import "components/autofill/core/browser/autofill_test_utils.h"
#import "components/autofill/core/browser/browser_autofill_manager.h"
#import "components/autofill/core/browser/test_autofill_client.h"
#import "components/autofill/core/browser/test_autofill_manager_waiter.h"
#import "components/autofill/core/common/autofill_features.h"
#import "components/autofill/core/common/form_data_test_api.h"
#import "components/autofill/core/common/form_field_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/mock_password_autofill_agent_delegate.h"
#import "components/autofill/ios/browser/test_autofill_manager_injector.h"
#import "components/autofill/ios/form_util/autofill_test_with_web_state.h"
#import "components/autofill/ios/form_util/child_frame_registrar.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/prefs/testing_pref_service.h"
#import "ios/testing/embedded_test_server_handlers.h"
#import "ios/web/public/js_messaging/web_frame.h"
#import "ios/web/public/js_messaging/web_frames_manager.h"
#import "ios/web/public/test/fakes/fake_web_client.h"
#import "ios/web/public/test/navigation_test_util.h"
#import "net/test/embedded_test_server/embedded_test_server.h"
#import "net/test/embedded_test_server/request_handler_util.h"
#import "testing/gmock/include/gmock/gmock.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "url/gurl.h"

using base::test::ios::kWaitForJSCompletionTimeout;
using net::test_server::EmbeddedTestServer;
using ::testing::AllOf;
using ::testing::AssertionFailure;
using ::testing::AssertionResult;
using ::testing::AssertionSuccess;
using ::testing::Each;
using ::testing::IsEmpty;
using ::testing::IsTrue;
using ::testing::Property;
using ::testing::SizeIs;
using ::testing::UnorderedElementsAre;
using testing::VariantWith;

namespace autofill {

namespace {
// Returns the FormFieldData pointer for the field in `fields` that has the
// corresponding `placeholder`. Returns nullptr if there is no field to be
// found.
FormFieldData* GetFieldWithPlaceholder(const std::u16string& placeholder,
                                       std::vector<FormFieldData>* fields) {
  auto it =
      base::ranges::find(*fields, placeholder, &FormFieldData::placeholder);
  return it != fields->end() ? &(*it) : nullptr;
}

// Gets a mutable pointer to the first field with `id_attr` among `fields`.
FormFieldData* GetMutableFieldWithId(const std::string& id_attr,
                                     std::vector<FormFieldData>* fields) {
  auto it = base::ranges::find(*fields, base::UTF8ToUTF16(id_attr),
                               &FormFieldData::id_attribute);
  return it != fields->end() ? &*it : nullptr;
}

// Gets a const pointer to the first field with `id_attr` among `fields`.
const FormFieldData* GetFieldWithId(const std::string& id_attr,
                                    const std::vector<FormFieldData>& fields) {
  auto it = base::ranges::find(fields, base::UTF8ToUTF16(id_attr),
                               &FormFieldData::id_attribute);
  return it != fields.end() ? &(*it) : nullptr;
}

// Set the fill data for the `field`.
void SetFillDataForField(
    const std::u16string& value,
    FieldType field_type,
    FormFieldData* field,
    base::flat_map<FieldGlobalId, FieldType>* field_type_map) {
  CHECK(field);
  field->set_value(value);
  field->set_is_autofilled(true);
  field->set_is_user_edited(false);
  (*field_type_map)[field->global_id()] = field_type;
}

// Waits on the input field that corresponds to `field_id` in the `frame` DOM to
// be filled with `expected_value`. Returns AssertionSuccess() on success or
// AssertionFailure() with an error message when it times out.
[[nodiscard]] ::testing::AssertionResult WaitOnFieldFilledWithValue(
    web::WebFrame* frame,
    const std::string& field_id,
    const std::u16string& expected_value) {
  __block bool execute_script = true;
  __block std::u16string value;
  bool res = base::test::ios::WaitUntilConditionOrTimeout(
      kWaitForJSCompletionTimeout, ^() {
        if (execute_script) {
          const std::u16string script = base::UTF8ToUTF16(base::StringPrintf(
              "document.getElementById('%s').value;", field_id.c_str()));
          execute_script = false;
          frame->ExecuteJavaScript(
              script, base::BindOnce(^(const base::Value* result) {
                // Script execution is done, re-arm.
                execute_script = true;

                if (!result || !result->is_string()) {
                  return;
                }
                value = base::UTF8ToUTF16(result->GetString());
              }));
        }
        return value == expected_value;
      });

  return res ? AssertionSuccess()
             : AssertionFailure() << "field with id \"" + field_id +
                                         "\"wasn't filled with expected value";
}

// Executes `script` in the specified `frame`, wait until execution is done,
// then pass the execution result to the provided `callback`.
[[nodiscard]] bool ExecuteJavaScriptInFrame(
    web::WebFrame* frame,
    const std::u16string& script,
    base::OnceCallback<void(const base::Value*)> callback =
        base::DoNothingAs<void(const base::Value*)>()) {
  __block bool done = false;

  frame->ExecuteJavaScript(script,
                           base::BindOnce(
                               ^(base::OnceCallback<void(const base::Value*)> c,
                                 const base::Value* result) {
                                 done = true;
                                 std::move(c).Run(result);
                               },
                               std::move(callback)));
  return base::test::ios::WaitUntilConditionOrTimeout(
      kWaitForJSCompletionTimeout, ^() {
        return done;
      });
}

// Contains the template information to construct an input field for testing
// along with helpers.
struct TestFieldInfo {
  std::string id_attribute;
  std::string autocomplete_attribute;
  std::string fill_value;
  bool should_be_filled;
  // Attributes that can only be set when the field is rendered.
  FieldGlobalId global_id;
  LocalFrameToken host_frame;

  // Parses the field info to a HTML <input> field element.
  std::string ToHtmlInput() const {
    CHECK(!id_attribute.empty() && !autocomplete_attribute.empty());
    return base::StringPrintf(
        R"(<input type="text" autocomplete="%s" id="%s">)",
        autocomplete_attribute.c_str(), id_attribute.c_str());
  }

  // Parses the field info to a HTML <form> element.
  std::string ToHtmlForm() const {
    return "<form>" + ToHtmlInput() + "</form>";
  }
};

struct TestCreditCardForm {
  TestFieldInfo name_field;
  TestFieldInfo cc_number_field;
  TestFieldInfo exp_field;
  TestFieldInfo cvc_field;

  // Returns all fields in the credit card form.
  std::vector<TestFieldInfo> all_fields() const {
    return {name_field, cc_number_field, exp_field, cvc_field};
  }

  // Verifies that the fields corresponding to `filled_field_ids` are filled in
  // the renderer content with their fill value and that they aren't filled with
  // anything if not listed.
  [[nodiscard]] AssertionResult VerifyFieldsAreCorrectlyFilled(
      web::WebFramesManager* frames_manager,
      const base::flat_set<FieldGlobalId>& filled_field_ids) {
    std::vector<TestFieldInfo> fields = {name_field, cc_number_field, exp_field,
                                         cvc_field};

    for (const auto& field : fields) {
      const std::string frame_id = field.host_frame->ToString();
      web::WebFrame* frame = frames_manager->GetFrameWithId(frame_id);
      if (!frame) {
        return AssertionFailure()
               << "frame with id " << frame_id << " couldn't be found";
      }
      const bool should_be_filled =
          base::Contains(filled_field_ids, field.global_id);

      const std::u16string expected_filled_value =
          should_be_filled ? base::UTF8ToUTF16(field.fill_value) : u"";
      if (!should_be_filled) {
        // Wait some time to make sure that the field is indeed not filled.
        base::test::ios::SpinRunLoopWithMinDelay(base::Seconds(2));
      }
      if (AssertionResult result = WaitOnFieldFilledWithValue(
              frame, field.id_attribute, expected_filled_value);
          !result) {
        return result;
      }
    }
    return AssertionSuccess();
  }

  // Set the fill data in `fields` that map with the fields in this test form.
  [[nodiscard]] AssertionResult SetFillData(
      std::vector<FormFieldData>* fields,
      base::flat_map<FieldGlobalId, FieldType>* field_type_map) {
    auto fields_to_fill = {
        std::make_pair(FieldType::CREDIT_CARD_NAME_FULL, &name_field),
        std::make_pair(FieldType::CREDIT_CARD_NUMBER, &cc_number_field),
        std::make_pair(FieldType::CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR,
                       &exp_field),
        std::make_pair(FieldType::CREDIT_CARD_VERIFICATION_CODE, &cvc_field)};
    for (auto [field_type, field_info] : fields_to_fill) {
      FormFieldData* field =
          GetMutableFieldWithId(field_info->id_attribute, fields);
      if (!field) {
        return AssertionFailure()
               << "\"" << field_info->id_attribute << "\" field not found";
      }
      SetFillDataForField(base::UTF8ToUTF16(field_info->fill_value), field_type,
                          field, field_type_map);
      field_info->global_id = field->global_id();
      field_info->host_frame = field->host_frame();
    }
    return AssertionSuccess();
  }
};

// Gets the representation of a credit card form for testing.
TestCreditCardForm GetTestCreditCardForm() {
  return {.name_field = {.id_attribute = "cc-name-field-id",
                         .autocomplete_attribute = "cc-name",
                         .fill_value = "Bob Bobbertson"},
          .cc_number_field = {.id_attribute = "cc-number-field-id",
                              .autocomplete_attribute = "cc-number",
                              .fill_value = "4545454545454545"},
          .exp_field = {.id_attribute = "cc-exp-field-id",
                        .autocomplete_attribute = "cc-exp",
                        .fill_value = "07/2028"},
          .cvc_field = {.id_attribute = "cc-cvc-field-id",
                        .autocomplete_attribute = "cc-csc",
                        .fill_value = "123"}};
}

}  // namespace

// Version of AutofillManager that caches the FormData it receives so we can
// examine them. The public API deals with FormStructure, the post-parsing
// data structure, but we want to intercept the FormData and ensure we're
// providing the right inputs to the parsing process.
class TestAutofillManager : public BrowserAutofillManager {
 public:
  explicit TestAutofillManager(AutofillDriverIOS* driver)
      : BrowserAutofillManager(driver, "en-US") {}

  [[nodiscard]] testing::AssertionResult WaitForFormsSeen(
      int min_num_awaited_calls) {
    return forms_seen_waiter_.Wait(min_num_awaited_calls);
  }

  [[nodiscard]] testing::AssertionResult WaitForFormsFilled(
      int min_num_awaited_calls) {
    return did_fill_forms_waiter_.Wait(min_num_awaited_calls);
  }

  [[nodiscard]] testing::AssertionResult WaitForFormsSubmitted(
      int min_num_awaited_calls) {
    return did_submit_forms_waiter_.Wait(min_num_awaited_calls);
  }

  [[nodiscard]] testing::AssertionResult WaitForFormsAskedForFillData(
      int min_num_awaited_calls) {
    return ask_for_filldata_forms_waiter_.Wait(min_num_awaited_calls);
  }

  [[nodiscard]] testing::AssertionResult WaitOnTextFieldDidChange(
      int min_num_awaited_calls) {
    return text_field_did_change_forms_waiter_.Wait(min_num_awaited_calls);
  }

  void OnFormsSeen(const std::vector<FormData>& updated_forms,
                   const std::vector<FormGlobalId>& removed_forms) override {
    seen_forms_.insert(seen_forms_.end(), updated_forms.begin(),
                       updated_forms.end());
    removed_forms_.insert(removed_forms_.end(), removed_forms.begin(),
                          removed_forms.end());
    BrowserAutofillManager::OnFormsSeen(updated_forms, removed_forms);
  }

  void OnDidFillAutofillFormData(const FormData& form,
                                 base::TimeTicks timestamp) override {
    filled_forms_.push_back(form);
    BrowserAutofillManager::OnDidFillAutofillFormData(form, timestamp);
  }

  void OnFormSubmitted(const FormData& form,
                       const bool known_success,
                       const mojom::SubmissionSource source) override {
    submitted_forms_.emplace_back(form);
    BrowserAutofillManager::OnFormSubmitted(form, known_success, source);
  }

  void OnAskForValuesToFill(
      const FormData& form,
      const FieldGlobalId& field_id,
      const gfx::Rect& caret_bounds,
      AutofillSuggestionTriggerSource trigger_source) override {
    ask_for_filldata_forms_.emplace_back(form);
    BrowserAutofillManager::OnAskForValuesToFill(form, field_id, caret_bounds,
                                                 trigger_source);
  }

  void OnTextFieldDidChange(const FormData& form,
                            const FieldGlobalId& field_id,
                            const base::TimeTicks timestamp) override {
    text_field_did_change_forms_.emplace_back(form);
    BrowserAutofillManager::OnTextFieldDidChange(form, field_id, timestamp);
  }

  const std::vector<FormData>& seen_forms() { return seen_forms_; }
  const std::vector<FormGlobalId>& removed_forms() { return removed_forms_; }
  const std::vector<FormData>& filled_forms() { return filled_forms_; }
  const std::vector<FormData>& submitted_forms() { return submitted_forms_; }
  const std::vector<FormData>& ask_for_filldata_forms() {
    return ask_for_filldata_forms_;
  }
  const std::vector<FormData>& text_filled_did_change_forms() {
    return text_field_did_change_forms_;
  }

  void ResetTestState() {
    seen_forms_.clear();
    removed_forms_.clear();
    filled_forms_.clear();
    submitted_forms_.clear();
    ask_for_filldata_forms_.clear();
    text_field_did_change_forms_.clear();
    forms_seen_waiter_.Reset();
    did_fill_forms_waiter_.Reset();
    did_submit_forms_waiter_.Reset();
    ask_for_filldata_forms_waiter_.Reset();
    text_field_did_change_forms_waiter_.Reset();
  }

 private:
  std::vector<FormData> seen_forms_;
  std::vector<FormGlobalId> removed_forms_;
  std::vector<FormData> filled_forms_;
  std::vector<FormData> submitted_forms_;
  std::vector<FormData> ask_for_filldata_forms_;
  std::vector<FormData> text_field_did_change_forms_;

  TestAutofillManagerWaiter forms_seen_waiter_{
      *this,
      {AutofillManagerEvent::kFormsSeen}};

  TestAutofillManagerWaiter did_fill_forms_waiter_{
      *this,
      {AutofillManagerEvent::kDidFillAutofillFormData}};

  TestAutofillManagerWaiter did_submit_forms_waiter_{
      *this,
      {AutofillManagerEvent::kFormSubmitted}};

  TestAutofillManagerWaiter ask_for_filldata_forms_waiter_{
      *this,
      {AutofillManagerEvent::kAskForValuesToFill}};

  TestAutofillManagerWaiter text_field_did_change_forms_waiter_{
      *this,
      {AutofillManagerEvent::kTextFieldDidChange}};
};

// Catcher that captures the latest new frame reported by `manager`.
class NewFrameCatcher : public web::WebFramesManager::Observer {
 public:
  explicit NewFrameCatcher(web::WebFramesManager* manager) {
    scoped_observer_.Observe(manager);
  }

  // Returns the latest new frame that was observed. Returns nullptr if nothing
  // was seen.
  web::WebFrame* latest_new_frame() { return latest_new_frame_; }

 private:
  void WebFrameBecameAvailable(web::WebFramesManager* web_frames_manager,
                               web::WebFrame* web_frame) override {
    latest_new_frame_ = web_frame;
  }

  web::WebFrame* latest_new_frame_ = nullptr;
  base::ScopedObservation<web::WebFramesManager,
                          web::WebFramesManager::Observer>
      scoped_observer_{this};
};

// A mock child frame registrar observer.
class MockRegistrarObserver : public autofill::ChildFrameRegistrarObserver {
 public:
  MOCK_METHOD(void,
              OnDidDoubleRegistration,
              (LocalFrameToken local),
              (override));
};

class AutofillAcrossIframesTest : public AutofillTestWithWebState {
 public:
  AutofillAcrossIframesTest()
      : AutofillTestWithWebState(std::make_unique<web::FakeWebClient>()),
        feature_list_(features::kAutofillAcrossIframesIos) {}

  void SetUp() override {
    AutofillTestWithWebState::SetUp();

    web::FakeWebClient* web_client =
        static_cast<web::FakeWebClient*>(GetWebClient());
    web_client->SetJavaScriptFeatures(
        {AutofillJavaScriptFeature::GetInstance(),
         FormUtilJavaScriptFeature::GetInstance(),
         FormHandlersJavaScriptFeature::GetInstance()});

    // We need an AutofillAgent to exist or else the form will never get parsed.
    prefs_ = autofill::test::PrefServiceForTesting();
    autofill_agent_ = [[AutofillAgent alloc] initWithPrefService:prefs_.get()
                                                        webState:web_state()];

    // Driver factory needs to exist before any call to
    // `AutofillDriverIOS::FromWebStateAndWebFrame`, or we crash.
    autofill::AutofillDriverIOSFactory::CreateForWebState(
        web_state(), &autofill_client_, /*bridge=*/autofill_agent_,
        /*locale=*/"en");

    // Password autofill agent needs to exist before any call to fill data.
    autofill::PasswordAutofillAgent::CreateForWebState(web_state(),
                                                       &delegate_mock_);

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

  web::WebFrame* WaitForMainFrame() {
    __block web::WebFrame* main_frame = nullptr;
    EXPECT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
        kWaitForJSCompletionTimeout, ^bool {
          main_frame = web_frames_manager()->GetMainWebFrame();
          return main_frame != nullptr;
        }));
    return main_frame;
  }

  // Wait for a new frame to become available.
  web::WebFrame* WaitForNewFrame() {
    NewFrameCatcher catcher(web_frames_manager());
    NewFrameCatcher* catcher_ptr = &catcher;
    EXPECT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
        kWaitForJSCompletionTimeout, ^bool {
          return !!catcher_ptr->latest_new_frame();
        }));
    return catcher_ptr->latest_new_frame();
  }

  // Wait for the browser form to be considered as completed (fully constructed)
  // based on `child_frames_count` and `fields_count`. It is in the hands of
  // the caller to decide when the browser form is deemed complete.
  [[nodiscard]] std::pair<FormData, ::testing::AssertionResult>
  WaitForCompleteBrowserForm(size_t child_frames_count, size_t fields_count) {
    main_frame_manager().ResetTestState();

    __block FormData form;
    bool res = base::test::ios::WaitUntilConditionOrTimeout(
        kWaitForJSCompletionTimeout, ^{
          if (main_frame_manager().seen_forms().empty()) {
            return false;
          }
          form = main_frame_manager().seen_forms().back();
          return form.child_frames().size() == child_frames_count &&
                 form.fields().size() == fields_count;
        });

    // Wait for all pending calls to be done. No calls are awaited at this point
    // but there might be pending FormsSeen() calls that aren't fully completed
    // yet because of async tasks.
    auto wait_res = main_frame_manager().WaitForFormsSeen(0);
    if (!wait_res) {
      return std::make_pair(FormData{}, wait_res);
    }

    main_frame_manager().ResetTestState();
    return res ? std::make_pair(form, AssertionSuccess())
               : std::make_pair(FormData{}, AssertionFailure());
  }

  AutofillDriverIOS* main_frame_driver() {
    return AutofillDriverIOS::FromWebStateAndWebFrame(web_state(),
                                                      WaitForMainFrame());
  }

  TestAutofillManager& main_frame_manager() {
    return static_cast<TestAutofillManager&>(
        main_frame_driver()->GetAutofillManager());
  }

  web::WebFramesManager* web_frames_manager() {
    return autofill::FormUtilJavaScriptFeature::GetInstance()
        ->GetWebFramesManager(web_state());
  }

  autofill::ChildFrameRegistrar* registrar() {
    return autofill::ChildFrameRegistrar::GetOrCreateForWebState(web_state());
  }

  // Serve document with `contents` accessible at `path` on main origin server.
  void ServeDocument(const std::string& path, const std::string& contents) {
    test_server_.RegisterRequestHandler(base::BindRepeating(
        &net::test_server::HandlePrefixedRequest, "/" + path,
        base::BindRepeating(&testing::HandlePageWithHtml, contents)));
  }

  // Functions for setting up the pages to be loaded. Tests should call one or
  // more of the `Add*` functions, then call `StartTestServerAndLoad`.

  // Adds an iframe loading `path` to the main frame's HTML, and registers a
  // handler on the test server to return `contents` when `path` is requested.
  void AddIframe(const std::string& path, const std::string& contents) {
    main_frame_html_ += "<iframe src=\"/" + path + "\"></iframe>";
    ServeDocument(path, contents);
  }

  // Setup `test_server` to serve `contents` accessible at `path`. You need to
  // start `test_server` before using AddXoriginIframe() to add an iframe
  // sourced at `path`.
  void ServeCrossOriginDocument(const std::string& path,
                                const std::string& contents,
                                EmbeddedTestServer* test_server) {
    test_server->RegisterRequestHandler(base::BindRepeating(
        &net::test_server::HandlePrefixedRequest, "/" + path,
        base::BindRepeating(&testing::HandlePageWithHtml, contents)));
  }

  // Add an iframe sourced at `path` from another origin hosted by `test_server`
  // different from the main frame origin.
  void AddCrossOriginIframe(const std::string& path,
                            EmbeddedTestServer* test_server) {
    const std::string absolute_path = test_server->GetURL("/" + path).spec();
    main_frame_html_ += "<iframe src=\"" + absolute_path + "\"></iframe>";
  }

  // Adds an input parsed from `field` to the main frame's HTML.
  void AddInput(const TestFieldInfo& field) {
    main_frame_html_ += field.ToHtmlInput();
  }

  // Adds an input of type `type` with placeholder `ph` to the main frame's
  // HTML.
  void AddInput(const std::string& type, const std::string& ph) {
    main_frame_html_ +=
        "<input type=\"" + type + "\" placeholder =\"" + ph + "\">";
  }

  // Starts the test server and loads a page containing `main_frame_html_` in
  // the main frame.
  void StartTestServerAndLoad(bool use_synthetic_form = false) {
    if (use_synthetic_form) {
      main_frame_html_ = base::StrCat({"<body>", main_frame_html_, "</body>"});
    } else {
      main_frame_html_ =
          base::StrCat({"<body><form>", main_frame_html_, "</form></body>"});
    }
    test_server_.RegisterRequestHandler(base::BindRepeating(
        &net::test_server::HandlePrefixedRequest, "/testpage",
        base::BindRepeating(&testing::HandlePageWithHtml, main_frame_html_)));
    ASSERT_TRUE(test_server_.Start());
    GURL url = test_server_.GetURL("/testpage");
    web::test::LoadUrl(web_state(), url);
    web_state()->WasShown();
    autofill_client_.set_last_committed_primary_main_frame_url(url);
  }

  // Returns the frame that corresponds to `frame_id`.
  web::WebFrame* GetFrameByID(const std::string& frame_id) {
    return web_frames_manager()->GetFrameWithId(frame_id);
  }

  // Gets the host frame of the first field with `id_attr` among `fields`.
  // Returns nullptr if the frame can't be found.
  web::WebFrame* GetFrameForFieldWithIdAttr(
      const std::string& id_attr,
      const std::vector<FormFieldData>& fields) {
    const FormFieldData* field = GetFieldWithId(id_attr, fields);
    if (!field) {
      return nullptr;
    }
    return web_frames_manager()->GetFrameWithId(
        field->host_frame()->ToString());
  }

  AutofillDriverIOS* GetDriverForFrame(web::WebFrame* frame) {
    auto* driver =
        AutofillDriverIOS::FromWebStateAndWebFrame(web_state(), frame);
    return driver;
  }

  TestAutofillManager* GetManagerForFrame(web::WebFrame* frame) {
    if (auto* driver =
            AutofillDriverIOS::FromWebStateAndWebFrame(web_state(), frame)) {
      return static_cast<TestAutofillManager*>(&driver->GetAutofillManager());
    }
    return nullptr;
  }

  // Fills the form represented by `browser_form` and that corresponds to
  // `cc_form_info` using ApplyFormAction() and verifies that the filled fields
  // correspond to `expected_filled_fields`. It is assumed that
  // the `browser_form` is a xframe form where each field are in their own frame
  // with one distinct form per field. The fill data of the `cc_form_info` will
  // be set when running this routine so subsequent verifications can be done
  // after this.
  void FillAndVerify(TestCreditCardForm& cc_form_info,
                     const FormData& browser_form,
                     const TestFieldInfo& trigger_field,
                     const std::vector<TestFieldInfo>& expected_filled_fields) {
    std::vector<FormFieldData> fields = browser_form.fields();

    base::flat_map<FieldGlobalId, FieldType> field_type_map;
    ASSERT_TRUE(cc_form_info.SetFillData(&fields, &field_type_map));

    // Extract the global ids of the fields that are expected to be filled.
    std::vector<FieldGlobalId> expected_filled_field_ids;
    for (const auto& expected_filled_field : expected_filled_fields) {
      expected_filled_field_ids.push_back(
          CHECK_DEREF(
              GetFieldWithId(expected_filled_field.id_attribute, fields))
              .global_id());
    }

    // Trigger fill.
    web::WebFrame* trigger_frame =
        GetFrameForFieldWithIdAttr(trigger_field.id_attribute, fields);
    ASSERT_TRUE(trigger_frame);
    url::Origin trigger_origin =
        url::Origin::Create(trigger_frame->GetSecurityOrigin());
    base::flat_set<FieldGlobalId> filled_field_ids =
        GetDriverForFrame(trigger_frame)
            ->ApplyFormAction(mojom::FormActionType::kFill,
                              mojom::ActionPersistence::kFill, fields,
                              trigger_origin, field_type_map);

    // Verify that filled fields correspond to the expected ones by comparing
    // their global ids.
    ASSERT_THAT(filled_field_ids, ::testing::UnorderedElementsAreArray(
                                      expected_filled_field_ids));

    // Wait that all the expected fields are filled, one field per frame and
    // form. The fill events are all routed to the frame hosting the browser
    // form, which corresponds to the root form in the forms structure.
    const size_t expected_filled_forms_count = expected_filled_field_ids.size();
    ASSERT_TRUE(
        main_frame_manager().WaitForFormsFilled(expected_filled_forms_count));
    ASSERT_THAT(main_frame_manager().filled_forms(),
                SizeIs(expected_filled_forms_count));

    // Verify that what is actually filled corresponds to what was anticipated.
    EXPECT_TRUE(cc_form_info.VerifyFieldsAreCorrectlyFilled(
        web_frames_manager(), filled_field_ids));

    main_frame_manager().ResetTestState();
  }

  std::unique_ptr<TestAutofillManagerInjector<TestAutofillManager>>
      autofill_manager_injector_;
  std::unique_ptr<PrefService> prefs_;
  autofill::TestAutofillClient autofill_client_;
  AutofillAgent* autofill_agent_;
  autofill::MockPasswordAutofillAgentDelegate delegate_mock_;
  base::test::ScopedFeatureList feature_list_;

  EmbeddedTestServer test_server_;
  std::string main_frame_html_;
};

// If a page has no child frames, the corresponding field in the saved form
// structure should be empty.
TEST_F(AutofillAcrossIframesTest, NoChildFrames) {
  AddInput("text", "name");
  AddInput("text", "address");
  StartTestServerAndLoad();

  ASSERT_TRUE(main_frame_manager().WaitForFormsSeen(1));
  ASSERT_EQ(main_frame_manager().seen_forms().size(), 1u);

  const FormData& form = main_frame_manager().seen_forms()[0];
  EXPECT_EQ(form.child_frames().size(), 0u);

  // The main frame driver should have the correct local frame token set even
  // without any child frames.
  LocalFrameToken token = main_frame_driver()->GetFrameToken();
  ASSERT_TRUE(token);
  web::WebFramesManager* frames_manager = web_frames_manager();
  ASSERT_TRUE(frames_manager);
  web::WebFrame* frame = frames_manager->GetFrameWithId(token.ToString());
  EXPECT_EQ(frame, main_frame_driver()->web_frame());
}

// Ensure that child frames are assigned a token during form extraction, are
// registered under that token with the registrar, and can be found in the
// WebFramesManager using the frame ID provided by the registrar.
TEST_F(AutofillAcrossIframesTest, WithChildFrames) {
  AddIframe("cf1", "child frame 1");
  AddInput("text", "name");
  AddIframe("cf2", "child frame 2");
  AddInput("text", "address");
  StartTestServerAndLoad();

  // Wait for the 3 forms to be reported as seen to the main frame that hosts
  // the browser form (which is the flattened representation of all forms in the
  // tree structure that share a common parent).
  ASSERT_TRUE(main_frame_manager().WaitForFormsSeen(3));
  ASSERT_EQ(main_frame_manager().seen_forms().size(), 3u);

  // Pick the last form that was seen which reflects the latest and most
  // complete state of the browser form, which contains all fields in the forms
  // tree (aka browser form).
  const FormData& form = main_frame_manager().seen_forms().back();
  ASSERT_THAT(form.child_frames(), SizeIs(2u));

  FrameTokenWithPredecessor remote_token1 = form.child_frames()[0];
  FrameTokenWithPredecessor remote_token2 = form.child_frames()[1];

  // Verify that tokens hold the right alternative, and the token objects are
  // valid (the bool cast checks this).
  EXPECT_THAT(remote_token1.token, VariantWith<RemoteFrameToken>(IsTrue()));
  EXPECT_THAT(remote_token2.token, VariantWith<RemoteFrameToken>(IsTrue()));

  // Veify that the predecessor of each token is correctly set. The predecessor
  // being the index of the last input field preceeding the frame. Set to -1 if
  // there is no predecessor.
  EXPECT_EQ(-1, remote_token1.predecessor);
  EXPECT_EQ(0, remote_token2.predecessor);

  auto* registrar =
      autofill::ChildFrameRegistrar::GetOrCreateForWebState(web_state());
  ASSERT_TRUE(registrar);

  // Get the frame tokens from the registrar. Wrap this in a block because the
  // registrar receives these from each frame in a separate JS message.
  __block std::optional<LocalFrameToken> local_token1, local_token2;
  ASSERT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
      kWaitForJSCompletionTimeout, ^bool {
        local_token1 = registrar->LookupChildFrame(
            absl::get<RemoteFrameToken>(remote_token1.token));
        local_token2 = registrar->LookupChildFrame(
            absl::get<RemoteFrameToken>(remote_token2.token));
        return local_token1.has_value() && local_token2.has_value();
      }));

  web::WebFramesManager* frames_manager = web_frames_manager();
  ASSERT_TRUE(frames_manager);

  web::WebFrame* frame1 =
      frames_manager->GetFrameWithId(local_token1->ToString());
  EXPECT_TRUE(frame1);

  web::WebFrame* frame2 =
      frames_manager->GetFrameWithId(local_token2->ToString());
  EXPECT_TRUE(frame2);

  // TODO(crbug.com/40266126): Check contents of frames to make sure they're the
  // right ones.

  // Also check that data relating to the frame was properly set on the form-
  // and field-level data when extracted.
  ASSERT_TRUE(form.host_frame());
  web::WebFrame* main_frame_from_form_data =
      frames_manager->GetFrameWithId(form.host_frame().ToString());
  ASSERT_TRUE(main_frame_from_form_data);
  EXPECT_TRUE(main_frame_from_form_data->IsMainFrame());

  // Verify that the form information in the fields corresponds to the
  // information that is actually in the form.
  FormSignature form_signature = CalculateFormSignature(form);
  url::Origin form_origin = url::Origin::Create(form.url());
  EXPECT_THAT(
      form.fields(),
      Each(AllOf(
          Property(&FormFieldData::host_frame, form.host_frame()),
          Property(&FormFieldData::host_form_id, form.renderer_id()),
          Property(&FormFieldData::origin, form_origin),
          Property(&FormFieldData::host_form_signature, form_signature))));
}

// Ensure that, for a synthetic form (an aggregate of standalone/unowned fields
// not associated with a form), child frames are assigned a token during form
// extraction. This doesn't test the full token registration flow which is
// covered by other tests.
TEST_F(AutofillAcrossIframesTest, WithChildFrames_SyntheticForm) {
  AddIframe("cf1", "child frame 1");
  AddInput("text", "name");
  AddIframe("cf2", "child frame 2");
  AddInput("text", "address");
  StartTestServerAndLoad(/*use_synthetic_form=*/true);

  // Wait for the 3 forms to be reported as seen to the main frame that hosts
  // the browser form (which is the flattened representation of all forms in the
  // tree structure that share a common parent).
  ASSERT_TRUE(main_frame_manager().WaitForFormsSeen(3));
  ASSERT_EQ(main_frame_manager().seen_forms().size(), 3u);

  // Pick the last form that was seen which reflects the latest and most
  // complete state of the browser form, which contains all fields in the forms
  // tree (aka browser form).
  const FormData& form = main_frame_manager().seen_forms().back();
  ASSERT_EQ(form.child_frames().size(), 2u);

  FrameTokenWithPredecessor remote_token1 = form.child_frames()[0];
  FrameTokenWithPredecessor remote_token2 = form.child_frames()[1];

  // Verify that tokens hold the right alternative, and the token objects are
  // valid (the bool cast checks this).
  EXPECT_THAT(remote_token1.token, VariantWith<RemoteFrameToken>(IsTrue()));
  EXPECT_THAT(remote_token2.token, VariantWith<RemoteFrameToken>(IsTrue()));
}

// Ensure that, for a synthetic form that is only composed of child frames
// without input elements, child frames are assigned a token during form
// extraction. This doesn't test the full token registration flow which is
// covered by other tests.
TEST_F(AutofillAcrossIframesTest,
       WithChildFrames_SyntheticForm_WithoutInputElements) {
  AddIframe("cf1", "child frame 1");
  AddIframe("cf2", "child frame 2");
  StartTestServerAndLoad(/*use_synthetic_form=*/true);

  // Wait for the 3 forms to be reported as seen to the main frame that hosts
  // the browser form (which is the flattened representation of all forms in the
  // tree structure that share a common parent).
  ASSERT_TRUE(main_frame_manager().WaitForFormsSeen(3));
  ASSERT_EQ(main_frame_manager().seen_forms().size(), 3u);

  // Pick the last form that was seen which reflects the latest and most
  // complete state of the browser form, which contains all fields in the forms
  // tree (aka browser form).
  const FormData& form = main_frame_manager().seen_forms().back();
  ASSERT_EQ(form.child_frames().size(), 2u);

  FrameTokenWithPredecessor remote_token1 = form.child_frames()[0];
  FrameTokenWithPredecessor remote_token2 = form.child_frames()[1];

  // Verify that tokens hold the right alternative, and the token objects are
  // valid (the bool cast checks this).
  EXPECT_THAT(remote_token1.token, VariantWith<RemoteFrameToken>(IsTrue()));
  EXPECT_THAT(remote_token2.token, VariantWith<RemoteFrameToken>(IsTrue()));
}

// Largely repeats `WithChildFrames` above, but exercises the Resolve method on
// AutofillDriverIOS.
TEST_F(AutofillAcrossIframesTest, Resolve) {
  AddIframe("cf1", "child frame 1");
  AddInput("text", "name");
  StartTestServerAndLoad();

  // Wait for a form with a child frame, and grab its remote token.
  ASSERT_TRUE(main_frame_manager().WaitForFormsSeen(1));
  ASSERT_EQ(main_frame_manager().seen_forms().size(), 1u);
  const FormData& form = main_frame_manager().seen_forms()[0];
  ASSERT_EQ(form.child_frames().size(), 1u);
  FrameTokenWithPredecessor remote_token = form.child_frames()[0];
  EXPECT_THAT(remote_token.token, VariantWith<RemoteFrameToken>(IsTrue()));

  // Wait for the child frame to register itself.
  auto* registrar =
      autofill::ChildFrameRegistrar::GetOrCreateForWebState(web_state());
  ASSERT_TRUE(registrar);
  ASSERT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
      kWaitForJSCompletionTimeout, ^bool {
        return registrar
            ->LookupChildFrame(absl::get<RemoteFrameToken>(remote_token.token))
            .has_value();
      }));

  // Verify that resolving the registered remote token returns a valid local
  // token that corresponds to a known frame.
  std::optional<LocalFrameToken> local_token =
      main_frame_driver()->Resolve(remote_token.token);
  ASSERT_TRUE(local_token.has_value());
  web::WebFramesManager* frames_manager = web_frames_manager();
  ASSERT_TRUE(frames_manager);
  EXPECT_TRUE(frames_manager->GetFrameWithId(local_token->ToString()));

  // Verify that resolving a local token is an identity operation.
  EXPECT_EQ(local_token, main_frame_driver()->Resolve(*local_token));

  // Verify that resolving a made-up remote token returns nullopt.
  RemoteFrameToken junk_remote_token =
      RemoteFrameToken(base::UnguessableToken::Create());
  std::optional<LocalFrameToken> shouldnt_exist =
      main_frame_driver()->Resolve(junk_remote_token);
  EXPECT_FALSE(shouldnt_exist.has_value());
}

TEST_F(AutofillAcrossIframesTest, SetAndGetParent) {
  AddIframe("cf1", "child frame 1");
  AddInput("text", "name");
  StartTestServerAndLoad();

  // Wait for a form with a child frame, and grab its remote token.
  ASSERT_TRUE(main_frame_manager().WaitForFormsSeen(1));
  ASSERT_EQ(main_frame_manager().seen_forms().size(), 1u);
  const FormData& form = main_frame_manager().seen_forms()[0];
  ASSERT_EQ(form.child_frames().size(), 1u);
  FrameTokenWithPredecessor remote_token = form.child_frames()[0];
  EXPECT_THAT(remote_token.token, VariantWith<RemoteFrameToken>(IsTrue()));

  // Wait for the child frame to register itself.
  auto* registrar =
      autofill::ChildFrameRegistrar::GetOrCreateForWebState(web_state());
  ASSERT_TRUE(registrar);
  ASSERT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
      kWaitForJSCompletionTimeout, ^bool {
        return registrar
            ->LookupChildFrame(absl::get<RemoteFrameToken>(remote_token.token))
            .has_value();
      }));

  // The main frame shouldn't have a parent – it's the root.
  EXPECT_FALSE(main_frame_driver()->GetParent());

  // The child frame should have the main frame as its parent.
  std::optional<LocalFrameToken> local_token =
      main_frame_driver()->Resolve(remote_token.token);
  ASSERT_TRUE(local_token);
  auto* child_frame_driver = AutofillDriverIOS::FromWebStateAndLocalFrameToken(
      web_state(), *local_token);
  ASSERT_TRUE(child_frame_driver);
  EXPECT_EQ(main_frame_driver(), child_frame_driver->GetParent());
}

TEST_F(AutofillAcrossIframesTest, TriggerExtractionInFrame) {
  AddInput("text", "name");
  AddIframe("cf1", "<form><input id='address'></form>");
  StartTestServerAndLoad();

  web::WebFramesManager* frames_manager = web_frames_manager();
  ASSERT_TRUE(frames_manager);

  // Wait for the main frame and the child frame to be known to the
  // WebFramesManager.
  ASSERT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
      kWaitForJSCompletionTimeout, ^bool {
        return frames_manager->GetAllWebFrames().size() == 2;
      }));

  for (web::WebFrame* frame : frames_manager->GetAllWebFrames()) {
    auto* driver =
        AutofillDriverIOS::FromWebStateAndWebFrame(web_state(), frame);
    auto& manager =
        static_cast<TestAutofillManager&>(driver->GetAutofillManager());

    // Extraction will have triggered on page load. Wait for this to complete.
    EXPECT_TRUE(manager.WaitForFormsSeen(1));
    manager.ResetTestState();

    // Manually retrigger extraction, and wait for a fresh FormsSeen event.
    test_api(*driver).TriggerFormExtractionInDriverFrame();
    EXPECT_TRUE(manager.WaitForFormsSeen(1));
  }
}

// Tests that extraction can be done across frames to constitute a browser form.
TEST_F(AutofillAcrossIframesTest, TriggerExtraction_AcrossFrames) {
  EmbeddedTestServer test_server1;

  ServeCrossOriginDocument("cf1", "<form><input id='address'></form>",
                           &test_server1);
  ASSERT_TRUE(test_server1.Start());

  AddInput("text", "name");
  AddCrossOriginIframe("cf1", &test_server1);

  StartTestServerAndLoad();

  // Verify that the browser form is fully constructed from the main frame
  // and the other cross origin frame, totalling 2 fields.
  ASSERT_TRUE(
      WaitForCompleteBrowserForm(/*child_frames_count=*/1u, /*fields_count=*/2u)
          .second);
}

// Tests that the feature does not break filling in the main frame.
TEST_F(AutofillAcrossIframesTest, Fill_MainFrameForm) {
  const std::u16string kNamePlaceholder = u"Name";
  const std::u16string kFakeName = u"Bob Bobbertson";
  const std::u16string kPhonePlaceholder = u"Phone";
  const std::u16string kFakePhone = u"18005551234";

  AddInput("text", base::UTF16ToUTF8(kNamePlaceholder));
  AddInput("text", base::UTF16ToUTF8(kPhonePlaceholder));
  StartTestServerAndLoad();

  ASSERT_TRUE(main_frame_manager().WaitForFormsSeen(1));
  ASSERT_EQ(main_frame_manager().seen_forms().size(), 1u);

  // Copy the extracted form and put a name and phone number in it.
  FormData form = main_frame_manager().seen_forms()[0];
  base::flat_map<FieldGlobalId, FieldType> field_type_map;

  for (FormFieldData& field : test_api(form).fields()) {
    if (field.placeholder() == kNamePlaceholder) {
      field.set_value(kFakeName);
      field_type_map[field.global_id()] = FieldType::NAME_FULL;
    } else if (field.placeholder() == kPhonePlaceholder) {
      field.set_value(kFakePhone);
      field_type_map[field.global_id()] = FieldType::NAME_FULL;
    } else {
      ADD_FAILURE() << "Found unexpected field with placeholder: "
                    << field.placeholder();
    }
    field.set_is_autofilled(true);
    field.set_is_user_edited(false);
  }

  main_frame_driver()->ApplyFormAction(
      mojom::FormActionType::kFill, mojom::ActionPersistence::kFill,
      form.fields(), form.main_frame_origin(), field_type_map);

  ASSERT_TRUE(main_frame_manager().WaitForFormsFilled(1));
  ASSERT_EQ(main_frame_manager().filled_forms().size(), 1u);

  // Inspect the extracted, filled form, and ensure the expected data was
  // filled into the desired fields.
  const FormData& filled_form = main_frame_manager().filled_forms()[0];
  ASSERT_EQ(filled_form.fields().size(), 2u);
  for (const FormFieldData& field : filled_form.fields()) {
    if (field.placeholder() == kNamePlaceholder) {
      EXPECT_EQ(field.value(), kFakeName);
    } else if (field.placeholder() == kPhonePlaceholder) {
      EXPECT_EQ(field.value(), kFakePhone);
    } else {
      ADD_FAILURE() << "Found unexpected field with placeholder: "
                    << field.placeholder();
    }
  }
}

// Tests filling across multiple frames in the same forms tree structure.
TEST_F(AutofillAcrossIframesTest, Fill_MultiFrameForm) {
  const std::u16string kNamePlaceholder = u"Name";
  const std::u16string kFakeName = u"Bob Bobbertson";
  const std::u16string kPhonePlaceholder = u"Phone";
  const std::u16string kFakePhone = u"18005551234";

  AddIframe("cf1", "<form><input type=\"text\" placeholder=\"" +
                       base::UTF16ToUTF8(kNamePlaceholder) + "\"></form>");
  AddIframe("cf2", "<form><input type=\"text\" placeholder=\"" +
                       base::UTF16ToUTF8(kPhonePlaceholder) + "\"></form>");
  StartTestServerAndLoad();

  // Wait for the 3 forms to be reported as seen to the main frame that hosts
  // the browser form (which is the flattened representation of all forms in the
  // tree structure that share the form in the main frame as a common root).
  ASSERT_TRUE(main_frame_manager().WaitForFormsSeen(3));
  ASSERT_EQ(main_frame_manager().seen_forms().size(), 3u);

  // Pick the last form that was seen which reflects the latest and most
  // complete state of the browser form.
  FormData form = main_frame_manager().seen_forms().back();
  ASSERT_EQ(form.child_frames().size(), 2u);
  ASSERT_EQ(form.fields().size(), 2u);

  base::flat_map<FieldGlobalId, FieldType> field_type_map;

  std::vector<FormFieldData> fields = form.fields();

  FormFieldData* name_field =
      GetFieldWithPlaceholder(kNamePlaceholder, &fields);
  FormFieldData* phone_field =
      GetFieldWithPlaceholder(kPhonePlaceholder, &fields);

  // Set fill data for name field.
  SetFillDataForField(kFakeName, FieldType::NAME_FULL, name_field,
                      &field_type_map);
  // Set fill data for phone field.
  SetFillDataForField(kFakePhone, FieldType::PHONE_HOME_NUMBER, phone_field,
                      &field_type_map);

  base::flat_set<FieldGlobalId> filled_field_ids =
      main_frame_driver()->ApplyFormAction(
          mojom::FormActionType::kFill, mojom::ActionPersistence::kFill, fields,
          form.main_frame_origin(), field_type_map);

  EXPECT_THAT(filled_field_ids, UnorderedElementsAre(name_field->global_id(),
                                                     phone_field->global_id()));

  // Wait that the 2 forms are filled, one for each frame. The fill events are
  // all routed to the frame hosting the browser form, which corresponds to the
  // root form in the forms structure.
  ASSERT_TRUE(main_frame_manager().WaitForFormsFilled(2));
  ASSERT_EQ(main_frame_manager().filled_forms().size(), 2u);

  // Inspect the extracted, filled form, and ensure the expected data was
  // filled into the desired fields, where the last form correspond to the
  // most up to date snapshot of the browser form, a virtual form flattened
  // across frames and forms in the same tree.
  FormData filled_form = main_frame_manager().filled_forms().back();
  EXPECT_THAT(
      filled_form.fields(),
      UnorderedElementsAre(
          // Verify the name field.
          AllOf(Property(&FormFieldData::placeholder, kNamePlaceholder),
                Property(&FormFieldData::value, kFakeName)),
          // Verify the phone field.
          AllOf(Property(&FormFieldData::placeholder, kPhonePlaceholder),
                Property(&FormFieldData::value, kFakePhone))));
}

// Tests filling fields singularly, one by one, in a multi frame form.
// This tests the scenario where the user fills the form with the fill data
// from a suggestion they've selected.
TEST_F(AutofillAcrossIframesTest, DISABLED_FillMultiFrameForm_SingleField) {
  const std::u16string kNamePlaceholder = u"Name";
  const std::u16string kFakeName = u"Bob Bobbertson";
  const std::u16string kPhonePlaceholder = u"Phone";
  const std::u16string kFakePhone = u"18005551234";

  AddIframe("cf1", "<form><input type=\"text\" placeholder=\"" +
                       base::UTF16ToUTF8(kNamePlaceholder) +
                       "\" id=\"name-field\"></form>");
  AddIframe("cf2", "<form><input type=\"text\" placeholder=\"" +
                       base::UTF16ToUTF8(kPhonePlaceholder) +
                       "\" id=\"phone-field\"></form>");
  StartTestServerAndLoad();

  // Wait for the 3 forms to be reported as seen to the main frame that hosts
  // the browser form (which is the flattened representation of all forms in the
  // tree structure that share a common parent).
  ASSERT_TRUE(main_frame_manager().WaitForFormsSeen(3));
  ASSERT_EQ(main_frame_manager().seen_forms().size(), 3u);

  // Pick the last form that was seen which reflects the latest and most
  // complete state of the browser form.
  FormData form = main_frame_manager().seen_forms().back();
  ASSERT_EQ(form.child_frames().size(), 2u);
  ASSERT_EQ(form.fields().size(), 2u);

  std::vector<FormFieldData> fields = form.fields();

  FormFieldData* name_field =
      GetFieldWithPlaceholder(kNamePlaceholder, &fields);
  ASSERT_TRUE(name_field);
  FormFieldData* phone_field =
      GetFieldWithPlaceholder(kPhonePlaceholder, &fields);
  ASSERT_TRUE(phone_field);

  // Fill each field individually, one by one.
  main_frame_driver()->ApplyFieldAction(mojom::FieldActionType::kReplaceAll,
                                        mojom::ActionPersistence::kFill,
                                        name_field->global_id(), kFakeName);
  main_frame_driver()->ApplyFieldAction(mojom::FieldActionType::kReplaceAll,
                                        mojom::ActionPersistence::kFill,
                                        phone_field->global_id(), kFakePhone);

  // Verify that the name field was filled.
  {
    web::WebFrame* frame = GetFrameByID(name_field->host_frame().ToString());
    ASSERT_TRUE(frame);
    EXPECT_TRUE(WaitOnFieldFilledWithValue(frame, "name-field", kFakeName));
  }

  // Verify that the phone field was filled.
  {
    web::WebFrame* frame = GetFrameByID(phone_field->host_frame().ToString());
    ASSERT_TRUE(frame);
    EXPECT_TRUE(WaitOnFieldFilledWithValue(frame, "phone-field", kFakePhone));
  }
}

// Tests that the data from the multi frame browser form is passed upon
// submission. This tests the scenario where the user submits the form where
// they might be asked whether they want to save their profile.
TEST_F(AutofillAcrossIframesTest, SubmitMultiFrameForm) {
  const std::u16string kNamePlaceholder = u"Name";
  const std::u16string kFakeName = u"Bob Bobbertson";
  const std::u16string kPhonePlaceholder = u"Phone";
  const std::u16string kFakePhone = u"18005551234";

  AddIframe("cf1", "<form><input type=\"text\" placeholder=\"" +
                       base::UTF16ToUTF8(kNamePlaceholder) + "\"></form>");
  AddIframe("cf2", "<form><input type=\"text\" placeholder=\"" +
                       base::UTF16ToUTF8(kPhonePlaceholder) + "\"></form>");
  StartTestServerAndLoad();

  // Wait for the 3 forms to be reported as seen to the main frame that hosts
  // the browser form (which is the flattened representation of all forms in the
  // tree structure that share the form in the main frame as a common root).
  ASSERT_TRUE(main_frame_manager().WaitForFormsSeen(3));
  ASSERT_EQ(main_frame_manager().seen_forms().size(), 3u);

  // Pick the last form that was seen which reflects the latest and most
  // complete state of the browser form.
  const FormData& form = main_frame_manager().seen_forms().back();
  ASSERT_EQ(form.child_frames().size(), 2u);
  ASSERT_EQ(form.fields().size(), 2u);

  std::vector<FieldGlobalId> field_global_ids(form.fields().size());
  base::ranges::transform(
      form.fields(), field_global_ids.begin(),
      [](const FormFieldData& field) { return field.global_id(); });

  main_frame_driver()->FormSubmitted(main_frame_manager().seen_forms().front(),
                                     /*known_success=*/true,
                                     mojom::SubmissionSource::FORM_SUBMISSION);

  // Wait on the main frame form to report itself as submitted, which is the
  // only form in the forms tree that was submitted.
  ASSERT_TRUE(main_frame_manager().WaitForFormsSubmitted(1));
  ASSERT_EQ(main_frame_manager().submitted_forms().size(), 1u);

  // Verify that the submitted form represent the browser form across frames.
  const FormData& submitted_form = main_frame_manager().submitted_forms()[0];
  EXPECT_THAT(submitted_form.fields(),
              UnorderedElementsAre(
                  Property(&FormFieldData::global_id, field_global_ids[0]),
                  Property(&FormFieldData::global_id, field_global_ids[1])));
}

// Tests that, when asked for, there is a query made to retrive fill data for
// the entire browser form, across frames. This tests the scenario where
// Autofill suggestions are provided to the user upon taping on one of the
// fields in the form.
TEST_F(AutofillAcrossIframesTest, AskForFillDataOnMultiFrameForm) {
  const std::u16string kNamePlaceholder = u"Name";
  const std::u16string kFakeName = u"Bob Bobbertson";
  const std::u16string kPhonePlaceholder = u"Phone";
  const std::u16string kFakePhone = u"18005551234";

  AddIframe("cf1", "<form><input type=\"text\" placeholder=\"" +
                       base::UTF16ToUTF8(kNamePlaceholder) + "\"></form>");
  AddIframe("cf2", "<form><input type=\"text\" placeholder=\"" +
                       base::UTF16ToUTF8(kPhonePlaceholder) + "\"></form>");
  StartTestServerAndLoad();

  // Wait for the 3 forms to be reported as seen to the main frame that hosts
  // the browser form (which is the flattened representation of all forms in the
  // tree structure that share the form in the main frame as a common root).
  ASSERT_TRUE(main_frame_manager().WaitForFormsSeen(3));
  ASSERT_EQ(main_frame_manager().seen_forms().size(), 3u);

  // Pick the last form that was seen which reflects the latest and most
  // complete state of the browser form.
  FormData form = main_frame_manager().seen_forms().back();
  ASSERT_EQ(form.child_frames().size(), 2u);
  ASSERT_EQ(form.fields().size(), 2u);

  std::vector<FieldGlobalId> field_global_ids(form.fields().size());
  base::ranges::transform(
      form.fields(), field_global_ids.begin(),
      [](const FormFieldData& field) { return field.global_id(); });

  std::vector<FormFieldData> fields = form.fields();

  FormFieldData* name_field =
      GetFieldWithPlaceholder(kNamePlaceholder, &fields);

  main_frame_driver()->AskForValuesToFill(form, name_field->global_id());

  // Wait on the main frame form to report itself as having fill data for the
  // entire browser form, across frames.
  ASSERT_TRUE(main_frame_manager().WaitForFormsAskedForFillData(1));
  ASSERT_EQ(main_frame_manager().ask_for_filldata_forms().size(), 1u);

  // Verify that the form that we ask fill data for represents the browser form
  // across frames.
  const FormData& filldata_form =
      main_frame_manager().ask_for_filldata_forms()[0];
  EXPECT_THAT(filldata_form.fields(),
              UnorderedElementsAre(
                  Property(&FormFieldData::global_id, field_global_ids[0]),
                  Property(&FormFieldData::global_id, field_global_ids[1])));
}

// Tests that any text change on one of the child frames is correctly routed
// to the parent form where it represents the whole browser form.
TEST_F(AutofillAcrossIframesTest, TextChangeOnMultiFrameForm) {
  const std::u16string kNamePlaceholder = u"Name";
  const std::u16string kFakeName = u"Bob Bobbertson";
  const std::u16string kPhonePlaceholder = u"Phone";
  const std::u16string kFakePhone = u"18005551234";

  AddIframe("cf1", "<form><input type=\"text\" placeholder=\"" +
                       base::UTF16ToUTF8(kNamePlaceholder) + "\"></form>");
  AddIframe("cf2", "<form><input type=\"text\" placeholder=\"" +
                       base::UTF16ToUTF8(kPhonePlaceholder) + "\"></form>");
  StartTestServerAndLoad();

  // Wait for the 3 forms to be reported as seen to the main frame that hosts
  // the browser form (which is the flattened representation of all forms in the
  // tree structure that share the form in the main frame as a common root).
  ASSERT_TRUE(main_frame_manager().WaitForFormsSeen(3));
  ASSERT_EQ(main_frame_manager().seen_forms().size(), 3u);

  // Pick the last form that was seen which reflects the latest and most
  // complete state of the browser form.
  FormData form = main_frame_manager().seen_forms().back();
  ASSERT_EQ(form.child_frames().size(), 2u);
  ASSERT_EQ(form.fields().size(), 2u);

  std::vector<FieldGlobalId> field_global_ids(form.fields().size());
  base::ranges::transform(
      form.fields(), field_global_ids.begin(),
      [](const FormFieldData& field) { return field.global_id(); });

  std::vector<FormFieldData> fields = form.fields();

  FormFieldData* name_field =
      GetFieldWithPlaceholder(kNamePlaceholder, &fields);

  main_frame_driver()->TextFieldDidChange(form, name_field->global_id(),
                                          base::TimeTicks::Now());

  // Wait on the main frame form to report itself as having fill data for the
  // entire browser form, across frames.
  ASSERT_TRUE(main_frame_manager().WaitOnTextFieldDidChange(1));
  ASSERT_EQ(main_frame_manager().text_filled_did_change_forms().size(), 1u);

  // Verify that the form that we ask fill data for represents the browser form
  // across frames.
  const FormData& text_filled_form =
      main_frame_manager().text_filled_did_change_forms()[0];
  EXPECT_THAT(text_filled_form.fields(),
              UnorderedElementsAre(
                  Property(&FormFieldData::global_id, field_global_ids[0]),
                  Property(&FormFieldData::global_id, field_global_ids[1])));
}

// Tests that frame deletion is taken into consideration where the browser form
// is updated accordingly.
TEST_F(AutofillAcrossIframesTest, UpdateOnFrameDeletion) {
  const std::u16string kNamePlaceholder = u"Name";
  const std::u16string kFakeName = u"Bob Bobbertson";
  const std::u16string kPhonePlaceholder = u"Phone";
  const std::u16string kFakePhone = u"18005551234";

  AddIframe("cf1", "<form><input type=\"text\" placeholder=\"" +
                       base::UTF16ToUTF8(kNamePlaceholder) + "\"></form>");
  AddIframe("cf2", "<form><input type=\"text\" placeholder=\"" +
                       base::UTF16ToUTF8(kPhonePlaceholder) + "\"></form>");
  StartTestServerAndLoad();

  // Wait for the 3 forms to be reported as seen to the main frame that hosts
  // the browser form (which is the flattened representation of all forms in the
  // tree structure that share the form in the main frame as a common root).
  ASSERT_TRUE(main_frame_manager().WaitForFormsSeen(3));
  ASSERT_EQ(main_frame_manager().seen_forms().size(), 3u);

  // Pick the last form that was seen which reflects the latest and most
  // complete state of the browser form.
  FormData form = main_frame_manager().seen_forms().back();
  ASSERT_EQ(form.child_frames().size(), 2u);
  ASSERT_EQ(form.fields().size(), 2u);

  ASSERT_TRUE(ExecuteJavaScriptInFrame(
      WaitForMainFrame(),
      u"document.forms[0].getElementsByTagName('iframe')[0].remove();"));

  base::flat_map<FieldGlobalId, FieldType> field_type_map;

  std::vector<FormFieldData> fields = form.fields();

  FormFieldData* name_field =
      GetFieldWithPlaceholder(kNamePlaceholder, &fields);
  FormFieldData* phone_field =
      GetFieldWithPlaceholder(kPhonePlaceholder, &fields);

  // Set fill data for name field.
  SetFillDataForField(kFakeName, FieldType::NAME_FULL, name_field,
                      &field_type_map);
  // Set fill data for phone field.
  SetFillDataForField(kFakePhone, FieldType::PHONE_HOME_NUMBER, phone_field,
                      &field_type_map);

  // Attempt to fill the 2 fields in the browser form while there is actually
  // only one.
  ASSERT_THAT(main_frame_driver()->ApplyFormAction(
                  mojom::FormActionType::kFill, mojom::ActionPersistence::kFill,
                  fields, form.main_frame_origin(), field_type_map),
              SizeIs(1));

  // Wait on the fill to be done.
  ASSERT_TRUE(main_frame_manager().WaitForFormsFilled(1));
  ASSERT_EQ(main_frame_manager().filled_forms().size(), 1u);

  // Verify that the form was updated to take into consideration the deleted
  // frame where the is only one field that is actually filled.
  FormData filled_form = main_frame_manager().filled_forms().back();
  EXPECT_THAT(
      filled_form.fields(),
      UnorderedElementsAre(
          // Verify the phone field.
          AllOf(Property(&FormFieldData::placeholder, kPhonePlaceholder),
                Property(&FormFieldData::value, kFakePhone))));
}

// Tests that form deletion in a child frame is taken into consideration where
// the parent browser form is updated accordingly.
TEST_F(AutofillAcrossIframesTest, UpdateOnFormDeletion) {
  AddIframe("cf1", "<form><input type=\"text\"></form>");
  AddIframe("cf2", "<form><input type=\"text\"></form>");
  StartTestServerAndLoad();

  // Wait for the 3 forms to be reported as seen to the main frame that hosts
  // the browser form (which is the flattened representation of all forms in the
  // tree structure that share the form in the main frame as a common root).
  ASSERT_TRUE(main_frame_manager().WaitForFormsSeen(3));
  ASSERT_EQ(main_frame_manager().seen_forms().size(), 3u);

  FormGlobalId browser_form_global_id;
  {
    const FormData& form = main_frame_manager().seen_forms().back();
    browser_form_global_id = form.global_id();
    // There should be 2 fields in the initial browser form.
    ASSERT_EQ(2u, form.fields().size());
  }

  main_frame_manager().ResetTestState();

  {
    // Remove form in the top child frame.
    const std::u16string script =
        u"const frame = document.querySelector('iframe'); "
        "frame.contentWindow.eval('document.forms[0].remove()');";
    ASSERT_TRUE(ExecuteJavaScriptInFrame(WaitForMainFrame(), script));
  }

  // Wait for the deleted form to be reported as seen to the main frame that
  // hosts the browser form.
  ASSERT_TRUE(main_frame_manager().WaitForFormsSeen(1));
  ASSERT_EQ(main_frame_manager().removed_forms().size(), 1u);

  // Verify that the field count is now 1 for the xframes browser form since
  // there was one form containing one field that was deleted.
  FormStructure* form =
      main_frame_manager().FindCachedFormById(browser_form_global_id);
  ASSERT_TRUE(form);
  EXPECT_EQ(1u, form->field_count());
}

// Tests that synthethic form deletion in a child frame is taken into
// consideration where the parent browser form is updated accordingly.
TEST_F(AutofillAcrossIframesTest, UpdateOnFormDeletion_Synthetic) {
  AddIframe("cf1", "<div id=\"form1\"><input type=\"text\">"
                   "<input type=\"text\"></div>");
  AddIframe("cf2", "<div id=\"form1\"><input type=\"text\">"
                   "<input type=\"text\"></div>");
  StartTestServerAndLoad();

  // Wait for the 3 forms to be reported as seen to the main frame that hosts
  // the browser form (which is the flattened representation of all forms in the
  // tree structure that share the form in the main frame as a common root).
  ASSERT_TRUE(main_frame_manager().WaitForFormsSeen(3));
  ASSERT_EQ(main_frame_manager().seen_forms().size(), 3u);

  FormGlobalId browser_form_global_id;
  {
    const FormData& form = main_frame_manager().seen_forms().back();
    browser_form_global_id = form.global_id();
    // There should be 4 fields in the initial browser form, 2 per frame.
    ASSERT_EQ(4u, form.fields().size());
  }

  main_frame_manager().ResetTestState();

  {
    // Remove all input fields in the top child frame, to repoduce synthetic
    // form deletion.
    const std::u16string script =
        u"const frame = document.querySelector('iframe'); "
        "frame.contentWindow.eval(\"document.getElementById('form1')"
        ".remove()\");";
    ASSERT_TRUE(ExecuteJavaScriptInFrame(WaitForMainFrame(), script));
  }

  // Wait for the deleted form to be reported as seen to the main frame that
  // hosts the browser form.
  ASSERT_TRUE(main_frame_manager().WaitForFormsSeen(1));
  ASSERT_EQ(main_frame_manager().removed_forms().size(), 1u);

  // Verify that the field count is now 2 for the xframe browser form since
  // the synthetic form in one of the frames was deleted.
  FormStructure* form =
      main_frame_manager().FindCachedFormById(browser_form_global_id);
  ASSERT_TRUE(form);
  EXPECT_EQ(2u, form->field_count());
}

// Tests that the partial deletion of fields in the synthethic form of a child
// frame isn't reported as form removal since the synthetic still remains.
TEST_F(AutofillAcrossIframesTest, UpdateOnFormDeletion_Synthetic_Partial) {
  AddIframe("cf1", "<input type=\"text\">"
                   "<input type=\"text\">");
  AddIframe("cf2", "<input type=\"text\">"
                   "<input type=\"text\">");
  StartTestServerAndLoad();

  // Wait for the 3 forms to be reported as seen to the main frame that hosts
  // the browser form (which is the flattened representation of all forms in the
  // tree structure that share the form in the main frame as a common root).
  ASSERT_TRUE(main_frame_manager().WaitForFormsSeen(3));
  ASSERT_EQ(main_frame_manager().seen_forms().size(), 3u);

  FormGlobalId browser_form_global_id;
  {
    const FormData& form = main_frame_manager().seen_forms().back();
    browser_form_global_id = form.global_id();
    // There should be 4 fields in the initial browser form, 2 per frame.
    ASSERT_EQ(4u, form.fields().size());
  }

  main_frame_manager().ResetTestState();

  {
    // Remove one input field in the top child frame without entirely deleting
    // the synthethic form.
    const std::u16string script =
        u"const frame = document.querySelector('iframe'); "
        "frame.contentWindow.eval(\"document.querySelector('input').remove()\")"
        ";";
    ASSERT_TRUE(ExecuteJavaScriptInFrame(WaitForMainFrame(), script));
  }

  // Give some time to handle the deleted input field.
  base::test::ios::SpinRunLoopWithMinDelay(base::Milliseconds(200));

  // Verify that there we no forms reported as removed.
  EXPECT_EQ(main_frame_manager().removed_forms().size(), 0u);

  // Verify that the field count is still 4 for the xframe browser form since
  // the synthetic form in one of the frames was deleted.
  FormStructure* form =
      main_frame_manager().FindCachedFormById(browser_form_global_id);
  ASSERT_TRUE(form);
  EXPECT_EQ(4u, form->field_count());
}

// Tests that double registration is correctly notified.
TEST_F(AutofillAcrossIframesTest, FrameDoubleRegistration_Notify) {
  const std::u16string kNamePlaceholder = u"Name";
  const std::u16string kFakeName = u"Bob Bobbertson";
  const std::u16string kPhonePlaceholder = u"Phone";
  const std::u16string kFakePhone = u"18005551234";

  AddIframe("cf1", "<form><input type=\"text\" placeholder=\"" +
                       base::UTF16ToUTF8(kNamePlaceholder) + "\"></form>");
  AddIframe("cf2", "<form><input type=\"text\" placeholder=\"" +
                       base::UTF16ToUTF8(kPhonePlaceholder) + "\"></form>");
  StartTestServerAndLoad();

  // Wait for the 3 forms to be reported as seen to the main frame that hosts
  // the browser form (which is the flattened representation of all forms in the
  // tree structure that share the form in the main frame as a common root).
  ASSERT_TRUE(main_frame_manager().WaitForFormsSeen(3));
  ASSERT_EQ(main_frame_manager().seen_forms().size(), 3u);

  // Pick the last form that was seen which reflects the latest and most
  // complete state of the browser form.
  FormData form = main_frame_manager().seen_forms().back();
  ASSERT_EQ(form.child_frames().size(), 2u);
  ASSERT_EQ(form.fields().size(), 2u);

  main_frame_manager().ResetTestState();

  // Inject the spoofy frame that will attempt double registration.
  {
    std::u16string script = u"const doc = `<body><form>"
                            "<input type=\"text\" placeholder=\"Stolen Name\">"
                            "</form></body>`;"
                            "const iframe = document.createElement('iframe');"
                            "iframe.srcdoc = doc;"
                            "document.body.appendChild(iframe); true";
    ASSERT_TRUE(ExecuteJavaScriptInFrame(WaitForMainFrame(), script));
  }

  web::WebFrame* spoofy_frame = WaitForNewFrame();
  ASSERT_TRUE(spoofy_frame);

  TestAutofillManager* spoofy_manager = GetManagerForFrame(spoofy_frame);
  ASSERT_TRUE(spoofy_manager);

  // Wait for the spoofy frame forms to be seen so they were be ingested by the
  // system.
  ASSERT_TRUE(spoofy_manager->WaitForFormsSeen(1));
  ASSERT_EQ(spoofy_manager->seen_forms().size(), 1u);

  // Pick the last form that was seen which reflects the latest and most
  // complete state of the browser form, but should be the spoofy form in this
  // case as this is in a separate tree from the other browser form (a single
  // node in this case).
  FormData spoofy_form = spoofy_manager->seen_forms().back();
  ASSERT_EQ(spoofy_form.fields().size(), 1u);

  MockRegistrarObserver registrar_observer;
  base::ScopedObservation<autofill::ChildFrameRegistrar,
                          autofill::ChildFrameRegistrarObserver>
      registrar_scoped_observation{&registrar_observer};
  registrar_scoped_observation.Observe(registrar());

  RemoteFrameToken stolen_remote_token =
      absl::get<RemoteFrameToken>(form.child_frames()[0].token);
  std::optional<LocalFrameToken> attacked_frame =
      registrar()->LookupChildFrame(stolen_remote_token);
  ASSERT_TRUE(attacked_frame);

  // Expect that double registration is notified for the frame that was attacked
  // which has its remote token stolen.
  EXPECT_CALL(registrar_observer, OnDidDoubleRegistration(*attacked_frame))
      .Times(1);

  {
    std::string unformatted_script =
        "__gCrWeb.common.sendWebKitMessage('FormHandlersMessage', "
        "{'command': 'registerAsChildFrame', 'local_frame_id': "
        "__gCrWeb.frameId, 'remote_frame_id':'%s'});";
    std::u16string script = base::UTF8ToUTF16(base::StringPrintf(
        unformatted_script.c_str(), stolen_remote_token.ToString().c_str()));
    ASSERT_TRUE(ExecuteJavaScriptInFrame(spoofy_frame, script));
  }
}

// Tests that a frame can be unregistered without necessarily being deleted when
// detecting a spoofing attempt for example.
TEST_F(AutofillAcrossIframesTest, FrameDoubleRegistration_Unregister) {
  const std::u16string kNamePlaceholder = u"Name";
  const std::u16string kFakeName = u"Bob Bobbertson";
  const std::u16string kPhonePlaceholder = u"Phone";
  const std::u16string kFakePhone = u"18005551234";

  AddIframe("cf1", "<form><input type=\"text\" placeholder=\"" +
                       base::UTF16ToUTF8(kNamePlaceholder) + "\"></form>");
  AddIframe("cf2", "<form><input type=\"text\" placeholder=\"" +
                       base::UTF16ToUTF8(kPhonePlaceholder) + "\"></form>");
  StartTestServerAndLoad();

  // Wait for the 3 forms to be reported as seen to the main frame that hosts
  // the browser form (which is the flattened representation of all forms in the
  // tree structure that share the form in the main frame as a common root).
  ASSERT_TRUE(main_frame_manager().WaitForFormsSeen(3));
  ASSERT_EQ(main_frame_manager().seen_forms().size(), 3u);

  // Pick the last form that was seen which reflects the latest and most
  // complete state of the browser form.
  FormData browser_form = main_frame_manager().seen_forms().back();
  ASSERT_EQ(browser_form.child_frames().size(), 2u);
  ASSERT_EQ(browser_form.fields().size(), 2u);

  std::vector<FormFieldData> fields_to_fill = browser_form.fields();

  FormFieldData* name_field =
      GetFieldWithPlaceholder(kNamePlaceholder, &fields_to_fill);
  FormFieldData* phone_field =
      GetFieldWithPlaceholder(kPhonePlaceholder, &fields_to_fill);
  ASSERT_TRUE(name_field && phone_field);

  // Pick one non-main frame to unregister based on field, the name field in
  // this case. Since there is only one frame per field, we know that deleting
  // the frame will only concern that field.
  const LocalFrameToken frame_to_unregister = name_field->host_frame();

  // Unregister the frame (via the driver) of the name field.
  {
    auto* driver = AutofillDriverIOS::FromWebStateAndLocalFrameToken(
        web_state(), frame_to_unregister);
    ASSERT_TRUE(driver);
    driver->Unregister();
  }

  base::flat_map<FieldGlobalId, FieldType> field_type_map;

  // Set fill data for both fields.
  SetFillDataForField(kFakeName, FieldType::NAME_FULL, name_field,
                      &field_type_map);
  SetFillDataForField(kFakePhone, FieldType::PHONE_HOME_NUMBER, phone_field,
                      &field_type_map);

  // Verify that the only the phone field will be filled, where the name field
  // in the unregistered frame shouldn't be filled.
  EXPECT_THAT(
      main_frame_driver()->ApplyFormAction(
          mojom::FormActionType::kFill, mojom::ActionPersistence::kFill,
          fields_to_fill, browser_form.main_frame_origin(), field_type_map),
      UnorderedElementsAre(phone_field->global_id()));

  main_frame_manager().ResetTestState();
}

// Tests that forms aren't parsed when their host frame ID differs from the ID
// of the frame on which forms extraction was requested.
TEST_F(AutofillAcrossIframesTest, FrameAndFormIdsDontMatch) {
  // Serve form on main frame.
  AddInput("text", "name");
  AddInput("text", "address");
  StartTestServerAndLoad();

  // Verify that the form can be parsed, intially.
  ASSERT_TRUE(main_frame_manager().WaitForFormsSeen(1));
  ASSERT_EQ(main_frame_manager().seen_forms().size(), 1u);
  main_frame_manager().ResetTestState();

  // Change the ID of the main frame on the renderer side but not in the
  // browser, making the two IDs different.
  {
    web::WebFrame* main_frame = WaitForMainFrame();
    std::string new_frame_id = main_frame->GetFrameId();
    // Reverse the main frame id to make it a brand new id.
    std::reverse(new_frame_id.begin(), new_frame_id.end());

    // Change the frame ID provided by getFrameId() to simulate a different
    // frame receiving the forms extraction request.
    std::u16string script = u"__gCrWeb.message.getFrameId = () => "
                            "'1effd8f52a067c8d3a01762d3c41dfd8'; true";
    ASSERT_TRUE(ExecuteJavaScriptInFrame(main_frame, script));
  }

  // Trigger extraction on the `main_frame` where the frame ID obtained within
  // the script during extraction is different from the ID the main frame was
  // initially registered with.
  test_api(*main_frame_driver()).TriggerFormExtractionInDriverFrame();

  // Give enough time for the JS request to be done.
  base::test::ios::SpinRunLoopWithMinDelay(base::Seconds(2));

  // Verify that no forms could be parsed (hence seen) this time because the
  // forms had a different frame ID than the frame ID for the request hence the
  // extracted forms couldn't be parsed, resulting in no forms seen.
  ASSERT_EQ(main_frame_manager().seen_forms().size(), 0u);
}

// Ensure that disabling the feature actually disables the feature.
TEST_F(AutofillAcrossIframesTest, FeatureDisabled) {
  base::test::ScopedFeatureList disable;
  disable.InitAndDisableFeature(features::kAutofillAcrossIframesIos);

  AddIframe("cf1", "child frame 1");
  AddInput("text", "name");
  AddIframe("cf2", "child frame 2");
  AddInput("text", "address");
  StartTestServerAndLoad();

  ASSERT_TRUE(main_frame_manager().WaitForFormsSeen(1));
  ASSERT_EQ(main_frame_manager().seen_forms().size(), 1u);

  const FormData& form = main_frame_manager().seen_forms()[0];
  EXPECT_EQ(form.child_frames().size(), 0u);

  EXPECT_FALSE(
      autofill::ChildFrameRegistrar::GetOrCreateForWebState(web_state()));
}

// Suite of tests that focuses on testing the security of xframe filling.
//
// These tests verify that the filling horizon is respected for the ios
// implementation, where (1) the main frame can be filled with non-sensitive
// data, (2) rule (1) also applies to direct child frames of the main frame that
// are on the same origin as the main frame, and (3) the frames on the same
// origin as the trigger field can be filled. No special permissions can be
// granted from the element itself.
using AutofillAcrossIframesFillSecurityTest = AutofillAcrossIframesTest;

// Tests filling a credit card form from a cross origin frame as the trigger.
// Also tests that fields on the main origin can be filled with non-sensitive
// data.
//
// Representation of the tested xframe form structure with the expected outcome
// in [] next to each input field and trigger field indicated with <--:
// =======================================
// Main Frame
//   Input: name [filled]
//   Iframe (origin1):
//     Input: cc number [filled] <--
//   Iframe (main origin):
//     Input: exp date [filled]
//   Iframe (origin1):
//     Input: cvc [filled]
// =======================================
TEST_F(AutofillAcrossIframesFillSecurityTest, XoriginTrigger) {
  EmbeddedTestServer test_server1;

  TestCreditCardForm cc_form_info = GetTestCreditCardForm();

  // Serve the cc number and exp fields from the other origin. They are all on
  // the same origin.
  ServeCrossOriginDocument("cf1", cc_form_info.cc_number_field.ToHtmlForm(),
                           &test_server1);
  ServeCrossOriginDocument("cf3", cc_form_info.cvc_field.ToHtmlForm(),
                           &test_server1);
  ASSERT_TRUE(test_server1.Start());

  // Add the name input to the main frame.
  AddInput(cc_form_info.name_field);
  // Add iframe on the other origin that holds the credit card number.
  AddCrossOriginIframe("cf1", &test_server1);
  // Add iframe on main origin holding the exp field.
  AddIframe("cf2", cc_form_info.exp_field.ToHtmlForm());
  // Add iframe on the other holding the cvc field.
  AddCrossOriginIframe("cf3", &test_server1);

  // Start serving main frame content.
  StartTestServerAndLoad();

  // Wait on the browser form to be fully constructed from both the frame on the
  // main origin and the other cross origin frames, totalling 4 fields.
  const auto [browser_form, res] =
      WaitForCompleteBrowserForm(/*child_frames_count=*/3, /*fields_count=*/4);
  ASSERT_TRUE(res);

  // Fill and verify that all the fields are filled.
  FillAndVerify(cc_form_info, browser_form, cc_form_info.cc_number_field,
                cc_form_info.all_fields());
}

// Test that the shared-autofill permission isn't propagated to the nested
// frames on the main origin that aren't a direct children of the main
// frame. Fields on the same origin as the trigger field should be filled even
// if nested.
//
// Representation of the tested xframe form structure with the expected outcome
// in [] next to each input field and the trigger field indicated with <--:
// =======================================
// Main Frame
//   Iframe (main origin):
//     Iframe (main origin):
//       Input: name [not filled]
//   Iframe (origin1):
//     Input: cc number [filled] <--
//   Iframe (origin1):
//     Iframe (main origin):
//       Input: exp date [not filled]
//   Iframe (origin2):
//     Iframe (origin1)
//       Input: cvc [filled]
// =======================================
TEST_F(AutofillAcrossIframesFillSecurityTest, XoriginTrigger_NestedFrame) {
  EmbeddedTestServer test_server1;
  EmbeddedTestServer test_server2;

  TestCreditCardForm cc_form_info = GetTestCreditCardForm();

  // Serve documents in frames.

  // Serve the document with the name field on the main origin.
  ServeDocument("cf1a", cc_form_info.name_field.ToHtmlForm());
  // Serve document with the CC number field.
  ServeCrossOriginDocument("cf2", cc_form_info.cc_number_field.ToHtmlForm(),
                           &test_server1);
  // Serve empty document where we will inject the iframe with the expiry date
  // field later.
  ServeCrossOriginDocument("cf3", "<body></body>", &test_server1);
  // Serve the document with the exp field on the main origin.
  ServeDocument("cf3a", cc_form_info.exp_field.ToHtmlForm());
  // Serve empty document where we will inject the iframe with cvc field later.
  ServeCrossOriginDocument("cf4", "<body></body>", &test_server2);
  // Serve document with the CVC number field.
  ServeCrossOriginDocument("cf4a", cc_form_info.cvc_field.ToHtmlForm(),
                           &test_server1);
  ASSERT_TRUE(test_server1.Start());
  ASSERT_TRUE(test_server2.Start());

  // Add iframes to the main page.

  // Add iframe that hosts another nested iframe holding the name field, a
  // non-sensitive field. Both frames are from the main origin.
  AddIframe("cf1", "<body><iframe src='/cf1a'></iframe></body>");
  // Add iframe on another holding the credit card number field, a
  // sensitive field.
  AddCrossOriginIframe("cf2", &test_server1);
  // Add iframe on another origin that hosts another nested iframe on the main
  // origin holding the expiry date, a non-sensitive field.
  AddCrossOriginIframe("cf3", &test_server1);
  // Add iframe on another origin that hosts another nested iframe holding the
  // cvc field, a sensitive field.
  AddCrossOriginIframe("cf4", &test_server2);

  // Start serving main frame content.
  StartTestServerAndLoad();

  // Wait on the browser form to be fully constructed from both the frame on the
  // main origin and the other cross origin frames, totalling 2 fields. At this
  // state 2 of the 4 child frames are empty in which we will inject the missing
  // fields later.
  const auto [browser_form, res] =
      WaitForCompleteBrowserForm(/*child_frames_count=*/4, /*fields_count=*/2);
  ASSERT_TRUE(res);

  // Injects an iframe sourced from `server` at `path` as a child frame of the
  // frame that corresponds to `parent_remote_token`. Update the tree to take
  // the new frame.
  const auto InjectNewIframe = [&](RemoteFrameToken parent_remote_token,
                                   const EmbeddedTestServer& server,
                                   const std::string& path) {
    std::optional<LocalFrameToken> parent_frame_token =
        registrar()->LookupChildFrame(parent_remote_token);
    ASSERT_TRUE(parent_frame_token);
    web::WebFrame* parent_frame = GetFrameByID(parent_frame_token->ToString());
    ASSERT_TRUE(parent_frame);

    const std::u16string full_path =
        base::UTF8ToUTF16(server.GetURL(path).spec());

    // Inject nested frame in its parent frame.
    const std::u16string script =
        u"const iframe = document.createElement('iframe');"
        "iframe.src = '" +
        full_path +
        u"';"
        "document.body.appendChild(iframe); true";
    ASSERT_TRUE(ExecuteJavaScriptInFrame(parent_frame, script));

    web::WebFrame* new_frame = WaitForNewFrame();

    TestAutofillManager* new_frame_manager = GetManagerForFrame(new_frame);
    ASSERT_TRUE(new_frame);

    // Wait for the new frame forms to be seen so they can be ingested by
    // the system.
    ASSERT_TRUE(new_frame_manager->WaitForFormsSeen(1));
    ASSERT_EQ(new_frame_manager->seen_forms().size(), 1u);

    main_frame_manager().ResetTestState();

    // Re-trigger form extraction to add the new child frame to the tree.
    // This won't be needed anymore once we uncouple registration from form
    // extraction (crbug.com/358334625).
    auto* parent_frame_driver =
        AutofillDriverIOS::FromWebStateAndWebFrame(web_state(), parent_frame);
    test_api(*parent_frame_driver).TriggerFormExtractionInDriverFrame();
    ASSERT_TRUE(main_frame_manager().WaitForFormsSeen(2));
    ASSERT_EQ(main_frame_manager().seen_forms().size(), 2u);
  };

  ASSERT_THAT(browser_form.child_frames(), SizeIs(4));

  // Inject the frame holding the expiry date.
  InjectNewIframe(
      absl::get<RemoteFrameToken>(browser_form.child_frames()[2].token),
      test_server_, "/cf3a");
  // Inject the frame holding the cvc number.
  InjectNewIframe(
      absl::get<RemoteFrameToken>(browser_form.child_frames()[3].token),
      test_server1, "/cf4a");

  // Fill and verify that all the fields are filled.
  FillAndVerify(cc_form_info, main_frame_manager().seen_forms().back(),
                cc_form_info.cc_number_field,
                {cc_form_info.cc_number_field, cc_form_info.cvc_field});
}

// Tests that only the frame on the trigger origin is filled when all other
// frames are another origin that isn't the main frame origin. Testing that
// filling is correctly siloed. Triggers filling from each frame.
//
// Representation of the tested xframe form structure with the expected outcome
// in [] next to each input field and the trigger field indicated with <--:
// =======================================
// Main Frame
//   Iframe (origin1):
//     Input: name [filled] <-- #1
//   Iframe (origin2):
//     Input: cc number [filled] <-- #2
//   Iframe (origin3):
//     Input: exp date [filled] <-- #3
//   Iframe (origin4):
//     Input: cvc [filled] <-- #4
// =======================================
TEST_F(AutofillAcrossIframesFillSecurityTest,
       Fill_MultiFrameForm_XoriginTrigger_Siloed) {
  EmbeddedTestServer test_server1;
  EmbeddedTestServer test_server2;
  EmbeddedTestServer test_server3;
  EmbeddedTestServer test_server4;

  TestCreditCardForm cc_form_info = GetTestCreditCardForm();

  // Serve all fields their own specific origin.
  ServeCrossOriginDocument("cf1", cc_form_info.name_field.ToHtmlForm(),
                           &test_server1);
  ServeCrossOriginDocument("cf2", cc_form_info.cc_number_field.ToHtmlForm(),
                           &test_server2);
  ServeCrossOriginDocument("cf3", cc_form_info.exp_field.ToHtmlForm(),
                           &test_server3);
  ServeCrossOriginDocument("cf4", cc_form_info.cvc_field.ToHtmlForm(),
                           &test_server4);
  ASSERT_TRUE(test_server1.Start());
  ASSERT_TRUE(test_server2.Start());
  ASSERT_TRUE(test_server3.Start());
  ASSERT_TRUE(test_server4.Start());

  // Hold each field in an iframe on a different origin.
  AddCrossOriginIframe("cf1", &test_server1);
  AddCrossOriginIframe("cf2", &test_server2);
  AddCrossOriginIframe("cf3", &test_server3);
  AddCrossOriginIframe("cf4", &test_server4);

  // Start serving main frame content.
  StartTestServerAndLoad();

  // Wait on the browser form to be fully constructed including the fields
  // from all frames.
  const auto [browser_form, res] =
      WaitForCompleteBrowserForm(/*child_frames_count=*/4, /*fields_count=*/4);
  ASSERT_TRUE(res);

  std::vector<FormFieldData> fields = browser_form.fields();

  // Fill from each trigger field and verify that only the trigger field itself
  // is filled since this is the only field on the same origin as the trigger.
  // Verify that the fields in the form are only filled incrementaly, one by
  // one, as we are filling from the different origins.
  std::vector<FieldGlobalId> filled_fields_so_far;
  FillAndVerify(cc_form_info, browser_form, cc_form_info.name_field,
                {cc_form_info.name_field});
  filled_fields_so_far.push_back(
      CHECK_DEREF(GetFieldWithId(cc_form_info.name_field.id_attribute, fields))
          .global_id());
  EXPECT_TRUE(cc_form_info.VerifyFieldsAreCorrectlyFilled(
      web_frames_manager(), filled_fields_so_far));

  FillAndVerify(cc_form_info, browser_form, cc_form_info.cc_number_field,
                {cc_form_info.cc_number_field});
  filled_fields_so_far.push_back(
      CHECK_DEREF(
          GetFieldWithId(cc_form_info.cc_number_field.id_attribute, fields))
          .global_id());
  EXPECT_TRUE(cc_form_info.VerifyFieldsAreCorrectlyFilled(
      web_frames_manager(), filled_fields_so_far));

  FillAndVerify(cc_form_info, browser_form, cc_form_info.exp_field,
                {cc_form_info.exp_field});
  filled_fields_so_far.push_back(
      CHECK_DEREF(GetFieldWithId(cc_form_info.exp_field.id_attribute, fields))
          .global_id());
  EXPECT_TRUE(cc_form_info.VerifyFieldsAreCorrectlyFilled(
      web_frames_manager(), filled_fields_so_far));

  FillAndVerify(cc_form_info, browser_form, {cc_form_info.cvc_field},
                {cc_form_info.cvc_field});
  filled_fields_so_far.push_back(
      CHECK_DEREF(GetFieldWithId(cc_form_info.cvc_field.id_attribute, fields))
          .global_id());
  EXPECT_TRUE(cc_form_info.VerifyFieldsAreCorrectlyFilled(
      web_frames_manager(), filled_fields_so_far));
}

// Tests that sensitive information isn't filled on the main origin when the
// trigger is from another origin.
//
// Representation of the tested xframe form structure with the expected outcome
// in [] next to each input field and the trigger field indicated with <--:
// =======================================
// Main Frame
//   Iframe (origin1):
//     Input: name [filled] <--
//   Input: cc number [not filled]
//   Iframe (origin1):
//     Input: exp date [filled]
//   Iframe (main origin):
//     Input: cvc [not filled]
// =======================================
TEST_F(AutofillAcrossIframesFillSecurityTest,
       XoriginTrigger_SensitiveFieldsOnMainOrigin) {
  EmbeddedTestServer test_server1;

  TestCreditCardForm cc_form_info = GetTestCreditCardForm();

  // Serve the cc number and exp fields from the other origin.
  ServeCrossOriginDocument("cf1", cc_form_info.name_field.ToHtmlForm(),
                           &test_server1);
  ServeCrossOriginDocument("cf2", cc_form_info.exp_field.ToHtmlForm(),
                           &test_server1);
  ASSERT_TRUE(test_server1.Start());

  // Add iframe on another origin holding the name, a
  // non-sensitive field.
  AddCrossOriginIframe("cf1", &test_server1);
  // Add an input holding the credit card number on the main frame, a sensitive
  // field.
  AddInput(cc_form_info.cc_number_field);
  // Add iframe on another origin holding the expiry date, a
  // non-sensitive field.
  AddCrossOriginIframe("cf2", &test_server1);
  // Add iframe on the main frame origin holding the cvc field, a sensitive
  // field.
  AddIframe("cf3", cc_form_info.cvc_field.ToHtmlForm());

  // Start serving main frame content.
  StartTestServerAndLoad();

  // Wait on the browser form to be fully constructed from both the frame on the
  // main origin and the other cross origin frames, totalling 4 fields.
  const auto [browser_form, res] =
      WaitForCompleteBrowserForm(/*child_frames_count=*/3, /*fields_count=*/4);
  ASSERT_TRUE(res);

  std::vector<FormFieldData> fields = browser_form.fields();

  // Fill and verify that all the fields are filled.
  FillAndVerify(cc_form_info, browser_form, cc_form_info.name_field,
                {cc_form_info.name_field, cc_form_info.exp_field});
}

// Tests that sensitive information can be filled on the main origin when the
// trigger is also on the main origin. Fields on other origins shouldn't be
// filled regardless of their sensitivity.
//
// Representation of the tested xframe form structure with the expected outcome
// in [] next to each input field and the trigger field indicated with <--:
// =======================================
// Main Frame
//   Iframe (origin1):
//     Input: name [not filled]
//   Input: cc number [filled] <--
//   Iframe (origin1):
//     Input: exp date [not filled]
//   Iframe (main origin):
//     Input: cvc [filled]
// =======================================
TEST_F(AutofillAcrossIframesFillSecurityTest, MainOriginTrigger) {
  EmbeddedTestServer test_server1;

  TestCreditCardForm cc_form_info = GetTestCreditCardForm();

  // Serve the cc number and exp fields from the other origin.
  ServeCrossOriginDocument("cf1", cc_form_info.name_field.ToHtmlForm(),
                           &test_server1);
  ServeCrossOriginDocument("cf2", cc_form_info.exp_field.ToHtmlForm(),
                           &test_server1);
  ASSERT_TRUE(test_server1.Start());

  // Add iframe on another origin holding the name, a
  // non-sensitive field.
  AddCrossOriginIframe("cf1", &test_server1);

  // Add an input holding the credit card number on the main frame, a sensitive
  // field.
  AddInput(cc_form_info.cc_number_field);

  // Add iframe on another origin holding the expiry date, a
  // non-sensitive field.
  AddCrossOriginIframe("cf2", &test_server1);

  // Add iframe on the main frame origin holding the cvc field, a sensitive
  // field.
  AddIframe("cf3", cc_form_info.cvc_field.ToHtmlForm());

  // Start serving main frame content.
  StartTestServerAndLoad();

  // Wait on the browser form to be fully constructed from both the frame on the
  // main origin and the other cross origin frames, totalling 4 fields.
  const auto [browser_form, res] =
      WaitForCompleteBrowserForm(/*child_frames_count=*/3, /*fields_count=*/4);
  ASSERT_TRUE(res);

  std::vector<FormFieldData> fields = browser_form.fields();

  // Fill and verify that all the fields are filled.
  FillAndVerify(cc_form_info, browser_form, cc_form_info.cc_number_field,
                {cc_form_info.cc_number_field, cc_form_info.cvc_field});
}

}  // namespace autofill