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

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: Don't show view until suggestions available, unlisten when switching tabs, DCHECK keyboard view 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 }
236 [[NSNotificationCenter defaultCenter]
237 addObserver:self
238 selector:@selector(keyboardWillOrDidChangeFrame:)
239 name:UIKeyboardDidChangeFrameNotification
240 object:nil];
241 [[NSNotificationCenter defaultCenter]
242 addObserver:self
243 selector:@selector(keyboardDidHide:)
244 name:UIKeyboardDidHideNotification
245 object:nil];
246 }
247
248 - (void)wasHidden {
249 [_customAccessoryView removeFromSuperview];
250 [[NSNotificationCenter defaultCenter] removeObserver:self];
251 }
252
232 - (void)dealloc { 253 - (void)dealloc {
233 [[NSNotificationCenter defaultCenter] removeObserver:self]; 254 [[NSNotificationCenter defaultCenter] removeObserver:self];
234 [super dealloc]; 255 [super dealloc];
235 } 256 }
236 257
237 - (web::WebState*)webState { 258 - (web::WebState*)webState {
238 return _webStateObserverBridge ? _webStateObserverBridge->web_state() 259 return _webStateObserverBridge ? _webStateObserverBridge->web_state()
239 : nullptr; 260 : nullptr;
240 } 261 }
241 262
242 - (id<CRWWebViewProxy>)webViewProxy { 263 - (id<CRWWebViewProxy>)webViewProxy {
243 return self.webState ? self.webState->GetWebViewProxy() : nil; 264 return self.webState ? self.webState->GetWebViewProxy() : nil;
244 } 265 }
245 266
246 - (void)hideSubviewsInOriginalAccessoryView:(UIView*)accessoryView { 267 - (void)hideSubviewsInOriginalAccessoryView:(UIView*)accessoryView {
247 for (UIView* subview in [accessoryView subviews]) { 268 for (UIView* subview in [accessoryView subviews]) {
248 if (!subview.hidden) { 269 if (!subview.hidden) {
249 [_hiddenOriginalSubviews addObject:subview]; 270 [_hiddenOriginalSubviews addObject:subview];
250 subview.hidden = YES; 271 subview.hidden = YES;
251 } 272 }
252 } 273 }
253 } 274 }
254 275
255 - (void)showCustomInputAccessoryView:(UIView*)view { 276 - (void)showCustomInputAccessoryView:(UIView*)view {
justincohen 2015/08/20 17:29:16 DCHECK(view)
Justin Donnelly 2015/08/20 20:24:28 Done.
256 [self restoreDefaultInputAccessoryView]; 277 if (base::ios::IsRunningOnIOS9OrLater() && IsIPadIdiom()) {
257 CGRect leftFrame; 278 // On iPads running iOS 9 or later, there's no inputAccessoryView available
258 CGRect rightFrame; 279 // so we attach the custom view directly to the keyboard view instead.
259 UIView* inputAccessoryView = [self.webViewProxy getKeyboardAccessory]; 280 [_customAccessoryView removeFromSuperview];
260 if (ComputeFramesOfKeyboardParts(inputAccessoryView, &leftFrame, 281 UIViewController* rootViewController =
261 &rightFrame)) { 282 [UIApplication sharedApplication].keyWindow.rootViewController;
262 [self hideSubviewsInOriginalAccessoryView:inputAccessoryView]; 283
284 // If the keyboard isn't visible, another controller has been presented over
285 // the webview (as in the case of the credit card unmask dialog), or no
286 // suggestions have been trigger yet, don't show the custom view.
justincohen 2015/08/20 17:29:16 triggered
Justin Donnelly 2015/08/20 20:24:28 Done.
287 int numSuggestions =
288 [[static_cast<FormSuggestionView*>(view) suggestions] count];
289 if (CGRectIntersection([UIScreen mainScreen].bounds, _keyboardFrame)
290 .size.height == 0 ||
291 CGRectEqualToRect(_keyboardFrame, CGRectZero) ||
292 [rootViewController presentedViewController] ||
293 (!_suggestionsHaveBeenShown && numSuggestions == 0)) {
294 _customAccessoryView.reset();
295 return;
296 }
297 _suggestionsHaveBeenShown = YES;
298
299 CGFloat height = autofill::kInputAccessoryHeight;
300 CGRect frame = CGRectMake(_keyboardFrame.origin.x, -height,
301 _keyboardFrame.size.width, height);
263 _customAccessoryView.reset( 302 _customAccessoryView.reset(
264 [[FormInputAccessoryView alloc] initWithFrame:inputAccessoryView.frame 303 [[FormInputAccessoryView alloc] initWithFrame:frame customView:view]);
265 delegate:self 304 UIView* keyboardView = [self getKeyboardView];
266 customView:view 305 DCHECK(keyboardView);
justincohen 2015/08/20 17:29:16 I'm curious what keyboardView is if you have a phy
Justin Donnelly 2015/08/20 20:24:28 I actually do most of my testing in the simulator
267 leftFrame:leftFrame 306 [keyboardView addSubview:_customAccessoryView];
268 rightFrame:rightFrame]); 307 } else {
269 [inputAccessoryView addSubview:_customAccessoryView]; 308 // On all other versions, the custom view replaces the default UI of the
309 // inputAccessoryView.
310 [self restoreDefaultInputAccessoryView];
311 CGRect leftFrame;
312 CGRect rightFrame;
313 UIView* inputAccessoryView = [self.webViewProxy getKeyboardAccessory];
314 if (ComputeFramesOfKeyboardParts(inputAccessoryView, &leftFrame,
315 &rightFrame)) {
316 [self hideSubviewsInOriginalAccessoryView:inputAccessoryView];
317 _customAccessoryView.reset([[FormInputAccessoryView alloc]
318 initWithFrame:inputAccessoryView.frame
319 delegate:self
320 customView:view
321 leftFrame:leftFrame
322 rightFrame:rightFrame]);
323 [inputAccessoryView addSubview:_customAccessoryView];
324 }
270 } 325 }
271 } 326 }
272 327
273 - (void)restoreDefaultInputAccessoryView { 328 - (void)restoreDefaultInputAccessoryView {
274 [_customAccessoryView removeFromSuperview]; 329 [_customAccessoryView removeFromSuperview];
275 _customAccessoryView.reset(); 330 _customAccessoryView.reset();
276 for (UIView* subview in _hiddenOriginalSubviews.get()) { 331 for (UIView* subview in _hiddenOriginalSubviews.get()) {
277 subview.hidden = NO; 332 subview.hidden = NO;
278 } 333 }
279 [_hiddenOriginalSubviews removeAllObjects]; 334 [_hiddenOriginalSubviews removeAllObjects];
(...skipping 194 matching lines...) Expand 10 before | Expand all | Expand 10 after
474 webState:webState 529 webState:webState
475 accessoryViewUpdateBlock:readyCompletion]; 530 accessoryViewUpdateBlock:readyCompletion];
476 }; 531 };
477 532
478 // Run all the blocks in |findProviderBlocks| until one invokes its 533 // Run all the blocks in |findProviderBlocks| until one invokes its
479 // completion with YES. The first one to do so will be passed to 534 // completion with YES. The first one to do so will be passed to
480 // |onProviderFound|. 535 // |onProviderFound|.
481 passwords::RunSearchPipeline(findProviderBlocks, onProviderFound); 536 passwords::RunSearchPipeline(findProviderBlocks, onProviderFound);
482 } 537 }
483 538
484 - (void)keyboardDidChangeFrame:(NSNotification*)notification { 539 - (UIView*)getKeyboardView {
540 NSArray* windows = [UIApplication sharedApplication].windows;
541 if (windows.count < 2)
542 return nil;
543
544 UIWindow* window = windows[1];
545 for (UIView* subview in window.subviews) {
546 if ([NSStringFromClass([subview class])
547 isEqualToString:@"UIPeripheralHost"]) {
548 return subview;
549 }
550 if ([NSStringFromClass([subview class])
551 isEqualToString:@"UIInputSetContainerView"]) {
552 for (UIView* subsubview in subview.subviews) {
553 if ([NSStringFromClass([subsubview class])
554 isEqualToString:@"UIInputSetHostView"]) {
555 return subsubview;
556 }
557 }
558 }
559 }
560
561 return nil;
562 }
563
564 - (void)keyboardWillOrDidChangeFrame:(NSNotification*)notification {
485 if (!self.webState || !_currentProvider) 565 if (!self.webState || !_currentProvider)
486 return; 566 return;
487 CGRect keyboardFrame = 567 CGRect keyboardFrame =
488 [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; 568 [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
489 // With iOS8 (beta) this method can be called even when the rect has not 569 // With iOS8 (beta) this method can be called even when the rect has not
490 // changed. When this is detected we exit early. 570 // changed. When this is detected we exit early.
491 if (CGRectEqualToRect(CGRectIntegral(_keyboardFrame), 571 if (CGRectEqualToRect(CGRectIntegral(_keyboardFrame),
492 CGRectIntegral(keyboardFrame))) { 572 CGRectIntegral(keyboardFrame))) {
493 return; 573 return;
494 } 574 }
495 _keyboardFrame = keyboardFrame; 575 _keyboardFrame = keyboardFrame;
496 [_currentProvider resizeAccessoryView]; 576 [_currentProvider resizeAccessoryView];
497 } 577 }
498 578
499 - (void)keyboardDidHide:(NSNotification*)notification { 579 - (void)keyboardDidHide:(NSNotification*)notification {
500 _keyboardFrame = CGRectZero; 580 _keyboardFrame = CGRectZero;
501 } 581 }
502 582
503 @end 583 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698