910e62b5创建于 1月15日历史提交
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/web_view/shell/shell_autofill_delegate.h"

#import <UIKit/UIKit.h>

#import "ios/web_view/shell/shell_risk_data_loader.h"

@interface ShellAutofillDelegate ()

// Autofill controller.
@property(nonatomic, strong) CWVAutofillController* autofillController;

// Risk data loader.
@property(nonatomic, strong) ShellRiskDataLoader* riskDataLoader;

// Autofill progress dialog
@property(nonatomic, weak) UIAlertController* autofillProgressDialog;

// Returns an action for a suggestion.
- (UIAlertAction*)actionForSuggestion:(CWVAutofillSuggestion*)suggestion
                              atIndex:(NSInteger)index;

// A structure to hold the title and message strings used to present an
// autofill-related progress dialog (e.g., for card verification).
// This struct is used as the return type for methods that dynamically determine
// the appropriate user-facing text for various progress states.
typedef struct {
  NSString* title;
  NSString* message;
} AutofillDialogText;

// Returns an array contaning the appropriate title and message for the given
// `CWVAutofillProgressDialogType`
- (AutofillDialogText)titleAndMessageForAutofillProgressDialogWithType:
    (CWVAutofillProgressDialogType)type;

@end

@implementation ShellAutofillDelegate

@synthesize autofillController = _autofillController;
@synthesize riskDataLoader = _riskDataLoader;

- (instancetype)init {
  self = [super init];
  if (self) {
    _riskDataLoader = [[ShellRiskDataLoader alloc] init];
  }
  return self;
}

#pragma mark - CWVAutofillControllerDelegate methods

- (void)autofillController:(CWVAutofillController*)autofillController
    didFocusOnFieldWithIdentifier:(NSString*)fieldIdentifier
                        fieldType:(NSString*)fieldType
                         formName:(NSString*)formName
                          frameID:(NSString*)frameID
                            value:(NSString*)value
                    userInitiated:(BOOL)userInitiated {
  _autofillController = autofillController;

  __weak ShellAutofillDelegate* weakSelf = self;
  id completionHandler = ^(NSArray<CWVAutofillSuggestion*>* suggestions) {
    ShellAutofillDelegate* strongSelf = weakSelf;
    if (!suggestions.count || !strongSelf) {
      return;
    }

    UIAlertController* alertController = [UIAlertController
        alertControllerWithTitle:@"Pick a suggestion"
                         message:nil
                  preferredStyle:UIAlertControllerStyleActionSheet];
    alertController.popoverPresentationController.sourceView =
        [self anyKeyWindow];
    CGRect bounds = [self anyKeyWindow].bounds;
    alertController.popoverPresentationController.sourceRect =
        CGRectMake(CGRectGetWidth(bounds) / 2, 60, 1, 1);
    UIAlertAction* cancelAction =
        [UIAlertAction actionWithTitle:@"Cancel"
                                 style:UIAlertActionStyleCancel
                               handler:nil];
    [alertController addAction:cancelAction];
    for (NSUInteger i = 0; i < suggestions.count; ++i) {
      CWVAutofillSuggestion* suggestion = suggestions[i];
      [alertController addAction:[self actionForSuggestion:suggestion
                                                   atIndex:i]];
    }

    [[self anyKeyWindow].rootViewController
        presentViewController:alertController
                     animated:YES
                   completion:nil];
  };
  [autofillController fetchSuggestionsForFormWithName:formName
                                      fieldIdentifier:fieldIdentifier
                                            fieldType:fieldType
                                              frameID:frameID
                                    completionHandler:completionHandler];
}

- (void)autofillController:(CWVAutofillController*)autofillController
    didInputInFieldWithIdentifier:(NSString*)fieldIdentifier
                        fieldType:(NSString*)fieldType
                         formName:(NSString*)formName
                          frameID:(NSString*)frameID
                            value:(NSString*)value
                    userInitiated:(BOOL)userInitiated {
  // TODO(crbug.com/40224850): Fetching suggestions has an important side effect
  // of calling PasswordFormManager::UpdateStateOnUserInput. This will ensure
  // that the typed information can be remembered during the save dialogue.
  // Make this method a no-op once the bug is fixed.
  id completionHandler = ^(NSArray<CWVAutofillSuggestion*>* suggestions) {
    NSLog(@"%@ suggestions: %@", NSStringFromSelector(_cmd), suggestions);
  };
  [autofillController fetchSuggestionsForFormWithName:formName
                                      fieldIdentifier:fieldIdentifier
                                            fieldType:fieldType
                                              frameID:frameID
                                    completionHandler:completionHandler];
}

