blob: caf12461e4986488b86dee7d2c92bf4e0e7498fc [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "components/autofill/ios/browser/autofill_driver_ios.h"
#import <memory>
#import <optional>
#import "base/test/mock_callback.h"
#import "base/test/test_future.h"
#import "components/autofill/core/browser/test_utils/autofill_form_test_utils.h"
#import "components/autofill/core/browser/test_utils/autofill_test_utils.h"
#import "components/autofill/core/common/autofill_test_utils.h"
#import "components/autofill/core/common/form_data.h"
#import "components/autofill/core/common/form_field_data.h"
#import "components/autofill/core/common/unique_ids.h"
#import "components/autofill/ios/browser/autofill_driver_ios_factory.h"
#import "components/autofill/ios/browser/autofill_java_script_feature.h"
#import "components/autofill/ios/browser/test_autofill_client_ios.h"
#import "components/autofill/ios/form_util/child_frame_registrar.h"
#import "ios/web/public/test/fakes/fake_web_frame.h"
#import "ios/web/public/test/fakes/fake_web_frames_manager.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "ios/web/public/test/web_test.h"
#import "testing/gmock/include/gmock/gmock.h"
#import "testing/gtest/include/gtest/gtest.h"
// AutofillDriverIosBridge used for testing. Provides a simple implementation of
// the methods that are used during testing, e.g. call the completion block upon
// calling -fetchFormsFiltered.
@interface FakeAutofillDriverIOSBridge : NSObject <AutofillDriverIOSBridge>
- (instancetype)init;
- (void)setForms:(std::vector<autofill::FormData>)forms;
@end
@implementation FakeAutofillDriverIOSBridge {
std::vector<autofill::FormData> _forms;
}
- (instancetype)init {
if ((self = [super init])) {
_forms = {};
}
return self;
}
- (void)setForms:(std::vector<autofill::FormData>)forms {
_forms = std::move(forms);
}
- (void)fillData:(const std::vector<autofill::FormFieldData::FillData>&)fields
section:(const autofill::Section&)section
inFrame:(web::WebFrame*)frame {
}
- (void)fillSpecificFormField:(const autofill::FieldRendererId&)field
withValue:(const std::u16string)value
inFrame:(web::WebFrame*)frame {
}
- (void)handleParsedForms:
(const std::vector<
raw_ptr<autofill::FormStructure, VectorExperimental>>&)forms
inFrame:(web::WebFrame*)frame {
}
- (void)fillFormDataPredictions:
(const std::vector<autofill::FormDataPredictions>&)forms
inFrame:(web::WebFrame*)frame {
}
- (void)scanFormsInWebState:(web::WebState*)webState
inFrame:(web::WebFrame*)webFrame {
}
- (void)notifyFormsSeen:(const std::vector<autofill::FormData>&)updatedForms
inFrame:(web::WebFrame*)frame {
}
- (void)fetchFormsFiltered:(BOOL)filtered
withName:(const std::u16string&)formName
inFrame:(web::WebFrame*)frame
completionHandler:(FormFetchCompletion)completionHandler {
std::move(completionHandler).Run(_forms);
}
@end
namespace autofill {
namespace {
using testing::Eq;
using testing::Optional;
using testing::Property;
class AutofillDriverIOSTest : public web::WebTest {
protected:
void SetUp() override {
web::WebTest::SetUp();
OverrideJavaScriptFeatures(
{autofill::AutofillJavaScriptFeature::GetInstance()});
GURL url("https://example.com");
web_state_.SetCurrentURL(url);
web_state_.SetBrowserState(GetBrowserState());
web_state_.SetContentIsHTML(true);
// Define frames managers for both content worlds so that the
// `ChildFrameRegistrar` below can be created.
for (auto content_world : {web::ContentWorld::kIsolatedWorld,
web::ContentWorld::kPageContentWorld}) {
auto frames_manager = std::make_unique<web::FakeWebFramesManager>();
web_state_.SetWebFramesManager(content_world, std::move(frames_manager));
}
web_frames_manager_ = static_cast<web::FakeWebFramesManager*>(
web_state_.GetWebFramesManager(AutofillJavaScriptFeature::GetInstance()
->GetSupportedContentWorld()));
web::WebFrame* main_frame = CreateFrame(url, /*main_frame=*/true);
web::WebFrame* iframe = CreateFrame(url, /*main_frame=*/false);
// Create the main Autofill classes.
test_bridge_ = [[FakeAutofillDriverIOSBridge alloc] init];
autofill_client_ =
std::make_unique<TestAutofillClientIOS>(&web_state_, test_bridge_);
main_frame_driver_ =
AutofillDriverIOS::FromWebStateAndWebFrame(&web_state_, main_frame);
iframe_driver_ =
AutofillDriverIOS::FromWebStateAndWebFrame(&web_state_, iframe);
ASSERT_TRUE(main_frame_driver_ && main_frame_driver_->GetFrameToken());
ASSERT_TRUE(iframe_driver_ && iframe_driver_->GetFrameToken());
ASSERT_NE(iframe_driver_->GetFrameToken(),
main_frame_driver_->GetFrameToken());
// Register the remote-local frame token mapping for the iframe driver so
// that the main frame driver can accurately set itself as a parent of the
// iframe driver.
registrar()->RegisterMapping(GetRemoteFrameToken(iframe_driver_),
iframe_driver_->GetFrameToken());
}
AutofillDriverIOS* main_frame_driver() { return main_frame_driver_; }
AutofillDriverIOS* iframe_driver() { return iframe_driver_; }
FakeAutofillDriverIOSBridge* bridge() { return test_bridge_; }
FormData MakeForm(bool main_frame) {
FormGlobalId form_id{main_frame ? main_frame_driver()->GetFrameToken()
: iframe_driver()->GetFrameToken(),
test::MakeFormRendererId()};
FormData form;
form.set_renderer_id(form_id.renderer_id);
form.set_host_frame(form_id.frame_token);
// Add a field to the form to make it non-trivial.
FormFieldData field = test::GetFormFieldData({});
field.set_host_form_id(form.renderer_id());
field.set_host_frame(form.host_frame());
field.set_value(main_frame ? u"Main frame field" : u"Iframe field");
form.set_fields({std::move(field)});
if (main_frame) {
std::vector<FrameTokenWithPredecessor> child_frames(1);
child_frames.back().token = GetRemoteFrameToken(iframe_driver());
form.set_child_frames(std::move(child_frames));
}
return form;
}
private:
web::WebFrame* CreateFrame(GURL url, bool main_frame) {
auto frame = main_frame ? web::FakeWebFrame::CreateMainWebFrame(url)
: web::FakeWebFrame::CreateChildWebFrame(url);
frame->set_browser_state(GetBrowserState());
web::WebFrame* frame_ptr = frame.get();
web_frames_manager_->AddWebFrame(std::move(frame));
return frame_ptr;
}
autofill::ChildFrameRegistrar* registrar() {
return autofill::ChildFrameRegistrar::GetOrCreateForWebState(&web_state_);
}
RemoteFrameToken GetRemoteFrameToken(AutofillDriver* driver) {
return RemoteFrameToken(driver->GetFrameToken().value());
}
test::AutofillUnitTestEnvironment autofill_test_environment_;
web::FakeWebState web_state_;
raw_ptr<web::FakeWebFramesManager> web_frames_manager_ = nullptr;
FakeAutofillDriverIOSBridge* test_bridge_ = nullptr;
std::unique_ptr<TestAutofillClientIOS> autofill_client_;
raw_ptr<AutofillDriverIOS> main_frame_driver_ = nullptr;
raw_ptr<AutofillDriverIOS> iframe_driver_ = nullptr;
};
// Test when ExtractFormWithField() is called and where the form is found.
TEST_F(AutofillDriverIOSTest, ExtractFormWithField_FormFound) {
FormData iframe_form = MakeForm(/*main_frame=*/false);
FormData main_frame_form = MakeForm(/*main_frame=*/true);
// Notify the drivers of seeing these forms so that the router can register
// them along with their frame.
main_frame_driver()->FormsSeen({main_frame_form}, /*removed_forms=*/{});
iframe_driver()->FormsSeen({iframe_form}, /*removed_forms=*/{});
ASSERT_EQ(iframe_driver()->GetParent(), main_frame_driver());
// Update the value of the fields in both forms to check the result of the
// extraction later.
auto update_form = [](FormData& form) {
std::vector<FormFieldData> fields = form.ExtractFields();
fields.back().set_value(fields.back().value() + u" extracted");
form.set_fields(std::move(fields));
};
update_form(main_frame_form);
update_form(iframe_form);
// Setup the forms that the bridge should return upon calling
// fetchFormsFiltered(). Those should be the two forms created above.
std::vector<FormData> bridge_result{iframe_form, main_frame_form};
[bridge() setForms:(bridge_result)];
base::MockCallback<
base::OnceCallback<void(AutofillDriver*, const std::optional<FormData>&)>>
final_callback;
// The callback must be called with the result of the extraction operation,
// this should be the browser form containing the iframe form, with the
// updated version of the iframe form.
EXPECT_CALL(
final_callback,
Run(main_frame_driver(),
Optional(
AllOf(Property(&FormData::global_id, main_frame_form.global_id()),
Property(&FormData::fields,
ElementsAre(Property(&FormFieldData::value,
u"Iframe field extracted"),
Property(&FormFieldData::value,
u"Main frame field")))))));
main_frame_driver()->ExtractFormWithField(iframe_form.fields()[0].global_id(),
final_callback.Get());
}
// Test the case where the bridge returns forms, but not the one we want.
TEST_F(AutofillDriverIOSTest, ExtractFormWithField_FormNotFound) {
FieldGlobalId field_id{main_frame_driver()->GetFrameToken(),
test::MakeFieldRendererId()};
FormData form_not_to_find = MakeForm(/*main_frame=*/true);
// Setup the forms that the bridge should return upon calling
// fetchFormsFiltered().
std::vector<FormData> bridge_result = {form_not_to_find};
[bridge() setForms:(bridge_result)];
// Notify the driver of seeing this form so that the router can register it
// along with its frame.
main_frame_driver()->FormsSeen({form_not_to_find}, /*removed_forms=*/{});
// Expect that `final_callback` will be called with a null form, since the
// bridge couldn't find a form with the corresponding renderer id.
base::MockCallback<
base::OnceCallback<void(AutofillDriver*, const std::optional<FormData>&)>>
final_callback;
EXPECT_CALL(final_callback, Run(nullptr, Eq(std::nullopt)));
main_frame_driver()->ExtractFormWithField(field_id, final_callback.Get());
}
} // namespace
} // namespace autofill