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