Commit 67f0af81 authored by Javier Ernesto Flores Robles's avatar Javier Ernesto Flores Robles Committed by Commit Bot

[iOS][MF] Enables manual fill behind a flag

This CL wires all the parts and enables Manual Fallback, still work in
progress, behind it's flag.

Bug: 845472
Cq-Include-Trybots: luci.chromium.try:ios-simulator-cronet;luci.chromium.try:ios-simulator-full-configs
Change-Id: I8bef297135511a1d122dcac0c6199e263f112566
Reviewed-on: https://chromium-review.googlesource.com/1203994
Commit-Queue: Javier Ernesto Flores Robles <javierrobles@chromium.org>
Reviewed-by: default avatarMoe Ahmadi <mahmadi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#590323}
parent 47791912
...@@ -151,6 +151,7 @@ source_set("unit_tests") { ...@@ -151,6 +151,7 @@ source_set("unit_tests") {
"//ios/chrome/browser/ssl", "//ios/chrome/browser/ssl",
"//ios/chrome/browser/ui", "//ios/chrome/browser/ui",
"//ios/chrome/browser/ui/autofill", "//ios/chrome/browser/ui/autofill",
"//ios/chrome/browser/ui/coordinators:chrome_coordinators",
"//ios/chrome/browser/ui/settings:test_support", "//ios/chrome/browser/ui/settings:test_support",
"//ios/chrome/browser/web:test_support", "//ios/chrome/browser/web:test_support",
"//ios/chrome/browser/web:web_internal", "//ios/chrome/browser/web:web_internal",
......
...@@ -11,9 +11,9 @@ ...@@ -11,9 +11,9 @@
@protocol FormInputAccessoryConsumer<NSObject> @protocol FormInputAccessoryConsumer<NSObject>
// Restores the default input accessory view, removing (if necessary) any // Restores the keyboard and its default input accessory view, removing (if
// previously-added custom view. // necessary) any previously-added custom view.
- (void)restoreDefaultInputAccessoryView; - (void)restoreKeyboardView;
// Hides the default input accessory view and replaces it with one that shows // Hides the default input accessory view and replaces it with one that shows
// |customView| and form navigation controls. // |customView| and form navigation controls.
......
...@@ -18,6 +18,10 @@ extern CGFloat const kInputAccessoryHeight; ...@@ -18,6 +18,10 @@ extern CGFloat const kInputAccessoryHeight;
// accessory view elements. // accessory view elements.
@interface FormInputAccessoryViewController @interface FormInputAccessoryViewController
: NSObject<FormInputAccessoryConsumer> : NSObject<FormInputAccessoryConsumer>
// Presents a view above the keyboard.
- (void)presentView:(UIView*)view;
@end @end
#endif // IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_VIEW_CONTROLLER_H_ #endif // IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_VIEW_CONTROLLER_H_
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#import "ios/chrome/browser/autofill/form_input_accessory_view_controller.h" #import "ios/chrome/browser/autofill/form_input_accessory_view_controller.h"
#include "base/mac/foundation_util.h" #include "base/mac/foundation_util.h"
#include "components/autofill/core/common/autofill_features.h"
#import "ios/chrome/browser/autofill/form_input_accessory_view.h" #import "ios/chrome/browser/autofill/form_input_accessory_view.h"
#import "ios/chrome/browser/autofill/form_suggestion_view.h" #import "ios/chrome/browser/autofill/form_suggestion_view.h"
#include "ios/chrome/browser/ui/ui_util.h" #include "ios/chrome/browser/ui/ui_util.h"
...@@ -25,6 +26,9 @@ CGFloat const kInputAccessoryHeight = 44.0f; ...@@ -25,6 +26,9 @@ CGFloat const kInputAccessoryHeight = 44.0f;
// http://crbug.com/847523 // http://crbug.com/847523
@property(nonatomic, strong) UIView* grayBackgroundView; @property(nonatomic, strong) UIView* grayBackgroundView;
// The keyboard replacement view, if any.
@property(nonatomic, weak) UIView* keyboardReplacementView;
// Called when the keyboard will or did change frame. // Called when the keyboard will or did change frame.
- (void)keyboardWillOrDidChangeFrame:(NSNotification*)notification; - (void)keyboardWillOrDidChangeFrame:(NSNotification*)notification;
...@@ -49,6 +53,7 @@ CGFloat const kInputAccessoryHeight = 44.0f; ...@@ -49,6 +53,7 @@ CGFloat const kInputAccessoryHeight = 44.0f;
} }
@synthesize grayBackgroundView = _grayBackgroundView; @synthesize grayBackgroundView = _grayBackgroundView;
@synthesize keyboardReplacementView = _keyboardReplacementView;
#pragma mark - Life Cycle #pragma mark - Life Cycle
...@@ -88,6 +93,18 @@ CGFloat const kInputAccessoryHeight = 44.0f; ...@@ -88,6 +93,18 @@ CGFloat const kInputAccessoryHeight = 44.0f;
return self; return self;
} }
- (void)presentView:(UIView*)view {
UIView* keyboardView = [self getKeyboardView];
view.accessibilityViewIsModal = YES;
[keyboardView.superview addSubview:view];
UIView* constrainingView =
[self recursiveGetKeyboardConstraintView:keyboardView];
DCHECK(constrainingView);
view.translatesAutoresizingMaskIntoConstraints = NO;
AddSameConstraints(view, constrainingView);
self.keyboardReplacementView = view;
}
#pragma mark - Private #pragma mark - Private
- (void)hideSubviewsInOriginalAccessoryView:(UIView*)accessoryView { - (void)hideSubviewsInOriginalAccessoryView:(UIView*)accessoryView {
...@@ -99,12 +116,42 @@ CGFloat const kInputAccessoryHeight = 44.0f; ...@@ -99,12 +116,42 @@ CGFloat const kInputAccessoryHeight = 44.0f;
} }
} }
// This searches in a keyboard view hierarchy for the best candidate to
// constrain a view to the keyboard.
- (UIView*)recursiveGetKeyboardConstraintView:(UIView*)view {
for (UIView* subview in view.subviews) {
// TODO(crbug.com/845472): verify this on iOS 10-12 and all devices.
// Currently only tested on X-iOS12, 6+-iOS11 and 7+-iOS10. iPhoneX, iOS 11
// and 12 uses "Dock" and iOS 10 uses "Backdrop". iPhone6+, iOS 11 uses
// "Dock".
if ([NSStringFromClass([subview class]) containsString:@"Dock"] ||
[NSStringFromClass([subview class]) containsString:@"Backdrop"]) {
return subview;
}
UIView* found = [self recursiveGetKeyboardConstraintView:subview];
if (found) {
return found;
}
}
return nil;
}
- (UIView*)getKeyboardView { - (UIView*)getKeyboardView {
NSArray* windows = [UIApplication sharedApplication].windows; NSArray* windows = [UIApplication sharedApplication].windows;
if (windows.count < 2) if (windows.count < 2)
return nil; return nil;
UIWindow* window = windows[1]; UIWindow* window;
BOOL isManualFillEnabled =
base::FeatureList::IsEnabled(autofill::features::kAutofillManualFallback);
if (isManualFillEnabled) {
// TODO(crbug.com/845472): verify this works on iPad with split view before
// making this the default.
window = windows.lastObject;
} else {
window = windows[1];
}
for (UIView* subview in window.subviews) { for (UIView* subview in window.subviews) {
if ([NSStringFromClass([subview class]) rangeOfString:@"PeripheralHost"] if ([NSStringFromClass([subview class]) rangeOfString:@"PeripheralHost"]
.location != NSNotFound) { .location != NSNotFound) {
...@@ -208,7 +255,7 @@ CGFloat const kInputAccessoryHeight = 44.0f; ...@@ -208,7 +255,7 @@ CGFloat const kInputAccessoryHeight = 44.0f;
} else { } else {
// On iPhone, the custom view replaces the default UI of the // On iPhone, the custom view replaces the default UI of the
// inputAccessoryView. // inputAccessoryView.
[self restoreDefaultInputAccessoryView]; [self restoreInputAccessoryView];
_customAccessoryView = [[FormInputAccessoryView alloc] init]; _customAccessoryView = [[FormInputAccessoryView alloc] init];
[_customAccessoryView setUpWithNavigationDelegate:navigationDelegate [_customAccessoryView setUpWithNavigationDelegate:navigationDelegate
customView:view]; customView:view];
...@@ -261,7 +308,7 @@ CGFloat const kInputAccessoryHeight = 44.0f; ...@@ -261,7 +308,7 @@ CGFloat const kInputAccessoryHeight = 44.0f;
} }
} }
- (void)restoreDefaultInputAccessoryView { - (void)restoreInputAccessoryView {
[_customAccessoryView removeFromSuperview]; [_customAccessoryView removeFromSuperview];
[self.grayBackgroundView removeFromSuperview]; [self.grayBackgroundView removeFromSuperview];
...@@ -272,4 +319,10 @@ CGFloat const kInputAccessoryHeight = 44.0f; ...@@ -272,4 +319,10 @@ CGFloat const kInputAccessoryHeight = 44.0f;
[_hiddenOriginalSubviews removeAllObjects]; [_hiddenOriginalSubviews removeAllObjects];
} }
- (void)restoreKeyboardView {
[self restoreInputAccessoryView];
[self.keyboardReplacementView removeFromSuperview];
self.keyboardReplacementView = nil;
}
@end @end
...@@ -6,13 +6,11 @@ ...@@ -6,13 +6,11 @@
#include <memory> #include <memory>
#include "base/feature_list.h"
#include "base/mac/foundation_util.h" #include "base/mac/foundation_util.h"
#include "base/mac/scoped_block.h" #include "base/mac/scoped_block.h"
#include "base/strings/sys_string_conversions.h" #include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "components/autofill/core/browser/autofill_popup_delegate.h" #include "components/autofill/core/browser/autofill_popup_delegate.h"
#include "components/autofill/core/common/autofill_features.h"
#import "components/autofill/ios/browser/form_suggestion.h" #import "components/autofill/ios/browser/form_suggestion.h"
#import "components/autofill/ios/browser/form_suggestion_provider.h" #import "components/autofill/ios/browser/form_suggestion_provider.h"
#include "components/autofill/ios/form_util/form_activity_params.h" #include "components/autofill/ios/form_util/form_activity_params.h"
...@@ -81,9 +79,6 @@ AutofillSuggestionState::AutofillSuggestionState( ...@@ -81,9 +79,6 @@ AutofillSuggestionState::AutofillSuggestionState(
id<CRWWebViewProxy> _webViewProxy; id<CRWWebViewProxy> _webViewProxy;
} }
// Returns an autoreleased input accessory view that shows |suggestions|.
- (UIView*)suggestionViewWithSuggestions:(NSArray*)suggestions;
// Updates keyboard for |suggestionState|. // Updates keyboard for |suggestionState|.
- (void)updateKeyboard:(AutofillSuggestionState*)suggestionState; - (void)updateKeyboard:(AutofillSuggestionState*)suggestionState;
...@@ -269,13 +264,7 @@ AutofillSuggestionState::AutofillSuggestionState( ...@@ -269,13 +264,7 @@ AutofillSuggestionState::AutofillSuggestionState(
if (!accessoryViewUpdateBlock_) { if (!accessoryViewUpdateBlock_) {
return; return;
} }
BOOL isManualFillEnabled = accessoryViewUpdateBlock_([self suggestionViewWithSuggestions:@[]], self);
base::FeatureList::IsEnabled(autofill::features::kAutofillManualFallback);
if (isManualFillEnabled) {
accessoryViewUpdateBlock_(nil, self);
} else {
accessoryViewUpdateBlock_([self suggestionViewWithSuggestions:@[]], self);
}
} }
- (void)onSuggestionsReady:(NSArray<FormSuggestion*>*)suggestions - (void)onSuggestionsReady:(NSArray<FormSuggestion*>*)suggestions
...@@ -317,14 +306,16 @@ AutofillSuggestionState::AutofillSuggestionState( ...@@ -317,14 +306,16 @@ AutofillSuggestionState::AutofillSuggestionState(
} }
} }
- (void)updateKeyboardWithSuggestions:(NSArray*)suggestions { - (void)updateKeyboardWithSuggestions:(NSArray<FormSuggestion*>*)suggestions {
if (accessoryViewUpdateBlock_) { if (accessoryViewUpdateBlock_) {
accessoryViewUpdateBlock_([self suggestionViewWithSuggestions:suggestions], accessoryViewUpdateBlock_([self suggestionViewWithSuggestions:suggestions],
self); self);
} }
} }
- (UIView*)suggestionViewWithSuggestions:(NSArray*)suggestions { // Returns an autoreleased input accessory view that shows |suggestions|.
- (UIView*)suggestionViewWithSuggestions:
(NSArray<FormSuggestion*>*)suggestions {
CGRect frame = [_webViewProxy keyboardAccessory].frame; CGRect frame = [_webViewProxy keyboardAccessory].frame;
// Force the desired height on iPad where the height of the // Force the desired height on iPad where the height of the
// inputAccessoryView is 0. // inputAccessoryView is 0.
......
...@@ -179,15 +179,7 @@ class FormSuggestionControllerTest : public PlatformTest { ...@@ -179,15 +179,7 @@ class FormSuggestionControllerTest : public PlatformTest {
id mock_consumer_ = [OCMockObject id mock_consumer_ = [OCMockObject
niceMockForProtocol:@protocol(FormInputAccessoryConsumer)]; niceMockForProtocol:@protocol(FormInputAccessoryConsumer)];
accessory_mediator_ = // Mock the consumer to verify the suggestion views.
[[FormInputAccessoryMediator alloc] initWithConsumer:mock_consumer_
webStateList:NULL];
[accessory_mediator_ injectWebState:&test_web_state_];
[accessory_mediator_
injectProviders:@[ [suggestion_controller_ accessoryViewProvider] ]];
[accessory_mediator_ injectSuggestionManager:mock_js_suggestion_manager_];
// Mock the mediator consumer used to verify the suggestion views.
void (^mockShow)(NSInvocation*) = ^(NSInvocation* invocation) { void (^mockShow)(NSInvocation*) = ^(NSInvocation* invocation) {
for (UIView* view in [input_accessory_view_ subviews]) { for (UIView* view in [input_accessory_view_ subviews]) {
[view removeFromSuperview]; [view removeFromSuperview];
...@@ -200,13 +192,22 @@ class FormSuggestionControllerTest : public PlatformTest { ...@@ -200,13 +192,22 @@ class FormSuggestionControllerTest : public PlatformTest {
showCustomInputAccessoryView:[OCMArg any] showCustomInputAccessoryView:[OCMArg any]
navigationDelegate:[OCMArg any]]; navigationDelegate:[OCMArg any]];
// Mock restore keyboard to verify cleanup.
void (^mockRestore)(NSInvocation*) = ^(NSInvocation* invocation) { void (^mockRestore)(NSInvocation*) = ^(NSInvocation* invocation) {
for (UIView* view in [input_accessory_view_ subviews]) { for (UIView* view in [input_accessory_view_ subviews]) {
[view removeFromSuperview]; [view removeFromSuperview];
} }
}; };
[[[mock_consumer_ stub] andDo:mockRestore] [[[mock_consumer_ stub] andDo:mockRestore] restoreKeyboardView];
restoreDefaultInputAccessoryView];
accessory_mediator_ =
[[FormInputAccessoryMediator alloc] initWithConsumer:mock_consumer_
webStateList:NULL];
[accessory_mediator_ injectWebState:&test_web_state_];
[accessory_mediator_
injectProviders:@[ [suggestion_controller_ accessoryViewProvider] ]];
[accessory_mediator_ injectSuggestionManager:mock_js_suggestion_manager_];
} }
// The FormSuggestionController under test. // The FormSuggestionController under test.
......
...@@ -7,20 +7,23 @@ ...@@ -7,20 +7,23 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@class FormSuggestion;
@protocol FormSuggestionViewClient; @protocol FormSuggestionViewClient;
// A scrollable view for displaying user-selectable autofill form suggestions. // A scrollable view for displaying user-selectable autofill form suggestions.
@interface FormSuggestionView : UIScrollView<UIInputViewAudioFeedback> @interface FormSuggestionView : UIScrollView<UIInputViewAudioFeedback>
// The current suggestions this view is showing.
@property(nonatomic, readonly) NSArray<FormSuggestion*>* suggestions;
// A view added at the end of the current suggestions.
@property(nonatomic, strong) UIView* trailingView;
// Initializes with |frame| and |client| to show |suggestions|. // Initializes with |frame| and |client| to show |suggestions|.
- (instancetype)initWithFrame:(CGRect)frame - (instancetype)initWithFrame:(CGRect)frame
client:(id<FormSuggestionViewClient>)client client:(id<FormSuggestionViewClient>)client
suggestions:(NSArray*)suggestions; suggestions:(NSArray<FormSuggestion*>*)suggestions;
@end
@interface FormSuggestionView (ForTesting)
@property(nonatomic, readonly) NSArray* suggestions;
@end @end
#endif // IOS_CHROME_BROWSER_AUTOFILL_FORM_SUGGESTION_VIEW_H_ #endif // IOS_CHROME_BROWSER_AUTOFILL_FORM_SUGGESTION_VIEW_H_
...@@ -39,13 +39,18 @@ const CGFloat kSuggestionHorizontalMargin = 6; ...@@ -39,13 +39,18 @@ const CGFloat kSuggestionHorizontalMargin = 6;
// The FormSuggestions that are displayed by this view. // The FormSuggestions that are displayed by this view.
NSArray* _suggestions; NSArray* _suggestions;
// The stack view with the suggestions.
UIStackView* _stackView;
// Handles user interactions. // Handles user interactions.
id<FormSuggestionViewClient> _client; id<FormSuggestionViewClient> _client;
} }
@synthesize trailingView = _trailingView;
- (instancetype)initWithFrame:(CGRect)frame - (instancetype)initWithFrame:(CGRect)frame
client:(id<FormSuggestionViewClient>)client client:(id<FormSuggestionViewClient>)client
suggestions:(NSArray*)suggestions { suggestions:(NSArray<FormSuggestion*>*)suggestions {
self = [super initWithFrame:frame]; self = [super initWithFrame:frame];
if (self) { if (self) {
_client = client; _client = client;
...@@ -122,10 +127,28 @@ const CGFloat kSuggestionHorizontalMargin = 6; ...@@ -122,10 +127,28 @@ const CGFloat kSuggestionHorizontalMargin = 6;
} }
}; };
[_suggestions enumerateObjectsUsingBlock:setupBlock]; [_suggestions enumerateObjectsUsingBlock:setupBlock];
if (self.trailingView) {
[stackView addArrangedSubview:self.trailingView];
}
_stackView = stackView;
} }
#pragma mark - Getters
- (NSArray*)suggestions { - (NSArray*)suggestions {
return _suggestions; return _suggestions;
} }
#pragma mark - Setters
- (void)setTrailingView:(UIView*)subview {
if (_trailingView.superview) {
[_stackView removeArrangedSubview:_trailingView];
}
_trailingView = subview;
if (_stackView) {
[_stackView addArrangedSubview:_trailingView];
}
}
@end @end
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
#import "ios/web/public/web_state/web_state_observer_bridge.h" #import "ios/web/public/web_state/web_state_observer_bridge.h"
@protocol CRWWebViewProxy;
class WebStateList; class WebStateList;
// Creates and manages a custom input accessory view while the user is // Creates and manages a custom input accessory view while the user is
......
...@@ -8,42 +8,117 @@ ...@@ -8,42 +8,117 @@
#import "components/autofill/ios/browser/js_suggestion_manager.h" #import "components/autofill/ios/browser/js_suggestion_manager.h"
#import "ios/chrome/browser/autofill/form_input_accessory_view_controller.h" #import "ios/chrome/browser/autofill/form_input_accessory_view_controller.h"
#import "ios/chrome/browser/ui/autofill/form_input_accessory_mediator.h" #import "ios/chrome/browser/ui/autofill/form_input_accessory_mediator.h"
#import "ios/chrome/browser/ui/autofill/manual_fill/manual_fill_accessory_view_controller.h"
#import "ios/chrome/browser/ui/autofill/manual_fill/password_coordinator.h"
#if !defined(__has_feature) || !__has_feature(objc_arc) #if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support." #error "This file requires ARC support."
#endif #endif
@interface FormInputAccessoryCoordinator () @interface FormInputAccessoryCoordinator ()<
ManualFillAccessoryViewControllerDelegate>
// The Mediator for the input accessory view controller.
@property(nonatomic, strong)
FormInputAccessoryMediator* formInputAccessoryMediator;
// The View Controller for the input accessory view. // The View Controller for the input accessory view.
@property FormInputAccessoryViewController* formInputAccessoryViewController; @property(nonatomic, strong)
FormInputAccessoryViewController* formInputAccessoryViewController;
// The Mediator for the input accessory view controller. // The manual fill accessory to show above the keyboard.
@property FormInputAccessoryMediator* formInputAccessoryMediator; @property(nonatomic, strong)
ManualFillAccessoryViewController* manualFillAccessoryViewController;
// The WebStateList for this instance. Used to instantiate the child
// coordinators lazily.
@property(nonatomic, assign) WebStateList* webStateList;
@end @end
@implementation FormInputAccessoryCoordinator @implementation FormInputAccessoryCoordinator
@synthesize formInputAccessoryMediator = _formInputAccessoryMediator;
@synthesize formInputAccessoryViewController = @synthesize formInputAccessoryViewController =
_formInputAccessoryViewController; _formInputAccessoryViewController;
@synthesize formInputAccessoryMediator = _formInputAccessoryMediator; @synthesize manualFillAccessoryViewController =
_manualFillAccessoryViewController;
@synthesize webStateList = _webStateList;
- (instancetype)initWithBaseViewController:(UIViewController*)viewController - (instancetype)initWithBaseViewController:(UIViewController*)viewController
browserState: browserState:
(ios::ChromeBrowserState*)browserState (ios::ChromeBrowserState*)browserState
webStateList:(WebStateList*)webStateList { webStateList:(WebStateList*)webStateList {
DCHECK(browserState);
DCHECK(webStateList);
self = [super initWithBaseViewController:viewController self = [super initWithBaseViewController:viewController
browserState:browserState]; browserState:browserState];
if (self) { if (self) {
DCHECK(webStateList); _webStateList = webStateList;
_formInputAccessoryViewController = _formInputAccessoryViewController =
[[FormInputAccessoryViewController alloc] init]; [[FormInputAccessoryViewController alloc] init];
_manualFillAccessoryViewController =
[[ManualFillAccessoryViewController alloc] initWithDelegate:self];
_formInputAccessoryMediator = [[FormInputAccessoryMediator alloc] _formInputAccessoryMediator = [[FormInputAccessoryMediator alloc]
initWithConsumer:self.formInputAccessoryViewController initWithConsumer:self.formInputAccessoryViewController
webStateList:webStateList]; webStateList:webStateList];
_formInputAccessoryMediator.manualFillAccessoryViewController =
_manualFillAccessoryViewController;
} }
return self; return self;
} }
- (void)stop {
[self stopChildren];
[self.manualFillAccessoryViewController reset];
[self.formInputAccessoryViewController restoreKeyboardView];
}
#pragma mark - Presenting Children
- (void)stopChildren {
for (ChromeCoordinator* coordinator in self.childCoordinators) {
[coordinator stop];
}
[self.childCoordinators removeAllObjects];
}
- (void)startPasswords {
ManualFillPasswordCoordinator* passwordCoordinator =
[[ManualFillPasswordCoordinator alloc]
initWithBaseViewController:self.baseViewController
browserState:self.browserState
webStateList:self.webStateList];
[self.formInputAccessoryViewController
presentView:passwordCoordinator.viewController.view];
[self.childCoordinators addObject:passwordCoordinator];
[self.formInputAccessoryMediator disableSuggestions];
}
#pragma mark - ManualFillAccessoryViewControllerDelegate
- (void)keyboardButtonPressed {
[self stopChildren];
[self.formInputAccessoryMediator enableSuggestions];
}
- (void)accountButtonPressed {
[self stopChildren];
// TODO(crbug.com/845472): implement.
}
- (void)cardButtonPressed {
[self stopChildren];
// TODO(crbug.com/845472): implement.
}
- (void)passwordButtonPressed {
[self stopChildren];
[self startPasswords];
}
@end @end
...@@ -11,9 +11,11 @@ ...@@ -11,9 +11,11 @@
#import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h" #import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h"
#import "ios/web/public/web_state/web_state_observer_bridge.h" #import "ios/web/public/web_state/web_state_observer_bridge.h"
@class ChromeCoordinator;
@protocol FormInputAccessoryConsumer; @protocol FormInputAccessoryConsumer;
@protocol FormInputAccessoryViewProvider; @protocol FormInputAccessoryViewProvider;
@class JsSuggestionManager; @class JsSuggestionManager;
@class ManualFillAccessoryViewController;
class WebStateList; class WebStateList;
namespace web { namespace web {
class WebState; class WebState;
...@@ -24,6 +26,11 @@ class WebState; ...@@ -24,6 +26,11 @@ class WebState;
// accessory view shoeuld be restored to the system default. // accessory view shoeuld be restored to the system default.
@interface FormInputAccessoryMediator : NSObject @interface FormInputAccessoryMediator : NSObject
// The manual fill accessory view controller to add at the end of the
// suggestions.
@property(nonatomic, weak)
ManualFillAccessoryViewController* manualFillAccessoryViewController;
// Returns a mediator observing the passed `WebStateList` and associated with // Returns a mediator observing the passed `WebStateList` and associated with
// the passed consumer. `webSateList` can be nullptr and `consumer` can be nil. // the passed consumer. `webSateList` can be nullptr and `consumer` can be nil.
- (instancetype)initWithConsumer:(id<FormInputAccessoryConsumer>)consumer - (instancetype)initWithConsumer:(id<FormInputAccessoryConsumer>)consumer
...@@ -32,6 +39,14 @@ class WebState; ...@@ -32,6 +39,14 @@ class WebState;
// Unavailable, use initWithConsumer:webStateList: instead. // Unavailable, use initWithConsumer:webStateList: instead.
- (instancetype)init NS_UNAVAILABLE; - (instancetype)init NS_UNAVAILABLE;
// Disables suggestions updates and asks the consumer to remove the current
// ones.
- (void)disableSuggestions;
// Enables suggestions updates and sends the current ones, if any, to the
// consumer.
- (void)enableSuggestions;
@end @end
// Methods to allow injection in tests. // Methods to allow injection in tests.
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "base/ios/block_types.h" #include "base/ios/block_types.h"
#include "base/mac/foundation_util.h" #include "base/mac/foundation_util.h"
#include "base/mac/scoped_block.h" #include "base/mac/scoped_block.h"
#include "components/autofill/core/common/autofill_features.h"
#import "components/autofill/ios/browser/js_suggestion_manager.h" #import "components/autofill/ios/browser/js_suggestion_manager.h"
#import "components/autofill/ios/form_util/form_activity_observer_bridge.h" #import "components/autofill/ios/form_util/form_activity_observer_bridge.h"
#include "components/autofill/ios/form_util/form_activity_params.h" #include "components/autofill/ios/form_util/form_activity_params.h"
...@@ -14,7 +15,10 @@ ...@@ -14,7 +15,10 @@
#import "ios/chrome/browser/autofill/form_input_accessory_view_handler.h" #import "ios/chrome/browser/autofill/form_input_accessory_view_handler.h"
#import "ios/chrome/browser/autofill/form_input_accessory_view_provider.h" #import "ios/chrome/browser/autofill/form_input_accessory_view_provider.h"
#import "ios/chrome/browser/autofill/form_suggestion_tab_helper.h" #import "ios/chrome/browser/autofill/form_suggestion_tab_helper.h"
#import "ios/chrome/browser/autofill/form_suggestion_view.h"
#import "ios/chrome/browser/passwords/password_generation_utils.h" #import "ios/chrome/browser/passwords/password_generation_utils.h"
#import "ios/chrome/browser/ui/autofill/manual_fill/manual_fill_accessory_view_controller.h"
#import "ios/chrome/browser/ui/coordinators/chrome_coordinator.h"
#include "ios/chrome/browser/ui/ui_util.h" #include "ios/chrome/browser/ui/ui_util.h"
#import "ios/chrome/browser/web_state_list/web_state_list.h" #import "ios/chrome/browser/web_state_list/web_state_list.h"
#import "ios/web/public/url_scheme_util.h" #import "ios/web/public/url_scheme_util.h"
...@@ -28,11 +32,6 @@ ...@@ -28,11 +32,6 @@
@interface FormInputAccessoryMediator ()<FormActivityObserver, @interface FormInputAccessoryMediator ()<FormActivityObserver,
CRWWebStateObserver, CRWWebStateObserver,
WebStateListObserving> WebStateListObserving>
@property(nonatomic, strong)
FormInputAccessoryViewHandler* formInputAccessoryHandler;
// The WebState this instance is observing. Can be null.
@property(nonatomic, assign) web::WebState* webState;
// The JS manager for interacting with the underlying form. // The JS manager for interacting with the underlying form.
@property(nonatomic, weak) JsSuggestionManager* JSSuggestionManager; @property(nonatomic, weak) JsSuggestionManager* JSSuggestionManager;
...@@ -43,11 +42,27 @@ ...@@ -43,11 +42,27 @@
// The object that manages the currently-shown custom accessory view. // The object that manages the currently-shown custom accessory view.
@property(nonatomic, weak) id<FormInputAccessoryViewProvider> currentProvider; @property(nonatomic, weak) id<FormInputAccessoryViewProvider> currentProvider;
// The form input handler. This is in charge of form navigation.
@property(nonatomic, strong)
FormInputAccessoryViewHandler* formInputAccessoryHandler;
// Last seen provider. Used to reenable suggestions.
@property(nonatomic, weak) id<FormInputAccessoryViewProvider> lastProvider;
// Last seen suggestions. Used to reenable suggestions.
@property(nonatomic, strong) UIView* lastSuggestionView;
// Whether suggestions are disabled.
@property(nonatomic, assign) BOOL suggestionsDisabled;
// The objects that can provide a custom input accessory view while filling // The objects that can provide a custom input accessory view while filling
// forms. // forms.
@property(nonatomic, copy) @property(nonatomic, copy)
NSArray<id<FormInputAccessoryViewProvider>>* providers; NSArray<id<FormInputAccessoryViewProvider>>* providers;
// The WebState this instance is observing. Can be null.
@property(nonatomic, assign) web::WebState* webState;
@end @end
@implementation FormInputAccessoryMediator { @implementation FormInputAccessoryMediator {
...@@ -73,7 +88,12 @@ ...@@ -73,7 +88,12 @@
@synthesize currentProvider = _currentProvider; @synthesize currentProvider = _currentProvider;
@synthesize formInputAccessoryHandler = _formInputAccessoryHandler; @synthesize formInputAccessoryHandler = _formInputAccessoryHandler;
@synthesize JSSuggestionManager = _JSSuggestionManager; @synthesize JSSuggestionManager = _JSSuggestionManager;
@synthesize lastProvider = _lastProvider;
@synthesize lastSuggestionView = _lastSuggestionView;
@synthesize manualFillAccessoryViewController =
_manualFillAccessoryViewController;
@synthesize providers = _providers; @synthesize providers = _providers;
@synthesize suggestionsDisabled = _suggestionsDisabled;
@synthesize webState = _webState; @synthesize webState = _webState;
- (instancetype)initWithConsumer:(id<FormInputAccessoryConsumer>)consumer - (instancetype)initWithConsumer:(id<FormInputAccessoryConsumer>)consumer
...@@ -199,6 +219,32 @@ ...@@ -199,6 +219,32 @@
[self updateWithNewWebState:newWebState]; [self updateWithNewWebState:newWebState];
} }
#pragma mark - Public
- (void)disableSuggestions {
self.suggestionsDisabled = YES;
[self updateWithProvider:nil suggestionView:nil];
}
- (void)enableSuggestions {
self.suggestionsDisabled = NO;
if (self.lastProvider && self.lastSuggestionView) {
[self updateWithProvider:self.lastProvider
suggestionView:self.lastSuggestionView];
}
}
#pragma mark - Setters
- (void)setCurrentProvider:(id<FormInputAccessoryViewProvider>)currentProvider {
if (_currentProvider == currentProvider) {
return;
}
[_currentProvider inputAccessoryViewControllerDidReset];
_currentProvider = currentProvider;
[_currentProvider setAccessoryViewDelegate:self.formInputAccessoryHandler];
}
#pragma mark - Private #pragma mark - Private
// Updates the accessory mediator with the passed web state, its JS suggestion // Updates the accessory mediator with the passed web state, its JS suggestion
...@@ -227,11 +273,12 @@ ...@@ -227,11 +273,12 @@
} }
} }
// Resets the current provider, the handler and the consumer to a clean state. // Resets the current provider, the consumer view and the navigation handler. As
// well as reenables suggestions.
- (void)reset { - (void)reset {
[self.consumer restoreDefaultInputAccessoryView]; [self.consumer restoreKeyboardView];
self.suggestionsDisabled = NO;
[self.currentProvider inputAccessoryViewControllerDidReset];
self.currentProvider = nil; self.currentProvider = nil;
[self.formInputAccessoryHandler reset]; [self.formInputAccessoryHandler reset];
...@@ -242,6 +289,10 @@ ...@@ -242,6 +289,10 @@
- (void)retrieveAccessoryViewForForm:(const autofill::FormActivityParams&)params - (void)retrieveAccessoryViewForForm:(const autofill::FormActivityParams&)params
webState:(web::WebState*)webState { webState:(web::WebState*)webState {
DCHECK_EQ(webState, self.webState); DCHECK_EQ(webState, self.webState);
// TODO(crbug.com/845472): refactor this overly complex code. There is
// always at max one provider in _providers.
// Build a block for each provider that will invoke its completion with YES // Build a block for each provider that will invoke its completion with YES
// if the provider can provide an accessory view for the specified form/field // if the provider can provide an accessory view for the specified form/field
// and NO otherwise. // and NO otherwise.
...@@ -255,13 +306,11 @@ ...@@ -255,13 +306,11 @@
// Run all the blocks in |findProviderBlocks| until one invokes its // Run all the blocks in |findProviderBlocks| until one invokes its
// completion with YES. The first one to do so will be passed to // completion with YES. The first one to do so will be passed to
// |onProviderFound|. // |onProviderFound|.
__weak __typeof(self) weakSelf = self; passwords::RunSearchPipeline(findProviderBlocks, ^(NSUInteger providerIndex){
passwords::RunSearchPipeline(findProviderBlocks, ^(NSUInteger providerIndex) { // No need to do anything if no suggestions
// If no view was retrieved, reset self. // are found. The provider will
if (providerIndex == NSNotFound) { // update with an empty suggestions array.
[weakSelf reset]; });
}
});
} }
// Returns a pipeline block used to search for a provider with the current form // Returns a pipeline block used to search for a provider with the current form
...@@ -297,21 +346,46 @@ queryViewBlockForProvider:(id<FormInputAccessoryViewProvider>)provider ...@@ -297,21 +346,46 @@ queryViewBlockForProvider:(id<FormInputAccessoryViewProvider>)provider
// Once the view is retrieved, tell the pipeline to stop and // Once the view is retrieved, tell the pipeline to stop and
// update the UI. // update the UI.
completion(YES); completion(YES);
FormInputAccessoryMediator* strongSelf = weakSelf; [weakSelf updateWithProvider:provider suggestionView:accessoryView];
if (!strongSelf) {
return;
}
if (strongSelf.currentProvider != provider) {
[strongSelf.currentProvider inputAccessoryViewControllerDidReset];
}
strongSelf.currentProvider = provider;
[provider setAccessoryViewDelegate:strongSelf.formInputAccessoryHandler];
[strongSelf.consumer
showCustomInputAccessoryView:accessoryView
navigationDelegate:strongSelf.formInputAccessoryHandler];
}; };
} }
// Post the passed |suggestionView| to the consumer. In case suggestions are
// disabled, it's keep for later.
- (void)updateWithProvider:(id<FormInputAccessoryViewProvider>)provider
suggestionView:(UIView*)suggestionView {
// If the povider is valid, save the view and the provider for later. This is
// used to restore the state when re-enabling suggestions.
if (provider) {
self.lastSuggestionView = suggestionView;
self.lastProvider = provider;
}
// If the suggestions are disabled, post this view with no suggestions to the
// consumer. This allows the navigation buttons be in sync.
UIView* consumerView = suggestionView;
if (self.suggestionsDisabled) {
consumerView = [[FormSuggestionView alloc] initWithFrame:CGRectZero
client:nil
suggestions:@[]];
} else {
// If suggestions are enabled update |currentProvider|.
self.currentProvider = provider;
}
// If Manual Fallback is enabled, add its view after the suggestions.
BOOL isManualFillEnabled =
base::FeatureList::IsEnabled(autofill::features::kAutofillManualFallback);
if (isManualFillEnabled) {
FormSuggestionView* formSuggestionView =
base::mac::ObjCCast<FormSuggestionView>(consumerView);
formSuggestionView.trailingView =
self.manualFillAccessoryViewController.view;
}
// Post it to the consumer.
[self.consumer showCustomInputAccessoryView:consumerView
navigationDelegate:self.formInputAccessoryHandler];
}
// When any text field or text view (e.g. omnibox, settings, card unmask dialog) // When any text field or text view (e.g. omnibox, settings, card unmask dialog)
// begins editing, reset ourselves so that we don't present our custom view over // begins editing, reset ourselves so that we don't present our custom view over
// the keyboard. // the keyboard.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment