Chromium Code Reviews| OLD | NEW |
|---|---|
| 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" |
| 16 #import "ios/chrome/browser/passwords/password_generation_utils.h" | 15 #import "ios/chrome/browser/passwords/password_generation_utils.h" |
| 16 #include "ios/chrome/browser/ui/ui_util.h" | |
| 17 #include "ios/web/public/test/crw_test_js_injection_receiver.h" | 17 #include "ios/web/public/test/crw_test_js_injection_receiver.h" |
| 18 #include "ios/web/public/url_scheme_util.h" | 18 #include "ios/web/public/url_scheme_util.h" |
| 19 #import "ios/web/public/web_state/crw_web_view_proxy.h" | 19 #import "ios/web/public/web_state/crw_web_view_proxy.h" |
| 20 #include "ios/web/public/web_state/url_verification_constants.h" | 20 #include "ios/web/public/web_state/url_verification_constants.h" |
| 21 #include "ios/web/public/web_state/web_state.h" | 21 #include "ios/web/public/web_state/web_state.h" |
| 22 #include "url/gurl.h" | 22 #include "url/gurl.h" |
| 23 | 23 |
| 24 namespace autofill { | 24 namespace autofill { |
| 25 NSString* const kFormSuggestionAssistButtonPreviousElement = @"previousTap"; | 25 NSString* const kFormSuggestionAssistButtonPreviousElement = @"previousTap"; |
| 26 NSString* const kFormSuggestionAssistButtonNextElement = @"nextTap"; | 26 NSString* const kFormSuggestionAssistButtonNextElement = @"nextTap"; |
| 27 NSString* const kFormSuggestionAssistButtonDone = @"done"; | 27 NSString* const kFormSuggestionAssistButtonDone = @"done"; |
| 28 CGFloat const kInputAccessoryHeight = 44.0f; | |
| 28 } // namespace autofill | 29 } // namespace autofill |
| 29 | 30 |
| 30 namespace { | 31 namespace { |
| 31 | 32 |
| 32 // Finds all views of a particular kind if class |klass| in the subview | 33 // Finds all views of a particular kind if class |klass| in the subview |
| 33 // hierarchy of the given |root| view. | 34 // hierarchy of the given |root| view. |
| 34 NSArray* FindDescendantsOfClass(UIView* root, Class klass) { | 35 NSArray* FindDescendantsOfClass(UIView* root, Class klass) { |
| 35 DCHECK(root); | 36 DCHECK(root); |
| 36 NSMutableArray* viewsToExamine = [NSMutableArray arrayWithObject:root]; | 37 NSMutableArray* viewsToExamine = [NSMutableArray arrayWithObject:root]; |
| 37 NSMutableArray* descendants = [NSMutableArray array]; | 38 NSMutableArray* descendants = [NSMutableArray array]; |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 86 | 87 |
| 87 // Computes the frame of each part of the accessory view of the keyboard. It is | 88 // 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 | 89 // assumed that the keyboard has either two parts (when it is split) or one part |
| 89 // (when it is merged). | 90 // (when it is merged). |
| 90 // | 91 // |
| 91 // If there are two parts, the frame of the left part is returned in | 92 // 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|. | 93 // |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 | 94 // If there is only one part, the frame is returned in |leftFrame| and |
| 94 // |rightFrame| has size zero. | 95 // |rightFrame| has size zero. |
| 95 // | 96 // |
| 96 // Heuristics are used to compute this information. It returns true if the | 97 // Heuristics are used to compute this information. It returns false if the |
| 97 // number of |inputAccessoryView.subviews| is not 2. | 98 // number of |inputAccessoryView.subviews| is not 2. |
| 98 bool ComputeFramesOfKeyboardParts(UIView* inputAccessoryView, | 99 bool ComputeFramesOfKeyboardParts(UIView* inputAccessoryView, |
| 99 CGRect* leftFrame, | 100 CGRect* leftFrame, |
| 100 CGRect* rightFrame) { | 101 CGRect* rightFrame) { |
| 101 // It is observed (on iOS 6) there are always two subviews in the original | 102 // 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 | 103 // 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 | 104 // 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 | 105 // 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. | 106 // and the other has zero size with the screen width as origin.x. |
| 106 // The computation here is based on this observation. | 107 // The computation here is based on this observation. |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 123 | 124 |
| 124 } // namespace | 125 } // namespace |
| 125 | 126 |
| 126 @interface FormInputAccessoryViewController () | 127 @interface FormInputAccessoryViewController () |
| 127 | 128 |
| 128 // Allows injection of the JsSuggestionManager. | 129 // Allows injection of the JsSuggestionManager. |
| 129 - (instancetype)initWithWebState:(web::WebState*)webState | 130 - (instancetype)initWithWebState:(web::WebState*)webState |
| 130 JSSuggestionManager:(JsSuggestionManager*)JSSuggestionManager | 131 JSSuggestionManager:(JsSuggestionManager*)JSSuggestionManager |
| 131 providers:(NSArray*)providers; | 132 providers:(NSArray*)providers; |
| 132 | 133 |
| 133 // Called when the keyboard did change frame. | 134 // Called when the keyboard will or did change frame. |
| 134 - (void)keyboardDidChangeFrame:(NSNotification*)notification; | 135 - (void)keyboardWillOrDidChangeFrame:(NSNotification*)notification; |
| 135 | 136 |
| 136 // Called when the keyboard is dismissed. | 137 // Called when the keyboard is dismissed. |
| 137 - (void)keyboardDidHide:(NSNotification*)notification; | 138 - (void)keyboardDidHide:(NSNotification*)notification; |
| 138 | 139 |
| 139 // Hides the subviews in |accessoryView|. | 140 // Hides the subviews in |accessoryView|. |
| 140 - (void)hideSubviewsInOriginalAccessoryView:(UIView*)accessoryView; | 141 - (void)hideSubviewsInOriginalAccessoryView:(UIView*)accessoryView; |
| 141 | 142 |
| 142 // Attempts to execute/tap/send-an-event-to the iOS built-in "next" and | 143 // 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 | 144 // "previous" form assist controls. Returns NO if this attempt failed, YES |
| 144 // otherwise. [HACK] | 145 // otherwise. [HACK] |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 208 self = [super init]; | 209 self = [super init]; |
| 209 if (self) { | 210 if (self) { |
| 210 _JSSuggestionManager.reset([JSSuggestionManager retain]); | 211 _JSSuggestionManager.reset([JSSuggestionManager retain]); |
| 211 _hiddenOriginalSubviews.reset([[NSMutableArray alloc] init]); | 212 _hiddenOriginalSubviews.reset([[NSMutableArray alloc] init]); |
| 212 _webStateObserverBridge.reset( | 213 _webStateObserverBridge.reset( |
| 213 new web::WebStateObserverBridge(webState, self)); | 214 new web::WebStateObserverBridge(webState, self)); |
| 214 _providers.reset([providers copy]); | 215 _providers.reset([providers copy]); |
| 215 // There is no defined relation on the timing of JavaScript events and | 216 // There is no defined relation on the timing of JavaScript events and |
| 216 // keyboard showing up. So it is necessary to listen to the keyboard | 217 // keyboard showing up. So it is necessary to listen to the keyboard |
| 217 // notification to make sure the keyboard is updated. | 218 // notification to make sure the keyboard is updated. |
| 219 if (base::ios::IsRunningOnIOS9OrLater() && IsIPadIdiom()) { | |
| 220 [[NSNotificationCenter defaultCenter] | |
| 221 addObserver:self | |
| 222 selector:@selector(keyboardWillOrDidChangeFrame:) | |
| 223 name:UIKeyboardWillChangeFrameNotification | |
| 224 object:nil]; | |
| 225 } | |
| 218 [[NSNotificationCenter defaultCenter] | 226 [[NSNotificationCenter defaultCenter] |
| 219 addObserver:self | 227 addObserver:self |
| 220 selector:@selector(keyboardDidChangeFrame:) | 228 selector:@selector(keyboardWillOrDidChangeFrame:) |
| 221 name:UIKeyboardDidChangeFrameNotification | 229 name:UIKeyboardDidChangeFrameNotification |
| 222 object:nil]; | 230 object:nil]; |
| 223 [[NSNotificationCenter defaultCenter] | 231 [[NSNotificationCenter defaultCenter] |
| 224 addObserver:self | 232 addObserver:self |
| 225 selector:@selector(keyboardDidHide:) | 233 selector:@selector(keyboardDidHide:) |
| 226 name:UIKeyboardDidHideNotification | 234 name:UIKeyboardDidHideNotification |
| 227 object:nil]; | 235 object:nil]; |
| 228 } | 236 } |
| 229 return self; | 237 return self; |
| 230 } | 238 } |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 246 - (void)hideSubviewsInOriginalAccessoryView:(UIView*)accessoryView { | 254 - (void)hideSubviewsInOriginalAccessoryView:(UIView*)accessoryView { |
| 247 for (UIView* subview in [accessoryView subviews]) { | 255 for (UIView* subview in [accessoryView subviews]) { |
| 248 if (!subview.hidden) { | 256 if (!subview.hidden) { |
| 249 [_hiddenOriginalSubviews addObject:subview]; | 257 [_hiddenOriginalSubviews addObject:subview]; |
| 250 subview.hidden = YES; | 258 subview.hidden = YES; |
| 251 } | 259 } |
| 252 } | 260 } |
| 253 } | 261 } |
| 254 | 262 |
| 255 - (void)showCustomInputAccessoryView:(UIView*)view { | 263 - (void)showCustomInputAccessoryView:(UIView*)view { |
| 256 [self restoreDefaultInputAccessoryView]; | 264 if (base::ios::IsRunningOnIOS9OrLater() && IsIPadIdiom()) { |
| 257 CGRect leftFrame; | 265 // On iPads running iOS 9 or later, there's no inputAccessoryView available |
| 258 CGRect rightFrame; | 266 // so we attach the custom view directly to the keyboard view instead. |
| 259 UIView* inputAccessoryView = [self.webViewProxy getKeyboardAccessory]; | 267 [_customAccessoryView removeFromSuperview]; |
| 260 if (ComputeFramesOfKeyboardParts(inputAccessoryView, &leftFrame, | 268 UIViewController* rootViewController = |
| 261 &rightFrame)) { | 269 [UIApplication sharedApplication].keyWindow.rootViewController; |
| 262 [self hideSubviewsInOriginalAccessoryView:inputAccessoryView]; | 270 |
| 271 // If the keyboard isn't visible or another controller has been presented | |
| 272 // over the webview (as in the case of the credit card unmask dialog), don't | |
| 273 // show the custom view. | |
| 274 if (CGRectIntersection([UIScreen mainScreen].bounds, _keyboardFrame) | |
| 275 .size.height == 0 || | |
| 276 CGRectEqualToRect(_keyboardFrame, CGRectZero) || | |
| 277 [rootViewController presentedViewController]) { | |
| 278 _customAccessoryView.reset(); | |
| 279 return; | |
| 280 } | |
| 281 | |
| 282 CGFloat height = autofill::kInputAccessoryHeight; | |
| 283 CGRect frame = CGRectMake(_keyboardFrame.origin.x, -height, | |
| 284 _keyboardFrame.size.width, height); | |
| 263 _customAccessoryView.reset( | 285 _customAccessoryView.reset( |
| 264 [[FormInputAccessoryView alloc] initWithFrame:inputAccessoryView.frame | 286 [[FormInputAccessoryView alloc] initWithFrame:frame customView:view]); |
| 265 delegate:self | 287 [[self getKeyboardView] addSubview:_customAccessoryView]; |
|
justincohen
2015/08/19 20:34:19
Is there a way we can DCHECK if something changes
Justin Donnelly
2015/08/20 16:38:01
Done.
| |
| 266 customView:view | 288 } else { |
| 267 leftFrame:leftFrame | 289 // On all other versions, the custom view replaces the default UI of the |
| 268 rightFrame:rightFrame]); | 290 // inputAccessoryView. |
| 269 [inputAccessoryView addSubview:_customAccessoryView]; | 291 [self restoreDefaultInputAccessoryView]; |
| 292 CGRect leftFrame; | |
| 293 CGRect rightFrame; | |
| 294 UIView* inputAccessoryView = [self.webViewProxy getKeyboardAccessory]; | |
| 295 if (ComputeFramesOfKeyboardParts(inputAccessoryView, &leftFrame, | |
| 296 &rightFrame)) { | |
| 297 [self hideSubviewsInOriginalAccessoryView:inputAccessoryView]; | |
| 298 _customAccessoryView.reset([[FormInputAccessoryView alloc] | |
| 299 initWithFrame:inputAccessoryView.frame | |
| 300 delegate:self | |
| 301 customView:view | |
| 302 leftFrame:leftFrame | |
| 303 rightFrame:rightFrame]); | |
| 304 [inputAccessoryView addSubview:_customAccessoryView]; | |
| 305 } | |
| 270 } | 306 } |
| 271 } | 307 } |
| 272 | 308 |
| 273 - (void)restoreDefaultInputAccessoryView { | 309 - (void)restoreDefaultInputAccessoryView { |
| 274 [_customAccessoryView removeFromSuperview]; | 310 [_customAccessoryView removeFromSuperview]; |
| 275 _customAccessoryView.reset(); | 311 _customAccessoryView.reset(); |
| 276 for (UIView* subview in _hiddenOriginalSubviews.get()) { | 312 for (UIView* subview in _hiddenOriginalSubviews.get()) { |
| 277 subview.hidden = NO; | 313 subview.hidden = NO; |
| 278 } | 314 } |
| 279 [_hiddenOriginalSubviews removeAllObjects]; | 315 [_hiddenOriginalSubviews removeAllObjects]; |
| (...skipping 194 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 474 webState:webState | 510 webState:webState |
| 475 accessoryViewUpdateBlock:readyCompletion]; | 511 accessoryViewUpdateBlock:readyCompletion]; |
| 476 }; | 512 }; |
| 477 | 513 |
| 478 // Run all the blocks in |findProviderBlocks| until one invokes its | 514 // Run all the blocks in |findProviderBlocks| until one invokes its |
| 479 // completion with YES. The first one to do so will be passed to | 515 // completion with YES. The first one to do so will be passed to |
| 480 // |onProviderFound|. | 516 // |onProviderFound|. |
| 481 passwords::RunSearchPipeline(findProviderBlocks, onProviderFound); | 517 passwords::RunSearchPipeline(findProviderBlocks, onProviderFound); |
| 482 } | 518 } |
| 483 | 519 |
| 484 - (void)keyboardDidChangeFrame:(NSNotification*)notification { | 520 - (UIView*)getKeyboardView { |
| 521 NSArray* windows = [UIApplication sharedApplication].windows; | |
| 522 if (windows.count < 2) | |
| 523 return nil; | |
| 524 | |
| 525 UIWindow* window = windows[1]; | |
| 526 for (UIView* subview in window.subviews) { | |
| 527 if ([NSStringFromClass([subview class]) | |
| 528 isEqualToString:@"UIPeripheralHost"]) { | |
| 529 return subview; | |
| 530 } | |
| 531 if ([NSStringFromClass([subview class]) | |
| 532 isEqualToString:@"UIInputSetContainerView"]) { | |
| 533 for (UIView* subsubview in subview.subviews) { | |
| 534 if ([NSStringFromClass([subsubview class]) | |
| 535 isEqualToString:@"UIInputSetHostView"]) { | |
| 536 return subsubview; | |
| 537 } | |
| 538 } | |
| 539 } | |
| 540 } | |
| 541 | |
| 542 return nil; | |
| 543 } | |
| 544 | |
| 545 - (void)keyboardWillOrDidChangeFrame:(NSNotification*)notification { | |
| 485 if (!self.webState || !_currentProvider) | 546 if (!self.webState || !_currentProvider) |
| 486 return; | 547 return; |
| 487 CGRect keyboardFrame = | 548 CGRect keyboardFrame = |
| 488 [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; | 549 [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; |
| 489 // With iOS8 (beta) this method can be called even when the rect has not | 550 // With iOS8 (beta) this method can be called even when the rect has not |
| 490 // changed. When this is detected we exit early. | 551 // changed. When this is detected we exit early. |
| 491 if (CGRectEqualToRect(CGRectIntegral(_keyboardFrame), | 552 if (CGRectEqualToRect(CGRectIntegral(_keyboardFrame), |
| 492 CGRectIntegral(keyboardFrame))) { | 553 CGRectIntegral(keyboardFrame))) { |
| 493 return; | 554 return; |
| 494 } | 555 } |
| 495 _keyboardFrame = keyboardFrame; | 556 _keyboardFrame = keyboardFrame; |
| 496 [_currentProvider resizeAccessoryView]; | 557 [_currentProvider resizeAccessoryView]; |
| 497 } | 558 } |
| 498 | 559 |
| 499 - (void)keyboardDidHide:(NSNotification*)notification { | 560 - (void)keyboardDidHide:(NSNotification*)notification { |
| 500 _keyboardFrame = CGRectZero; | 561 _keyboardFrame = CGRectZero; |
| 501 } | 562 } |
| 502 | 563 |
| 503 @end | 564 @end |
| OLD | NEW |