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

Side by Side Diff: ios/chrome/browser/ui/toolbar/web_toolbar_controller.mm

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

Powered by Google App Engine
This is Rietveld 408576698