blob: 0d5c01340645db25f7bd4eae48c1823dabc4901a [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/web_view/internal/autofill/cwv_autofill_controller_internal.h"
#import <Foundation/Foundation.h>
#include <memory>
#include "base/run_loop.h"
#include "base/strings/sys_string_conversions.h"
#import "base/test/ios/wait_util.h"
#include "components/autofill/core/browser/autofill_manager.h"
#include "components/autofill/core/browser/logging/stub_log_manager.h"
#include "components/autofill/core/browser/payments/test_strike_database.h"
#include "components/autofill/core/browser/test_personal_data_manager.h"
#include "components/autofill/core/common/autofill_prefs.h"
#import "components/autofill/ios/browser/fake_autofill_agent.h"
#import "components/autofill/ios/browser/fake_js_autofill_manager.h"
#import "components/autofill/ios/browser/form_suggestion.h"
#include "components/autofill/ios/form_util/form_activity_params.h"
#import "components/autofill/ios/form_util/form_activity_tab_helper.h"
#import "components/autofill/ios/form_util/test_form_activity_tab_helper.h"
#include "components/autofill/ios/form_util/unique_id_data_tab_helper.h"
#include "components/password_manager/core/browser/leak_detection_dialog_utils.h"
#include "components/password_manager/core/browser/password_manager.h"
#include "components/password_manager/core/common/password_manager_pref_names.h"
#import "components/password_manager/ios/shared_password_controller.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "components/sync/driver/test_sync_service.h"
#import "ios/web/public/deprecated/crw_test_js_injection_receiver.h"
#include "ios/web/public/js_messaging/web_frames_manager.h"
#include "ios/web/public/test/fakes/fake_browser_state.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"
#include "ios/web/public/test/web_task_environment.h"
#import "ios/web_view/internal/autofill/cwv_autofill_suggestion_internal.h"
#import "ios/web_view/internal/autofill/web_view_autofill_client_ios.h"
#import "ios/web_view/internal/passwords/web_view_password_manager_client.h"
#import "ios/web_view/internal/passwords/web_view_password_manager_driver.h"
#include "ios/web_view/internal/web_view_browser_state.h"
#import "ios/web_view/public/cwv_autofill_controller_delegate.h"
#import "net/base/mac/url_conversions.h"
#include "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#include "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#include "third_party/ocmock/gtest_support.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using autofill::FormRendererId;
using autofill::FieldRendererId;
using base::test::ios::kWaitForActionTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;
namespace ios_web_view {
namespace {
const char kApplicationLocale[] = "en-US";
NSString* const kTestFormName = @"FormName";
FormRendererId kTestUniqueFormID = FormRendererId(0);
NSString* const kTestFieldIdentifier = @"FieldIdentifier";
FieldRendererId kTestUniqueFieldID = FieldRendererId(1);
NSString* const kTestFieldValue = @"FieldValue";
NSString* const kTestDisplayDescription = @"DisplayDescription";
} // namespace
class CWVAutofillControllerTest : public PlatformTest {
protected:
CWVAutofillControllerTest() {
pref_service_.registry()->RegisterBooleanPref(
password_manager::prefs::kCredentialsEnableService, true);
pref_service_.registry()->RegisterBooleanPref(
autofill::prefs::kAutofillProfileEnabled, true);
web_state_.SetBrowserState(&browser_state_);
CRWTestJSInjectionReceiver* injectionReceiver =
[[CRWTestJSInjectionReceiver alloc] init];
web_state_.SetJSInjectionReceiver(injectionReceiver);
js_autofill_manager_ = [[FakeJSAutofillManager alloc] init];
UniqueIDDataTabHelper::CreateForWebState(&web_state_);
autofill_agent_ =
[[FakeAutofillAgent alloc] initWithPrefService:&pref_service_
webState:&web_state_];
frame_id_ = base::SysUTF8ToNSString(web::kMainFakeFrameId);
auto frames_manager = std::make_unique<web::FakeWebFramesManager>();
web_frames_manager_ = frames_manager.get();
web_state_.SetWebFramesManager(std::move(frames_manager));
auto password_manager_client =
std::make_unique<WebViewPasswordManagerClient>(
&web_state_, /*sync_service=*/nullptr, &pref_service_,
/*identity_manager=*/nullptr, /*log_manager=*/nullptr,
/*profile_store=*/nullptr, /*account_store=*/nullptr,
/*requirements_service=*/nullptr);
auto password_manager = std::make_unique<password_manager::PasswordManager>(
password_manager_client.get());
auto password_manager_driver =
std::make_unique<WebViewPasswordManagerDriver>(password_manager.get());
password_controller_ = OCMClassMock([SharedPasswordController class]);
password_manager_client_ = password_manager_client.get();
auto autofill_client = std::make_unique<autofill::WebViewAutofillClientIOS>(
kApplicationLocale, &pref_service_, &personal_data_manager_,
/*autocomplete_history_manager=*/nullptr, &web_state_,
/*identity_manager=*/nullptr, &strike_database_, &sync_service_,
std::make_unique<autofill::StubLogManager>());
autofill_controller_ = [[CWVAutofillController alloc]
initWithWebState:&web_state_
autofillClient:std::move(autofill_client)
autofillAgent:autofill_agent_
JSAutofillManager:js_autofill_manager_
passwordManager:std::move(password_manager)
passwordManagerClient:std::move(password_manager_client)
passwordManagerDriver:std::move(password_manager_driver)
passwordController:password_controller_
applicationLocale:kApplicationLocale];
form_activity_tab_helper_ =
std::make_unique<autofill::TestFormActivityTabHelper>(&web_state_);
}
void AddWebFrame(std::unique_ptr<web::WebFrame> frame) {
web::WebFrame* frame_ptr = frame.get();
web_frames_manager_->AddWebFrame(std::move(frame));
web_state_.OnWebFrameDidBecomeAvailable(frame_ptr);
}
web::WebTaskEnvironment task_environment_;
TestingPrefServiceSimple pref_service_;
web::FakeBrowserState browser_state_;
web::FakeWebState web_state_;
autofill::TestPersonalDataManager personal_data_manager_;
autofill::TestStrikeDatabase strike_database_;
syncer::TestSyncService sync_service_;
NSString* frame_id_;
web::FakeWebFramesManager* web_frames_manager_;
CWVAutofillController* autofill_controller_;
FakeAutofillAgent* autofill_agent_;
FakeJSAutofillManager* js_autofill_manager_;
id password_controller_;
std::unique_ptr<autofill::TestFormActivityTabHelper>
form_activity_tab_helper_;
WebViewPasswordManagerClient* password_manager_client_;
};
// Tests CWVAutofillController fetch suggestions for profiles.
TEST_F(CWVAutofillControllerTest, FetchProfileSuggestions) {
FormSuggestion* suggestion =
[FormSuggestion suggestionWithValue:kTestFieldValue
displayDescription:kTestDisplayDescription
icon:nil
identifier:0
requiresReauth:NO];
[autofill_agent_ addSuggestion:suggestion
forFormName:kTestFormName
fieldIdentifier:kTestFieldIdentifier
frameID:frame_id_];
OCMExpect([password_controller_
checkIfSuggestionsAvailableForForm:[OCMArg any]
isMainFrame:NO
hasUserGesture:YES
webState:&web_state_
completionHandler:[OCMArg checkWithBlock:^(void (
^suggestionsAvailable)(BOOL)) {
suggestionsAvailable(NO);
return YES;
}]]);
__block BOOL fetch_completion_was_called = NO;
id fetch_completion = ^(NSArray<CWVAutofillSuggestion*>* suggestions) {
ASSERT_EQ(1U, suggestions.count);
CWVAutofillSuggestion* suggestion = suggestions.firstObject;
EXPECT_NSEQ(kTestFieldValue, suggestion.value);
EXPECT_NSEQ(kTestDisplayDescription, suggestion.displayDescription);
EXPECT_NSEQ(kTestFormName, suggestion.formName);
fetch_completion_was_called = YES;
};
[autofill_controller_ fetchSuggestionsForFormWithName:kTestFormName
fieldIdentifier:kTestFieldIdentifier
fieldType:@""
frameID:frame_id_
completionHandler:fetch_completion];
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^bool {
base::RunLoop().RunUntilIdle();
return fetch_completion_was_called;
}));
EXPECT_OCMOCK_VERIFY(password_controller_);
}
// Tests CWVAutofillController fetch suggestions for passwords.
TEST_F(CWVAutofillControllerTest, FetchPasswordSuggestions) {
FormSuggestion* suggestion =
[FormSuggestion suggestionWithValue:kTestFieldValue
displayDescription:nil
icon:nil
identifier:0
requiresReauth:NO];
OCMExpect([password_controller_
checkIfSuggestionsAvailableForForm:[OCMArg any]
isMainFrame:NO
hasUserGesture:YES
webState:&web_state_
completionHandler:[OCMArg checkWithBlock:^(void (
^suggestionsAvailable)(BOOL)) {
suggestionsAvailable(YES);
return YES;
}]]);
OCMExpect([password_controller_
retrieveSuggestionsForForm:[OCMArg any]
webState:&web_state_
completionHandler:[OCMArg checkWithBlock:^(void (
^completionHandler)(NSArray*, id)) {
completionHandler(@[ suggestion ], nil);
return YES;
}]]);
__block BOOL fetch_completion_was_called = NO;
id fetch_completion = ^(NSArray<CWVAutofillSuggestion*>* suggestions) {
ASSERT_EQ(1U, suggestions.count);
CWVAutofillSuggestion* suggestion = suggestions.firstObject;
EXPECT_TRUE([suggestion isPasswordSuggestion]);
EXPECT_NSEQ(kTestFieldValue, suggestion.value);
EXPECT_NSEQ(kTestFormName, suggestion.formName);
fetch_completion_was_called = YES;
};
[autofill_controller_ fetchSuggestionsForFormWithName:kTestFormName
fieldIdentifier:kTestFieldIdentifier
fieldType:@""
frameID:frame_id_
completionHandler:fetch_completion];
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^bool {
base::RunLoop().RunUntilIdle();
return fetch_completion_was_called;
}));
EXPECT_OCMOCK_VERIFY(password_controller_);
}
// Tests CWVAutofillController accepts suggestion.
TEST_F(CWVAutofillControllerTest, AcceptSuggestion) {
FormSuggestion* form_suggestion =
[FormSuggestion suggestionWithValue:kTestFieldValue
displayDescription:nil
icon:nil
identifier:0
requiresReauth:NO];
CWVAutofillSuggestion* suggestion =
[[CWVAutofillSuggestion alloc] initWithFormSuggestion:form_suggestion
formName:kTestFormName
fieldIdentifier:kTestFieldIdentifier
frameID:frame_id_
isPasswordSuggestion:NO];
__block BOOL accept_completion_was_called = NO;
[autofill_controller_ acceptSuggestion:suggestion
completionHandler:^{
accept_completion_was_called = YES;
}];
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^bool {
base::RunLoop().RunUntilIdle();
return accept_completion_was_called;
}));
EXPECT_NSEQ(
form_suggestion,
[autofill_agent_ selectedSuggestionForFormName:kTestFormName
fieldIdentifier:kTestFieldIdentifier
frameID:frame_id_]);
}
// Tests CWVAutofillController clears form.
TEST_F(CWVAutofillControllerTest, ClearForm) {
auto frame = std::make_unique<web::FakeMainWebFrame>(GURL::EmptyGURL());
AddWebFrame(std::move(frame));
__block BOOL clear_form_completion_was_called = NO;
[autofill_controller_ clearFormWithName:kTestFormName
fieldIdentifier:kTestFieldIdentifier
frameID:frame_id_
completionHandler:^{
clear_form_completion_was_called = YES;
}];
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^bool {
base::RunLoop().RunUntilIdle();
return clear_form_completion_was_called;
}));
EXPECT_NSEQ(kTestFormName, js_autofill_manager_.lastClearedFormName);
EXPECT_NSEQ(kTestFieldIdentifier,
js_autofill_manager_.lastClearedFieldIdentifier);
EXPECT_NSEQ(frame_id_, js_autofill_manager_.lastClearedFrameIdentifier);
}
// Tests CWVAutofillController delegate focus callback is invoked.
TEST_F(CWVAutofillControllerTest, FocusCallback) {
id delegate = OCMProtocolMock(@protocol(CWVAutofillControllerDelegate));
autofill_controller_.delegate = delegate;
[[delegate expect] autofillController:autofill_controller_
didFocusOnFieldWithIdentifier:kTestFieldIdentifier
fieldType:@""
formName:kTestFormName
frameID:frame_id_
value:kTestFieldValue
userInitiated:YES];
autofill::FormActivityParams params;
params.form_name = base::SysNSStringToUTF8(kTestFormName);
params.unique_form_id = kTestUniqueFormID;
params.field_identifier = base::SysNSStringToUTF8(kTestFieldIdentifier);
params.unique_field_id = kTestUniqueFieldID;
params.value = base::SysNSStringToUTF8(kTestFieldValue);
params.frame_id = web::kMainFakeFrameId;
params.has_user_gesture = true;
params.type = "focus";
web::FakeMainWebFrame frame(GURL::EmptyGURL());
form_activity_tab_helper_->FormActivityRegistered(&frame, params);
[delegate verify];
}
// Tests CWVAutofillController delegate input callback is invoked.
TEST_F(CWVAutofillControllerTest, InputCallback) {
id delegate = OCMProtocolMock(@protocol(CWVAutofillControllerDelegate));
autofill_controller_.delegate = delegate;
[[delegate expect] autofillController:autofill_controller_
didInputInFieldWithIdentifier:kTestFieldIdentifier
fieldType:@""
formName:kTestFormName
frameID:frame_id_
value:kTestFieldValue
userInitiated:YES];
autofill::FormActivityParams params;
params.form_name = base::SysNSStringToUTF8(kTestFormName);
params.field_identifier = base::SysNSStringToUTF8(kTestFieldIdentifier);
params.value = base::SysNSStringToUTF8(kTestFieldValue);
params.frame_id = web::kMainFakeFrameId;
params.type = "input";
params.has_user_gesture = true;
web::FakeMainWebFrame frame(GURL::EmptyGURL());
form_activity_tab_helper_->FormActivityRegistered(&frame, params);
[delegate verify];
}
// Tests CWVAutofillController delegate blur callback is invoked.
TEST_F(CWVAutofillControllerTest, BlurCallback) {
id delegate = OCMProtocolMock(@protocol(CWVAutofillControllerDelegate));
autofill_controller_.delegate = delegate;
[[delegate expect] autofillController:autofill_controller_
didBlurOnFieldWithIdentifier:kTestFieldIdentifier
fieldType:@""
formName:kTestFormName
frameID:frame_id_
value:kTestFieldValue
userInitiated:YES];
autofill::FormActivityParams params;
params.form_name = base::SysNSStringToUTF8(kTestFormName);
params.field_identifier = base::SysNSStringToUTF8(kTestFieldIdentifier);
params.value = base::SysNSStringToUTF8(kTestFieldValue);
params.frame_id = web::kMainFakeFrameId;
params.type = "blur";
params.has_user_gesture = true;
web::FakeMainWebFrame frame(GURL::EmptyGURL());
form_activity_tab_helper_->FormActivityRegistered(&frame, params);
[delegate verify];
}
// Tests CWVAutofillController delegate submit callback is invoked.
TEST_F(CWVAutofillControllerTest, SubmitCallback) {
id delegate = OCMProtocolMock(@protocol(CWVAutofillControllerDelegate));
autofill_controller_.delegate = delegate;
[[delegate expect] autofillController:autofill_controller_
didSubmitFormWithName:kTestFormName
frameID:frame_id_
userInitiated:YES];
web::FakeMainWebFrame frame(GURL::EmptyGURL());
form_activity_tab_helper_->DocumentSubmitted(
/*sender_frame*/ &frame, base::SysNSStringToUTF8(kTestFormName),
/*form_data=*/"",
/*user_initiated=*/true,
/*is_main_frame=*/true);
[[delegate expect] autofillController:autofill_controller_
didSubmitFormWithName:kTestFormName
frameID:frame_id_
userInitiated:NO];
form_activity_tab_helper_->DocumentSubmitted(
/*sender_frame*/ &frame, base::SysNSStringToUTF8(kTestFormName),
/*form_data=*/"",
/*user_initiated=*/false,
/*is_main_frame=*/true);
[delegate verify];
}
// Tests that CWVAutofillController notifies user of password leaks.
TEST_F(CWVAutofillControllerTest, NotifyUserOfLeak) {
id delegate = OCMProtocolMock(@protocol(CWVAutofillControllerDelegate));
autofill_controller_.delegate = delegate;
GURL leak_url("https://www.chromium.org");
password_manager::CredentialLeakType leak_type =
password_manager::CreateLeakType(password_manager::IsSaved(true),
password_manager::IsReused(true),
password_manager::IsSyncing(true));
CWVPasswordLeakType expected_leak_type = CWVPasswordLeakTypeSaved |
CWVPasswordLeakTypeUsedOnOtherSites |
CWVPasswordLeakTypeSyncingNormally;
OCMExpect([delegate autofillController:autofill_controller_
notifyUserOfPasswordLeakOnURL:net::NSURLWithGURL(leak_url)
leakType:expected_leak_type]);
password_manager_client_->NotifyUserCredentialsWereLeaked(
leak_type, password_manager::CompromisedSitesCount(1), leak_url,
base::SysNSStringToUTF16(@"fake-username"));
[delegate verify];
}
// Tests that CWVAutofillController suggests passwords to its delegate.
TEST_F(CWVAutofillControllerTest, SuggestPasswordCallback) {
NSString* fake_generated_password = @"12345";
id delegate = OCMProtocolMock(@protocol(CWVAutofillControllerDelegate));
autofill_controller_.delegate = delegate;
OCMExpect([delegate autofillController:autofill_controller_
suggestGeneratedPassword:fake_generated_password
decisionHandler:[OCMArg checkWithBlock:^(void (
^decisionHandler)(BOOL)) {
decisionHandler(/*accept=*/YES);
return YES;
}]]);
__block BOOL decision_handler_called = NO;
[autofill_controller_ sharedPasswordController:password_controller_
showGeneratedPotentialPassword:fake_generated_password
decisionHandler:^(BOOL accept) {
decision_handler_called = YES;
EXPECT_TRUE(accept);
}];
EXPECT_TRUE(decision_handler_called);
[delegate verify];
}
} // namespace ios_web_view