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") { ...@@ -7,11 +7,15 @@ source_set("autofill") {
sources = [ sources = [
"address_normalizer_factory.cc", "address_normalizer_factory.cc",
"address_normalizer_factory.h", "address_normalizer_factory.h",
"form_input_accessory_consumer.h",
"form_input_accessory_view.h", "form_input_accessory_view.h",
"form_input_accessory_view.mm", "form_input_accessory_view.mm",
"form_input_accessory_view_controller.h", "form_input_accessory_view_controller.h",
"form_input_accessory_view_controller.mm", "form_input_accessory_view_controller.mm",
"form_input_accessory_view_delegate.h", "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.h",
"form_suggestion_controller.mm", "form_suggestion_controller.mm",
"form_suggestion_label.h", "form_suggestion_label.h",
...@@ -48,7 +52,6 @@ source_set("autofill") { ...@@ -48,7 +52,6 @@ source_set("autofill") {
"//base:i18n", "//base:i18n",
"//components/autofill/core/browser", "//components/autofill/core/browser",
"//components/autofill/ios/browser", "//components/autofill/ios/browser",
"//components/autofill/ios/form_util",
"//components/keyed_service/core", "//components/keyed_service/core",
"//components/keyed_service/ios", "//components/keyed_service/ios",
"//components/prefs", "//components/prefs",
...@@ -474,6 +477,7 @@ source_set("eg_tests") { ...@@ -474,6 +477,7 @@ source_set("eg_tests") {
":autofill", ":autofill",
"//base", "//base",
"//base/test:test_support", "//base/test:test_support",
"//components/autofill/ios/browser:browser",
"//ios/chrome/app/strings", "//ios/chrome/app/strings",
"//ios/chrome/browser/ui", "//ios/chrome/browser/ui",
"//ios/chrome/test/app:test_support", "//ios/chrome/test/app:test_support",
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "base/guid.h" #include "base/guid.h"
#include "base/ios/ios_util.h" #include "base/ios/ios_util.h"
#include "base/mac/foundation_util.h"
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/task/task_scheduler/task_scheduler.h" #include "base/task/task_scheduler/task_scheduler.h"
...@@ -19,17 +20,18 @@ ...@@ -19,17 +20,18 @@
#import "components/autofill/ios/browser/autofill_agent.h" #import "components/autofill/ios/browser/autofill_agent.h"
#include "components/autofill/ios/browser/autofill_driver_ios.h" #include "components/autofill/ios/browser/autofill_driver_ios.h"
#import "components/autofill/ios/browser/form_suggestion.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/confirm_infobar_delegate.h"
#include "components/infobars/core/infobar.h" #include "components/infobars/core/infobar.h"
#include "components/infobars/core/infobar_manager.h" #include "components/infobars/core/infobar_manager.h"
#include "components/keyed_service/core/service_access_type.h" #include "components/keyed_service/core/service_access_type.h"
#include "components/security_state/ios/ssl_status_input_event_data.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" #import "ios/chrome/browser/autofill/form_suggestion_controller.h"
#include "ios/chrome/browser/browser_state/test_chrome_browser_state.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/infobars/infobar_manager_impl.h"
#include "ios/chrome/browser/ssl/ios_security_state_tab_helper.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/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/ui/settings/personal_data_manager_data_changed_observer.h"
#include "ios/chrome/browser/web/chrome_web_client.h" #include "ios/chrome/browser/web/chrome_web_client.h"
#import "ios/chrome/browser/web/chrome_web_test.h" #import "ios/chrome/browser/web/chrome_web_test.h"
...@@ -37,6 +39,7 @@ ...@@ -37,6 +39,7 @@
#import "ios/web/public/navigation_item.h" #import "ios/web/public/navigation_item.h"
#import "ios/web/public/navigation_manager.h" #import "ios/web/public/navigation_manager.h"
#include "ios/web/public/ssl_status.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 "ios/web/public/web_state/web_state.h"
#import "testing/gtest_mac.h" #import "testing/gtest_mac.h"
#include "ui/base/test/ios/ui_view_test_utils.h" #include "ui/base/test/ios/ui_view_test_utils.h"
...@@ -192,7 +195,7 @@ class AutofillControllerTest : public ChromeWebTest { ...@@ -192,7 +195,7 @@ class AutofillControllerTest : public ChromeWebTest {
TestSuggestionController* suggestion_controller_; TestSuggestionController* suggestion_controller_;
// Retrieves accessory views according to form events. // Retrieves accessory views according to form events.
FormInputAccessoryViewController* accessory_controller_; FormInputAccessoryMediator* accessory_mediator_;
// Manages autofill for a single page. // Manages autofill for a single page.
AutofillController* autofill_controller_; AutofillController* autofill_controller_;
...@@ -222,9 +225,17 @@ void AutofillControllerTest::SetUp() { ...@@ -222,9 +225,17 @@ void AutofillControllerTest::SetUp() {
suggestion_controller_ = [[TestSuggestionController alloc] suggestion_controller_ = [[TestSuggestionController alloc]
initWithWebState:web_state() initWithWebState:web_state()
providers:@[ [autofill_controller_ suggestionProvider] ]]; providers:@[ [autofill_controller_ suggestionProvider] ]];
accessory_controller_ = [[FormInputAccessoryViewController alloc] accessory_mediator_ =
initWithWebState:web_state() [[FormInputAccessoryMediator alloc] initWithConsumer:nil
providers:@[ [suggestion_controller_ accessoryViewProvider] ]]; 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()); 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 @@ ...@@ -7,16 +7,9 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "ios/chrome/browser/autofill/form_input_accessory_view_delegate.h" #import "ios/chrome/browser/autofill/form_input_accessory_consumer.h"
#import "ios/web/public/web_state/web_state_observer_bridge.h"
@protocol CRWWebViewProxy;
@protocol FormInputAccessoryViewProvider;
namespace autofill { namespace autofill {
extern NSString* const kFormSuggestionAssistButtonPreviousElement;
extern NSString* const kFormSuggestionAssistButtonNextElement;
extern NSString* const kFormSuggestionAssistButtonDone;
extern CGFloat const kInputAccessoryHeight; extern CGFloat const kInputAccessoryHeight;
} // namespace autofill } // namespace autofill
...@@ -24,33 +17,7 @@ extern CGFloat const kInputAccessoryHeight; ...@@ -24,33 +17,7 @@ extern CGFloat const kInputAccessoryHeight;
// interacting with a form. Also handles hiding and showing the default // interacting with a form. Also handles hiding and showing the default
// accessory view elements. // accessory view elements.
@interface FormInputAccessoryViewController @interface FormInputAccessoryViewController
: NSObject<CRWWebStateObserver, FormInputAccessoryViewDelegate> : NSObject<FormInputAccessoryConsumer>
// 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;
@end @end
#endif // IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_VIEW_CONTROLLER_H_ #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 @@ ...@@ -5,6 +5,8 @@
#ifndef IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_VIEW_PROVIDER_H_ #ifndef IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_VIEW_PROVIDER_H_
#define 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 { namespace web {
struct FormActivityParams; struct FormActivityParams;
class WebState; class WebState;
...@@ -12,9 +14,6 @@ class WebState; ...@@ -12,9 +14,6 @@ class WebState;
@protocol FormInputAccessoryViewDelegate; @protocol FormInputAccessoryViewDelegate;
@protocol FormInputAccessoryViewProvider; @protocol FormInputAccessoryViewProvider;
@class FormInputAccessoryViewController;
#import <UIKit/UIKit.h>
// Block type to provide an accessory view asynchronously. // Block type to provide an accessory view asynchronously.
typedef void (^AccessoryViewReadyCompletion)( typedef void (^AccessoryViewReadyCompletion)(
...@@ -37,13 +36,7 @@ typedef void (^AccessoryViewReadyCompletion)( ...@@ -37,13 +36,7 @@ typedef void (^AccessoryViewReadyCompletion)(
(AccessoryViewReadyCompletion)accessoryViewUpdateBlock; (AccessoryViewReadyCompletion)accessoryViewUpdateBlock;
// Notifies this provider that the accessory view is going away. // Notifies this provider that the accessory view is going away.
- (void)inputAccessoryViewControllerDidReset: - (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;
@end @end
......
...@@ -4,9 +4,11 @@ ...@@ -4,9 +4,11 @@
#import <EarlGrey/EarlGrey.h> #import <EarlGrey/EarlGrey.h>
#include "base/mac/foundation_util.h"
#include "base/strings/sys_string_conversions.h" #include "base/strings/sys_string_conversions.h"
#import "base/test/ios/wait_util.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/browser/ui/ui_util.h"
#include "ios/chrome/grit/ios_strings.h" #include "ios/chrome/grit/ios_strings.h"
#import "ios/chrome/test/app/chrome_test_util.h" #import "ios/chrome/test/app/chrome_test_util.h"
...@@ -19,6 +21,7 @@ ...@@ -19,6 +21,7 @@
#include "ios/web/public/test/element_selector.h" #include "ios/web/public/test/element_selector.h"
#import "ios/web/public/test/http_server/http_server.h" #import "ios/web/public/test/http_server/http_server.h"
#include "ios/web/public/test/http_server/http_server_util.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) #if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support." #error "This file requires ARC support."
...@@ -154,14 +157,17 @@ void AssertElementIsFocused(const std::string& element_id) { ...@@ -154,14 +157,17 @@ void AssertElementIsFocused(const std::string& element_id) {
// Tests that trying to programmatically dismiss the keyboard when it isn't // Tests that trying to programmatically dismiss the keyboard when it isn't
// visible doesn't crash the browser. // visible doesn't crash the browser.
- (void)testCloseKeyboardWhenNotVisible { - (void)testCloseKeyboardWhenNotVisible {
FormInputAccessoryViewController* viewController = FormInputAccessoryViewHandler* accessoryViewDelegate =
[[FormInputAccessoryViewController alloc] init]; [[FormInputAccessoryViewHandler alloc] init];
GREYAssertNotNil( GREYAssertNotNil(accessoryViewDelegate,
viewController, @"The Accessory View Delegate should not be non nil.");
@"The input accessory view controller should not be non nil."); [accessoryViewDelegate closeKeyboardWithoutButtonPress];
[viewController closeKeyboardWithoutButtonPress]; CRWJSInjectionReceiver* injectionReceiver =
viewController.webState = chrome_test_util::GetCurrentWebState(); chrome_test_util::GetCurrentWebState()->GetJSInjectionReceiver();
[viewController closeKeyboardWithoutButtonPress]; accessoryViewDelegate.JSSuggestionManager =
base::mac::ObjCCastStrict<JsSuggestionManager>(
[injectionReceiver instanceOfClass:[JsSuggestionManager class]]);
[accessoryViewDelegate closeKeyboardWithoutButtonPress];
} }
@end @end
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#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"
#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/autofill/form_input_accessory_view_delegate.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_view.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"
...@@ -350,7 +351,6 @@ AutofillSuggestionState::AutofillSuggestionState( ...@@ -350,7 +351,6 @@ AutofillSuggestionState::AutofillSuggestionState(
completionHandler:^{ completionHandler:^{
[[weakSelf accessoryViewDelegate] closeKeyboardWithoutButtonPress]; [[weakSelf accessoryViewDelegate] closeKeyboardWithoutButtonPress];
}]; }];
_provider = nil;
} }
- (id<FormInputAccessoryViewProvider>)accessoryViewProvider { - (id<FormInputAccessoryViewProvider>)accessoryViewProvider {
...@@ -379,14 +379,9 @@ AutofillSuggestionState::AutofillSuggestionState( ...@@ -379,14 +379,9 @@ AutofillSuggestionState::AutofillSuggestionState(
[self retrieveSuggestionsForForm:params webState:webState]; [self retrieveSuggestionsForForm:params webState:webState];
} }
- (void)inputAccessoryViewControllerDidReset: - (void)inputAccessoryViewControllerDidReset {
(FormInputAccessoryViewController*)controller {
accessoryViewUpdateBlock_ = nil; accessoryViewUpdateBlock_ = nil;
[self resetSuggestionState]; [self resetSuggestionState];
} }
- (void)resizeAccessoryView {
[self updateKeyboard:_suggestionState.get()];
}
@end @end
...@@ -14,8 +14,9 @@ ...@@ -14,8 +14,9 @@
#import "components/autofill/ios/browser/form_suggestion_provider.h" #import "components/autofill/ios/browser/form_suggestion_provider.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/test_form_activity_tab_helper.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/autofill/form_suggestion_view.h"
#import "ios/chrome/browser/ui/autofill/form_input_accessory_mediator.h"
#include "ios/chrome/browser/ui/ui_util.h" #include "ios/chrome/browser/ui/ui_util.h"
#import "ios/web/public/navigation_manager.h" #import "ios/web/public/navigation_manager.h"
#import "ios/web/public/test/fakes/test_web_state.h" #import "ios/web/public/test/fakes/test_web_state.h"
...@@ -31,12 +32,6 @@ ...@@ -31,12 +32,6 @@
#error "This file requires ARC support." #error "This file requires ARC support."
#endif #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. // Test provider that records invocations of its interface methods.
@interface TestSuggestionProvider : NSObject<FormSuggestionProvider> @interface TestSuggestionProvider : NSObject<FormSuggestionProvider>
...@@ -181,52 +176,38 @@ class FormSuggestionControllerTest : public PlatformTest { ...@@ -181,52 +176,38 @@ class FormSuggestionControllerTest : public PlatformTest {
providers:providers providers:providers
JsSuggestionManager:mock_js_suggestion_manager_]; JsSuggestionManager:mock_js_suggestion_manager_];
[suggestion_controller_ setWebViewProxy:mock_web_view_proxy_]; [suggestion_controller_ setWebViewProxy:mock_web_view_proxy_];
@autoreleasepool {
accessory_controller_ = [[FormInputAccessoryViewController alloc] id mock_consumer_ = [OCMockObject
initWithWebState:&test_web_state_ niceMockForProtocol:@protocol(FormInputAccessoryConsumer)];
JSSuggestionManager:mock_js_suggestion_manager_ accessory_mediator_ =
providers:@[ [[FormInputAccessoryMediator alloc] initWithConsumer:mock_consumer_
[suggestion_controller_ accessoryViewProvider] webStateList:NULL];
]]; [accessory_mediator_ injectWebState:&test_web_state_];
} [accessory_mediator_
// Mock out the FormInputAccessoryViewController so it can use the fake injectProviders:@[ [suggestion_controller_ accessoryViewProvider] ]];
// CRWWebViewProxy [accessory_mediator_ injectSuggestionManager:mock_js_suggestion_manager_];
id mock_accessory_controller =
[OCMockObject partialMockForObject:accessory_controller_]; // Mock the mediator consumer used to verify the suggestion views.
[[[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) { void (^mockShow)(NSInvocation*) = ^(NSInvocation* invocation) {
__unsafe_unretained UIView* view;
[invocation getArgument:&view atIndex:2];
for (UIView* view in [input_accessory_view_ subviews]) { for (UIView* view in [input_accessory_view_ subviews]) {
[view removeFromSuperview]; [view removeFromSuperview];
} }
__unsafe_unretained UIView* view;
[invocation getArgument:&view atIndex:2];
[input_accessory_view_ addSubview:view]; [input_accessory_view_ addSubview:view];
}; };
[[[mock_accessory_controller stub] andDo:mockShow] [[[mock_consumer_ stub] andDo:mockShow]
showCustomInputAccessoryView:[OCMArg any]]; showCustomInputAccessoryView:[OCMArg any]
navigationDelegate:[OCMArg any]];
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_accessory_controller stub] andDo:mockRestore] [[[mock_consumer_ stub] andDo:mockRestore]
restoreDefaultInputAccessoryView]; restoreDefaultInputAccessoryView];
} }
}
// The FormSuggestionController under test. // The FormSuggestionController under test.
FormSuggestionController* suggestion_controller_; FormSuggestionController* suggestion_controller_;
...@@ -241,7 +222,7 @@ class FormSuggestionControllerTest : public PlatformTest { ...@@ -241,7 +222,7 @@ class FormSuggestionControllerTest : public PlatformTest {
id mock_web_view_proxy_; id mock_web_view_proxy_;
// Accessory view controller. // Accessory view controller.
FormInputAccessoryViewController* accessory_controller_; FormInputAccessoryMediator* accessory_mediator_;
// The fake WebState to simulate navigation and JavaScript events. // The fake WebState to simulate navigation and JavaScript events.
web::TestWebState test_web_state_; web::TestWebState test_web_state_;
......
...@@ -137,6 +137,7 @@ source_set("unit_tests") { ...@@ -137,6 +137,7 @@ source_set("unit_tests") {
"//ios/chrome/browser/autofill", "//ios/chrome/browser/autofill",
"//ios/chrome/browser/browser_state:test_support", "//ios/chrome/browser/browser_state:test_support",
"//ios/chrome/browser/ssl", "//ios/chrome/browser/ssl",
"//ios/chrome/browser/ui/autofill:autofill",
"//ios/chrome/browser/ui/commands", "//ios/chrome/browser/ui/commands",
"//ios/chrome/browser/web:test_support", "//ios/chrome/browser/web:test_support",
"//ios/chrome/browser/web:web_internal", "//ios/chrome/browser/web:web_internal",
......
...@@ -27,10 +27,10 @@ ...@@ -27,10 +27,10 @@
#include "components/prefs/pref_registry_simple.h" #include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h" #include "components/prefs/testing_pref_service.h"
#include "components/security_state/ios/ssl_status_input_event_data.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" #import "ios/chrome/browser/autofill/form_suggestion_controller.h"
#include "ios/chrome/browser/browser_state/test_chrome_browser_state.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/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" #include "ios/chrome/browser/web/chrome_web_client.h"
#import "ios/chrome/browser/web/chrome_web_test.h" #import "ios/chrome/browser/web/chrome_web_test.h"
#import "ios/web/public/navigation_item.h" #import "ios/web/public/navigation_item.h"
...@@ -207,9 +207,12 @@ class PasswordControllerTest : public ChromeWebTest { ...@@ -207,9 +207,12 @@ class PasswordControllerTest : public ChromeWebTest {
suggestionController_ = [[PasswordsTestSuggestionController alloc] suggestionController_ = [[PasswordsTestSuggestionController alloc]
initWithWebState:web_state() initWithWebState:web_state()
providers:@[ [passwordController_ suggestionProvider] ]]; providers:@[ [passwordController_ suggestionProvider] ]];
accessoryViewController_ = [[FormInputAccessoryViewController alloc] accessoryMediator_ =
initWithWebState:web_state() [[FormInputAccessoryMediator alloc] initWithConsumer:nil
providers:@[ [suggestionController_ accessoryViewProvider] ]]; webStateList:NULL];
[accessoryMediator_ injectWebState:web_state()];
[accessoryMediator_
injectProviders:@[ [suggestionController_ accessoryViewProvider] ]];
} }
} }
...@@ -275,8 +278,8 @@ class PasswordControllerTest : public ChromeWebTest { ...@@ -275,8 +278,8 @@ class PasswordControllerTest : public ChromeWebTest {
// SuggestionController for testing. // SuggestionController for testing.
PasswordsTestSuggestionController* suggestionController_; PasswordsTestSuggestionController* suggestionController_;
// FormInputAccessoryViewController for testing. // FormInputAccessoryMediatorfor testing.
FormInputAccessoryViewController* accessoryViewController_; FormInputAccessoryMediator* accessoryMediator_;
// PasswordController for testing. // PasswordController for testing.
PasswordController* passwordController_; PasswordController* passwordController_;
......
...@@ -13,6 +13,8 @@ source_set("autofill") { ...@@ -13,6 +13,8 @@ source_set("autofill") {
"chrome_autofill_client_ios.mm", "chrome_autofill_client_ios.mm",
"form_input_accessory_coordinator.h", "form_input_accessory_coordinator.h",
"form_input_accessory_coordinator.mm", "form_input_accessory_coordinator.mm",
"form_input_accessory_mediator.h",
"form_input_accessory_mediator.mm",
] ]
deps = [ deps = [
":autofill_ui", ":autofill_ui",
...@@ -20,6 +22,7 @@ source_set("autofill") { ...@@ -20,6 +22,7 @@ source_set("autofill") {
"//components/autofill/core/browser", "//components/autofill/core/browser",
"//components/autofill/core/common", "//components/autofill/core/common",
"//components/autofill/ios/browser", "//components/autofill/ios/browser",
"//components/autofill/ios/form_util",
"//components/browser_sync", "//components/browser_sync",
"//components/infobars/core", "//components/infobars/core",
"//components/keyed_service/core", "//components/keyed_service/core",
...@@ -32,6 +35,7 @@ source_set("autofill") { ...@@ -32,6 +35,7 @@ source_set("autofill") {
"//ios/chrome/browser/browser_state", "//ios/chrome/browser/browser_state",
"//ios/chrome/browser/infobars", "//ios/chrome/browser/infobars",
"//ios/chrome/browser/metrics", "//ios/chrome/browser/metrics",
"//ios/chrome/browser/passwords:passwords_generation_utils",
"//ios/chrome/browser/signin", "//ios/chrome/browser/signin",
"//ios/chrome/browser/ssl", "//ios/chrome/browser/ssl",
"//ios/chrome/browser/sync", "//ios/chrome/browser/sync",
......
...@@ -4,33 +4,30 @@ ...@@ -4,33 +4,30 @@
#import "ios/chrome/browser/ui/autofill/form_input_accessory_coordinator.h" #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/autofill/form_input_accessory_view_controller.h"
#import "ios/chrome/browser/web_state_list/web_state_list.h" #import "ios/chrome/browser/ui/autofill/form_input_accessory_mediator.h"
#import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h"
#include "ios/web/public/web_state/web_state.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 ()<WebStateListObserving> @interface FormInputAccessoryCoordinator ()
// The View Controller for the input accessory view. // The View Controller for the input accessory view.
@property FormInputAccessoryViewController* formInputAccessoryViewController; @property FormInputAccessoryViewController* formInputAccessoryViewController;
@end // The Mediator for the input accessory view controller.
@property FormInputAccessoryMediator* formInputAccessoryMediator;
@implementation FormInputAccessoryCoordinator { @end
// The WebStateList this instance is observing in order to update the
// active WebState.
WebStateList* _webStateList;
// Bridge to observe the web state list from Objective-C. @implementation FormInputAccessoryCoordinator
std::unique_ptr<WebStateListObserverBridge> _webStateListObserver;
}
@synthesize formInputAccessoryViewController = @synthesize formInputAccessoryViewController =
_formInputAccessoryViewController; _formInputAccessoryViewController;
@synthesize formInputAccessoryMediator = _formInputAccessoryMediator;
- (instancetype)initWithBaseViewController:(UIViewController*)viewController - (instancetype)initWithBaseViewController:(UIViewController*)viewController
browserState: browserState:
...@@ -40,46 +37,13 @@ ...@@ -40,46 +37,13 @@
browserState:browserState]; browserState:browserState];
if (self) { if (self) {
DCHECK(webStateList); DCHECK(webStateList);
_webStateList = webStateList; _formInputAccessoryViewController =
_webStateListObserver = std::make_unique<WebStateListObserverBridge>(self); [[FormInputAccessoryViewController alloc] init];
_webStateList->AddObserver(_webStateListObserver.get()); _formInputAccessoryMediator = [[FormInputAccessoryMediator alloc]
initWithConsumer:self.formInputAccessoryViewController
webStateList:webStateList];
} }
return self; 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 @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 @@ ...@@ -65,9 +65,6 @@
// Returns the currently visible keyboard accessory, or nil. // Returns the currently visible keyboard accessory, or nil.
- (UIView*)keyboardAccessory; - (UIView*)keyboardAccessory;
// Returns the currently visible keyboard input assistant item, or nil.
- (UITextInputAssistantItem*)inputAssistantItem;
// Wrapper around the becomeFirstResponder method of the webview. // Wrapper around the becomeFirstResponder method of the webview.
- (BOOL)becomeFirstResponder; - (BOOL)becomeFirstResponder;
......
...@@ -175,13 +175,6 @@ UIView* GetFirstResponderSubview(UIView* view) { ...@@ -175,13 +175,6 @@ UIView* GetFirstResponderSubview(UIView* view) {
return firstResponder.inputAccessoryView; return firstResponder.inputAccessoryView;
} }
- (UITextInputAssistantItem*)inputAssistantItem {
if (!_contentView)
return nil;
UIView* firstResponder = GetFirstResponderSubview(_contentView);
return firstResponder.inputAssistantItem;
}
- (BOOL)becomeFirstResponder { - (BOOL)becomeFirstResponder {
return [_contentView 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