Index: ios/chrome/browser/ui/toolbar/web_toolbar_controller.mm |
diff --git a/ios/chrome/browser/ui/toolbar/web_toolbar_controller.mm b/ios/chrome/browser/ui/toolbar/web_toolbar_controller.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..48d04753790ffba50f48f50cb13942a9dba26f83 |
--- /dev/null |
+++ b/ios/chrome/browser/ui/toolbar/web_toolbar_controller.mm |
@@ -0,0 +1,2573 @@ |
+// Copyright 2012 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#import "ios/chrome/browser/ui/toolbar/web_toolbar_controller.h" |
+ |
+#import <CoreLocation/CoreLocation.h> |
+#include <QuartzCore/QuartzCore.h> |
+ |
+#include <stdint.h> |
+ |
+#include <algorithm> |
+#include <memory> |
+ |
+#include "base/command_line.h" |
+#include "base/ios/weak_nsobject.h" |
+#include "base/logging.h" |
+#include "base/mac/bundle_locations.h" |
+#include "base/mac/foundation_util.h" |
+#include "base/metrics/histogram.h" |
+#include "base/metrics/user_metrics.h" |
+#include "base/metrics/user_metrics_action.h" |
+#include "base/strings/sys_string_conversions.h" |
+#include "components/google/core/browser/google_util.h" |
+#include "components/omnibox/browser/omnibox_edit_model.h" |
+#include "components/reading_list/core/reading_list_switches.h" |
+#include "components/search_engines/util.h" |
+#include "components/strings/grit/components_strings.h" |
+#include "components/toolbar/toolbar_model.h" |
+#include "ios/chrome/browser/autocomplete/autocomplete_scheme_classifier_impl.h" |
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
+#include "ios/chrome/browser/chrome_url_constants.h" |
+#include "ios/chrome/browser/reading_list/reading_list_model_factory.h" |
+#include "ios/chrome/browser/search_engines/template_url_service_factory.h" |
+#import "ios/chrome/browser/tabs/tab.h" |
+#import "ios/chrome/browser/tabs/tab_model.h" |
+#import "ios/chrome/browser/ui/animation_util.h" |
+#import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h" |
+#import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h" |
+#import "ios/chrome/browser/ui/commands/generic_chrome_command.h" |
+#include "ios/chrome/browser/ui/commands/ios_command_ids.h" |
+#import "ios/chrome/browser/ui/history/tab_history_popup_controller.h" |
+#import "ios/chrome/browser/ui/image_util.h" |
+#import "ios/chrome/browser/ui/keyboard/hardware_keyboard_watcher.h" |
+#include "ios/chrome/browser/ui/omnibox/omnibox_view_ios.h" |
+#import "ios/chrome/browser/ui/reversed_animation.h" |
+#include "ios/chrome/browser/ui/rtl_geometry.h" |
+#import "ios/chrome/browser/ui/toolbar/toolbar_controller+protected.h" |
+#import "ios/chrome/browser/ui/toolbar/toolbar_controller.h" |
+#import "ios/chrome/browser/ui/toolbar/toolbar_model_ios.h" |
+#include "ios/chrome/browser/ui/toolbar/toolbar_resource_macros.h" |
+#include "ios/chrome/browser/ui/ui_util.h" |
+#import "ios/chrome/browser/ui/uikit_ui_util.h" |
+#import "ios/chrome/browser/ui/url_loader.h" |
+#import "ios/chrome/browser/ui/voice/text_to_speech_player.h" |
+#import "ios/chrome/browser/ui/voice/voice_search_notification_names.h" |
+#import "ios/chrome/common/material_timing.h" |
+#include "ios/chrome/grit/ios_strings.h" |
+#import "ios/public/provider/chrome/browser/chrome_browser_provider.h" |
+#import "ios/public/provider/chrome/browser/images/branded_image_provider.h" |
+#import "ios/public/provider/chrome/browser/voice/voice_search_provider.h" |
+#import "ios/third_party/material_components_ios/src/components/Palettes/src/MaterialPalettes.h" |
+#import "ios/third_party/material_components_ios/src/components/ProgressView/src/MaterialProgressView.h" |
+#import "ios/third_party/material_components_ios/src/components/Typography/src/MaterialTypography.h" |
+#include "ios/web/public/referrer.h" |
+#import "ios/web/public/web_state/web_state.h" |
+#import "net/base/mac/url_conversions.h" |
+#include "ui/base/l10n/l10n_util.h" |
+#include "ui/base/page_transition_types.h" |
+#include "ui/base/resource/resource_bundle.h" |
+#import "ui/gfx/ios/NSString+CrStringDrawing.h" |
+ |
+using base::UserMetricsAction; |
+using ios::material::TimingFunction; |
+ |
+NSString* const kTabHistoryPopupWillShowNotification = |
+ @"kTabHistoryPopupWillShowNotification"; |
+NSString* const kTabHistoryPopupWillHideNotification = |
+ @"kTabHistoryPopupWillHideNotification"; |
+const CGFloat kiPhoneOmniboxPlaceholderColorBrightness = 150 / 255.0; |
+ |
+// The histogram recording CLAuthorizationStatus for omnibox queries. |
+const char* const kOmniboxQueryLocationAuthorizationStatusHistogram = |
+ "Omnibox.QueryIosLocationAuthorizationStatus"; |
+// The number of possible CLAuthorizationStatus values to report. |
+const int kLocationAuthorizationStatusCount = 4; |
+ |
+namespace { |
+ |
+// The brightness of the toolbar's background color (visible on NTPs when the |
+// background view is hidden). |
+const CGFloat kNTPBackgroundColorBrightness = 1.0; |
+const CGFloat kNTPBackgroundColorBrightnessIncognito = 34.0 / 255.0; |
+ |
+// How far below the omnibox to position the popup. |
+const CGFloat kiPadOmniboxPopupVerticalOffset = 3; |
+ |
+// Padding to place on the sides of the omnibox when expanded. |
+const CGFloat kExpandedOmniboxPadding = 6; |
+ |
+// Padding between the back button and the omnibox when the forward button isn't |
+// displayed. |
+const CGFloat kBackButtonTrailingPadding = 7.0; |
+const CGFloat kForwardButtonTrailingPadding = -1.0; |
+const CGFloat kReloadButtonTrailingPadding = 4.0; |
+ |
+// Cancel button sizing. |
+const CGFloat kCancelButtonBottomMargin = 4.0; |
+const CGFloat kCancelButtonTopMargin = 4.0; |
+const CGFloat kCancelButtonLeadingMargin = 7.0; |
+const CGFloat kCancelButtonWidth = 40.0; |
+const CGFloat kIpadButtonTitleFontSize = 20.0; |
+const CGFloat kIphoneButtonTitleFontSize = 15.0; |
+ |
+// Additional offset to adjust the y coordinate of the determinate progress bar |
+// up by. |
+const CGFloat kDeterminateProgressBarYOffset = 1.0; |
+const CGFloat kMaterialProgressBarHeight = 2.0; |
+const CGFloat kLoadCompleteHideProgressBarDelay = 0.5; |
+// The default position animation is 10 pixels toward the trailing side, so |
+// that's a negative leading offset. |
+const LayoutOffset kPositionAnimationLeadingOffset = -10.0; |
+ |
+const CGFloat kIPadToolbarY = 53; |
+const CGFloat kScrollFadeDistance = 30; |
+// Offset from the image edge to the beginning of the visible omnibox rectangle. |
+// The image is symmetrical, so the offset is equal on each side. |
+const CGFloat kBackgroundImageVisibleRectOffset = 6; |
+ |
+enum { |
+ WebToolbarButtonNameBack = NumberOfToolbarButtonNames, |
+ WebToolbarButtonNameCallingApp, |
+ WebToolbarButtonNameForward, |
+ WebToolbarButtonNameReload, |
+ WebToolbarButtonNameStar, |
+ WebToolbarButtonNameStop, |
+ WebToolbarButtonNameTTS, |
+ WebToolbarButtonNameVoice, |
+ NumberOfWebToolbarButtonNames, |
+}; |
+ |
+const CGFloat kWebToolbarWidths[INTERFACE_IDIOM_COUNT] = {224, 720}; |
+// UI layouts. iPhone values followed by iPad values. |
+// Frame for the WebToolbar-specific container, which is a subview of the |
+// toolbar view itself. |
+// clang-format off |
+const LayoutRect kWebToolbarFrame[INTERFACE_IDIOM_COUNT] = { |
+ {kPortraitWidth[IPHONE_IDIOM], LayoutRectPositionZero, |
+ {kWebToolbarWidths[IPHONE_IDIOM], 56}}, |
+ {kPortraitWidth[IPAD_IDIOM], LayoutRectPositionZero, |
+ {kWebToolbarWidths[IPAD_IDIOM], 56}}, |
+}; |
+ |
+#define IPHONE_LAYOUT(L, Y, H, W) \ |
+ { kWebToolbarWidths[IPHONE_IDIOM], {L, Y}, {H, W} } |
+#define IPAD_LAYOUT(L, Y, H, W) \ |
+ { kWebToolbarWidths[IPAD_IDIOM], {L, Y}, {H, W} } |
+ |
+// Layouts inside the WebToolbar frame |
+const LayoutRect kOmniboxFrame[INTERFACE_IDIOM_COUNT] = { |
+ IPHONE_LAYOUT(55, 7, 169, 43), IPAD_LAYOUT(152, 7, 568, 43), |
+}; |
+const LayoutRect kBackButtonFrame[INTERFACE_IDIOM_COUNT] = { |
+ IPHONE_LAYOUT(0, 4, 48, 48), IPAD_LAYOUT(4, 4, 48, 48), |
+}; |
+const LayoutRect kForwardButtonFrame[INTERFACE_IDIOM_COUNT] = { |
+ IPHONE_LAYOUT(48, 4, 48, 48), IPAD_LAYOUT(52, 4, 48, 48), |
+}; |
+// clang-format on |
+ |
+// iPad-only layouts |
+// Layout for both the stop and reload buttons, which are in the same location. |
+const LayoutRect kStopReloadButtonFrame = IPAD_LAYOUT(100, 4, 48, 48); |
+const LayoutRect kStarButtonFrame = IPAD_LAYOUT(644, 4, 36, 48); |
+const LayoutRect kVoiceSearchButtonFrame = IPAD_LAYOUT(680, 4, 36, 48); |
+// Vertical distance between the y-coordinate of the omnibox's center and the |
+// y-coordinate of the web toolbar's center. |
+const CGFloat kOmniboxCenterOffsetY = 0.5; |
+ |
+// Last button in accessory view for keyboard, commonly used TLD. |
+NSString* const kDotComTLD = @".com"; |
+ |
+void RunBlockAfterDelay(NSTimeInterval delay, void (^block)(void)) { |
+ DCHECK([NSThread isMainThread]); |
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * delay), |
+ dispatch_get_main_queue(), block); |
+} |
+ |
+CGRect RectShiftedDownForStatusBar(CGRect rect) { |
+ if (IsIPadIdiom()) |
+ return rect; |
+ rect.origin.y += StatusBarHeight(); |
+ return rect; |
+} |
+ |
+CGRect RectShiftedUpForStatusBar(CGRect rect) { |
+ if (IsIPadIdiom()) |
+ return rect; |
+ rect.origin.y -= StatusBarHeight(); |
+ return rect; |
+} |
+ |
+CGRect RectShiftedUpAndResizedForStatusBar(CGRect rect) { |
+ if (IsIPadIdiom()) |
+ return rect; |
+ rect.size.height += StatusBarHeight(); |
+ return RectShiftedUpForStatusBar(rect); |
+} |
+ |
+CGRect RectShiftedDownAndResizedForStatusBar(CGRect rect) { |
+ if (IsIPadIdiom()) |
+ return rect; |
+ rect.size.height -= StatusBarHeight(); |
+ return RectShiftedDownForStatusBar(rect); |
+} |
+ |
+} // namespace |
+ |
+// View for the accessory view above the keyboard. Subclassed to allow playing |
+// input clicks when pressed. |
+@interface KeyboardAccessoryView : UIInputView<UIInputViewAudioFeedback> |
+@end |
+ |
+@implementation KeyboardAccessoryView |
+ |
+- (BOOL)enableInputClicksWhenVisible { |
+ return YES; |
+} |
+ |
+@end |
+ |
+// TODO(crbug.com/619982) Remove this block and add CAAnimationDelegate when we |
+// switch the main bots to Xcode 8. |
+#if defined(__IPHONE_10_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0) |
+@interface WebToolbarController ()<CAAnimationDelegate> |
+@end |
+#endif |
+ |
+@interface WebToolbarController ()<LocationBarDelegate, |
+ OmniboxPopupPositioner, |
+ ToolbarFrameDelegate> { |
+ // Top-level view for web content. |
+ base::scoped_nsobject<UIView> _webToolbar; |
+ base::scoped_nsobject<UIButton> _backButton; |
+ base::scoped_nsobject<UIButton> _forwardButton; |
+ base::scoped_nsobject<UIButton> _reloadButton; |
+ base::scoped_nsobject<UIButton> _stopButton; |
+ base::scoped_nsobject<UIButton> _starButton; |
+ base::scoped_nsobject<UIButton> _voiceSearchButton; |
+ base::scoped_nsobject<OmniboxTextFieldIOS> _omniBox; |
+ base::scoped_nsobject<UIButton> _cancelButton; |
+ base::scoped_nsobject<UIView> _keyBoardAccessoryView; |
+ base::scoped_nsobject<UIButton> _keyboardVoiceSearchButton; |
+ // Progress bar used to show what fraction of the page has loaded. |
+ base::scoped_nsobject<MDCProgressView> _determinateProgressView; |
+ base::scoped_nsobject<UIImageView> _omniboxBackground; |
+ BOOL _prerenderAnimating; |
+ base::scoped_nsobject<UIImageView> _incognitoIcon; |
+ base::scoped_nsobject<UIView> _clippingView; |
+ |
+ std::unique_ptr<LocationBarViewIOS> _locationBar; |
+ BOOL _initialLayoutComplete; |
+ // If |YES|, toolbar is incognito. |
+ BOOL _incognito; |
+ |
+ // If set to |YES|, disables animations that tests would otherwise trigger. |
+ BOOL _unitTesting; |
+ |
+ // If set to |YES|, text to speech is currently playing and the toolbar voice |
+ // icon should indicate so. |
+ BOOL _isTTSPlaying; |
+ |
+ // Keeps track of whether or not the back button's images have been reversed. |
+ ToolbarButtonMode _backButtonMode; |
+ |
+ // Keeps track of whether or not the forward button's images have been |
+ // reversed. |
+ ToolbarButtonMode _forwardButtonMode; |
+ |
+ // Keeps track of last known trait collection used by the subviews. |
+ base::scoped_nsobject<UITraitCollection> _lastKnownTraitCollection; |
+ |
+ // A snapshot of the current toolbar view. Only valid for phone, will be nil |
+ // if on tablet. |
+ base::scoped_nsobject<UIImage> _snapshot; |
+ // A hash of the state of the toolbar when the snapshot was taken. |
+ uint32_t _snapshotHash; |
+ |
+ // View controller for displaying tab history when the user long presses the |
+ // back or forward button. nil if not visible. |
+ base::scoped_nsobject<TabHistoryPopupController> _tabHistoryPopupController; |
+ |
+ // Hardware keyboard watcher, to detect the type of keyboard currently |
+ // attached. |
+ base::scoped_nsobject<HardwareKeyboardWatcher> _hardwareKeyboardWatcher; |
+ |
+ // The current browser state. |
+ ios::ChromeBrowserState* _browserState; // weak |
+} |
+ |
+// Accessor for cancel button. Handles lazy initialization. |
+- (UIButton*)cancelButton; |
+// Handler called after user pressed the cancel button. |
+- (void)cancelButtonPressed:(id)sender; |
+- (void)layoutCancelButton; |
+// Change the location bar dimensions according to the focus status. |
+// Also show/hide relevant buttons. |
+- (void)layoutOmnibox; |
+- (void)setBackButtonEnabled:(BOOL)enabled; |
+// Show or hide the forward button, animating the frame of the location bar to |
+// be in the right position. This can be called multiple times. |
+- (void)setForwardButtonEnabled:(BOOL)enabled; |
+- (void)startProgressBar; |
+- (void)stopProgressBar; |
+- (void)hideProgressBarAndTakeSnapshot; |
+- (void)showReloadButton; |
+- (void)showStopButton; |
+// Creates a hash of the state of the toolbar to know whether or not the cached |
+// snapshot is out of date. |
+// The hash takes into account any UI that may change the appearance of the |
+// toolbar. The one UI state it ignores is the stack view button's press |
+// state. That is because we want the snapshot to be valid when the stack is |
+// pressed, rather than creating a new snapshot exactly during the user |
+// interaction this snapshot is aimed to optimize. |
+- (uint32_t)snapshotHashWithWidth:(CGFloat)width; |
+// Called by long press gesture recognizer, used to display back/forward |
+// history. |
+- (void)handleLongPress:(UILongPressGestureRecognizer*)gesture; |
+- (void)setImagesForNavButton:(UIButton*)button |
+ withTabHistoryVisible:(BOOL)tabHistoryVisible; |
+// Received when a TTS player has received audio data. |
+- (void)audioReadyForPlayback:(NSNotification*)notification; |
+// Updates the TTS button depending on whether or not TTS is currently playing. |
+- (void)updateIsTTSPlaying:(NSNotification*)notify; |
+// Moves VoiceOver to the button used to perform a voice search. |
+- (void)moveVoiceOverToVoiceSearchButton; |
+// Fade in and out toolbar items as the frame moves off screen. |
+- (void)updateToolbarAlphaForFrame:(CGRect)frame; |
+// Navigate to |query| from omnibox. |
+- (void)loadURLForQuery:(NSString*)query; |
+- (UIView*)keyboardButtonWithTitle:(NSString*)title frame:(CGRect)frame; |
+// Lazily instantiate the keyboard accessory view. |
+- (UIView*)keyboardAccessoryView; |
+- (void)preloadVoiceSearch:(id)sender; |
+// Calculates the CGRect to use for the omnibox's frame. Also sets the frames |
+// of some buttons and |_webToolbar|. |
+- (CGRect)newOmniboxFrame; |
+- (void)animateMaterialOmnibox; |
+- (void)fadeInOmniboxTrailingView; |
+- (void)fadeInOmniboxLeadingView; |
+- (void)fadeOutOmniboxTrailingView; |
+- (void)fadeOutOmniboxLeadingView; |
+- (void)fadeInIncognitoIcon; |
+- (void)fadeOutIncognitoIcon; |
+// Fade in the visible navigation buttons. |
+- (void)fadeInNavigationControls; |
+// Fade out the visible navigation buttons. |
+- (void)fadeOutNavigationControls; |
+// When the collapse animation is complete, hide the Material background and |
+// restore the omnibox's background image. |
+- (void)animationDidStop:(CAAnimation*)anim finished:(BOOL)flag; |
+- (void)updateSnapshotWithWidth:(CGFloat)width forced:(BOOL)force; |
+// Insert 'com' without the period if cursor is directly after a period. |
+- (NSString*)updateTextForDotCom:(NSString*)text; |
+// Handle the user pressing a key in the keyboard accessory view. |
+- (void)pressKey:(id)sender; |
+@end |
+ |
+@implementation WebToolbarController |
+ |
+@synthesize delegate = _delegate; |
+@synthesize urlLoader = _urlLoader; |
+ |
+- (instancetype)initWithDelegate:(id<WebToolbarDelegate>)delegate |
+ urlLoader:(id<UrlLoader>)urlLoader |
+ browserState:(ios::ChromeBrowserState*)browserState |
+ preloadProvider:(id<PreloadProvider>)preloader { |
+ DCHECK(delegate); |
+ DCHECK(urlLoader); |
+ DCHECK(browserState); |
+ _delegate = delegate; |
+ _urlLoader = urlLoader; |
+ _browserState = browserState; |
+ _incognito = browserState->IsOffTheRecord(); |
+ self = [super initWithStyle:(_incognito ? ToolbarControllerStyleIncognitoMode |
+ : ToolbarControllerStyleLightMode)]; |
+ if (!self) |
+ return nil; |
+ |
+ if (reading_list::switches::IsReadingListEnabled()) { |
+ self.readingListModel = |
+ ReadingListModelFactory::GetForBrowserState(browserState); |
+ } |
+ |
+ InterfaceIdiom idiom = IsIPadIdiom() ? IPAD_IDIOM : IPHONE_IDIOM; |
+ // Note that |_webToolbar| gets its frame set to -specificControlArea later in |
+ // this method. |
+ _webToolbar.reset([[UIView alloc] |
+ initWithFrame:LayoutRectGetRect(kWebToolbarFrame[idiom])]); |
+ UIColor* textColor = |
+ _incognito |
+ ? [UIColor whiteColor] |
+ : [UIColor colorWithWhite:0 alpha:[MDCTypography body1FontOpacity]]; |
+ UIColor* tintColor = _incognito ? textColor : nil; |
+ CGRect omniboxRect = LayoutRectGetRect(kOmniboxFrame[idiom]); |
+ _omniBox.reset([[OmniboxTextFieldIOS alloc] |
+ initWithFrame:omniboxRect |
+ font:[MDCTypography subheadFont] |
+ textColor:textColor |
+ tintColor:tintColor]); |
+ if (_incognito) { |
+ [_omniBox setIncognito:YES]; |
+ [_omniBox |
+ setSelectedTextBackgroundColor:[UIColor colorWithWhite:1 alpha:0.1]]; |
+ [_omniBox setPlaceholderTextColor:[UIColor colorWithWhite:1 alpha:0.5]]; |
+ } else if (!IsIPadIdiom()) { |
+ // Set placeholder text color to match fakebox placeholder text color when |
+ // on iPhone and in regular mode. |
+ UIColor* placeholderTextColor = |
+ [UIColor colorWithWhite:kiPhoneOmniboxPlaceholderColorBrightness |
+ alpha:1.0]; |
+ [_omniBox setPlaceholderTextColor:placeholderTextColor]; |
+ } |
+ _backButton.reset([[UIButton alloc] |
+ initWithFrame:LayoutRectGetRect(kBackButtonFrame[idiom])]); |
+ [_backButton setAutoresizingMask:UIViewAutoresizingFlexibleTrailingMargin() | |
+ UIViewAutoresizingFlexibleTopMargin | |
+ UIViewAutoresizingFlexibleBottomMargin]; |
+ // Note that the forward button gets repositioned when -layoutOmnibox is |
+ // called. |
+ _forwardButton.reset([[UIButton alloc] |
+ initWithFrame:LayoutRectGetRect(kForwardButtonFrame[idiom])]); |
+ [_forwardButton |
+ setAutoresizingMask:UIViewAutoresizingFlexibleTrailingMargin() | |
+ UIViewAutoresizingFlexibleBottomMargin]; |
+ |
+ [_webToolbar addSubview:_backButton]; |
+ [_webToolbar addSubview:_forwardButton]; |
+ |
+ // _omniboxBackground needs to be added under _omniBox so as not to cover up |
+ // _omniBox. |
+ _omniboxBackground.reset([[UIImageView alloc] initWithFrame:omniboxRect]); |
+ [_omniboxBackground |
+ setAutoresizingMask:UIViewAutoresizingFlexibleWidth | |
+ UIViewAutoresizingFlexibleBottomMargin]; |
+ |
+ if (idiom == IPAD_IDIOM) { |
+ [_webToolbar addSubview:_omniboxBackground]; |
+ } else { |
+ [_backButton setImageEdgeInsets:UIEdgeInsetsMakeDirected(0, 0, 0, -9)]; |
+ [_forwardButton setImageEdgeInsets:UIEdgeInsetsMakeDirected(0, -7, 0, 0)]; |
+ CGRect clippingFrame = |
+ RectShiftedUpAndResizedForStatusBar(kToolbarFrame[idiom]); |
+ _clippingView.reset([[UIView alloc] initWithFrame:clippingFrame]); |
+ [_clippingView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | |
+ UIViewAutoresizingFlexibleBottomMargin]; |
+ [_clippingView setClipsToBounds:YES]; |
+ [_clippingView setUserInteractionEnabled:NO]; |
+ [_webToolbar addSubview:_clippingView]; |
+ |
+ CGRect omniboxBackgroundFrame = |
+ RectShiftedDownForStatusBar([_omniboxBackground frame]); |
+ [_omniboxBackground setFrame:omniboxBackgroundFrame]; |
+ [_clippingView addSubview:_omniboxBackground]; |
+ [self.view |
+ setBackgroundColor:[UIColor colorWithWhite:kNTPBackgroundColorBrightness |
+ alpha:1.0]]; |
+ |
+ if (_incognito) { |
+ [self.view |
+ setBackgroundColor: |
+ [UIColor colorWithWhite:kNTPBackgroundColorBrightnessIncognito |
+ alpha:1.0]]; |
+ _incognitoIcon.reset([[UIImageView alloc] |
+ initWithImage:[UIImage imageNamed:@"incognito_marker_typing"]]); |
+ [_incognitoIcon setAlpha:0]; |
+ [_incognitoIcon |
+ setAutoresizingMask:UIViewAutoresizingFlexibleTrailingMargin()]; |
+ [self layoutIncognitoIcon]; |
+ [_webToolbar addSubview:_incognitoIcon]; |
+ } |
+ } |
+ |
+ [_webToolbar addSubview:_omniBox]; |
+ |
+ [_backButton setEnabled:NO]; |
+ [_forwardButton setEnabled:NO]; |
+ |
+ if (idiom == IPAD_IDIOM) { |
+ // Note that the reload button gets repositioned when -layoutOmnibox is |
+ // called. |
+ _reloadButton.reset([[UIButton alloc] |
+ initWithFrame:LayoutRectGetRect(kStopReloadButtonFrame)]); |
+ [_reloadButton |
+ setAutoresizingMask:UIViewAutoresizingFlexibleTrailingMargin() | |
+ UIViewAutoresizingFlexibleBottomMargin]; |
+ _stopButton.reset([[UIButton alloc] |
+ initWithFrame:LayoutRectGetRect(kStopReloadButtonFrame)]); |
+ [_stopButton |
+ setAutoresizingMask:UIViewAutoresizingFlexibleTrailingMargin() | |
+ UIViewAutoresizingFlexibleBottomMargin]; |
+ _starButton.reset( |
+ [[UIButton alloc] initWithFrame:LayoutRectGetRect(kStarButtonFrame)]); |
+ [_starButton setAutoresizingMask:UIViewAutoresizingFlexibleBottomMargin | |
+ UIViewAutoresizingFlexibleLeadingMargin()]; |
+ _voiceSearchButton.reset([[UIButton alloc] |
+ initWithFrame:LayoutRectGetRect(kVoiceSearchButtonFrame)]); |
+ [_voiceSearchButton |
+ setAutoresizingMask:UIViewAutoresizingFlexibleBottomMargin | |
+ UIViewAutoresizingFlexibleLeadingMargin()]; |
+ [_webToolbar addSubview:_voiceSearchButton]; |
+ [_webToolbar addSubview:_starButton]; |
+ [_webToolbar addSubview:_stopButton]; |
+ [_webToolbar addSubview:_reloadButton]; |
+ [self setUpButton:_voiceSearchButton |
+ withImageEnum:WebToolbarButtonNameVoice |
+ forInitialState:UIControlStateNormal |
+ hasDisabledImage:NO |
+ synchronously:NO]; |
+ [self setUpButton:_starButton |
+ withImageEnum:WebToolbarButtonNameStar |
+ forInitialState:UIControlStateNormal |
+ hasDisabledImage:NO |
+ synchronously:YES]; |
+ [self setUpButton:_stopButton |
+ withImageEnum:WebToolbarButtonNameStop |
+ forInitialState:UIControlStateDisabled |
+ hasDisabledImage:YES |
+ synchronously:NO]; |
+ [self setUpButton:_reloadButton |
+ withImageEnum:WebToolbarButtonNameReload |
+ forInitialState:UIControlStateNormal |
+ hasDisabledImage:YES |
+ synchronously:NO]; |
+ [_stopButton setHidden:YES]; |
+ } else { |
+ [_forwardButton setAlpha:0.0]; |
+ } |
+ |
+ // Set up the button images and omnibox background. |
+ [self setUpButton:_backButton |
+ withImageEnum:WebToolbarButtonNameBack |
+ forInitialState:UIControlStateDisabled |
+ hasDisabledImage:YES |
+ synchronously:NO]; |
+ [self setUpButton:_forwardButton |
+ withImageEnum:WebToolbarButtonNameForward |
+ forInitialState:UIControlStateDisabled |
+ hasDisabledImage:YES |
+ synchronously:NO]; |
+ |
+ _backButtonMode = ToolbarButtonModeNormal; |
+ _forwardButtonMode = ToolbarButtonModeNormal; |
+ base::scoped_nsobject<UILongPressGestureRecognizer> backLongPress( |
+ [[UILongPressGestureRecognizer alloc] |
+ initWithTarget:self |
+ action:@selector(handleLongPress:)]); |
+ [_backButton addGestureRecognizer:backLongPress]; |
+ base::scoped_nsobject<UILongPressGestureRecognizer> forwardLongPress( |
+ [[UILongPressGestureRecognizer alloc] |
+ initWithTarget:self |
+ action:@selector(handleLongPress:)]); |
+ [_forwardButton addGestureRecognizer:forwardLongPress]; |
+ |
+ // TODO(leng): Consider moving this to a pak file as well. For now, |
+ // because it is also used by find_bar_controller_ios, leave it as is. |
+ NSString* imageName = |
+ _incognito ? @"omnibox_transparent_background" : @"omnibox_background"; |
+ [_omniboxBackground setImage:StretchableImageNamed(imageName, 12, 12)]; |
+ [_omniBox setAutoresizingMask:UIViewAutoresizingFlexibleWidth | |
+ UIViewAutoresizingFlexibleBottomMargin]; |
+ [_reloadButton addTarget:self |
+ action:@selector(cancelOmniboxEdit) |
+ forControlEvents:UIControlEventTouchUpInside]; |
+ [_stopButton addTarget:self |
+ action:@selector(cancelOmniboxEdit) |
+ forControlEvents:UIControlEventTouchUpInside]; |
+ |
+ [_backButton setTag:IDC_BACK]; |
+ [_forwardButton setTag:IDC_FORWARD]; |
+ [_reloadButton setTag:IDC_RELOAD]; |
+ [_stopButton setTag:IDC_STOP]; |
+ [_starButton setTag:IDC_BOOKMARK_PAGE]; |
+ [_voiceSearchButton setTag:IDC_VOICE_SEARCH]; |
+ |
+ SetA11yLabelAndUiAutomationName(_backButton, IDS_ACCNAME_BACK, @"Back"); |
+ SetA11yLabelAndUiAutomationName(_forwardButton, IDS_ACCNAME_FORWARD, |
+ @"Forward"); |
+ SetA11yLabelAndUiAutomationName(_reloadButton, IDS_IOS_ACCNAME_RELOAD, |
+ @"Reload"); |
+ SetA11yLabelAndUiAutomationName(_stopButton, IDS_IOS_ACCNAME_STOP, @"Stop"); |
+ SetA11yLabelAndUiAutomationName(_starButton, IDS_TOOLTIP_STAR, @"Bookmark"); |
+ SetA11yLabelAndUiAutomationName( |
+ _voiceSearchButton, IDS_IOS_ACCNAME_VOICE_SEARCH, @"Voice Search"); |
+ SetA11yLabelAndUiAutomationName(_omniBox, IDS_ACCNAME_LOCATION, @"Address"); |
+ |
+ // Resize the container to match the available area. |
+ [self.view addSubview:_webToolbar]; |
+ [_webToolbar setAutoresizingMask:UIViewAutoresizingFlexibleWidth | |
+ UIViewAutoresizingFlexibleBottomMargin]; |
+ [_webToolbar setFrame:[self specificControlsArea]]; |
+ _locationBar.reset( |
+ new LocationBarViewIOS(_omniBox, _browserState, preloader, self, self)); |
+ |
+ // Create the determinate progress bar (phone only). |
+ if (idiom == IPHONE_IDIOM) { |
+ CGFloat progressWidth = self.view.frame.size.width; |
+ CGFloat progressHeight = 0; |
+ progressHeight = kMaterialProgressBarHeight; |
+ _determinateProgressView.reset([[MDCProgressView alloc] init]); |
+ _determinateProgressView.get().hidden = YES; |
+ [_determinateProgressView |
+ setProgressTintColor:[MDCPalette cr_bluePalette].tint500]; |
+ [_determinateProgressView |
+ setTrackTintColor:[MDCPalette cr_bluePalette].tint100]; |
+ int progressBarYOffset = |
+ floor(progressHeight / 2) + kDeterminateProgressBarYOffset; |
+ int progressBarY = self.view.bounds.size.height - progressBarYOffset; |
+ CGRect progressBarFrame = |
+ CGRectMake(0, progressBarY, progressWidth, progressHeight); |
+ [_determinateProgressView setFrame:progressBarFrame]; |
+ [self.view addSubview:_determinateProgressView]; |
+ } |
+ |
+ // Attach the spacebar view to the omnibox. |
+ [_omniBox setInputAccessoryView:[self keyboardAccessoryView]]; |
+ |
+ // Add the handler to preload voice search when the voice search button is |
+ // tapped, but only if voice search is enabled. |
+ if (ios::GetChromeBrowserProvider() |
+ ->GetVoiceSearchProvider() |
+ ->IsVoiceSearchEnabled()) { |
+ [_voiceSearchButton addTarget:self |
+ action:@selector(preloadVoiceSearch:) |
+ forControlEvents:UIControlEventTouchDown]; |
+ } else { |
+ [_voiceSearchButton setEnabled:NO]; |
+ } |
+ |
+ // Register for text-to-speech (TTS) events on tablet. |
+ if (idiom == IPAD_IDIOM) { |
+ NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter]; |
+ [defaultCenter addObserver:self |
+ selector:@selector(audioReadyForPlayback:) |
+ name:kTTSAudioReadyForPlaybackNotification |
+ object:nil]; |
+ [defaultCenter addObserver:self |
+ selector:@selector(updateIsTTSPlaying:) |
+ name:kTTSWillStartPlayingNotification |
+ object:nil]; |
+ [defaultCenter addObserver:self |
+ selector:@selector(updateIsTTSPlaying:) |
+ name:kTTSDidStopPlayingNotification |
+ object:nil]; |
+ [defaultCenter addObserver:self |
+ selector:@selector(moveVoiceOverToVoiceSearchButton) |
+ name:kVoiceSearchWillHideNotification |
+ object:nil]; |
+ } |
+ [self.view setDelegate:self]; |
+ |
+ return self; |
+} |
+ |
+- (void)browserStateDestroyed { |
+ // The location bar has a browser state reference, so must be destroyed at |
+ // this point. |
+ _locationBar.reset(); |
+ _browserState = nullptr; |
+} |
+ |
+- (void)dealloc { |
+ [[NSNotificationCenter defaultCenter] removeObserver:self]; |
+ [_tabHistoryPopupController setDelegate:nil]; |
+ [super dealloc]; |
+} |
+ |
+#pragma mark - |
+#pragma mark Public methods. |
+ |
+- (void)updateToolbarState { |
+ ToolbarModelIOS* toolbarModelIOS = [self.delegate toolbarModelIOS]; |
+ if (toolbarModelIOS->IsLoading()) { |
+ [self showStopButton]; |
+ [self startProgressBar]; |
+ [_determinateProgressView |
+ setProgress:toolbarModelIOS->GetLoadProgressFraction() |
+ animated:YES |
+ completion:nil]; |
+ } else { |
+ [self stopProgressBar]; |
+ [self showReloadButton]; |
+ } |
+ |
+ _locationBar->SetShouldShowHintText(toolbarModelIOS->ShouldDisplayHintText()); |
+ _locationBar->OnToolbarUpdated(); |
+ BOOL forwardButtonEnabled = toolbarModelIOS->CanGoForward(); |
+ [self setBackButtonEnabled:toolbarModelIOS->CanGoBack()]; |
+ [self setForwardButtonEnabled:forwardButtonEnabled]; |
+ |
+ // Update the bookmarked "starred" state of current tab. |
+ [_starButton setSelected:toolbarModelIOS->IsCurrentTabBookmarked()]; |
+ |
+ if (!_initialLayoutComplete) |
+ _initialLayoutComplete = YES; |
+ if (!toolbarModelIOS->IsLoading() && !IsIPadIdiom()) |
+ [self updateSnapshotWithWidth:0 forced:NO]; |
+} |
+ |
+- (void)updateToolbarForSideSwipeSnapshot:(Tab*)tab { |
+ web::WebState* webState = tab.webState; |
+ BOOL isCurrentTab = webState == [self.delegate currentWebState]; |
+ BOOL isNTP = webState->GetVisibleURL() == GURL(kChromeUINewTabURL); |
+ |
+ // Don't do anything for a live non-ntp tab. |
+ if (isCurrentTab && !isNTP) { |
+ // This has the effect of making any alpha-ed out items visible. |
+ [self updateToolbarAlphaForFrame:CGRectZero]; |
+ [_omniBox setHidden:NO]; |
+ return; |
+ } |
+ |
+ [[self view] setHidden:NO]; |
+ [_determinateProgressView setHidden:YES]; |
+ BOOL forwardEnabled = tab.canGoForward; |
+ [_backButton setHidden:isNTP ? !forwardEnabled : NO]; |
+ [_backButton setEnabled:tab.canGoBack]; |
+ [_forwardButton setHidden:!forwardEnabled]; |
+ [_forwardButton setEnabled:forwardEnabled]; |
+ [_omniBox setHidden:YES]; |
+ [self.backgroundView setAlpha:isNTP ? 0 : 1]; |
+ [_omniboxBackground setHidden:isNTP ? YES : NO]; |
+ [self hideViewsForNewTabPage:isNTP ? YES : NO]; |
+ [self layoutOmnibox]; |
+} |
+ |
+- (void)resetToolbarAfterSideSwipeSnapshot { |
+ [_omniBox setHidden:NO]; |
+ [_backButton setHidden:NO]; |
+ [_forwardButton setHidden:NO]; |
+ [_omniboxBackground setHidden:NO]; |
+ [self.backgroundView setAlpha:1]; |
+ [self hideViewsForNewTabPage:NO]; |
+ [self updateToolbarState]; |
+} |
+ |
+- (void)showPrerenderingAnimation { |
+ _prerenderAnimating = YES; |
+ [_determinateProgressView setProgress:0]; |
+ [_determinateProgressView setHidden:NO |
+ animated:YES |
+ completion:^(BOOL finished) { |
+ [_determinateProgressView |
+ setProgress:1 |
+ animated:YES |
+ completion:^(BOOL finished) { |
+ [_determinateProgressView setHidden:YES |
+ animated:YES |
+ completion:nil]; |
+ }]; |
+ }]; |
+} |
+ |
+- (void)setControlsHidden:(BOOL)hidden { |
+ [self setStandardControlsVisible:!hidden]; |
+ [_webToolbar setHidden:hidden]; |
+} |
+ |
+- (void)setControlsAlpha:(CGFloat)alpha { |
+ [self setStandardControlsAlpha:alpha]; |
+ [_webToolbar setAlpha:alpha]; |
+} |
+ |
+- (void)currentPageLoadStarted { |
+ [self startProgressBar]; |
+} |
+ |
+- (void)selectedTabChanged { |
+ [self cancelOmniboxEdit]; |
+} |
+ |
+- (CGRect)bookmarkButtonAnchorRect { |
+ // Shrink the padding around the bookmark button so the popovers are anchored |
+ // correctly. |
+ return CGRectInset([_starButton bounds], 6, 11); |
+} |
+ |
+- (UIView*)bookmarkButtonView { |
+ return _starButton.get(); |
+} |
+ |
+- (CGRect)visibleOmniboxFrame { |
+ CGRect frame = _omniboxBackground.get().frame; |
+ frame = [self.view.superview convertRect:frame |
+ fromView:[_omniboxBackground superview]]; |
+ // Account for the omnibox background image transparent sides. |
+ return CGRectInset(frame, -kBackgroundImageVisibleRectOffset, 0); |
+} |
+ |
+- (UIImage*)snapshotWithWidth:(CGFloat)width { |
+ if (IsIPadIdiom()) |
+ return nil; |
+ // Below call will be no-op if cached snapshot is valid. |
+ [self updateSnapshotWithWidth:width forced:YES]; |
+ return [[_snapshot retain] autorelease]; |
+} |
+ |
+- (void)showTabHistoryPopupInView:(UIView*)view |
+ withSessionEntries:(NSArray*)sessionEntries |
+ forBackHistory:(BOOL)isBackHistory { |
+ if (_tabHistoryPopupController) |
+ return; |
+ |
+ base::RecordAction(UserMetricsAction("MobileToolbarShowTabHistoryMenu")); |
+ |
+ UIButton* historyButton = isBackHistory ? _backButton : _forwardButton; |
+ // Keep the button pressed by swapping the normal and highlighted images. |
+ [self setImagesForNavButton:historyButton withTabHistoryVisible:YES]; |
+ |
+ // Set the origin for the tools popup to the leading side of the bottom of the |
+ // pressed buttons. |
+ CGRect buttonBounds = [historyButton.imageView bounds]; |
+ CGPoint origin = CGPointMake(CGRectGetLeadingEdge(buttonBounds), |
+ CGRectGetMaxY(buttonBounds)); |
+ CGPoint convertedOrigin = |
+ [view convertPoint:origin fromView:historyButton.imageView]; |
+ _tabHistoryPopupController.reset([[TabHistoryPopupController alloc] |
+ initWithOrigin:convertedOrigin |
+ parentView:view |
+ entries:sessionEntries]); |
+ [_tabHistoryPopupController setDelegate:self]; |
+ |
+ [[NSNotificationCenter defaultCenter] |
+ postNotificationName:kTabHistoryPopupWillShowNotification |
+ object:nil]; |
+} |
+ |
+- (void)dismissTabHistoryPopup { |
+ if (!_tabHistoryPopupController) |
+ return; |
+ TabHistoryPopupController* tempTHPC = _tabHistoryPopupController.get(); |
+ [tempTHPC containerView].userInteractionEnabled = NO; |
+ [tempTHPC dismissAnimatedWithCompletion:^{ |
+ // Unpress the back/forward button by restoring the normal and |
+ // highlighted images to their usual state. |
+ [self setImagesForNavButton:_backButton withTabHistoryVisible:NO]; |
+ [self setImagesForNavButton:_forwardButton withTabHistoryVisible:NO]; |
+ // Reference tempTHPC so the block retains it. |
+ [tempTHPC self]; |
+ }]; |
+ // reset _tabHistoryPopupController to prevent -applicationDidEnterBackground |
+ // from posting another kTabHistoryPopupWillHideNotification. |
+ _tabHistoryPopupController.reset(); |
+ |
+ [[NSNotificationCenter defaultCenter] |
+ postNotificationName:kTabHistoryPopupWillHideNotification |
+ object:nil]; |
+} |
+ |
+- (BOOL)isOmniboxFirstResponder { |
+ return [_omniBox isFirstResponder]; |
+} |
+ |
+- (BOOL)showingOmniboxPopup { |
+ OmniboxViewIOS* omniboxViewIOS = |
+ static_cast<OmniboxViewIOS*>(_locationBar.get()->GetLocationEntry()); |
+ return omniboxViewIOS->IsPopupOpen(); |
+} |
+ |
+- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection { |
+ [super traitCollectionDidChange:previousTraitCollection]; |
+ _lastKnownTraitCollection.reset([[UITraitCollection |
+ traitCollectionWithTraitsFromCollections:@[ self.view.traitCollection ]] |
+ retain]); |
+ if (IsIPadIdiom()) { |
+ // Update toolbar accessory views. |
+ BOOL isCompactTabletView = IsCompactTablet(self.view); |
+ [_voiceSearchButton setHidden:isCompactTabletView]; |
+ [_starButton setHidden:isCompactTabletView]; |
+ [_reloadButton setHidden:isCompactTabletView]; |
+ [_stopButton setHidden:isCompactTabletView]; |
+ [self updateToolbarState]; |
+ |
+ // Update keyboard accessory views. |
+ BOOL hidden = [_keyboardVoiceSearchButton isHidden]; |
+ _keyBoardAccessoryView.reset(); |
+ [_omniBox setInputAccessoryView:[self keyboardAccessoryView]]; |
+ [_keyboardVoiceSearchButton setHidden:hidden]; |
+ if ([_omniBox isFirstResponder]) { |
+ [_omniBox reloadInputViews]; |
+ } |
+ |
+ // Re-layout toolbar and omnibox. |
+ [_webToolbar setFrame:[self specificControlsArea]]; |
+ [self layoutOmnibox]; |
+ } |
+} |
+ |
+#pragma mark - |
+#pragma mark Overridden superclass methods. |
+ |
+- (void)applicationDidEnterBackground:(NSNotification*)notify { |
+ if (_tabHistoryPopupController) { |
+ // Dismiss the tab history popup without animation. |
+ [self setImagesForNavButton:_backButton withTabHistoryVisible:NO]; |
+ [self setImagesForNavButton:_forwardButton withTabHistoryVisible:NO]; |
+ _tabHistoryPopupController.reset(nil); |
+ [[NSNotificationCenter defaultCenter] |
+ postNotificationName:kTabHistoryPopupWillHideNotification |
+ object:nil]; |
+ } |
+ [super applicationDidEnterBackground:notify]; |
+} |
+ |
+- (void)setUpButton:(UIButton*)button |
+ withImageEnum:(int)imageEnum |
+ forInitialState:(UIControlState)initialState |
+ hasDisabledImage:(BOOL)hasDisabledImage |
+ synchronously:(BOOL)synchronously { |
+ [super setUpButton:button |
+ withImageEnum:imageEnum |
+ forInitialState:initialState |
+ hasDisabledImage:hasDisabledImage |
+ synchronously:synchronously]; |
+ |
+ if (button != _starButton.get()) |
+ return; |
+ // The star button behaves slightly differently. It uses the pressed |
+ // image for its selected state as well as its pressed state. |
+ void (^starBlock)(void) = ^{ |
+ UIImage* starImage = [self imageForImageEnum:WebToolbarButtonNameStar |
+ forState:ToolbarButtonUIStatePressed]; |
+ [_starButton setAdjustsImageWhenHighlighted:NO]; |
+ [_starButton setImage:starImage forState:UIControlStateSelected]; |
+ }; |
+ if (synchronously) { |
+ starBlock(); |
+ } else { |
+ dispatch_time_t addImageDelay = |
+ dispatch_time(DISPATCH_TIME_NOW, kNonInitialImageAdditionDelayNanosec); |
+ dispatch_after(addImageDelay, dispatch_get_main_queue(), starBlock); |
+ } |
+} |
+ |
+- (void)standardButtonPressed:(UIButton*)sender { |
+ [super standardButtonPressed:sender]; |
+ [self cancelOmniboxEdit]; |
+} |
+ |
+- (IBAction)recordUserMetrics:(id)sender { |
+ if (sender == _backButton.get()) { |
+ base::RecordAction(UserMetricsAction("MobileToolbarBack")); |
+ } else if (sender == _forwardButton.get()) { |
+ base::RecordAction(UserMetricsAction("MobileToolbarForward")); |
+ } else if (sender == _reloadButton.get()) { |
+ base::RecordAction(UserMetricsAction("MobileToolbarReload")); |
+ } else if (sender == _stopButton.get()) { |
+ base::RecordAction(UserMetricsAction("MobileToolbarStop")); |
+ } else if (sender == _voiceSearchButton.get()) { |
+ base::RecordAction(UserMetricsAction("MobileToolbarVoiceSearch")); |
+ } else if (sender == _keyboardVoiceSearchButton.get()) { |
+ base::RecordAction(UserMetricsAction("MobileCustomRowVoiceSearch")); |
+ } else if (sender == _starButton.get()) { |
+ base::RecordAction(UserMetricsAction("MobileToolbarToggleBookmark")); |
+ } else { |
+ [super recordUserMetrics:sender]; |
+ } |
+} |
+ |
+- (IBAction)stackButtonTouchDown:(id)sender { |
+ [self.delegate prepareToEnterTabSwitcher:self]; |
+} |
+ |
+- (BOOL)imageShouldFlipForRightToLeftLayoutDirection:(int)imageEnum { |
+ DCHECK(imageEnum < NumberOfWebToolbarButtonNames); |
+ if (imageEnum < NumberOfToolbarButtonNames) |
+ return [super imageShouldFlipForRightToLeftLayoutDirection:imageEnum]; |
+ if (imageEnum == WebToolbarButtonNameBack || |
+ imageEnum == WebToolbarButtonNameForward || |
+ imageEnum == WebToolbarButtonNameReload || |
+ imageEnum == WebToolbarButtonNameCallingApp) { |
+ return YES; |
+ } |
+ return NO; |
+} |
+ |
+- (int)imageEnumForButton:(UIButton*)button { |
+ if (button == _voiceSearchButton.get()) |
+ return _isTTSPlaying ? WebToolbarButtonNameTTS : WebToolbarButtonNameVoice; |
+ if (button == _starButton.get()) |
+ return WebToolbarButtonNameStar; |
+ if (button == _stopButton.get()) |
+ return WebToolbarButtonNameStop; |
+ if (button == _reloadButton.get()) |
+ return WebToolbarButtonNameReload; |
+ if (button == _backButton.get()) |
+ return WebToolbarButtonNameBack; |
+ if (button == _forwardButton.get()) |
+ return WebToolbarButtonNameForward; |
+ return [super imageEnumForButton:button]; |
+} |
+ |
+- (int)imageIdForImageEnum:(int)index |
+ style:(ToolbarControllerStyle)style |
+ forState:(ToolbarButtonUIState)state { |
+ DCHECK(style < ToolbarControllerStyleMaxStyles); |
+ DCHECK(state < NumberOfToolbarButtonUIStates); |
+ // Additional checking. These three buttons should only ever be used on iPad. |
+ DCHECK(IsIPadIdiom() || index != WebToolbarButtonNameStar); |
+ DCHECK(IsIPadIdiom() || index != WebToolbarButtonNameTTS); |
+ DCHECK(IsIPadIdiom() || index != WebToolbarButtonNameVoice); |
+ |
+ if (index >= NumberOfWebToolbarButtonNames) |
+ NOTREACHED(); |
+ if (index < NumberOfToolbarButtonNames) |
+ return [super imageIdForImageEnum:index style:style forState:state]; |
+ |
+ // Incognito mode gets dark buttons. |
+ if (style == ToolbarControllerStyleIncognitoMode) |
+ style = ToolbarControllerStyleDarkMode; |
+ |
+ // Some images can be overridden by the branded image provider. |
+ if (index == WebToolbarButtonNameVoice) { |
+ int image_id; |
+ if (ios::GetChromeBrowserProvider() |
+ ->GetBrandedImageProvider() |
+ ->GetToolbarVoiceSearchButtonImageId(&image_id)) { |
+ return image_id; |
+ } |
+ // Otherwise fall through to the default voice search button images below. |
+ } |
+ |
+ // Rebase |index| so that it properly indexes into the array below. |
+ index -= NumberOfToolbarButtonNames; |
+ const int numberOfAddedNames = |
+ NumberOfWebToolbarButtonNames - NumberOfToolbarButtonNames; |
+ // Name, style [light, dark], UIControlState [normal, pressed, disabled] |
+ static int buttonImageIds[numberOfAddedNames][2] |
+ [NumberOfToolbarButtonUIStates] = { |
+ TOOLBAR_IDR_THREE_STATE(BACK), |
+ TOOLBAR_IDR_LIGHT_ONLY_TWO_STATE(CALLINGAPP), |
+ TOOLBAR_IDR_THREE_STATE(FORWARD), |
+ TOOLBAR_IDR_THREE_STATE(RELOAD), |
+ TOOLBAR_IDR_TWO_STATE(STAR), |
+ TOOLBAR_IDR_THREE_STATE(STOP), |
+ TOOLBAR_IDR_TWO_STATE(TTS), |
+ TOOLBAR_IDR_TWO_STATE(VOICE), |
+ }; |
+ return buttonImageIds[index][style][state]; |
+} |
+ |
+- (void)animateTransitionWithBeginFrame:(CGRect)beginFrame |
+ endFrame:(CGRect)endFrame |
+ transitionStyle:(ToolbarTransitionStyle)style { |
+ // Convert background to clear for animations. This is necessary because we |
+ // need to see the animating buttons on the card's tab, which are occurring |
+ // behind the toolbar. The desired color is restored upon completion. |
+ self.view.backgroundColor = [UIColor clearColor]; |
+ |
+ // Add base toolbar animations |
+ [super animateTransitionWithBeginFrame:beginFrame |
+ endFrame:endFrame |
+ transitionStyle:style]; |
+ |
+ // Animation values |
+ CAAnimation* frameAnimation = nil; |
+ CFTimeInterval frameDuration = ios::material::kDuration1; |
+ CAMediaTimingFunction* frameTiming = |
+ TimingFunction(ios::material::CurveEaseInOut); |
+ CGRect beginBounds = {CGPointZero, beginFrame.size}; |
+ CGRect endBounds = {CGPointZero, endFrame.size}; |
+ |
+ // Animate web toolbar: Maintain the trailing padding so that toolbar buttons |
+ // on the trailing side have enough room, and ensure that the height is at |
+ // most the specific control area's height. |
+ CGRect specificControlsArea = [self specificControlsArea]; |
+ CGFloat webToolbarTrailingPadding = |
+ CGRectGetTrailingLayoutOffsetInBoundingRect(specificControlsArea, |
+ self.view.bounds); |
+ CGFloat webToolbarMaxHeight = specificControlsArea.size.height; |
+ UIEdgeInsets webToolbarInsets = |
+ UIEdgeInsetsMakeDirected(0, 0, 0, webToolbarTrailingPadding); |
+ webToolbarInsets.top = |
+ std::max<CGFloat>(0.f, beginBounds.size.height - webToolbarMaxHeight); |
+ CGRect webToolbarBeginFrame = |
+ UIEdgeInsetsInsetRect(beginBounds, webToolbarInsets); |
+ webToolbarInsets.top = |
+ std::max<CGFloat>(0.f, endBounds.size.height - webToolbarMaxHeight); |
+ CGRect webToolbarEndFrame = |
+ UIEdgeInsetsInsetRect(endBounds, webToolbarInsets); |
+ frameAnimation = FrameAnimationMake([_webToolbar layer], webToolbarBeginFrame, |
+ webToolbarEndFrame); |
+ frameAnimation.duration = frameDuration; |
+ frameAnimation.timingFunction = frameTiming; |
+ [self.transitionLayers addObject:[_webToolbar layer]]; |
+ [[_webToolbar layer] addAnimation:frameAnimation |
+ forKey:kToolbarTransitionAnimationKey]; |
+ |
+ // Animate omnibox: center the omnibox vertically within the card web toolbar, |
+ // maintain its leading offset, and adjusting its width to match the available |
+ // space on the card. |
+ CGFloat omniboxHeight = [_omniBox frame].size.height; |
+ LayoutRect toolbarOmniboxLayout = LayoutRectForRectInBoundingRect( |
+ [_omniBox frame], [_omniBox superview].bounds); |
+ CGFloat omniboxLeading = toolbarOmniboxLayout.position.leading; |
+ |
+ LayoutRect omniboxBeginLayout = toolbarOmniboxLayout; |
+ omniboxBeginLayout.boundingWidth = CGRectGetWidth(webToolbarBeginFrame); |
+ omniboxBeginLayout.position.originY = |
+ kOmniboxCenterOffsetY + |
+ 0.5 * (CGRectGetHeight(webToolbarBeginFrame) - omniboxHeight); |
+ omniboxBeginLayout.size.width = |
+ CGRectGetWidth(webToolbarBeginFrame) - omniboxLeading; |
+ omniboxBeginLayout.size.height = omniboxHeight; |
+ |
+ LayoutRect omniboxEndLayout = toolbarOmniboxLayout; |
+ omniboxEndLayout.boundingWidth = CGRectGetWidth(webToolbarEndFrame); |
+ omniboxEndLayout.position.originY = |
+ kOmniboxCenterOffsetY + |
+ 0.5 * (CGRectGetHeight(webToolbarEndFrame) - omniboxHeight); |
+ omniboxEndLayout.size.width = |
+ CGRectGetWidth(webToolbarEndFrame) - omniboxLeading; |
+ omniboxEndLayout.size.height = omniboxHeight; |
+ |
+ CGRect omniboxBeginFrame = |
+ AlignRectOriginAndSizeToPixels(LayoutRectGetRect(omniboxBeginLayout)); |
+ CGRect omniboxEndFrame = |
+ AlignRectOriginAndSizeToPixels(LayoutRectGetRect(omniboxEndLayout)); |
+ |
+ frameAnimation = |
+ FrameAnimationMake([_omniBox layer], omniboxBeginFrame, omniboxEndFrame); |
+ frameAnimation.duration = frameDuration; |
+ frameAnimation.timingFunction = frameTiming; |
+ [self.transitionLayers addObject:[_omniBox layer]]; |
+ [[_omniBox layer] addAnimation:frameAnimation |
+ forKey:kToolbarTransitionAnimationKey]; |
+ [_omniBox |
+ animateFadeWithStyle:((style == TOOLBAR_TRANSITION_STYLE_TO_STACK_VIEW) |
+ ? OMNIBOX_TEXT_FIELD_FADE_STYLE_OUT |
+ : OMNIBOX_TEXT_FIELD_FADE_STYLE_IN)]; |
+ |
+ // Animate clipping view: convert the begin and end bounds since |
+ // |_clippingView| is a subview of |_webToolbar|. |
+ CGRect clippingViewBeginFrame = |
+ CGRectOffset(beginBounds, -webToolbarBeginFrame.origin.x, |
+ -webToolbarBeginFrame.origin.y); |
+ CGRect clippingViewEndFrame = CGRectOffset( |
+ endBounds, -webToolbarEndFrame.origin.x, -webToolbarEndFrame.origin.y); |
+ frameAnimation = FrameAnimationMake( |
+ [_clippingView layer], clippingViewBeginFrame, clippingViewEndFrame); |
+ frameAnimation.duration = frameDuration; |
+ frameAnimation.timingFunction = frameTiming; |
+ [self.transitionLayers addObject:[_clippingView layer]]; |
+ [[_clippingView layer] addAnimation:frameAnimation |
+ forKey:kToolbarTransitionAnimationKey]; |
+ |
+ // Animate omnibox background: the frames should match the omnibox, but in |
+ // |_clippingView|'s coordinate system. |
+ CGRect omniboxBackgroundBeginFrame = |
+ CGRectOffset(omniboxBeginFrame, -clippingViewBeginFrame.origin.x, |
+ -clippingViewBeginFrame.origin.y); |
+ CGRect omniboxBackgroundEndFrame = |
+ CGRectOffset(omniboxEndFrame, -clippingViewEndFrame.origin.x, |
+ -clippingViewEndFrame.origin.y); |
+ frameAnimation = FrameAnimationMake([_omniboxBackground layer], |
+ omniboxBackgroundBeginFrame, |
+ omniboxBackgroundEndFrame); |
+ frameAnimation.duration = frameDuration; |
+ frameAnimation.timingFunction = frameTiming; |
+ BOOL shouldFadeOutOmnibox = (style == TOOLBAR_TRANSITION_STYLE_TO_STACK_VIEW); |
+ CAAnimation* opacityAnimation = OpacityAnimationMake( |
+ shouldFadeOutOmnibox ? 1.0 : 0.0, shouldFadeOutOmnibox ? 0.0 : 1.0); |
+ opacityAnimation.duration = shouldFadeOutOmnibox ? ios::material::kDuration8 |
+ : ios::material::kDuration6; |
+ opacityAnimation.beginTime = |
+ shouldFadeOutOmnibox ? 0.0 : ios::material::kDuration8; |
+ |
+ opacityAnimation.timingFunction = |
+ TimingFunction(shouldFadeOutOmnibox ? ios::material::CurveEaseIn |
+ : ios::material::CurveEaseOut); |
+ CAAnimation* animationGroup = |
+ AnimationGroupMake(@[ frameAnimation, opacityAnimation ]); |
+ [self.transitionLayers addObject:[_omniboxBackground layer]]; |
+ [[_omniboxBackground layer] addAnimation:animationGroup |
+ forKey:kToolbarTransitionAnimationKey]; |
+ |
+ // Animate progress bar: Match the width and bottom edge of the content |
+ // bounds while maintaining the height. |
+ int progressBarYOffset = |
+ floor(kMaterialProgressBarHeight / 2) + kDeterminateProgressBarYOffset; |
+ CGRect progressBarBeginFrame = AlignRectToPixel(CGRectMake( |
+ beginBounds.origin.x, beginBounds.size.height - progressBarYOffset, |
+ beginBounds.size.width, kMaterialProgressBarHeight)); |
+ CGRect progressBarEndFrame = AlignRectToPixel( |
+ CGRectMake(endBounds.origin.x, endBounds.size.height - progressBarYOffset, |
+ endBounds.size.width, kMaterialProgressBarHeight)); |
+ frameAnimation = |
+ FrameAnimationMake([_determinateProgressView layer], |
+ progressBarBeginFrame, progressBarEndFrame); |
+ frameAnimation.duration = frameDuration; |
+ frameAnimation.timingFunction = frameTiming; |
+ [self.transitionLayers addObject:[_determinateProgressView layer]]; |
+ [[_determinateProgressView layer] |
+ addAnimation:frameAnimation |
+ forKey:kToolbarTransitionAnimationKey]; |
+ |
+ // Animate buttons |
+ [self animateTransitionForButtonsInView:_webToolbar |
+ containerBeginBounds:beginBounds |
+ containerEndBounds:endBounds |
+ transitionStyle:style]; |
+} |
+ |
+- (void)reverseTransitionAnimations { |
+ [super reverseTransitionAnimations]; |
+ [_omniBox reverseFadeAnimations]; |
+} |
+ |
+- (void)cleanUpTransitionAnimations { |
+ CGFloat backgroundColorBrightness = |
+ _incognito ? kNTPBackgroundColorBrightnessIncognito |
+ : kNTPBackgroundColorBrightness; |
+ self.view.backgroundColor = |
+ [UIColor colorWithWhite:backgroundColorBrightness alpha:1.0]; |
+ [super cleanUpTransitionAnimations]; |
+ [_omniBox cleanUpFadeAnimations]; |
+} |
+ |
+- (void)hideViewsForNewTabPage:(BOOL)hide { |
+ [super hideViewsForNewTabPage:hide]; |
+ if (_incognito) { |
+ CGFloat alpha = hide ? 0 : 1; |
+ [self.backgroundView setAlpha:alpha]; |
+ } |
+} |
+ |
+#pragma mark - |
+#pragma mark LocationBarDelegate methods. |
+ |
+- (void)loadGURLFromLocationBar:(const GURL&)url |
+ transition:(ui::PageTransition)transition { |
+ if (url.SchemeIs(url::kJavaScriptScheme)) { |
+ // Evaluate the URL as JavaScript if its scheme is JavaScript. |
+ NSString* jsToEval = [base::SysUTF8ToNSString(url.GetContent()) |
+ stringByRemovingPercentEncoding]; |
+ [self.delegate loadJavaScriptFromLocationBar:jsToEval]; |
+ } else { |
+ // When opening a URL, force the omnibox to resign first responder. This |
+ // will also close the popup. |
+ |
+ // TODO(rohitrao): Is it ok to call |cancelOmniboxEdit| after |loadURL|? It |
+ // doesn't seem to be causing major problems. If we call cancel before |
+ // load, then any prerendered pages get destroyed before the call to load. |
+ [self.urlLoader loadURL:url |
+ referrer:web::Referrer() |
+ transition:transition |
+ rendererInitiated:NO]; |
+ |
+ if (google_util::IsGoogleSearchUrl(url)) { |
+ UMA_HISTOGRAM_ENUMERATION( |
+ kOmniboxQueryLocationAuthorizationStatusHistogram, |
+ [CLLocationManager authorizationStatus], |
+ kLocationAuthorizationStatusCount); |
+ } |
+ } |
+ [self cancelOmniboxEdit]; |
+} |
+ |
+- (void)locationBarHasBecomeFirstResponder { |
+ [self.delegate locationBarDidBecomeFirstResponder:self]; |
+ [self animateMaterialOmnibox]; |
+ |
+ [_keyboardVoiceSearchButton setHidden:NO]; |
+ |
+ // Record the appropriate user action for focusing the omnibox. |
+ web::WebState* webState = [self.delegate currentWebState]; |
+ if (webState) { |
+ if (webState->GetVisibleURL() == GURL(kChromeUINewTabURL)) { |
+ OmniboxEditModel* model = _locationBar->GetLocationEntry()->model(); |
+ if (model->is_caret_visible()) { |
+ base::RecordAction( |
+ base::UserMetricsAction("MobileFocusedOmniboxOnNtp")); |
+ } else { |
+ base::RecordAction( |
+ base::UserMetricsAction("MobileFocusedFakeboxOnNtp")); |
+ } |
+ } else { |
+ base::RecordAction( |
+ base::UserMetricsAction("MobileFocusedOmniboxNotOnNtp")); |
+ } |
+ } |
+} |
+ |
+- (void)locationBarHasResignedFirstResponder { |
+ [self.delegate locationBarDidResignFirstResponder:self]; |
+ [self animateMaterialOmnibox]; |
+} |
+ |
+- (void)locationBarBeganEdit { |
+ [self.delegate locationBarBeganEdit:self]; |
+} |
+ |
+- (void)locationBarChanged { |
+ // Hide the voice search button once the user starts editing the omnibox but |
+ // show it if the omnibox is empty. |
+ bool isEditingOrEmpty = _locationBar->GetLocationEntry()->IsEditingOrEmpty(); |
+ BOOL editingAndNotEmpty = isEditingOrEmpty && _omniBox.get().text.length != 0; |
+ // If the voice search button is visible but about to be hidden (i.e. |
+ // the omnibox is no longer empty) then this is the first omnibox text so |
+ // record a user action. |
+ if (![_keyboardVoiceSearchButton isHidden] && editingAndNotEmpty) { |
+ base::RecordAction(UserMetricsAction("MobileFirstTextInOmnibox")); |
+ } |
+ [_keyboardVoiceSearchButton setHidden:editingAndNotEmpty]; |
+} |
+ |
+- (web::WebState*)getWebState { |
+ return [self.delegate currentWebState]; |
+} |
+ |
+- (ToolbarModel*)toolbarModel { |
+ ToolbarModelIOS* toolbarModelIOS = [self.delegate toolbarModelIOS]; |
+ return toolbarModelIOS ? toolbarModelIOS->GetToolbarModel() : nullptr; |
+} |
+ |
+#pragma mark - |
+#pragma mark OmniboxFocuser methods. |
+ |
+- (void)focusOmnibox { |
+ if (![_webToolbar isHidden]) |
+ [_omniBox becomeFirstResponder]; |
+} |
+ |
+- (void)cancelOmniboxEdit { |
+ _locationBar->HideKeyboardAndEndEditing(); |
+ [self updateToolbarState]; |
+} |
+ |
+- (void)focusFakebox { |
+ OmniboxEditModel* model = _locationBar->GetLocationEntry()->model(); |
+ // Setting the caret visibility to false causes OmniboxEditModel to indicate |
+ // that omnibox interaction was initiated from the fakebox. Note that |
+ // SetCaretVisibility is a no-op unless OnSetFocus is called first. |
+ model->OnSetFocus(false); |
+ model->SetCaretVisibility(false); |
+ |
+ if (!IsIPadIdiom()) { |
+ // Set the omnibox background's frame to full bleed. |
+ CGRect mobFrame = CGRectInset([_clippingView bounds], -2, -2); |
+ [_omniboxBackground setFrame:mobFrame]; |
+ } |
+ |
+ [self focusOmnibox]; |
+} |
+ |
+- (void)onFakeboxBlur { |
+ DCHECK(!IsIPadIdiom()); |
+ // Hide the toolbar if the NTP is currently displayed. |
+ web::WebState* webState = [self.delegate currentWebState]; |
+ if (webState && (webState->GetVisibleURL() == GURL(kChromeUINewTabURL))) { |
+ [self.view setHidden:YES]; |
+ } |
+} |
+ |
+- (void)onFakeboxAnimationComplete { |
+ DCHECK(!IsIPadIdiom()); |
+ [self.view setHidden:NO]; |
+} |
+ |
+#pragma mark - |
+#pragma mark OmniboxPopupPositioner methods. |
+ |
+- (UIView*)popupAnchorView { |
+ return self.view; |
+} |
+ |
+- (CGRect)popupFrame:(CGFloat)height { |
+ UIView* parent = [[self popupAnchorView] superview]; |
+ CGRect frame = [parent bounds]; |
+ |
+ // Set tablet popup width to the same width and origin of omnibox. |
+ if (IsIPadIdiom()) { |
+ // For iPad, the omnibox visually extends to include the voice search button |
+ // on the right. Start with the field's frame in |parent|'s coordinate |
+ // system. |
+ CGRect fieldFrame = |
+ [parent convertRect:[_omniBox bounds] fromView:_omniBox]; |
+ |
+ // Now create a new frame that's below the field, stretching the full width |
+ // of |parent|, minus an inset on each side. |
+ CGFloat maxY = CGRectGetMaxY(fieldFrame); |
+ |
+ // The popup extends to the full width of the screen. |
+ frame.origin.x = 0; |
+ frame.size.width = self.view.frame.size.width; |
+ |
+ frame.origin.y = maxY + kiPadOmniboxPopupVerticalOffset; |
+ frame.size.height = height; |
+ } else { |
+ // For iPhone place the popup just below the toolbar. |
+ CGRect fieldFrame = |
+ [parent convertRect:[_webToolbar bounds] fromView:_webToolbar]; |
+ frame.origin.y = |
+ CGRectGetMaxY(fieldFrame) - [ToolbarController toolbarDropShadowHeight]; |
+ frame.size.height = CGRectGetMaxY([parent bounds]) - frame.origin.y; |
+ } |
+ return frame; |
+} |
+ |
+#pragma mark - |
+#pragma mark PopupMenuDelegate methods. |
+ |
+- (void)dismissPopupMenu:(PopupMenuController*)controller { |
+ if ([controller isKindOfClass:[TabHistoryPopupController class]] && |
+ (TabHistoryPopupController*)controller == _tabHistoryPopupController) |
+ [self dismissTabHistoryPopup]; |
+ else |
+ [super dismissPopupMenu:controller]; |
+} |
+ |
+#pragma mark - |
+#pragma mark ToolbarFrameDelegate methods. |
+ |
+- (void)frameDidChangeFrame:(CGRect)newFrame fromFrame:(CGRect)oldFrame { |
+ if (oldFrame.origin.y == newFrame.origin.y) |
+ return; |
+ [self updateToolbarAlphaForFrame:newFrame]; |
+} |
+ |
+- (void)windowDidChange { |
+ if (![_lastKnownTraitCollection |
+ containsTraitsInCollection:self.view.traitCollection]) { |
+ [self traitCollectionDidChange:_lastKnownTraitCollection]; |
+ } |
+} |
+ |
+#pragma mark - |
+#pragma mark VoiceSearchControllerDelegate methods. |
+ |
+- (void)receiveVoiceSearchResult:(NSString*)result { |
+ DCHECK(result); |
+ [self loadURLForQuery:result]; |
+} |
+ |
+#pragma mark - |
+#pragma mark QRScannerViewControllerDelegate methods. |
+ |
+- (void)receiveQRScannerResult:(NSString*)result loadImmediately:(BOOL)load { |
+ DCHECK(result); |
+ if (load) { |
+ [self loadURLForQuery:result]; |
+ } else { |
+ [self focusOmnibox]; |
+ [_omniBox insertTextWhileEditing:result]; |
+ // Notify the accessibility system to start reading the new contents of the |
+ // Omnibox. |
+ UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, |
+ _omniBox); |
+ } |
+} |
+ |
+#pragma mark - |
+#pragma mark Private methods. |
+ |
+- (UIButton*)cancelButton { |
+ if (_cancelButton) |
+ return _cancelButton; |
+ _cancelButton.reset([[UIButton buttonWithType:UIButtonTypeCustom] retain]); |
+ NSString* collapseName = _incognito ? @"collapse_incognito" : @"collapse"; |
+ [_cancelButton setImage:[UIImage imageNamed:collapseName] |
+ forState:UIControlStateNormal]; |
+ NSString* collapsePressedName = |
+ _incognito ? @"collapse_pressed_incognito" : @"collapse_pressed"; |
+ [_cancelButton setImage:[UIImage imageNamed:collapsePressedName] |
+ forState:UIControlStateHighlighted]; |
+ [_cancelButton setAccessibilityLabel:l10n_util::GetNSString(IDS_CANCEL)]; |
+ [_cancelButton setAutoresizingMask:UIViewAutoresizingFlexibleLeadingMargin() | |
+ UIViewAutoresizingFlexibleHeight]; |
+ [_cancelButton addTarget:self |
+ action:@selector(cancelButtonPressed:) |
+ forControlEvents:UIControlEventTouchUpInside]; |
+ |
+ [_webToolbar addSubview:_cancelButton]; |
+ return _cancelButton; |
+} |
+ |
+- (void)cancelButtonPressed:(id)sender { |
+ [self cancelOmniboxEdit]; |
+} |
+ |
+- (void)layoutCancelButton { |
+ CGFloat height = CGRectGetHeight([self specificControlsArea]) - |
+ kCancelButtonTopMargin - kCancelButtonBottomMargin; |
+ LayoutRect cancelButtonLayout; |
+ cancelButtonLayout.position.leading = |
+ CGRectGetWidth([_webToolbar bounds]) - height; |
+ cancelButtonLayout.boundingWidth = CGRectGetWidth([_webToolbar bounds]); |
+ cancelButtonLayout.position.originY = kCancelButtonTopMargin; |
+ cancelButtonLayout.size = CGSizeMake(kCancelButtonWidth, height); |
+ |
+ CGRect frame = LayoutRectGetRect(cancelButtonLayout); |
+ // Use the property to force creation. |
+ [self.cancelButton setFrame:frame]; |
+} |
+ |
+- (void)layoutIncognitoIcon { |
+ LayoutRect iconLayout = LayoutRectForRectInBoundingRect( |
+ [_incognitoIcon frame], [_webToolbar bounds]); |
+ iconLayout.position.leading = 16; |
+ iconLayout.position.originY = floor( |
+ (kToolbarFrame[IPHONE_IDIOM].size.height - iconLayout.size.height) / 2); |
+ [_incognitoIcon setFrame:LayoutRectGetRect(iconLayout)]; |
+} |
+ |
+- (void)layoutOmnibox { |
+ // Position the forward and reload buttons trailing the back button in that |
+ // order. |
+ LayoutRect leadingControlLayout = LayoutRectForRectInBoundingRect( |
+ [_backButton frame], [_webToolbar bounds]); |
+ LayoutRect forwardButtonLayout = |
+ LayoutRectGetTrailingLayout(leadingControlLayout); |
+ forwardButtonLayout.position.originY = [_forwardButton frame].origin.y; |
+ forwardButtonLayout.size = [_forwardButton frame].size; |
+ |
+ LayoutRect reloadButtonLayout = |
+ LayoutRectGetTrailingLayout(forwardButtonLayout); |
+ reloadButtonLayout.position.originY = [_reloadButton frame].origin.y; |
+ reloadButtonLayout.size = [_reloadButton frame].size; |
+ |
+ CGRect newForwardButtonFrame = LayoutRectGetRect(forwardButtonLayout); |
+ CGRect newReloadButtonFrame = LayoutRectGetRect(reloadButtonLayout); |
+ CGRect newOmniboxFrame = [self newOmniboxFrame]; |
+ BOOL isPad = IsIPadIdiom(); |
+ BOOL growOmnibox = [_omniBox isFirstResponder]; |
+ |
+ // Animate buttons. Hide most of the buttons (standard set, back, forward) |
+ // for extended omnibox layout. Also show an extra cancel button so the |
+ // extended mode can be cancelled. |
+ CGFloat alphaForNormalOmniboxButton = growOmnibox ? 0.0 : 1.0; |
+ CGFloat alphaForGrownOmniboxButton = growOmnibox ? 1.0 : 0.0; |
+ CGFloat forwardButtonAlpha = |
+ [_forwardButton isEnabled] || (isPad && !IsCompactTablet(self.view)) |
+ ? alphaForNormalOmniboxButton |
+ : 0.0; |
+ CGFloat backButtonAlpha = alphaForNormalOmniboxButton; |
+ // For the initial layout/testing we just set the length of animation to 0.0 |
+ // which means the changes will be done without any animation. |
+ [UIView animateWithDuration:((_initialLayoutComplete && !_unitTesting) ? 0.2 |
+ : 0.0) |
+ delay:0.0 |
+ options:UIViewAnimationOptionAllowUserInteraction | |
+ UIViewAnimationOptionBeginFromCurrentState |
+ animations:^{ |
+ if (!isPad) |
+ [self setStandardControlsVisible:!growOmnibox]; |
+ if (!(isPad && growOmnibox)) { |
+ [_backButton setAlpha:backButtonAlpha]; |
+ [_forwardButton setAlpha:forwardButtonAlpha]; |
+ [_forwardButton setFrame:newForwardButtonFrame]; |
+ [_cancelButton setAlpha:alphaForGrownOmniboxButton]; |
+ [_reloadButton setFrame:newReloadButtonFrame]; |
+ [_stopButton setFrame:newReloadButtonFrame]; |
+ } |
+ } |
+ completion:nil]; |
+ |
+ if (CGRectEqualToRect([_omniBox frame], newOmniboxFrame)) |
+ return; |
+ |
+ // Hide the clear and voice search buttons during omniBox frame animations. |
+ [_omniBox setRightViewMode:UITextFieldViewModeNever]; |
+ |
+ // Make sure the accessory images are in the correct positions so they do not |
+ // move during the animation. |
+ [_omniBox rightView].frame = |
+ [_omniBox rightViewRectForBounds:newOmniboxFrame]; |
+ [_omniBox leftView].frame = [_omniBox leftViewRectForBounds:newOmniboxFrame]; |
+ |
+ CGRect materialBackgroundFrame = RectShiftedDownForStatusBar(newOmniboxFrame); |
+ |
+ // Extreme jank happens during initial layout if an animation is invoked. Not |
+ // certain why. o_O |
+ // Duration set to 0.0 prevents the animation during initial layout. |
+ [UIView animateWithDuration:((_initialLayoutComplete && !_unitTesting) ? 0.2 |
+ : 0.0) |
+ delay:0.0 |
+ options:UIViewAnimationOptionAllowUserInteraction |
+ animations:^{ |
+ [_omniBox setFrame:newOmniboxFrame]; |
+ [_omniboxBackground setFrame:materialBackgroundFrame]; |
+ } |
+ completion:^(BOOL finished) { |
+ [_omniBox setRightViewMode:UITextFieldViewModeAlways]; |
+ }]; |
+} |
+ |
+- (void)setBackButtonEnabled:(BOOL)enabled { |
+ [_backButton setEnabled:enabled]; |
+} |
+ |
+- (void)setForwardButtonEnabled:(BOOL)enabled { |
+ if (enabled == [_forwardButton isEnabled]) |
+ return; |
+ |
+ [_forwardButton setEnabled:enabled]; |
+ if (!enabled && [_forwardButton isHidden]) |
+ return; |
+ [self layoutOmnibox]; |
+} |
+ |
+- (void)startProgressBar { |
+ if ([_determinateProgressView isHidden]) { |
+ [_determinateProgressView setProgress:0]; |
+ [_determinateProgressView setHidden:NO animated:YES completion:nil]; |
+ } |
+} |
+ |
+- (void)stopProgressBar { |
+ if (_determinateProgressView && ![_determinateProgressView isHidden]) { |
+ // Update the toolbar snapshot, but only after the progress bar has |
+ // disappeared. |
+ |
+ if (!_prerenderAnimating) { |
+ // Calling -completeAndHide while a prerender animation is in progress |
+ // will result in hiding the progress bar before the animation is |
+ // complete. |
+ [_determinateProgressView setProgress:1 |
+ animated:YES |
+ completion:^(BOOL finished) { |
+ [_determinateProgressView setHidden:YES |
+ animated:YES |
+ completion:nil]; |
+ }]; |
+ } |
+ CGFloat delay = _unitTesting ? 0 : kLoadCompleteHideProgressBarDelay; |
+ [self performSelector:@selector(hideProgressBarAndTakeSnapshot) |
+ withObject:nil |
+ afterDelay:delay]; |
+ } |
+} |
+ |
+- (void)hideProgressBarAndTakeSnapshot { |
+ // The UI may have been torn down while this selector was queued. If |
+ // |self.delegate| is nil, it is not safe to continue. |
+ if (!self.delegate) |
+ return; |
+ |
+ [_determinateProgressView setHidden:YES]; |
+ [self updateSnapshotWithWidth:0 forced:NO]; |
+ _prerenderAnimating = NO; |
+} |
+ |
+- (void)showReloadButton { |
+ if (!IsCompactTablet(self.view)) { |
+ [_reloadButton setHidden:NO]; |
+ [_stopButton setHidden:YES]; |
+ } |
+} |
+ |
+- (void)showStopButton { |
+ if (!IsCompactTablet(self.view)) { |
+ [_reloadButton setHidden:YES]; |
+ [_stopButton setHidden:NO]; |
+ } |
+} |
+ |
+- (uint32_t)snapshotHashWithWidth:(CGFloat)width { |
+ uint32_t hash = [super snapshotHash]; |
+ // Take only the lower 3 bits of the UIButton state, as they are the only |
+ // ones that change per enabled, highlighted, or nomal. |
+ const uint32_t kButtonStateMask = 0x07; |
+ hash ^= ([_backButton state] & kButtonStateMask) | |
+ (([_forwardButton state] & kButtonStateMask) << 3) | |
+ (([_cancelButton state] & kButtonStateMask) << 6); |
+ // Omnibox size & text it contains. |
+ hash ^= [[_omniBox text] hash]; |
+ hash ^= static_cast<uint32_t>([_omniBox frame].size.width) << 16; |
+ hash ^= static_cast<uint32_t>([_omniBox frame].size.height) << 24; |
+ // Also note progress bar state. |
+ float progress = 0; |
+ if (_determinateProgressView && ![_determinateProgressView isHidden]) |
+ progress = [_determinateProgressView progress]; |
+ // The progress is in the range 0 to 1, so static_cast<uint32_t> won't work. |
+ // Normally, static_cast does the right thing: truncates the float to an int. |
+ // Here, that would not provide the necessary granularity. |
+ hash ^= *(reinterpret_cast<uint32_t*>(&progress)); |
+ // Size changes matter. |
+ hash ^= static_cast<uint32_t>(width) << 15; |
+ hash ^= static_cast<uint32_t>([self view].frame.size.height) << 23; |
+ return hash; |
+} |
+ |
+- (void)handleLongPress:(UILongPressGestureRecognizer*)gesture { |
+ if (gesture.state != UIGestureRecognizerStateBegan) |
+ return; |
+ |
+ if (gesture.view == _backButton.get()) { |
+ base::scoped_nsobject<GenericChromeCommand> command( |
+ [[GenericChromeCommand alloc] initWithTag:IDC_SHOW_BACK_HISTORY]); |
+ [_backButton chromeExecuteCommand:command]; |
+ } else if (gesture.view == _forwardButton.get()) { |
+ base::scoped_nsobject<GenericChromeCommand> command( |
+ [[GenericChromeCommand alloc] initWithTag:IDC_SHOW_FORWARD_HISTORY]); |
+ [_forwardButton chromeExecuteCommand:command]; |
+ } |
+} |
+ |
+- (void)setImagesForNavButton:(UIButton*)button |
+ withTabHistoryVisible:(BOOL)tabHistoryVisible { |
+ BOOL isBackButton = button == _backButton; |
+ ToolbarButtonMode newMode = |
+ tabHistoryVisible ? ToolbarButtonModeReversed : ToolbarButtonModeNormal; |
+ if (isBackButton && newMode == _backButtonMode) |
+ return; |
+ if (!isBackButton && newMode == _forwardButtonMode) |
+ return; |
+ |
+ base::scoped_nsobject<UIImage> normalImage( |
+ [[button imageForState:UIControlStateNormal] retain]); |
+ base::scoped_nsobject<UIImage> highlightedImage( |
+ [[button imageForState:UIControlStateHighlighted] retain]); |
+ [button setImage:highlightedImage forState:UIControlStateNormal]; |
+ [button setImage:normalImage forState:UIControlStateHighlighted]; |
+ if (isBackButton) |
+ _backButtonMode = newMode; |
+ else |
+ _forwardButtonMode = newMode; |
+} |
+ |
+- (void)audioReadyForPlayback:(NSNotification*)notification { |
+ if (![_voiceSearchButton isHidden]) { |
+ // Only trigger TTS playback when the voice search button is visible. |
+ TextToSpeechPlayer* TTSPlayer = |
+ base::mac::ObjCCastStrict<TextToSpeechPlayer>(notification.object); |
+ [TTSPlayer beginPlayback]; |
+ } |
+} |
+ |
+- (void)updateIsTTSPlaying:(NSNotification*)notify { |
+ BOOL wasTTSPlaying = _isTTSPlaying; |
+ _isTTSPlaying = |
+ [notify.name isEqualToString:kTTSWillStartPlayingNotification]; |
+ if (wasTTSPlaying != _isTTSPlaying && IsIPadIdiom()) { |
+ [self setUpButton:_voiceSearchButton |
+ withImageEnum:(_isTTSPlaying ? WebToolbarButtonNameTTS |
+ : WebToolbarButtonNameVoice) |
+ forInitialState:UIControlStateNormal |
+ hasDisabledImage:NO |
+ synchronously:NO]; |
+ } |
+ [self updateToolbarState]; |
+ if (_isTTSPlaying && UIAccessibilityIsVoiceOverRunning()) { |
+ // Moving VoiceOver without RunBlockAfterDelay results in VoiceOver not |
+ // staying on |_voiceSearchButton| and instead moving to views inside the |
+ // UIWebView. |
+ // Use |voiceSearchButton| in the block to prevent |self| from being |
+ // retained. |
+ UIButton* voiceSearchButton = _voiceSearchButton; |
+ RunBlockAfterDelay(0.0, ^{ |
+ UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, |
+ voiceSearchButton); |
+ }); |
+ } |
+} |
+ |
+- (void)moveVoiceOverToVoiceSearchButton { |
+ UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, |
+ _voiceSearchButton); |
+} |
+ |
+- (void)updateToolbarAlphaForFrame:(CGRect)frame { |
+ // Don't update the toolbar buttons if we are animating for a transition. |
+ if (self.view.animatingTransition) |
+ return; |
+ |
+ CGFloat distanceOffscreen = |
+ IsIPadIdiom() |
+ ? fmax((kIPadToolbarY - frame.origin.y) - kScrollFadeDistance, 0) |
+ : -1 * frame.origin.y; |
+ CGFloat fraction = 1 - fmin(distanceOffscreen / kScrollFadeDistance, 1); |
+ if (![_omniBox isFirstResponder]) |
+ [self setStandardControlsAlpha:fraction]; |
+ |
+ [_backButton setAlpha:fraction]; |
+ if ([_forwardButton isEnabled]) |
+ [_forwardButton setAlpha:fraction]; |
+ [_reloadButton setAlpha:fraction]; |
+ [_omniboxBackground setAlpha:fraction]; |
+ [_omniBox setAlpha:fraction]; |
+ [_starButton setAlpha:fraction]; |
+ [_voiceSearchButton setAlpha:fraction]; |
+} |
+ |
+- (void)loadURLForQuery:(NSString*)query { |
+ GURL searchURL; |
+ metrics::OmniboxInputType::Type type = AutocompleteInput::Parse( |
+ base::SysNSStringToUTF16(query), std::string(), |
+ AutocompleteSchemeClassifierImpl(), nullptr, nullptr, &searchURL); |
+ if (type != metrics::OmniboxInputType::URL || !searchURL.is_valid()) { |
+ searchURL = GetDefaultSearchURLForSearchTerms( |
+ ios::TemplateURLServiceFactory::GetForBrowserState(_browserState), |
+ base::SysNSStringToUTF16(query)); |
+ } |
+ if (searchURL.is_valid()) { |
+ // It is necessary to include PAGE_TRANSITION_FROM_ADDRESS_BAR in the |
+ // transition type is so that query-in-the-omnibox is triggered for the |
+ // URL. |
+ ui::PageTransition transition = ui::PageTransitionFromInt( |
+ ui::PAGE_TRANSITION_LINK | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR); |
+ [self.urlLoader loadURL:GURL(searchURL) |
+ referrer:web::Referrer() |
+ transition:transition |
+ rendererInitiated:NO]; |
+ } |
+} |
+ |
+- (UIView*)keyboardButtonWithTitle:(NSString*)title frame:(CGRect)frame { |
+ UIButton* button = [UIButton buttonWithType:UIButtonTypeCustom]; |
+ UIFont* font = nil; |
+ UIImage* backgroundImage = nil; |
+ if (IsIPadIdiom()) { |
+ font = GetUIFont(FONT_HELVETICA, false, kIpadButtonTitleFontSize); |
+ } else { |
+ font = GetUIFont(FONT_HELVETICA, true, kIphoneButtonTitleFontSize); |
+ } |
+ // TODO(leng): Consider moving these images to pak files as well. |
+ backgroundImage = [UIImage imageNamed:@"keyboard_button"]; |
+ |
+ button.frame = frame; |
+ [button setTitle:title forState:UIControlStateNormal]; |
+ [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; |
+ [button.titleLabel setFont:font]; |
+ [button setBackgroundImage:backgroundImage forState:UIControlStateNormal]; |
+ [button addTarget:self |
+ action:@selector(pressKey:) |
+ forControlEvents:UIControlEventTouchUpInside]; |
+ button.isAccessibilityElement = YES; |
+ [button setAccessibilityLabel:title]; |
+ |
+ return button; |
+} |
+ |
+- (UIView*)keyboardAccessoryView { |
+ const CGFloat kViewHeightTablet = 70.0; |
+ const CGFloat kViewHeightPhone = 43.0; |
+ const CGFloat kButtonInset = 5.0; |
+ const CGFloat kButtonSizeXTablet = 61.0; |
+ const CGFloat kButtonSizeXPhone = 46.0; |
+ const CGFloat kButtonSizeYTablet = 62.0; |
+ const CGFloat kButtonSizeYPhone = 35.0; |
+ const CGFloat kBetweenButtonSpacing = 15.0; |
+ const CGFloat kBetweenButtonSpacingPhone = 7.0; |
+ |
+ if (_keyBoardAccessoryView) |
+ return _keyBoardAccessoryView; |
+ |
+ const BOOL isTablet = IsIPadIdiom() && !IsCompactTablet(self.view); |
+ |
+ // TODO(pinkerton): purge this view when low memory. |
+ CGFloat width = [[UIScreen mainScreen] bounds].size.width; |
+ CGFloat height = isTablet ? kViewHeightTablet : kViewHeightPhone; |
+ CGRect frame = CGRectMake(0.0, 0.0, width, height); |
+ |
+ _keyBoardAccessoryView.reset([[KeyboardAccessoryView alloc] |
+ initWithFrame:frame |
+ inputViewStyle:UIInputViewStyleKeyboard]); |
+ [_keyBoardAccessoryView setAutoresizingMask:UIViewAutoresizingFlexibleWidth]; |
+ |
+ NSArray* buttonTitles = |
+ [NSArray arrayWithObjects:@":", @".", @"-", @"/", kDotComTLD, nil]; |
+ |
+ // Center buttons in available space by placing them within a parent view |
+ // that auto-centers. |
+ CGFloat betweenButtonSpacing = |
+ isTablet ? kBetweenButtonSpacing : kBetweenButtonSpacingPhone; |
+ const CGFloat buttonWidth = isTablet ? kButtonSizeXTablet : kButtonSizeXPhone; |
+ |
+ CGFloat totalWidth = (buttonTitles.count * buttonWidth) + |
+ ((buttonTitles.count - 1) * betweenButtonSpacing); |
+ CGFloat indent = floor((width - totalWidth) / 2.0); |
+ if (indent < kButtonInset) |
+ indent = kButtonInset; |
+ CGRect parentViewRect = CGRectMake(indent, 0.0, totalWidth, height); |
+ base::scoped_nsobject<UIView> parentView( |
+ [[UIView alloc] initWithFrame:parentViewRect]); |
+ [parentView setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | |
+ UIViewAutoresizingFlexibleRightMargin]; |
+ [_keyBoardAccessoryView addSubview:parentView]; |
+ |
+ // Create the buttons, starting at the left edge of |parentView|. |
+ CGRect currentFrame = |
+ CGRectMake(0.0, kButtonInset, buttonWidth, |
+ isTablet ? kButtonSizeYTablet : kButtonSizeYPhone); |
+ |
+ for (NSString* title in buttonTitles) { |
+ UIView* button = [self keyboardButtonWithTitle:title frame:currentFrame]; |
+ [parentView addSubview:button]; |
+ currentFrame.origin.x = CGRectGetMaxX(currentFrame) + betweenButtonSpacing; |
+ } |
+ |
+ // Create the voice search button and add it to _keyBoardAccessoryView over |
+ // the text buttons. |
+ _keyboardVoiceSearchButton.reset( |
+ [[UIButton buttonWithType:UIButtonTypeCustom] retain]); |
+ [_keyboardVoiceSearchButton |
+ setAutoresizingMask:UIViewAutoresizingFlexibleWidth]; |
+ [_keyboardVoiceSearchButton setTag:IDC_VOICE_SEARCH]; |
+ SetA11yLabelAndUiAutomationName(_keyboardVoiceSearchButton, |
+ IDS_IOS_ACCNAME_VOICE_SEARCH, |
+ @"Voice Search"); |
+ // TODO(leng): Consider moving these icons into a pak file. |
+ UIImage* voiceRow = [UIImage imageNamed:@"custom_row_voice"]; |
+ UIImage* voiceRowPressed = [UIImage imageNamed:@"custom_row_voice_pressed"]; |
+ [_keyboardVoiceSearchButton setBackgroundImage:voiceRow |
+ forState:UIControlStateNormal]; |
+ [_keyboardVoiceSearchButton setBackgroundImage:voiceRowPressed |
+ forState:UIControlStateHighlighted]; |
+ |
+ UIImage* voiceIcon = [UIImage imageNamed:@"voice_icon_keyboard_accessory"]; |
+ [_keyboardVoiceSearchButton setAdjustsImageWhenHighlighted:NO]; |
+ [_keyboardVoiceSearchButton setImage:voiceIcon forState:UIControlStateNormal]; |
+ [_keyboardVoiceSearchButton setFrame:[_keyBoardAccessoryView bounds]]; |
+ |
+ // Only add the voice search actions if voice search is enabled. |
+ if (ios::GetChromeBrowserProvider() |
+ ->GetVoiceSearchProvider() |
+ ->IsVoiceSearchEnabled()) { |
+ [_keyboardVoiceSearchButton addTarget:self |
+ action:@selector(recordUserMetrics:) |
+ forControlEvents:UIControlEventTouchUpInside]; |
+ [_keyboardVoiceSearchButton addTarget:_keyboardVoiceSearchButton |
+ action:@selector(chromeExecuteCommand:) |
+ forControlEvents:UIControlEventTouchUpInside]; |
+ [_keyboardVoiceSearchButton addTarget:self |
+ action:@selector(preloadVoiceSearch:) |
+ forControlEvents:UIControlEventTouchDown]; |
+ } else { |
+ [_keyboardVoiceSearchButton addTarget:self |
+ action:@selector(ignoreVoiceSearch:) |
+ forControlEvents:UIControlEventTouchUpInside]; |
+ } |
+ |
+ [_keyBoardAccessoryView addSubview:_keyboardVoiceSearchButton]; |
+ |
+ // Reset the external keyboard watcher. |
+ _hardwareKeyboardWatcher.reset([[HardwareKeyboardWatcher alloc] |
+ initWithAccessoryView:_keyBoardAccessoryView]); |
+ |
+ return _keyBoardAccessoryView; |
+} |
+ |
+- (void)preloadVoiceSearch:(id)sender { |
+ DCHECK(ios::GetChromeBrowserProvider() |
+ ->GetVoiceSearchProvider() |
+ ->IsVoiceSearchEnabled()); |
+ [sender removeTarget:self |
+ action:@selector(preloadVoiceSearch:) |
+ forControlEvents:UIControlEventTouchDown]; |
+ |
+ // Use a GenericChromeCommand because |sender| already has a tag set for a |
+ // different command. |
+ base::scoped_nsobject<GenericChromeCommand> command( |
+ [[GenericChromeCommand alloc] initWithTag:IDC_PRELOAD_VOICE_SEARCH]); |
+ [sender chromeExecuteCommand:command]; |
+} |
+ |
+// Called when the keyboard voice search button is tapped with voice search |
+// disabled. Hides the voice search button but takes no other action. |
+- (void)ignoreVoiceSearch:(id)sender { |
+ [_keyboardVoiceSearchButton setHidden:YES]; |
+} |
+ |
+- (LocationBarViewIOS*)locationBar { |
+ return _locationBar.get(); |
+} |
+ |
+- (CGFloat)omniboxLeading { |
+ // Compute what the leading (x-origin) position for the omniboox should be |
+ // based on what other controls are active. |
+ InterfaceIdiom idiom = IsIPadIdiom() ? IPAD_IDIOM : IPHONE_IDIOM; |
+ |
+ CGFloat trailingPadding = 0.0; |
+ |
+ LayoutRect leadingControlLayout = LayoutRectForRectInBoundingRect( |
+ [_backButton frame], [_webToolbar bounds]); |
+ LayoutRect forwardButtonLayout = |
+ LayoutRectGetTrailingLayout(leadingControlLayout); |
+ forwardButtonLayout.position.leading += trailingPadding; |
+ forwardButtonLayout.position.originY = [_forwardButton frame].origin.y; |
+ forwardButtonLayout.size = [_forwardButton frame].size; |
+ |
+ LayoutRect reloadButtonLayout = |
+ LayoutRectGetTrailingLayout(forwardButtonLayout); |
+ reloadButtonLayout.position.originY = [_reloadButton frame].origin.y; |
+ reloadButtonLayout.size = [_reloadButton frame].size; |
+ |
+ LayoutRect omniboxReferenceLayout; |
+ if (idiom == IPAD_IDIOM && !IsCompactTablet(self.view)) { |
+ omniboxReferenceLayout = reloadButtonLayout; |
+ omniboxReferenceLayout.size.width += kReloadButtonTrailingPadding; |
+ } else if ([_forwardButton isEnabled]) { |
+ omniboxReferenceLayout = forwardButtonLayout; |
+ omniboxReferenceLayout.size.width += kForwardButtonTrailingPadding; |
+ } else { |
+ omniboxReferenceLayout = kBackButtonFrame[idiom]; |
+ omniboxReferenceLayout.size.width += kBackButtonTrailingPadding; |
+ } |
+ DCHECK(!(omniboxReferenceLayout.position.leading == 0 && |
+ CGSizeEqualToSize(omniboxReferenceLayout.size, CGSizeZero))); |
+ |
+ return LayoutRectGetTrailingEdge(omniboxReferenceLayout); |
+} |
+ |
+// TODO(crbug.com/525943): refactor this method and related code to not resize |
+// |_webToolbar| and buttons as a side effect. |
+- (CGRect)newOmniboxFrame { |
+ InterfaceIdiom idiom = IsIPadIdiom() ? IPAD_IDIOM : IPHONE_IDIOM; |
+ LayoutRect newOmniboxLayout; |
+ // Grow the omnibox if focused. |
+ BOOL growOmnibox = [_omniBox isFirstResponder]; |
+ if (idiom == IPAD_IDIOM) { |
+ // When the omnibox is focused, the star button is hidden. |
+ [_starButton setAlpha:(growOmnibox ? 0 : 1)]; |
+ |
+ newOmniboxLayout = |
+ LayoutRectForRectInBoundingRect([_omniBox frame], [_webToolbar bounds]); |
+ CGFloat omniboxLeading = [self omniboxLeading]; |
+ CGFloat omniboxLeadingDiff = |
+ omniboxLeading - newOmniboxLayout.position.leading; |
+ |
+ newOmniboxLayout.position.leading = omniboxLeading; |
+ newOmniboxLayout.size.width -= omniboxLeadingDiff; |
+ } else { |
+ // If the omnibox is growing, take over the whole toolbar area (standard |
+ // toolbar controls will be hidden below). Temporarily suppress autoresizing |
+ // to avoid interfering with the omnibox animation. |
+ [_webToolbar setAutoresizesSubviews:NO]; |
+ CGRect expandedFrame = |
+ RectShiftedDownAndResizedForStatusBar(self.view.bounds); |
+ [_webToolbar |
+ setFrame:growOmnibox ? expandedFrame : [self specificControlsArea]]; |
+ [_webToolbar setAutoresizesSubviews:YES]; |
+ |
+ // Compute new omnibox layout after the web toolbar is resized. |
+ newOmniboxLayout = |
+ LayoutRectForRectInBoundingRect([_omniBox frame], [_webToolbar bounds]); |
+ |
+ if (growOmnibox) { |
+ // If the omnibox is expanded, there is padding on both the left and right |
+ // sides. |
+ newOmniboxLayout.position.leading = kExpandedOmniboxPadding; |
+ // When the |_webToolbar| is resized without autoresizes subviews enabled |
+ // the cancel button does not pin to the trailing side and is out of |
+ // place. Lazily allocate and force a layout here. |
+ [self layoutCancelButton]; |
+ LayoutRect cancelButtonLayout = LayoutRectForRectInBoundingRect( |
+ [self cancelButton].frame, [_webToolbar bounds]); |
+ newOmniboxLayout.size.width = cancelButtonLayout.position.leading - |
+ kExpandedOmniboxPadding - |
+ newOmniboxLayout.position.leading; |
+ // Likewise the incognito icon will not be pinned to the leading side, so |
+ // force a layout here. |
+ if (_incognitoIcon) |
+ [self layoutIncognitoIcon]; |
+ } else { |
+ newOmniboxLayout.position.leading = [self omniboxLeading]; |
+ newOmniboxLayout.size.width = CGRectGetWidth([_webToolbar frame]) - |
+ newOmniboxLayout.position.leading; |
+ } |
+ } |
+ |
+ CGRect newOmniboxFrame = LayoutRectGetRect(newOmniboxLayout); |
+ DCHECK(!CGRectEqualToRect(newOmniboxFrame, CGRectZero)); |
+ |
+ return newOmniboxFrame; |
+} |
+ |
+- (void)animateMaterialOmnibox { |
+ // The iPad omnibox does not animate. |
+ if (IsIPadIdiom()) |
+ return [self layoutOmnibox]; |
+ |
+ CGRect newOmniboxFrame = [self newOmniboxFrame]; |
+ BOOL growOmnibox = [_omniBox isFirstResponder]; |
+ |
+ // Determine the starting and ending bounds and position for |_omniBox|. |
+ // Increasing the height of _omniBox results in the text inside it jumping |
+ // vertically during the animation, so the height change will not be animated. |
+ CGRect fromBounds = [_omniBox layer].bounds; |
+ LayoutRect toLayout = |
+ LayoutRectForRectInBoundingRect(newOmniboxFrame, [_webToolbar bounds]); |
+ CGRect toBounds = CGRectZero; |
+ CGPoint fromPosition = [_omniBox layer].position; |
+ CGPoint toPosition = fromPosition; |
+ CGRect omniboxRect = LayoutRectGetRect(kOmniboxFrame[IPHONE_IDIOM]); |
+ if (growOmnibox) { |
+ toLayout.size = CGSizeMake([_webToolbar bounds].size.width - |
+ self.cancelButton.frame.size.width - |
+ kCancelButtonLeadingMargin, |
+ omniboxRect.size.height); |
+ toLayout.position.leading = 0; |
+ if (_incognito) { |
+ // Adjust the width and leading of the omnibox to account for the |
+ // incognito icon. |
+ // TODO(crbug/525943): Refactor so this value isn't calculated here, and |
+ // instead is calculated in -newOmniboxFrame? |
+ // (include in (crbug/525943) refactor). |
+ LayoutRect incognitioIconLayout = LayoutRectForRectInBoundingRect( |
+ [_incognitoIcon frame], [_webToolbar frame]); |
+ CGFloat trailingEdge = LayoutRectGetTrailingEdge(incognitioIconLayout); |
+ toLayout.size.width -= trailingEdge; |
+ toLayout.position.leading = trailingEdge; |
+ } |
+ } else { |
+ // Shrink the omnibox to its original width. |
+ toLayout.size = |
+ CGSizeMake(newOmniboxFrame.size.width, omniboxRect.size.height); |
+ } |
+ toBounds = LayoutRectGetBoundsRect(toLayout); |
+ toPosition.x = |
+ LayoutRectGetPositionForAnchor(toLayout, [_omniBox layer].anchorPoint).x; |
+ |
+ // Determine starting and ending bounds for |_omniboxBackground|. |
+ // _omniboxBackground is needed to simulate the omnibox growing vertically and |
+ // covering the entire height of the toolbar. |
+ CGRect backgroundFromBounds = [_omniboxBackground layer].bounds; |
+ CGRect backgroundToBounds = CGRectZero; |
+ CGPoint backgroundFromPosition = [_omniboxBackground layer].position; |
+ CGPoint backgroundToPosition = CGPointZero; |
+ if (growOmnibox) { |
+ // Grow the background to cover the whole toolbar. |
+ backgroundToBounds = [_clippingView bounds]; |
+ backgroundToPosition.x = backgroundToBounds.size.width / 2; |
+ backgroundToPosition.y = backgroundToBounds.size.height / 2; |
+ // Increase the bounds of the background so that the border extends past the |
+ // toolbar and is clipped. |
+ backgroundToBounds = CGRectInset(backgroundToBounds, -2, -2); |
+ } else { |
+ // Shrink the background to the same bounds as the omnibox. |
+ backgroundToBounds = toBounds; |
+ backgroundToPosition = toPosition; |
+ backgroundToPosition.y += StatusBarHeight(); |
+ } |
+ |
+ // Is the omnibox already at the new size? Then there's nothing to animate. |
+ if (CGRectEqualToRect([_omniBox layer].bounds, toBounds)) |
+ return; |
+ |
+ [self animateStandardControlsForOmniboxExpansion:growOmnibox]; |
+ if (growOmnibox) { |
+ [self fadeOutNavigationControls]; |
+ [self fadeInOmniboxTrailingView]; |
+ if (![_omniBox isShowingQueryRefinementChip]) { |
+ // Don't animate the query refinement chip. |
+ if (_locationBar.get()->IsShowingPlaceholderWhileCollapsed()) |
+ [self fadeOutOmniboxLeadingView]; |
+ else |
+ [_omniBox leftView].alpha = 0; |
+ } |
+ if (_incognito) |
+ [self fadeInIncognitoIcon]; |
+ } else { |
+ [self fadeInNavigationControls]; |
+ [self fadeOutOmniboxTrailingView]; |
+ if (![_omniBox isShowingQueryRefinementChip]) { |
+ // Don't animate the query refinement chip. |
+ if (_locationBar.get()->IsShowingPlaceholderWhileCollapsed()) |
+ [self fadeInOmniboxLeadingView]; |
+ else |
+ [_omniBox leftView].alpha = 1; |
+ } |
+ if (_incognito) |
+ [self fadeOutIncognitoIcon]; |
+ } |
+ |
+ // Animate the new bounds and position for the omnibox and background. |
+ [CATransaction begin]; |
+ [CATransaction setCompletionBlock:^{ |
+ // Re-layout the omnibox's subviews after the animation to allow VoiceOver |
+ // to select the clear text button. |
+ [_omniBox setNeedsLayout]; |
+ }]; |
+ CGFloat duration = ios::material::kDuration1; |
+ // If app is on the regular New Tab Page, make this animation occur instantly |
+ // since this page has a fakebox to omnibox transition. |
+ web::WebState* webState = [self.delegate currentWebState]; |
+ if (webState && webState->GetVisibleURL() == GURL(kChromeUINewTabURL) && |
+ !_incognito) { |
+ duration = 0.0; |
+ } |
+ [CATransaction setAnimationDuration:duration]; |
+ [CATransaction |
+ setAnimationTimingFunction:TimingFunction(ios::material::CurveEaseInOut)]; |
+ |
+ // TODO(crbug.com/525943): As part of animation cleanup, refactor |
+ // these animations into groups produced by FrameAnimationMake() from |
+ // animation_util, and do all of the bounds/position calculations above in |
+ // terms of frames. |
+ |
+ CABasicAnimation* resizeAnimation = |
+ [CABasicAnimation animationWithKeyPath:@"bounds"]; |
+ resizeAnimation.delegate = self; |
+ [resizeAnimation setValue:@"resizeOmnibox" forKey:@"id"]; |
+ resizeAnimation.fromValue = [NSValue valueWithCGRect:fromBounds]; |
+ resizeAnimation.toValue = [NSValue valueWithCGRect:toBounds]; |
+ [_omniBox layer].bounds = toBounds; |
+ [[_omniBox layer] addAnimation:resizeAnimation forKey:@"resizeBounds"]; |
+ CABasicAnimation* positionAnimation = |
+ [CABasicAnimation animationWithKeyPath:@"position"]; |
+ positionAnimation.fromValue = [NSValue valueWithCGPoint:fromPosition]; |
+ positionAnimation.toValue = [NSValue valueWithCGPoint:toPosition]; |
+ [_omniBox layer].position = toPosition; |
+ [[_omniBox layer] addAnimation:positionAnimation forKey:@"movePosition"]; |
+ |
+ resizeAnimation = [CABasicAnimation animationWithKeyPath:@"bounds"]; |
+ resizeAnimation.fromValue = [NSValue valueWithCGRect:backgroundFromBounds]; |
+ resizeAnimation.toValue = [NSValue valueWithCGRect:backgroundToBounds]; |
+ [_omniboxBackground layer].bounds = backgroundToBounds; |
+ [[_omniboxBackground layer] addAnimation:resizeAnimation |
+ forKey:@"resizeBounds"]; |
+ CABasicAnimation* backgroundPositionAnimation = |
+ [CABasicAnimation animationWithKeyPath:@"position"]; |
+ backgroundPositionAnimation.fromValue = |
+ [NSValue valueWithCGPoint:backgroundFromPosition]; |
+ backgroundPositionAnimation.toValue = |
+ [NSValue valueWithCGPoint:backgroundToPosition]; |
+ [_omniboxBackground layer].position = backgroundToPosition; |
+ [[_omniboxBackground layer] addAnimation:backgroundPositionAnimation |
+ forKey:@"movePosition"]; |
+ [CATransaction commit]; |
+} |
+ |
+- (void)fadeInOmniboxTrailingView { |
+ UIView* trailingView = [_omniBox rightView]; |
+ trailingView.alpha = 0; |
+ [_cancelButton setAlpha:0]; |
+ [_cancelButton setHidden:NO]; |
+ |
+ // Instead of passing a delay into -fadeInView:, wait to call -fadeInView:. |
+ // The CABasicAnimation's start and end positions are calculated immediately |
+ // instead of after the animation's delay, but the omnibox's layer isn't set |
+ // yet to its final state and as a result the start and end positions will not |
+ // be correct. |
+ dispatch_time_t delay = dispatch_time( |
+ DISPATCH_TIME_NOW, ios::material::kDuration6 * NSEC_PER_SEC); |
+ dispatch_after(delay, dispatch_get_main_queue(), ^(void) { |
+ [self fadeInView:trailingView |
+ fromLeadingOffset:kPositionAnimationLeadingOffset |
+ withDuration:ios::material::kDuration1 |
+ afterDelay:0.2]; |
+ [self fadeInView:_cancelButton |
+ fromLeadingOffset:kPositionAnimationLeadingOffset |
+ withDuration:ios::material::kDuration1 |
+ afterDelay:0.1]; |
+ }); |
+} |
+ |
+- (void)fadeInOmniboxLeadingView { |
+ UIView* leadingView = [_omniBox leftView]; |
+ leadingView.alpha = 0; |
+ // Instead of passing a delay into -fadeInView:, wait to call -fadeInView:. |
+ // The CABasicAnimation's start and end positions are calculated immediately |
+ // instead of after the animation's delay, but the omnibox's layer isn't set |
+ // yet to its final state and as a result the start and end positions will not |
+ // be correct. |
+ dispatch_time_t delay = dispatch_time( |
+ DISPATCH_TIME_NOW, ios::material::kDuration2 * NSEC_PER_SEC); |
+ dispatch_after(delay, dispatch_get_main_queue(), ^(void) { |
+ [self fadeInView:leadingView |
+ fromLeadingOffset:kPositionAnimationLeadingOffset |
+ withDuration:ios::material::kDuration1 |
+ afterDelay:0]; |
+ }); |
+} |
+ |
+- (void)fadeOutOmniboxTrailingView { |
+ UIView* trailingView = [_omniBox rightView]; |
+ |
+ // Animate the opacity of the trailingView to 0. |
+ [CATransaction begin]; |
+ [CATransaction setAnimationDuration:ios::material::kDuration4]; |
+ [CATransaction |
+ setAnimationTimingFunction:TimingFunction(ios::material::CurveEaseIn)]; |
+ [CATransaction setCompletionBlock:^{ |
+ [_cancelButton setHidden:YES]; |
+ }]; |
+ CABasicAnimation* fadeOut = |
+ [CABasicAnimation animationWithKeyPath:@"opacity"]; |
+ fadeOut.fromValue = @1; |
+ fadeOut.toValue = @0; |
+ trailingView.layer.opacity = 0; |
+ [trailingView.layer addAnimation:fadeOut forKey:@"fade"]; |
+ |
+ // Animate the trailingView |kPositionAnimationLeadingOffset| pixels towards |
+ // the |
+ // leading edge. |
+ CABasicAnimation* shift = [CABasicAnimation animationWithKeyPath:@"position"]; |
+ CGPoint startPosition = [trailingView layer].position; |
+ CGPoint endPosition = |
+ CGPointLayoutOffset(startPosition, kPositionAnimationLeadingOffset); |
+ shift.fromValue = [NSValue valueWithCGPoint:startPosition]; |
+ shift.toValue = [NSValue valueWithCGPoint:endPosition]; |
+ [[trailingView layer] addAnimation:shift forKey:@"shift"]; |
+ |
+ [_cancelButton layer].opacity = 0; |
+ [[_cancelButton layer] addAnimation:fadeOut forKey:@"fade"]; |
+ |
+ CABasicAnimation* shiftCancelButton = |
+ [CABasicAnimation animationWithKeyPath:@"position"]; |
+ startPosition = [_cancelButton layer].position; |
+ endPosition = |
+ CGPointLayoutOffset(startPosition, kPositionAnimationLeadingOffset); |
+ shiftCancelButton.fromValue = [NSValue valueWithCGPoint:startPosition]; |
+ shiftCancelButton.toValue = [NSValue valueWithCGPoint:endPosition]; |
+ [[_cancelButton layer] addAnimation:shiftCancelButton forKey:@"shift"]; |
+ |
+ [CATransaction commit]; |
+} |
+ |
+- (void)fadeOutOmniboxLeadingView { |
+ UIView* leadingView = [_omniBox leftView]; |
+ |
+ // Animate the opacity of leadingView to 0. |
+ [CATransaction begin]; |
+ [CATransaction setAnimationDuration:ios::material::kDuration2]; |
+ [CATransaction |
+ setAnimationTimingFunction:TimingFunction(ios::material::CurveEaseInOut)]; |
+ CABasicAnimation* fadeOut = |
+ [CABasicAnimation animationWithKeyPath:@"opacity"]; |
+ fadeOut.fromValue = @1; |
+ fadeOut.toValue = @0; |
+ leadingView.layer.opacity = 0; |
+ [leadingView.layer addAnimation:fadeOut forKey:@"fade"]; |
+ |
+ // Animate leadingView |kPositionAnimationLeadingOffset| pixels trailing. |
+ CABasicAnimation* shift = [CABasicAnimation animationWithKeyPath:@"position"]; |
+ CGPoint startPosition = [leadingView layer].position; |
+ CGPoint endPosition = |
+ CGPointLayoutOffset(startPosition, kPositionAnimationLeadingOffset); |
+ shift.fromValue = [NSValue valueWithCGPoint:startPosition]; |
+ shift.toValue = [NSValue valueWithCGPoint:endPosition]; |
+ [[leadingView layer] addAnimation:shift forKey:@"shift"]; |
+ [CATransaction commit]; |
+} |
+ |
+- (void)fadeInIncognitoIcon { |
+ DCHECK(_incognito); |
+ DCHECK(!IsIPadIdiom()); |
+ [self fadeInView:_incognitoIcon |
+ fromLeadingOffset:-kPositionAnimationLeadingOffset |
+ withDuration:ios::material::kDuration2 |
+ afterDelay:ios::material::kDuration2]; |
+} |
+ |
+- (void)fadeOutIncognitoIcon { |
+ DCHECK(_incognito); |
+ DCHECK(!IsIPadIdiom()); |
+ |
+ // Animate the opacity of the incognito icon to 0. |
+ CALayer* layer = [_incognitoIcon layer]; |
+ [CATransaction begin]; |
+ [CATransaction setAnimationDuration:ios::material::kDuration4]; |
+ [CATransaction |
+ setAnimationTimingFunction:TimingFunction(ios::material::CurveEaseIn)]; |
+ CABasicAnimation* fadeOut = |
+ [CABasicAnimation animationWithKeyPath:@"opacity"]; |
+ fadeOut.fromValue = @1; |
+ fadeOut.toValue = @0; |
+ layer.opacity = 0; |
+ [layer addAnimation:fadeOut forKey:@"fade"]; |
+ |
+ // Animate the incognito icon |kPositionAnimationLeadingOffset| pixels |
+ // trailing. |
+ CABasicAnimation* shift = [CABasicAnimation animationWithKeyPath:@"position"]; |
+ CGPoint startPosition = layer.position; |
+ // Constant is a leading value, so invert it to move in the trailing |
+ // direction. |
+ CGPoint endPosition = |
+ CGPointLayoutOffset(startPosition, -kPositionAnimationLeadingOffset); |
+ shift.fromValue = [NSValue valueWithCGPoint:startPosition]; |
+ shift.toValue = [NSValue valueWithCGPoint:endPosition]; |
+ [layer addAnimation:shift forKey:@"shift"]; |
+ [CATransaction commit]; |
+} |
+ |
+- (void)fadeInNavigationControls { |
+ // Determine which navigation buttons are visible and need to be animated. |
+ UIView* firstView = nil; |
+ UIView* secondView = nil; |
+ if ([_forwardButton isEnabled]) { |
+ firstView = _forwardButton; |
+ secondView = _backButton; |
+ } else { |
+ firstView = _backButton; |
+ } |
+ |
+ [self fadeInView:firstView |
+ fromLeadingOffset:kPositionAnimationLeadingOffset |
+ withDuration:ios::material::kDuration2 |
+ afterDelay:ios::material::kDuration1]; |
+ |
+ if (secondView) { |
+ [self fadeInView:secondView |
+ fromLeadingOffset:kPositionAnimationLeadingOffset |
+ withDuration:ios::material::kDuration2 |
+ afterDelay:ios::material::kDuration3]; |
+ } |
+} |
+ |
+- (void)fadeOutNavigationControls { |
+ [CATransaction begin]; |
+ [CATransaction setAnimationDuration:ios::material::kDuration2]; |
+ [CATransaction |
+ setAnimationTimingFunction:TimingFunction(ios::material::CurveEaseIn)]; |
+ CABasicAnimation* fadeButtons = |
+ [CABasicAnimation animationWithKeyPath:@"opacity"]; |
+ fadeButtons.fromValue = @1; |
+ fadeButtons.toValue = @0; |
+ if ([_forwardButton isEnabled]) { |
+ [_forwardButton layer].opacity = 0; |
+ [[_forwardButton layer] addAnimation:fadeButtons forKey:@"fadeOut"]; |
+ } |
+ [_backButton layer].opacity = 0; |
+ [[_backButton layer] addAnimation:fadeButtons forKey:@"fadeOut"]; |
+ [CATransaction commit]; |
+ |
+ // Animate the navigation buttons 10 pixels to the left. |
+ [CATransaction begin]; |
+ [CATransaction setAnimationDuration:ios::material::kDuration1]; |
+ [CATransaction |
+ setAnimationTimingFunction:TimingFunction(ios::material::CurveEaseIn)]; |
+ CABasicAnimation* shiftBackButton = |
+ [CABasicAnimation animationWithKeyPath:@"position"]; |
+ CGPoint startPosition = [_backButton layer].position; |
+ CGPoint endPosition = |
+ CGPointLayoutOffset(startPosition, kPositionAnimationLeadingOffset); |
+ shiftBackButton.fromValue = [NSValue valueWithCGPoint:startPosition]; |
+ shiftBackButton.toValue = [NSValue valueWithCGPoint:endPosition]; |
+ [[_backButton layer] addAnimation:shiftBackButton forKey:@"shiftButton"]; |
+ CABasicAnimation* shiftForwardButton = |
+ [CABasicAnimation animationWithKeyPath:@"position"]; |
+ startPosition = [_forwardButton layer].position; |
+ endPosition = |
+ CGPointLayoutOffset(startPosition, kPositionAnimationLeadingOffset); |
+ shiftForwardButton.fromValue = [NSValue valueWithCGPoint:startPosition]; |
+ shiftForwardButton.toValue = [NSValue valueWithCGPoint:endPosition]; |
+ [[_forwardButton layer] addAnimation:shiftForwardButton |
+ forKey:@"shiftButton"]; |
+ [CATransaction commit]; |
+} |
+ |
+- (void)animationDidStop:(CAAnimation*)anim finished:(BOOL)flag { |
+ if ([[anim valueForKey:@"id"] isEqual:@"resizeOmnibox"] && |
+ ![_omniBox isFirstResponder]) { |
+ [_omniBox setRightView:nil]; |
+ } |
+} |
+ |
+- (void)updateSnapshotWithWidth:(CGFloat)width forced:(BOOL)force { |
+ // If |width| is 0, the current view's width is acceptable. |
+ if (width < 1) |
+ width = [self view].frame.size.width; |
+ |
+ // Snapshot is not used on the iPad. |
+ if (IsIPadIdiom()) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ // If the snapshot is valid, don't redraw. |
+ if (_snapshot.get() && _snapshotHash == [self snapshotHashWithWidth:width]) |
+ return; |
+ |
+ // Don't update the snapshot while the progress bar is moving, or while the |
+ // tools menu is open, unless |force| is true. |
+ BOOL shouldRedraw = force || ([self toolsPopupController] == nil && |
+ [_determinateProgressView isHidden]); |
+ if (!shouldRedraw) |
+ return; |
+ |
+ if ([[self delegate] |
+ respondsToSelector:@selector(willUpdateToolbarSnapshot)]) { |
+ [[self delegate] willUpdateToolbarSnapshot]; |
+ } |
+ |
+ // Temporarily resize the toolbar if necessary in order to match the desired |
+ // width. (Such a mismatch can occur if the device has been rotated while this |
+ // view was not visible, for example.) |
+ CGRect frame = [self view].frame; |
+ CGFloat oldWidth = frame.size.width; |
+ frame.size.width = width; |
+ [self view].frame = frame; |
+ |
+ UIGraphicsBeginImageContextWithOptions(frame.size, NO, 0.0); |
+ [[self view].layer renderInContext:UIGraphicsGetCurrentContext()]; |
+ _snapshot.reset([UIGraphicsGetImageFromCurrentImageContext() retain]); |
+ UIGraphicsEndImageContext(); |
+ |
+ // In the past, when the current tab was prerendered, taking a snapshot |
+ // sometimes lead to layout of its UIWebView. As this may be the fist time |
+ // the UIWebViews was layed out, its scroll view was scrolled. This lead |
+ // to scroll events that changed the frame of the toolbar when fullscreen |
+ // was enabled. |
+ // DCHECK that the toolbar frame does not change while taking a snapshot. |
+ DCHECK_EQ(frame.origin.x, [self view].frame.origin.x); |
+ DCHECK_EQ(frame.origin.y, [self view].frame.origin.y); |
+ DCHECK_EQ(frame.size.width, [self view].frame.size.width); |
+ DCHECK_EQ(frame.size.height, [self view].frame.size.height); |
+ |
+ frame.size.width = oldWidth; |
+ [self view].frame = frame; |
+ |
+ _snapshotHash = [self snapshotHashWithWidth:width]; |
+} |
+ |
+- (NSString*)updateTextForDotCom:(NSString*)text { |
+ if ([text isEqualToString:kDotComTLD]) { |
+ UITextRange* textRange = [_omniBox selectedTextRange]; |
+ NSInteger pos = [_omniBox offsetFromPosition:[_omniBox beginningOfDocument] |
+ toPosition:textRange.start]; |
+ if (pos > 0 && [[_omniBox text] characterAtIndex:pos - 1] == '.') |
+ return [kDotComTLD substringFromIndex:1]; |
+ } |
+ return text; |
+} |
+ |
+- (void)pressKey:(id)sender { |
+ DCHECK([sender isKindOfClass:[UIButton class]]); |
+ [[UIDevice currentDevice] playInputClick]; |
+ NSString* text = [self updateTextForDotCom:[sender currentTitle]]; |
+ [_omniBox insertTextWhileEditing:text]; |
+} |
+ |
+@end |
+ |
+@implementation WebToolbarController (Testing) |
+ |
+- (BOOL)isForwardButtonEnabled { |
+ return [_forwardButton isEnabled]; |
+} |
+ |
+- (BOOL)isBackButtonEnabled { |
+ return [_backButton isEnabled]; |
+} |
+ |
+- (BOOL)isStarButtonSelected { |
+ return [_starButton isSelected]; |
+} |
+ |
+- (void)setUnitTesting:(BOOL)unitTesting { |
+ _unitTesting = unitTesting; |
+} |
+ |
+- (CGFloat)loadProgressFraction { |
+ return [_determinateProgressView progress]; |
+} |
+ |
+- (std::string)getLocationText { |
+ return base::UTF16ToUTF8([_omniBox displayedText]); |
+} |
+ |
+- (BOOL)isLoading { |
+ return ![_determinateProgressView isHidden]; |
+} |
+ |
+- (BOOL)isPrerenderAnimationRunning { |
+ return _prerenderAnimating; |
+} |
+ |
+- (OmniboxTextFieldIOS*)omnibox { |
+ return _omniBox.get(); |
+} |
+ |
+@end |