summaryrefslogtreecommitdiffstats
path: root/ios/chrome
diff options
context:
space:
mode:
authordroger <droger@chromium.org>2015-03-23 10:51:03 -0700
committerCommit bot <commit-bot@chromium.org>2015-03-23 17:51:56 +0000
commit69b5fc8fc036b5ade5e1498d368ef5c49fb74e48 (patch)
tree928eef46c2a61c848b40c8b2cabab66cfa2686a0 /ios/chrome
parent744256e2f3933feaa05c967541afac36825056fa (diff)
downloadchromium_src-69b5fc8fc036b5ade5e1498d368ef5c49fb74e48.zip
chromium_src-69b5fc8fc036b5ade5e1498d368ef5c49fb74e48.tar.gz
chromium_src-69b5fc8fc036b5ade5e1498d368ef5c49fb74e48.tar.bz2
[iOS] Upstream files in //ios/chrome/browser/autofill
BUG=437508 Review URL: https://codereview.chromium.org/1022463002 Cr-Commit-Position: refs/heads/master@{#321805}
Diffstat (limited to 'ios/chrome')
-rw-r--r--ios/chrome/DEPS2
-rw-r--r--ios/chrome/browser/autofill/OWNERS1
-rw-r--r--ios/chrome/browser/autofill/autofill_agent_utils.h35
-rw-r--r--ios/chrome/browser/autofill/autofill_agent_utils.mm139
-rw-r--r--ios/chrome/browser/autofill/form_input_accessory_view.h29
-rw-r--r--ios/chrome/browser/autofill/form_input_accessory_view.mm371
-rw-r--r--ios/chrome/browser/autofill/form_input_accessory_view_controller.h95
-rw-r--r--ios/chrome/browser/autofill/form_input_accessory_view_controller.mm507
-rw-r--r--ios/chrome/browser/autofill/form_input_accessory_view_delegate.h32
-rw-r--r--ios/chrome/browser/autofill/form_suggestion_controller.h60
-rw-r--r--ios/chrome/browser/autofill/form_suggestion_controller.mm352
-rw-r--r--ios/chrome/browser/autofill/form_suggestion_label.h28
-rw-r--r--ios/chrome/browser/autofill/form_suggestion_label.mm238
-rw-r--r--ios/chrome/browser/autofill/form_suggestion_provider.h55
-rw-r--r--ios/chrome/browser/autofill/form_suggestion_view.h28
-rw-r--r--ios/chrome/browser/autofill/form_suggestion_view.mm97
-rw-r--r--ios/chrome/browser/autofill/form_suggestion_view_client.h20
-rw-r--r--ios/chrome/browser/passwords/OWNERS1
-rw-r--r--ios/chrome/browser/passwords/password_generation_utils.h29
-rw-r--r--ios/chrome/browser/passwords/password_generation_utils.mm50
-rw-r--r--ios/chrome/ios_chrome.gyp19
21 files changed, 2188 insertions, 0 deletions
diff --git a/ios/chrome/DEPS b/ios/chrome/DEPS
index a33b946..f809f8d 100644
--- a/ios/chrome/DEPS
+++ b/ios/chrome/DEPS
@@ -4,6 +4,8 @@ include_rules = [
"-ios/chrome",
"+ios/chrome/grit",
+ "+components/autofill/core/browser",
+ "+components/autofill/ios/browser",
"+components/dom_distiller/core",
"+components/dom_distiller/ios",
"+components/infobars/core",
diff --git a/ios/chrome/browser/autofill/OWNERS b/ios/chrome/browser/autofill/OWNERS
new file mode 100644
index 0000000..cdbac76f
--- /dev/null
+++ b/ios/chrome/browser/autofill/OWNERS
@@ -0,0 +1 @@
+dconnelly@chromium.org
diff --git a/ios/chrome/browser/autofill/autofill_agent_utils.h b/ios/chrome/browser/autofill/autofill_agent_utils.h
new file mode 100644
index 0000000..d8f6c13
--- /dev/null
+++ b/ios/chrome/browser/autofill/autofill_agent_utils.h
@@ -0,0 +1,35 @@
+// Copyright 2013 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_AUTOFILL_AGENT_UTILS_H_
+#define IOS_CHROME_BROWSER_AUTOFILL_AUTOFILL_AGENT_UTILS_H_
+
+// TODO (sgrant): Switch to componentized version of this code when
+// http://crbug/328070 is fixed.
+
+namespace autofill {
+class FormStructure;
+}
+
+namespace autofill_agent_util {
+
+// Determines if the |structure| has any fields that are of type
+// autofill::CREDIT_CARD and thus asking for credit card info.
+bool RequestingCreditCardInfo(const autofill::FormStructure* structure);
+
+// Returns true if one of the nodes in |structure| request information related
+// to a billing address.
+bool RequestFullBillingAddress(autofill::FormStructure* structure);
+
+// Returns true if one of the nodes in |structure| request information related
+// to a shipping address. To determine this actually attempt to fill the form
+// using an empty data model that tracks which fields are requested.
+bool RequestShippingAddress(autofill::FormStructure* structure);
+
+// Returns true if one of the nodes in |structure| request information related
+// to a phone number.
+bool RequestPhoneNumber(autofill::FormStructure* structure);
+
+} // namespace autofill_agent_util
+
+#endif // IOS_CHROME_BROWSER_AUTOFILL_AUTOFILL_AGENT_UTILS_H_
diff --git a/ios/chrome/browser/autofill/autofill_agent_utils.mm b/ios/chrome/browser/autofill/autofill_agent_utils.mm
new file mode 100644
index 0000000..33e7ddd
--- /dev/null
+++ b/ios/chrome/browser/autofill/autofill_agent_utils.mm
@@ -0,0 +1,139 @@
+// Copyright 2013 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/autofill_agent_utils.h"
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "components/autofill/core/browser/autofill_manager.h"
+#include "components/autofill/core/browser/detail_input.h"
+#include "components/autofill/core/browser/dialog_section.h"
+#include "components/autofill/core/browser/server_field_types_util.h"
+#include "grit/components_strings.h"
+#include "ios/chrome/browser/application_context.h"
+#include "ui/base/l10n/l10n_util.h"
+
+// TODO (sgrant): Switch to componentized version of this code when
+// http://crbug/328070 is fixed.
+// This code was largely copied from autofill_dialog_controller_android.cc
+
+namespace {
+
+// Returns true if |input_type| in |section| is needed for |form_structure|.
+bool IsSectionInputUsedInFormStructure(
+ autofill::DialogSection section,
+ autofill::ServerFieldType input_type,
+ const autofill::FormStructure& form_structure) {
+ autofill::DetailInput input;
+ input.length = autofill::DetailInput::SHORT;
+ input.type = input_type;
+ input.placeholder_text = base::string16();
+ input.expand_weight = 0;
+
+ for (size_t i = 0; i < form_structure.field_count(); ++i) {
+ const autofill::AutofillField* field = form_structure.field(i);
+ if (field && autofill::ServerTypeMatchesField(section, input.type, *field))
+ return true;
+ }
+ return false;
+}
+
+} // namespace
+
+namespace autofill_agent_util {
+
+// Determines if the |structure| has any fields that are of type
+// autofill::CREDIT_CARD and thus asking for credit card info.
+bool RequestingCreditCardInfo(const autofill::FormStructure* structure) {
+ DCHECK(structure);
+
+ size_t field_count = structure->field_count();
+ for (size_t i = 0; i < field_count; ++i) {
+ autofill::AutofillType type(structure->field(i)->Type().GetStorableType());
+ if (type.group() == autofill::CREDIT_CARD)
+ return true;
+ }
+
+ return false;
+}
+
+// Returns true if one of the nodes in |structure| request information related
+// to a billing address.
+bool RequestFullBillingAddress(autofill::FormStructure* structure) {
+ const autofill::ServerFieldType fieldsToCheckFor[] = {
+ autofill::ADDRESS_BILLING_LINE1,
+ autofill::ADDRESS_BILLING_LINE2,
+ autofill::ADDRESS_BILLING_CITY,
+ autofill::ADDRESS_BILLING_STATE,
+ autofill::PHONE_BILLING_WHOLE_NUMBER};
+
+ for (size_t i = 0; i < arraysize(fieldsToCheckFor); ++i) {
+ if (IsSectionInputUsedInFormStructure(autofill::SECTION_BILLING,
+ fieldsToCheckFor[i], *structure)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Return empty info string for fill fields method.
+base::string16 ReturnEmptyInfo(const autofill::AutofillType& type) {
+ return base::string16();
+}
+
+// Returns true if one of the nodes in |structure| request information related
+// to a shipping address. To determine this actually attempt to fill the form
+// using an empty data model that tracks which fields are requested.
+bool RequestShippingAddress(autofill::FormStructure* structure) {
+ // Country code is unused for iOS and Android, so it
+ // doesn't matter what's passed.
+ std::string country_code;
+ autofill::DetailInputs inputs;
+ // TODO(eugenebut): Clean up kShippingInputs definition, unify with
+ // android codebase. crbug.com/371074
+ const autofill::DetailInput kShippingInputs[] = {
+ {autofill::DetailInput::LONG, autofill::NAME_FULL},
+ {autofill::DetailInput::LONG, autofill::ADDRESS_HOME_LINE1},
+ {autofill::DetailInput::LONG, autofill::ADDRESS_HOME_LINE2},
+ {autofill::DetailInput::LONG, autofill::ADDRESS_HOME_CITY},
+ {autofill::DetailInput::SHORT,
+ autofill::ADDRESS_HOME_STATE,
+ l10n_util::GetStringUTF16(IDS_AUTOFILL_FIELD_LABEL_STATE)},
+ {autofill::DetailInput::SHORT_EOL, autofill::ADDRESS_HOME_ZIP},
+ {autofill::DetailInput::NONE, autofill::ADDRESS_HOME_COUNTRY},
+ };
+ autofill::BuildInputs(kShippingInputs, arraysize(kShippingInputs), &inputs);
+
+ // TODO(ios): [Merge r284576]: The 4th argument to FillFields()
+ // is the language code based on either the billing or shipping address.
+ // See implementation in upstream's autofill_dialog_controller_impl.cc
+ // AutofillDialogControllerImpl::MutableAddressLanguageCodeForSection()
+ // Temporarily using std::string here to complete merge.
+ // See http://crbug/363063.
+ return structure->FillFields(
+ autofill::TypesFromInputs(inputs),
+ base::Bind(autofill::ServerTypeMatchesField, autofill::SECTION_SHIPPING),
+ base::Bind(&ReturnEmptyInfo), std::string(),
+ GetApplicationContext()->GetApplicationLocale());
+}
+
+// Returns true if one of the nodes in |structure| request information related
+// to a phone number.
+bool RequestPhoneNumber(autofill::FormStructure* structure) {
+ if (IsSectionInputUsedInFormStructure(autofill::SECTION_BILLING,
+ autofill::PHONE_BILLING_WHOLE_NUMBER,
+ *structure)) {
+ return true;
+ }
+
+ if (IsSectionInputUsedInFormStructure(autofill::SECTION_SHIPPING,
+ autofill::PHONE_HOME_WHOLE_NUMBER,
+ *structure)) {
+ return true;
+ }
+
+ return false;
+}
+}
diff --git a/ios/chrome/browser/autofill/form_input_accessory_view.h b/ios/chrome/browser/autofill/form_input_accessory_view.h
new file mode 100644
index 0000000..b8992a5
--- /dev/null
+++ b/ios/chrome/browser/autofill/form_input_accessory_view.h
@@ -0,0 +1,29 @@
+// Copyright 2014 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_H_
+#define IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_VIEW_H_
+
+#import <UIKit/UIKit.h>
+
+@protocol FormInputAccessoryViewDelegate;
+
+// Subview of the accessory view for web forms. Shows a custom view with form
+// navigation controls above the keyboard. Subclassed to enable input clicks by
+// way of the playInputClick method.
+@interface FormInputAccessoryView : UIView<UIInputViewAudioFeedback>
+
+// Initializes with |frame| and |delegate| to show |customView|. If the size of
+// |rightFrame| is non-zero, the view will have two parts: the left one has
+// frame |leftFrame| and the right one has frame |rightFrame|. Otherwise the
+// view will be shown in |leftFrame|.
+- (instancetype)initWithFrame:(CGRect)frame
+ delegate:(id<FormInputAccessoryViewDelegate>)delegate
+ customView:(UIView*)customView
+ leftFrame:(CGRect)leftFrame
+ rightFrame:(CGRect)rightFrame;
+
+@end
+
+#endif // IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_VIEW_H_
diff --git a/ios/chrome/browser/autofill/form_input_accessory_view.mm b/ios/chrome/browser/autofill/form_input_accessory_view.mm
new file mode 100644
index 0000000..a794ab4
--- /dev/null
+++ b/ios/chrome/browser/autofill/form_input_accessory_view.mm
@@ -0,0 +1,371 @@
+// Copyright 2014 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.h"
+
+#import <QuartzCore/QuartzCore.h>
+
+#include "base/i18n/rtl.h"
+#include "base/ios/weak_nsobject.h"
+#include "base/mac/scoped_nsobject.h"
+#import "ios/chrome/browser/autofill/form_input_accessory_view_delegate.h"
+#import "ios/chrome/browser/ui/image_util.h"
+#include "ios/chrome/browser/ui/ui_util.h"
+
+namespace {
+
+// The alpha value of the background color.
+const CGFloat kBackgroundColorAlpha = 1.0;
+
+// Horizontal margin around the custom view.
+const CGFloat kCustomViewHorizontalMargin = 2;
+
+// The width of the previous and next buttons.
+const CGFloat kNavigationButtonWidth = 44;
+
+// The width of the separators of the previous and next buttons.
+const CGFloat kNavigationButtonSeparatorWidth = 1;
+
+// The width of the shadow part of the navigation area separator.
+const CGFloat kNavigationAreaSeparatorShadowWidth = 2;
+
+// The width of the navigation area / custom view separator asset.
+const CGFloat kNavigationAreaSeparatorWidth = 1;
+
+// Returns YES if the keyboard close button should be shown on the accessory.
+BOOL ShouldShowCloseButton() {
+ return !IsIPadIdiom();
+}
+
+// Returns the width of navigation view.
+CGFloat GetNavigationViewWidth() {
+ // The number of naviation buttons (includes close button if shown).
+ NSUInteger numberNavigationButtons = 2;
+ if (ShouldShowCloseButton())
+ numberNavigationButtons++;
+ return numberNavigationButtons * kNavigationButtonWidth +
+ (numberNavigationButtons - 1) * kNavigationButtonSeparatorWidth +
+ kNavigationAreaSeparatorWidth;
+}
+
+} // namespace
+
+@interface FormInputAccessoryView ()
+
+// Initializes the view with the given |customView|.
+// If the size of |rightFrame| is non-zero, the view will be split into two
+// parts with |leftFrame| and |rightFrame|. Otherwise the Autofill view will
+// be shown in |leftFrame|.
+- (void)initializeViewWithCustomView:(UIView*)customView
+ leftFrame:(CGRect)leftFrame
+ rightFrame:(CGRect)rightFrame;
+
+// Returns a view that shows navigation buttons in the |frame|.
+- (UIView*)viewForNavigationButtonsInFrame:(CGRect)frame;
+
+// Returns a navigation button for Autofill that has |normalImage| for state
+// UIControlStateNormal, a |pressedImage| for states UIControlStateSelected and
+// UIControlStateHighlighted, and an optional |disabledImage| for
+// UIControlStateDisabled.
+- (UIButton*)keyboardNavButtonWithNormalImage:(UIImage*)normalImage
+ pressedImage:(UIImage*)pressedImage
+ disabledImage:(UIImage*)disabledImage
+ target:(id)target
+ action:(SEL)action
+ enabled:(BOOL)enabled
+ originX:(CGFloat)originX
+ originY:(CGFloat)originY
+ height:(CGFloat)height;
+
+// Adds a background image to |view|. The supplied image is stretched to fit the
+// space by stretching the content its horizontal and vertical centers.
++ (void)addBackgroundImageInView:(UIView*)view
+ withImageName:(NSString*)imageName;
+
+// Adds an image view in |view| with an image named |imageName| at
+// (|originX|, 0). The width is |width| and the height is the height of |view|.
++ (void)addImageViewWithImageName:(NSString*)imageName
+ originX:(CGFloat)originX
+ originY:(CGFloat)originY
+ width:(CGFloat)width
+ inView:(UIView*)view;
+
+@end
+
+@implementation FormInputAccessoryView {
+ // The custom view that is displayed in the input accessory view.
+ base::scoped_nsobject<UIView> _customView;
+
+ // Delegate of this view.
+ base::WeakNSProtocol<id<FormInputAccessoryViewDelegate>> _delegate;
+}
+
+- (instancetype)initWithFrame:(CGRect)frame
+ delegate:(id<FormInputAccessoryViewDelegate>)delegate
+ customView:(UIView*)customView
+ leftFrame:(CGRect)leftFrame
+ rightFrame:(CGRect)rightFrame {
+ DCHECK(delegate);
+ self = [super initWithFrame:frame];
+ if (self) {
+ _delegate.reset(delegate);
+ _customView.reset([customView retain]);
+ [self initializeViewWithCustomView:_customView
+ leftFrame:leftFrame
+ rightFrame:rightFrame];
+ }
+ return self;
+}
+
+#pragma mark -
+#pragma mark UIInputViewAudioFeedback
+
+- (BOOL)enableInputClicksWhenVisible {
+ return YES;
+}
+
+#pragma mark -
+#pragma mark Private Methods
+
+- (void)initializeViewWithCustomView:(UIView*)customView
+ leftFrame:(CGRect)leftFrame
+ rightFrame:(CGRect)rightFrame {
+ UIView* customViewContainer = [[[UIView alloc] init] autorelease];
+ [self addSubview:customViewContainer];
+ UIView* navView = [[[UIView alloc] init] autorelease];
+ [self addSubview:navView];
+
+ bool splitKeyboard = CGRectGetWidth(rightFrame) != 0;
+ BOOL isRTL = base::i18n::IsRTL();
+
+ // The computed frame for |customView|.
+ CGRect customViewFrame;
+ // Frame of a subview of |navView| in which navigation buttons will be shown.
+ CGRect navFrame = CGRectZero;
+ if (splitKeyboard) {
+ NSString* navViewBackgroundImageName = nil;
+ NSString* customViewContainerBackgroundImageName = nil;
+ NSUInteger navFrameOriginX = 0;
+ if (isRTL) {
+ navView.frame = leftFrame;
+ navViewBackgroundImageName = @"autofill_keyboard_background_left";
+ customViewContainer.frame = rightFrame;
+ customViewContainerBackgroundImageName =
+ @"autofill_keyboard_background_right";
+ // Navigation buttons will be shown on the left side.
+ navFrameOriginX = 0;
+ } else {
+ customViewContainer.frame = leftFrame;
+ customViewContainerBackgroundImageName =
+ @"autofill_keyboard_background_left";
+ navView.frame = rightFrame;
+ navViewBackgroundImageName = @"autofill_keyboard_background_right";
+ // Navigation buttons will be shown on the right side.
+ navFrameOriginX =
+ CGRectGetWidth(navView.frame) - GetNavigationViewWidth();
+ }
+
+ [[self class]
+ addBackgroundImageInView:customViewContainer
+ withImageName:customViewContainerBackgroundImageName];
+ [[self class] addBackgroundImageInView:navView
+ withImageName:navViewBackgroundImageName];
+
+ // For RTL, the custom view is the right view; the padding should be at the
+ // left side of this view. Otherwise, the custom view is the left view
+ // and the space is at the right side.
+ customViewFrame = CGRectMake(isRTL ? kCustomViewHorizontalMargin : 0, 0,
+ CGRectGetWidth(customViewContainer.bounds) -
+ kCustomViewHorizontalMargin,
+ CGRectGetHeight(customViewContainer.bounds));
+ navFrame = CGRectMake(navFrameOriginX, 0, GetNavigationViewWidth(),
+ CGRectGetHeight(navView.frame));
+ } else {
+ NSUInteger navViewFrameOriginX = 0;
+ NSUInteger customViewContainerFrameOrginX = 0;
+ if (isRTL) {
+ navViewFrameOriginX = kNavigationAreaSeparatorShadowWidth;
+ customViewContainerFrameOrginX = GetNavigationViewWidth();
+ } else {
+ navViewFrameOriginX =
+ CGRectGetWidth(leftFrame) - GetNavigationViewWidth();
+ }
+
+ customViewContainer.frame =
+ CGRectMake(customViewContainerFrameOrginX, 0,
+ CGRectGetWidth(leftFrame) - GetNavigationViewWidth() +
+ kNavigationAreaSeparatorShadowWidth,
+ CGRectGetHeight(leftFrame));
+ navView.frame = CGRectMake(navViewFrameOriginX, 0, GetNavigationViewWidth(),
+ CGRectGetHeight(leftFrame));
+
+ customViewFrame = customViewContainer.bounds;
+ navFrame = navView.bounds;
+ [[self class] addBackgroundImageInView:self
+ withImageName:@"autofill_keyboard_background"];
+ }
+
+ [customView setFrame:customViewFrame];
+ [customViewContainer addSubview:customView];
+ [navView addSubview:[self viewForNavigationButtonsInFrame:navFrame]];
+}
+
+UIImage* ButtonImage(NSString* name) {
+ UIImage* rawImage = [UIImage imageNamed:name];
+ return StretchableImageFromUIImage(rawImage, 1, 0);
+}
+
+- (UIView*)viewForNavigationButtonsInFrame:(CGRect)frame {
+ UIView* navView = [[[UIView alloc] initWithFrame:frame] autorelease];
+
+ BOOL isRTL = base::i18n::IsRTL();
+
+ // Vertical space is left for a dividing line.
+ CGFloat firstRow = 1;
+
+ CGFloat currentX = 0;
+
+ // Navigation view is at the right side if not RTL. Add a left separator in
+ // this case.
+ if (!isRTL) {
+ [[self class] addImageViewWithImageName:@"autofill_left_sep"
+ originX:currentX
+ originY:firstRow
+ width:kNavigationAreaSeparatorWidth
+ inView:navView];
+ currentX = kNavigationAreaSeparatorWidth;
+ }
+
+ UIButton* previousButton = [self
+ keyboardNavButtonWithNormalImage:ButtonImage(@"autofill_prev")
+ pressedImage:ButtonImage(@"autofill_prev_pressed")
+ disabledImage:ButtonImage(@"autofill_prev_inactive")
+ target:_delegate
+ action:@selector(selectPreviousElement)
+ enabled:NO
+ originX:currentX
+ originY:firstRow
+ height:CGRectGetHeight(frame)];
+ [navView addSubview:previousButton];
+ currentX += kNavigationButtonWidth;
+
+ // Add internal separator.
+ [[self class] addImageViewWithImageName:@"autofill_middle_sep"
+ originX:currentX
+ originY:firstRow
+ width:kNavigationButtonSeparatorWidth
+ inView:navView];
+ currentX += kNavigationButtonSeparatorWidth;
+
+ UIButton* nextButton = [self
+ keyboardNavButtonWithNormalImage:ButtonImage(@"autofill_next")
+ pressedImage:ButtonImage(@"autofill_next_pressed")
+ disabledImage:ButtonImage(@"autofill_next_inactive")
+ target:_delegate
+ action:@selector(selectNextElement)
+ enabled:NO
+ originX:currentX
+ originY:firstRow
+ height:CGRectGetHeight(frame)];
+ [navView addSubview:nextButton];
+ currentX += kNavigationButtonWidth;
+
+ [_delegate fetchPreviousAndNextElementsPresenceWithCompletionHandler:
+ ^(BOOL hasPreviousElement, BOOL hasNextElement) {
+ previousButton.enabled = hasPreviousElement;
+ nextButton.enabled = hasNextElement;
+ }];
+
+ if (ShouldShowCloseButton()) {
+ // Add internal separator.
+ [[self class] addImageViewWithImageName:@"autofill_middle_sep"
+ originX:currentX
+ originY:firstRow
+ width:kNavigationButtonSeparatorWidth
+ inView:navView];
+ currentX += kNavigationButtonSeparatorWidth;
+
+ [navView addSubview:[self
+ keyboardNavButtonWithNormalImage:ButtonImage(@"autofill_close")
+ pressedImage:ButtonImage(@"autofill_close_pressed")
+ disabledImage:nil
+ target:_delegate
+ action:@selector(closeKeyboard)
+ enabled:YES
+ originX:currentX
+ originY:firstRow
+ height:CGRectGetHeight(frame)]];
+ currentX += kNavigationButtonWidth;
+ }
+
+ // Navigation view is at the left side for RTL. Add a right separator in
+ // this case.
+ if (isRTL) {
+ [[self class] addImageViewWithImageName:@"autofill_right_sep"
+ originX:currentX
+ originY:firstRow
+ width:kNavigationAreaSeparatorWidth
+ inView:navView];
+ }
+
+ return navView;
+}
+
+- (UIButton*)keyboardNavButtonWithNormalImage:(UIImage*)normalImage
+ pressedImage:(UIImage*)pressedImage
+ disabledImage:(UIImage*)disabledImage
+ target:(id)target
+ action:(SEL)action
+ enabled:(BOOL)enabled
+ originX:(CGFloat)originX
+ originY:(CGFloat)originY
+ height:(CGFloat)height {
+ UIButton* button = [UIButton buttonWithType:UIButtonTypeCustom];
+
+ button.frame =
+ CGRectMake(originX, originY, kNavigationButtonWidth, height - originY);
+
+ [button setBackgroundImage:normalImage forState:UIControlStateNormal];
+ [button setBackgroundImage:pressedImage forState:UIControlStateSelected];
+ [button setBackgroundImage:pressedImage forState:UIControlStateHighlighted];
+ if (disabledImage)
+ [button setBackgroundImage:disabledImage forState:UIControlStateDisabled];
+
+ CALayer* layer = [button layer];
+ layer.borderWidth = 0;
+ layer.borderColor = [[UIColor blackColor] CGColor];
+ button.enabled = enabled;
+ [button addTarget:target
+ action:action
+ forControlEvents:UIControlEventTouchUpInside];
+ return button;
+}
+
++ (void)addBackgroundImageInView:(UIView*)view
+ withImageName:(NSString*)imageName {
+ UIImage* backgroundImage = StretchableImageNamed(imageName);
+
+ UIImageView* backgroundImageView =
+ [[[UIImageView alloc] initWithFrame:view.bounds] autorelease];
+ [backgroundImageView setImage:backgroundImage];
+ [backgroundImageView setAlpha:kBackgroundColorAlpha];
+ [view addSubview:backgroundImageView];
+ [view sendSubviewToBack:backgroundImageView];
+}
+
++ (void)addImageViewWithImageName:(NSString*)imageName
+ originX:(CGFloat)originX
+ originY:(CGFloat)originY
+ width:(CGFloat)width
+ inView:(UIView*)view {
+ UIImage* image =
+ StretchableImageFromUIImage([UIImage imageNamed:imageName], 0, 0);
+ base::scoped_nsobject<UIImageView> imageView(
+ [[UIImageView alloc] initWithImage:image]);
+ [imageView setFrame:CGRectMake(originX, originY, width,
+ CGRectGetHeight(view.bounds) - originY)];
+ [view addSubview:imageView];
+}
+
+@end
diff --git a/ios/chrome/browser/autofill/form_input_accessory_view_controller.h b/ios/chrome/browser/autofill/form_input_accessory_view_controller.h
new file mode 100644
index 0000000..1ecb8ef
--- /dev/null
+++ b/ios/chrome/browser/autofill/form_input_accessory_view_controller.h
@@ -0,0 +1,95 @@
+// Copyright 2014 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_CONTROLLER_H_
+#define IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_VIEW_CONTROLLER_H_
+
+#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;
+
+namespace ios_internal {
+namespace autofill {
+extern NSString* const kFormSuggestionAssistButtonPreviousElement;
+extern NSString* const kFormSuggestionAssistButtonNextElement;
+extern NSString* const kFormSuggestionAssistButtonDone;
+} // namespace autofill
+} // namespace ios_internal
+
+@protocol FormInputAccessoryViewProvider;
+@class FormInputAccessoryViewController;
+
+// Block type to indicate that a FormInputAccessoryViewProvider has an accessory
+// view to provide.
+typedef void (^AccessoryViewAvailableCompletion)(
+ BOOL inputAccessoryViewAvailable);
+
+// Block type to provide an accessory view asynchronously.
+typedef void (^AccessoryViewReadyCompletion)(
+ UIView* view,
+ id<FormInputAccessoryViewProvider> provider);
+
+// Represents an object that can provide a custom keyboard input accessory view.
+@protocol FormInputAccessoryViewProvider<NSObject>
+
+// A delegate for form navigation.
+@property(nonatomic, assign)
+ id<FormInputAccessoryViewDelegate> accessoryViewDelegate;
+
+// Determines asynchronously if this provider has a view available for the
+// specified form/field and invokes |completionHandler| with the answer.
+- (void)checkIfAccessoryViewAvailableForFormNamed:(const std::string&)formName
+ fieldName:(const std::string&)fieldName
+ webState:(web::WebState*)webState
+ completionHandler:
+ (AccessoryViewAvailableCompletion)
+ completionHandler;
+
+// Asynchronously retrieves an accessory view from this provider for the
+// specified form/field and returns it via |completionHandler|.
+- (void)retrieveAccessoryViewForFormNamed:(const std::string&)formName
+ fieldName:(const std::string&)fieldName
+ value:(const std::string&)value
+ type:(const std::string&)type
+ webState:(web::WebState*)webState
+ completionHandler:
+ (AccessoryViewReadyCompletion)completionHandler;
+
+// 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
+// returned using the completion received in
+// |retrieveAccessoryViewForForm:field:value:type:webState:completionHandler:|.
+- (void)resizeAccessoryView;
+
+@end
+
+// Creates and manages a custom input accessory view while the user is
+// interacting with a form. Also handles hiding and showing the default
+// accessory view elements.
+@interface FormInputAccessoryViewController
+ : NSObject<CRWWebStateObserver, FormInputAccessoryViewDelegate>
+
+// Initializes a new controller with the specified |providers| of input
+// accessory views.
+- (instancetype)initWithWebState:(web::WebState*)webState
+ providers:(NSArray*)providers;
+
+// 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
+
+#endif // IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/autofill/form_input_accessory_view_controller.mm b/ios/chrome/browser/autofill/form_input_accessory_view_controller.mm
new file mode 100644
index 0000000..8f82627
--- /dev/null
+++ b/ios/chrome/browser/autofill/form_input_accessory_view_controller.mm
@@ -0,0 +1,507 @@
+// Copyright 2014 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_controller.h"
+
+#include "base/ios/block_types.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_block.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#import "components/autofill/ios/browser/js_suggestion_manager.h"
+#import "ios/chrome/browser/autofill/form_input_accessory_view.h"
+#import "ios/chrome/browser/passwords/password_generation_utils.h"
+#include "ios/web/public/test/crw_test_js_injection_receiver.h"
+#include "ios/web/public/url_scheme_util.h"
+#import "ios/web/public/web_state/crw_web_view_proxy.h"
+#include "ios/web/public/web_state/url_verification_constants.h"
+#include "ios/web/public/web_state/web_state.h"
+#include "url/gurl.h"
+
+namespace ios_internal {
+namespace autofill {
+NSString* const kFormSuggestionAssistButtonPreviousElement = @"previousTap";
+NSString* const kFormSuggestionAssistButtonNextElement = @"nextTap";
+NSString* const kFormSuggestionAssistButtonDone = @"done";
+} // namespace autofill
+} // namespace ios_internal
+
+namespace {
+
+// Finds all views of a particular kind if class |klass| in the subview
+// hierarchy of the given |root| view.
+NSArray* FindDescendantsOfClass(UIView* root, Class klass) {
+ DCHECK(root);
+ NSMutableArray* viewsToExamine = [NSMutableArray arrayWithObject:root];
+ NSMutableArray* descendants = [NSMutableArray array];
+
+ while ([viewsToExamine count]) {
+ UIView* view = [viewsToExamine lastObject];
+ if ([view isKindOfClass:klass])
+ [descendants addObject:view];
+
+ [viewsToExamine removeLastObject];
+ [viewsToExamine addObjectsFromArray:[view subviews]];
+ }
+
+ return descendants;
+}
+
+// Finds all UIToolbarItems associated with a given UIToolbar |toolbar| with
+// action selectors with a name that containts the action name specified by
+// |actionName|.
+NSArray* FindToolbarItemsForActionName(UIToolbar* toolbar,
+ NSString* actionName) {
+ NSMutableArray* toolbarItems = [NSMutableArray array];
+
+ for (UIBarButtonItem* item in [toolbar items]) {
+ SEL itemAction = [item action];
+ if (!itemAction)
+ continue;
+ NSString* itemActionName = NSStringFromSelector(itemAction);
+
+ // We don't do a strict string match for the action name.
+ if ([itemActionName rangeOfString:actionName].location != NSNotFound)
+ [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 = FindDescendantsOfClass(root, [UIToolbar class]);
+ for (UIToolbar* toolbar in toolbars) {
+ [descendants
+ addObjectsFromArray:FindToolbarItemsForActionName(toolbar, actionName)];
+ }
+
+ return descendants;
+}
+
+// Computes the frame of each part of the accessory view of the keyboard. It is
+// assumed that the keyboard has either two parts (when it is split) or one part
+// (when it is merged).
+//
+// If there are two parts, the frame of the left part is returned in
+// |leftFrame| and the frame of the right part is returned in |rightFrame|.
+// If there is only one part, the frame is returned in |leftFrame| and
+// |rightFrame| has size zero.
+//
+// Heuristics are used to compute this information. It returns true if the
+// number of |inputAccessoryView.subviews| is not 2.
+bool ComputeFramesOfKeyboardParts(UIView* inputAccessoryView,
+ CGRect* leftFrame,
+ CGRect* rightFrame) {
+ // It is observed (on iOS 6) there are always two subviews in the original
+ // input accessory view. When the keyboard is split, each subview represents
+ // one part of the accesssary view of the keyboard. When the keyboard is
+ // merged, one subview has the same frame as that of the whole accessory view
+ // and the other has zero size with the screen width as origin.x.
+ // The computation here is based on this observation.
+ NSArray* subviews = inputAccessoryView.subviews;
+ if (subviews.count != 2)
+ return false;
+
+ CGRect first_frame = static_cast<UIView*>(subviews[0]).frame;
+ CGRect second_frame = static_cast<UIView*>(subviews[1]).frame;
+ if (CGRectGetMinX(first_frame) < CGRectGetMinX(second_frame) ||
+ CGRectGetWidth(second_frame) == 0) {
+ *leftFrame = first_frame;
+ *rightFrame = second_frame;
+ } else {
+ *rightFrame = first_frame;
+ *leftFrame = second_frame;
+ }
+ return true;
+}
+
+} // namespace
+
+@interface FormInputAccessoryViewController ()
+
+// Allows injection of the JsSuggestionManager.
+- (instancetype)initWithWebState:(web::WebState*)webState
+ JSSuggestionManager:(JsSuggestionManager*)JSSuggestionManager
+ providers:(NSArray*)providers;
+
+// Called when the keyboard did change frame.
+- (void)keyboardDidChangeFrame:(NSNotification*)notification;
+
+// Called when the keyboard is dismissed.
+- (void)keyboardDidHide:(NSNotification*)notification;
+
+// Hides the subviews in |accessoryView|.
+- (void)hideSubviewsInOriginalAccessoryView:(UIView*)accessoryView;
+
+// 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]
+- (BOOL)executeFormAssistAction:(NSString*)actionName;
+
+// Runs |block| while allowing the keyboard to be displayed as a result of focus
+// changes caused by |block|.
+- (void)runBlockAllowingKeyboardDisplay:(ProceduralBlock)block;
+
+// Asynchronously retrieves an accessory view from |_providers|.
+- (void)retrieveAccessoryViewForForm:(const std::string&)formName
+ field:(const std::string&)fieldName
+ value:(const std::string&)value
+ type:(const std::string&)type
+ webState:(web::WebState*)webState;
+
+// Clears the current custom accessory view and restores the default.
+- (void)reset;
+
+// The current web state.
+@property(nonatomic, readonly) web::WebState* webState;
+
+// The current web view proxy.
+@property(nonatomic, readonly) id<CRWWebViewProxy> webViewProxy;
+
+@end
+
+@implementation FormInputAccessoryViewController {
+ // Bridge to observe the web state from Objective-C.
+ scoped_ptr<web::WebStateObserverBridge> _webStateObserverBridge;
+
+ // Last registered keyboard rectangle.
+ CGRect _keyboardFrame;
+
+ // The custom view that should be shown in the input accessory view.
+ base::scoped_nsobject<UIView> _customAccessoryView;
+
+ // The JS manager for interacting with the underlying form.
+ base::scoped_nsobject<JsSuggestionManager> _JSSuggestionManager;
+
+ // The original subviews in keyboard accessory view that were originally not
+ // hidden but were hidden when showing Autofill suggestions.
+ base::scoped_nsobject<NSMutableArray> _hiddenOriginalSubviews;
+
+ // The objects that can provide a custom input accessory view while filling
+ // forms.
+ base::scoped_nsobject<NSArray> _providers;
+
+ // The object that manages the currently-shown custom accessory view.
+ base::WeakNSProtocol<id<FormInputAccessoryViewProvider>> _currentProvider;
+}
+
+- (instancetype)initWithWebState:(web::WebState*)webState
+ providers:(NSArray*)providers {
+ JsSuggestionManager* suggestionManager =
+ base::mac::ObjCCastStrict<JsSuggestionManager>(
+ [webState->GetJSInjectionReceiver()
+ instanceOfClass:[JsSuggestionManager class]]);
+ return [self initWithWebState:webState
+ JSSuggestionManager:suggestionManager
+ providers:providers];
+}
+
+- (instancetype)initWithWebState:(web::WebState*)webState
+ JSSuggestionManager:(JsSuggestionManager*)JSSuggestionManager
+ providers:(NSArray*)providers {
+ self = [super init];
+ if (self) {
+ _JSSuggestionManager.reset([JSSuggestionManager retain]);
+ _hiddenOriginalSubviews.reset([[NSMutableArray alloc] init]);
+ _webStateObserverBridge.reset(
+ new web::WebStateObserverBridge(webState, self));
+ _providers.reset([providers copy]);
+ // There is no defined relation on the timing of JavaScript events and
+ // keyboard showing up. So it is necessary to listen to the keyboard
+ // notification to make sure the keyboard is updated.
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(keyboardDidChangeFrame:)
+ name:UIKeyboardDidChangeFrameNotification
+ object:nil];
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(keyboardDidHide:)
+ name:UIKeyboardDidHideNotification
+ object:nil];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [super dealloc];
+}
+
+- (web::WebState*)webState {
+ return _webStateObserverBridge ? _webStateObserverBridge->web_state()
+ : nullptr;
+}
+
+- (id<CRWWebViewProxy>)webViewProxy {
+ return self.webState ? self.webState->GetWebViewProxy() : nil;
+}
+
+- (void)hideSubviewsInOriginalAccessoryView:(UIView*)accessoryView {
+ for (UIView* subview in [accessoryView subviews]) {
+ if (!subview.hidden) {
+ [_hiddenOriginalSubviews addObject:subview];
+ subview.hidden = YES;
+ }
+ }
+}
+
+- (void)showCustomInputAccessoryView:(UIView*)view {
+ [self restoreDefaultInputAccessoryView];
+ CGRect leftFrame;
+ CGRect rightFrame;
+ UIView* inputAccessoryView = [self.webViewProxy getKeyboardAccessory];
+ if (ComputeFramesOfKeyboardParts(inputAccessoryView, &leftFrame,
+ &rightFrame)) {
+ [self hideSubviewsInOriginalAccessoryView:inputAccessoryView];
+ _customAccessoryView.reset(
+ [[FormInputAccessoryView alloc] initWithFrame:inputAccessoryView.frame
+ delegate:self
+ customView:view
+ leftFrame:leftFrame
+ rightFrame:rightFrame]);
+ [inputAccessoryView addSubview:_customAccessoryView];
+ }
+}
+
+- (void)restoreDefaultInputAccessoryView {
+ [_customAccessoryView removeFromSuperview];
+ _customAccessoryView.reset();
+ for (UIView* subview in _hiddenOriginalSubviews.get()) {
+ subview.hidden = NO;
+ }
+ [_hiddenOriginalSubviews removeAllObjects];
+}
+
+- (void)closeKeyboard {
+ BOOL performedAction =
+ [self executeFormAssistAction:ios_internal::autofill::
+ kFormSuggestionAssistButtonDone];
+
+ if (!performedAction) {
+ // We could not find the built-in form assist controls, so try to focus
+ // the next or previous control using JavaScript.
+ [self runBlockAllowingKeyboardDisplay:^{
+ [_JSSuggestionManager closeKeyboard];
+ }];
+ }
+}
+
+- (BOOL)executeFormAssistAction:(NSString*)actionName {
+ UIView* inputAccessoryView = [self.webViewProxy getKeyboardAccessory];
+ if (!inputAccessoryView)
+ return NO;
+
+ NSArray* descendants =
+ FindDescendantToolbarItemsForActionName(inputAccessoryView, actionName);
+
+ if (![descendants count])
+ return NO;
+
+ UIBarButtonItem* item = descendants[0];
+ [[item target] performSelector:[item action] withObject:item];
+ return YES;
+}
+
+- (void)runBlockAllowingKeyboardDisplay:(ProceduralBlock)block {
+ DCHECK([UIWebView
+ instancesRespondToSelector:@selector(keyboardDisplayRequiresUserAction)]);
+
+ BOOL originalValue = [self.webViewProxy keyboardDisplayRequiresUserAction];
+ [self.webViewProxy setKeyboardDisplayRequiresUserAction:NO];
+ block();
+ [self.webViewProxy setKeyboardDisplayRequiresUserAction:originalValue];
+}
+
+#pragma mark -
+#pragma mark FormInputAccessoryViewDelegate
+
+- (void)selectPreviousElement {
+ BOOL performedAction = [self
+ executeFormAssistAction:ios_internal::autofill::
+ kFormSuggestionAssistButtonPreviousElement];
+ if (!performedAction) {
+ // We could not find the built-in form assist controls, so try to focus
+ // the next or previous control using JavaScript.
+ [self runBlockAllowingKeyboardDisplay:^{
+ [_JSSuggestionManager selectPreviousElement];
+ }];
+ }
+}
+
+- (void)selectNextElement {
+ BOOL performedAction =
+ [self executeFormAssistAction:ios_internal::autofill::
+ kFormSuggestionAssistButtonNextElement];
+
+ if (!performedAction) {
+ // We could not find the built-in form assist controls, so try to focus
+ // the next or previous control using JavaScript.
+ [self runBlockAllowingKeyboardDisplay:^{
+ [_JSSuggestionManager selectNextElement];
+ }];
+ }
+}
+
+- (void)fetchPreviousAndNextElementsPresenceWithCompletionHandler:
+ (void (^)(BOOL, BOOL))completionHandler {
+ DCHECK(completionHandler);
+ [_JSSuggestionManager
+ fetchPreviousAndNextElementsPresenceWithCompletionHandler:
+ completionHandler];
+}
+
+#pragma mark -
+#pragma mark CRWWebStateObserver
+
+- (void)pageLoaded:(web::WebState*)webState {
+ [self reset];
+}
+
+- (void)formActivity:(web::WebState*)webState
+ formName:(const std::string&)formName
+ fieldName:(const std::string&)fieldName
+ type:(const std::string&)type
+ value:(const std::string&)value
+ keyCode:(int)keyCode
+ error:(BOOL)error {
+ web::URLVerificationTrustLevel trustLevel;
+ const GURL pageURL(webState->GetCurrentURL(&trustLevel));
+ if (error || trustLevel != web::URLVerificationTrustLevel::kAbsolute ||
+ !web::UrlHasWebScheme(pageURL) || !webState->ContentIsHTML()) {
+ [self reset];
+ return;
+ }
+
+ if ((type == "blur" || type == "change")) {
+ return;
+ }
+
+ [self retrieveAccessoryViewForForm:formName
+ field:fieldName
+ value:value
+ type:type
+ webState:webState];
+}
+
+- (void)webStateDestroyed:(web::WebState*)webState {
+ [self reset];
+ _webStateObserverBridge.reset();
+}
+
+- (void)reset {
+ if (_currentProvider) {
+ [_currentProvider inputAccessoryViewControllerDidReset:self];
+ _currentProvider.reset();
+ }
+ [self restoreDefaultInputAccessoryView];
+}
+
+- (void)retrieveAccessoryViewForForm:(const std::string&)formName
+ field:(const std::string&)fieldName
+ value:(const std::string&)value
+ type:(const std::string&)type
+ webState:(web::WebState*)webState {
+ base::WeakNSObject<FormInputAccessoryViewController> weakSelf(self);
+ std::string strongFormName = formName;
+ std::string strongFieldName = fieldName;
+ std::string strongValue = value;
+ std::string strongType = type;
+
+ // 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
+ // and NO otherwise.
+ base::scoped_nsobject<NSMutableArray> findProviderBlocks(
+ [[NSMutableArray alloc] init]);
+ for (NSUInteger i = 0; i < [_providers count]; i++) {
+ base::mac::ScopedBlock<passwords::PipelineBlock> block(
+ ^(void (^completion)(BOOL success)) {
+ // Access all the providers through |self| to guarantee that both
+ // |self| and all the providers exist when the block is executed.
+ // |_providers| is immutable, so the subscripting is always valid.
+ base::scoped_nsobject<FormInputAccessoryViewController> strongSelf(
+ [weakSelf retain]);
+ if (!strongSelf)
+ return;
+ id<FormInputAccessoryViewProvider> provider =
+ strongSelf.get()->_providers[i];
+ [provider checkIfAccessoryViewAvailableForFormNamed:strongFormName
+ fieldName:strongFieldName
+ webState:webState
+ completionHandler:completion];
+ },
+ base::scoped_policy::RETAIN);
+ [findProviderBlocks addObject:block];
+ }
+
+ // Once the view is retrieved, update the UI.
+ AccessoryViewReadyCompletion readyCompletion =
+ ^(UIView* accessoryView, id<FormInputAccessoryViewProvider> provider) {
+ base::scoped_nsobject<FormInputAccessoryViewController> strongSelf(
+ [weakSelf retain]);
+ if (!strongSelf || !strongSelf.get()->_currentProvider)
+ return;
+ DCHECK_EQ(strongSelf.get()->_currentProvider.get(), provider);
+ [provider setAccessoryViewDelegate:strongSelf];
+ [strongSelf showCustomInputAccessoryView:accessoryView];
+ };
+
+ // Once a provider is found, use it to retrieve the accessory view.
+ passwords::PipelineCompletionBlock onProviderFound =
+ ^(NSUInteger providerIndex) {
+ if (providerIndex == NSNotFound) {
+ [weakSelf reset];
+ return;
+ }
+ base::scoped_nsobject<FormInputAccessoryViewController> strongSelf(
+ [weakSelf retain]);
+ if (!strongSelf || ![strongSelf webState])
+ return;
+ id<FormInputAccessoryViewProvider> provider =
+ strongSelf.get()->_providers[providerIndex];
+ [strongSelf.get()->_currentProvider
+ inputAccessoryViewControllerDidReset:self];
+ strongSelf.get()->_currentProvider.reset(provider);
+ [strongSelf.get()->_currentProvider
+ retrieveAccessoryViewForFormNamed:strongFormName
+ fieldName:strongFieldName
+ value:strongValue
+ type:strongType
+ webState:webState
+ completionHandler:readyCompletion];
+ };
+
+ // Run all the blocks in |findProviderBlocks| until one invokes its
+ // completion with YES. The first one to do so will be passed to
+ // |onProviderFound|.
+ passwords::RunSearchPipeline(findProviderBlocks, onProviderFound);
+}
+
+- (void)keyboardDidChangeFrame:(NSNotification*)notification {
+ if (!self.webState || !_currentProvider)
+ return;
+ CGRect keyboardFrame =
+ [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
+ // With iOS8 (beta) this method can be called even when the rect has not
+ // changed. When this is detected we exit early.
+ if (CGRectEqualToRect(CGRectIntegral(_keyboardFrame),
+ CGRectIntegral(keyboardFrame))) {
+ return;
+ }
+ _keyboardFrame = keyboardFrame;
+ [_currentProvider resizeAccessoryView];
+}
+
+- (void)keyboardDidHide:(NSNotification*)notification {
+ _keyboardFrame = CGRectZero;
+}
+
+@end
diff --git a/ios/chrome/browser/autofill/form_input_accessory_view_delegate.h b/ios/chrome/browser/autofill/form_input_accessory_view_delegate.h
new file mode 100644
index 0000000..38c6f18
--- /dev/null
+++ b/ios/chrome/browser/autofill/form_input_accessory_view_delegate.h
@@ -0,0 +1,32 @@
+// Copyright 2014 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_DELEGATE_H_
+#define IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_VIEW_DELEGATE_H_
+
+#import <Foundation/Foundation.h>
+
+// Handles user interaction with a FormInputAccessoryView.
+@protocol FormInputAccessoryViewDelegate<NSObject>
+
+// Called when the close button is clicked.
+- (void)closeKeyboard;
+
+// Called when the previous button is clicked.
+- (void)selectPreviousElement;
+
+// Called when the next button is clicked.
+- (void)selectNextElement;
+
+// Called when updating the keyboard view. Checks if the page contains a next
+// and a previous element.
+// |completionHandler| is called with 2 BOOLs, the first indicating if a
+// previous element was found, and the second indicating if a next element was
+// found. |completionHandler| cannot be nil.
+- (void)fetchPreviousAndNextElementsPresenceWithCompletionHandler:
+ (void (^)(BOOL, BOOL))completionHandler;
+
+@end
+
+#endif // IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_VIEW_DELEGATE_H_
diff --git a/ios/chrome/browser/autofill/form_suggestion_controller.h b/ios/chrome/browser/autofill/form_suggestion_controller.h
new file mode 100644
index 0000000..6cf39c7
--- /dev/null
+++ b/ios/chrome/browser/autofill/form_suggestion_controller.h
@@ -0,0 +1,60 @@
+// Copyright 2014 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_SUGGESTION_CONTROLLER_H_
+#define IOS_CHROME_BROWSER_AUTOFILL_FORM_SUGGESTION_CONTROLLER_H_
+
+#import <UIKit/UIKit.h>
+
+#include <string>
+
+#import "components/autofill/ios/browser/js_suggestion_manager.h"
+#import "ios/chrome/browser/autofill/form_suggestion_view.h"
+#import "ios/chrome/browser/autofill/form_suggestion_view_client.h"
+#import "ios/web/public/web_state/web_state_observer_bridge.h"
+
+namespace web {
+class WebState;
+}
+
+@protocol CRWWebViewProxy;
+@protocol FormInputAccessoryViewProvider;
+
+// Handles form focus events and presents input suggestions.
+@interface FormSuggestionController
+ : NSObject<CRWWebStateObserver, FormSuggestionViewClient>
+
+// Initializes a new FormSuggestionController with the specified WebState and a
+// list of FormSuggestionProviders.
+// When suggestions are required for an input field, the |providers| will be
+// asked (in order) if they can handle the field; the first provider to return
+// YES from [FormSuggestionProvider canProviderSuggestionsForForm:field:] will
+// be expected to provide those suggestions using [FormSuggestionProvider
+// retrieveSuggestionsForForm:field:withCompletion:].
+- (instancetype)initWithWebState:(web::WebState*)webState
+ providers:(NSArray*)providers;
+
+// Instructs the controller to detach itself from the WebState.
+- (void)detachFromWebState;
+
+// Provides an input accessory view for form suggestions.
+@property(nonatomic, readonly)
+ id<FormInputAccessoryViewProvider> accessoryViewProvider;
+
+@end
+
+@interface FormSuggestionController (ForTesting)
+
+// Initializes a new controller in the same way as the public initializer, but
+// supports specifying a JsSuggestionManager for testing.
+- (instancetype)initWithWebState:(web::WebState*)webState
+ providers:(NSArray*)providers
+ JsSuggestionManager:(JsSuggestionManager*)jsSuggestionManager;
+
+// Overrides the web view proxy.
+- (void)setWebViewProxy:(id<CRWWebViewProxy>)webViewProxy;
+
+@end
+
+#endif // IOS_CHROME_BROWSER_AUTOFILL_FORM_SUGGESTION_CONTROLLER_H_
diff --git a/ios/chrome/browser/autofill/form_suggestion_controller.mm b/ios/chrome/browser/autofill/form_suggestion_controller.mm
new file mode 100644
index 0000000..e3eb8ae
--- /dev/null
+++ b/ios/chrome/browser/autofill/form_suggestion_controller.mm
@@ -0,0 +1,352 @@
+// Copyright 2014 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_suggestion_controller.h"
+
+#include "base/ios/weak_nsobject.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_block.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/autofill/core/browser/autofill_popup_delegate.h"
+#import "components/autofill/ios/browser/form_suggestion.h"
+#import "ios/chrome/browser/autofill/form_input_accessory_view_controller.h"
+#import "ios/chrome/browser/autofill/form_suggestion_provider.h"
+#import "ios/chrome/browser/autofill/form_suggestion_view.h"
+#import "ios/chrome/browser/passwords/password_generation_utils.h"
+#include "ios/web/public/url_scheme_util.h"
+#import "ios/web/public/web_state/crw_web_view_proxy.h"
+#import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
+#import "ios/web/public/web_state/web_state.h"
+
+namespace {
+
+// Struct that describes suggestion state.
+struct AutofillSuggestionState {
+ AutofillSuggestionState(const std::string& form_name,
+ const std::string& field_name,
+ const std::string& typed_value);
+ // The name of the form for autofill.
+ std::string form_name;
+ // The name of the field for autofill.
+ std::string field_name;
+ // The user-typed value in the field.
+ std::string typed_value;
+ // The suggestions for the form field. An array of |FormSuggestion|.
+ base::scoped_nsobject<NSArray> suggestions;
+};
+
+AutofillSuggestionState::AutofillSuggestionState(const std::string& form_name,
+ const std::string& field_name,
+ const std::string& typed_value)
+ : form_name(form_name), field_name(field_name), typed_value(typed_value) {
+}
+
+} // namespace
+
+@interface FormSuggestionController () <FormInputAccessoryViewProvider> {
+ // Form navigation delegate.
+ base::WeakNSProtocol<id<FormInputAccessoryViewDelegate>> _delegate;
+
+ // Callback to update the accessory view.
+ base::mac::ScopedBlock<AccessoryViewReadyCompletion> completionHandler_;
+
+ // Autofill suggestion state.
+ scoped_ptr<AutofillSuggestionState> _suggestionState;
+
+ // Providers for suggestions, sorted according to the order in which
+ // they should be asked for suggestions, with highest priority in front.
+ base::scoped_nsobject<NSArray> _suggestionProviders;
+
+ // Access to WebView from the CRWWebController.
+ base::scoped_nsprotocol<id<CRWWebViewProxy>> _webViewProxy;
+}
+
+// Returns an autoreleased input accessory view that shows |suggestions|.
+- (UIView*)suggestionViewWithSuggestions:(NSArray*)suggestions;
+
+// Updates keyboard for |suggestionState|.
+- (void)updateKeyboard:(AutofillSuggestionState*)suggestionState;
+
+// Updates keyboard with |suggestions|.
+- (void)updateKeyboardWithSuggestions:(NSArray*)suggestions;
+
+// Clears state in between page loads.
+- (void)resetSuggestionState;
+
+// Finds a FormSuggestionProvider that can supply suggestions for the specified
+// form, requests them, and updates the view accordingly.
+- (void)retrieveSuggestionsForFormNamed:(const std::string&)formName
+ fieldName:(const std::string&)fieldName
+ type:(const std::string&)type
+ webState:(web::WebState*)webState;
+
+@end
+
+@implementation FormSuggestionController {
+ // Bridge to observe the web state from Objective-C.
+ scoped_ptr<web::WebStateObserverBridge> _webStateObserverBridge;
+
+ // Manager for FormSuggestion JavaScripts.
+ base::scoped_nsobject<JsSuggestionManager> _jsSuggestionManager;
+
+ // The provider for the current set of suggestions.
+ __weak id<FormSuggestionProvider> _provider;
+}
+
+- (instancetype)initWithWebState:(web::WebState*)webState
+ providers:(NSArray*)providers
+ JsSuggestionManager:(JsSuggestionManager*)jsSuggestionManager {
+ self = [super init];
+ if (self) {
+ _webStateObserverBridge.reset(
+ new web::WebStateObserverBridge(webState, self));
+ _webViewProxy.reset([webState->GetWebViewProxy() retain]);
+ _jsSuggestionManager.reset([jsSuggestionManager retain]);
+ _suggestionProviders.reset([providers copy]);
+ }
+ return self;
+}
+
+- (instancetype)initWithWebState:(web::WebState*)webState
+ providers:(NSArray*)providers {
+ JsSuggestionManager* jsSuggestionManager =
+ base::mac::ObjCCast<JsSuggestionManager>(
+ [webState->GetJSInjectionReceiver()
+ instanceOfClass:[JsSuggestionManager class]]);
+ return [self initWithWebState:webState
+ providers:providers
+ JsSuggestionManager:jsSuggestionManager];
+}
+
+- (void)detachFromWebState {
+ _webStateObserverBridge.reset();
+}
+
+#pragma mark -
+#pragma mark CRWWebStateObserver
+
+- (void)webStateDestroyed:(web::WebState*)webState {
+ [self detachFromWebState];
+}
+
+- (void)pageLoaded:(web::WebState*)webState {
+ [self processPage:webState];
+}
+
+- (void)processPage:(web::WebState*)webState {
+ [self resetSuggestionState];
+
+ web::URLVerificationTrustLevel trustLevel =
+ web::URLVerificationTrustLevel::kNone;
+ const GURL pageURL(webState->GetCurrentURL(&trustLevel));
+ if (trustLevel != web::URLVerificationTrustLevel::kAbsolute) {
+ DLOG(WARNING) << "Page load not handled on untrusted page";
+ return;
+ }
+
+ if (web::UrlHasWebScheme(pageURL) && webState->ContentIsHTML())
+ [_jsSuggestionManager inject];
+}
+
+- (void)setWebViewProxy:(id<CRWWebViewProxy>)webViewProxy {
+ _webViewProxy.reset([webViewProxy retain]);
+}
+
+- (void)retrieveSuggestionsForFormNamed:(const std::string&)formName
+ fieldName:(const std::string&)fieldName
+ type:(const std::string&)type
+ webState:(web::WebState*)webState {
+ base::WeakNSObject<FormSuggestionController> weakSelf(self);
+ base::scoped_nsobject<NSString> strongFormName(
+ [base::SysUTF8ToNSString(formName) copy]);
+ base::scoped_nsobject<NSString> strongFieldName(
+ [base::SysUTF8ToNSString(fieldName) copy]);
+ base::scoped_nsobject<NSString> strongType(
+ [base::SysUTF8ToNSString(type) copy]);
+ base::scoped_nsobject<NSString> strongValue(
+ [base::SysUTF8ToNSString(_suggestionState.get()->typed_value) copy]);
+
+ // Build a block for each provider that will invoke its completion with YES
+ // if the provider can provide suggestions for the specified form/field/type
+ // and NO otherwise.
+ base::scoped_nsobject<NSMutableArray> findProviderBlocks(
+ [[NSMutableArray alloc] init]);
+ for (NSUInteger i = 0; i < [_suggestionProviders count]; i++) {
+ base::mac::ScopedBlock<passwords::PipelineBlock> block(
+ ^(void (^completion)(BOOL success)) {
+ // Access all the providers through |self| to guarantee that both
+ // |self| and all the providers exist when the block is executed.
+ // |_suggestionProviders| is immutable, so the subscripting is
+ // always valid.
+ base::scoped_nsobject<FormSuggestionController> strongSelf(
+ [weakSelf retain]);
+ if (!strongSelf)
+ return;
+ id<FormSuggestionProvider> provider =
+ strongSelf.get()->_suggestionProviders[i];
+ [provider checkIfSuggestionsAvailableForForm:strongFormName
+ field:strongFieldName
+ type:strongType
+ typedValue:strongValue
+ webState:webState
+ completionHandler:completion];
+ },
+ base::scoped_policy::RETAIN);
+ [findProviderBlocks addObject:block];
+ }
+
+ // Once the suggestions are retrieved, update the suggestions UI.
+ SuggestionsReadyCompletion readyCompletion =
+ ^(NSArray* suggestions, id<FormSuggestionProvider> provider) {
+ [weakSelf onSuggestionsReady:suggestions provider:provider];
+ };
+
+ // Once a provider is found, use it to retrieve suggestions.
+ passwords::PipelineCompletionBlock completion = ^(NSUInteger providerIndex) {
+ if (providerIndex == NSNotFound)
+ return;
+ base::scoped_nsobject<FormSuggestionController> strongSelf(
+ [weakSelf retain]);
+ if (!strongSelf)
+ return;
+ id<FormSuggestionProvider> provider =
+ strongSelf.get()->_suggestionProviders[providerIndex];
+ [provider retrieveSuggestionsForForm:strongFormName
+ field:strongFieldName
+ type:strongType
+ typedValue:strongValue
+ webState:webState
+ completionHandler:readyCompletion];
+ };
+
+ // Run all the blocks in |findProviderBlocks| until one invokes its
+ // completion with YES. The first one to do so will be passed to
+ // |onProviderFound|.
+ passwords::RunSearchPipeline(findProviderBlocks, completion);
+}
+
+- (void)onSuggestionsReady:(NSArray*)suggestions
+ provider:(id<FormSuggestionProvider>)provider {
+ // TODO(ios): crbug.com/249916. If we can also pass in the form/field for
+ // which |sugguestions| are, we should check here if |suggestions| are for
+ // the current active element. If not, reset |_suggestionState|.
+ if (!_suggestionState) {
+ // The suggestion state was reset in between the call to Autofill API (e.g.
+ // OnQueryFormFieldAutofill) and this method being called back. Results are
+ // therefore no longer relevant.
+ return;
+ }
+
+ _provider = provider;
+ _suggestionState->suggestions.reset([suggestions copy]);
+ [self updateKeyboard:_suggestionState.get()];
+}
+
+- (void)resetSuggestionState {
+ _provider = nil;
+ _suggestionState.reset();
+}
+
+- (void)clearSuggestions {
+ // Note that other parts of the suggestionsState are not reset.
+ if (!_suggestionState.get())
+ return;
+ _suggestionState->suggestions.reset([[NSArray alloc] init]);
+ [self updateKeyboard:_suggestionState.get()];
+}
+
+- (void)updateKeyboard:(AutofillSuggestionState*)suggestionState {
+ if (!_suggestionState) {
+ if (completionHandler_)
+ completionHandler_.get()(nil, self);
+ } else {
+ [self updateKeyboardWithSuggestions:_suggestionState->suggestions];
+ }
+}
+
+- (void)updateKeyboardWithSuggestions:(NSArray*)suggestions {
+ if (completionHandler_)
+ completionHandler_.get()([self suggestionViewWithSuggestions:suggestions],
+ self);
+}
+
+- (UIView*)suggestionViewWithSuggestions:(NSArray*)suggestions {
+ base::scoped_nsobject<FormSuggestionView> view([[FormSuggestionView alloc]
+ initWithFrame:[_webViewProxy getKeyboardAccessory].frame
+ client:self
+ suggestions:suggestions]);
+ return view.autorelease();
+}
+
+- (void)didSelectSuggestion:(FormSuggestion*)suggestion {
+ if (!_suggestionState)
+ return;
+
+ // Send the suggestion to the provider and advance the cursor.
+ base::WeakNSObject<FormSuggestionController> weakSelf(self);
+ [_provider
+ didSelectSuggestion:suggestion
+ forField:base::SysUTF8ToNSString(_suggestionState->field_name)
+ form:base::SysUTF8ToNSString(_suggestionState->form_name)
+ completionHandler:^{
+ [[weakSelf accessoryViewDelegate] selectNextElement];
+ }];
+ _provider = nil;
+}
+
+- (id<FormInputAccessoryViewProvider>)accessoryViewProvider {
+ return self;
+}
+
+#pragma mark FormInputAccessoryViewProvider
+
+- (id<FormInputAccessoryViewDelegate>)accessoryViewDelegate {
+ return _delegate.get();
+}
+
+- (void)setAccessoryViewDelegate:(id<FormInputAccessoryViewDelegate>)delegate {
+ _delegate.reset(delegate);
+}
+
+- (void)checkIfAccessoryViewAvailableForFormNamed:(const std::string&)formName
+ fieldName:(const std::string&)fieldName
+ webState:(web::WebState*)webState
+ completionHandler:
+ (AccessoryViewAvailableCompletion)
+ completionHandler {
+ [self processPage:webState];
+ completionHandler(YES);
+}
+
+- (void)retrieveAccessoryViewForFormNamed:(const std::string&)formName
+ fieldName:(const std::string&)fieldName
+ value:(const std::string&)value
+ type:(const std::string&)type
+ webState:(web::WebState*)webState
+ completionHandler:
+ (AccessoryViewReadyCompletion)completionHandler {
+ _suggestionState.reset(
+ new AutofillSuggestionState(formName, fieldName, value));
+ completionHandler([self suggestionViewWithSuggestions:@[]], self);
+ completionHandler_.reset([completionHandler copy]);
+ [self retrieveSuggestionsForFormNamed:formName
+ fieldName:fieldName
+ type:type
+ webState:webState];
+}
+
+- (void)inputAccessoryViewControllerDidReset:
+ (FormInputAccessoryViewController*)controller {
+ completionHandler_.reset();
+ [self resetSuggestionState];
+}
+
+- (void)resizeAccessoryView {
+ [self updateKeyboard:_suggestionState.get()];
+}
+
+@end
diff --git a/ios/chrome/browser/autofill/form_suggestion_label.h b/ios/chrome/browser/autofill/form_suggestion_label.h
new file mode 100644
index 0000000..44a784b
--- /dev/null
+++ b/ios/chrome/browser/autofill/form_suggestion_label.h
@@ -0,0 +1,28 @@
+// Copyright 2013 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_SUGGESTION_LABEL_H_
+#define IOS_CHROME_BROWSER_AUTOFILL_FORM_SUGGESTION_LABEL_H_
+
+#import <UIKit/UIKit.h>
+
+#include "base/ios/weak_nsobject.h"
+#include "base/mac/scoped_nsobject.h"
+
+@class FormSuggestion;
+@protocol FormSuggestionViewClient;
+
+// Class for Autofill suggestion in the customized keyboard.
+@interface FormSuggestionLabel : UIView
+
+// Designated initializer. Initializes with |proposedFrame| and |client| for
+// |suggestion|. Its width will be adjusted according to the length of
+// |suggestion| and width in |proposedFrame| is ignored.
+- (id)initWithSuggestion:(FormSuggestion*)suggestion
+ proposedFrame:(CGRect)proposedFrame
+ client:(id<FormSuggestionViewClient>)client;
+
+@end
+
+#endif // IOS_CHROME_BROWSER_AUTOFILL_FORM_SUGGESTION_LABEL_H_
diff --git a/ios/chrome/browser/autofill/form_suggestion_label.mm b/ios/chrome/browser/autofill/form_suggestion_label.mm
new file mode 100644
index 0000000..60e9022
--- /dev/null
+++ b/ios/chrome/browser/autofill/form_suggestion_label.mm
@@ -0,0 +1,238 @@
+// Copyright 2013 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_suggestion_label.h"
+
+#include <cmath>
+
+#import <QuartzCore/QuartzCore.h>
+
+#include "base/strings/sys_string_conversions.h"
+#include "components/autofill/core/browser/credit_card.h"
+#import "components/autofill/ios/browser/form_suggestion.h"
+#import "ios/chrome/browser/autofill/form_suggestion_view_client.h"
+#include "ios/chrome/browser/ui/ui_util.h"
+
+namespace {
+
+// The button corner radius.
+const CGFloat kCornerRadius = 3.0f;
+
+// The width of the border in the button background image.
+const CGFloat kBorderWidth = 1.0f;
+
+// Font size of button titles.
+const CGFloat kIpadFontSize = 15.0f;
+const CGFloat kIphoneFontSize = 13.0f;
+
+// The grayscale value of the color object.
+const CGFloat kTitleColor = 51.0f / 255.0f;
+
+// The alpha value of the suggestion's description label.
+const CGFloat kDescriptionLabelAlpha = 0.54f;
+
+// The edge inset for background image.
+const CGFloat kBackgroundImageEdgeInsetSize = 8.0f;
+// The space between items in the label.
+const CGFloat kSpacing = 8.0f;
+
+// Structure that record the image for each icon.
+struct IconImageMap {
+ const char* const icon_name;
+ NSString* image_name;
+};
+
+const IconImageMap kCreditCardIconImageMap[] = {
+ {autofill::kAmericanExpressCard, @"autofill_card_american_express"},
+ {autofill::kDiscoverCard, @"autofill_card_discover"},
+ {autofill::kMasterCard, @"autofill_card_mastercard"},
+ {autofill::kVisaCard, @"autofill_card_visa"},
+ {autofill::kDinersCard, @"autofill_card_diners"},
+ {autofill::kGenericCard, @"autofill_card_generic"},
+ {autofill::kJCBCard, @"autofill_card_jcb"},
+ {autofill::kUnionPay, @"autofill_card_unionpay"},
+};
+
+// Creates a label with the given |text| and |alpha| suitable for use in a
+// suggestion button in the keyboard accessory view.
+UILabel* TextLabel(NSString* text, CGFloat alpha) {
+ base::scoped_nsobject<UILabel> label([[UILabel alloc] init]);
+ [label setText:text];
+ UIFont* font = IsIPadIdiom() ? [UIFont systemFontOfSize:kIpadFontSize]
+ : [UIFont systemFontOfSize:kIphoneFontSize];
+ [label setFont:font];
+ [label setTextColor:[UIColor colorWithWhite:kTitleColor alpha:alpha]];
+ [label setBackgroundColor:[UIColor clearColor]];
+ [label sizeToFit];
+ return label.autorelease();
+}
+
+} // namespace
+
+@interface FormSuggestionLabel ()
+
+@property(nonatomic, readonly) UIColor* normalBackgroundColor;
+@property(nonatomic, readonly) UIColor* pressedBackgroundColor;
+
+// Returns the color generated from the image named |imageName| resized to
+// |rect|.
+- (UIColor*)backgroundColorFromImageNamed:(NSString*)imageName
+ inRect:(CGRect)rect;
+// Returns the name of the image for credit card icon.
++ (NSString*)imageNameForCreditCardIcon:(NSString*)icon;
+@end
+
+@implementation FormSuggestionLabel {
+ // Client of this view.
+ base::WeakNSProtocol<id<FormSuggestionViewClient>> client_;
+ base::scoped_nsobject<FormSuggestion> suggestion_;
+
+ // Background color when the label is not pressed.
+ base::scoped_nsobject<UIColor> normalBackgroundColor_;
+ // Background color when the label is pressed.
+ base::scoped_nsobject<UIColor> pressedBackgroundColor_;
+}
+
+- (id)initWithSuggestion:(FormSuggestion*)suggestion
+ proposedFrame:(CGRect)proposedFrame
+ client:(id<FormSuggestionViewClient>)client {
+ // TODO(jimblackler): implement sizeThatFits: and layoutSubviews, and perform
+ // layout in those methods instead of in the designated initializer.
+ self = [super initWithFrame:CGRectZero];
+ if (self) {
+ suggestion_.reset([suggestion retain]);
+ client_.reset(client);
+
+ const CGFloat frameHeight = CGRectGetHeight(proposedFrame);
+ CGFloat currentX = kBorderWidth + kSpacing;
+
+ UIImage* iconImage = [UIImage imageNamed:
+ [FormSuggestionLabel imageNameForCreditCardIcon:suggestion.icon]];
+ if (iconImage) {
+ UIImageView* iconView =
+ [[[UIImageView alloc] initWithImage:iconImage] autorelease];
+ const CGFloat iconY =
+ std::floor((frameHeight - iconImage.size.height) / 2.0f);
+ iconView.frame = CGRectMake(currentX, iconY, iconImage.size.width,
+ iconImage.size.height);
+ [self addSubview:iconView];
+ currentX += CGRectGetWidth(iconView.frame) + kSpacing;
+ }
+
+ UILabel* label = TextLabel(suggestion.value, 1.0f);
+ const CGFloat labelY =
+ std::floor(frameHeight / 2.0f - CGRectGetMidY(label.frame));
+ label.frame = CGRectMake(currentX, labelY, CGRectGetWidth(label.frame),
+ CGRectGetHeight(label.frame));
+ [self addSubview:label];
+ currentX += CGRectGetWidth(label.frame) + kSpacing;
+
+ if (suggestion.displayDescription) {
+ UILabel* description =
+ TextLabel(suggestion.displayDescription, kDescriptionLabelAlpha);
+ const CGFloat descriptionY =
+ std::floor(frameHeight / 2.0f - CGRectGetMidY(description.frame));
+ description.frame =
+ CGRectMake(currentX, descriptionY, CGRectGetWidth(description.frame),
+ CGRectGetHeight(description.frame));
+ [self addSubview:description];
+ currentX += CGRectGetWidth(description.frame) + kSpacing;
+ }
+
+ currentX += kBorderWidth;
+
+ self.frame = CGRectMake(proposedFrame.origin.x, proposedFrame.origin.y,
+ currentX, proposedFrame.size.height);
+ [self setBackgroundColor:[self normalBackgroundColor]];
+ [[self layer] setCornerRadius:kCornerRadius];
+
+ [self setClipsToBounds:YES];
+ [self setIsAccessibilityElement:YES];
+ [self setAccessibilityLabel:suggestion.value];
+ [self setUserInteractionEnabled:YES];
+ }
+
+ return self;
+}
+
+- (id)initWithFrame:(CGRect)frame {
+ NOTREACHED();
+ return nil;
+}
+
+- (void)layoutSubviews {
+ // Resets the colors so the size will be updated in their getters.
+ normalBackgroundColor_.reset();
+ pressedBackgroundColor_.reset();
+ [super layoutSubviews];
+}
+
+#pragma mark -
+#pragma mark UIResponder
+
+- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
+ [self setBackgroundColor:self.pressedBackgroundColor];
+}
+
+- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
+ [self setBackgroundColor:self.normalBackgroundColor];
+}
+
+- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
+ [self setBackgroundColor:self.normalBackgroundColor];
+ [client_ didSelectSuggestion:suggestion_];
+}
+
+#pragma mark -
+#pragma mark Private
+
+- (UIColor*)normalBackgroundColor {
+ if (!normalBackgroundColor_) {
+ normalBackgroundColor_.reset(
+ [[self backgroundColorFromImageNamed:@"autofill_button"
+ inRect:self.bounds] retain]);
+ }
+ return normalBackgroundColor_;
+}
+
+- (UIColor*)pressedBackgroundColor {
+ if (!pressedBackgroundColor_) {
+ pressedBackgroundColor_.reset(
+ [[self backgroundColorFromImageNamed:@"autofill_button_pressed"
+ inRect:self.bounds] retain]);
+ }
+ return pressedBackgroundColor_;
+}
+
+- (UIColor*)backgroundColorFromImageNamed:(NSString*)imageName
+ inRect:(CGRect)rect {
+ UIEdgeInsets edgeInsets = UIEdgeInsetsMake(
+ kBackgroundImageEdgeInsetSize, kBackgroundImageEdgeInsetSize,
+ kBackgroundImageEdgeInsetSize, kBackgroundImageEdgeInsetSize);
+ UIImage* image =
+ [[UIImage imageNamed:imageName] resizableImageWithCapInsets:edgeInsets];
+
+ UIGraphicsBeginImageContextWithOptions(rect.size, NO,
+ UIScreen.mainScreen.scale);
+ [image drawInRect:rect];
+ UIImage* resizedImage = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+
+ return [UIColor colorWithPatternImage:resizedImage];
+}
+
++ (NSString*)imageNameForCreditCardIcon:(NSString*)icon {
+ if (!icon || [icon length] == 0) {
+ return nil;
+ }
+ std::string iconName(base::SysNSStringToUTF8(icon));
+ for (size_t i = 0; i < arraysize(kCreditCardIconImageMap); ++i) {
+ if (iconName.compare(kCreditCardIconImageMap[i].icon_name) == 0) {
+ return kCreditCardIconImageMap[i].image_name;
+ }
+ }
+ return nil;
+}
+
+@end
diff --git a/ios/chrome/browser/autofill/form_suggestion_provider.h b/ios/chrome/browser/autofill/form_suggestion_provider.h
new file mode 100644
index 0000000..09ba946
--- /dev/null
+++ b/ios/chrome/browser/autofill/form_suggestion_provider.h
@@ -0,0 +1,55 @@
+// Copyright 2014 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_SUGGESTION_PROVIDER_H_
+#define IOS_CHROME_BROWSER_AUTOFILL_FORM_SUGGESTION_PROVIDER_H_
+
+#import "components/autofill/ios/browser/form_suggestion.h"
+
+@protocol FormSuggestionProvider;
+
+namespace web {
+class WebState;
+} // namespace web
+
+typedef void (^SuggestionsAvailableCompletion)(BOOL suggestionsAvailable);
+typedef void (^SuggestionsReadyCompletion)(NSArray* suggestions,
+ id<FormSuggestionProvider> delegate);
+typedef void (^SuggestionHandledCompletion)(void);
+
+// Provides user-selectable suggestions for an input field of a web form
+// and handles user interaction with those suggestions.
+@protocol FormSuggestionProvider<NSObject>
+
+// Determines whether the receiver can provide suggestions for the specified
+// |form| and |field|, returning the result using the provided |completion|.
+// |typedValue| contains the text that the user has typed into the field so far.
+- (void)checkIfSuggestionsAvailableForForm:(NSString*)formName
+ field:(NSString*)fieldName
+ type:(NSString*)type
+ typedValue:(NSString*)typedValue
+ webState:(web::WebState*)webState
+ completionHandler:
+ (SuggestionsAvailableCompletion)completion;
+
+// Retrieves suggestions for the specified |form| and |field| and returns them
+// using the provided |completion|. |typedValue| contains the text that the
+// user has typed into the field so far.
+- (void)retrieveSuggestionsForForm:(NSString*)formName
+ field:(NSString*)fieldName
+ type:(NSString*)type
+ typedValue:(NSString*)typedValue
+ webState:(web::WebState*)webState
+ completionHandler:(SuggestionsReadyCompletion)completion;
+
+// Handles user selection of a suggestion for the specified form and
+// field, invoking |completion| when finished.
+- (void)didSelectSuggestion:(FormSuggestion*)suggestion
+ forField:(NSString*)fieldName
+ form:(NSString*)formName
+ completionHandler:(SuggestionHandledCompletion)completion;
+
+@end
+
+#endif // IOS_CHROME_BROWSER_AUTOFILL_FORM_SUGGESTION_PROVIDER_H_
diff --git a/ios/chrome/browser/autofill/form_suggestion_view.h b/ios/chrome/browser/autofill/form_suggestion_view.h
new file mode 100644
index 0000000..9a8c33c
--- /dev/null
+++ b/ios/chrome/browser/autofill/form_suggestion_view.h
@@ -0,0 +1,28 @@
+// Copyright 2013 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_SUGGESTION_VIEW_H_
+#define IOS_CHROME_BROWSER_AUTOFILL_FORM_SUGGESTION_VIEW_H_
+
+#import <UIKit/UIKit.h>
+
+#include "base/ios/weak_nsobject.h"
+
+@protocol FormSuggestionViewClient;
+
+// A scrollable view for displaying user-selectable autofill form suggestions.
+@interface FormSuggestionView : UIScrollView<UIInputViewAudioFeedback>
+
+// Initializes with |frame| and |client| to show |suggestions|.
+- (instancetype)initWithFrame:(CGRect)frame
+ client:(id<FormSuggestionViewClient>)client
+ suggestions:(NSArray*)suggestions;
+
+@end
+
+@interface FormSuggestionView (ForTesting)
+@property(nonatomic, readonly) NSArray* suggestions;
+@end
+
+#endif // IOS_CHROME_BROWSER_AUTOFILL_FORM_SUGGESTION_VIEW_H_
diff --git a/ios/chrome/browser/autofill/form_suggestion_view.mm b/ios/chrome/browser/autofill/form_suggestion_view.mm
new file mode 100644
index 0000000..4ac0467
--- /dev/null
+++ b/ios/chrome/browser/autofill/form_suggestion_view.mm
@@ -0,0 +1,97 @@
+// Copyright 2013 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_suggestion_view.h"
+
+#include "base/i18n/rtl.h"
+#include "base/mac/scoped_nsobject.h"
+#import "ios/chrome/browser/autofill/form_suggestion_label.h"
+#import "ios/chrome/browser/autofill/form_suggestion_view_client.h"
+
+namespace {
+
+// Vertical margin between suggestions and the edge of the suggestion content
+// frame.
+const CGFloat kSuggestionVerticalMargin = 4;
+
+// Horizontal margin around suggestions (i.e. between suggestions, and between
+// the end suggestions and the suggestion content frame).
+const CGFloat kSuggestionHorizontalMargin = 2;
+
+} // namespace
+
+@implementation FormSuggestionView {
+ // The FormSuggestions that are displayed by this view.
+ base::scoped_nsobject<NSArray> _suggestions;
+}
+
+- (instancetype)initWithFrame:(CGRect)frame
+ client:(id<FormSuggestionViewClient>)client
+ suggestions:(NSArray*)suggestions {
+ self = [super initWithFrame:frame];
+ if (self) {
+ _suggestions.reset([suggestions copy]);
+
+ self.showsVerticalScrollIndicator = NO;
+ self.showsHorizontalScrollIndicator = NO;
+ self.bounces = NO;
+ self.canCancelContentTouches = YES;
+
+ // Total height occupied by the label content, padding, border and margin.
+ const CGFloat labelHeight =
+ CGRectGetHeight(frame) - kSuggestionVerticalMargin * 2;
+
+ BOOL isRTL = base::i18n::IsRTL();
+
+ NSUInteger suggestionCount = [_suggestions count];
+ // References to labels. These references are used to adjust the labels'
+ // positions if they don't take up the whole suggestion view area for RTL.
+ base::scoped_nsobject<NSMutableArray> labels(
+ [[NSMutableArray alloc] initWithCapacity:suggestionCount]);
+ __block CGFloat currentX = kSuggestionHorizontalMargin;
+ void (^setupBlock)(FormSuggestion* suggestion, NSUInteger idx, BOOL* stop) =
+ ^(FormSuggestion* suggestion, NSUInteger idx, BOOL* stop) {
+ // FormSuggestionLabel will adjust the width, so here 0 is used for
+ // the width.
+ CGRect proposedFrame =
+ CGRectMake(currentX, kSuggestionVerticalMargin, 0, labelHeight);
+ base::scoped_nsobject<UIView> label(
+ [[FormSuggestionLabel alloc] initWithSuggestion:suggestion
+ proposedFrame:proposedFrame
+ client:client]);
+ [self addSubview:label];
+ [labels addObject:label];
+ currentX +=
+ CGRectGetWidth([label frame]) + kSuggestionHorizontalMargin;
+ };
+ [_suggestions enumerateObjectsWithOptions:(isRTL ? NSEnumerationReverse : 0)
+ usingBlock:setupBlock];
+
+ if (isRTL) {
+ if (currentX < CGRectGetWidth(frame)) {
+ self.contentSize = frame.size;
+ // Offsets labels for right alignment.
+ CGFloat offset = CGRectGetWidth(frame) - currentX;
+ for (UIView* label in labels.get()) {
+ label.frame = CGRectOffset(label.frame, offset, 0);
+ }
+ } else {
+ self.contentSize = CGSizeMake(currentX, CGRectGetHeight(frame));
+ // Sets the visible rectangle so suggestions at the right end are
+ // initially visible.
+ CGRect initRect = {{currentX - CGRectGetWidth(frame), 0}, frame.size};
+ [self scrollRectToVisible:initRect animated:NO];
+ }
+ } else {
+ self.contentSize = CGSizeMake(currentX, CGRectGetHeight(frame));
+ }
+ }
+ return self;
+}
+
+- (NSArray*)suggestions {
+ return _suggestions.get();
+}
+
+@end
diff --git a/ios/chrome/browser/autofill/form_suggestion_view_client.h b/ios/chrome/browser/autofill/form_suggestion_view_client.h
new file mode 100644
index 0000000..a3af25f
--- /dev/null
+++ b/ios/chrome/browser/autofill/form_suggestion_view_client.h
@@ -0,0 +1,20 @@
+// Copyright 2013 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_SUGGESTION_VIEW_CLIENT_H_
+#define IOS_CHROME_BROWSER_AUTOFILL_FORM_SUGGESTION_VIEW_CLIENT_H_
+
+#import <Foundation/Foundation.h>
+
+@class FormSuggestion;
+
+// Handles user interaction with a FormSuggestionView.
+@protocol FormSuggestionViewClient<NSObject>
+
+// Called when a suggestion is selected.
+- (void)didSelectSuggestion:(FormSuggestion*)suggestion;
+
+@end
+
+#endif // IOS_CHROME_BROWSER_AUTOFILL_FORM_SUGGESTION_VIEW_CLIENT_H_
diff --git a/ios/chrome/browser/passwords/OWNERS b/ios/chrome/browser/passwords/OWNERS
new file mode 100644
index 0000000..cdbac76f
--- /dev/null
+++ b/ios/chrome/browser/passwords/OWNERS
@@ -0,0 +1 @@
+dconnelly@chromium.org
diff --git a/ios/chrome/browser/passwords/password_generation_utils.h b/ios/chrome/browser/passwords/password_generation_utils.h
new file mode 100644
index 0000000..63e485b
--- /dev/null
+++ b/ios/chrome/browser/passwords/password_generation_utils.h
@@ -0,0 +1,29 @@
+// Copyright 2014 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_PASSWORDS_PASSWORD_GENERATION_UTILS_H_
+#define IOS_CHROME_BROWSER_PASSWORDS_PASSWORD_GENERATION_UTILS_H_
+
+#import <Foundation/Foundation.h>
+#import <CoreGraphics/CoreGraphics.h>
+
+namespace passwords {
+
+// Returns the frame determined by moving |inner_frame| inside |outer_frame|
+// for the password generation input accessory views.
+CGRect GetGenerationAccessoryFrame(CGRect outer_frame, CGRect inner_frame);
+
+// Block types for |RunSearchPipeline|.
+typedef void (^PipelineBlock)(void (^completion)(BOOL));
+typedef void (^PipelineCompletionBlock)(NSUInteger index);
+
+// Executes each PipelineBlock in |blocks| in order until one invokes its
+// completion with YES, in which case |on_complete| will be invoked with the
+// |index| of the succeeding block, or until they all invoke their completions
+// with NO, in which case |on_complete| will be invoked with NSNotFound.
+void RunSearchPipeline(NSArray* blocks, PipelineCompletionBlock on_complete);
+
+} // namespace passwords
+
+#endif // IOS_CHROME_BROWSER_PASSWORDS_PASSWORD_GENERATION_UTILS_H_
diff --git a/ios/chrome/browser/passwords/password_generation_utils.mm b/ios/chrome/browser/passwords/password_generation_utils.mm
new file mode 100644
index 0000000..7f1abd4
--- /dev/null
+++ b/ios/chrome/browser/passwords/password_generation_utils.mm
@@ -0,0 +1,50 @@
+// Copyright 2014 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.
+
+#include "ios/chrome/browser/passwords/password_generation_utils.h"
+
+#include "base/i18n/rtl.h"
+#include "ios/chrome/browser/ui/ui_util.h"
+
+namespace passwords {
+
+namespace {
+
+const CGFloat kPadding = IsIPadIdiom() ? 16 : 8;
+
+// The actual implementation of |RunPipeline| that begins with the first block
+// in |blocks|.
+void RunSearchPipeline(NSArray* blocks,
+ PipelineCompletionBlock on_complete,
+ NSUInteger from_index) {
+ if (from_index == [blocks count]) {
+ on_complete(NSNotFound);
+ return;
+ }
+ PipelineBlock block = blocks[from_index];
+ block(^(BOOL success) {
+ if (success)
+ on_complete(from_index);
+ else
+ RunSearchPipeline(blocks, on_complete, from_index + 1);
+ });
+}
+
+} // namespace
+
+CGRect GetGenerationAccessoryFrame(CGRect outer_frame, CGRect inner_frame) {
+ CGFloat x = kPadding;
+ if (base::i18n::IsRTL())
+ x = CGRectGetWidth(outer_frame) - CGRectGetWidth(inner_frame) - kPadding;
+ const CGFloat y =
+ (CGRectGetHeight(outer_frame) - CGRectGetHeight(inner_frame)) / 2.0;
+ inner_frame.origin = CGPointMake(x, y);
+ return inner_frame;
+}
+
+void RunSearchPipeline(NSArray* blocks, PipelineCompletionBlock on_complete) {
+ RunSearchPipeline(blocks, on_complete, 0);
+}
+
+} // namespace passwords
diff --git a/ios/chrome/ios_chrome.gyp b/ios/chrome/ios_chrome.gyp
index bd89b5c..5334f35 100644
--- a/ios/chrome/ios_chrome.gyp
+++ b/ios/chrome/ios_chrome.gyp
@@ -15,6 +15,8 @@
],
'dependencies': [
'../../base/base.gyp:base',
+ '../../components/components.gyp:autofill_core_browser',
+ '../../components/components.gyp:autofill_ios_browser',
'../../components/components.gyp:dom_distiller_core',
'../../components/components.gyp:dom_distiller_ios',
'../../components/components.gyp:infobars_core',
@@ -53,6 +55,21 @@
'browser/application_context_impl.h',
'browser/arch_util.cc',
'browser/arch_util.h',
+ 'browser/autofill/autofill_agent_utils.h',
+ 'browser/autofill/autofill_agent_utils.mm',
+ 'browser/autofill/form_input_accessory_view.h',
+ 'browser/autofill/form_input_accessory_view.mm',
+ 'browser/autofill/form_input_accessory_view_controller.h',
+ 'browser/autofill/form_input_accessory_view_controller.mm',
+ 'browser/autofill/form_input_accessory_view_delegate.h',
+ 'browser/autofill/form_suggestion_controller.h',
+ 'browser/autofill/form_suggestion_controller.mm',
+ 'browser/autofill/form_suggestion_label.h',
+ 'browser/autofill/form_suggestion_label.mm',
+ 'browser/autofill/form_suggestion_provider.h',
+ 'browser/autofill/form_suggestion_view.h',
+ 'browser/autofill/form_suggestion_view.mm',
+ 'browser/autofill/form_suggestion_view_client.h',
'browser/browser_state/browser_state_otr_helper.cc',
'browser/browser_state/browser_state_otr_helper.h',
'browser/chrome_switches.cc',
@@ -81,6 +98,8 @@
'browser/infobars/infobar_utils.mm',
'browser/net/image_fetcher.h',
'browser/net/image_fetcher.mm',
+ 'browser/passwords/password_generation_utils.h',
+ 'browser/passwords/password_generation_utils.mm',
'browser/pref_names.cc',
'browser/pref_names.h',
'browser/snapshots/snapshot_cache.h',