// 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 "components/password_manager/ios/password_suggestion_helper.h"
#import "base/memory/raw_ptr.h"
#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "components/autofill/core/browser/test_autofill_client.h"
#import "components/autofill/core/common/autofill_test_utils.h"
#import "components/autofill/core/common/form_data.h"
#import "components/autofill/core/common/password_form_fill_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/form_suggestion.h"
#import "components/autofill/ios/browser/form_suggestion_provider_query.h"
#import "components/autofill/ios/form_util/form_util_java_script_feature.h"
#import "components/password_manager/core/browser/password_ui_utils.h"
#import "components/password_manager/ios/account_select_fill_data.h"
#import "components/password_manager/ios/test_helpers.h"
#import "ios/web/public/test/fakes/fake_navigation_manager.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_task_environment.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/gtest_support.h"
#import "url/gurl.h"
#import "url/origin.h"
using autofill::FieldRendererId;
using autofill::FormRendererId;
using autofill::PasswordFormFillData;
using autofill::test::MakeFieldRendererId;
using autofill::test::MakeFormRendererId;
using base::SysUTF8ToNSString;
using base::UTF8ToUTF16;
namespace {
constexpr char kTestUrl[] = "http://foo.com";
constexpr char kFillDataUsername[] = "[email protected]";
constexpr char kFillDataPassword[] = "super!secret";
NSString* const kTestFrameID = @"mainframe";
NSString* const kTextFieldType = @"text";
NSString* const kQueryFocusType = @"focus";
NSString* NSFrameId(web::WebFrame* frame) {
return SysUTF8ToNSString(frame->GetFrameId());
}
PasswordFormFillData CreatePasswordFillData(
FormRendererId form_renderer_id,
FieldRendererId username_renderer_id,
FieldRendererId password_renderer_id) {
PasswordFormFillData form_fill_data;
test_helpers::SetPasswordFormFillData(
kTestUrl, "", form_renderer_id.value(), "", username_renderer_id.value(),
kFillDataUsername, "", password_renderer_id.value(), kFillDataPassword,
nullptr, nullptr, &form_fill_data);
return form_fill_data;
}
} // namespace
class PasswordSuggestionHelperTest : public PlatformTest {
protected:
void SetUp() override {
web_state_.SetCurrentURL(GURL(kTestUrl));
auto frames_manager = std::make_unique<web::FakeWebFramesManager>();
frames_manager_ = frames_manager.get();
auto main_frame =
web::FakeWebFrame::Create("mainframe", true, GURL(kTestUrl));
main_frame_ = main_frame.get();
AddWebFrame(std::move(main_frame));
web::ContentWorld content_world =
autofill::FormUtilJavaScriptFeature::GetInstance()
->GetSupportedContentWorld();
web_state_.SetWebFramesManager(content_world, std::move(frames_manager));
autofill::AutofillDriverIOSFactory::CreateForWebState(
&web_state_, &autofill_client_, /*autofill_agent=*/nil,
/*locale=*/"en");
delegate_ = OCMProtocolMock(@protocol(PasswordSuggestionHelperDelegate));
helper_ = [[PasswordSuggestionHelper alloc] initWithWebState:&web_state_];
helper_.delegate = delegate_;
}
void AddWebFrame(std::unique_ptr<web::WebFrame> frame) {
frames_manager_->AddWebFrame(std::move(frame));
}
FormSuggestionProviderQuery* BuildQuery(
autofill::FormRendererId formRendererID,
NSString* fieldIdentifier,
autofill::FieldRendererId fieldRendererID,
NSString* fieldType,
NSString* frameID) {
return [[FormSuggestionProviderQuery alloc] initWithFormName:@"form1"
formRendererID:formRendererID
fieldIdentifier:fieldIdentifier
fieldRendererID:fieldRendererID
fieldType:fieldType
type:kQueryFocusType
typedValue:@""
frameID:frameID];
}
FormSuggestionProviderQuery* BuildPasswordQuery(
autofill::FormRendererId formRendererID,
autofill::FieldRendererId fieldRendererID,
NSString* frameID) {
return BuildQuery(formRendererID, @"password1", fieldRendererID,
kObfuscatedFieldType, frameID);
}
FormSuggestionProviderQuery* BuildQuery(NSString* fieldIdentifier,
NSString* fieldType,
NSString* frameID) {
return BuildQuery(autofill::test::MakeFormRendererId(), fieldIdentifier,
autofill::test::MakeFieldRendererId(), fieldType,
frameID);
}
web::WebTaskEnvironment task_environment_;
autofill::test::AutofillUnitTestEnvironment autofill_test_environment_;
autofill::TestAutofillClient autofill_client_;
web::FakeWebState web_state_;
id delegate_;
PasswordSuggestionHelper* helper_;
raw_ptr<web::FakeWebFrame> main_frame_;
raw_ptr<web::FakeWebFramesManager> frames_manager_;
};
// Tests that the suggestions check query passes when there is fill data for the
// password field in the form that is being checked.
TEST_F(PasswordSuggestionHelperTest,
CheckIfSuggestions_WithFillDataImmediately_OnPasswordField) {
FormSuggestionProviderQuery* query =
BuildQuery(@"pwd1", kObfuscatedFieldType, NSFrameId(main_frame_));
FormRendererId form1_renderer_id = query.formRendererID;
FieldRendererId username1_renderer_id = autofill::test::MakeFieldRendererId();
FieldRendererId password1_renderer_id = query.fieldRendererID;
OCMExpect([[delegate_ ignoringNonObjectArgs]
attachListenersForBottomSheet:{}
forFrameId:main_frame_->GetFrameId()]);
PasswordFormFillData form_fill_data = CreatePasswordFillData(
form1_renderer_id, username1_renderer_id, password1_renderer_id);
[helper_ processWithPasswordFormFillData:form_fill_data
forFrameId:main_frame_->GetFrameId()
isMainFrame:main_frame_->IsMainFrame()
forSecurityOrigin:main_frame_->GetSecurityOrigin()];
__block BOOL retrieved_suggestions = NO;
__block BOOL completion_called = NO;
SuggestionsAvailableCompletion completion = ^(BOOL suggestionsAvailable) {
retrieved_suggestions = suggestionsAvailable;
completion_called = YES;
};
OCMReject([delegate_
suggestionHelperShouldTriggerFormExtraction:helper_
inFrame:main_frame_]);
[helper_ checkIfSuggestionsAvailableForForm:query
completionHandler:completion];
ASSERT_EQ(YES, completion_called);
EXPECT_EQ(YES, retrieved_suggestions);
EXPECT_OCMOCK_VERIFY(delegate_);
}
// Tests that the suggestions check query passes when there is fill data for the
// username field in the form that is being checked.
TEST_F(PasswordSuggestionHelperTest,
CheckIfSuggestions_WithFillDataImmediately_OnUsernameField) {
FormSuggestionProviderQuery* query =
BuildQuery(@"username1", kTextFieldType, NSFrameId(main_frame_));
FormRendererId form1_renderer_id = query.formRendererID;
FieldRendererId username1_renderer_id = query.fieldRendererID;
FieldRendererId password1_renderer_id = autofill::test::MakeFieldRendererId();
OCMExpect([[delegate_ ignoringNonObjectArgs]
attachListenersForBottomSheet:{}
forFrameId:main_frame_->GetFrameId()]);
PasswordFormFillData form_fill_data = CreatePasswordFillData(
form1_renderer_id, username1_renderer_id, password1_renderer_id);
[helper_ processWithPasswordFormFillData:form_fill_data
forFrameId:main_frame_->GetFrameId()
isMainFrame:main_frame_->IsMainFrame()
forSecurityOrigin:main_frame_->GetSecurityOrigin()];
__block BOOL retrieved_suggestions = NO;
__block BOOL completion_called = NO;
SuggestionsAvailableCompletion completion = ^(BOOL suggestionsAvailable) {
retrieved_suggestions = suggestionsAvailable;
completion_called = YES;
};
OCMReject([delegate_
suggestionHelperShouldTriggerFormExtraction:helper_
inFrame:main_frame_]);
[helper_ checkIfSuggestionsAvailableForForm:query
completionHandler:completion];
ASSERT_EQ(YES, completion_called);
EXPECT_EQ(YES, retrieved_suggestions);
EXPECT_OCMOCK_VERIFY(delegate_);
}
// Tests that the suggestions check query fails when there is no fill data for
// the field in the form that is being checked, not even with the triggered
// forms extraction.
TEST_F(PasswordSuggestionHelperTest,
CheckIfSuggestions_WithoutFillDataOnTriggeredFormsExtraction) {
__block BOOL retrieved_suggestions = NO;
__block BOOL completion_called = NO;
SuggestionsAvailableCompletion completion = ^(BOOL suggestionsAvailable) {
retrieved_suggestions = suggestionsAvailable;
completion_called = YES;
};
FormSuggestionProviderQuery* query =
BuildQuery(@"pwd1", kObfuscatedFieldType, NSFrameId(main_frame_));
OCMExpect([delegate_
suggestionHelperShouldTriggerFormExtraction:helper_
inFrame:main_frame_]);
[helper_ checkIfSuggestionsAvailableForForm:query
completionHandler:completion];
EXPECT_OCMOCK_VERIFY(delegate_);
// The completion shouldn't be called yet as there is no fill data for the
// queried form. The helper triggers forms extraction in an attempt to get
// fill data for the form.
ASSERT_EQ(NO, completion_called);
// Give feedback to the helper that no saved credentials could be retrieved,
// with the triggered forms extraction.
[helper_ processWithNoSavedCredentialsWithFrameId:main_frame_->GetFrameId()];
// Now the completion should be called since the triggered forms extraction
// was done.
ASSERT_EQ(YES, completion_called);
EXPECT_EQ(NO, retrieved_suggestions);
}
// Tests that the suggestions check query passes when there is fill data for the
// field in the form that is being checked, after the triggered forms
// extraction.
TEST_F(PasswordSuggestionHelperTest,
CheckIfSuggestions_WithFillDataOnTriggeredFormsExtraction) {
__block BOOL retrieved_suggestions = NO;
__block BOOL completion_called = NO;
SuggestionsAvailableCompletion completion = ^(BOOL suggestionsAvailable) {
retrieved_suggestions = suggestionsAvailable;
completion_called = YES;
};
FormSuggestionProviderQuery* query =
BuildQuery(@"pwd1", kObfuscatedFieldType, kTestFrameID);
FormRendererId form1_renderer_id = query.formRendererID;
FieldRendererId username1_renderer_id = autofill::test::MakeFieldRendererId();
FieldRendererId password1_renderer_id = query.fieldRendererID;
OCMExpect([delegate_
suggestionHelperShouldTriggerFormExtraction:helper_
inFrame:main_frame_]);
[helper_ checkIfSuggestionsAvailableForForm:query
completionHandler:completion];
// The completion shouldn't be called yet as there is no fill data for the
// queried form. The helper triggers forms extraction in an attempt to get
// fill data for the form.
ASSERT_EQ(NO, completion_called);
OCMExpect([[delegate_ ignoringNonObjectArgs]
attachListenersForBottomSheet:{}
forFrameId:main_frame_->GetFrameId()]);
// Process fill data for the queried form after the check query call. Will run
// the queued queries.
PasswordFormFillData form_fill_data = CreatePasswordFillData(
form1_renderer_id, username1_renderer_id, password1_renderer_id);
[helper_ processWithPasswordFormFillData:form_fill_data
forFrameId:main_frame_->GetFrameId()
isMainFrame:main_frame_->IsMainFrame()
forSecurityOrigin:main_frame_->GetSecurityOrigin()];
// Now the completion should be called since the triggered forms extraction
// was done.
ASSERT_EQ(YES, completion_called);
EXPECT_EQ(YES, retrieved_suggestions);
EXPECT_OCMOCK_VERIFY(delegate_);
}
// Tests that multiple queued check queries across forms are completed correctly
// with the right result after processing fill data.
TEST_F(PasswordSuggestionHelperTest,
CheckIfSuggestions_MultipleQueries_AcrossForms) {
OCMExpect([delegate_
suggestionHelperShouldTriggerFormExtraction:helper_
inFrame:main_frame_]);
// First query.
__block BOOL retrieved_suggestions1 = NO;
__block BOOL completion1_called = NO;
SuggestionsAvailableCompletion completion1 = ^(BOOL suggestionsAvailable) {
retrieved_suggestions1 = suggestionsAvailable;
completion1_called = YES;
};
FormSuggestionProviderQuery* query1 =
BuildQuery(@"password1", kObfuscatedFieldType, NSFrameId(main_frame_));
FormRendererId form1_renderer_id = query1.formRendererID;
FormRendererId form2_renderer_id = autofill::test::MakeFormRendererId();
FieldRendererId username1_renderer_id = query1.fieldRendererID;
FieldRendererId password1_renderer_id = autofill::test::MakeFieldRendererId();
[helper_ checkIfSuggestionsAvailableForForm:query1
completionHandler:completion1];
// Second query for the same focused field as query 1.
__block BOOL retrieved_suggestions2 = NO;
__block BOOL completion2_called = NO;
SuggestionsAvailableCompletion completion2 = ^(BOOL suggestionsAvailable) {
retrieved_suggestions2 = suggestionsAvailable;
completion2_called = YES;
};
FormSuggestionProviderQuery* query2 = BuildPasswordQuery(
form1_renderer_id, password1_renderer_id, NSFrameId(main_frame_));
[helper_ checkIfSuggestionsAvailableForForm:query2
completionHandler:completion2];
// Third query for a field in a different form.
__block BOOL retrieved_suggestions3 = NO;
__block BOOL completion3_called = NO;
SuggestionsAvailableCompletion completion3 = ^(BOOL suggestionsAvailable) {
retrieved_suggestions3 = suggestionsAvailable;
completion3_called = YES;
};
FormSuggestionProviderQuery* query3 = BuildPasswordQuery(
form2_renderer_id, password1_renderer_id, NSFrameId(main_frame_));
// Queue the third query to check if suggestions are available. At this point
// there are 4 queries in the queue to be executed the next time fill data is
// processed.
[helper_ checkIfSuggestionsAvailableForForm:query3
completionHandler:completion3];
// The completions shouldn't be called yet as there is no fill data for the
// queried forms. The helper triggers a in an attempt to get fill
// data for the form.
ASSERT_EQ(NO, completion1_called);
ASSERT_EQ(NO, completion2_called);
ASSERT_EQ(NO, completion3_called);
// Process fill data for the queried form after the check query call. Will run
// the queued queries.
OCMExpect([[delegate_ ignoringNonObjectArgs]
attachListenersForBottomSheet:{}
forFrameId:main_frame_->GetFrameId()]);
PasswordFormFillData form_fill_data = CreatePasswordFillData(
form1_renderer_id, username1_renderer_id, password1_renderer_id);
[helper_ processWithPasswordFormFillData:form_fill_data
forFrameId:main_frame_->GetFrameId()
isMainFrame:main_frame_->IsMainFrame()
forSecurityOrigin:main_frame_->GetSecurityOrigin()];
// Now the queued completion blocks should be called since the triggered forms
// extraction was done.
ASSERT_EQ(YES, completion1_called);
ASSERT_EQ(YES, completion2_called);
ASSERT_EQ(YES, completion3_called);
EXPECT_EQ(YES, retrieved_suggestions1);
EXPECT_EQ(YES, retrieved_suggestions2);
// Expect no suggestions for the third query because there was no fill data
// for the queried form2.
EXPECT_EQ(NO, retrieved_suggestions3);
EXPECT_OCMOCK_VERIFY(delegate_);
}
// Tests that multiple queued check queries across frames are completed
// correctly with the right result after processing fill data.
TEST_F(PasswordSuggestionHelperTest,
CheckIfSuggestions_MultipleQueries_AcrossFrames) {
FormRendererId form1_renderer_id = autofill::test::MakeFormRendererId();
FieldRendererId username1_renderer_id = autofill::test::MakeFieldRendererId();
FieldRendererId password1_renderer_id = autofill::test::MakeFieldRendererId();
auto frame1 = web::FakeWebFrame::Create("subframe1", false, GURL(kTestUrl));
web::FakeWebFrame* frame1_ptr = frame1.get();
AddWebFrame(std::move(frame1));
auto frame2 = web::FakeWebFrame::Create("subframe2", false, GURL(kTestUrl));
web::FakeWebFrame* frame2_ptr = frame2.get();
AddWebFrame(std::move(frame2));
OCMExpect([delegate_
suggestionHelperShouldTriggerFormExtraction:helper_
inFrame:main_frame_]);
OCMExpect([delegate_ suggestionHelperShouldTriggerFormExtraction:helper_
inFrame:frame1_ptr]);
OCMExpect([delegate_ suggestionHelperShouldTriggerFormExtraction:helper_
inFrame:frame2_ptr]);
// First query for form in main frame.
__block BOOL retrieved_suggestions1 = NO;
__block BOOL completion1_called = NO;
SuggestionsAvailableCompletion completion1 = ^(BOOL suggestionsAvailable) {
retrieved_suggestions1 = suggestionsAvailable;
completion1_called = YES;
};
{
FormSuggestionProviderQuery* query = BuildPasswordQuery(
form1_renderer_id, password1_renderer_id, NSFrameId(main_frame_));
[helper_ checkIfSuggestionsAvailableForForm:query
completionHandler:completion1];
}
// Second query for other form in main frame.
__block BOOL retrieved_suggestions2 = NO;
__block BOOL completion2_called = NO;
SuggestionsAvailableCompletion completion2 = ^(BOOL suggestionsAvailable) {
retrieved_suggestions2 = suggestionsAvailable;
completion2_called = YES;
};
{
FormSuggestionProviderQuery* query = BuildPasswordQuery(
form1_renderer_id, password1_renderer_id, NSFrameId(main_frame_));
[helper_ checkIfSuggestionsAvailableForForm:query
completionHandler:completion2];
}
// Third query for form in subframe.
__block BOOL retrieved_suggestions3 = NO;
__block BOOL completion3_called = NO;
SuggestionsAvailableCompletion completion3 = ^(BOOL suggestionsAvailable) {
retrieved_suggestions3 = suggestionsAvailable;
completion3_called = YES;
};
{
FormSuggestionProviderQuery* query = BuildPasswordQuery(
form1_renderer_id, password1_renderer_id, NSFrameId(frame1_ptr));
[helper_ checkIfSuggestionsAvailableForForm:query
completionHandler:completion3];
}
// Fourth query for form in other subframe.
__block BOOL retrieved_suggestions4 = NO;
__block BOOL completion4_called = NO;
SuggestionsAvailableCompletion completion4 = ^(BOOL suggestionsAvailable) {
retrieved_suggestions4 = suggestionsAvailable;
completion4_called = YES;
};
{
FormSuggestionProviderQuery* query = BuildPasswordQuery(
form1_renderer_id, password1_renderer_id, NSFrameId(frame2_ptr));
// Queue the third query to check if suggestions are available. At this
// point there are 4 queries in the queue to be executed when processing
// fill data.
[helper_ checkIfSuggestionsAvailableForForm:query
completionHandler:completion4];
}
// The completions shouldn't be called yet as there is no fill data for the
// queried forms. The helper triggers forms extraction in an attempt to get
// fill data for the form.
ASSERT_EQ(NO, completion1_called);
ASSERT_EQ(NO, completion2_called);
ASSERT_EQ(NO, completion3_called);
ASSERT_EQ(NO, completion4_called);
// Process fill data for the queried form in the main frame.
OCMExpect([[delegate_ ignoringNonObjectArgs]
attachListenersForBottomSheet:{}
forFrameId:main_frame_->GetFrameId()]);
{
PasswordFormFillData form_fill_data = CreatePasswordFillData(
form1_renderer_id, username1_renderer_id, password1_renderer_id);
[helper_ processWithPasswordFormFillData:form_fill_data
forFrameId:main_frame_->GetFrameId()
isMainFrame:main_frame_->IsMainFrame()
forSecurityOrigin:main_frame_->GetSecurityOrigin()];
}
// Queries for the forms in main frame should be completed after processing
// the fill data for that frame.
ASSERT_EQ(YES, completion1_called);
ASSERT_EQ(YES, completion2_called);
EXPECT_EQ(YES, retrieved_suggestions1);
EXPECT_EQ(YES, retrieved_suggestions2);
OCMExpect([[delegate_ ignoringNonObjectArgs]
attachListenersForBottomSheet:{}
forFrameId:frame1_ptr->GetFrameId()]);
{
PasswordFormFillData form_fill_data = CreatePasswordFillData(
form1_renderer_id, username1_renderer_id, password1_renderer_id);
[helper_ processWithPasswordFormFillData:form_fill_data
forFrameId:frame1_ptr->GetFrameId()
isMainFrame:frame1_ptr->IsMainFrame()
forSecurityOrigin:frame1_ptr->GetSecurityOrigin()];
}
// Queries for the forms in first subframe should be completed after
// processing the fill data for that frame.
ASSERT_EQ(YES, completion3_called);
EXPECT_EQ(YES, retrieved_suggestions3);
OCMExpect([[delegate_ ignoringNonObjectArgs]
attachListenersForBottomSheet:{}
forFrameId:frame2_ptr->GetFrameId()]);
{
PasswordFormFillData form_fill_data = CreatePasswordFillData(
form1_renderer_id, username1_renderer_id, password1_renderer_id);
[helper_ processWithPasswordFormFillData:form_fill_data
forFrameId:frame2_ptr->GetFrameId()
isMainFrame:frame2_ptr->IsMainFrame()
forSecurityOrigin:frame2_ptr->GetSecurityOrigin()];
}
// Queries for the forms in second subframe should be completed after
// processing the fill data for that frame.
ASSERT_EQ(YES, completion4_called);
EXPECT_EQ(YES, retrieved_suggestions4);
EXPECT_OCMOCK_VERIFY(delegate_);
}
// Tests that the completed check queries are popped out of the queue. The
// test will crash with a CHECK failure if a query is run more than one time.
TEST_F(PasswordSuggestionHelperTest,
CheckIfSuggestions_CompletedQueriesPoppedOut) {
FormRendererId form_renderer_id = autofill::test::MakeFormRendererId();
FieldRendererId username_renderer_id = autofill::test::MakeFieldRendererId();
FieldRendererId password_renderer_id = autofill::test::MakeFieldRendererId();
auto frame1 = web::FakeWebFrame::Create("subframe1", false, GURL(kTestUrl));
web::FakeWebFrame* frame1_ptr = frame1.get();
AddWebFrame(std::move(frame1));
auto frame2 = web::FakeWebFrame::Create("subframe2", false, GURL(kTestUrl));
web::FakeWebFrame* frame2_ptr = frame2.get();
AddWebFrame(std::move(frame2));
// First query for form in main frame.
{
FormSuggestionProviderQuery* query = BuildPasswordQuery(
form_renderer_id, password_renderer_id, NSFrameId(main_frame_));
[helper_ checkIfSuggestionsAvailableForForm:query
completionHandler:^(BOOL suggestionsAvailable){
}];
}
// Second query for form in main frame.
{
FormSuggestionProviderQuery* query = BuildPasswordQuery(
form_renderer_id, password_renderer_id, NSFrameId(main_frame_));
[helper_ checkIfSuggestionsAvailableForForm:query
completionHandler:^(BOOL suggestionsAvailable){
}];
}
// Third query for form in first subframe.
{
FormSuggestionProviderQuery* query = BuildPasswordQuery(
form_renderer_id, password_renderer_id, NSFrameId(frame1_ptr));
[helper_ checkIfSuggestionsAvailableForForm:query
completionHandler:^(BOOL suggestionsAvailable){
}];
}
// Fourth query for form in second subframe.
{
FormSuggestionProviderQuery* query = BuildPasswordQuery(
form_renderer_id, password_renderer_id, NSFrameId(frame2_ptr));
// Queue the third query to check if suggestions are available. At this
// point there are 4 queries in the queue to be executed the next time fill
// data is processed.
[helper_ checkIfSuggestionsAvailableForForm:query
completionHandler:^(BOOL suggestionsAvailable){
}];
}
// Process fill data for the main frame.
// Process the fill data a first time where all queries for main frame must
// be popped out.
{
PasswordFormFillData form_fill_data = CreatePasswordFillData(
form_renderer_id, username_renderer_id, password_renderer_id);
[helper_ processWithPasswordFormFillData:form_fill_data
forFrameId:main_frame_->GetFrameId()
isMainFrame:main_frame_->IsMainFrame()
forSecurityOrigin:main_frame_->GetSecurityOrigin()];
}
// Process the fill data a second time to verify that all the queries for
// the main frame were popped out of the queue in which case the query
// only-once CHECK won't fail.
{
PasswordFormFillData form_fill_data = CreatePasswordFillData(
form_renderer_id, username_renderer_id, password_renderer_id);
[helper_ processWithPasswordFormFillData:form_fill_data
forFrameId:main_frame_->GetFrameId()
isMainFrame:main_frame_->IsMainFrame()
forSecurityOrigin:main_frame_->GetSecurityOrigin()];
}
// Process fill data for the first subframe.
// Process the fill data a first time where all queries for the first
// subframe must be popped out.
{
PasswordFormFillData form_fill_data = CreatePasswordFillData(
form_renderer_id, username_renderer_id, password_renderer_id);
[helper_ processWithPasswordFormFillData:form_fill_data
forFrameId:frame1_ptr->GetFrameId()
isMainFrame:frame1_ptr->IsMainFrame()
forSecurityOrigin:frame1_ptr->GetSecurityOrigin()];
}
// Process the fill data a second time to verify that all the queries for
// the first subframe were popped out of the queue in which case the query
// only-once CHECK won't fail.
{
PasswordFormFillData form_fill_data = CreatePasswordFillData(
form_renderer_id, username_renderer_id, password_renderer_id);
[helper_ processWithPasswordFormFillData:form_fill_data
forFrameId:frame1_ptr->GetFrameId()
isMainFrame:frame1_ptr->IsMainFrame()
forSecurityOrigin:frame1_ptr->GetSecurityOrigin()];
}
// Process fill data for the second subframe.
// Process the fill data a first time where all queries for the second
// subframe must be popped out.
{
PasswordFormFillData form_fill_data = CreatePasswordFillData(
form_renderer_id, username_renderer_id, password_renderer_id);
[helper_ processWithPasswordFormFillData:form_fill_data
forFrameId:frame2_ptr->GetFrameId()
isMainFrame:frame2_ptr->IsMainFrame()
forSecurityOrigin:frame2_ptr->GetSecurityOrigin()];
}
// Process the fill data a second time to verify that all the queries for
// the second subframe were popped out of the queue in which case the query
// only-once CHECK won't fail.
{
PasswordFormFillData form_fill_data = CreatePasswordFillData(
form_renderer_id, username_renderer_id, password_renderer_id);
[helper_ processWithPasswordFormFillData:form_fill_data
forFrameId:frame2_ptr->GetFrameId()
isMainFrame:frame2_ptr->IsMainFrame()
forSecurityOrigin:frame2_ptr->GetSecurityOrigin()];
}
// Reaching this line means the no CHECK were triggered and that the queued
// queries were correctly popped out.
}
// Tests retrieving suggestions on username field in form when available.
TEST_F(PasswordSuggestionHelperTest, RetrieveSuggestions_OnUsernameField) {
FormSuggestionProviderQuery* query =
BuildQuery(@"username1", kTextFieldType, NSFrameId(main_frame_));
FormRendererId form1_renderer_id = query.formRendererID;
FieldRendererId username1_renderer_id = query.fieldRendererID;
FieldRendererId password1_renderer_id = autofill::test::MakeFieldRendererId();
PasswordFormFillData form_fill_data = CreatePasswordFillData(
form1_renderer_id, username1_renderer_id, password1_renderer_id);
[helper_ processWithPasswordFormFillData:form_fill_data
forFrameId:main_frame_->GetFrameId()
isMainFrame:main_frame_->IsMainFrame()
forSecurityOrigin:main_frame_->GetSecurityOrigin()];
NSArray<FormSuggestion*>* suggestions =
[helper_ retrieveSuggestionsWithForm:query];
ASSERT_EQ(1ul, [suggestions count]);
FormSuggestion* suggestionToEvaluate = suggestions.firstObject;
EXPECT_NSEQ(SysUTF8ToNSString(kFillDataUsername), suggestionToEvaluate.value);
EXPECT_FALSE(suggestionToEvaluate.metadata.is_single_username_form);
}
// Tests retrieving suggestions on password field in form when available.
TEST_F(PasswordSuggestionHelperTest, RetrieveSuggestions_OnPasswordField) {
FormSuggestionProviderQuery* query =
BuildQuery(@"password1", kObfuscatedFieldType, NSFrameId(main_frame_));
FormRendererId form1_renderer_id = query.formRendererID;
FieldRendererId username1_renderer_id = autofill::test::MakeFieldRendererId();
FieldRendererId password1_renderer_id = query.fieldRendererID;
PasswordFormFillData form_fill_data = CreatePasswordFillData(
form1_renderer_id, username1_renderer_id, password1_renderer_id);
[helper_ processWithPasswordFormFillData:form_fill_data
forFrameId:main_frame_->GetFrameId()
isMainFrame:main_frame_->IsMainFrame()
forSecurityOrigin:main_frame_->GetSecurityOrigin()];
NSArray<FormSuggestion*>* suggestions =
[helper_ retrieveSuggestionsWithForm:query];
ASSERT_EQ(1ul, [suggestions count]);
FormSuggestion* suggestionToEvaluate = suggestions.firstObject;
EXPECT_NSEQ(SysUTF8ToNSString(kFillDataUsername), suggestionToEvaluate.value);
EXPECT_FALSE(suggestionToEvaluate.metadata.is_single_username_form);
}
// Tests retrieving suggestions for a single username form on the username
// field.
TEST_F(PasswordSuggestionHelperTest, RetrieveSuggestions_OnSingleUsernameForm) {
FormSuggestionProviderQuery* query =
BuildQuery(@"username1", kTextFieldType, NSFrameId(main_frame_));
FormRendererId form1_renderer_id = query.formRendererID;
FieldRendererId username1_renderer_id = query.fieldRendererID;
FieldRendererId password1_renderer_id = FieldRendererId();
PasswordFormFillData form_fill_data = CreatePasswordFillData(
form1_renderer_id, username1_renderer_id, password1_renderer_id);
[helper_ processWithPasswordFormFillData:form_fill_data
forFrameId:main_frame_->GetFrameId()
isMainFrame:main_frame_->IsMainFrame()
forSecurityOrigin:main_frame_->GetSecurityOrigin()];
NSArray<FormSuggestion*>* suggestions =
[helper_ retrieveSuggestionsWithForm:query];
ASSERT_EQ(1ul, [suggestions count]);
FormSuggestion* suggestionToEvaluate = suggestions.firstObject;
EXPECT_NSEQ(SysUTF8ToNSString(kFillDataUsername), suggestionToEvaluate.value);
EXPECT_TRUE(suggestionToEvaluate.metadata.is_single_username_form);
}
// Tests retrieving suggestions for form when there are no suggestions.
TEST_F(PasswordSuggestionHelperTest, RetrieveSuggestions_Empty) {
FormSuggestionProviderQuery* form1_query =
BuildQuery(@"username1", kTextFieldType, NSFrameId(main_frame_));
// Create a 2nd form with the same username field id but a different form id.
FormRendererId form2_renderer_id = autofill::test::MakeFormRendererId();
FieldRendererId username1_renderer_id = form1_query.fieldRendererID;
FieldRendererId password1_renderer_id = autofill::test::MakeFieldRendererId();
ASSERT_NE(form1_query.formRendererID, form2_renderer_id);
// Process fill data for form2.
PasswordFormFillData form_fill_data = CreatePasswordFillData(
form2_renderer_id, username1_renderer_id, password1_renderer_id);
[helper_ processWithPasswordFormFillData:form_fill_data
forFrameId:main_frame_->GetFrameId()
isMainFrame:main_frame_->IsMainFrame()
forSecurityOrigin:main_frame_->GetSecurityOrigin()];
// Try to get suggestions for form1 which doesn't have fill data.
NSArray<FormSuggestion*>* suggestions =
[helper_ retrieveSuggestionsWithForm:form1_query];
ASSERT_EQ(0ul, [suggestions count]);
}
// Tests getting password fill data when available.
TEST_F(PasswordSuggestionHelperTest, GetPasswordFillData) {
FormSuggestionProviderQuery* query =
BuildQuery(@"username1", kTextFieldType, NSFrameId(main_frame_));
FormRendererId form1_renderer_id = query.formRendererID;
FieldRendererId username1_renderer_id = query.fieldRendererID;
FieldRendererId password1_renderer_id = autofill::test::MakeFieldRendererId();
PasswordFormFillData form_fill_data = CreatePasswordFillData(
form1_renderer_id, username1_renderer_id, password1_renderer_id);
[helper_ processWithPasswordFormFillData:form_fill_data
forFrameId:main_frame_->GetFrameId()
isMainFrame:main_frame_->IsMainFrame()
forSecurityOrigin:main_frame_->GetSecurityOrigin()];
// Get suggestions first before getting the fill data for the selected
// suggestion because this is a mandatory step.
NSArray<FormSuggestion*>* suggestions =
[helper_ retrieveSuggestionsWithForm:query];
std::unique_ptr<password_manager::FillData> fill_data =
[helper_ passwordFillDataForUsername:SysUTF8ToNSString(kFillDataUsername)
forFrameId:main_frame_->GetFrameId()];
ASSERT_EQ(1ul, [suggestions count]);
EXPECT_EQ(GURL(kTestUrl), (*fill_data).origin);
EXPECT_EQ(form1_renderer_id, (*fill_data).form_id);
EXPECT_EQ(username1_renderer_id, (*fill_data).username_element_id);
EXPECT_EQ(UTF8ToUTF16(std::string("[email protected]")),
(*fill_data).username_value);
EXPECT_EQ(password1_renderer_id, (*fill_data).password_element_id);
EXPECT_EQ(UTF8ToUTF16(std::string("super!secret")),
(*fill_data).password_value);
EXPECT_OCMOCK_VERIFY(delegate_);
}
// Tests that the helper is correctly reset.
TEST_F(PasswordSuggestionHelperTest, ResetForNewPage) {
FormSuggestionProviderQuery* main_frame_query =
BuildQuery(@"username1", kTextFieldType, NSFrameId(main_frame_));
FormRendererId form1_renderer_id = main_frame_query.formRendererID;
FieldRendererId username1_renderer_id = main_frame_query.fieldRendererID;
FieldRendererId password1_renderer_id = autofill::test::MakeFieldRendererId();
auto frame1 = web::FakeWebFrame::Create("subframe1", false, GURL(kTestUrl));
web::FakeWebFrame* frame1_ptr = frame1.get();
AddWebFrame(std::move(frame1));
// Queue check query for subframe.
__block BOOL completion_called = NO;
SuggestionsAvailableCompletion completion = ^(BOOL suggestionsAvailable) {
completion_called = YES;
};
{
FormSuggestionProviderQuery* iframe_query =
BuildQuery(@"password1", kObfuscatedFieldType, NSFrameId(frame1_ptr));
[helper_ checkIfSuggestionsAvailableForForm:iframe_query
completionHandler:completion];
}
// Process fill data for main frame.
PasswordFormFillData form_fill_data = CreatePasswordFillData(
form1_renderer_id, username1_renderer_id, password1_renderer_id);
[helper_ processWithPasswordFormFillData:form_fill_data
forFrameId:main_frame_->GetFrameId()
isMainFrame:main_frame_->IsMainFrame()
forSecurityOrigin:main_frame_->GetSecurityOrigin()];
{
// Get suggestions and fill data for main frame when there is still fill
// data.
NSArray<FormSuggestion*>* suggestions =
[helper_ retrieveSuggestionsWithForm:main_frame_query];
std::unique_ptr<password_manager::FillData> fill_data = [helper_
passwordFillDataForUsername:SysUTF8ToNSString(kFillDataUsername)
forFrameId:main_frame_->GetFrameId()];
// Check that there are suggestions for the main frame before the reset.
ASSERT_EQ(1ul, [suggestions count]);
ASSERT_TRUE(fill_data);
}
[helper_ resetForNewPage];
{
// Retry to get suggestions for the main frame which had processed fill data
// before the reset.
NSArray<FormSuggestion*>* suggestions =
[helper_ retrieveSuggestionsWithForm:main_frame_query];
// Check that there shouldn't be fill data anymore for the main frame.
EXPECT_EQ(0ul, [suggestions count]);
}
// Process fill data after the reset where the query completion queue should
// be empty.
[helper_ processWithNoSavedCredentialsWithFrameId:frame1_ptr->GetFrameId()];
EXPECT_EQ(NO, completion_called);
EXPECT_OCMOCK_VERIFY(delegate_);
}