Don't show supervised user as "already on this device" while they're being imported.
[chromium-blink-merge.git] / ios / chrome / browser / autofill / form_suggestion_controller.mm
blob89f5fbc68bc218a05f3c8e54290c01b6e5ffcaf3
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "ios/chrome/browser/autofill/form_suggestion_controller.h"
7 #include "base/ios/weak_nsobject.h"
8 #include "base/mac/foundation_util.h"
9 #include "base/mac/scoped_block.h"
10 #include "base/mac/scoped_nsobject.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/strings/sys_string_conversions.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "components/autofill/core/browser/autofill_popup_delegate.h"
15 #import "components/autofill/ios/browser/form_suggestion.h"
16 #import "ios/chrome/browser/autofill/form_input_accessory_view_controller.h"
17 #import "ios/chrome/browser/autofill/form_suggestion_provider.h"
18 #import "ios/chrome/browser/autofill/form_suggestion_view.h"
19 #import "ios/chrome/browser/passwords/password_generation_utils.h"
20 #include "ios/web/public/url_scheme_util.h"
21 #import "ios/web/public/web_state/crw_web_view_proxy.h"
22 #import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
23 #import "ios/web/public/web_state/web_state.h"
25 namespace {
27 // Struct that describes suggestion state.
28 struct AutofillSuggestionState {
29   AutofillSuggestionState(const std::string& form_name,
30                           const std::string& field_name,
31                           const std::string& typed_value);
32   // The name of the form for autofill.
33   std::string form_name;
34   // The name of the field for autofill.
35   std::string field_name;
36   // The user-typed value in the field.
37   std::string typed_value;
38   // The suggestions for the form field. An array of |FormSuggestion|.
39   base::scoped_nsobject<NSArray> suggestions;
42 AutofillSuggestionState::AutofillSuggestionState(const std::string& form_name,
43                                                  const std::string& field_name,
44                                                  const std::string& typed_value)
45     : form_name(form_name), field_name(field_name), typed_value(typed_value) {
48 }  // namespace
50 @interface FormSuggestionController () <FormInputAccessoryViewProvider> {
51   // Form navigation delegate.
52   base::WeakNSProtocol<id<FormInputAccessoryViewDelegate>> _delegate;
54   // Callback to update the accessory view.
55   base::mac::ScopedBlock<AccessoryViewReadyCompletion>
56       accessoryViewUpdateBlock_;
58   // Autofill suggestion state.
59   scoped_ptr<AutofillSuggestionState> _suggestionState;
61   // Providers for suggestions, sorted according to the order in which
62   // they should be asked for suggestions, with highest priority in front.
63   base::scoped_nsobject<NSArray> _suggestionProviders;
65   // Access to WebView from the CRWWebController.
66   base::scoped_nsprotocol<id<CRWWebViewProxy>> _webViewProxy;
69 // Returns an autoreleased input accessory view that shows |suggestions|.
70 - (UIView*)suggestionViewWithSuggestions:(NSArray*)suggestions;
72 // Updates keyboard for |suggestionState|.
73 - (void)updateKeyboard:(AutofillSuggestionState*)suggestionState;
75 // Updates keyboard with |suggestions|.
76 - (void)updateKeyboardWithSuggestions:(NSArray*)suggestions;
78 // Clears state in between page loads.
79 - (void)resetSuggestionState;
81 // Finds a FormSuggestionProvider that can supply suggestions for the specified
82 // form, requests them, and updates the view accordingly.
83 - (void)retrieveSuggestionsForFormNamed:(const std::string&)formName
84                               fieldName:(const std::string&)fieldName
85                                    type:(const std::string&)type
86                                webState:(web::WebState*)webState;
88 @end
90 @implementation FormSuggestionController {
91   // Bridge to observe the web state from Objective-C.
92   scoped_ptr<web::WebStateObserverBridge> _webStateObserverBridge;
94   // Manager for FormSuggestion JavaScripts.
95   base::scoped_nsobject<JsSuggestionManager> _jsSuggestionManager;
97   // The provider for the current set of suggestions.
98   __weak id<FormSuggestionProvider> _provider;
101 - (instancetype)initWithWebState:(web::WebState*)webState
102                        providers:(NSArray*)providers
103              JsSuggestionManager:(JsSuggestionManager*)jsSuggestionManager {
104   self = [super init];
105   if (self) {
106     _webStateObserverBridge.reset(
107         new web::WebStateObserverBridge(webState, self));
108     _webViewProxy.reset([webState->GetWebViewProxy() retain]);
109     _jsSuggestionManager.reset([jsSuggestionManager retain]);
110     _suggestionProviders.reset([providers copy]);
111   }
112   return self;
115 - (instancetype)initWithWebState:(web::WebState*)webState
116                        providers:(NSArray*)providers {
117   JsSuggestionManager* jsSuggestionManager =
118       base::mac::ObjCCast<JsSuggestionManager>(
119           [webState->GetJSInjectionReceiver()
120               instanceOfClass:[JsSuggestionManager class]]);
121   return [self initWithWebState:webState
122                       providers:providers
123             JsSuggestionManager:jsSuggestionManager];
126 - (void)detachFromWebState {
127   _webStateObserverBridge.reset();
130 #pragma mark -
131 #pragma mark CRWWebStateObserver
133 - (void)webStateDestroyed:(web::WebState*)webState {
134   [self detachFromWebState];
137 - (void)webStateDidLoadPage:(web::WebState*)webState {
138   [self processPage:webState];
141 - (void)processPage:(web::WebState*)webState {
142   [self resetSuggestionState];
144   web::URLVerificationTrustLevel trustLevel =
145       web::URLVerificationTrustLevel::kNone;
146   const GURL pageURL(webState->GetCurrentURL(&trustLevel));
147   if (trustLevel != web::URLVerificationTrustLevel::kAbsolute) {
148     DLOG(WARNING) << "Page load not handled on untrusted page";
149     return;
150   }
152   if (web::UrlHasWebScheme(pageURL) && webState->ContentIsHTML())
153     [_jsSuggestionManager inject];
156 - (void)setWebViewProxy:(id<CRWWebViewProxy>)webViewProxy {
157   _webViewProxy.reset([webViewProxy retain]);
160 - (void)retrieveSuggestionsForFormNamed:(const std::string&)formName
161                               fieldName:(const std::string&)fieldName
162                                    type:(const std::string&)type
163                                webState:(web::WebState*)webState {
164   base::WeakNSObject<FormSuggestionController> weakSelf(self);
165   base::scoped_nsobject<NSString> strongFormName(
166       [base::SysUTF8ToNSString(formName) copy]);
167   base::scoped_nsobject<NSString> strongFieldName(
168       [base::SysUTF8ToNSString(fieldName) copy]);
169   base::scoped_nsobject<NSString> strongType(
170       [base::SysUTF8ToNSString(type) copy]);
171   base::scoped_nsobject<NSString> strongValue(
172       [base::SysUTF8ToNSString(_suggestionState.get()->typed_value) copy]);
174   // Build a block for each provider that will invoke its completion with YES
175   // if the provider can provide suggestions for the specified form/field/type
176   // and NO otherwise.
177   base::scoped_nsobject<NSMutableArray> findProviderBlocks(
178       [[NSMutableArray alloc] init]);
179   for (NSUInteger i = 0; i < [_suggestionProviders count]; i++) {
180     base::mac::ScopedBlock<passwords::PipelineBlock> block(
181         ^(void (^completion)(BOOL success)) {
182           // Access all the providers through |self| to guarantee that both
183           // |self| and all the providers exist when the block is executed.
184           // |_suggestionProviders| is immutable, so the subscripting is
185           // always valid.
186           base::scoped_nsobject<FormSuggestionController> strongSelf(
187               [weakSelf retain]);
188           if (!strongSelf)
189             return;
190           id<FormSuggestionProvider> provider =
191               strongSelf.get()->_suggestionProviders[i];
192           [provider checkIfSuggestionsAvailableForForm:strongFormName
193                                                  field:strongFieldName
194                                                   type:strongType
195                                             typedValue:strongValue
196                                               webState:webState
197                                      completionHandler:completion];
198         },
199         base::scoped_policy::RETAIN);
200     [findProviderBlocks addObject:block];
201   }
203   // Once the suggestions are retrieved, update the suggestions UI.
204   SuggestionsReadyCompletion readyCompletion =
205       ^(NSArray* suggestions, id<FormSuggestionProvider> provider) {
206         [weakSelf onSuggestionsReady:suggestions provider:provider];
207       };
209   // Once a provider is found, use it to retrieve suggestions.
210   passwords::PipelineCompletionBlock completion = ^(NSUInteger providerIndex) {
211     if (providerIndex == NSNotFound)
212       return;
213     base::scoped_nsobject<FormSuggestionController> strongSelf(
214         [weakSelf retain]);
215     if (!strongSelf)
216       return;
217     id<FormSuggestionProvider> provider =
218         strongSelf.get()->_suggestionProviders[providerIndex];
219     [provider retrieveSuggestionsForForm:strongFormName
220                                    field:strongFieldName
221                                     type:strongType
222                               typedValue:strongValue
223                                 webState:webState
224                        completionHandler:readyCompletion];
225   };
227   // Run all the blocks in |findProviderBlocks| until one invokes its
228   // completion with YES. The first one to do so will be passed to
229   // |onProviderFound|.
230   passwords::RunSearchPipeline(findProviderBlocks, completion);
233 - (void)onSuggestionsReady:(NSArray*)suggestions
234                   provider:(id<FormSuggestionProvider>)provider {
235   // TODO(ios): crbug.com/249916. If we can also pass in the form/field for
236   // which |sugguestions| are, we should check here if |suggestions| are for
237   // the current active element. If not, reset |_suggestionState|.
238   if (!_suggestionState) {
239     // The suggestion state was reset in between the call to Autofill API (e.g.
240     // OnQueryFormFieldAutofill) and this method being called back. Results are
241     // therefore no longer relevant.
242     return;
243   }
245   _provider = provider;
246   _suggestionState->suggestions.reset([suggestions copy]);
247   [self updateKeyboard:_suggestionState.get()];
250 - (void)resetSuggestionState {
251   _provider = nil;
252   _suggestionState.reset();
255 - (void)clearSuggestions {
256   // Note that other parts of the suggestionsState are not reset.
257   if (!_suggestionState.get())
258     return;
259   _suggestionState->suggestions.reset([[NSArray alloc] init]);
260   [self updateKeyboard:_suggestionState.get()];
263 - (void)updateKeyboard:(AutofillSuggestionState*)suggestionState {
264   if (!_suggestionState) {
265     if (accessoryViewUpdateBlock_)
266       accessoryViewUpdateBlock_.get()(nil, self);
267   } else {
268     [self updateKeyboardWithSuggestions:_suggestionState->suggestions];
269   }
272 - (void)updateKeyboardWithSuggestions:(NSArray*)suggestions {
273   if (accessoryViewUpdateBlock_) {
274     accessoryViewUpdateBlock_.get()(
275         [self suggestionViewWithSuggestions:suggestions], self);
276   }
279 - (UIView*)suggestionViewWithSuggestions:(NSArray*)suggestions {
280   base::scoped_nsobject<FormSuggestionView> view([[FormSuggestionView alloc]
281       initWithFrame:[_webViewProxy getKeyboardAccessory].frame
282              client:self
283         suggestions:suggestions]);
284   return view.autorelease();
287 - (void)didSelectSuggestion:(FormSuggestion*)suggestion {
288   if (!_suggestionState)
289     return;
291   // Send the suggestion to the provider and advance the cursor.
292   base::WeakNSObject<FormSuggestionController> weakSelf(self);
293   [_provider
294       didSelectSuggestion:suggestion
295                  forField:base::SysUTF8ToNSString(_suggestionState->field_name)
296                      form:base::SysUTF8ToNSString(_suggestionState->form_name)
297         completionHandler:^{
298           [[weakSelf accessoryViewDelegate] selectNextElement];
299         }];
300   _provider = nil;
303 - (id<FormInputAccessoryViewProvider>)accessoryViewProvider {
304   return self;
307 #pragma mark FormInputAccessoryViewProvider
309 - (id<FormInputAccessoryViewDelegate>)accessoryViewDelegate {
310   return _delegate.get();
313 - (void)setAccessoryViewDelegate:(id<FormInputAccessoryViewDelegate>)delegate {
314   _delegate.reset(delegate);
317 - (void)
318     checkIfAccessoryViewIsAvailableForFormNamed:(const std::string&)formName
319                                       fieldName:(const std::string&)fieldName
320                                        webState:(web::WebState*)webState
321                               completionHandler:
322                                   (AccessoryViewAvailableCompletion)
323                                       completionHandler {
324   [self processPage:webState];
325   completionHandler(YES);
328 - (void)retrieveAccessoryViewForFormNamed:(const std::string&)formName
329                                 fieldName:(const std::string&)fieldName
330                                     value:(const std::string&)value
331                                      type:(const std::string&)type
332                                  webState:(web::WebState*)webState
333                  accessoryViewUpdateBlock:
334                      (AccessoryViewReadyCompletion)accessoryViewUpdateBlock {
335   _suggestionState.reset(
336       new AutofillSuggestionState(formName, fieldName, value));
337   accessoryViewUpdateBlock([self suggestionViewWithSuggestions:@[]], self);
338   accessoryViewUpdateBlock_.reset([accessoryViewUpdateBlock copy]);
339   [self retrieveSuggestionsForFormNamed:formName
340                               fieldName:fieldName
341                                    type:type
342                                webState:webState];
345 - (void)inputAccessoryViewControllerDidReset:
346         (FormInputAccessoryViewController*)controller {
347   accessoryViewUpdateBlock_.reset();
348   [self resetSuggestionState];
351 - (void)resizeAccessoryView {
352   [self updateKeyboard:_suggestionState.get()];
355 @end