| // 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; |
| |
| // Returns an action for a suggestion. |
| - (UIAlertAction*)actionForSuggestion:(CWVAutofillSuggestion*)suggestion |
| atIndex:(NSInteger)index; |
| |
| @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 { |
| // 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"); |
| } |
| |
| #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]; |
| }]; |
| } |
| |
| #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 |