| 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
|
|
|