- (void)autofillController:(CWVAutofillController*)autofillController
    didBlurOnFieldWithIdentifier:(NSString*)fieldIdentifier
                       fieldType:(NSString*)fieldType
                        formName:(NSString*)formName
                         frameID:(NSString*)frameID
                           value:(NSString*)value
                   userInitiated:(BOOL)userInitiated {
  // Not implemented.
}

- (void)autofillController:(CWVAutofillController*)autofillController
     didSubmitFormWithName:(NSString*)formName
                   frameID:(NSString*)frameID
             userInitiated:(BOOL)userInitiated
            perfectFilling:(BOOL)perfectFilling {
  // Not implemented.
}

- (void)autofillController:(CWVAutofillController*)autofillController
              didFindForms:(NSArray<CWVAutofillForm*>*)forms
                   frameID:(NSString*)frameID {
  if (forms.count == 0) {
    return;
  }

  NSArray<NSString*>* debugDescriptions =
      [forms valueForKey:NSStringFromSelector(@selector(debugDescription))];
  NSLog(@"Found forms in frame %@\n%@", frameID, debugDescriptions);
}

- (void)autofillController:(CWVAutofillController*)autofillController
    saveCreditCardWithSaver:(CWVCreditCardSaver*)saver {
  CWVCreditCard* creditCard = saver.creditCard;
  UIAlertController* alertController =
      [UIAlertController alertControllerWithTitle:@"Save card?"
                                          message:creditCard.debugDescription
                                   preferredStyle:UIAlertControllerStyleAlert];
  __weak UIAlertController* weakAlertController = alertController;
  __weak ShellAutofillDelegate* weakSelf = self;
  UIAlertAction* allowAction = [UIAlertAction
      actionWithTitle:@"Allow"
                style:UIAlertActionStyleDefault
              handler:^(UIAlertAction* _Nonnull action) {
                NSString* cardHolderFullName =
                    weakAlertController.textFields[0].text;
                NSString* expirationMonth =
                    weakAlertController.textFields[1].text;
                NSString* expirationYear =
                    weakAlertController.textFields[2].text;
                [saver acceptWithCardHolderFullName:cardHolderFullName
                                    expirationMonth:expirationMonth
                                     expirationYear:expirationYear
                                           riskData:weakSelf.riskDataLoader
                                                        .riskData
                                  completionHandler:^(BOOL cardSaved) {
                                    if (!cardSaved) {
                                      NSLog(@"Failed to save: %@",
                                            saver.creditCard);
                                    }
                                  }];
              }];
  UIAlertAction* cancelAction =
      [UIAlertAction actionWithTitle:@"Cancel"
                               style:UIAlertActionStyleCancel
                             handler:^(UIAlertAction* _Nonnull action) {
                               [saver decline];
                             }];
  [alertController addAction:allowAction];
  [alertController addAction:cancelAction];

  [alertController
      addTextFieldWithConfigurationHandler:^(UITextField* textField) {
        textField.placeholder = @"Card holder full name";
        textField.keyboardType = UIKeyboardTypeDefault;
      }];
  [alertController
      addTextFieldWithConfigurationHandler:^(UITextField* textField) {
        textField.placeholder = @"Expiration month (MM)";
        textField.keyboardType = UIKeyboardTypeNumberPad;
      }];
  [alertController
      addTextFieldWithConfigurationHandler:^(UITextField* textField) {
        textField.placeholder = @"Expiration year (YYYY)";
        textField.keyboardType = UIKeyboardTypeNumberPad;
      }];

  [[self anyKeyWindow].rootViewController presentViewController:alertController
                                                       animated:YES
                                                     completion:nil];
}

- (void)autofillController:(CWVAutofillController*)autofillController
    decideSavePolicyForPassword:(CWVPassword*)password
                decisionHandler:(void (^)(CWVPasswordUserDecision decision))
                                    decisionHandler {
  UIAlertController* alertController =
      [UIAlertController alertControllerWithTitle:@"Save password?"
                                          message:password.debugDescription
                                   preferredStyle:UIAlertControllerStyleAlert];

  UIAlertAction* noAction = [UIAlertAction
      actionWithTitle:@"Not this time"
                style:UIAlertActionStyleCancel
              handler:^(UIAlertAction* _Nonnull action) {
                decisionHandler(CWVPasswordUserDecisionNotThisTime);
              }];
  [alertController addAction:noAction];

  UIAlertAction* neverAction =
      [UIAlertAction actionWithTitle:@"Never"
                               style:UIAlertActionStyleDefault
                             handler:^(UIAlertAction* _Nonnull action) {
                               decisionHandler(CWVPasswordUserDecisionNever);
                             }];
  [alertController addAction:neverAction];

  UIAlertAction* yesAction =
      [UIAlertAction actionWithTitle:@"Save"
                               style:UIAlertActionStyleDefault
                             handler:^(UIAlertAction* _Nonnull action) {
                               decisionHandler(CWVPasswordUserDecisionYes);
                             }];
  [alertController addAction:yesAction];

  [[self anyKeyWindow].rootViewController presentViewController:alertController
                                                       animated:YES
                                                     completion:nil];
}

