1// Copyright 2017 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#import "ios/web_view/internal/autofill/cwv_autofill_controller_internal.h" 6 7#import <Foundation/Foundation.h> 8 9#include <memory> 10 11#include "base/run_loop.h" 12#include "base/strings/sys_string_conversions.h" 13#import "base/test/ios/wait_util.h" 14#include "components/autofill/core/browser/autofill_manager.h" 15#include "components/autofill/core/browser/logging/stub_log_manager.h" 16#include "components/autofill/core/browser/payments/test_strike_database.h" 17#include "components/autofill/core/browser/test_personal_data_manager.h" 18#include "components/autofill/core/common/autofill_prefs.h" 19#import "components/autofill/ios/browser/fake_autofill_agent.h" 20#import "components/autofill/ios/browser/fake_js_autofill_manager.h" 21#import "components/autofill/ios/browser/form_suggestion.h" 22#import "components/autofill/ios/browser/js_suggestion_manager.h" 23#include "components/autofill/ios/form_util/form_activity_params.h" 24#import "components/autofill/ios/form_util/form_activity_tab_helper.h" 25#import "components/autofill/ios/form_util/test_form_activity_tab_helper.h" 26#include "components/autofill/ios/form_util/unique_id_data_tab_helper.h" 27#include "components/password_manager/core/browser/leak_detection_dialog_utils.h" 28#include "components/password_manager/core/browser/password_manager.h" 29#include "components/password_manager/core/common/password_manager_pref_names.h" 30#import "components/password_manager/ios/shared_password_controller.h" 31#include "components/prefs/pref_registry_simple.h" 32#include "components/prefs/testing_pref_service.h" 33#include "components/sync/driver/test_sync_service.h" 34#import "ios/web/public/deprecated/crw_test_js_injection_receiver.h" 35#include "ios/web/public/js_messaging/web_frames_manager.h" 36#import "ios/web/public/test/fakes/fake_web_frame.h" 37#import "ios/web/public/test/fakes/fake_web_frames_manager.h" 38#include "ios/web/public/test/fakes/test_browser_state.h" 39#import "ios/web/public/test/fakes/test_web_state.h" 40#include "ios/web/public/test/web_task_environment.h" 41#import "ios/web_view/internal/autofill/cwv_autofill_suggestion_internal.h" 42#import "ios/web_view/internal/autofill/web_view_autofill_client_ios.h" 43#import "ios/web_view/internal/passwords/web_view_password_manager_client.h" 44#import "ios/web_view/internal/passwords/web_view_password_manager_driver.h" 45#include "ios/web_view/internal/web_view_browser_state.h" 46#import "ios/web_view/public/cwv_autofill_controller_delegate.h" 47#import "net/base/mac/url_conversions.h" 48#include "testing/gtest/include/gtest/gtest.h" 49#import "testing/gtest_mac.h" 50#include "testing/platform_test.h" 51#import "third_party/ocmock/OCMock/OCMock.h" 52#include "third_party/ocmock/gtest_support.h" 53 54#if !defined(__has_feature) || !__has_feature(objc_arc) 55#error "This file requires ARC support." 56#endif 57 58using autofill::FormRendererId; 59using autofill::FieldRendererId; 60using base::test::ios::kWaitForActionTimeout; 61using base::test::ios::WaitUntilConditionOrTimeout; 62 63namespace ios_web_view { 64 65namespace { 66 67const char kApplicationLocale[] = "en-US"; 68NSString* const kTestFormName = @"FormName"; 69FormRendererId kTestUniqueFormID = FormRendererId(0); 70NSString* const kTestFieldIdentifier = @"FieldIdentifier"; 71FieldRendererId kTestUniqueFieldID = FieldRendererId(1); 72NSString* const kTestFieldValue = @"FieldValue"; 73NSString* const kTestDisplayDescription = @"DisplayDescription"; 74 75} // namespace 76 77class CWVAutofillControllerTest : public PlatformTest { 78 protected: 79 CWVAutofillControllerTest() { 80 pref_service_.registry()->RegisterBooleanPref( 81 password_manager::prefs::kCredentialsEnableService, true); 82 pref_service_.registry()->RegisterBooleanPref( 83 autofill::prefs::kAutofillProfileEnabled, true); 84 85 web_state_.SetBrowserState(&browser_state_); 86 CRWTestJSInjectionReceiver* injectionReceiver = 87 [[CRWTestJSInjectionReceiver alloc] init]; 88 web_state_.SetJSInjectionReceiver(injectionReceiver); 89 90 js_autofill_manager_ = [[FakeJSAutofillManager alloc] init]; 91 js_suggestion_manager_ = OCMClassMock([JsSuggestionManager class]); 92 93 UniqueIDDataTabHelper::CreateForWebState(&web_state_); 94 95 autofill_agent_ = 96 [[FakeAutofillAgent alloc] initWithPrefService:&pref_service_ 97 webState:&web_state_]; 98 99 frame_id_ = base::SysUTF8ToNSString(web::kMainFakeFrameId); 100 101 auto frames_manager = std::make_unique<web::FakeWebFramesManager>(); 102 web_frames_manager_ = frames_manager.get(); 103 web_state_.SetWebFramesManager(std::move(frames_manager)); 104 105 auto password_manager_client = 106 std::make_unique<WebViewPasswordManagerClient>( 107 &web_state_, /*sync_service=*/nullptr, &pref_service_, 108 /*identity_manager=*/nullptr, /*log_manager=*/nullptr, 109 /*profile_store=*/nullptr, /*account_store=*/nullptr, 110 /*requirements_service=*/nullptr); 111 auto password_manager = std::make_unique<password_manager::PasswordManager>( 112 password_manager_client.get()); 113 auto password_manager_driver = 114 std::make_unique<WebViewPasswordManagerDriver>(password_manager.get()); 115 password_controller_ = OCMClassMock([SharedPasswordController class]); 116 password_manager_client_ = password_manager_client.get(); 117 118 auto autofill_client = std::make_unique<autofill::WebViewAutofillClientIOS>( 119 kApplicationLocale, &pref_service_, &personal_data_manager_, 120 /*autocomplete_history_manager=*/nullptr, &web_state_, 121 /*identity_manager=*/nullptr, &strike_database_, &sync_service_, 122 std::make_unique<autofill::StubLogManager>()); 123 autofill_controller_ = [[CWVAutofillController alloc] 124 initWithWebState:&web_state_ 125 autofillClient:std::move(autofill_client) 126 autofillAgent:autofill_agent_ 127 JSAutofillManager:js_autofill_manager_ 128 JSSuggestionManager:js_suggestion_manager_ 129 passwordManager:std::move(password_manager) 130 passwordManagerClient:std::move(password_manager_client) 131 passwordManagerDriver:std::move(password_manager_driver) 132 passwordController:password_controller_ 133 applicationLocale:kApplicationLocale]; 134 form_activity_tab_helper_ = 135 std::make_unique<autofill::TestFormActivityTabHelper>(&web_state_); 136 } 137 138 void AddWebFrame(std::unique_ptr<web::WebFrame> frame) { 139 web::WebFrame* frame_ptr = frame.get(); 140 web_frames_manager_->AddWebFrame(std::move(frame)); 141 web_state_.OnWebFrameDidBecomeAvailable(frame_ptr); 142 } 143 144 web::WebTaskEnvironment task_environment_; 145 TestingPrefServiceSimple pref_service_; 146 web::TestBrowserState browser_state_; 147 web::TestWebState web_state_; 148 autofill::TestPersonalDataManager personal_data_manager_; 149 autofill::TestStrikeDatabase strike_database_; 150 syncer::TestSyncService sync_service_; 151 NSString* frame_id_; 152 web::FakeWebFramesManager* web_frames_manager_; 153 CWVAutofillController* autofill_controller_; 154 FakeAutofillAgent* autofill_agent_; 155 FakeJSAutofillManager* js_autofill_manager_; 156 id password_controller_; 157 std::unique_ptr<autofill::TestFormActivityTabHelper> 158 form_activity_tab_helper_; 159 id js_suggestion_manager_; 160 WebViewPasswordManagerClient* password_manager_client_; 161}; 162 163// Tests CWVAutofillController fetch suggestions for profiles. 164TEST_F(CWVAutofillControllerTest, FetchProfileSuggestions) { 165 FormSuggestion* suggestion = 166 [FormSuggestion suggestionWithValue:kTestFieldValue 167 displayDescription:kTestDisplayDescription 168 icon:nil 169 identifier:0 170 requiresReauth:NO]; 171 [autofill_agent_ addSuggestion:suggestion 172 forFormName:kTestFormName 173 fieldIdentifier:kTestFieldIdentifier 174 frameID:frame_id_]; 175 176 OCMExpect([password_controller_ 177 checkIfSuggestionsAvailableForForm:[OCMArg any] 178 isMainFrame:NO 179 hasUserGesture:YES 180 webState:&web_state_ 181 completionHandler:[OCMArg checkWithBlock:^(void ( 182 ^suggestionsAvailable)(BOOL)) { 183 suggestionsAvailable(NO); 184 return YES; 185 }]]); 186 187 __block BOOL fetch_completion_was_called = NO; 188 id fetch_completion = ^(NSArray<CWVAutofillSuggestion*>* suggestions) { 189 ASSERT_EQ(1U, suggestions.count); 190 CWVAutofillSuggestion* suggestion = suggestions.firstObject; 191 EXPECT_NSEQ(kTestFieldValue, suggestion.value); 192 EXPECT_NSEQ(kTestDisplayDescription, suggestion.displayDescription); 193 EXPECT_NSEQ(kTestFormName, suggestion.formName); 194 fetch_completion_was_called = YES; 195 }; 196 [autofill_controller_ fetchSuggestionsForFormWithName:kTestFormName 197 fieldIdentifier:kTestFieldIdentifier 198 fieldType:@"" 199 frameID:frame_id_ 200 completionHandler:fetch_completion]; 201 202 EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^bool { 203 base::RunLoop().RunUntilIdle(); 204 return fetch_completion_was_called; 205 })); 206 207 EXPECT_OCMOCK_VERIFY(password_controller_); 208} 209 210// Tests CWVAutofillController fetch suggestions for passwords. 211TEST_F(CWVAutofillControllerTest, FetchPasswordSuggestions) { 212 FormSuggestion* suggestion = 213 [FormSuggestion suggestionWithValue:kTestFieldValue 214 displayDescription:nil 215 icon:nil 216 identifier:0 217 requiresReauth:NO]; 218 OCMExpect([password_controller_ 219 checkIfSuggestionsAvailableForForm:[OCMArg any] 220 isMainFrame:NO 221 hasUserGesture:YES 222 webState:&web_state_ 223 completionHandler:[OCMArg checkWithBlock:^(void ( 224 ^suggestionsAvailable)(BOOL)) { 225 suggestionsAvailable(YES); 226 return YES; 227 }]]); 228 OCMExpect([password_controller_ 229 retrieveSuggestionsForForm:[OCMArg any] 230 webState:&web_state_ 231 completionHandler:[OCMArg checkWithBlock:^(void ( 232 ^completionHandler)(NSArray*, id)) { 233 completionHandler(@[ suggestion ], nil); 234 return YES; 235 }]]); 236 237 __block BOOL fetch_completion_was_called = NO; 238 id fetch_completion = ^(NSArray<CWVAutofillSuggestion*>* suggestions) { 239 ASSERT_EQ(1U, suggestions.count); 240 CWVAutofillSuggestion* suggestion = suggestions.firstObject; 241 EXPECT_TRUE([suggestion isPasswordSuggestion]); 242 EXPECT_NSEQ(kTestFieldValue, suggestion.value); 243 EXPECT_NSEQ(kTestFormName, suggestion.formName); 244 fetch_completion_was_called = YES; 245 }; 246 [autofill_controller_ fetchSuggestionsForFormWithName:kTestFormName 247 fieldIdentifier:kTestFieldIdentifier 248 fieldType:@"" 249 frameID:frame_id_ 250 completionHandler:fetch_completion]; 251 252 EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^bool { 253 base::RunLoop().RunUntilIdle(); 254 return fetch_completion_was_called; 255 })); 256 257 EXPECT_OCMOCK_VERIFY(password_controller_); 258} 259 260// Tests CWVAutofillController accepts suggestion. 261TEST_F(CWVAutofillControllerTest, AcceptSuggestion) { 262 FormSuggestion* form_suggestion = 263 [FormSuggestion suggestionWithValue:kTestFieldValue 264 displayDescription:nil 265 icon:nil 266 identifier:0 267 requiresReauth:NO]; 268 CWVAutofillSuggestion* suggestion = 269 [[CWVAutofillSuggestion alloc] initWithFormSuggestion:form_suggestion 270 formName:kTestFormName 271 fieldIdentifier:kTestFieldIdentifier 272 frameID:frame_id_ 273 isPasswordSuggestion:NO]; 274 __block BOOL accept_completion_was_called = NO; 275 [autofill_controller_ acceptSuggestion:suggestion 276 completionHandler:^{ 277 accept_completion_was_called = YES; 278 }]; 279 280 EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^bool { 281 base::RunLoop().RunUntilIdle(); 282 return accept_completion_was_called; 283 })); 284 EXPECT_NSEQ( 285 form_suggestion, 286 [autofill_agent_ selectedSuggestionForFormName:kTestFormName 287 fieldIdentifier:kTestFieldIdentifier 288 frameID:frame_id_]); 289} 290 291// Tests CWVAutofillController clears form. 292TEST_F(CWVAutofillControllerTest, ClearForm) { 293 auto frame = std::make_unique<web::FakeMainWebFrame>(GURL::EmptyGURL()); 294 AddWebFrame(std::move(frame)); 295 __block BOOL clear_form_completion_was_called = NO; 296 [autofill_controller_ clearFormWithName:kTestFormName 297 fieldIdentifier:kTestFieldIdentifier 298 frameID:frame_id_ 299 completionHandler:^{ 300 clear_form_completion_was_called = YES; 301 }]; 302 303 EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^bool { 304 base::RunLoop().RunUntilIdle(); 305 return clear_form_completion_was_called; 306 })); 307 EXPECT_NSEQ(kTestFormName, js_autofill_manager_.lastClearedFormName); 308 EXPECT_NSEQ(kTestFieldIdentifier, 309 js_autofill_manager_.lastClearedFieldIdentifier); 310 EXPECT_NSEQ(frame_id_, js_autofill_manager_.lastClearedFrameIdentifier); 311} 312 313// Tests CWVAutofillController focus previous field. 314TEST_F(CWVAutofillControllerTest, FocusPrevious) { 315 [[js_suggestion_manager_ expect] selectPreviousElementInFrameWithID:nil]; 316 [autofill_controller_ focusPreviousField]; 317 [js_suggestion_manager_ verify]; 318} 319 320// Tests CWVAutofillController focus next field. 321TEST_F(CWVAutofillControllerTest, FocusNext) { 322 [[js_suggestion_manager_ expect] selectNextElementInFrameWithID:nil]; 323 [autofill_controller_ focusNextField]; 324 [js_suggestion_manager_ verify]; 325} 326 327// Tests CWVAutofillController checks previous and next focusable state. 328TEST_F(CWVAutofillControllerTest, CheckFocus) { 329 id completionHandler = ^(BOOL previous, BOOL next) { 330 }; 331 [[js_suggestion_manager_ expect] 332 fetchPreviousAndNextElementsPresenceInFrameWithID:nil 333 completionHandler:completionHandler]; 334 [autofill_controller_ 335 checkIfPreviousAndNextFieldsAreAvailableForFocusWithCompletionHandler: 336 completionHandler]; 337 [js_suggestion_manager_ verify]; 338} 339 340// Tests CWVAutofillController delegate focus callback is invoked. 341TEST_F(CWVAutofillControllerTest, FocusCallback) { 342 id delegate = OCMProtocolMock(@protocol(CWVAutofillControllerDelegate)); 343 autofill_controller_.delegate = delegate; 344 345 [[delegate expect] autofillController:autofill_controller_ 346 didFocusOnFieldWithIdentifier:kTestFieldIdentifier 347 fieldType:@"" 348 formName:kTestFormName 349 frameID:frame_id_ 350 value:kTestFieldValue 351 userInitiated:YES]; 352 353 autofill::FormActivityParams params; 354 params.form_name = base::SysNSStringToUTF8(kTestFormName); 355 params.unique_form_id = kTestUniqueFormID; 356 params.field_identifier = base::SysNSStringToUTF8(kTestFieldIdentifier); 357 params.unique_field_id = kTestUniqueFieldID; 358 params.value = base::SysNSStringToUTF8(kTestFieldValue); 359 params.frame_id = web::kMainFakeFrameId; 360 params.has_user_gesture = true; 361 params.type = "focus"; 362 web::FakeMainWebFrame frame(GURL::EmptyGURL()); 363 form_activity_tab_helper_->FormActivityRegistered(&frame, params); 364 [delegate verify]; 365} 366 367// Tests CWVAutofillController delegate input callback is invoked. 368TEST_F(CWVAutofillControllerTest, InputCallback) { 369 id delegate = OCMProtocolMock(@protocol(CWVAutofillControllerDelegate)); 370 autofill_controller_.delegate = delegate; 371 372 [[delegate expect] autofillController:autofill_controller_ 373 didInputInFieldWithIdentifier:kTestFieldIdentifier 374 fieldType:@"" 375 formName:kTestFormName 376 frameID:frame_id_ 377 value:kTestFieldValue 378 userInitiated:YES]; 379 380 autofill::FormActivityParams params; 381 params.form_name = base::SysNSStringToUTF8(kTestFormName); 382 params.field_identifier = base::SysNSStringToUTF8(kTestFieldIdentifier); 383 params.value = base::SysNSStringToUTF8(kTestFieldValue); 384 params.frame_id = web::kMainFakeFrameId; 385 params.type = "input"; 386 params.has_user_gesture = true; 387 web::FakeMainWebFrame frame(GURL::EmptyGURL()); 388 form_activity_tab_helper_->FormActivityRegistered(&frame, params); 389 [delegate verify]; 390} 391 392// Tests CWVAutofillController delegate blur callback is invoked. 393TEST_F(CWVAutofillControllerTest, BlurCallback) { 394 id delegate = OCMProtocolMock(@protocol(CWVAutofillControllerDelegate)); 395 autofill_controller_.delegate = delegate; 396 397 [[delegate expect] autofillController:autofill_controller_ 398 didBlurOnFieldWithIdentifier:kTestFieldIdentifier 399 fieldType:@"" 400 formName:kTestFormName 401 frameID:frame_id_ 402 value:kTestFieldValue 403 userInitiated:YES]; 404 405 autofill::FormActivityParams params; 406 params.form_name = base::SysNSStringToUTF8(kTestFormName); 407 params.field_identifier = base::SysNSStringToUTF8(kTestFieldIdentifier); 408 params.value = base::SysNSStringToUTF8(kTestFieldValue); 409 params.frame_id = web::kMainFakeFrameId; 410 params.type = "blur"; 411 params.has_user_gesture = true; 412 web::FakeMainWebFrame frame(GURL::EmptyGURL()); 413 form_activity_tab_helper_->FormActivityRegistered(&frame, params); 414 415 [delegate verify]; 416} 417 418// Tests CWVAutofillController delegate submit callback is invoked. 419TEST_F(CWVAutofillControllerTest, SubmitCallback) { 420 id delegate = OCMProtocolMock(@protocol(CWVAutofillControllerDelegate)); 421 autofill_controller_.delegate = delegate; 422 423 [[delegate expect] autofillController:autofill_controller_ 424 didSubmitFormWithName:kTestFormName 425 frameID:frame_id_ 426 userInitiated:YES]; 427 web::FakeMainWebFrame frame(GURL::EmptyGURL()); 428 form_activity_tab_helper_->DocumentSubmitted( 429 /*sender_frame*/ &frame, base::SysNSStringToUTF8(kTestFormName), 430 /*form_data=*/"", 431 /*user_initiated=*/true, 432 /*is_main_frame=*/true); 433 434 [[delegate expect] autofillController:autofill_controller_ 435 didSubmitFormWithName:kTestFormName 436 frameID:frame_id_ 437 userInitiated:NO]; 438 439 form_activity_tab_helper_->DocumentSubmitted( 440 /*sender_frame*/ &frame, base::SysNSStringToUTF8(kTestFormName), 441 /*form_data=*/"", 442 /*user_initiated=*/false, 443 /*is_main_frame=*/true); 444 445 [delegate verify]; 446} 447 448// Tests that CWVAutofillController notifies user of password leaks. 449TEST_F(CWVAutofillControllerTest, NotifyUserOfLeak) { 450 id delegate = OCMProtocolMock(@protocol(CWVAutofillControllerDelegate)); 451 autofill_controller_.delegate = delegate; 452 453 GURL leak_url("https://www.chromium.org"); 454 password_manager::CredentialLeakType leak_type = 455 password_manager::CreateLeakType(password_manager::IsSaved(true), 456 password_manager::IsReused(true), 457 password_manager::IsSyncing(true)); 458 CWVPasswordLeakType expected_leak_type = CWVPasswordLeakTypeSaved | 459 CWVPasswordLeakTypeUsedOnOtherSites | 460 CWVPasswordLeakTypeSyncingNormally; 461 OCMExpect([delegate autofillController:autofill_controller_ 462 notifyUserOfPasswordLeakOnURL:net::NSURLWithGURL(leak_url) 463 leakType:expected_leak_type]); 464 465 password_manager_client_->NotifyUserCredentialsWereLeaked( 466 leak_type, password_manager::CompromisedSitesCount(1), leak_url, 467 base::SysNSStringToUTF16(@"fake-username")); 468 469 [delegate verify]; 470} 471 472// Tests that CWVAutofillController suggests passwords to its delegate. 473TEST_F(CWVAutofillControllerTest, SuggestPasswordCallback) { 474 NSString* fake_generated_password = @"12345"; 475 id delegate = OCMProtocolMock(@protocol(CWVAutofillControllerDelegate)); 476 autofill_controller_.delegate = delegate; 477 OCMExpect([delegate autofillController:autofill_controller_ 478 suggestGeneratedPassword:fake_generated_password 479 decisionHandler:[OCMArg checkWithBlock:^(void ( 480 ^decisionHandler)(BOOL)) { 481 decisionHandler(/*accept=*/YES); 482 return YES; 483 }]]); 484 __block BOOL decision_handler_called = NO; 485 [autofill_controller_ sharedPasswordController:password_controller_ 486 showGeneratedPotentialPassword:fake_generated_password 487 decisionHandler:^(BOOL accept) { 488 decision_handler_called = YES; 489 EXPECT_TRUE(accept); 490 }]; 491 EXPECT_TRUE(decision_handler_called); 492 493 [delegate verify]; 494} 495 496} // namespace ios_web_view 497