Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(65)

Side by Side Diff: ios/chrome/browser/autofill/form_input_accessory_view_controller.mm

Issue 1305433003: Make Autofill work again on iPads running iOS 9. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #import "ios/chrome/browser/autofill/form_input_accessory_view_controller.h" 5 #import "ios/chrome/browser/autofill/form_input_accessory_view_controller.h"
6 6
7 #include "base/ios/block_types.h" 7 #include "base/ios/block_types.h"
8 #include "base/ios/ios_util.h"
8 #include "base/mac/foundation_util.h" 9 #include "base/mac/foundation_util.h"
9 #include "base/mac/scoped_block.h" 10 #include "base/mac/scoped_block.h"
10 #include "base/mac/scoped_nsobject.h" 11 #include "base/mac/scoped_nsobject.h"
11 #include "base/memory/scoped_ptr.h" 12 #include "base/memory/scoped_ptr.h"
12 #include "base/strings/sys_string_conversions.h"
13 #include "base/strings/utf_string_conversions.h"
14 #import "components/autofill/ios/browser/js_suggestion_manager.h" 13 #import "components/autofill/ios/browser/js_suggestion_manager.h"
15 #import "ios/chrome/browser/autofill/form_input_accessory_view.h" 14 #import "ios/chrome/browser/autofill/form_input_accessory_view.h"
15 #import "ios/chrome/browser/autofill/form_suggestion_view.h"
16 #import "ios/chrome/browser/passwords/password_generation_utils.h" 16 #import "ios/chrome/browser/passwords/password_generation_utils.h"
17 #include "ios/chrome/browser/ui/ui_util.h"
17 #include "ios/web/public/test/crw_test_js_injection_receiver.h" 18 #include "ios/web/public/test/crw_test_js_injection_receiver.h"
18 #include "ios/web/public/url_scheme_util.h" 19 #include "ios/web/public/url_scheme_util.h"
19 #import "ios/web/public/web_state/crw_web_view_proxy.h" 20 #import "ios/web/public/web_state/crw_web_view_proxy.h"
20 #include "ios/web/public/web_state/url_verification_constants.h" 21 #include "ios/web/public/web_state/url_verification_constants.h"
21 #include "ios/web/public/web_state/web_state.h" 22 #include "ios/web/public/web_state/web_state.h"
22 #include "url/gurl.h" 23 #include "url/gurl.h"
23 24
24 namespace autofill { 25 namespace autofill {
25 NSString* const kFormSuggestionAssistButtonPreviousElement = @"previousTap"; 26 NSString* const kFormSuggestionAssistButtonPreviousElement = @"previousTap";
26 NSString* const kFormSuggestionAssistButtonNextElement = @"nextTap"; 27 NSString* const kFormSuggestionAssistButtonNextElement = @"nextTap";
27 NSString* const kFormSuggestionAssistButtonDone = @"done"; 28 NSString* const kFormSuggestionAssistButtonDone = @"done";
29 CGFloat const kInputAccessoryHeight = 44.0f;
28 } // namespace autofill 30 } // namespace autofill
29 31
30 namespace { 32 namespace {
31 33
32 // Finds all views of a particular kind if class |klass| in the subview 34 // Finds all views of a particular kind if class |klass| in the subview
33 // hierarchy of the given |root| view. 35 // hierarchy of the given |root| view.
34 NSArray* FindDescendantsOfClass(UIView* root, Class klass) { 36 NSArray* FindDescendantsOfClass(UIView* root, Class klass) {
35 DCHECK(root); 37 DCHECK(root);
36 NSMutableArray* viewsToExamine = [NSMutableArray arrayWithObject:root]; 38 NSMutableArray* viewsToExamine = [NSMutableArray arrayWithObject:root];
37 NSMutableArray* descendants = [NSMutableArray array]; 39 NSMutableArray* descendants = [NSMutableArray array];
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
86 88
87 // Computes the frame of each part of the accessory view of the keyboard. It is 89 // Computes the frame of each part of the accessory view of the keyboard. It is
88 // assumed that the keyboard has either two parts (when it is split) or one part 90 // assumed that the keyboard has either two parts (when it is split) or one part
89 // (when it is merged). 91 // (when it is merged).
90 // 92 //
91 // If there are two parts, the frame of the left part is returned in 93 // If there are two parts, the frame of the left part is returned in
92 // |leftFrame| and the frame of the right part is returned in |rightFrame|. 94 // |leftFrame| and the frame of the right part is returned in |rightFrame|.
93 // If there is only one part, the frame is returned in |leftFrame| and 95 // If there is only one part, the frame is returned in |leftFrame| and
94 // |rightFrame| has size zero. 96 // |rightFrame| has size zero.
95 // 97 //
96 // Heuristics are used to compute this information. It returns true if the 98 // Heuristics are used to compute this information. It returns false if the
97 // number of |inputAccessoryView.subviews| is not 2. 99 // number of |inputAccessoryView.subviews| is not 2.
98 bool ComputeFramesOfKeyboardParts(UIView* inputAccessoryView, 100 bool ComputeFramesOfKeyboardParts(UIView* inputAccessoryView,
99 CGRect* leftFrame, 101 CGRect* leftFrame,
100 CGRect* rightFrame) { 102 CGRect* rightFrame) {
101 // It is observed (on iOS 6) there are always two subviews in the original 103 // It is observed (on iOS 6) there are always two subviews in the original
102 // input accessory view. When the keyboard is split, each subview represents 104 // input accessory view. When the keyboard is split, each subview represents
103 // one part of the accesssary view of the keyboard. When the keyboard is 105 // one part of the accesssary view of the keyboard. When the keyboard is
104 // merged, one subview has the same frame as that of the whole accessory view 106 // merged, one subview has the same frame as that of the whole accessory view
105 // and the other has zero size with the screen width as origin.x. 107 // and the other has zero size with the screen width as origin.x.
106 // The computation here is based on this observation. 108 // The computation here is based on this observation.
(...skipping 16 matching lines...) Expand all
123 125
124 } // namespace 126 } // namespace
125 127
126 @interface FormInputAccessoryViewController () 128 @interface FormInputAccessoryViewController ()
127 129
128 // Allows injection of the JsSuggestionManager. 130 // Allows injection of the JsSuggestionManager.
129 - (instancetype)initWithWebState:(web::WebState*)webState 131 - (instancetype)initWithWebState:(web::WebState*)webState
130 JSSuggestionManager:(JsSuggestionManager*)JSSuggestionManager 132 JSSuggestionManager:(JsSuggestionManager*)JSSuggestionManager
131 providers:(NSArray*)providers; 133 providers:(NSArray*)providers;
132 134
133 // Called when the keyboard did change frame. 135 // Called when the keyboard will or did change frame.
134 - (void)keyboardDidChangeFrame:(NSNotification*)notification; 136 - (void)keyboardWillOrDidChangeFrame:(NSNotification*)notification;
135 137
136 // Called when the keyboard is dismissed. 138 // Called when the keyboard is dismissed.
137 - (void)keyboardDidHide:(NSNotification*)notification; 139 - (void)keyboardDidHide:(NSNotification*)notification;
138 140
139 // Hides the subviews in |accessoryView|. 141 // Hides the subviews in |accessoryView|.
140 - (void)hideSubviewsInOriginalAccessoryView:(UIView*)accessoryView; 142 - (void)hideSubviewsInOriginalAccessoryView:(UIView*)accessoryView;
141 143
142 // Attempts to execute/tap/send-an-event-to the iOS built-in "next" and 144 // Attempts to execute/tap/send-an-event-to the iOS built-in "next" and
143 // "previous" form assist controls. Returns NO if this attempt failed, YES 145 // "previous" form assist controls. Returns NO if this attempt failed, YES
144 // otherwise. [HACK] 146 // otherwise. [HACK]
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
180 base::scoped_nsobject<JsSuggestionManager> _JSSuggestionManager; 182 base::scoped_nsobject<JsSuggestionManager> _JSSuggestionManager;
181 183
182 // The original subviews in keyboard accessory view that were originally not 184 // The original subviews in keyboard accessory view that were originally not
183 // hidden but were hidden when showing Autofill suggestions. 185 // hidden but were hidden when showing Autofill suggestions.
184 base::scoped_nsobject<NSMutableArray> _hiddenOriginalSubviews; 186 base::scoped_nsobject<NSMutableArray> _hiddenOriginalSubviews;
185 187
186 // The objects that can provide a custom input accessory view while filling 188 // The objects that can provide a custom input accessory view while filling
187 // forms. 189 // forms.
188 base::scoped_nsobject<NSArray> _providers; 190 base::scoped_nsobject<NSArray> _providers;
189 191
192 // Whether suggestions have previously been shown.
193 BOOL _suggestionsHaveBeenShown;
194
190 // The object that manages the currently-shown custom accessory view. 195 // The object that manages the currently-shown custom accessory view.
191 base::WeakNSProtocol<id<FormInputAccessoryViewProvider>> _currentProvider; 196 base::WeakNSProtocol<id<FormInputAccessoryViewProvider>> _currentProvider;
192 } 197 }
193 198
194 - (instancetype)initWithWebState:(web::WebState*)webState 199 - (instancetype)initWithWebState:(web::WebState*)webState
195 providers:(NSArray*)providers { 200 providers:(NSArray*)providers {
196 JsSuggestionManager* suggestionManager = 201 JsSuggestionManager* suggestionManager =
197 base::mac::ObjCCastStrict<JsSuggestionManager>( 202 base::mac::ObjCCastStrict<JsSuggestionManager>(
198 [webState->GetJSInjectionReceiver() 203 [webState->GetJSInjectionReceiver()
199 instanceOfClass:[JsSuggestionManager class]]); 204 instanceOfClass:[JsSuggestionManager class]]);
200 return [self initWithWebState:webState 205 return [self initWithWebState:webState
201 JSSuggestionManager:suggestionManager 206 JSSuggestionManager:suggestionManager
202 providers:providers]; 207 providers:providers];
203 } 208 }
204 209
205 - (instancetype)initWithWebState:(web::WebState*)webState 210 - (instancetype)initWithWebState:(web::WebState*)webState
206 JSSuggestionManager:(JsSuggestionManager*)JSSuggestionManager 211 JSSuggestionManager:(JsSuggestionManager*)JSSuggestionManager
207 providers:(NSArray*)providers { 212 providers:(NSArray*)providers {
208 self = [super init]; 213 self = [super init];
209 if (self) { 214 if (self) {
210 _JSSuggestionManager.reset([JSSuggestionManager retain]); 215 _JSSuggestionManager.reset([JSSuggestionManager retain]);
211 _hiddenOriginalSubviews.reset([[NSMutableArray alloc] init]); 216 _hiddenOriginalSubviews.reset([[NSMutableArray alloc] init]);
212 _webStateObserverBridge.reset( 217 _webStateObserverBridge.reset(
213 new web::WebStateObserverBridge(webState, self)); 218 new web::WebStateObserverBridge(webState, self));
214 _providers.reset([providers copy]); 219 _providers.reset([providers copy]);
215 // There is no defined relation on the timing of JavaScript events and 220 _suggestionsHaveBeenShown = NO;
216 // keyboard showing up. So it is necessary to listen to the keyboard
217 // notification to make sure the keyboard is updated.
218 [[NSNotificationCenter defaultCenter]
219 addObserver:self
220 selector:@selector(keyboardDidChangeFrame:)
221 name:UIKeyboardDidChangeFrameNotification
222 object:nil];
223 [[NSNotificationCenter defaultCenter]
224 addObserver:self
225 selector:@selector(keyboardDidHide:)
226 name:UIKeyboardDidHideNotification
227 object:nil];
228 } 221 }
229 return self; 222 return self;
230 } 223 }
231 224
225 - (void)wasShown {
226 // There is no defined relation on the timing of JavaScript events and
227 // keyboard showing up. So it is necessary to listen to the keyboard
228 // notification to make sure the keyboard is updated.
229 if (base::ios::IsRunningOnIOS9OrLater() && IsIPadIdiom()) {
230 [[NSNotificationCenter defaultCenter]
231 addObserver:self
232 selector:@selector(keyboardWillOrDidChangeFrame:)
233 name:UIKeyboardWillChangeFrameNotification
234 object:nil];
235 [[NSNotificationCenter defaultCenter]
236 addObserver:self
237 selector:@selector(textInputDidBeginEditing:)
238 name:UITextFieldTextDidBeginEditingNotification
239 object:nil];
240 [[NSNotificationCenter defaultCenter]
241 addObserver:self
242 selector:@selector(textInputDidBeginEditing:)
243 name:UITextViewTextDidBeginEditingNotification
244 object:nil];
245 }
246 [[NSNotificationCenter defaultCenter]
247 addObserver:self
248 selector:@selector(keyboardWillOrDidChangeFrame:)
249 name:UIKeyboardDidChangeFrameNotification
250 object:nil];
251 [[NSNotificationCenter defaultCenter]
252 addObserver:self
253 selector:@selector(keyboardDidHide:)
254 name:UIKeyboardDidHideNotification
255 object:nil];
256 }
257
258 - (void)wasHidden {
259 [_customAccessoryView removeFromSuperview];
260 [[NSNotificationCenter defaultCenter] removeObserver:self];
261 }
262
232 - (void)dealloc { 263 - (void)dealloc {
233 [[NSNotificationCenter defaultCenter] removeObserver:self]; 264 [[NSNotificationCenter defaultCenter] removeObserver:self];
234 [super dealloc]; 265 [super dealloc];
235 } 266 }
236 267
237 - (web::WebState*)webState { 268 - (web::WebState*)webState {
238 return _webStateObserverBridge ? _webStateObserverBridge->web_state() 269 return _webStateObserverBridge ? _webStateObserverBridge->web_state()
239 : nullptr; 270 : nullptr;
240 } 271 }
241 272
242 - (id<CRWWebViewProxy>)webViewProxy { 273 - (id<CRWWebViewProxy>)webViewProxy {
243 return self.webState ? self.webState->GetWebViewProxy() : nil; 274 return self.webState ? self.webState->GetWebViewProxy() : nil;
244 } 275 }
245 276
246 - (void)hideSubviewsInOriginalAccessoryView:(UIView*)accessoryView { 277 - (void)hideSubviewsInOriginalAccessoryView:(UIView*)accessoryView {
247 for (UIView* subview in [accessoryView subviews]) { 278 for (UIView* subview in [accessoryView subviews]) {
248 if (!subview.hidden) { 279 if (!subview.hidden) {
249 [_hiddenOriginalSubviews addObject:subview]; 280 [_hiddenOriginalSubviews addObject:subview];
250 subview.hidden = YES; 281 subview.hidden = YES;
251 } 282 }
252 } 283 }
253 } 284 }
254 285
255 - (void)showCustomInputAccessoryView:(UIView*)view { 286 - (void)showCustomInputAccessoryView:(UIView*)view {
256 [self restoreDefaultInputAccessoryView]; 287 DCHECK(view);
257 CGRect leftFrame; 288 if (base::ios::IsRunningOnIOS9OrLater() && IsIPadIdiom()) {
258 CGRect rightFrame; 289 // On iPads running iOS 9 or later, there's no inputAccessoryView available
259 UIView* inputAccessoryView = [self.webViewProxy getKeyboardAccessory]; 290 // so we attach the custom view directly to the keyboard view instead.
260 if (ComputeFramesOfKeyboardParts(inputAccessoryView, &leftFrame, 291 [_customAccessoryView removeFromSuperview];
261 &rightFrame)) { 292
262 [self hideSubviewsInOriginalAccessoryView:inputAccessoryView]; 293 // If the keyboard isn't visible or no suggestions have been triggered yet,
294 // don't show the custom view.
295 int numSuggestions =
296 [[static_cast<FormSuggestionView*>(view) suggestions] count];
297 if (CGRectIntersection([UIScreen mainScreen].bounds, _keyboardFrame)
298 .size.height == 0 ||
299 CGRectEqualToRect(_keyboardFrame, CGRectZero) ||
300 (!_suggestionsHaveBeenShown && numSuggestions == 0)) {
301 _customAccessoryView.reset();
302 return;
303 }
304 _suggestionsHaveBeenShown = YES;
305
306 CGFloat height = autofill::kInputAccessoryHeight;
307 CGRect frame = CGRectMake(_keyboardFrame.origin.x, -height,
308 _keyboardFrame.size.width, height);
263 _customAccessoryView.reset( 309 _customAccessoryView.reset(
264 [[FormInputAccessoryView alloc] initWithFrame:inputAccessoryView.frame 310 [[FormInputAccessoryView alloc] initWithFrame:frame customView:view]);
265 delegate:self 311 UIView* keyboardView = [self getKeyboardView];
266 customView:view 312 DCHECK(keyboardView);
267 leftFrame:leftFrame 313 [keyboardView addSubview:_customAccessoryView];
268 rightFrame:rightFrame]); 314 } else {
269 [inputAccessoryView addSubview:_customAccessoryView]; 315 // On all other versions, the custom view replaces the default UI of the
316 // inputAccessoryView.
317 [self restoreDefaultInputAccessoryView];
318 CGRect leftFrame;
319 CGRect rightFrame;
320 UIView* inputAccessoryView = [self.webViewProxy getKeyboardAccessory];
321 if (ComputeFramesOfKeyboardParts(inputAccessoryView, &leftFrame,
322 &rightFrame)) {
323 [self hideSubviewsInOriginalAccessoryView:inputAccessoryView];
324 _customAccessoryView.reset([[FormInputAccessoryView alloc]
325 initWithFrame:inputAccessoryView.frame
326 delegate:self
327 customView:view
328 leftFrame:leftFrame
329 rightFrame:rightFrame]);
330 [inputAccessoryView addSubview:_customAccessoryView];
331 }
270 } 332 }
271 } 333 }
272 334
273 - (void)restoreDefaultInputAccessoryView { 335 - (void)restoreDefaultInputAccessoryView {
274 [_customAccessoryView removeFromSuperview]; 336 [_customAccessoryView removeFromSuperview];
275 _customAccessoryView.reset(); 337 _customAccessoryView.reset();
276 for (UIView* subview in _hiddenOriginalSubviews.get()) { 338 for (UIView* subview in _hiddenOriginalSubviews.get()) {
277 subview.hidden = NO; 339 subview.hidden = NO;
278 } 340 }
279 [_hiddenOriginalSubviews removeAllObjects]; 341 [_hiddenOriginalSubviews removeAllObjects];
(...skipping 194 matching lines...) Expand 10 before | Expand all | Expand 10 after
474 webState:webState 536 webState:webState
475 accessoryViewUpdateBlock:readyCompletion]; 537 accessoryViewUpdateBlock:readyCompletion];
476 }; 538 };
477 539
478 // Run all the blocks in |findProviderBlocks| until one invokes its 540 // Run all the blocks in |findProviderBlocks| until one invokes its
479 // completion with YES. The first one to do so will be passed to 541 // completion with YES. The first one to do so will be passed to
480 // |onProviderFound|. 542 // |onProviderFound|.
481 passwords::RunSearchPipeline(findProviderBlocks, onProviderFound); 543 passwords::RunSearchPipeline(findProviderBlocks, onProviderFound);
482 } 544 }
483 545
484 - (void)keyboardDidChangeFrame:(NSNotification*)notification { 546 - (UIView*)getKeyboardView {
547 NSArray* windows = [UIApplication sharedApplication].windows;
548 if (windows.count < 2)
549 return nil;
550
551 UIWindow* window = windows[1];
552 for (UIView* subview in window.subviews) {
553 if ([NSStringFromClass([subview class]) rangeOfString:@"PeripheralHost"]
554 .location != NSNotFound) {
555 return subview;
556 }
557 if ([NSStringFromClass([subview class]) rangeOfString:@"SetContainer"]
558 .location != NSNotFound) {
559 for (UIView* subsubview in subview.subviews) {
560 if ([NSStringFromClass([subsubview class]) rangeOfString:@"SetHost"]
561 .location != NSNotFound) {
562 return subsubview;
563 }
564 }
565 }
566 }
567
568 return nil;
569 }
570
571 - (void)keyboardWillOrDidChangeFrame:(NSNotification*)notification {
485 if (!self.webState || !_currentProvider) 572 if (!self.webState || !_currentProvider)
486 return; 573 return;
487 CGRect keyboardFrame = 574 CGRect keyboardFrame =
488 [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; 575 [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
489 // With iOS8 (beta) this method can be called even when the rect has not 576 // With iOS8 (beta) this method can be called even when the rect has not
490 // changed. When this is detected we exit early. 577 // changed. When this is detected we exit early.
491 if (CGRectEqualToRect(CGRectIntegral(_keyboardFrame), 578 if (CGRectEqualToRect(CGRectIntegral(_keyboardFrame),
492 CGRectIntegral(keyboardFrame))) { 579 CGRectIntegral(keyboardFrame))) {
493 return; 580 return;
494 } 581 }
495 _keyboardFrame = keyboardFrame; 582 _keyboardFrame = keyboardFrame;
496 [_currentProvider resizeAccessoryView]; 583 [_currentProvider resizeAccessoryView];
497 } 584 }
498 585
586 // On iPads running iOS 9 or later, when any text field or text view (e.g.
587 // omnibox, settings, card unmask dialog) begins editing, reset ourselves so
588 // that we don't present our custom view over the keyboard.
589 - (void)textInputDidBeginEditing:(NSNotification*)notification {
590 [self reset];
591 }
592
499 - (void)keyboardDidHide:(NSNotification*)notification { 593 - (void)keyboardDidHide:(NSNotification*)notification {
500 _keyboardFrame = CGRectZero; 594 _keyboardFrame = CGRectZero;
501 } 595 }
502 596
503 @end 597 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698