OLD | NEW |
(Empty) | |
| 1 // Copyright 2012 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. |
| 4 |
| 5 #import "ios/chrome/browser/ui/toolbar/web_toolbar_controller.h" |
| 6 |
| 7 #import <CoreLocation/CoreLocation.h> |
| 8 #include <QuartzCore/QuartzCore.h> |
| 9 |
| 10 #include <stdint.h> |
| 11 |
| 12 #include <algorithm> |
| 13 #include <memory> |
| 14 |
| 15 #include "base/command_line.h" |
| 16 #include "base/ios/weak_nsobject.h" |
| 17 #include "base/logging.h" |
| 18 #include "base/mac/bundle_locations.h" |
| 19 #include "base/mac/foundation_util.h" |
| 20 #include "base/metrics/histogram.h" |
| 21 #include "base/metrics/user_metrics.h" |
| 22 #include "base/metrics/user_metrics_action.h" |
| 23 #include "base/strings/sys_string_conversions.h" |
| 24 #include "components/google/core/browser/google_util.h" |
| 25 #include "components/omnibox/browser/omnibox_edit_model.h" |
| 26 #include "components/reading_list/core/reading_list_switches.h" |
| 27 #include "components/search_engines/util.h" |
| 28 #include "components/strings/grit/components_strings.h" |
| 29 #include "components/toolbar/toolbar_model.h" |
| 30 #include "ios/chrome/browser/autocomplete/autocomplete_scheme_classifier_impl.h" |
| 31 #include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| 32 #include "ios/chrome/browser/chrome_url_constants.h" |
| 33 #include "ios/chrome/browser/reading_list/reading_list_model_factory.h" |
| 34 #include "ios/chrome/browser/search_engines/template_url_service_factory.h" |
| 35 #import "ios/chrome/browser/tabs/tab.h" |
| 36 #import "ios/chrome/browser/tabs/tab_model.h" |
| 37 #import "ios/chrome/browser/ui/animation_util.h" |
| 38 #import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h" |
| 39 #import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h" |
| 40 #import "ios/chrome/browser/ui/commands/generic_chrome_command.h" |
| 41 #include "ios/chrome/browser/ui/commands/ios_command_ids.h" |
| 42 #import "ios/chrome/browser/ui/history/tab_history_popup_controller.h" |
| 43 #import "ios/chrome/browser/ui/image_util.h" |
| 44 #import "ios/chrome/browser/ui/keyboard/hardware_keyboard_watcher.h" |
| 45 #include "ios/chrome/browser/ui/omnibox/omnibox_view_ios.h" |
| 46 #import "ios/chrome/browser/ui/reversed_animation.h" |
| 47 #include "ios/chrome/browser/ui/rtl_geometry.h" |
| 48 #import "ios/chrome/browser/ui/toolbar/toolbar_controller+protected.h" |
| 49 #import "ios/chrome/browser/ui/toolbar/toolbar_controller.h" |
| 50 #import "ios/chrome/browser/ui/toolbar/toolbar_model_ios.h" |
| 51 #include "ios/chrome/browser/ui/toolbar/toolbar_resource_macros.h" |
| 52 #include "ios/chrome/browser/ui/ui_util.h" |
| 53 #import "ios/chrome/browser/ui/uikit_ui_util.h" |
| 54 #import "ios/chrome/browser/ui/url_loader.h" |
| 55 #import "ios/chrome/browser/ui/voice/text_to_speech_player.h" |
| 56 #import "ios/chrome/browser/ui/voice/voice_search_notification_names.h" |
| 57 #import "ios/chrome/common/material_timing.h" |
| 58 #include "ios/chrome/grit/ios_strings.h" |
| 59 #import "ios/public/provider/chrome/browser/chrome_browser_provider.h" |
| 60 #import "ios/public/provider/chrome/browser/images/branded_image_provider.h" |
| 61 #import "ios/public/provider/chrome/browser/voice/voice_search_provider.h" |
| 62 #import "ios/third_party/material_components_ios/src/components/Palettes/src/Mat
erialPalettes.h" |
| 63 #import "ios/third_party/material_components_ios/src/components/ProgressView/src
/MaterialProgressView.h" |
| 64 #import "ios/third_party/material_components_ios/src/components/Typography/src/M
aterialTypography.h" |
| 65 #include "ios/web/public/referrer.h" |
| 66 #import "ios/web/public/web_state/web_state.h" |
| 67 #import "net/base/mac/url_conversions.h" |
| 68 #include "ui/base/l10n/l10n_util.h" |
| 69 #include "ui/base/page_transition_types.h" |
| 70 #include "ui/base/resource/resource_bundle.h" |
| 71 #import "ui/gfx/ios/NSString+CrStringDrawing.h" |
| 72 |
| 73 using base::UserMetricsAction; |
| 74 using ios::material::TimingFunction; |
| 75 |
| 76 NSString* const kTabHistoryPopupWillShowNotification = |
| 77 @"kTabHistoryPopupWillShowNotification"; |
| 78 NSString* const kTabHistoryPopupWillHideNotification = |
| 79 @"kTabHistoryPopupWillHideNotification"; |
| 80 const CGFloat kiPhoneOmniboxPlaceholderColorBrightness = 150 / 255.0; |
| 81 |
| 82 // The histogram recording CLAuthorizationStatus for omnibox queries. |
| 83 const char* const kOmniboxQueryLocationAuthorizationStatusHistogram = |
| 84 "Omnibox.QueryIosLocationAuthorizationStatus"; |
| 85 // The number of possible CLAuthorizationStatus values to report. |
| 86 const int kLocationAuthorizationStatusCount = 4; |
| 87 |
| 88 namespace { |
| 89 |
| 90 // The brightness of the toolbar's background color (visible on NTPs when the |
| 91 // background view is hidden). |
| 92 const CGFloat kNTPBackgroundColorBrightness = 1.0; |
| 93 const CGFloat kNTPBackgroundColorBrightnessIncognito = 34.0 / 255.0; |
| 94 |
| 95 // How far below the omnibox to position the popup. |
| 96 const CGFloat kiPadOmniboxPopupVerticalOffset = 3; |
| 97 |
| 98 // Padding to place on the sides of the omnibox when expanded. |
| 99 const CGFloat kExpandedOmniboxPadding = 6; |
| 100 |
| 101 // Padding between the back button and the omnibox when the forward button isn't |
| 102 // displayed. |
| 103 const CGFloat kBackButtonTrailingPadding = 7.0; |
| 104 const CGFloat kForwardButtonTrailingPadding = -1.0; |
| 105 const CGFloat kReloadButtonTrailingPadding = 4.0; |
| 106 |
| 107 // Cancel button sizing. |
| 108 const CGFloat kCancelButtonBottomMargin = 4.0; |
| 109 const CGFloat kCancelButtonTopMargin = 4.0; |
| 110 const CGFloat kCancelButtonLeadingMargin = 7.0; |
| 111 const CGFloat kCancelButtonWidth = 40.0; |
| 112 const CGFloat kIpadButtonTitleFontSize = 20.0; |
| 113 const CGFloat kIphoneButtonTitleFontSize = 15.0; |
| 114 |
| 115 // Additional offset to adjust the y coordinate of the determinate progress bar |
| 116 // up by. |
| 117 const CGFloat kDeterminateProgressBarYOffset = 1.0; |
| 118 const CGFloat kMaterialProgressBarHeight = 2.0; |
| 119 const CGFloat kLoadCompleteHideProgressBarDelay = 0.5; |
| 120 // The default position animation is 10 pixels toward the trailing side, so |
| 121 // that's a negative leading offset. |
| 122 const LayoutOffset kPositionAnimationLeadingOffset = -10.0; |
| 123 |
| 124 const CGFloat kIPadToolbarY = 53; |
| 125 const CGFloat kScrollFadeDistance = 30; |
| 126 // Offset from the image edge to the beginning of the visible omnibox rectangle. |
| 127 // The image is symmetrical, so the offset is equal on each side. |
| 128 const CGFloat kBackgroundImageVisibleRectOffset = 6; |
| 129 |
| 130 enum { |
| 131 WebToolbarButtonNameBack = NumberOfToolbarButtonNames, |
| 132 WebToolbarButtonNameCallingApp, |
| 133 WebToolbarButtonNameForward, |
| 134 WebToolbarButtonNameReload, |
| 135 WebToolbarButtonNameStar, |
| 136 WebToolbarButtonNameStop, |
| 137 WebToolbarButtonNameTTS, |
| 138 WebToolbarButtonNameVoice, |
| 139 NumberOfWebToolbarButtonNames, |
| 140 }; |
| 141 |
| 142 const CGFloat kWebToolbarWidths[INTERFACE_IDIOM_COUNT] = {224, 720}; |
| 143 // UI layouts. iPhone values followed by iPad values. |
| 144 // Frame for the WebToolbar-specific container, which is a subview of the |
| 145 // toolbar view itself. |
| 146 // clang-format off |
| 147 const LayoutRect kWebToolbarFrame[INTERFACE_IDIOM_COUNT] = { |
| 148 {kPortraitWidth[IPHONE_IDIOM], LayoutRectPositionZero, |
| 149 {kWebToolbarWidths[IPHONE_IDIOM], 56}}, |
| 150 {kPortraitWidth[IPAD_IDIOM], LayoutRectPositionZero, |
| 151 {kWebToolbarWidths[IPAD_IDIOM], 56}}, |
| 152 }; |
| 153 |
| 154 #define IPHONE_LAYOUT(L, Y, H, W) \ |
| 155 { kWebToolbarWidths[IPHONE_IDIOM], {L, Y}, {H, W} } |
| 156 #define IPAD_LAYOUT(L, Y, H, W) \ |
| 157 { kWebToolbarWidths[IPAD_IDIOM], {L, Y}, {H, W} } |
| 158 |
| 159 // Layouts inside the WebToolbar frame |
| 160 const LayoutRect kOmniboxFrame[INTERFACE_IDIOM_COUNT] = { |
| 161 IPHONE_LAYOUT(55, 7, 169, 43), IPAD_LAYOUT(152, 7, 568, 43), |
| 162 }; |
| 163 const LayoutRect kBackButtonFrame[INTERFACE_IDIOM_COUNT] = { |
| 164 IPHONE_LAYOUT(0, 4, 48, 48), IPAD_LAYOUT(4, 4, 48, 48), |
| 165 }; |
| 166 const LayoutRect kForwardButtonFrame[INTERFACE_IDIOM_COUNT] = { |
| 167 IPHONE_LAYOUT(48, 4, 48, 48), IPAD_LAYOUT(52, 4, 48, 48), |
| 168 }; |
| 169 // clang-format on |
| 170 |
| 171 // iPad-only layouts |
| 172 // Layout for both the stop and reload buttons, which are in the same location. |
| 173 const LayoutRect kStopReloadButtonFrame = IPAD_LAYOUT(100, 4, 48, 48); |
| 174 const LayoutRect kStarButtonFrame = IPAD_LAYOUT(644, 4, 36, 48); |
| 175 const LayoutRect kVoiceSearchButtonFrame = IPAD_LAYOUT(680, 4, 36, 48); |
| 176 // Vertical distance between the y-coordinate of the omnibox's center and the |
| 177 // y-coordinate of the web toolbar's center. |
| 178 const CGFloat kOmniboxCenterOffsetY = 0.5; |
| 179 |
| 180 // Last button in accessory view for keyboard, commonly used TLD. |
| 181 NSString* const kDotComTLD = @".com"; |
| 182 |
| 183 void RunBlockAfterDelay(NSTimeInterval delay, void (^block)(void)) { |
| 184 DCHECK([NSThread isMainThread]); |
| 185 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * delay), |
| 186 dispatch_get_main_queue(), block); |
| 187 } |
| 188 |
| 189 CGRect RectShiftedDownForStatusBar(CGRect rect) { |
| 190 if (IsIPadIdiom()) |
| 191 return rect; |
| 192 rect.origin.y += StatusBarHeight(); |
| 193 return rect; |
| 194 } |
| 195 |
| 196 CGRect RectShiftedUpForStatusBar(CGRect rect) { |
| 197 if (IsIPadIdiom()) |
| 198 return rect; |
| 199 rect.origin.y -= StatusBarHeight(); |
| 200 return rect; |
| 201 } |
| 202 |
| 203 CGRect RectShiftedUpAndResizedForStatusBar(CGRect rect) { |
| 204 if (IsIPadIdiom()) |
| 205 return rect; |
| 206 rect.size.height += StatusBarHeight(); |
| 207 return RectShiftedUpForStatusBar(rect); |
| 208 } |
| 209 |
| 210 CGRect RectShiftedDownAndResizedForStatusBar(CGRect rect) { |
| 211 if (IsIPadIdiom()) |
| 212 return rect; |
| 213 rect.size.height -= StatusBarHeight(); |
| 214 return RectShiftedDownForStatusBar(rect); |
| 215 } |
| 216 |
| 217 } // namespace |
| 218 |
| 219 // View for the accessory view above the keyboard. Subclassed to allow playing |
| 220 // input clicks when pressed. |
| 221 @interface KeyboardAccessoryView : UIInputView<UIInputViewAudioFeedback> |
| 222 @end |
| 223 |
| 224 @implementation KeyboardAccessoryView |
| 225 |
| 226 - (BOOL)enableInputClicksWhenVisible { |
| 227 return YES; |
| 228 } |
| 229 |
| 230 @end |
| 231 |
| 232 // TODO(crbug.com/619982) Remove this block and add CAAnimationDelegate when we |
| 233 // switch the main bots to Xcode 8. |
| 234 #if defined(__IPHONE_10_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0) |
| 235 @interface WebToolbarController ()<CAAnimationDelegate> |
| 236 @end |
| 237 #endif |
| 238 |
| 239 @interface WebToolbarController ()<LocationBarDelegate, |
| 240 OmniboxPopupPositioner, |
| 241 ToolbarFrameDelegate> { |
| 242 // Top-level view for web content. |
| 243 base::scoped_nsobject<UIView> _webToolbar; |
| 244 base::scoped_nsobject<UIButton> _backButton; |
| 245 base::scoped_nsobject<UIButton> _forwardButton; |
| 246 base::scoped_nsobject<UIButton> _reloadButton; |
| 247 base::scoped_nsobject<UIButton> _stopButton; |
| 248 base::scoped_nsobject<UIButton> _starButton; |
| 249 base::scoped_nsobject<UIButton> _voiceSearchButton; |
| 250 base::scoped_nsobject<OmniboxTextFieldIOS> _omniBox; |
| 251 base::scoped_nsobject<UIButton> _cancelButton; |
| 252 base::scoped_nsobject<UIView> _keyBoardAccessoryView; |
| 253 base::scoped_nsobject<UIButton> _keyboardVoiceSearchButton; |
| 254 // Progress bar used to show what fraction of the page has loaded. |
| 255 base::scoped_nsobject<MDCProgressView> _determinateProgressView; |
| 256 base::scoped_nsobject<UIImageView> _omniboxBackground; |
| 257 BOOL _prerenderAnimating; |
| 258 base::scoped_nsobject<UIImageView> _incognitoIcon; |
| 259 base::scoped_nsobject<UIView> _clippingView; |
| 260 |
| 261 std::unique_ptr<LocationBarViewIOS> _locationBar; |
| 262 BOOL _initialLayoutComplete; |
| 263 // If |YES|, toolbar is incognito. |
| 264 BOOL _incognito; |
| 265 |
| 266 // If set to |YES|, disables animations that tests would otherwise trigger. |
| 267 BOOL _unitTesting; |
| 268 |
| 269 // If set to |YES|, text to speech is currently playing and the toolbar voice |
| 270 // icon should indicate so. |
| 271 BOOL _isTTSPlaying; |
| 272 |
| 273 // Keeps track of whether or not the back button's images have been reversed. |
| 274 ToolbarButtonMode _backButtonMode; |
| 275 |
| 276 // Keeps track of whether or not the forward button's images have been |
| 277 // reversed. |
| 278 ToolbarButtonMode _forwardButtonMode; |
| 279 |
| 280 // Keeps track of last known trait collection used by the subviews. |
| 281 base::scoped_nsobject<UITraitCollection> _lastKnownTraitCollection; |
| 282 |
| 283 // A snapshot of the current toolbar view. Only valid for phone, will be nil |
| 284 // if on tablet. |
| 285 base::scoped_nsobject<UIImage> _snapshot; |
| 286 // A hash of the state of the toolbar when the snapshot was taken. |
| 287 uint32_t _snapshotHash; |
| 288 |
| 289 // View controller for displaying tab history when the user long presses the |
| 290 // back or forward button. nil if not visible. |
| 291 base::scoped_nsobject<TabHistoryPopupController> _tabHistoryPopupController; |
| 292 |
| 293 // Hardware keyboard watcher, to detect the type of keyboard currently |
| 294 // attached. |
| 295 base::scoped_nsobject<HardwareKeyboardWatcher> _hardwareKeyboardWatcher; |
| 296 |
| 297 // The current browser state. |
| 298 ios::ChromeBrowserState* _browserState; // weak |
| 299 } |
| 300 |
| 301 // Accessor for cancel button. Handles lazy initialization. |
| 302 - (UIButton*)cancelButton; |
| 303 // Handler called after user pressed the cancel button. |
| 304 - (void)cancelButtonPressed:(id)sender; |
| 305 - (void)layoutCancelButton; |
| 306 // Change the location bar dimensions according to the focus status. |
| 307 // Also show/hide relevant buttons. |
| 308 - (void)layoutOmnibox; |
| 309 - (void)setBackButtonEnabled:(BOOL)enabled; |
| 310 // Show or hide the forward button, animating the frame of the location bar to |
| 311 // be in the right position. This can be called multiple times. |
| 312 - (void)setForwardButtonEnabled:(BOOL)enabled; |
| 313 - (void)startProgressBar; |
| 314 - (void)stopProgressBar; |
| 315 - (void)hideProgressBarAndTakeSnapshot; |
| 316 - (void)showReloadButton; |
| 317 - (void)showStopButton; |
| 318 // Creates a hash of the state of the toolbar to know whether or not the cached |
| 319 // snapshot is out of date. |
| 320 // The hash takes into account any UI that may change the appearance of the |
| 321 // toolbar. The one UI state it ignores is the stack view button's press |
| 322 // state. That is because we want the snapshot to be valid when the stack is |
| 323 // pressed, rather than creating a new snapshot exactly during the user |
| 324 // interaction this snapshot is aimed to optimize. |
| 325 - (uint32_t)snapshotHashWithWidth:(CGFloat)width; |
| 326 // Called by long press gesture recognizer, used to display back/forward |
| 327 // history. |
| 328 - (void)handleLongPress:(UILongPressGestureRecognizer*)gesture; |
| 329 - (void)setImagesForNavButton:(UIButton*)button |
| 330 withTabHistoryVisible:(BOOL)tabHistoryVisible; |
| 331 // Received when a TTS player has received audio data. |
| 332 - (void)audioReadyForPlayback:(NSNotification*)notification; |
| 333 // Updates the TTS button depending on whether or not TTS is currently playing. |
| 334 - (void)updateIsTTSPlaying:(NSNotification*)notify; |
| 335 // Moves VoiceOver to the button used to perform a voice search. |
| 336 - (void)moveVoiceOverToVoiceSearchButton; |
| 337 // Fade in and out toolbar items as the frame moves off screen. |
| 338 - (void)updateToolbarAlphaForFrame:(CGRect)frame; |
| 339 // Navigate to |query| from omnibox. |
| 340 - (void)loadURLForQuery:(NSString*)query; |
| 341 - (UIView*)keyboardButtonWithTitle:(NSString*)title frame:(CGRect)frame; |
| 342 // Lazily instantiate the keyboard accessory view. |
| 343 - (UIView*)keyboardAccessoryView; |
| 344 - (void)preloadVoiceSearch:(id)sender; |
| 345 // Calculates the CGRect to use for the omnibox's frame. Also sets the frames |
| 346 // of some buttons and |_webToolbar|. |
| 347 - (CGRect)newOmniboxFrame; |
| 348 - (void)animateMaterialOmnibox; |
| 349 - (void)fadeInOmniboxTrailingView; |
| 350 - (void)fadeInOmniboxLeadingView; |
| 351 - (void)fadeOutOmniboxTrailingView; |
| 352 - (void)fadeOutOmniboxLeadingView; |
| 353 - (void)fadeInIncognitoIcon; |
| 354 - (void)fadeOutIncognitoIcon; |
| 355 // Fade in the visible navigation buttons. |
| 356 - (void)fadeInNavigationControls; |
| 357 // Fade out the visible navigation buttons. |
| 358 - (void)fadeOutNavigationControls; |
| 359 // When the collapse animation is complete, hide the Material background and |
| 360 // restore the omnibox's background image. |
| 361 - (void)animationDidStop:(CAAnimation*)anim finished:(BOOL)flag; |
| 362 - (void)updateSnapshotWithWidth:(CGFloat)width forced:(BOOL)force; |
| 363 // Insert 'com' without the period if cursor is directly after a period. |
| 364 - (NSString*)updateTextForDotCom:(NSString*)text; |
| 365 // Handle the user pressing a key in the keyboard accessory view. |
| 366 - (void)pressKey:(id)sender; |
| 367 @end |
| 368 |
| 369 @implementation WebToolbarController |
| 370 |
| 371 @synthesize delegate = _delegate; |
| 372 @synthesize urlLoader = _urlLoader; |
| 373 |
| 374 - (instancetype)initWithDelegate:(id<WebToolbarDelegate>)delegate |
| 375 urlLoader:(id<UrlLoader>)urlLoader |
| 376 browserState:(ios::ChromeBrowserState*)browserState |
| 377 preloadProvider:(id<PreloadProvider>)preloader { |
| 378 DCHECK(delegate); |
| 379 DCHECK(urlLoader); |
| 380 DCHECK(browserState); |
| 381 _delegate = delegate; |
| 382 _urlLoader = urlLoader; |
| 383 _browserState = browserState; |
| 384 _incognito = browserState->IsOffTheRecord(); |
| 385 self = [super initWithStyle:(_incognito ? ToolbarControllerStyleIncognitoMode |
| 386 : ToolbarControllerStyleLightMode)]; |
| 387 if (!self) |
| 388 return nil; |
| 389 |
| 390 if (reading_list::switches::IsReadingListEnabled()) { |
| 391 self.readingListModel = |
| 392 ReadingListModelFactory::GetForBrowserState(browserState); |
| 393 } |
| 394 |
| 395 InterfaceIdiom idiom = IsIPadIdiom() ? IPAD_IDIOM : IPHONE_IDIOM; |
| 396 // Note that |_webToolbar| gets its frame set to -specificControlArea later in |
| 397 // this method. |
| 398 _webToolbar.reset([[UIView alloc] |
| 399 initWithFrame:LayoutRectGetRect(kWebToolbarFrame[idiom])]); |
| 400 UIColor* textColor = |
| 401 _incognito |
| 402 ? [UIColor whiteColor] |
| 403 : [UIColor colorWithWhite:0 alpha:[MDCTypography body1FontOpacity]]; |
| 404 UIColor* tintColor = _incognito ? textColor : nil; |
| 405 CGRect omniboxRect = LayoutRectGetRect(kOmniboxFrame[idiom]); |
| 406 _omniBox.reset([[OmniboxTextFieldIOS alloc] |
| 407 initWithFrame:omniboxRect |
| 408 font:[MDCTypography subheadFont] |
| 409 textColor:textColor |
| 410 tintColor:tintColor]); |
| 411 if (_incognito) { |
| 412 [_omniBox setIncognito:YES]; |
| 413 [_omniBox |
| 414 setSelectedTextBackgroundColor:[UIColor colorWithWhite:1 alpha:0.1]]; |
| 415 [_omniBox setPlaceholderTextColor:[UIColor colorWithWhite:1 alpha:0.5]]; |
| 416 } else if (!IsIPadIdiom()) { |
| 417 // Set placeholder text color to match fakebox placeholder text color when |
| 418 // on iPhone and in regular mode. |
| 419 UIColor* placeholderTextColor = |
| 420 [UIColor colorWithWhite:kiPhoneOmniboxPlaceholderColorBrightness |
| 421 alpha:1.0]; |
| 422 [_omniBox setPlaceholderTextColor:placeholderTextColor]; |
| 423 } |
| 424 _backButton.reset([[UIButton alloc] |
| 425 initWithFrame:LayoutRectGetRect(kBackButtonFrame[idiom])]); |
| 426 [_backButton setAutoresizingMask:UIViewAutoresizingFlexibleTrailingMargin() | |
| 427 UIViewAutoresizingFlexibleTopMargin | |
| 428 UIViewAutoresizingFlexibleBottomMargin]; |
| 429 // Note that the forward button gets repositioned when -layoutOmnibox is |
| 430 // called. |
| 431 _forwardButton.reset([[UIButton alloc] |
| 432 initWithFrame:LayoutRectGetRect(kForwardButtonFrame[idiom])]); |
| 433 [_forwardButton |
| 434 setAutoresizingMask:UIViewAutoresizingFlexibleTrailingMargin() | |
| 435 UIViewAutoresizingFlexibleBottomMargin]; |
| 436 |
| 437 [_webToolbar addSubview:_backButton]; |
| 438 [_webToolbar addSubview:_forwardButton]; |
| 439 |
| 440 // _omniboxBackground needs to be added under _omniBox so as not to cover up |
| 441 // _omniBox. |
| 442 _omniboxBackground.reset([[UIImageView alloc] initWithFrame:omniboxRect]); |
| 443 [_omniboxBackground |
| 444 setAutoresizingMask:UIViewAutoresizingFlexibleWidth | |
| 445 UIViewAutoresizingFlexibleBottomMargin]; |
| 446 |
| 447 if (idiom == IPAD_IDIOM) { |
| 448 [_webToolbar addSubview:_omniboxBackground]; |
| 449 } else { |
| 450 [_backButton setImageEdgeInsets:UIEdgeInsetsMakeDirected(0, 0, 0, -9)]; |
| 451 [_forwardButton setImageEdgeInsets:UIEdgeInsetsMakeDirected(0, -7, 0, 0)]; |
| 452 CGRect clippingFrame = |
| 453 RectShiftedUpAndResizedForStatusBar(kToolbarFrame[idiom]); |
| 454 _clippingView.reset([[UIView alloc] initWithFrame:clippingFrame]); |
| 455 [_clippingView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | |
| 456 UIViewAutoresizingFlexibleBottomMargin]; |
| 457 [_clippingView setClipsToBounds:YES]; |
| 458 [_clippingView setUserInteractionEnabled:NO]; |
| 459 [_webToolbar addSubview:_clippingView]; |
| 460 |
| 461 CGRect omniboxBackgroundFrame = |
| 462 RectShiftedDownForStatusBar([_omniboxBackground frame]); |
| 463 [_omniboxBackground setFrame:omniboxBackgroundFrame]; |
| 464 [_clippingView addSubview:_omniboxBackground]; |
| 465 [self.view |
| 466 setBackgroundColor:[UIColor colorWithWhite:kNTPBackgroundColorBrightness |
| 467 alpha:1.0]]; |
| 468 |
| 469 if (_incognito) { |
| 470 [self.view |
| 471 setBackgroundColor: |
| 472 [UIColor colorWithWhite:kNTPBackgroundColorBrightnessIncognito |
| 473 alpha:1.0]]; |
| 474 _incognitoIcon.reset([[UIImageView alloc] |
| 475 initWithImage:[UIImage imageNamed:@"incognito_marker_typing"]]); |
| 476 [_incognitoIcon setAlpha:0]; |
| 477 [_incognitoIcon |
| 478 setAutoresizingMask:UIViewAutoresizingFlexibleTrailingMargin()]; |
| 479 [self layoutIncognitoIcon]; |
| 480 [_webToolbar addSubview:_incognitoIcon]; |
| 481 } |
| 482 } |
| 483 |
| 484 [_webToolbar addSubview:_omniBox]; |
| 485 |
| 486 [_backButton setEnabled:NO]; |
| 487 [_forwardButton setEnabled:NO]; |
| 488 |
| 489 if (idiom == IPAD_IDIOM) { |
| 490 // Note that the reload button gets repositioned when -layoutOmnibox is |
| 491 // called. |
| 492 _reloadButton.reset([[UIButton alloc] |
| 493 initWithFrame:LayoutRectGetRect(kStopReloadButtonFrame)]); |
| 494 [_reloadButton |
| 495 setAutoresizingMask:UIViewAutoresizingFlexibleTrailingMargin() | |
| 496 UIViewAutoresizingFlexibleBottomMargin]; |
| 497 _stopButton.reset([[UIButton alloc] |
| 498 initWithFrame:LayoutRectGetRect(kStopReloadButtonFrame)]); |
| 499 [_stopButton |
| 500 setAutoresizingMask:UIViewAutoresizingFlexibleTrailingMargin() | |
| 501 UIViewAutoresizingFlexibleBottomMargin]; |
| 502 _starButton.reset( |
| 503 [[UIButton alloc] initWithFrame:LayoutRectGetRect(kStarButtonFrame)]); |
| 504 [_starButton setAutoresizingMask:UIViewAutoresizingFlexibleBottomMargin | |
| 505 UIViewAutoresizingFlexibleLeadingMargin()]; |
| 506 _voiceSearchButton.reset([[UIButton alloc] |
| 507 initWithFrame:LayoutRectGetRect(kVoiceSearchButtonFrame)]); |
| 508 [_voiceSearchButton |
| 509 setAutoresizingMask:UIViewAutoresizingFlexibleBottomMargin | |
| 510 UIViewAutoresizingFlexibleLeadingMargin()]; |
| 511 [_webToolbar addSubview:_voiceSearchButton]; |
| 512 [_webToolbar addSubview:_starButton]; |
| 513 [_webToolbar addSubview:_stopButton]; |
| 514 [_webToolbar addSubview:_reloadButton]; |
| 515 [self setUpButton:_voiceSearchButton |
| 516 withImageEnum:WebToolbarButtonNameVoice |
| 517 forInitialState:UIControlStateNormal |
| 518 hasDisabledImage:NO |
| 519 synchronously:NO]; |
| 520 [self setUpButton:_starButton |
| 521 withImageEnum:WebToolbarButtonNameStar |
| 522 forInitialState:UIControlStateNormal |
| 523 hasDisabledImage:NO |
| 524 synchronously:YES]; |
| 525 [self setUpButton:_stopButton |
| 526 withImageEnum:WebToolbarButtonNameStop |
| 527 forInitialState:UIControlStateDisabled |
| 528 hasDisabledImage:YES |
| 529 synchronously:NO]; |
| 530 [self setUpButton:_reloadButton |
| 531 withImageEnum:WebToolbarButtonNameReload |
| 532 forInitialState:UIControlStateNormal |
| 533 hasDisabledImage:YES |
| 534 synchronously:NO]; |
| 535 [_stopButton setHidden:YES]; |
| 536 } else { |
| 537 [_forwardButton setAlpha:0.0]; |
| 538 } |
| 539 |
| 540 // Set up the button images and omnibox background. |
| 541 [self setUpButton:_backButton |
| 542 withImageEnum:WebToolbarButtonNameBack |
| 543 forInitialState:UIControlStateDisabled |
| 544 hasDisabledImage:YES |
| 545 synchronously:NO]; |
| 546 [self setUpButton:_forwardButton |
| 547 withImageEnum:WebToolbarButtonNameForward |
| 548 forInitialState:UIControlStateDisabled |
| 549 hasDisabledImage:YES |
| 550 synchronously:NO]; |
| 551 |
| 552 _backButtonMode = ToolbarButtonModeNormal; |
| 553 _forwardButtonMode = ToolbarButtonModeNormal; |
| 554 base::scoped_nsobject<UILongPressGestureRecognizer> backLongPress( |
| 555 [[UILongPressGestureRecognizer alloc] |
| 556 initWithTarget:self |
| 557 action:@selector(handleLongPress:)]); |
| 558 [_backButton addGestureRecognizer:backLongPress]; |
| 559 base::scoped_nsobject<UILongPressGestureRecognizer> forwardLongPress( |
| 560 [[UILongPressGestureRecognizer alloc] |
| 561 initWithTarget:self |
| 562 action:@selector(handleLongPress:)]); |
| 563 [_forwardButton addGestureRecognizer:forwardLongPress]; |
| 564 |
| 565 // TODO(leng): Consider moving this to a pak file as well. For now, |
| 566 // because it is also used by find_bar_controller_ios, leave it as is. |
| 567 NSString* imageName = |
| 568 _incognito ? @"omnibox_transparent_background" : @"omnibox_background"; |
| 569 [_omniboxBackground setImage:StretchableImageNamed(imageName, 12, 12)]; |
| 570 [_omniBox setAutoresizingMask:UIViewAutoresizingFlexibleWidth | |
| 571 UIViewAutoresizingFlexibleBottomMargin]; |
| 572 [_reloadButton addTarget:self |
| 573 action:@selector(cancelOmniboxEdit) |
| 574 forControlEvents:UIControlEventTouchUpInside]; |
| 575 [_stopButton addTarget:self |
| 576 action:@selector(cancelOmniboxEdit) |
| 577 forControlEvents:UIControlEventTouchUpInside]; |
| 578 |
| 579 [_backButton setTag:IDC_BACK]; |
| 580 [_forwardButton setTag:IDC_FORWARD]; |
| 581 [_reloadButton setTag:IDC_RELOAD]; |
| 582 [_stopButton setTag:IDC_STOP]; |
| 583 [_starButton setTag:IDC_BOOKMARK_PAGE]; |
| 584 [_voiceSearchButton setTag:IDC_VOICE_SEARCH]; |
| 585 |
| 586 SetA11yLabelAndUiAutomationName(_backButton, IDS_ACCNAME_BACK, @"Back"); |
| 587 SetA11yLabelAndUiAutomationName(_forwardButton, IDS_ACCNAME_FORWARD, |
| 588 @"Forward"); |
| 589 SetA11yLabelAndUiAutomationName(_reloadButton, IDS_IOS_ACCNAME_RELOAD, |
| 590 @"Reload"); |
| 591 SetA11yLabelAndUiAutomationName(_stopButton, IDS_IOS_ACCNAME_STOP, @"Stop"); |
| 592 SetA11yLabelAndUiAutomationName(_starButton, IDS_TOOLTIP_STAR, @"Bookmark"); |
| 593 SetA11yLabelAndUiAutomationName( |
| 594 _voiceSearchButton, IDS_IOS_ACCNAME_VOICE_SEARCH, @"Voice Search"); |
| 595 SetA11yLabelAndUiAutomationName(_omniBox, IDS_ACCNAME_LOCATION, @"Address"); |
| 596 |
| 597 // Resize the container to match the available area. |
| 598 [self.view addSubview:_webToolbar]; |
| 599 [_webToolbar setAutoresizingMask:UIViewAutoresizingFlexibleWidth | |
| 600 UIViewAutoresizingFlexibleBottomMargin]; |
| 601 [_webToolbar setFrame:[self specificControlsArea]]; |
| 602 _locationBar.reset( |
| 603 new LocationBarViewIOS(_omniBox, _browserState, preloader, self, self)); |
| 604 |
| 605 // Create the determinate progress bar (phone only). |
| 606 if (idiom == IPHONE_IDIOM) { |
| 607 CGFloat progressWidth = self.view.frame.size.width; |
| 608 CGFloat progressHeight = 0; |
| 609 progressHeight = kMaterialProgressBarHeight; |
| 610 _determinateProgressView.reset([[MDCProgressView alloc] init]); |
| 611 _determinateProgressView.get().hidden = YES; |
| 612 [_determinateProgressView |
| 613 setProgressTintColor:[MDCPalette cr_bluePalette].tint500]; |
| 614 [_determinateProgressView |
| 615 setTrackTintColor:[MDCPalette cr_bluePalette].tint100]; |
| 616 int progressBarYOffset = |
| 617 floor(progressHeight / 2) + kDeterminateProgressBarYOffset; |
| 618 int progressBarY = self.view.bounds.size.height - progressBarYOffset; |
| 619 CGRect progressBarFrame = |
| 620 CGRectMake(0, progressBarY, progressWidth, progressHeight); |
| 621 [_determinateProgressView setFrame:progressBarFrame]; |
| 622 [self.view addSubview:_determinateProgressView]; |
| 623 } |
| 624 |
| 625 // Attach the spacebar view to the omnibox. |
| 626 [_omniBox setInputAccessoryView:[self keyboardAccessoryView]]; |
| 627 |
| 628 // Add the handler to preload voice search when the voice search button is |
| 629 // tapped, but only if voice search is enabled. |
| 630 if (ios::GetChromeBrowserProvider() |
| 631 ->GetVoiceSearchProvider() |
| 632 ->IsVoiceSearchEnabled()) { |
| 633 [_voiceSearchButton addTarget:self |
| 634 action:@selector(preloadVoiceSearch:) |
| 635 forControlEvents:UIControlEventTouchDown]; |
| 636 } else { |
| 637 [_voiceSearchButton setEnabled:NO]; |
| 638 } |
| 639 |
| 640 // Register for text-to-speech (TTS) events on tablet. |
| 641 if (idiom == IPAD_IDIOM) { |
| 642 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter]; |
| 643 [defaultCenter addObserver:self |
| 644 selector:@selector(audioReadyForPlayback:) |
| 645 name:kTTSAudioReadyForPlaybackNotification |
| 646 object:nil]; |
| 647 [defaultCenter addObserver:self |
| 648 selector:@selector(updateIsTTSPlaying:) |
| 649 name:kTTSWillStartPlayingNotification |
| 650 object:nil]; |
| 651 [defaultCenter addObserver:self |
| 652 selector:@selector(updateIsTTSPlaying:) |
| 653 name:kTTSDidStopPlayingNotification |
| 654 object:nil]; |
| 655 [defaultCenter addObserver:self |
| 656 selector:@selector(moveVoiceOverToVoiceSearchButton) |
| 657 name:kVoiceSearchWillHideNotification |
| 658 object:nil]; |
| 659 } |
| 660 [self.view setDelegate:self]; |
| 661 |
| 662 return self; |
| 663 } |
| 664 |
| 665 - (void)browserStateDestroyed { |
| 666 // The location bar has a browser state reference, so must be destroyed at |
| 667 // this point. |
| 668 _locationBar.reset(); |
| 669 _browserState = nullptr; |
| 670 } |
| 671 |
| 672 - (void)dealloc { |
| 673 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| 674 [_tabHistoryPopupController setDelegate:nil]; |
| 675 [super dealloc]; |
| 676 } |
| 677 |
| 678 #pragma mark - |
| 679 #pragma mark Public methods. |
| 680 |
| 681 - (void)updateToolbarState { |
| 682 ToolbarModelIOS* toolbarModelIOS = [self.delegate toolbarModelIOS]; |
| 683 if (toolbarModelIOS->IsLoading()) { |
| 684 [self showStopButton]; |
| 685 [self startProgressBar]; |
| 686 [_determinateProgressView |
| 687 setProgress:toolbarModelIOS->GetLoadProgressFraction() |
| 688 animated:YES |
| 689 completion:nil]; |
| 690 } else { |
| 691 [self stopProgressBar]; |
| 692 [self showReloadButton]; |
| 693 } |
| 694 |
| 695 _locationBar->SetShouldShowHintText(toolbarModelIOS->ShouldDisplayHintText()); |
| 696 _locationBar->OnToolbarUpdated(); |
| 697 BOOL forwardButtonEnabled = toolbarModelIOS->CanGoForward(); |
| 698 [self setBackButtonEnabled:toolbarModelIOS->CanGoBack()]; |
| 699 [self setForwardButtonEnabled:forwardButtonEnabled]; |
| 700 |
| 701 // Update the bookmarked "starred" state of current tab. |
| 702 [_starButton setSelected:toolbarModelIOS->IsCurrentTabBookmarked()]; |
| 703 |
| 704 if (!_initialLayoutComplete) |
| 705 _initialLayoutComplete = YES; |
| 706 if (!toolbarModelIOS->IsLoading() && !IsIPadIdiom()) |
| 707 [self updateSnapshotWithWidth:0 forced:NO]; |
| 708 } |
| 709 |
| 710 - (void)updateToolbarForSideSwipeSnapshot:(Tab*)tab { |
| 711 web::WebState* webState = tab.webState; |
| 712 BOOL isCurrentTab = webState == [self.delegate currentWebState]; |
| 713 BOOL isNTP = webState->GetVisibleURL() == GURL(kChromeUINewTabURL); |
| 714 |
| 715 // Don't do anything for a live non-ntp tab. |
| 716 if (isCurrentTab && !isNTP) { |
| 717 // This has the effect of making any alpha-ed out items visible. |
| 718 [self updateToolbarAlphaForFrame:CGRectZero]; |
| 719 [_omniBox setHidden:NO]; |
| 720 return; |
| 721 } |
| 722 |
| 723 [[self view] setHidden:NO]; |
| 724 [_determinateProgressView setHidden:YES]; |
| 725 BOOL forwardEnabled = tab.canGoForward; |
| 726 [_backButton setHidden:isNTP ? !forwardEnabled : NO]; |
| 727 [_backButton setEnabled:tab.canGoBack]; |
| 728 [_forwardButton setHidden:!forwardEnabled]; |
| 729 [_forwardButton setEnabled:forwardEnabled]; |
| 730 [_omniBox setHidden:YES]; |
| 731 [self.backgroundView setAlpha:isNTP ? 0 : 1]; |
| 732 [_omniboxBackground setHidden:isNTP ? YES : NO]; |
| 733 [self hideViewsForNewTabPage:isNTP ? YES : NO]; |
| 734 [self layoutOmnibox]; |
| 735 } |
| 736 |
| 737 - (void)resetToolbarAfterSideSwipeSnapshot { |
| 738 [_omniBox setHidden:NO]; |
| 739 [_backButton setHidden:NO]; |
| 740 [_forwardButton setHidden:NO]; |
| 741 [_omniboxBackground setHidden:NO]; |
| 742 [self.backgroundView setAlpha:1]; |
| 743 [self hideViewsForNewTabPage:NO]; |
| 744 [self updateToolbarState]; |
| 745 } |
| 746 |
| 747 - (void)showPrerenderingAnimation { |
| 748 _prerenderAnimating = YES; |
| 749 [_determinateProgressView setProgress:0]; |
| 750 [_determinateProgressView setHidden:NO |
| 751 animated:YES |
| 752 completion:^(BOOL finished) { |
| 753 [_determinateProgressView |
| 754 setProgress:1 |
| 755 animated:YES |
| 756 completion:^(BOOL finished) { |
| 757 [_determinateProgressView setHidden:YES |
| 758 animated:YES |
| 759 completion:nil]; |
| 760 }]; |
| 761 }]; |
| 762 } |
| 763 |
| 764 - (void)setControlsHidden:(BOOL)hidden { |
| 765 [self setStandardControlsVisible:!hidden]; |
| 766 [_webToolbar setHidden:hidden]; |
| 767 } |
| 768 |
| 769 - (void)setControlsAlpha:(CGFloat)alpha { |
| 770 [self setStandardControlsAlpha:alpha]; |
| 771 [_webToolbar setAlpha:alpha]; |
| 772 } |
| 773 |
| 774 - (void)currentPageLoadStarted { |
| 775 [self startProgressBar]; |
| 776 } |
| 777 |
| 778 - (void)selectedTabChanged { |
| 779 [self cancelOmniboxEdit]; |
| 780 } |
| 781 |
| 782 - (CGRect)bookmarkButtonAnchorRect { |
| 783 // Shrink the padding around the bookmark button so the popovers are anchored |
| 784 // correctly. |
| 785 return CGRectInset([_starButton bounds], 6, 11); |
| 786 } |
| 787 |
| 788 - (UIView*)bookmarkButtonView { |
| 789 return _starButton.get(); |
| 790 } |
| 791 |
| 792 - (CGRect)visibleOmniboxFrame { |
| 793 CGRect frame = _omniboxBackground.get().frame; |
| 794 frame = [self.view.superview convertRect:frame |
| 795 fromView:[_omniboxBackground superview]]; |
| 796 // Account for the omnibox background image transparent sides. |
| 797 return CGRectInset(frame, -kBackgroundImageVisibleRectOffset, 0); |
| 798 } |
| 799 |
| 800 - (UIImage*)snapshotWithWidth:(CGFloat)width { |
| 801 if (IsIPadIdiom()) |
| 802 return nil; |
| 803 // Below call will be no-op if cached snapshot is valid. |
| 804 [self updateSnapshotWithWidth:width forced:YES]; |
| 805 return [[_snapshot retain] autorelease]; |
| 806 } |
| 807 |
| 808 - (void)showTabHistoryPopupInView:(UIView*)view |
| 809 withSessionEntries:(NSArray*)sessionEntries |
| 810 forBackHistory:(BOOL)isBackHistory { |
| 811 if (_tabHistoryPopupController) |
| 812 return; |
| 813 |
| 814 base::RecordAction(UserMetricsAction("MobileToolbarShowTabHistoryMenu")); |
| 815 |
| 816 UIButton* historyButton = isBackHistory ? _backButton : _forwardButton; |
| 817 // Keep the button pressed by swapping the normal and highlighted images. |
| 818 [self setImagesForNavButton:historyButton withTabHistoryVisible:YES]; |
| 819 |
| 820 // Set the origin for the tools popup to the leading side of the bottom of the |
| 821 // pressed buttons. |
| 822 CGRect buttonBounds = [historyButton.imageView bounds]; |
| 823 CGPoint origin = CGPointMake(CGRectGetLeadingEdge(buttonBounds), |
| 824 CGRectGetMaxY(buttonBounds)); |
| 825 CGPoint convertedOrigin = |
| 826 [view convertPoint:origin fromView:historyButton.imageView]; |
| 827 _tabHistoryPopupController.reset([[TabHistoryPopupController alloc] |
| 828 initWithOrigin:convertedOrigin |
| 829 parentView:view |
| 830 entries:sessionEntries]); |
| 831 [_tabHistoryPopupController setDelegate:self]; |
| 832 |
| 833 [[NSNotificationCenter defaultCenter] |
| 834 postNotificationName:kTabHistoryPopupWillShowNotification |
| 835 object:nil]; |
| 836 } |
| 837 |
| 838 - (void)dismissTabHistoryPopup { |
| 839 if (!_tabHistoryPopupController) |
| 840 return; |
| 841 TabHistoryPopupController* tempTHPC = _tabHistoryPopupController.get(); |
| 842 [tempTHPC containerView].userInteractionEnabled = NO; |
| 843 [tempTHPC dismissAnimatedWithCompletion:^{ |
| 844 // Unpress the back/forward button by restoring the normal and |
| 845 // highlighted images to their usual state. |
| 846 [self setImagesForNavButton:_backButton withTabHistoryVisible:NO]; |
| 847 [self setImagesForNavButton:_forwardButton withTabHistoryVisible:NO]; |
| 848 // Reference tempTHPC so the block retains it. |
| 849 [tempTHPC self]; |
| 850 }]; |
| 851 // reset _tabHistoryPopupController to prevent -applicationDidEnterBackground |
| 852 // from posting another kTabHistoryPopupWillHideNotification. |
| 853 _tabHistoryPopupController.reset(); |
| 854 |
| 855 [[NSNotificationCenter defaultCenter] |
| 856 postNotificationName:kTabHistoryPopupWillHideNotification |
| 857 object:nil]; |
| 858 } |
| 859 |
| 860 - (BOOL)isOmniboxFirstResponder { |
| 861 return [_omniBox isFirstResponder]; |
| 862 } |
| 863 |
| 864 - (BOOL)showingOmniboxPopup { |
| 865 OmniboxViewIOS* omniboxViewIOS = |
| 866 static_cast<OmniboxViewIOS*>(_locationBar.get()->GetLocationEntry()); |
| 867 return omniboxViewIOS->IsPopupOpen(); |
| 868 } |
| 869 |
| 870 - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection { |
| 871 [super traitCollectionDidChange:previousTraitCollection]; |
| 872 _lastKnownTraitCollection.reset([[UITraitCollection |
| 873 traitCollectionWithTraitsFromCollections:@[ self.view.traitCollection ]] |
| 874 retain]); |
| 875 if (IsIPadIdiom()) { |
| 876 // Update toolbar accessory views. |
| 877 BOOL isCompactTabletView = IsCompactTablet(self.view); |
| 878 [_voiceSearchButton setHidden:isCompactTabletView]; |
| 879 [_starButton setHidden:isCompactTabletView]; |
| 880 [_reloadButton setHidden:isCompactTabletView]; |
| 881 [_stopButton setHidden:isCompactTabletView]; |
| 882 [self updateToolbarState]; |
| 883 |
| 884 // Update keyboard accessory views. |
| 885 BOOL hidden = [_keyboardVoiceSearchButton isHidden]; |
| 886 _keyBoardAccessoryView.reset(); |
| 887 [_omniBox setInputAccessoryView:[self keyboardAccessoryView]]; |
| 888 [_keyboardVoiceSearchButton setHidden:hidden]; |
| 889 if ([_omniBox isFirstResponder]) { |
| 890 [_omniBox reloadInputViews]; |
| 891 } |
| 892 |
| 893 // Re-layout toolbar and omnibox. |
| 894 [_webToolbar setFrame:[self specificControlsArea]]; |
| 895 [self layoutOmnibox]; |
| 896 } |
| 897 } |
| 898 |
| 899 #pragma mark - |
| 900 #pragma mark Overridden superclass methods. |
| 901 |
| 902 - (void)applicationDidEnterBackground:(NSNotification*)notify { |
| 903 if (_tabHistoryPopupController) { |
| 904 // Dismiss the tab history popup without animation. |
| 905 [self setImagesForNavButton:_backButton withTabHistoryVisible:NO]; |
| 906 [self setImagesForNavButton:_forwardButton withTabHistoryVisible:NO]; |
| 907 _tabHistoryPopupController.reset(nil); |
| 908 [[NSNotificationCenter defaultCenter] |
| 909 postNotificationName:kTabHistoryPopupWillHideNotification |
| 910 object:nil]; |
| 911 } |
| 912 [super applicationDidEnterBackground:notify]; |
| 913 } |
| 914 |
| 915 - (void)setUpButton:(UIButton*)button |
| 916 withImageEnum:(int)imageEnum |
| 917 forInitialState:(UIControlState)initialState |
| 918 hasDisabledImage:(BOOL)hasDisabledImage |
| 919 synchronously:(BOOL)synchronously { |
| 920 [super setUpButton:button |
| 921 withImageEnum:imageEnum |
| 922 forInitialState:initialState |
| 923 hasDisabledImage:hasDisabledImage |
| 924 synchronously:synchronously]; |
| 925 |
| 926 if (button != _starButton.get()) |
| 927 return; |
| 928 // The star button behaves slightly differently. It uses the pressed |
| 929 // image for its selected state as well as its pressed state. |
| 930 void (^starBlock)(void) = ^{ |
| 931 UIImage* starImage = [self imageForImageEnum:WebToolbarButtonNameStar |
| 932 forState:ToolbarButtonUIStatePressed]; |
| 933 [_starButton setAdjustsImageWhenHighlighted:NO]; |
| 934 [_starButton setImage:starImage forState:UIControlStateSelected]; |
| 935 }; |
| 936 if (synchronously) { |
| 937 starBlock(); |
| 938 } else { |
| 939 dispatch_time_t addImageDelay = |
| 940 dispatch_time(DISPATCH_TIME_NOW, kNonInitialImageAdditionDelayNanosec); |
| 941 dispatch_after(addImageDelay, dispatch_get_main_queue(), starBlock); |
| 942 } |
| 943 } |
| 944 |
| 945 - (void)standardButtonPressed:(UIButton*)sender { |
| 946 [super standardButtonPressed:sender]; |
| 947 [self cancelOmniboxEdit]; |
| 948 } |
| 949 |
| 950 - (IBAction)recordUserMetrics:(id)sender { |
| 951 if (sender == _backButton.get()) { |
| 952 base::RecordAction(UserMetricsAction("MobileToolbarBack")); |
| 953 } else if (sender == _forwardButton.get()) { |
| 954 base::RecordAction(UserMetricsAction("MobileToolbarForward")); |
| 955 } else if (sender == _reloadButton.get()) { |
| 956 base::RecordAction(UserMetricsAction("MobileToolbarReload")); |
| 957 } else if (sender == _stopButton.get()) { |
| 958 base::RecordAction(UserMetricsAction("MobileToolbarStop")); |
| 959 } else if (sender == _voiceSearchButton.get()) { |
| 960 base::RecordAction(UserMetricsAction("MobileToolbarVoiceSearch")); |
| 961 } else if (sender == _keyboardVoiceSearchButton.get()) { |
| 962 base::RecordAction(UserMetricsAction("MobileCustomRowVoiceSearch")); |
| 963 } else if (sender == _starButton.get()) { |
| 964 base::RecordAction(UserMetricsAction("MobileToolbarToggleBookmark")); |
| 965 } else { |
| 966 [super recordUserMetrics:sender]; |
| 967 } |
| 968 } |
| 969 |
| 970 - (IBAction)stackButtonTouchDown:(id)sender { |
| 971 [self.delegate prepareToEnterTabSwitcher:self]; |
| 972 } |
| 973 |
| 974 - (BOOL)imageShouldFlipForRightToLeftLayoutDirection:(int)imageEnum { |
| 975 DCHECK(imageEnum < NumberOfWebToolbarButtonNames); |
| 976 if (imageEnum < NumberOfToolbarButtonNames) |
| 977 return [super imageShouldFlipForRightToLeftLayoutDirection:imageEnum]; |
| 978 if (imageEnum == WebToolbarButtonNameBack || |
| 979 imageEnum == WebToolbarButtonNameForward || |
| 980 imageEnum == WebToolbarButtonNameReload || |
| 981 imageEnum == WebToolbarButtonNameCallingApp) { |
| 982 return YES; |
| 983 } |
| 984 return NO; |
| 985 } |
| 986 |
| 987 - (int)imageEnumForButton:(UIButton*)button { |
| 988 if (button == _voiceSearchButton.get()) |
| 989 return _isTTSPlaying ? WebToolbarButtonNameTTS : WebToolbarButtonNameVoice; |
| 990 if (button == _starButton.get()) |
| 991 return WebToolbarButtonNameStar; |
| 992 if (button == _stopButton.get()) |
| 993 return WebToolbarButtonNameStop; |
| 994 if (button == _reloadButton.get()) |
| 995 return WebToolbarButtonNameReload; |
| 996 if (button == _backButton.get()) |
| 997 return WebToolbarButtonNameBack; |
| 998 if (button == _forwardButton.get()) |
| 999 return WebToolbarButtonNameForward; |
| 1000 return [super imageEnumForButton:button]; |
| 1001 } |
| 1002 |
| 1003 - (int)imageIdForImageEnum:(int)index |
| 1004 style:(ToolbarControllerStyle)style |
| 1005 forState:(ToolbarButtonUIState)state { |
| 1006 DCHECK(style < ToolbarControllerStyleMaxStyles); |
| 1007 DCHECK(state < NumberOfToolbarButtonUIStates); |
| 1008 // Additional checking. These three buttons should only ever be used on iPad. |
| 1009 DCHECK(IsIPadIdiom() || index != WebToolbarButtonNameStar); |
| 1010 DCHECK(IsIPadIdiom() || index != WebToolbarButtonNameTTS); |
| 1011 DCHECK(IsIPadIdiom() || index != WebToolbarButtonNameVoice); |
| 1012 |
| 1013 if (index >= NumberOfWebToolbarButtonNames) |
| 1014 NOTREACHED(); |
| 1015 if (index < NumberOfToolbarButtonNames) |
| 1016 return [super imageIdForImageEnum:index style:style forState:state]; |
| 1017 |
| 1018 // Incognito mode gets dark buttons. |
| 1019 if (style == ToolbarControllerStyleIncognitoMode) |
| 1020 style = ToolbarControllerStyleDarkMode; |
| 1021 |
| 1022 // Some images can be overridden by the branded image provider. |
| 1023 if (index == WebToolbarButtonNameVoice) { |
| 1024 int image_id; |
| 1025 if (ios::GetChromeBrowserProvider() |
| 1026 ->GetBrandedImageProvider() |
| 1027 ->GetToolbarVoiceSearchButtonImageId(&image_id)) { |
| 1028 return image_id; |
| 1029 } |
| 1030 // Otherwise fall through to the default voice search button images below. |
| 1031 } |
| 1032 |
| 1033 // Rebase |index| so that it properly indexes into the array below. |
| 1034 index -= NumberOfToolbarButtonNames; |
| 1035 const int numberOfAddedNames = |
| 1036 NumberOfWebToolbarButtonNames - NumberOfToolbarButtonNames; |
| 1037 // Name, style [light, dark], UIControlState [normal, pressed, disabled] |
| 1038 static int buttonImageIds[numberOfAddedNames][2] |
| 1039 [NumberOfToolbarButtonUIStates] = { |
| 1040 TOOLBAR_IDR_THREE_STATE(BACK), |
| 1041 TOOLBAR_IDR_LIGHT_ONLY_TWO_STATE(CALLINGAPP), |
| 1042 TOOLBAR_IDR_THREE_STATE(FORWARD), |
| 1043 TOOLBAR_IDR_THREE_STATE(RELOAD), |
| 1044 TOOLBAR_IDR_TWO_STATE(STAR), |
| 1045 TOOLBAR_IDR_THREE_STATE(STOP), |
| 1046 TOOLBAR_IDR_TWO_STATE(TTS), |
| 1047 TOOLBAR_IDR_TWO_STATE(VOICE), |
| 1048 }; |
| 1049 return buttonImageIds[index][style][state]; |
| 1050 } |
| 1051 |
| 1052 - (void)animateTransitionWithBeginFrame:(CGRect)beginFrame |
| 1053 endFrame:(CGRect)endFrame |
| 1054 transitionStyle:(ToolbarTransitionStyle)style { |
| 1055 // Convert background to clear for animations. This is necessary because we |
| 1056 // need to see the animating buttons on the card's tab, which are occurring |
| 1057 // behind the toolbar. The desired color is restored upon completion. |
| 1058 self.view.backgroundColor = [UIColor clearColor]; |
| 1059 |
| 1060 // Add base toolbar animations |
| 1061 [super animateTransitionWithBeginFrame:beginFrame |
| 1062 endFrame:endFrame |
| 1063 transitionStyle:style]; |
| 1064 |
| 1065 // Animation values |
| 1066 CAAnimation* frameAnimation = nil; |
| 1067 CFTimeInterval frameDuration = ios::material::kDuration1; |
| 1068 CAMediaTimingFunction* frameTiming = |
| 1069 TimingFunction(ios::material::CurveEaseInOut); |
| 1070 CGRect beginBounds = {CGPointZero, beginFrame.size}; |
| 1071 CGRect endBounds = {CGPointZero, endFrame.size}; |
| 1072 |
| 1073 // Animate web toolbar: Maintain the trailing padding so that toolbar buttons |
| 1074 // on the trailing side have enough room, and ensure that the height is at |
| 1075 // most the specific control area's height. |
| 1076 CGRect specificControlsArea = [self specificControlsArea]; |
| 1077 CGFloat webToolbarTrailingPadding = |
| 1078 CGRectGetTrailingLayoutOffsetInBoundingRect(specificControlsArea, |
| 1079 self.view.bounds); |
| 1080 CGFloat webToolbarMaxHeight = specificControlsArea.size.height; |
| 1081 UIEdgeInsets webToolbarInsets = |
| 1082 UIEdgeInsetsMakeDirected(0, 0, 0, webToolbarTrailingPadding); |
| 1083 webToolbarInsets.top = |
| 1084 std::max<CGFloat>(0.f, beginBounds.size.height - webToolbarMaxHeight); |
| 1085 CGRect webToolbarBeginFrame = |
| 1086 UIEdgeInsetsInsetRect(beginBounds, webToolbarInsets); |
| 1087 webToolbarInsets.top = |
| 1088 std::max<CGFloat>(0.f, endBounds.size.height - webToolbarMaxHeight); |
| 1089 CGRect webToolbarEndFrame = |
| 1090 UIEdgeInsetsInsetRect(endBounds, webToolbarInsets); |
| 1091 frameAnimation = FrameAnimationMake([_webToolbar layer], webToolbarBeginFrame, |
| 1092 webToolbarEndFrame); |
| 1093 frameAnimation.duration = frameDuration; |
| 1094 frameAnimation.timingFunction = frameTiming; |
| 1095 [self.transitionLayers addObject:[_webToolbar layer]]; |
| 1096 [[_webToolbar layer] addAnimation:frameAnimation |
| 1097 forKey:kToolbarTransitionAnimationKey]; |
| 1098 |
| 1099 // Animate omnibox: center the omnibox vertically within the card web toolbar, |
| 1100 // maintain its leading offset, and adjusting its width to match the available |
| 1101 // space on the card. |
| 1102 CGFloat omniboxHeight = [_omniBox frame].size.height; |
| 1103 LayoutRect toolbarOmniboxLayout = LayoutRectForRectInBoundingRect( |
| 1104 [_omniBox frame], [_omniBox superview].bounds); |
| 1105 CGFloat omniboxLeading = toolbarOmniboxLayout.position.leading; |
| 1106 |
| 1107 LayoutRect omniboxBeginLayout = toolbarOmniboxLayout; |
| 1108 omniboxBeginLayout.boundingWidth = CGRectGetWidth(webToolbarBeginFrame); |
| 1109 omniboxBeginLayout.position.originY = |
| 1110 kOmniboxCenterOffsetY + |
| 1111 0.5 * (CGRectGetHeight(webToolbarBeginFrame) - omniboxHeight); |
| 1112 omniboxBeginLayout.size.width = |
| 1113 CGRectGetWidth(webToolbarBeginFrame) - omniboxLeading; |
| 1114 omniboxBeginLayout.size.height = omniboxHeight; |
| 1115 |
| 1116 LayoutRect omniboxEndLayout = toolbarOmniboxLayout; |
| 1117 omniboxEndLayout.boundingWidth = CGRectGetWidth(webToolbarEndFrame); |
| 1118 omniboxEndLayout.position.originY = |
| 1119 kOmniboxCenterOffsetY + |
| 1120 0.5 * (CGRectGetHeight(webToolbarEndFrame) - omniboxHeight); |
| 1121 omniboxEndLayout.size.width = |
| 1122 CGRectGetWidth(webToolbarEndFrame) - omniboxLeading; |
| 1123 omniboxEndLayout.size.height = omniboxHeight; |
| 1124 |
| 1125 CGRect omniboxBeginFrame = |
| 1126 AlignRectOriginAndSizeToPixels(LayoutRectGetRect(omniboxBeginLayout)); |
| 1127 CGRect omniboxEndFrame = |
| 1128 AlignRectOriginAndSizeToPixels(LayoutRectGetRect(omniboxEndLayout)); |
| 1129 |
| 1130 frameAnimation = |
| 1131 FrameAnimationMake([_omniBox layer], omniboxBeginFrame, omniboxEndFrame); |
| 1132 frameAnimation.duration = frameDuration; |
| 1133 frameAnimation.timingFunction = frameTiming; |
| 1134 [self.transitionLayers addObject:[_omniBox layer]]; |
| 1135 [[_omniBox layer] addAnimation:frameAnimation |
| 1136 forKey:kToolbarTransitionAnimationKey]; |
| 1137 [_omniBox |
| 1138 animateFadeWithStyle:((style == TOOLBAR_TRANSITION_STYLE_TO_STACK_VIEW) |
| 1139 ? OMNIBOX_TEXT_FIELD_FADE_STYLE_OUT |
| 1140 : OMNIBOX_TEXT_FIELD_FADE_STYLE_IN)]; |
| 1141 |
| 1142 // Animate clipping view: convert the begin and end bounds since |
| 1143 // |_clippingView| is a subview of |_webToolbar|. |
| 1144 CGRect clippingViewBeginFrame = |
| 1145 CGRectOffset(beginBounds, -webToolbarBeginFrame.origin.x, |
| 1146 -webToolbarBeginFrame.origin.y); |
| 1147 CGRect clippingViewEndFrame = CGRectOffset( |
| 1148 endBounds, -webToolbarEndFrame.origin.x, -webToolbarEndFrame.origin.y); |
| 1149 frameAnimation = FrameAnimationMake( |
| 1150 [_clippingView layer], clippingViewBeginFrame, clippingViewEndFrame); |
| 1151 frameAnimation.duration = frameDuration; |
| 1152 frameAnimation.timingFunction = frameTiming; |
| 1153 [self.transitionLayers addObject:[_clippingView layer]]; |
| 1154 [[_clippingView layer] addAnimation:frameAnimation |
| 1155 forKey:kToolbarTransitionAnimationKey]; |
| 1156 |
| 1157 // Animate omnibox background: the frames should match the omnibox, but in |
| 1158 // |_clippingView|'s coordinate system. |
| 1159 CGRect omniboxBackgroundBeginFrame = |
| 1160 CGRectOffset(omniboxBeginFrame, -clippingViewBeginFrame.origin.x, |
| 1161 -clippingViewBeginFrame.origin.y); |
| 1162 CGRect omniboxBackgroundEndFrame = |
| 1163 CGRectOffset(omniboxEndFrame, -clippingViewEndFrame.origin.x, |
| 1164 -clippingViewEndFrame.origin.y); |
| 1165 frameAnimation = FrameAnimationMake([_omniboxBackground layer], |
| 1166 omniboxBackgroundBeginFrame, |
| 1167 omniboxBackgroundEndFrame); |
| 1168 frameAnimation.duration = frameDuration; |
| 1169 frameAnimation.timingFunction = frameTiming; |
| 1170 BOOL shouldFadeOutOmnibox = (style == TOOLBAR_TRANSITION_STYLE_TO_STACK_VIEW); |
| 1171 CAAnimation* opacityAnimation = OpacityAnimationMake( |
| 1172 shouldFadeOutOmnibox ? 1.0 : 0.0, shouldFadeOutOmnibox ? 0.0 : 1.0); |
| 1173 opacityAnimation.duration = shouldFadeOutOmnibox ? ios::material::kDuration8 |
| 1174 : ios::material::kDuration6; |
| 1175 opacityAnimation.beginTime = |
| 1176 shouldFadeOutOmnibox ? 0.0 : ios::material::kDuration8; |
| 1177 |
| 1178 opacityAnimation.timingFunction = |
| 1179 TimingFunction(shouldFadeOutOmnibox ? ios::material::CurveEaseIn |
| 1180 : ios::material::CurveEaseOut); |
| 1181 CAAnimation* animationGroup = |
| 1182 AnimationGroupMake(@[ frameAnimation, opacityAnimation ]); |
| 1183 [self.transitionLayers addObject:[_omniboxBackground layer]]; |
| 1184 [[_omniboxBackground layer] addAnimation:animationGroup |
| 1185 forKey:kToolbarTransitionAnimationKey]; |
| 1186 |
| 1187 // Animate progress bar: Match the width and bottom edge of the content |
| 1188 // bounds while maintaining the height. |
| 1189 int progressBarYOffset = |
| 1190 floor(kMaterialProgressBarHeight / 2) + kDeterminateProgressBarYOffset; |
| 1191 CGRect progressBarBeginFrame = AlignRectToPixel(CGRectMake( |
| 1192 beginBounds.origin.x, beginBounds.size.height - progressBarYOffset, |
| 1193 beginBounds.size.width, kMaterialProgressBarHeight)); |
| 1194 CGRect progressBarEndFrame = AlignRectToPixel( |
| 1195 CGRectMake(endBounds.origin.x, endBounds.size.height - progressBarYOffset, |
| 1196 endBounds.size.width, kMaterialProgressBarHeight)); |
| 1197 frameAnimation = |
| 1198 FrameAnimationMake([_determinateProgressView layer], |
| 1199 progressBarBeginFrame, progressBarEndFrame); |
| 1200 frameAnimation.duration = frameDuration; |
| 1201 frameAnimation.timingFunction = frameTiming; |
| 1202 [self.transitionLayers addObject:[_determinateProgressView layer]]; |
| 1203 [[_determinateProgressView layer] |
| 1204 addAnimation:frameAnimation |
| 1205 forKey:kToolbarTransitionAnimationKey]; |
| 1206 |
| 1207 // Animate buttons |
| 1208 [self animateTransitionForButtonsInView:_webToolbar |
| 1209 containerBeginBounds:beginBounds |
| 1210 containerEndBounds:endBounds |
| 1211 transitionStyle:style]; |
| 1212 } |
| 1213 |
| 1214 - (void)reverseTransitionAnimations { |
| 1215 [super reverseTransitionAnimations]; |
| 1216 [_omniBox reverseFadeAnimations]; |
| 1217 } |
| 1218 |
| 1219 - (void)cleanUpTransitionAnimations { |
| 1220 CGFloat backgroundColorBrightness = |
| 1221 _incognito ? kNTPBackgroundColorBrightnessIncognito |
| 1222 : kNTPBackgroundColorBrightness; |
| 1223 self.view.backgroundColor = |
| 1224 [UIColor colorWithWhite:backgroundColorBrightness alpha:1.0]; |
| 1225 [super cleanUpTransitionAnimations]; |
| 1226 [_omniBox cleanUpFadeAnimations]; |
| 1227 } |
| 1228 |
| 1229 - (void)hideViewsForNewTabPage:(BOOL)hide { |
| 1230 [super hideViewsForNewTabPage:hide]; |
| 1231 if (_incognito) { |
| 1232 CGFloat alpha = hide ? 0 : 1; |
| 1233 [self.backgroundView setAlpha:alpha]; |
| 1234 } |
| 1235 } |
| 1236 |
| 1237 #pragma mark - |
| 1238 #pragma mark LocationBarDelegate methods. |
| 1239 |
| 1240 - (void)loadGURLFromLocationBar:(const GURL&)url |
| 1241 transition:(ui::PageTransition)transition { |
| 1242 if (url.SchemeIs(url::kJavaScriptScheme)) { |
| 1243 // Evaluate the URL as JavaScript if its scheme is JavaScript. |
| 1244 NSString* jsToEval = [base::SysUTF8ToNSString(url.GetContent()) |
| 1245 stringByRemovingPercentEncoding]; |
| 1246 [self.delegate loadJavaScriptFromLocationBar:jsToEval]; |
| 1247 } else { |
| 1248 // When opening a URL, force the omnibox to resign first responder. This |
| 1249 // will also close the popup. |
| 1250 |
| 1251 // TODO(rohitrao): Is it ok to call |cancelOmniboxEdit| after |loadURL|? It |
| 1252 // doesn't seem to be causing major problems. If we call cancel before |
| 1253 // load, then any prerendered pages get destroyed before the call to load. |
| 1254 [self.urlLoader loadURL:url |
| 1255 referrer:web::Referrer() |
| 1256 transition:transition |
| 1257 rendererInitiated:NO]; |
| 1258 |
| 1259 if (google_util::IsGoogleSearchUrl(url)) { |
| 1260 UMA_HISTOGRAM_ENUMERATION( |
| 1261 kOmniboxQueryLocationAuthorizationStatusHistogram, |
| 1262 [CLLocationManager authorizationStatus], |
| 1263 kLocationAuthorizationStatusCount); |
| 1264 } |
| 1265 } |
| 1266 [self cancelOmniboxEdit]; |
| 1267 } |
| 1268 |
| 1269 - (void)locationBarHasBecomeFirstResponder { |
| 1270 [self.delegate locationBarDidBecomeFirstResponder:self]; |
| 1271 [self animateMaterialOmnibox]; |
| 1272 |
| 1273 [_keyboardVoiceSearchButton setHidden:NO]; |
| 1274 |
| 1275 // Record the appropriate user action for focusing the omnibox. |
| 1276 web::WebState* webState = [self.delegate currentWebState]; |
| 1277 if (webState) { |
| 1278 if (webState->GetVisibleURL() == GURL(kChromeUINewTabURL)) { |
| 1279 OmniboxEditModel* model = _locationBar->GetLocationEntry()->model(); |
| 1280 if (model->is_caret_visible()) { |
| 1281 base::RecordAction( |
| 1282 base::UserMetricsAction("MobileFocusedOmniboxOnNtp")); |
| 1283 } else { |
| 1284 base::RecordAction( |
| 1285 base::UserMetricsAction("MobileFocusedFakeboxOnNtp")); |
| 1286 } |
| 1287 } else { |
| 1288 base::RecordAction( |
| 1289 base::UserMetricsAction("MobileFocusedOmniboxNotOnNtp")); |
| 1290 } |
| 1291 } |
| 1292 } |
| 1293 |
| 1294 - (void)locationBarHasResignedFirstResponder { |
| 1295 [self.delegate locationBarDidResignFirstResponder:self]; |
| 1296 [self animateMaterialOmnibox]; |
| 1297 } |
| 1298 |
| 1299 - (void)locationBarBeganEdit { |
| 1300 [self.delegate locationBarBeganEdit:self]; |
| 1301 } |
| 1302 |
| 1303 - (void)locationBarChanged { |
| 1304 // Hide the voice search button once the user starts editing the omnibox but |
| 1305 // show it if the omnibox is empty. |
| 1306 bool isEditingOrEmpty = _locationBar->GetLocationEntry()->IsEditingOrEmpty(); |
| 1307 BOOL editingAndNotEmpty = isEditingOrEmpty && _omniBox.get().text.length != 0; |
| 1308 // If the voice search button is visible but about to be hidden (i.e. |
| 1309 // the omnibox is no longer empty) then this is the first omnibox text so |
| 1310 // record a user action. |
| 1311 if (![_keyboardVoiceSearchButton isHidden] && editingAndNotEmpty) { |
| 1312 base::RecordAction(UserMetricsAction("MobileFirstTextInOmnibox")); |
| 1313 } |
| 1314 [_keyboardVoiceSearchButton setHidden:editingAndNotEmpty]; |
| 1315 } |
| 1316 |
| 1317 - (web::WebState*)getWebState { |
| 1318 return [self.delegate currentWebState]; |
| 1319 } |
| 1320 |
| 1321 - (ToolbarModel*)toolbarModel { |
| 1322 ToolbarModelIOS* toolbarModelIOS = [self.delegate toolbarModelIOS]; |
| 1323 return toolbarModelIOS ? toolbarModelIOS->GetToolbarModel() : nullptr; |
| 1324 } |
| 1325 |
| 1326 #pragma mark - |
| 1327 #pragma mark OmniboxFocuser methods. |
| 1328 |
| 1329 - (void)focusOmnibox { |
| 1330 if (![_webToolbar isHidden]) |
| 1331 [_omniBox becomeFirstResponder]; |
| 1332 } |
| 1333 |
| 1334 - (void)cancelOmniboxEdit { |
| 1335 _locationBar->HideKeyboardAndEndEditing(); |
| 1336 [self updateToolbarState]; |
| 1337 } |
| 1338 |
| 1339 - (void)focusFakebox { |
| 1340 OmniboxEditModel* model = _locationBar->GetLocationEntry()->model(); |
| 1341 // Setting the caret visibility to false causes OmniboxEditModel to indicate |
| 1342 // that omnibox interaction was initiated from the fakebox. Note that |
| 1343 // SetCaretVisibility is a no-op unless OnSetFocus is called first. |
| 1344 model->OnSetFocus(false); |
| 1345 model->SetCaretVisibility(false); |
| 1346 |
| 1347 if (!IsIPadIdiom()) { |
| 1348 // Set the omnibox background's frame to full bleed. |
| 1349 CGRect mobFrame = CGRectInset([_clippingView bounds], -2, -2); |
| 1350 [_omniboxBackground setFrame:mobFrame]; |
| 1351 } |
| 1352 |
| 1353 [self focusOmnibox]; |
| 1354 } |
| 1355 |
| 1356 - (void)onFakeboxBlur { |
| 1357 DCHECK(!IsIPadIdiom()); |
| 1358 // Hide the toolbar if the NTP is currently displayed. |
| 1359 web::WebState* webState = [self.delegate currentWebState]; |
| 1360 if (webState && (webState->GetVisibleURL() == GURL(kChromeUINewTabURL))) { |
| 1361 [self.view setHidden:YES]; |
| 1362 } |
| 1363 } |
| 1364 |
| 1365 - (void)onFakeboxAnimationComplete { |
| 1366 DCHECK(!IsIPadIdiom()); |
| 1367 [self.view setHidden:NO]; |
| 1368 } |
| 1369 |
| 1370 #pragma mark - |
| 1371 #pragma mark OmniboxPopupPositioner methods. |
| 1372 |
| 1373 - (UIView*)popupAnchorView { |
| 1374 return self.view; |
| 1375 } |
| 1376 |
| 1377 - (CGRect)popupFrame:(CGFloat)height { |
| 1378 UIView* parent = [[self popupAnchorView] superview]; |
| 1379 CGRect frame = [parent bounds]; |
| 1380 |
| 1381 // Set tablet popup width to the same width and origin of omnibox. |
| 1382 if (IsIPadIdiom()) { |
| 1383 // For iPad, the omnibox visually extends to include the voice search button |
| 1384 // on the right. Start with the field's frame in |parent|'s coordinate |
| 1385 // system. |
| 1386 CGRect fieldFrame = |
| 1387 [parent convertRect:[_omniBox bounds] fromView:_omniBox]; |
| 1388 |
| 1389 // Now create a new frame that's below the field, stretching the full width |
| 1390 // of |parent|, minus an inset on each side. |
| 1391 CGFloat maxY = CGRectGetMaxY(fieldFrame); |
| 1392 |
| 1393 // The popup extends to the full width of the screen. |
| 1394 frame.origin.x = 0; |
| 1395 frame.size.width = self.view.frame.size.width; |
| 1396 |
| 1397 frame.origin.y = maxY + kiPadOmniboxPopupVerticalOffset; |
| 1398 frame.size.height = height; |
| 1399 } else { |
| 1400 // For iPhone place the popup just below the toolbar. |
| 1401 CGRect fieldFrame = |
| 1402 [parent convertRect:[_webToolbar bounds] fromView:_webToolbar]; |
| 1403 frame.origin.y = |
| 1404 CGRectGetMaxY(fieldFrame) - [ToolbarController toolbarDropShadowHeight]; |
| 1405 frame.size.height = CGRectGetMaxY([parent bounds]) - frame.origin.y; |
| 1406 } |
| 1407 return frame; |
| 1408 } |
| 1409 |
| 1410 #pragma mark - |
| 1411 #pragma mark PopupMenuDelegate methods. |
| 1412 |
| 1413 - (void)dismissPopupMenu:(PopupMenuController*)controller { |
| 1414 if ([controller isKindOfClass:[TabHistoryPopupController class]] && |
| 1415 (TabHistoryPopupController*)controller == _tabHistoryPopupController) |
| 1416 [self dismissTabHistoryPopup]; |
| 1417 else |
| 1418 [super dismissPopupMenu:controller]; |
| 1419 } |
| 1420 |
| 1421 #pragma mark - |
| 1422 #pragma mark ToolbarFrameDelegate methods. |
| 1423 |
| 1424 - (void)frameDidChangeFrame:(CGRect)newFrame fromFrame:(CGRect)oldFrame { |
| 1425 if (oldFrame.origin.y == newFrame.origin.y) |
| 1426 return; |
| 1427 [self updateToolbarAlphaForFrame:newFrame]; |
| 1428 } |
| 1429 |
| 1430 - (void)windowDidChange { |
| 1431 if (![_lastKnownTraitCollection |
| 1432 containsTraitsInCollection:self.view.traitCollection]) { |
| 1433 [self traitCollectionDidChange:_lastKnownTraitCollection]; |
| 1434 } |
| 1435 } |
| 1436 |
| 1437 #pragma mark - |
| 1438 #pragma mark VoiceSearchControllerDelegate methods. |
| 1439 |
| 1440 - (void)receiveVoiceSearchResult:(NSString*)result { |
| 1441 DCHECK(result); |
| 1442 [self loadURLForQuery:result]; |
| 1443 } |
| 1444 |
| 1445 #pragma mark - |
| 1446 #pragma mark QRScannerViewControllerDelegate methods. |
| 1447 |
| 1448 - (void)receiveQRScannerResult:(NSString*)result loadImmediately:(BOOL)load { |
| 1449 DCHECK(result); |
| 1450 if (load) { |
| 1451 [self loadURLForQuery:result]; |
| 1452 } else { |
| 1453 [self focusOmnibox]; |
| 1454 [_omniBox insertTextWhileEditing:result]; |
| 1455 // Notify the accessibility system to start reading the new contents of the |
| 1456 // Omnibox. |
| 1457 UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, |
| 1458 _omniBox); |
| 1459 } |
| 1460 } |
| 1461 |
| 1462 #pragma mark - |
| 1463 #pragma mark Private methods. |
| 1464 |
| 1465 - (UIButton*)cancelButton { |
| 1466 if (_cancelButton) |
| 1467 return _cancelButton; |
| 1468 _cancelButton.reset([[UIButton buttonWithType:UIButtonTypeCustom] retain]); |
| 1469 NSString* collapseName = _incognito ? @"collapse_incognito" : @"collapse"; |
| 1470 [_cancelButton setImage:[UIImage imageNamed:collapseName] |
| 1471 forState:UIControlStateNormal]; |
| 1472 NSString* collapsePressedName = |
| 1473 _incognito ? @"collapse_pressed_incognito" : @"collapse_pressed"; |
| 1474 [_cancelButton setImage:[UIImage imageNamed:collapsePressedName] |
| 1475 forState:UIControlStateHighlighted]; |
| 1476 [_cancelButton setAccessibilityLabel:l10n_util::GetNSString(IDS_CANCEL)]; |
| 1477 [_cancelButton setAutoresizingMask:UIViewAutoresizingFlexibleLeadingMargin() | |
| 1478 UIViewAutoresizingFlexibleHeight]; |
| 1479 [_cancelButton addTarget:self |
| 1480 action:@selector(cancelButtonPressed:) |
| 1481 forControlEvents:UIControlEventTouchUpInside]; |
| 1482 |
| 1483 [_webToolbar addSubview:_cancelButton]; |
| 1484 return _cancelButton; |
| 1485 } |
| 1486 |
| 1487 - (void)cancelButtonPressed:(id)sender { |
| 1488 [self cancelOmniboxEdit]; |
| 1489 } |
| 1490 |
| 1491 - (void)layoutCancelButton { |
| 1492 CGFloat height = CGRectGetHeight([self specificControlsArea]) - |
| 1493 kCancelButtonTopMargin - kCancelButtonBottomMargin; |
| 1494 LayoutRect cancelButtonLayout; |
| 1495 cancelButtonLayout.position.leading = |
| 1496 CGRectGetWidth([_webToolbar bounds]) - height; |
| 1497 cancelButtonLayout.boundingWidth = CGRectGetWidth([_webToolbar bounds]); |
| 1498 cancelButtonLayout.position.originY = kCancelButtonTopMargin; |
| 1499 cancelButtonLayout.size = CGSizeMake(kCancelButtonWidth, height); |
| 1500 |
| 1501 CGRect frame = LayoutRectGetRect(cancelButtonLayout); |
| 1502 // Use the property to force creation. |
| 1503 [self.cancelButton setFrame:frame]; |
| 1504 } |
| 1505 |
| 1506 - (void)layoutIncognitoIcon { |
| 1507 LayoutRect iconLayout = LayoutRectForRectInBoundingRect( |
| 1508 [_incognitoIcon frame], [_webToolbar bounds]); |
| 1509 iconLayout.position.leading = 16; |
| 1510 iconLayout.position.originY = floor( |
| 1511 (kToolbarFrame[IPHONE_IDIOM].size.height - iconLayout.size.height) / 2); |
| 1512 [_incognitoIcon setFrame:LayoutRectGetRect(iconLayout)]; |
| 1513 } |
| 1514 |
| 1515 - (void)layoutOmnibox { |
| 1516 // Position the forward and reload buttons trailing the back button in that |
| 1517 // order. |
| 1518 LayoutRect leadingControlLayout = LayoutRectForRectInBoundingRect( |
| 1519 [_backButton frame], [_webToolbar bounds]); |
| 1520 LayoutRect forwardButtonLayout = |
| 1521 LayoutRectGetTrailingLayout(leadingControlLayout); |
| 1522 forwardButtonLayout.position.originY = [_forwardButton frame].origin.y; |
| 1523 forwardButtonLayout.size = [_forwardButton frame].size; |
| 1524 |
| 1525 LayoutRect reloadButtonLayout = |
| 1526 LayoutRectGetTrailingLayout(forwardButtonLayout); |
| 1527 reloadButtonLayout.position.originY = [_reloadButton frame].origin.y; |
| 1528 reloadButtonLayout.size = [_reloadButton frame].size; |
| 1529 |
| 1530 CGRect newForwardButtonFrame = LayoutRectGetRect(forwardButtonLayout); |
| 1531 CGRect newReloadButtonFrame = LayoutRectGetRect(reloadButtonLayout); |
| 1532 CGRect newOmniboxFrame = [self newOmniboxFrame]; |
| 1533 BOOL isPad = IsIPadIdiom(); |
| 1534 BOOL growOmnibox = [_omniBox isFirstResponder]; |
| 1535 |
| 1536 // Animate buttons. Hide most of the buttons (standard set, back, forward) |
| 1537 // for extended omnibox layout. Also show an extra cancel button so the |
| 1538 // extended mode can be cancelled. |
| 1539 CGFloat alphaForNormalOmniboxButton = growOmnibox ? 0.0 : 1.0; |
| 1540 CGFloat alphaForGrownOmniboxButton = growOmnibox ? 1.0 : 0.0; |
| 1541 CGFloat forwardButtonAlpha = |
| 1542 [_forwardButton isEnabled] || (isPad && !IsCompactTablet(self.view)) |
| 1543 ? alphaForNormalOmniboxButton |
| 1544 : 0.0; |
| 1545 CGFloat backButtonAlpha = alphaForNormalOmniboxButton; |
| 1546 // For the initial layout/testing we just set the length of animation to 0.0 |
| 1547 // which means the changes will be done without any animation. |
| 1548 [UIView animateWithDuration:((_initialLayoutComplete && !_unitTesting) ? 0.2 |
| 1549 : 0.0) |
| 1550 delay:0.0 |
| 1551 options:UIViewAnimationOptionAllowUserInteraction | |
| 1552 UIViewAnimationOptionBeginFromCurrentState |
| 1553 animations:^{ |
| 1554 if (!isPad) |
| 1555 [self setStandardControlsVisible:!growOmnibox]; |
| 1556 if (!(isPad && growOmnibox)) { |
| 1557 [_backButton setAlpha:backButtonAlpha]; |
| 1558 [_forwardButton setAlpha:forwardButtonAlpha]; |
| 1559 [_forwardButton setFrame:newForwardButtonFrame]; |
| 1560 [_cancelButton setAlpha:alphaForGrownOmniboxButton]; |
| 1561 [_reloadButton setFrame:newReloadButtonFrame]; |
| 1562 [_stopButton setFrame:newReloadButtonFrame]; |
| 1563 } |
| 1564 } |
| 1565 completion:nil]; |
| 1566 |
| 1567 if (CGRectEqualToRect([_omniBox frame], newOmniboxFrame)) |
| 1568 return; |
| 1569 |
| 1570 // Hide the clear and voice search buttons during omniBox frame animations. |
| 1571 [_omniBox setRightViewMode:UITextFieldViewModeNever]; |
| 1572 |
| 1573 // Make sure the accessory images are in the correct positions so they do not |
| 1574 // move during the animation. |
| 1575 [_omniBox rightView].frame = |
| 1576 [_omniBox rightViewRectForBounds:newOmniboxFrame]; |
| 1577 [_omniBox leftView].frame = [_omniBox leftViewRectForBounds:newOmniboxFrame]; |
| 1578 |
| 1579 CGRect materialBackgroundFrame = RectShiftedDownForStatusBar(newOmniboxFrame); |
| 1580 |
| 1581 // Extreme jank happens during initial layout if an animation is invoked. Not |
| 1582 // certain why. o_O |
| 1583 // Duration set to 0.0 prevents the animation during initial layout. |
| 1584 [UIView animateWithDuration:((_initialLayoutComplete && !_unitTesting) ? 0.2 |
| 1585 : 0.0) |
| 1586 delay:0.0 |
| 1587 options:UIViewAnimationOptionAllowUserInteraction |
| 1588 animations:^{ |
| 1589 [_omniBox setFrame:newOmniboxFrame]; |
| 1590 [_omniboxBackground setFrame:materialBackgroundFrame]; |
| 1591 } |
| 1592 completion:^(BOOL finished) { |
| 1593 [_omniBox setRightViewMode:UITextFieldViewModeAlways]; |
| 1594 }]; |
| 1595 } |
| 1596 |
| 1597 - (void)setBackButtonEnabled:(BOOL)enabled { |
| 1598 [_backButton setEnabled:enabled]; |
| 1599 } |
| 1600 |
| 1601 - (void)setForwardButtonEnabled:(BOOL)enabled { |
| 1602 if (enabled == [_forwardButton isEnabled]) |
| 1603 return; |
| 1604 |
| 1605 [_forwardButton setEnabled:enabled]; |
| 1606 if (!enabled && [_forwardButton isHidden]) |
| 1607 return; |
| 1608 [self layoutOmnibox]; |
| 1609 } |
| 1610 |
| 1611 - (void)startProgressBar { |
| 1612 if ([_determinateProgressView isHidden]) { |
| 1613 [_determinateProgressView setProgress:0]; |
| 1614 [_determinateProgressView setHidden:NO animated:YES completion:nil]; |
| 1615 } |
| 1616 } |
| 1617 |
| 1618 - (void)stopProgressBar { |
| 1619 if (_determinateProgressView && ![_determinateProgressView isHidden]) { |
| 1620 // Update the toolbar snapshot, but only after the progress bar has |
| 1621 // disappeared. |
| 1622 |
| 1623 if (!_prerenderAnimating) { |
| 1624 // Calling -completeAndHide while a prerender animation is in progress |
| 1625 // will result in hiding the progress bar before the animation is |
| 1626 // complete. |
| 1627 [_determinateProgressView setProgress:1 |
| 1628 animated:YES |
| 1629 completion:^(BOOL finished) { |
| 1630 [_determinateProgressView setHidden:YES |
| 1631 animated:YES |
| 1632 completion:nil]; |
| 1633 }]; |
| 1634 } |
| 1635 CGFloat delay = _unitTesting ? 0 : kLoadCompleteHideProgressBarDelay; |
| 1636 [self performSelector:@selector(hideProgressBarAndTakeSnapshot) |
| 1637 withObject:nil |
| 1638 afterDelay:delay]; |
| 1639 } |
| 1640 } |
| 1641 |
| 1642 - (void)hideProgressBarAndTakeSnapshot { |
| 1643 // The UI may have been torn down while this selector was queued. If |
| 1644 // |self.delegate| is nil, it is not safe to continue. |
| 1645 if (!self.delegate) |
| 1646 return; |
| 1647 |
| 1648 [_determinateProgressView setHidden:YES]; |
| 1649 [self updateSnapshotWithWidth:0 forced:NO]; |
| 1650 _prerenderAnimating = NO; |
| 1651 } |
| 1652 |
| 1653 - (void)showReloadButton { |
| 1654 if (!IsCompactTablet(self.view)) { |
| 1655 [_reloadButton setHidden:NO]; |
| 1656 [_stopButton setHidden:YES]; |
| 1657 } |
| 1658 } |
| 1659 |
| 1660 - (void)showStopButton { |
| 1661 if (!IsCompactTablet(self.view)) { |
| 1662 [_reloadButton setHidden:YES]; |
| 1663 [_stopButton setHidden:NO]; |
| 1664 } |
| 1665 } |
| 1666 |
| 1667 - (uint32_t)snapshotHashWithWidth:(CGFloat)width { |
| 1668 uint32_t hash = [super snapshotHash]; |
| 1669 // Take only the lower 3 bits of the UIButton state, as they are the only |
| 1670 // ones that change per enabled, highlighted, or nomal. |
| 1671 const uint32_t kButtonStateMask = 0x07; |
| 1672 hash ^= ([_backButton state] & kButtonStateMask) | |
| 1673 (([_forwardButton state] & kButtonStateMask) << 3) | |
| 1674 (([_cancelButton state] & kButtonStateMask) << 6); |
| 1675 // Omnibox size & text it contains. |
| 1676 hash ^= [[_omniBox text] hash]; |
| 1677 hash ^= static_cast<uint32_t>([_omniBox frame].size.width) << 16; |
| 1678 hash ^= static_cast<uint32_t>([_omniBox frame].size.height) << 24; |
| 1679 // Also note progress bar state. |
| 1680 float progress = 0; |
| 1681 if (_determinateProgressView && ![_determinateProgressView isHidden]) |
| 1682 progress = [_determinateProgressView progress]; |
| 1683 // The progress is in the range 0 to 1, so static_cast<uint32_t> won't work. |
| 1684 // Normally, static_cast does the right thing: truncates the float to an int. |
| 1685 // Here, that would not provide the necessary granularity. |
| 1686 hash ^= *(reinterpret_cast<uint32_t*>(&progress)); |
| 1687 // Size changes matter. |
| 1688 hash ^= static_cast<uint32_t>(width) << 15; |
| 1689 hash ^= static_cast<uint32_t>([self view].frame.size.height) << 23; |
| 1690 return hash; |
| 1691 } |
| 1692 |
| 1693 - (void)handleLongPress:(UILongPressGestureRecognizer*)gesture { |
| 1694 if (gesture.state != UIGestureRecognizerStateBegan) |
| 1695 return; |
| 1696 |
| 1697 if (gesture.view == _backButton.get()) { |
| 1698 base::scoped_nsobject<GenericChromeCommand> command( |
| 1699 [[GenericChromeCommand alloc] initWithTag:IDC_SHOW_BACK_HISTORY]); |
| 1700 [_backButton chromeExecuteCommand:command]; |
| 1701 } else if (gesture.view == _forwardButton.get()) { |
| 1702 base::scoped_nsobject<GenericChromeCommand> command( |
| 1703 [[GenericChromeCommand alloc] initWithTag:IDC_SHOW_FORWARD_HISTORY]); |
| 1704 [_forwardButton chromeExecuteCommand:command]; |
| 1705 } |
| 1706 } |
| 1707 |
| 1708 - (void)setImagesForNavButton:(UIButton*)button |
| 1709 withTabHistoryVisible:(BOOL)tabHistoryVisible { |
| 1710 BOOL isBackButton = button == _backButton; |
| 1711 ToolbarButtonMode newMode = |
| 1712 tabHistoryVisible ? ToolbarButtonModeReversed : ToolbarButtonModeNormal; |
| 1713 if (isBackButton && newMode == _backButtonMode) |
| 1714 return; |
| 1715 if (!isBackButton && newMode == _forwardButtonMode) |
| 1716 return; |
| 1717 |
| 1718 base::scoped_nsobject<UIImage> normalImage( |
| 1719 [[button imageForState:UIControlStateNormal] retain]); |
| 1720 base::scoped_nsobject<UIImage> highlightedImage( |
| 1721 [[button imageForState:UIControlStateHighlighted] retain]); |
| 1722 [button setImage:highlightedImage forState:UIControlStateNormal]; |
| 1723 [button setImage:normalImage forState:UIControlStateHighlighted]; |
| 1724 if (isBackButton) |
| 1725 _backButtonMode = newMode; |
| 1726 else |
| 1727 _forwardButtonMode = newMode; |
| 1728 } |
| 1729 |
| 1730 - (void)audioReadyForPlayback:(NSNotification*)notification { |
| 1731 if (![_voiceSearchButton isHidden]) { |
| 1732 // Only trigger TTS playback when the voice search button is visible. |
| 1733 TextToSpeechPlayer* TTSPlayer = |
| 1734 base::mac::ObjCCastStrict<TextToSpeechPlayer>(notification.object); |
| 1735 [TTSPlayer beginPlayback]; |
| 1736 } |
| 1737 } |
| 1738 |
| 1739 - (void)updateIsTTSPlaying:(NSNotification*)notify { |
| 1740 BOOL wasTTSPlaying = _isTTSPlaying; |
| 1741 _isTTSPlaying = |
| 1742 [notify.name isEqualToString:kTTSWillStartPlayingNotification]; |
| 1743 if (wasTTSPlaying != _isTTSPlaying && IsIPadIdiom()) { |
| 1744 [self setUpButton:_voiceSearchButton |
| 1745 withImageEnum:(_isTTSPlaying ? WebToolbarButtonNameTTS |
| 1746 : WebToolbarButtonNameVoice) |
| 1747 forInitialState:UIControlStateNormal |
| 1748 hasDisabledImage:NO |
| 1749 synchronously:NO]; |
| 1750 } |
| 1751 [self updateToolbarState]; |
| 1752 if (_isTTSPlaying && UIAccessibilityIsVoiceOverRunning()) { |
| 1753 // Moving VoiceOver without RunBlockAfterDelay results in VoiceOver not |
| 1754 // staying on |_voiceSearchButton| and instead moving to views inside the |
| 1755 // UIWebView. |
| 1756 // Use |voiceSearchButton| in the block to prevent |self| from being |
| 1757 // retained. |
| 1758 UIButton* voiceSearchButton = _voiceSearchButton; |
| 1759 RunBlockAfterDelay(0.0, ^{ |
| 1760 UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, |
| 1761 voiceSearchButton); |
| 1762 }); |
| 1763 } |
| 1764 } |
| 1765 |
| 1766 - (void)moveVoiceOverToVoiceSearchButton { |
| 1767 UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, |
| 1768 _voiceSearchButton); |
| 1769 } |
| 1770 |
| 1771 - (void)updateToolbarAlphaForFrame:(CGRect)frame { |
| 1772 // Don't update the toolbar buttons if we are animating for a transition. |
| 1773 if (self.view.animatingTransition) |
| 1774 return; |
| 1775 |
| 1776 CGFloat distanceOffscreen = |
| 1777 IsIPadIdiom() |
| 1778 ? fmax((kIPadToolbarY - frame.origin.y) - kScrollFadeDistance, 0) |
| 1779 : -1 * frame.origin.y; |
| 1780 CGFloat fraction = 1 - fmin(distanceOffscreen / kScrollFadeDistance, 1); |
| 1781 if (![_omniBox isFirstResponder]) |
| 1782 [self setStandardControlsAlpha:fraction]; |
| 1783 |
| 1784 [_backButton setAlpha:fraction]; |
| 1785 if ([_forwardButton isEnabled]) |
| 1786 [_forwardButton setAlpha:fraction]; |
| 1787 [_reloadButton setAlpha:fraction]; |
| 1788 [_omniboxBackground setAlpha:fraction]; |
| 1789 [_omniBox setAlpha:fraction]; |
| 1790 [_starButton setAlpha:fraction]; |
| 1791 [_voiceSearchButton setAlpha:fraction]; |
| 1792 } |
| 1793 |
| 1794 - (void)loadURLForQuery:(NSString*)query { |
| 1795 GURL searchURL; |
| 1796 metrics::OmniboxInputType::Type type = AutocompleteInput::Parse( |
| 1797 base::SysNSStringToUTF16(query), std::string(), |
| 1798 AutocompleteSchemeClassifierImpl(), nullptr, nullptr, &searchURL); |
| 1799 if (type != metrics::OmniboxInputType::URL || !searchURL.is_valid()) { |
| 1800 searchURL = GetDefaultSearchURLForSearchTerms( |
| 1801 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState), |
| 1802 base::SysNSStringToUTF16(query)); |
| 1803 } |
| 1804 if (searchURL.is_valid()) { |
| 1805 // It is necessary to include PAGE_TRANSITION_FROM_ADDRESS_BAR in the |
| 1806 // transition type is so that query-in-the-omnibox is triggered for the |
| 1807 // URL. |
| 1808 ui::PageTransition transition = ui::PageTransitionFromInt( |
| 1809 ui::PAGE_TRANSITION_LINK | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR); |
| 1810 [self.urlLoader loadURL:GURL(searchURL) |
| 1811 referrer:web::Referrer() |
| 1812 transition:transition |
| 1813 rendererInitiated:NO]; |
| 1814 } |
| 1815 } |
| 1816 |
| 1817 - (UIView*)keyboardButtonWithTitle:(NSString*)title frame:(CGRect)frame { |
| 1818 UIButton* button = [UIButton buttonWithType:UIButtonTypeCustom]; |
| 1819 UIFont* font = nil; |
| 1820 UIImage* backgroundImage = nil; |
| 1821 if (IsIPadIdiom()) { |
| 1822 font = GetUIFont(FONT_HELVETICA, false, kIpadButtonTitleFontSize); |
| 1823 } else { |
| 1824 font = GetUIFont(FONT_HELVETICA, true, kIphoneButtonTitleFontSize); |
| 1825 } |
| 1826 // TODO(leng): Consider moving these images to pak files as well. |
| 1827 backgroundImage = [UIImage imageNamed:@"keyboard_button"]; |
| 1828 |
| 1829 button.frame = frame; |
| 1830 [button setTitle:title forState:UIControlStateNormal]; |
| 1831 [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; |
| 1832 [button.titleLabel setFont:font]; |
| 1833 [button setBackgroundImage:backgroundImage forState:UIControlStateNormal]; |
| 1834 [button addTarget:self |
| 1835 action:@selector(pressKey:) |
| 1836 forControlEvents:UIControlEventTouchUpInside]; |
| 1837 button.isAccessibilityElement = YES; |
| 1838 [button setAccessibilityLabel:title]; |
| 1839 |
| 1840 return button; |
| 1841 } |
| 1842 |
| 1843 - (UIView*)keyboardAccessoryView { |
| 1844 const CGFloat kViewHeightTablet = 70.0; |
| 1845 const CGFloat kViewHeightPhone = 43.0; |
| 1846 const CGFloat kButtonInset = 5.0; |
| 1847 const CGFloat kButtonSizeXTablet = 61.0; |
| 1848 const CGFloat kButtonSizeXPhone = 46.0; |
| 1849 const CGFloat kButtonSizeYTablet = 62.0; |
| 1850 const CGFloat kButtonSizeYPhone = 35.0; |
| 1851 const CGFloat kBetweenButtonSpacing = 15.0; |
| 1852 const CGFloat kBetweenButtonSpacingPhone = 7.0; |
| 1853 |
| 1854 if (_keyBoardAccessoryView) |
| 1855 return _keyBoardAccessoryView; |
| 1856 |
| 1857 const BOOL isTablet = IsIPadIdiom() && !IsCompactTablet(self.view); |
| 1858 |
| 1859 // TODO(pinkerton): purge this view when low memory. |
| 1860 CGFloat width = [[UIScreen mainScreen] bounds].size.width; |
| 1861 CGFloat height = isTablet ? kViewHeightTablet : kViewHeightPhone; |
| 1862 CGRect frame = CGRectMake(0.0, 0.0, width, height); |
| 1863 |
| 1864 _keyBoardAccessoryView.reset([[KeyboardAccessoryView alloc] |
| 1865 initWithFrame:frame |
| 1866 inputViewStyle:UIInputViewStyleKeyboard]); |
| 1867 [_keyBoardAccessoryView setAutoresizingMask:UIViewAutoresizingFlexibleWidth]; |
| 1868 |
| 1869 NSArray* buttonTitles = |
| 1870 [NSArray arrayWithObjects:@":", @".", @"-", @"/", kDotComTLD, nil]; |
| 1871 |
| 1872 // Center buttons in available space by placing them within a parent view |
| 1873 // that auto-centers. |
| 1874 CGFloat betweenButtonSpacing = |
| 1875 isTablet ? kBetweenButtonSpacing : kBetweenButtonSpacingPhone; |
| 1876 const CGFloat buttonWidth = isTablet ? kButtonSizeXTablet : kButtonSizeXPhone; |
| 1877 |
| 1878 CGFloat totalWidth = (buttonTitles.count * buttonWidth) + |
| 1879 ((buttonTitles.count - 1) * betweenButtonSpacing); |
| 1880 CGFloat indent = floor((width - totalWidth) / 2.0); |
| 1881 if (indent < kButtonInset) |
| 1882 indent = kButtonInset; |
| 1883 CGRect parentViewRect = CGRectMake(indent, 0.0, totalWidth, height); |
| 1884 base::scoped_nsobject<UIView> parentView( |
| 1885 [[UIView alloc] initWithFrame:parentViewRect]); |
| 1886 [parentView setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | |
| 1887 UIViewAutoresizingFlexibleRightMargin]; |
| 1888 [_keyBoardAccessoryView addSubview:parentView]; |
| 1889 |
| 1890 // Create the buttons, starting at the left edge of |parentView|. |
| 1891 CGRect currentFrame = |
| 1892 CGRectMake(0.0, kButtonInset, buttonWidth, |
| 1893 isTablet ? kButtonSizeYTablet : kButtonSizeYPhone); |
| 1894 |
| 1895 for (NSString* title in buttonTitles) { |
| 1896 UIView* button = [self keyboardButtonWithTitle:title frame:currentFrame]; |
| 1897 [parentView addSubview:button]; |
| 1898 currentFrame.origin.x = CGRectGetMaxX(currentFrame) + betweenButtonSpacing; |
| 1899 } |
| 1900 |
| 1901 // Create the voice search button and add it to _keyBoardAccessoryView over |
| 1902 // the text buttons. |
| 1903 _keyboardVoiceSearchButton.reset( |
| 1904 [[UIButton buttonWithType:UIButtonTypeCustom] retain]); |
| 1905 [_keyboardVoiceSearchButton |
| 1906 setAutoresizingMask:UIViewAutoresizingFlexibleWidth]; |
| 1907 [_keyboardVoiceSearchButton setTag:IDC_VOICE_SEARCH]; |
| 1908 SetA11yLabelAndUiAutomationName(_keyboardVoiceSearchButton, |
| 1909 IDS_IOS_ACCNAME_VOICE_SEARCH, |
| 1910 @"Voice Search"); |
| 1911 // TODO(leng): Consider moving these icons into a pak file. |
| 1912 UIImage* voiceRow = [UIImage imageNamed:@"custom_row_voice"]; |
| 1913 UIImage* voiceRowPressed = [UIImage imageNamed:@"custom_row_voice_pressed"]; |
| 1914 [_keyboardVoiceSearchButton setBackgroundImage:voiceRow |
| 1915 forState:UIControlStateNormal]; |
| 1916 [_keyboardVoiceSearchButton setBackgroundImage:voiceRowPressed |
| 1917 forState:UIControlStateHighlighted]; |
| 1918 |
| 1919 UIImage* voiceIcon = [UIImage imageNamed:@"voice_icon_keyboard_accessory"]; |
| 1920 [_keyboardVoiceSearchButton setAdjustsImageWhenHighlighted:NO]; |
| 1921 [_keyboardVoiceSearchButton setImage:voiceIcon forState:UIControlStateNormal]; |
| 1922 [_keyboardVoiceSearchButton setFrame:[_keyBoardAccessoryView bounds]]; |
| 1923 |
| 1924 // Only add the voice search actions if voice search is enabled. |
| 1925 if (ios::GetChromeBrowserProvider() |
| 1926 ->GetVoiceSearchProvider() |
| 1927 ->IsVoiceSearchEnabled()) { |
| 1928 [_keyboardVoiceSearchButton addTarget:self |
| 1929 action:@selector(recordUserMetrics:) |
| 1930 forControlEvents:UIControlEventTouchUpInside]; |
| 1931 [_keyboardVoiceSearchButton addTarget:_keyboardVoiceSearchButton |
| 1932 action:@selector(chromeExecuteCommand:) |
| 1933 forControlEvents:UIControlEventTouchUpInside]; |
| 1934 [_keyboardVoiceSearchButton addTarget:self |
| 1935 action:@selector(preloadVoiceSearch:) |
| 1936 forControlEvents:UIControlEventTouchDown]; |
| 1937 } else { |
| 1938 [_keyboardVoiceSearchButton addTarget:self |
| 1939 action:@selector(ignoreVoiceSearch:) |
| 1940 forControlEvents:UIControlEventTouchUpInside]; |
| 1941 } |
| 1942 |
| 1943 [_keyBoardAccessoryView addSubview:_keyboardVoiceSearchButton]; |
| 1944 |
| 1945 // Reset the external keyboard watcher. |
| 1946 _hardwareKeyboardWatcher.reset([[HardwareKeyboardWatcher alloc] |
| 1947 initWithAccessoryView:_keyBoardAccessoryView]); |
| 1948 |
| 1949 return _keyBoardAccessoryView; |
| 1950 } |
| 1951 |
| 1952 - (void)preloadVoiceSearch:(id)sender { |
| 1953 DCHECK(ios::GetChromeBrowserProvider() |
| 1954 ->GetVoiceSearchProvider() |
| 1955 ->IsVoiceSearchEnabled()); |
| 1956 [sender removeTarget:self |
| 1957 action:@selector(preloadVoiceSearch:) |
| 1958 forControlEvents:UIControlEventTouchDown]; |
| 1959 |
| 1960 // Use a GenericChromeCommand because |sender| already has a tag set for a |
| 1961 // different command. |
| 1962 base::scoped_nsobject<GenericChromeCommand> command( |
| 1963 [[GenericChromeCommand alloc] initWithTag:IDC_PRELOAD_VOICE_SEARCH]); |
| 1964 [sender chromeExecuteCommand:command]; |
| 1965 } |
| 1966 |
| 1967 // Called when the keyboard voice search button is tapped with voice search |
| 1968 // disabled. Hides the voice search button but takes no other action. |
| 1969 - (void)ignoreVoiceSearch:(id)sender { |
| 1970 [_keyboardVoiceSearchButton setHidden:YES]; |
| 1971 } |
| 1972 |
| 1973 - (LocationBarViewIOS*)locationBar { |
| 1974 return _locationBar.get(); |
| 1975 } |
| 1976 |
| 1977 - (CGFloat)omniboxLeading { |
| 1978 // Compute what the leading (x-origin) position for the omniboox should be |
| 1979 // based on what other controls are active. |
| 1980 InterfaceIdiom idiom = IsIPadIdiom() ? IPAD_IDIOM : IPHONE_IDIOM; |
| 1981 |
| 1982 CGFloat trailingPadding = 0.0; |
| 1983 |
| 1984 LayoutRect leadingControlLayout = LayoutRectForRectInBoundingRect( |
| 1985 [_backButton frame], [_webToolbar bounds]); |
| 1986 LayoutRect forwardButtonLayout = |
| 1987 LayoutRectGetTrailingLayout(leadingControlLayout); |
| 1988 forwardButtonLayout.position.leading += trailingPadding; |
| 1989 forwardButtonLayout.position.originY = [_forwardButton frame].origin.y; |
| 1990 forwardButtonLayout.size = [_forwardButton frame].size; |
| 1991 |
| 1992 LayoutRect reloadButtonLayout = |
| 1993 LayoutRectGetTrailingLayout(forwardButtonLayout); |
| 1994 reloadButtonLayout.position.originY = [_reloadButton frame].origin.y; |
| 1995 reloadButtonLayout.size = [_reloadButton frame].size; |
| 1996 |
| 1997 LayoutRect omniboxReferenceLayout; |
| 1998 if (idiom == IPAD_IDIOM && !IsCompactTablet(self.view)) { |
| 1999 omniboxReferenceLayout = reloadButtonLayout; |
| 2000 omniboxReferenceLayout.size.width += kReloadButtonTrailingPadding; |
| 2001 } else if ([_forwardButton isEnabled]) { |
| 2002 omniboxReferenceLayout = forwardButtonLayout; |
| 2003 omniboxReferenceLayout.size.width += kForwardButtonTrailingPadding; |
| 2004 } else { |
| 2005 omniboxReferenceLayout = kBackButtonFrame[idiom]; |
| 2006 omniboxReferenceLayout.size.width += kBackButtonTrailingPadding; |
| 2007 } |
| 2008 DCHECK(!(omniboxReferenceLayout.position.leading == 0 && |
| 2009 CGSizeEqualToSize(omniboxReferenceLayout.size, CGSizeZero))); |
| 2010 |
| 2011 return LayoutRectGetTrailingEdge(omniboxReferenceLayout); |
| 2012 } |
| 2013 |
| 2014 // TODO(crbug.com/525943): refactor this method and related code to not resize |
| 2015 // |_webToolbar| and buttons as a side effect. |
| 2016 - (CGRect)newOmniboxFrame { |
| 2017 InterfaceIdiom idiom = IsIPadIdiom() ? IPAD_IDIOM : IPHONE_IDIOM; |
| 2018 LayoutRect newOmniboxLayout; |
| 2019 // Grow the omnibox if focused. |
| 2020 BOOL growOmnibox = [_omniBox isFirstResponder]; |
| 2021 if (idiom == IPAD_IDIOM) { |
| 2022 // When the omnibox is focused, the star button is hidden. |
| 2023 [_starButton setAlpha:(growOmnibox ? 0 : 1)]; |
| 2024 |
| 2025 newOmniboxLayout = |
| 2026 LayoutRectForRectInBoundingRect([_omniBox frame], [_webToolbar bounds]); |
| 2027 CGFloat omniboxLeading = [self omniboxLeading]; |
| 2028 CGFloat omniboxLeadingDiff = |
| 2029 omniboxLeading - newOmniboxLayout.position.leading; |
| 2030 |
| 2031 newOmniboxLayout.position.leading = omniboxLeading; |
| 2032 newOmniboxLayout.size.width -= omniboxLeadingDiff; |
| 2033 } else { |
| 2034 // If the omnibox is growing, take over the whole toolbar area (standard |
| 2035 // toolbar controls will be hidden below). Temporarily suppress autoresizing |
| 2036 // to avoid interfering with the omnibox animation. |
| 2037 [_webToolbar setAutoresizesSubviews:NO]; |
| 2038 CGRect expandedFrame = |
| 2039 RectShiftedDownAndResizedForStatusBar(self.view.bounds); |
| 2040 [_webToolbar |
| 2041 setFrame:growOmnibox ? expandedFrame : [self specificControlsArea]]; |
| 2042 [_webToolbar setAutoresizesSubviews:YES]; |
| 2043 |
| 2044 // Compute new omnibox layout after the web toolbar is resized. |
| 2045 newOmniboxLayout = |
| 2046 LayoutRectForRectInBoundingRect([_omniBox frame], [_webToolbar bounds]); |
| 2047 |
| 2048 if (growOmnibox) { |
| 2049 // If the omnibox is expanded, there is padding on both the left and right |
| 2050 // sides. |
| 2051 newOmniboxLayout.position.leading = kExpandedOmniboxPadding; |
| 2052 // When the |_webToolbar| is resized without autoresizes subviews enabled |
| 2053 // the cancel button does not pin to the trailing side and is out of |
| 2054 // place. Lazily allocate and force a layout here. |
| 2055 [self layoutCancelButton]; |
| 2056 LayoutRect cancelButtonLayout = LayoutRectForRectInBoundingRect( |
| 2057 [self cancelButton].frame, [_webToolbar bounds]); |
| 2058 newOmniboxLayout.size.width = cancelButtonLayout.position.leading - |
| 2059 kExpandedOmniboxPadding - |
| 2060 newOmniboxLayout.position.leading; |
| 2061 // Likewise the incognito icon will not be pinned to the leading side, so |
| 2062 // force a layout here. |
| 2063 if (_incognitoIcon) |
| 2064 [self layoutIncognitoIcon]; |
| 2065 } else { |
| 2066 newOmniboxLayout.position.leading = [self omniboxLeading]; |
| 2067 newOmniboxLayout.size.width = CGRectGetWidth([_webToolbar frame]) - |
| 2068 newOmniboxLayout.position.leading; |
| 2069 } |
| 2070 } |
| 2071 |
| 2072 CGRect newOmniboxFrame = LayoutRectGetRect(newOmniboxLayout); |
| 2073 DCHECK(!CGRectEqualToRect(newOmniboxFrame, CGRectZero)); |
| 2074 |
| 2075 return newOmniboxFrame; |
| 2076 } |
| 2077 |
| 2078 - (void)animateMaterialOmnibox { |
| 2079 // The iPad omnibox does not animate. |
| 2080 if (IsIPadIdiom()) |
| 2081 return [self layoutOmnibox]; |
| 2082 |
| 2083 CGRect newOmniboxFrame = [self newOmniboxFrame]; |
| 2084 BOOL growOmnibox = [_omniBox isFirstResponder]; |
| 2085 |
| 2086 // Determine the starting and ending bounds and position for |_omniBox|. |
| 2087 // Increasing the height of _omniBox results in the text inside it jumping |
| 2088 // vertically during the animation, so the height change will not be animated. |
| 2089 CGRect fromBounds = [_omniBox layer].bounds; |
| 2090 LayoutRect toLayout = |
| 2091 LayoutRectForRectInBoundingRect(newOmniboxFrame, [_webToolbar bounds]); |
| 2092 CGRect toBounds = CGRectZero; |
| 2093 CGPoint fromPosition = [_omniBox layer].position; |
| 2094 CGPoint toPosition = fromPosition; |
| 2095 CGRect omniboxRect = LayoutRectGetRect(kOmniboxFrame[IPHONE_IDIOM]); |
| 2096 if (growOmnibox) { |
| 2097 toLayout.size = CGSizeMake([_webToolbar bounds].size.width - |
| 2098 self.cancelButton.frame.size.width - |
| 2099 kCancelButtonLeadingMargin, |
| 2100 omniboxRect.size.height); |
| 2101 toLayout.position.leading = 0; |
| 2102 if (_incognito) { |
| 2103 // Adjust the width and leading of the omnibox to account for the |
| 2104 // incognito icon. |
| 2105 // TODO(crbug/525943): Refactor so this value isn't calculated here, and |
| 2106 // instead is calculated in -newOmniboxFrame? |
| 2107 // (include in (crbug/525943) refactor). |
| 2108 LayoutRect incognitioIconLayout = LayoutRectForRectInBoundingRect( |
| 2109 [_incognitoIcon frame], [_webToolbar frame]); |
| 2110 CGFloat trailingEdge = LayoutRectGetTrailingEdge(incognitioIconLayout); |
| 2111 toLayout.size.width -= trailingEdge; |
| 2112 toLayout.position.leading = trailingEdge; |
| 2113 } |
| 2114 } else { |
| 2115 // Shrink the omnibox to its original width. |
| 2116 toLayout.size = |
| 2117 CGSizeMake(newOmniboxFrame.size.width, omniboxRect.size.height); |
| 2118 } |
| 2119 toBounds = LayoutRectGetBoundsRect(toLayout); |
| 2120 toPosition.x = |
| 2121 LayoutRectGetPositionForAnchor(toLayout, [_omniBox layer].anchorPoint).x; |
| 2122 |
| 2123 // Determine starting and ending bounds for |_omniboxBackground|. |
| 2124 // _omniboxBackground is needed to simulate the omnibox growing vertically and |
| 2125 // covering the entire height of the toolbar. |
| 2126 CGRect backgroundFromBounds = [_omniboxBackground layer].bounds; |
| 2127 CGRect backgroundToBounds = CGRectZero; |
| 2128 CGPoint backgroundFromPosition = [_omniboxBackground layer].position; |
| 2129 CGPoint backgroundToPosition = CGPointZero; |
| 2130 if (growOmnibox) { |
| 2131 // Grow the background to cover the whole toolbar. |
| 2132 backgroundToBounds = [_clippingView bounds]; |
| 2133 backgroundToPosition.x = backgroundToBounds.size.width / 2; |
| 2134 backgroundToPosition.y = backgroundToBounds.size.height / 2; |
| 2135 // Increase the bounds of the background so that the border extends past the |
| 2136 // toolbar and is clipped. |
| 2137 backgroundToBounds = CGRectInset(backgroundToBounds, -2, -2); |
| 2138 } else { |
| 2139 // Shrink the background to the same bounds as the omnibox. |
| 2140 backgroundToBounds = toBounds; |
| 2141 backgroundToPosition = toPosition; |
| 2142 backgroundToPosition.y += StatusBarHeight(); |
| 2143 } |
| 2144 |
| 2145 // Is the omnibox already at the new size? Then there's nothing to animate. |
| 2146 if (CGRectEqualToRect([_omniBox layer].bounds, toBounds)) |
| 2147 return; |
| 2148 |
| 2149 [self animateStandardControlsForOmniboxExpansion:growOmnibox]; |
| 2150 if (growOmnibox) { |
| 2151 [self fadeOutNavigationControls]; |
| 2152 [self fadeInOmniboxTrailingView]; |
| 2153 if (![_omniBox isShowingQueryRefinementChip]) { |
| 2154 // Don't animate the query refinement chip. |
| 2155 if (_locationBar.get()->IsShowingPlaceholderWhileCollapsed()) |
| 2156 [self fadeOutOmniboxLeadingView]; |
| 2157 else |
| 2158 [_omniBox leftView].alpha = 0; |
| 2159 } |
| 2160 if (_incognito) |
| 2161 [self fadeInIncognitoIcon]; |
| 2162 } else { |
| 2163 [self fadeInNavigationControls]; |
| 2164 [self fadeOutOmniboxTrailingView]; |
| 2165 if (![_omniBox isShowingQueryRefinementChip]) { |
| 2166 // Don't animate the query refinement chip. |
| 2167 if (_locationBar.get()->IsShowingPlaceholderWhileCollapsed()) |
| 2168 [self fadeInOmniboxLeadingView]; |
| 2169 else |
| 2170 [_omniBox leftView].alpha = 1; |
| 2171 } |
| 2172 if (_incognito) |
| 2173 [self fadeOutIncognitoIcon]; |
| 2174 } |
| 2175 |
| 2176 // Animate the new bounds and position for the omnibox and background. |
| 2177 [CATransaction begin]; |
| 2178 [CATransaction setCompletionBlock:^{ |
| 2179 // Re-layout the omnibox's subviews after the animation to allow VoiceOver |
| 2180 // to select the clear text button. |
| 2181 [_omniBox setNeedsLayout]; |
| 2182 }]; |
| 2183 CGFloat duration = ios::material::kDuration1; |
| 2184 // If app is on the regular New Tab Page, make this animation occur instantly |
| 2185 // since this page has a fakebox to omnibox transition. |
| 2186 web::WebState* webState = [self.delegate currentWebState]; |
| 2187 if (webState && webState->GetVisibleURL() == GURL(kChromeUINewTabURL) && |
| 2188 !_incognito) { |
| 2189 duration = 0.0; |
| 2190 } |
| 2191 [CATransaction setAnimationDuration:duration]; |
| 2192 [CATransaction |
| 2193 setAnimationTimingFunction:TimingFunction(ios::material::CurveEaseInOut)]; |
| 2194 |
| 2195 // TODO(crbug.com/525943): As part of animation cleanup, refactor |
| 2196 // these animations into groups produced by FrameAnimationMake() from |
| 2197 // animation_util, and do all of the bounds/position calculations above in |
| 2198 // terms of frames. |
| 2199 |
| 2200 CABasicAnimation* resizeAnimation = |
| 2201 [CABasicAnimation animationWithKeyPath:@"bounds"]; |
| 2202 resizeAnimation.delegate = self; |
| 2203 [resizeAnimation setValue:@"resizeOmnibox" forKey:@"id"]; |
| 2204 resizeAnimation.fromValue = [NSValue valueWithCGRect:fromBounds]; |
| 2205 resizeAnimation.toValue = [NSValue valueWithCGRect:toBounds]; |
| 2206 [_omniBox layer].bounds = toBounds; |
| 2207 [[_omniBox layer] addAnimation:resizeAnimation forKey:@"resizeBounds"]; |
| 2208 CABasicAnimation* positionAnimation = |
| 2209 [CABasicAnimation animationWithKeyPath:@"position"]; |
| 2210 positionAnimation.fromValue = [NSValue valueWithCGPoint:fromPosition]; |
| 2211 positionAnimation.toValue = [NSValue valueWithCGPoint:toPosition]; |
| 2212 [_omniBox layer].position = toPosition; |
| 2213 [[_omniBox layer] addAnimation:positionAnimation forKey:@"movePosition"]; |
| 2214 |
| 2215 resizeAnimation = [CABasicAnimation animationWithKeyPath:@"bounds"]; |
| 2216 resizeAnimation.fromValue = [NSValue valueWithCGRect:backgroundFromBounds]; |
| 2217 resizeAnimation.toValue = [NSValue valueWithCGRect:backgroundToBounds]; |
| 2218 [_omniboxBackground layer].bounds = backgroundToBounds; |
| 2219 [[_omniboxBackground layer] addAnimation:resizeAnimation |
| 2220 forKey:@"resizeBounds"]; |
| 2221 CABasicAnimation* backgroundPositionAnimation = |
| 2222 [CABasicAnimation animationWithKeyPath:@"position"]; |
| 2223 backgroundPositionAnimation.fromValue = |
| 2224 [NSValue valueWithCGPoint:backgroundFromPosition]; |
| 2225 backgroundPositionAnimation.toValue = |
| 2226 [NSValue valueWithCGPoint:backgroundToPosition]; |
| 2227 [_omniboxBackground layer].position = backgroundToPosition; |
| 2228 [[_omniboxBackground layer] addAnimation:backgroundPositionAnimation |
| 2229 forKey:@"movePosition"]; |
| 2230 [CATransaction commit]; |
| 2231 } |
| 2232 |
| 2233 - (void)fadeInOmniboxTrailingView { |
| 2234 UIView* trailingView = [_omniBox rightView]; |
| 2235 trailingView.alpha = 0; |
| 2236 [_cancelButton setAlpha:0]; |
| 2237 [_cancelButton setHidden:NO]; |
| 2238 |
| 2239 // Instead of passing a delay into -fadeInView:, wait to call -fadeInView:. |
| 2240 // The CABasicAnimation's start and end positions are calculated immediately |
| 2241 // instead of after the animation's delay, but the omnibox's layer isn't set |
| 2242 // yet to its final state and as a result the start and end positions will not |
| 2243 // be correct. |
| 2244 dispatch_time_t delay = dispatch_time( |
| 2245 DISPATCH_TIME_NOW, ios::material::kDuration6 * NSEC_PER_SEC); |
| 2246 dispatch_after(delay, dispatch_get_main_queue(), ^(void) { |
| 2247 [self fadeInView:trailingView |
| 2248 fromLeadingOffset:kPositionAnimationLeadingOffset |
| 2249 withDuration:ios::material::kDuration1 |
| 2250 afterDelay:0.2]; |
| 2251 [self fadeInView:_cancelButton |
| 2252 fromLeadingOffset:kPositionAnimationLeadingOffset |
| 2253 withDuration:ios::material::kDuration1 |
| 2254 afterDelay:0.1]; |
| 2255 }); |
| 2256 } |
| 2257 |
| 2258 - (void)fadeInOmniboxLeadingView { |
| 2259 UIView* leadingView = [_omniBox leftView]; |
| 2260 leadingView.alpha = 0; |
| 2261 // Instead of passing a delay into -fadeInView:, wait to call -fadeInView:. |
| 2262 // The CABasicAnimation's start and end positions are calculated immediately |
| 2263 // instead of after the animation's delay, but the omnibox's layer isn't set |
| 2264 // yet to its final state and as a result the start and end positions will not |
| 2265 // be correct. |
| 2266 dispatch_time_t delay = dispatch_time( |
| 2267 DISPATCH_TIME_NOW, ios::material::kDuration2 * NSEC_PER_SEC); |
| 2268 dispatch_after(delay, dispatch_get_main_queue(), ^(void) { |
| 2269 [self fadeInView:leadingView |
| 2270 fromLeadingOffset:kPositionAnimationLeadingOffset |
| 2271 withDuration:ios::material::kDuration1 |
| 2272 afterDelay:0]; |
| 2273 }); |
| 2274 } |
| 2275 |
| 2276 - (void)fadeOutOmniboxTrailingView { |
| 2277 UIView* trailingView = [_omniBox rightView]; |
| 2278 |
| 2279 // Animate the opacity of the trailingView to 0. |
| 2280 [CATransaction begin]; |
| 2281 [CATransaction setAnimationDuration:ios::material::kDuration4]; |
| 2282 [CATransaction |
| 2283 setAnimationTimingFunction:TimingFunction(ios::material::CurveEaseIn)]; |
| 2284 [CATransaction setCompletionBlock:^{ |
| 2285 [_cancelButton setHidden:YES]; |
| 2286 }]; |
| 2287 CABasicAnimation* fadeOut = |
| 2288 [CABasicAnimation animationWithKeyPath:@"opacity"]; |
| 2289 fadeOut.fromValue = @1; |
| 2290 fadeOut.toValue = @0; |
| 2291 trailingView.layer.opacity = 0; |
| 2292 [trailingView.layer addAnimation:fadeOut forKey:@"fade"]; |
| 2293 |
| 2294 // Animate the trailingView |kPositionAnimationLeadingOffset| pixels towards |
| 2295 // the |
| 2296 // leading edge. |
| 2297 CABasicAnimation* shift = [CABasicAnimation animationWithKeyPath:@"position"]; |
| 2298 CGPoint startPosition = [trailingView layer].position; |
| 2299 CGPoint endPosition = |
| 2300 CGPointLayoutOffset(startPosition, kPositionAnimationLeadingOffset); |
| 2301 shift.fromValue = [NSValue valueWithCGPoint:startPosition]; |
| 2302 shift.toValue = [NSValue valueWithCGPoint:endPosition]; |
| 2303 [[trailingView layer] addAnimation:shift forKey:@"shift"]; |
| 2304 |
| 2305 [_cancelButton layer].opacity = 0; |
| 2306 [[_cancelButton layer] addAnimation:fadeOut forKey:@"fade"]; |
| 2307 |
| 2308 CABasicAnimation* shiftCancelButton = |
| 2309 [CABasicAnimation animationWithKeyPath:@"position"]; |
| 2310 startPosition = [_cancelButton layer].position; |
| 2311 endPosition = |
| 2312 CGPointLayoutOffset(startPosition, kPositionAnimationLeadingOffset); |
| 2313 shiftCancelButton.fromValue = [NSValue valueWithCGPoint:startPosition]; |
| 2314 shiftCancelButton.toValue = [NSValue valueWithCGPoint:endPosition]; |
| 2315 [[_cancelButton layer] addAnimation:shiftCancelButton forKey:@"shift"]; |
| 2316 |
| 2317 [CATransaction commit]; |
| 2318 } |
| 2319 |
| 2320 - (void)fadeOutOmniboxLeadingView { |
| 2321 UIView* leadingView = [_omniBox leftView]; |
| 2322 |
| 2323 // Animate the opacity of leadingView to 0. |
| 2324 [CATransaction begin]; |
| 2325 [CATransaction setAnimationDuration:ios::material::kDuration2]; |
| 2326 [CATransaction |
| 2327 setAnimationTimingFunction:TimingFunction(ios::material::CurveEaseInOut)]; |
| 2328 CABasicAnimation* fadeOut = |
| 2329 [CABasicAnimation animationWithKeyPath:@"opacity"]; |
| 2330 fadeOut.fromValue = @1; |
| 2331 fadeOut.toValue = @0; |
| 2332 leadingView.layer.opacity = 0; |
| 2333 [leadingView.layer addAnimation:fadeOut forKey:@"fade"]; |
| 2334 |
| 2335 // Animate leadingView |kPositionAnimationLeadingOffset| pixels trailing. |
| 2336 CABasicAnimation* shift = [CABasicAnimation animationWithKeyPath:@"position"]; |
| 2337 CGPoint startPosition = [leadingView layer].position; |
| 2338 CGPoint endPosition = |
| 2339 CGPointLayoutOffset(startPosition, kPositionAnimationLeadingOffset); |
| 2340 shift.fromValue = [NSValue valueWithCGPoint:startPosition]; |
| 2341 shift.toValue = [NSValue valueWithCGPoint:endPosition]; |
| 2342 [[leadingView layer] addAnimation:shift forKey:@"shift"]; |
| 2343 [CATransaction commit]; |
| 2344 } |
| 2345 |
| 2346 - (void)fadeInIncognitoIcon { |
| 2347 DCHECK(_incognito); |
| 2348 DCHECK(!IsIPadIdiom()); |
| 2349 [self fadeInView:_incognitoIcon |
| 2350 fromLeadingOffset:-kPositionAnimationLeadingOffset |
| 2351 withDuration:ios::material::kDuration2 |
| 2352 afterDelay:ios::material::kDuration2]; |
| 2353 } |
| 2354 |
| 2355 - (void)fadeOutIncognitoIcon { |
| 2356 DCHECK(_incognito); |
| 2357 DCHECK(!IsIPadIdiom()); |
| 2358 |
| 2359 // Animate the opacity of the incognito icon to 0. |
| 2360 CALayer* layer = [_incognitoIcon layer]; |
| 2361 [CATransaction begin]; |
| 2362 [CATransaction setAnimationDuration:ios::material::kDuration4]; |
| 2363 [CATransaction |
| 2364 setAnimationTimingFunction:TimingFunction(ios::material::CurveEaseIn)]; |
| 2365 CABasicAnimation* fadeOut = |
| 2366 [CABasicAnimation animationWithKeyPath:@"opacity"]; |
| 2367 fadeOut.fromValue = @1; |
| 2368 fadeOut.toValue = @0; |
| 2369 layer.opacity = 0; |
| 2370 [layer addAnimation:fadeOut forKey:@"fade"]; |
| 2371 |
| 2372 // Animate the incognito icon |kPositionAnimationLeadingOffset| pixels |
| 2373 // trailing. |
| 2374 CABasicAnimation* shift = [CABasicAnimation animationWithKeyPath:@"position"]; |
| 2375 CGPoint startPosition = layer.position; |
| 2376 // Constant is a leading value, so invert it to move in the trailing |
| 2377 // direction. |
| 2378 CGPoint endPosition = |
| 2379 CGPointLayoutOffset(startPosition, -kPositionAnimationLeadingOffset); |
| 2380 shift.fromValue = [NSValue valueWithCGPoint:startPosition]; |
| 2381 shift.toValue = [NSValue valueWithCGPoint:endPosition]; |
| 2382 [layer addAnimation:shift forKey:@"shift"]; |
| 2383 [CATransaction commit]; |
| 2384 } |
| 2385 |
| 2386 - (void)fadeInNavigationControls { |
| 2387 // Determine which navigation buttons are visible and need to be animated. |
| 2388 UIView* firstView = nil; |
| 2389 UIView* secondView = nil; |
| 2390 if ([_forwardButton isEnabled]) { |
| 2391 firstView = _forwardButton; |
| 2392 secondView = _backButton; |
| 2393 } else { |
| 2394 firstView = _backButton; |
| 2395 } |
| 2396 |
| 2397 [self fadeInView:firstView |
| 2398 fromLeadingOffset:kPositionAnimationLeadingOffset |
| 2399 withDuration:ios::material::kDuration2 |
| 2400 afterDelay:ios::material::kDuration1]; |
| 2401 |
| 2402 if (secondView) { |
| 2403 [self fadeInView:secondView |
| 2404 fromLeadingOffset:kPositionAnimationLeadingOffset |
| 2405 withDuration:ios::material::kDuration2 |
| 2406 afterDelay:ios::material::kDuration3]; |
| 2407 } |
| 2408 } |
| 2409 |
| 2410 - (void)fadeOutNavigationControls { |
| 2411 [CATransaction begin]; |
| 2412 [CATransaction setAnimationDuration:ios::material::kDuration2]; |
| 2413 [CATransaction |
| 2414 setAnimationTimingFunction:TimingFunction(ios::material::CurveEaseIn)]; |
| 2415 CABasicAnimation* fadeButtons = |
| 2416 [CABasicAnimation animationWithKeyPath:@"opacity"]; |
| 2417 fadeButtons.fromValue = @1; |
| 2418 fadeButtons.toValue = @0; |
| 2419 if ([_forwardButton isEnabled]) { |
| 2420 [_forwardButton layer].opacity = 0; |
| 2421 [[_forwardButton layer] addAnimation:fadeButtons forKey:@"fadeOut"]; |
| 2422 } |
| 2423 [_backButton layer].opacity = 0; |
| 2424 [[_backButton layer] addAnimation:fadeButtons forKey:@"fadeOut"]; |
| 2425 [CATransaction commit]; |
| 2426 |
| 2427 // Animate the navigation buttons 10 pixels to the left. |
| 2428 [CATransaction begin]; |
| 2429 [CATransaction setAnimationDuration:ios::material::kDuration1]; |
| 2430 [CATransaction |
| 2431 setAnimationTimingFunction:TimingFunction(ios::material::CurveEaseIn)]; |
| 2432 CABasicAnimation* shiftBackButton = |
| 2433 [CABasicAnimation animationWithKeyPath:@"position"]; |
| 2434 CGPoint startPosition = [_backButton layer].position; |
| 2435 CGPoint endPosition = |
| 2436 CGPointLayoutOffset(startPosition, kPositionAnimationLeadingOffset); |
| 2437 shiftBackButton.fromValue = [NSValue valueWithCGPoint:startPosition]; |
| 2438 shiftBackButton.toValue = [NSValue valueWithCGPoint:endPosition]; |
| 2439 [[_backButton layer] addAnimation:shiftBackButton forKey:@"shiftButton"]; |
| 2440 CABasicAnimation* shiftForwardButton = |
| 2441 [CABasicAnimation animationWithKeyPath:@"position"]; |
| 2442 startPosition = [_forwardButton layer].position; |
| 2443 endPosition = |
| 2444 CGPointLayoutOffset(startPosition, kPositionAnimationLeadingOffset); |
| 2445 shiftForwardButton.fromValue = [NSValue valueWithCGPoint:startPosition]; |
| 2446 shiftForwardButton.toValue = [NSValue valueWithCGPoint:endPosition]; |
| 2447 [[_forwardButton layer] addAnimation:shiftForwardButton |
| 2448 forKey:@"shiftButton"]; |
| 2449 [CATransaction commit]; |
| 2450 } |
| 2451 |
| 2452 - (void)animationDidStop:(CAAnimation*)anim finished:(BOOL)flag { |
| 2453 if ([[anim valueForKey:@"id"] isEqual:@"resizeOmnibox"] && |
| 2454 ![_omniBox isFirstResponder]) { |
| 2455 [_omniBox setRightView:nil]; |
| 2456 } |
| 2457 } |
| 2458 |
| 2459 - (void)updateSnapshotWithWidth:(CGFloat)width forced:(BOOL)force { |
| 2460 // If |width| is 0, the current view's width is acceptable. |
| 2461 if (width < 1) |
| 2462 width = [self view].frame.size.width; |
| 2463 |
| 2464 // Snapshot is not used on the iPad. |
| 2465 if (IsIPadIdiom()) { |
| 2466 NOTREACHED(); |
| 2467 return; |
| 2468 } |
| 2469 // If the snapshot is valid, don't redraw. |
| 2470 if (_snapshot.get() && _snapshotHash == [self snapshotHashWithWidth:width]) |
| 2471 return; |
| 2472 |
| 2473 // Don't update the snapshot while the progress bar is moving, or while the |
| 2474 // tools menu is open, unless |force| is true. |
| 2475 BOOL shouldRedraw = force || ([self toolsPopupController] == nil && |
| 2476 [_determinateProgressView isHidden]); |
| 2477 if (!shouldRedraw) |
| 2478 return; |
| 2479 |
| 2480 if ([[self delegate] |
| 2481 respondsToSelector:@selector(willUpdateToolbarSnapshot)]) { |
| 2482 [[self delegate] willUpdateToolbarSnapshot]; |
| 2483 } |
| 2484 |
| 2485 // Temporarily resize the toolbar if necessary in order to match the desired |
| 2486 // width. (Such a mismatch can occur if the device has been rotated while this |
| 2487 // view was not visible, for example.) |
| 2488 CGRect frame = [self view].frame; |
| 2489 CGFloat oldWidth = frame.size.width; |
| 2490 frame.size.width = width; |
| 2491 [self view].frame = frame; |
| 2492 |
| 2493 UIGraphicsBeginImageContextWithOptions(frame.size, NO, 0.0); |
| 2494 [[self view].layer renderInContext:UIGraphicsGetCurrentContext()]; |
| 2495 _snapshot.reset([UIGraphicsGetImageFromCurrentImageContext() retain]); |
| 2496 UIGraphicsEndImageContext(); |
| 2497 |
| 2498 // In the past, when the current tab was prerendered, taking a snapshot |
| 2499 // sometimes lead to layout of its UIWebView. As this may be the fist time |
| 2500 // the UIWebViews was layed out, its scroll view was scrolled. This lead |
| 2501 // to scroll events that changed the frame of the toolbar when fullscreen |
| 2502 // was enabled. |
| 2503 // DCHECK that the toolbar frame does not change while taking a snapshot. |
| 2504 DCHECK_EQ(frame.origin.x, [self view].frame.origin.x); |
| 2505 DCHECK_EQ(frame.origin.y, [self view].frame.origin.y); |
| 2506 DCHECK_EQ(frame.size.width, [self view].frame.size.width); |
| 2507 DCHECK_EQ(frame.size.height, [self view].frame.size.height); |
| 2508 |
| 2509 frame.size.width = oldWidth; |
| 2510 [self view].frame = frame; |
| 2511 |
| 2512 _snapshotHash = [self snapshotHashWithWidth:width]; |
| 2513 } |
| 2514 |
| 2515 - (NSString*)updateTextForDotCom:(NSString*)text { |
| 2516 if ([text isEqualToString:kDotComTLD]) { |
| 2517 UITextRange* textRange = [_omniBox selectedTextRange]; |
| 2518 NSInteger pos = [_omniBox offsetFromPosition:[_omniBox beginningOfDocument] |
| 2519 toPosition:textRange.start]; |
| 2520 if (pos > 0 && [[_omniBox text] characterAtIndex:pos - 1] == '.') |
| 2521 return [kDotComTLD substringFromIndex:1]; |
| 2522 } |
| 2523 return text; |
| 2524 } |
| 2525 |
| 2526 - (void)pressKey:(id)sender { |
| 2527 DCHECK([sender isKindOfClass:[UIButton class]]); |
| 2528 [[UIDevice currentDevice] playInputClick]; |
| 2529 NSString* text = [self updateTextForDotCom:[sender currentTitle]]; |
| 2530 [_omniBox insertTextWhileEditing:text]; |
| 2531 } |
| 2532 |
| 2533 @end |
| 2534 |
| 2535 @implementation WebToolbarController (Testing) |
| 2536 |
| 2537 - (BOOL)isForwardButtonEnabled { |
| 2538 return [_forwardButton isEnabled]; |
| 2539 } |
| 2540 |
| 2541 - (BOOL)isBackButtonEnabled { |
| 2542 return [_backButton isEnabled]; |
| 2543 } |
| 2544 |
| 2545 - (BOOL)isStarButtonSelected { |
| 2546 return [_starButton isSelected]; |
| 2547 } |
| 2548 |
| 2549 - (void)setUnitTesting:(BOOL)unitTesting { |
| 2550 _unitTesting = unitTesting; |
| 2551 } |
| 2552 |
| 2553 - (CGFloat)loadProgressFraction { |
| 2554 return [_determinateProgressView progress]; |
| 2555 } |
| 2556 |
| 2557 - (std::string)getLocationText { |
| 2558 return base::UTF16ToUTF8([_omniBox displayedText]); |
| 2559 } |
| 2560 |
| 2561 - (BOOL)isLoading { |
| 2562 return ![_determinateProgressView isHidden]; |
| 2563 } |
| 2564 |
| 2565 - (BOOL)isPrerenderAnimationRunning { |
| 2566 return _prerenderAnimating; |
| 2567 } |
| 2568 |
| 2569 - (OmniboxTextFieldIOS*)omnibox { |
| 2570 return _omniBox.get(); |
| 2571 } |
| 2572 |
| 2573 @end |
OLD | NEW |