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

[iOS][AF] Create FormInputAccessoryMediator

This CL moves all the logic from the FormInputAccessoryViewController
that is not directly related to managing its view to a new mediator.
Creates a consumer for the communication the the view controller.
Updates tests to use this mediator instead.
Removes the dependency on the CRWWebViewProxy.

Cq-Include-Trybots: luci.chromium.try:ios-simulator-full-configs;master.tryserver.chromium.mac:ios-simulator-cronet
Change-Id: I1b0f6c595f069ea45e04c9dacdc4437dbfe9041a
Bug: 661622, 727716, 845472, 847408
Reviewed-on: https://chromium-review.googlesource.com/1130522Reviewed-by: default avatarMoe Ahmadi <mahmadi@chromium.org>
Reviewed-by: default avatarMark Cogan <marq@chromium.org>
Commit-Queue: Javier Ernesto Flores Robles <javierrobles@chromium.org>
Cr-Commit-Position: refs/heads/master@{#582056}
parent f85180e5
......@@ -7,11 +7,15 @@ source_set("autofill") {
sources = [
"address_normalizer_factory.cc",
"address_normalizer_factory.h",
"form_input_accessory_consumer.h",
"form_input_accessory_view.h",
"form_input_accessory_view.mm",
"form_input_accessory_view_controller.h",
"form_input_accessory_view_controller.mm",
"form_input_accessory_view_delegate.h",
"form_input_accessory_view_handler.h",
"form_input_accessory_view_handler.mm",
"form_input_accessory_view_provider.h",
"form_suggestion_controller.h",
"form_suggestion_controller.mm",
"form_suggestion_label.h",
......@@ -48,7 +52,6 @@ source_set("autofill") {
"//base:i18n",
"//components/autofill/core/browser",
"//components/autofill/ios/browser",
"//components/autofill/ios/form_util",
"//components/keyed_service/core",
"//components/keyed_service/ios",
"//components/prefs",
......@@ -474,6 +477,7 @@ source_set("eg_tests") {
":autofill",
"//base",
"//base/test:test_support",
"//components/autofill/ios/browser:browser",
"//ios/chrome/app/strings",
"//ios/chrome/browser/ui",
"//ios/chrome/test/app:test_support",
......
......@@ -9,6 +9,7 @@
#include "base/guid.h"
#include "base/ios/ios_util.h"
#include "base/mac/foundation_util.h"
#include "base/memory/ptr_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/task_scheduler/task_scheduler.h"
......@@ -19,17 +20,18 @@
#import "components/autofill/ios/browser/autofill_agent.h"
#include "components/autofill/ios/browser/autofill_driver_ios.h"
#import "components/autofill/ios/browser/form_suggestion.h"
#import "components/autofill/ios/browser/js_suggestion_manager.h"
#include "components/infobars/core/confirm_infobar_delegate.h"
#include "components/infobars/core/infobar.h"
#include "components/infobars/core/infobar_manager.h"
#include "components/keyed_service/core/service_access_type.h"
#include "components/security_state/ios/ssl_status_input_event_data.h"
#import "ios/chrome/browser/autofill/form_input_accessory_view_controller.h"
#import "ios/chrome/browser/autofill/form_suggestion_controller.h"
#include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
#include "ios/chrome/browser/infobars/infobar_manager_impl.h"
#include "ios/chrome/browser/ssl/ios_security_state_tab_helper.h"
#import "ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h"
#import "ios/chrome/browser/ui/autofill/form_input_accessory_mediator.h"
#include "ios/chrome/browser/ui/settings/personal_data_manager_data_changed_observer.h"
#include "ios/chrome/browser/web/chrome_web_client.h"
#import "ios/chrome/browser/web/chrome_web_test.h"
......@@ -37,6 +39,7 @@
#import "ios/web/public/navigation_item.h"
#import "ios/web/public/navigation_manager.h"
#include "ios/web/public/ssl_status.h"
#import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
#import "ios/web/public/web_state/web_state.h"
#import "testing/gtest_mac.h"
#include "ui/base/test/ios/ui_view_test_utils.h"
......@@ -192,7 +195,7 @@ class AutofillControllerTest : public ChromeWebTest {
TestSuggestionController* suggestion_controller_;
// Retrieves accessory views according to form events.
FormInputAccessoryViewController* accessory_controller_;
FormInputAccessoryMediator* accessory_mediator_;
// Manages autofill for a single page.
AutofillController* autofill_controller_;
......@@ -222,9 +225,17 @@ void AutofillControllerTest::SetUp() {
suggestion_controller_ = [[TestSuggestionController alloc]
initWithWebState:web_state()
providers:@[ [autofill_controller_ suggestionProvider] ]];
accessory_controller_ = [[FormInputAccessoryViewController alloc]
initWithWebState:web_state()
providers:@[ [suggestion_controller_ accessoryViewProvider] ]];
accessory_mediator_ =
[[FormInputAccessoryMediator alloc] initWithConsumer:nil
webStateList:NULL];
[accessory_mediator_ injectWebState:web_state()];
[accessory_mediator_
injectProviders:@[ [suggestion_controller_ accessoryViewProvider] ]];
auto suggestionManager = base::mac::ObjCCastStrict<JsSuggestionManager>(
[web_state()->GetJSInjectionReceiver()
instanceOfClass:[JsSuggestionManager class]]);
[accessory_mediator_ injectSuggestionManager:suggestionManager];
histogram_tester_.reset(new base::HistogramTester());
}
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_CONSUMER_H_
#define IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_CONSUMER_H_
#import <UIKit/UIKit.h>
@protocol FormInputAccessoryViewDelegate;
@protocol FormInputAccessoryConsumer<NSObject>
// Restores the default input accessory view, removing (if necessary) any
// previously-added custom view.
- (void)restoreDefaultInputAccessoryView;
// Hides the default input accessory view and replaces it with one that shows
// |customView| and form navigation controls.
- (void)showCustomInputAccessoryView:(UIView*)view
navigationDelegate:
(id<FormInputAccessoryViewDelegate>)navigationDelegate;
@end
#endif // IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_CONSUMER_H_
......@@ -7,16 +7,9 @@
#import <UIKit/UIKit.h>
#import "ios/chrome/browser/autofill/form_input_accessory_view_delegate.h"
#import "ios/web/public/web_state/web_state_observer_bridge.h"
@protocol CRWWebViewProxy;
@protocol FormInputAccessoryViewProvider;
#import "ios/chrome/browser/autofill/form_input_accessory_consumer.h"
namespace autofill {
extern NSString* const kFormSuggestionAssistButtonPreviousElement;
extern NSString* const kFormSuggestionAssistButtonNextElement;
extern NSString* const kFormSuggestionAssistButtonDone;
extern CGFloat const kInputAccessoryHeight;
} // namespace autofill
......@@ -24,33 +17,7 @@ extern CGFloat const kInputAccessoryHeight;
// interacting with a form. Also handles hiding and showing the default
// accessory view elements.
@interface FormInputAccessoryViewController
: NSObject<CRWWebStateObserver, FormInputAccessoryViewDelegate>
// The current web view proxy.
// TODO(crbug.com/727716): This property should not be a part of the public
// interface, it is used in tests as a backdoor.
@property(nonatomic, readonly) id<CRWWebViewProxy> webViewProxy;
// The current web state that this view controller is observing.
@property(nonatomic) web::WebState* webState;
// Initializes a new controller with the specified |providers| of input
// accessory views.
- (instancetype)initWithWebState:(web::WebState*)webState
providers:(NSArray<id<FormInputAccessoryViewProvider>>*)
providers;
// Instructs the controller to detach itself from the WebState.
- (void)detachFromWebState;
// Hides the default input accessory view and replaces it with one that shows
// |customView| and form navigation controls.
- (void)showCustomInputAccessoryView:(UIView*)customView;
// Restores the default input accessory view, removing (if necessary) any
// previously-added custom view.
- (void)restoreDefaultInputAccessoryView;
: NSObject<FormInputAccessoryConsumer>
@end
#endif // IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_VIEW_CONTROLLER_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_VIEW_HANDLER_H_
#define IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_VIEW_HANDLER_H_
#import "ios/chrome/browser/autofill/form_input_accessory_view_delegate.h"
@class JsSuggestionManager;
// This handles user actions in the default keyboard accessory view buttons.
@interface FormInputAccessoryViewHandler
: NSObject<FormInputAccessoryViewDelegate>
// The JS manager for interacting with the underlying form.
@property(nonatomic, weak) JsSuggestionManager* JSSuggestionManager;
// Resets the metrics logger of the instance.
- (void)reset;
@end
#endif // IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_VIEW_HANDLER_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/autofill/form_input_accessory_view_handler.h"
#import <UIKit/UIKit.h>
#include "base/mac/foundation_util.h"
#import "components/autofill/core/browser/keyboard_accessory_metrics_logger.h"
#import "components/autofill/ios/browser/js_suggestion_manager.h"
#import "ios/chrome/browser/ui/uikit_ui_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace autofill {
NSString* const kFormSuggestionAssistButtonPreviousElement = @"previousTap";
NSString* const kFormSuggestionAssistButtonNextElement = @"nextTap";
NSString* const kFormSuggestionAssistButtonDone = @"done";
} // namespace autofill
namespace {
// Finds all views of a particular kind if class |aClass| in the subview
// hierarchy of the given |root| view.
NSArray* SubviewsWithClass(UIView* root, Class aClass) {
DCHECK(root);
NSMutableArray* viewsToExamine = [NSMutableArray arrayWithObject:root];
NSMutableArray* subviews = [NSMutableArray array];
while ([viewsToExamine count]) {
UIView* view = [viewsToExamine lastObject];
if ([view isKindOfClass:aClass])
[subviews addObject:view];
[viewsToExamine removeLastObject];
[viewsToExamine addObjectsFromArray:[view subviews]];
}
return subviews;
}
// Returns true if |item|'s action name contains |actionName|.
BOOL ItemActionMatchesName(UIBarButtonItem* item, NSString* actionName) {
SEL itemAction = [item action];
if (!itemAction)
return false;
NSString* itemActionName = NSStringFromSelector(itemAction);
// This doesn't do a strict string match for the action name.
return [itemActionName rangeOfString:actionName].location != NSNotFound;
}
// Finds all UIToolbarItems associated with a given UIToolbar |toolbar| with
// action selectors with a name that contains the action name specified by
// |actionName|.
NSArray* FindToolbarItemsForActionName(UIToolbar* toolbar,
NSString* actionName) {
NSMutableArray* toolbarItems = [NSMutableArray array];
for (UIBarButtonItem* item in [toolbar items]) {
if (ItemActionMatchesName(item, actionName))
[toolbarItems addObject:item];
}
return toolbarItems;
}
// Finds all UIToolbarItem(s) with action selectors of the name specified by
// |actionName| in any UIToolbars in the view hierarchy below |root|.
NSArray* FindDescendantToolbarItemsForActionName(UIView* root,
NSString* actionName) {
NSMutableArray* descendants = [NSMutableArray array];
NSArray* toolbars = SubviewsWithClass(root, [UIToolbar class]);
for (UIToolbar* toolbar in toolbars) {
[descendants
addObjectsFromArray:FindToolbarItemsForActionName(toolbar, actionName)];
}
return descendants;
}
// Finds all UIBarButtonItem(s) with action selectors of the name specified by
// |actionName| in the UITextInputAssistantItem passed.
NSArray* FindDescendantToolbarItemsForActionName(
UITextInputAssistantItem* inputAssistantItem,
NSString* actionName) {
NSMutableArray* toolbarItems = [NSMutableArray array];
NSMutableArray* buttonGroupsGroup = [[NSMutableArray alloc] init];
if (inputAssistantItem.leadingBarButtonGroups)
[buttonGroupsGroup addObject:inputAssistantItem.leadingBarButtonGroups];
if (inputAssistantItem.trailingBarButtonGroups)
[buttonGroupsGroup addObject:inputAssistantItem.trailingBarButtonGroups];
for (NSArray* buttonGroups in buttonGroupsGroup) {
for (UIBarButtonItemGroup* group in buttonGroups) {
NSArray* items = group.barButtonItems;
for (UIBarButtonItem* item in items) {
if (ItemActionMatchesName(item, actionName))
[toolbarItems addObject:item];
}
}
}
return toolbarItems;
}
} // namespace
@implementation FormInputAccessoryViewHandler {
// Logs UMA metrics for the keyboard accessory.
std::unique_ptr<autofill::KeyboardAccessoryMetricsLogger>
_keyboardAccessoryMetricsLogger;
}
@synthesize JSSuggestionManager = _JSSuggestionManager;
- (instancetype)init {
self = [super init];
if (self) {
_keyboardAccessoryMetricsLogger.reset(
new autofill::KeyboardAccessoryMetricsLogger());
}
return self;
}
// Attempts to execute/tap/send-an-event-to the iOS built-in "next" and
// "previous" form assist controls. Returns NO if this attempt failed, YES
// otherwise. [HACK] Because the buttons on the assist controls can change any
// time, this can break with any new iOS version.
- (BOOL)executeFormAssistAction:(NSString*)actionName {
NSArray* descendants = nil;
if (IsIPadIdiom()) {
// There is no input accessory view for iPads, instead Apple adds the assist
// controls to the UITextInputAssistantItem.
UIResponder* firstResponder = GetFirstResponder();
UITextInputAssistantItem* inputAssistantItem =
firstResponder.inputAssistantItem;
if (!inputAssistantItem)
return NO;
descendants =
FindDescendantToolbarItemsForActionName(inputAssistantItem, actionName);
} else {
UIResponder* firstResponder = GetFirstResponder();
UIView* inputAccessoryView = firstResponder.inputAccessoryView;
if (!inputAccessoryView)
return NO;
descendants =
FindDescendantToolbarItemsForActionName(inputAccessoryView, actionName);
}
if (![descendants count])
return NO;
UIBarButtonItem* item = descendants.firstObject;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[[item target] performSelector:[item action] withObject:item];
#pragma clang diagnostic pop
return YES;
}
- (void)reset {
_keyboardAccessoryMetricsLogger.reset(
new autofill::KeyboardAccessoryMetricsLogger());
}
#pragma mark - FormInputAccessoryViewDelegate
- (void)closeKeyboardWithButtonPress {
[self closeKeyboardLoggingButtonPressed:YES];
}
- (void)closeKeyboardWithoutButtonPress {
[self closeKeyboardLoggingButtonPressed:NO];
}
- (void)selectPreviousElementWithButtonPress {
[self selectPreviousElementLoggingButtonPressed:YES];
}
- (void)selectPreviousElementWithoutButtonPress {
[self selectPreviousElementLoggingButtonPressed:NO];
}
- (void)selectNextElementWithButtonPress {
[self selectNextElementLoggingButtonPressed:YES];
}
- (void)selectNextElementWithoutButtonPress {
[self selectNextElementLoggingButtonPressed:NO];
}
- (void)fetchPreviousAndNextElementsPresenceWithCompletionHandler:
(void (^)(BOOL, BOOL))completionHandler {
DCHECK(completionHandler);
[_JSSuggestionManager
fetchPreviousAndNextElementsPresenceWithCompletionHandler:
completionHandler];
}
#pragma mark - Private
// Tries to close the keyboard sendind an action to the default accessory bar
// if that fails, fallbacks on JavaScript. Logs metrics if loggingButtonPressed
// is YES.
- (void)closeKeyboardLoggingButtonPressed:(BOOL)loggingButtonPressed {
NSString* actionName = autofill::kFormSuggestionAssistButtonDone;
BOOL performedAction = [self executeFormAssistAction:actionName];
if (!performedAction) {
// We could not find the built-in form assist controls, so try to focus
// the next or previous control using JavaScript.
[_JSSuggestionManager closeKeyboard];
}
if (loggingButtonPressed) {
_keyboardAccessoryMetricsLogger->OnCloseButtonPressed();
}
}
// Tries to focus on the next element sendind an action to the default accessory
// bar if that fails, fallbacks on JavaScript. Logs metrics if
// loggingButtonPressed is YES.
- (void)selectPreviousElementLoggingButtonPressed:(BOOL)loggingButtonPressed {
NSString* actionName = autofill::kFormSuggestionAssistButtonPreviousElement;
BOOL performedAction = [self executeFormAssistAction:actionName];
if (!performedAction) {
// We could not find the built-in form assist controls, so try to focus
// the next or previous control using JavaScript.
[_JSSuggestionManager selectPreviousElement];
}
if (loggingButtonPressed) {
_keyboardAccessoryMetricsLogger->OnPreviousButtonPressed();
}
}
// Tries to focus on the previous element sendind an action to the default
// accessory bar if that fails, fallbacks on JavaScript. Logs metrics if
// loggingButtonPressed is YES.
- (void)selectNextElementLoggingButtonPressed:(BOOL)loggingButtonPressed {
NSString* actionName = autofill::kFormSuggestionAssistButtonNextElement;
BOOL performedAction = [self executeFormAssistAction:actionName];
if (!performedAction) {
// We could not find the built-in form assist controls, so try to focus
// the next or previous control using JavaScript.
[_JSSuggestionManager selectNextElement];
}
if (loggingButtonPressed) {
_keyboardAccessoryMetricsLogger->OnNextButtonPressed();
}
}
@end
......@@ -5,6 +5,8 @@
#ifndef IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_VIEW_PROVIDER_H_
#define IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_VIEW_PROVIDER_H_
#import <UIKit/UIKit.h>
namespace web {
struct FormActivityParams;
class WebState;
......@@ -12,9 +14,6 @@ class WebState;
@protocol FormInputAccessoryViewDelegate;
@protocol FormInputAccessoryViewProvider;
@class FormInputAccessoryViewController;
#import <UIKit/UIKit.h>
// Block type to provide an accessory view asynchronously.
typedef void (^AccessoryViewReadyCompletion)(
......@@ -37,13 +36,7 @@ typedef void (^AccessoryViewReadyCompletion)(
(AccessoryViewReadyCompletion)accessoryViewUpdateBlock;
// Notifies this provider that the accessory view is going away.
- (void)inputAccessoryViewControllerDidReset:
(FormInputAccessoryViewController*)controller;
// Notifies this provider that the accessory view frame is changing. If the
// view provided by this provider needs to change, the updated view should be
// set using |accessoryViewUpdateBlock|.
- (void)resizeAccessoryView;
- (void)inputAccessoryViewControllerDidReset;
@end
......
......@@ -4,9 +4,11 @@
#import <EarlGrey/EarlGrey.h>
#include "base/mac/foundation_util.h"
#include "base/strings/sys_string_conversions.h"
#import "base/test/ios/wait_util.h"
#import "ios/chrome/browser/autofill/form_input_accessory_view_controller.h"
#import "components/autofill/ios/browser/js_suggestion_manager.h"
#import "ios/chrome/browser/autofill/form_input_accessory_view_handler.h"
#include "ios/chrome/browser/ui/ui_util.h"
#include "ios/chrome/grit/ios_strings.h"
#import "ios/chrome/test/app/chrome_test_util.h"
......@@ -19,6 +21,7 @@
#include "ios/web/public/test/element_selector.h"
#import "ios/web/public/test/http_server/http_server.h"
#include "ios/web/public/test/http_server/http_server_util.h"
#import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
......@@ -154,14 +157,17 @@ void AssertElementIsFocused(const std::string& element_id) {
// Tests that trying to programmatically dismiss the keyboard when it isn't
// visible doesn't crash the browser.
- (void)testCloseKeyboardWhenNotVisible {
FormInputAccessoryViewController* viewController =
[[FormInputAccessoryViewController alloc] init];
GREYAssertNotNil(
viewController,
@"The input accessory view controller should not be non nil.");
[viewController closeKeyboardWithoutButtonPress];
viewController.webState = chrome_test_util::GetCurrentWebState();
[viewController closeKeyboardWithoutButtonPress];
FormInputAccessoryViewHandler* accessoryViewDelegate =
[[FormInputAccessoryViewHandler alloc] init];
GREYAssertNotNil(accessoryViewDelegate,
@"The Accessory View Delegate should not be non nil.");
[accessoryViewDelegate closeKeyboardWithoutButtonPress];
CRWJSInjectionReceiver* injectionReceiver =
chrome_test_util::GetCurrentWebState()->GetJSInjectionReceiver();
accessoryViewDelegate.JSSuggestionManager =
base::mac::ObjCCastStrict<JsSuggestionManager>(
[injectionReceiver instanceOfClass:[JsSuggestionManager class]]);
[accessoryViewDelegate closeKeyboardWithoutButtonPress];
}
@end
......@@ -16,6 +16,7 @@
#import "components/autofill/ios/browser/form_suggestion.h"
#import "components/autofill/ios/browser/form_suggestion_provider.h"
#import "ios/chrome/browser/autofill/form_input_accessory_view_controller.h"
#import "ios/chrome/browser/autofill/form_input_accessory_view_delegate.h"
#import "ios/chrome/browser/autofill/form_input_accessory_view_provider.h"
#import "ios/chrome/browser/autofill/form_suggestion_view.h"
#import "ios/chrome/browser/passwords/password_generation_utils.h"
......@@ -350,7 +351,6 @@ AutofillSuggestionState::AutofillSuggestionState(
completionHandler:^{
[[weakSelf accessoryViewDelegate] closeKeyboardWithoutButtonPress];
}];
_provider = nil;
}
- (id<FormInputAccessoryViewProvider>)accessoryViewProvider {
......@@ -379,14 +379,9 @@ AutofillSuggestionState::AutofillSuggestionState(
[self retrieveSuggestionsForForm:params webState:webState];
}
- (void)inputAccessoryViewControllerDidReset:
(FormInputAccessoryViewController*)controller {
- (void)inputAccessoryViewControllerDidReset {
accessoryViewUpdateBlock_ = nil;
[self resetSuggestionState];
}
- (void)resizeAccessoryView {
[self updateKeyboard:_suggestionState.get()];
}
@end
......@@ -14,8 +14,9 @@
#import "components/autofill/ios/browser/form_suggestion_provider.h"
#import "components/autofill/ios/form_util/form_activity_observer_bridge.h"
#include "components/autofill/ios/form_util/test_form_activity_tab_helper.h"
#import "ios/chrome/browser/autofill/form_input_accessory_view_controller.h"
#import "ios/chrome/browser/autofill/form_input_accessory_consumer.h"
#import "ios/chrome/browser/autofill/form_suggestion_view.h"
#import "ios/chrome/browser/ui/autofill/form_input_accessory_mediator.h"
#include "ios/chrome/browser/ui/ui_util.h"
#import "ios/web/public/navigation_manager.h"
#import "ios/web/public/test/fakes/test_web_state.h"
......@@ -31,12 +32,6 @@
#error "This file requires ARC support."
#endif
@interface FormInputAccessoryViewController (Testing)
- (instancetype)initWithWebState:(web::WebState*)webState
JSSuggestionManager:(JsSuggestionManager*)JSSuggestionManager
providers:(NSArray*)providers;
@end
// Test provider that records invocations of its interface methods.
@interface TestSuggestionProvider : NSObject<FormSuggestionProvider>
......@@ -181,51 +176,37 @@ class FormSuggestionControllerTest : public PlatformTest {
providers:providers
JsSuggestionManager:mock_js_suggestion_manager_];
[suggestion_controller_ setWebViewProxy:mock_web_view_proxy_];
@autoreleasepool {
accessory_controller_ = [[FormInputAccessoryViewController alloc]
initWithWebState:&test_web_state_
JSSuggestionManager:mock_js_suggestion_manager_
providers:@[
[suggestion_controller_ accessoryViewProvider]
]];
}
// Mock out the FormInputAccessoryViewController so it can use the fake
// CRWWebViewProxy
id mock_accessory_controller =
[OCMockObject partialMockForObject:accessory_controller_];
[[[mock_accessory_controller stub] andReturn:mock_web_view_proxy_]
webViewProxy];
// On iPad devices, the suggestion view is added directly to the
// keyboard view instead of to the input accessory view which is no longer
// available on iPad devices. The following code mocks out the methods on
// FormInputAccessoryViewController that add and remove the suggestion view.
// The mocks now just add and remove it directly to and from
// input_accessory_view_ so that the tests can locate it with
// GetSuggestionView (defined above).
// TODO(crbug.com/661622): Revisit this to see if there's a better way to
// test the iPad case. At a minimum, the name 'input_accessory_view_' should
// be made more generic.
if (IsIPadIdiom()) {
void (^mockShow)(NSInvocation*) = ^(NSInvocation* invocation) {
__unsafe_unretained UIView* view;
[invocation getArgument:&view atIndex:2];
for (UIView* view in [input_accessory_view_ subviews]) {
[view removeFromSuperview];
}
[input_accessory_view_ addSubview:view];
};
[[[mock_accessory_controller stub] andDo:mockShow]
showCustomInputAccessoryView:[OCMArg any]];
void (^mockRestore)(NSInvocation*) = ^(NSInvocation* invocation) {
for (UIView* view in [input_accessory_view_ subviews]) {
[view removeFromSuperview];
}
};
[[[mock_accessory_controller stub] andDo:mockRestore]
restoreDefaultInputAccessoryView];
}
id mock_consumer_ = [OCMockObject
niceMockForProtocol:@protocol(FormInputAccessoryConsumer)];
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_];
// Mock the mediator consumer used to verify the suggestion views.
void (^mockShow)(NSInvocation*) = ^(NSInvocation* invocation) {
for (UIView* view in [input_accessory_view_ subviews]) {
[view removeFromSuperview];
}
__unsafe_unretained UIView* view;
[invocation getArgument:&view atIndex:2];
[input_accessory_view_ addSubview:view];
};
[[[mock_consumer_ stub] andDo:mockShow]
showCustomInputAccessoryView:[OCMArg any]
navigationDelegate:[OCMArg any]];
void (^mockRestore)(NSInvocation*) = ^(NSInvocation* invocation) {
for (UIView* view in [input_accessory_view_ subviews]) {
[view removeFromSuperview];
}
};
[[[mock_consumer_ stub] andDo:mockRestore]
restoreDefaultInputAccessoryView];
}
// The FormSuggestionController under test.
......@@ -241,7 +222,7 @@ class FormSuggestionControllerTest : public PlatformTest {
id mock_web_view_proxy_;
// Accessory view controller.
FormInputAccessoryViewController* accessory_controller_;
FormInputAccessoryMediator* accessory_mediator_;
// The fake WebState to simulate navigation and JavaScript events.
web::TestWebState test_web_state_;
......
......@@ -137,6 +137,7 @@ source_set("unit_tests") {
"//ios/chrome/browser/autofill",
"//ios/chrome/browser/browser_state:test_support",
"//ios/chrome/browser/ssl",
"//ios/chrome/browser/ui/autofill:autofill",
"//ios/chrome/browser/ui/commands",
"//ios/chrome/browser/web:test_support",
"//ios/chrome/browser/web:web_internal",
......
......@@ -27,10 +27,10 @@
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "components/security_state/ios/ssl_status_input_event_data.h"
#import "ios/chrome/browser/autofill/form_input_accessory_view_controller.h"
#import "ios/chrome/browser/autofill/form_suggestion_controller.h"
#include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
#import "ios/chrome/browser/passwords/password_form_filler.h"
#import "ios/chrome/browser/ui/autofill/form_input_accessory_mediator.h"
#include "ios/chrome/browser/web/chrome_web_client.h"
#import "ios/chrome/browser/web/chrome_web_test.h"
#import "ios/web/public/navigation_item.h"
......@@ -207,9 +207,12 @@ class PasswordControllerTest : public ChromeWebTest {
suggestionController_ = [[PasswordsTestSuggestionController alloc]
initWithWebState:web_state()
providers:@[ [passwordController_ suggestionProvider] ]];
accessoryViewController_ = [[FormInputAccessoryViewController alloc]
initWithWebState:web_state()
providers:@[ [suggestionController_ accessoryViewProvider] ]];
accessoryMediator_ =
[[FormInputAccessoryMediator alloc] initWithConsumer:nil
webStateList:NULL];
[accessoryMediator_ injectWebState:web_state()];
[accessoryMediator_
injectProviders:@[ [suggestionController_ accessoryViewProvider] ]];
}
}
......@@ -275,8 +278,8 @@ class PasswordControllerTest : public ChromeWebTest {
// SuggestionController for testing.
PasswordsTestSuggestionController* suggestionController_;
// FormInputAccessoryViewController for testing.
FormInputAccessoryViewController* accessoryViewController_;
// FormInputAccessoryMediatorfor testing.
FormInputAccessoryMediator* accessoryMediator_;
// PasswordController for testing.
PasswordController* passwordController_;
......
......@@ -13,6 +13,8 @@ source_set("autofill") {
"chrome_autofill_client_ios.mm",
"form_input_accessory_coordinator.h",
"form_input_accessory_coordinator.mm",
"form_input_accessory_mediator.h",
"form_input_accessory_mediator.mm",
]
deps = [
":autofill_ui",
......@@ -20,6 +22,7 @@ source_set("autofill") {
"//components/autofill/core/browser",
"//components/autofill/core/common",
"//components/autofill/ios/browser",
"//components/autofill/ios/form_util",
"//components/browser_sync",
"//components/infobars/core",
"//components/keyed_service/core",
......@@ -32,6 +35,7 @@ source_set("autofill") {
"//ios/chrome/browser/browser_state",
"//ios/chrome/browser/infobars",
"//ios/chrome/browser/metrics",
"//ios/chrome/browser/passwords:passwords_generation_utils",
"//ios/chrome/browser/signin",
"//ios/chrome/browser/ssl",
"//ios/chrome/browser/sync",
......
......@@ -4,33 +4,30 @@
#import "ios/chrome/browser/ui/autofill/form_input_accessory_coordinator.h"
#include "base/mac/foundation_util.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/web_state_list/web_state_list.h"
#import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h"
#include "ios/web/public/web_state/web_state.h"
#import "ios/chrome/browser/ui/autofill/form_input_accessory_mediator.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface FormInputAccessoryCoordinator ()<WebStateListObserving>
@interface FormInputAccessoryCoordinator ()
// The View Controller for the input accessory view.
@property FormInputAccessoryViewController* formInputAccessoryViewController;
@end
// The Mediator for the input accessory view controller.
@property FormInputAccessoryMediator* formInputAccessoryMediator;
@implementation FormInputAccessoryCoordinator {
// The WebStateList this instance is observing in order to update the
// active WebState.
WebStateList* _webStateList;
@end
// Bridge to observe the web state list from Objective-C.
std::unique_ptr<WebStateListObserverBridge> _webStateListObserver;
}
@implementation FormInputAccessoryCoordinator
@synthesize formInputAccessoryViewController =
_formInputAccessoryViewController;
@synthesize formInputAccessoryMediator = _formInputAccessoryMediator;
- (instancetype)initWithBaseViewController:(UIViewController*)viewController
browserState:
......@@ -40,46 +37,13 @@
browserState:browserState];
if (self) {
DCHECK(webStateList);
_webStateList = webStateList;
_webStateListObserver = std::make_unique<WebStateListObserverBridge>(self);
_webStateList->AddObserver(_webStateListObserver.get());
_formInputAccessoryViewController =
[[FormInputAccessoryViewController alloc] init];
_formInputAccessoryMediator = [[FormInputAccessoryMediator alloc]
initWithConsumer:self.formInputAccessoryViewController
webStateList:webStateList];
}
return self;
}
- (void)dealloc {
if (_webStateList) {
_webStateList->RemoveObserver(_webStateListObserver.get());
_webStateListObserver.reset();
_webStateList = nullptr;
}
}
- (void)start {
self.formInputAccessoryViewController =
[[FormInputAccessoryViewController alloc] init];
[self updateFormInputAccessoryViewController];
}
- (void)stop {
self.formInputAccessoryViewController = nil;
}
#pragma mark - Private
- (void)updateFormInputAccessoryViewController {
web::WebState* activeWebState = _webStateList->GetActiveWebState();
self.formInputAccessoryViewController.webState = activeWebState;
}
#pragma mark - CRWWebStateListObserver
- (void)webStateList:(WebStateList*)webStateList
didChangeActiveWebState:(web::WebState*)newWebState
oldWebState:(web::WebState*)oldWebState
atIndex:(int)atIndex
reason:(int)reason {
self.formInputAccessoryViewController.webState = newWebState;
}
@end
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_AUTOFILL_FORM_INPUT_ACCESSORY_MEDIATOR_H_
#define IOS_CHROME_BROWSER_UI_AUTOFILL_FORM_INPUT_ACCESSORY_MEDIATOR_H_
#import <Foundation/Foundation.h>
#import "ios/chrome/browser/autofill/form_input_accessory_view_delegate.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"
@protocol FormInputAccessoryConsumer;
@protocol FormInputAccessoryViewProvider;
@class JsSuggestionManager;
class WebStateList;
namespace web {
class WebState;
}
// This class contains all the logic to get and provide keyboard input accessory
// views to its consumer. As well as telling the consumer when the default
// accessory view shoeuld be restored to the system default.
@interface FormInputAccessoryMediator : NSObject
// Returns a mediator observing the passed `WebStateList` and associated with
// the passed consumer. `webSateList` can be nullptr and `consumer` can be nil.
- (instancetype)initWithConsumer:(id<FormInputAccessoryConsumer>)consumer
webStateList:(WebStateList*)webStateList;
// Unavailable, use initWithConsumer:webStateList: instead.
- (instancetype)init NS_UNAVAILABLE;
@end
// Methods to allow injection in tests.
@interface FormInputAccessoryMediator (Tests)
// The WebState this instance is observing. Can be null.
- (void)injectWebState:(web::WebState*)webState;
// The JS manager for interacting with the underlying form.
- (void)injectSuggestionManager:(JsSuggestionManager*)JSSuggestionManager;
// The objects that can provide a custom input accessory view while filling
// forms.
- (void)injectProviders:(NSArray<id<FormInputAccessoryViewProvider>>*)providers;
@end
#endif // IOS_CHROME_BROWSER_UI_AUTOFILL_FORM_INPUT_ACCESSORY_MEDIATOR_H_
......@@ -65,9 +65,6 @@
// Returns the currently visible keyboard accessory, or nil.
- (UIView*)keyboardAccessory;
// Returns the currently visible keyboard input assistant item, or nil.
- (UITextInputAssistantItem*)inputAssistantItem;
// Wrapper around the becomeFirstResponder method of the webview.
- (BOOL)becomeFirstResponder;
......
......@@ -175,13 +175,6 @@ UIView* GetFirstResponderSubview(UIView* view) {
return firstResponder.inputAccessoryView;
}
- (UITextInputAssistantItem*)inputAssistantItem {
if (!_contentView)
return nil;
UIView* firstResponder = GetFirstResponderSubview(_contentView);
return firstResponder.inputAssistantItem;
}
- (BOOL)becomeFirstResponder {
return [_contentView becomeFirstResponder];
}
......
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