- (void)autofillController:(CWVAutofillController*)autofillController
    decideUpdatePolicyForPassword:(CWVPassword*)password
                  decisionHandler:(void (^)(CWVPasswordUserDecision decision))
                                      decisionHandler {
  UIAlertController* alertController =
      [UIAlertController alertControllerWithTitle:@"Update password?"
                                          message:password.debugDescription
                                   preferredStyle:UIAlertControllerStyleAlert];

  UIAlertAction* noAction = [UIAlertAction
      actionWithTitle:@"Not this time"
                style:UIAlertActionStyleCancel
              handler:^(UIAlertAction* _Nonnull action) {
                decisionHandler(CWVPasswordUserDecisionNotThisTime);
              }];
  [alertController addAction:noAction];

  UIAlertAction* yesAction =
      [UIAlertAction actionWithTitle:@"Update"
                               style:UIAlertActionStyleDefault
                             handler:^(UIAlertAction* _Nonnull action) {
                               decisionHandler(CWVPasswordUserDecisionYes);
                             }];
  [alertController addAction:yesAction];

  [[self anyKeyWindow].rootViewController presentViewController:alertController
                                                       animated:YES
                                                     completion:nil];
}

- (void)autofillController:(CWVAutofillController*)autofillController
    verifyCreditCardWithVerifier:(CWVCreditCardVerifier*)verifier {
  [[self anyKeyWindow] endEditing:YES];

  UIAlertController* alertController =
      [UIAlertController alertControllerWithTitle:@"Verify Card"
                                          message:@"Enter CVC"
                                   preferredStyle:UIAlertControllerStyleAlert];

  __weak UIAlertController* weakAlertController = alertController;
  __weak ShellAutofillDelegate* weakSelf = self;
  UIAlertAction* submit = [UIAlertAction
      actionWithTitle:@"Confirm"
                style:UIAlertActionStyleDefault
              handler:^(UIAlertAction* action) {
                UITextField* textField =
                    weakAlertController.textFields.firstObject;
                NSString* CVC = textField.text;
                [verifier verifyWithCVC:CVC
                        expirationMonth:nil
                         expirationYear:nil
                               riskData:weakSelf.riskDataLoader.riskData
                      completionHandler:^(NSError* error) {
                        if (error) {
                          NSLog(@"Card %@ failed to verify error: %@",
                                verifier.creditCard, error);
                        }
                      }];
              }];

  [alertController addAction:submit];

  UIAlertAction* cancel =
      [UIAlertAction actionWithTitle:@"Cancel"
                               style:UIAlertActionStyleCancel
                             handler:nil];
  [alertController addAction:cancel];

  [alertController
      addTextFieldWithConfigurationHandler:^(UITextField* textField) {
        textField.placeholder = @"CVC";
        textField.keyboardType = UIKeyboardTypeNumberPad;
      }];

  [[self anyKeyWindow].rootViewController presentViewController:alertController
                                                       animated:YES
                                                     completion:nil];
}

- (void)autofillController:(CWVAutofillController*)autofillController
    notifyUserOfPasswordLeakOnURL:(NSURL*)URL
                         leakType:(CWVPasswordLeakType)leakType
                         username:(NSString*)username {
  NSLog(@"Password on %@ is leaked for username %@!", URL, username);
}

- (void)autofillController:(CWVAutofillController*)autofillController
    suggestGeneratedPassword:(NSString*)generatedPassword
             decisionHandler:(void (^)(BOOL accept))decisionHandler {
  NSLog(@"Accepting suggested password: %@", generatedPassword);
  decisionHandler(YES);
}

- (void)autofillController:(CWVAutofillController*)autofillController
    confirmSaveForNewAutofillProfile:(CWVAutofillProfile*)newProfile
                          oldProfile:(nullable CWVAutofillProfile*)oldProfile
                     decisionHandler:
                         (void (^)(CWVAutofillProfileUserDecision decision))
                             decisionHandler {
  NSString* message =
      [NSString stringWithFormat:@"new: %@\nold: %@",
                                 newProfile.debugDescription, oldProfile];
  UIAlertController* alertController = [UIAlertController
      alertControllerWithTitle:@"Confirm save for new profile?"
                       message:message
                preferredStyle:UIAlertControllerStyleAlert];

  UIAlertAction* accept = [UIAlertAction
      actionWithTitle:@"Accept"
                style:UIAlertActionStyleDefault
              handler:^(UIAlertAction* action) {
                decisionHandler(CWVAutofillProfileUserDecisionAccepted);
              }];
  [alertController addAction:accept];

  UIAlertAction* decline = [UIAlertAction
      actionWithTitle:@"Decline"
                style:UIAlertActionStyleCancel
              handler:^(UIAlertAction* action) {
                decisionHandler(CWVAutofillProfileUserDecisionDeclined);
              }];
  [alertController addAction:decline];

  [[self anyKeyWindow].rootViewController presentViewController:alertController
                                                       animated:YES
                                                     completion:nil];
}

- (void)autofillControllerDidLoginWithExistingPassword:
    (CWVAutofillController*)autofillController {
  NSLog(@"User logged in with an existing password");
}

- (void)autofillController:(CWVAutofillController*)autofillController
    showProgressDialogOfType:(CWVAutofillProgressDialogType)type
                cancelAction:(ProceduralBlock)cancelAction {
  AutofillDialogText dialogText =
      [self titleAndMessageForAutofillProgressDialogWithType:type];

  UIAlertController* alertController =
      [UIAlertController alertControllerWithTitle:dialogText.title
                                          message:dialogText.message
                                   preferredStyle:UIAlertControllerStyleAlert];

  UIAlertAction* cancelActionUI =
      [UIAlertAction actionWithTitle:@"Cancel"
                               style:UIAlertActionStyleCancel
                             handler:^(UIAlertAction* action) {
                               if (cancelAction) {
                                 cancelAction();
                               }
                             }];
  [alertController addAction:cancelActionUI];

  self.autofillProgressDialog = alertController;

  [[self anyKeyWindow].rootViewController presentViewController:alertController
                                                       animated:YES
                                                     completion:nil];
}

- (void)autofillController:(CWVAutofillController*)autofillController
    closeProgressDialogWithConfirmation:(BOOL)showConfirmation
                             completion:(ProceduralBlock)completion {
  if (!self.autofillProgressDialog) {
    if (completion) {
      completion();
    }
    return;
  }

  if (showConfirmation) {
    // handle showing confirmation before dismissing.
  }

  [self.autofillProgressDialog dismissViewControllerAnimated:YES
                                                  completion:^{
                                                    if (completion) {
                                                      completion();
                                                    }
                                                  }];
  self.autofillProgressDialog = nil;
}

#pragma mark - Private Methods

- (UIAlertAction*)actionForSuggestion:(CWVAutofillSuggestion*)suggestion
                              atIndex:(NSInteger)index {
  NSString* title =
      [NSString stringWithFormat:@"%@ %@", suggestion.value,
                                 suggestion.displayDescription ?: @""];
  __weak ShellAutofillDelegate* weakSelf = self;
  return [UIAlertAction actionWithTitle:title
                                  style:UIAlertActionStyleDefault
                                handler:^(UIAlertAction* action) {
                                  ShellAutofillDelegate* strongSelf = weakSelf;
                                  if (!strongSelf) {
                                    return;
                                  }
                                  [strongSelf.autofillController
                                       acceptSuggestion:suggestion
                                                atIndex:index
                                      completionHandler:nil];
                                  [[self anyKeyWindow] endEditing:YES];
                                }];
}

- (AutofillDialogText)titleAndMessageForAutofillProgressDialogWithType:
    (CWVAutofillProgressDialogType)type {
  AutofillDialogText dialogText;

  switch (type) {
    case CWVAutofillProgressDialogTypeVirtualCardUnmask:
    case CWVAutofillProgressDialogTypeServerCardUnmask:
      dialogText.title = @"Verifying card...";

      dialogText.message =
          (type == CWVAutofillProgressDialogTypeVirtualCardUnmask)
              ? @"Checking your card details."
              : @"Verifying your card details";
      break;
    case CWVAutofillProgressDialogTypeCardInfoRetrievalEnrolledUnmask:
      dialogText.title = @"Retrieving Card Info...";
      break;
    case CWVAutofillProgressDialogTypeIbanUnmask:
    case CWVAutofillProgressDialogType3DSFetchVCN:
    case CWVAutofillProgressDialogTypeBNPLFetchVCN:
    case CWVAutofillProgressDialogTypeBNPLAmountExtraction:
    case CWVAutofillProgressDialogTypeUnspecified:
      dialogText.title = @"Processing...";
      dialogText.message = @"Please wait.";
      break;
    default:
      NSAssert(NO, @"Unhandled CWVAutofillProgressDialogType: %ld", (long)type);
      break;
  }

  return dialogText;
}

#pragma mark - Private

- (UIWindow*)anyKeyWindow {
  for (UIWindowScene* windowScene in UIApplication.sharedApplication
           .connectedScenes) {
    NSAssert([windowScene isKindOfClass:[UIWindowScene class]],
             @"UIScene is not a UIWindowScene: %@", windowScene);
    for (UIWindow* window in windowScene.windows) {
      if (window.isKeyWindow) {
        return window;
      }
    }
  }

  return nil;
}

@end