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

Side by Side Diff: ios/chrome/browser/ui/browser_view_controller.mm

Issue 2588713002: Upstream Chrome on iOS source code [4/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/browser_view_controller.h"
6
7 #import <AssetsLibrary/AssetsLibrary.h>
8 #import <MobileCoreServices/MobileCoreServices.h>
9 #import <PassKit/PassKit.h>
10 #import <Photos/Photos.h>
11 #import <QuartzCore/QuartzCore.h>
12
13 #include <stdint.h>
14 #include <cmath>
15 #include <memory>
16
17 #include "base/base64.h"
18 #include "base/command_line.h"
19 #include "base/format_macros.h"
20 #include "base/i18n/rtl.h"
21 #include "base/ios/block_types.h"
22 #include "base/ios/ios_util.h"
23 #include "base/ios/weak_nsobject.h"
24 #include "base/logging.h"
25 #include "base/mac/bind_objc_block.h"
26 #include "base/mac/bundle_locations.h"
27 #include "base/mac/foundation_util.h"
28 #include "base/mac/objc_property_releaser.h"
29 #import "base/mac/scoped_block.h"
30 #import "base/mac/scoped_nsobject.h"
31 #include "base/macros.h"
32 #include "base/memory/ptr_util.h"
33 #include "base/metrics/histogram.h"
34 #include "base/metrics/user_metrics.h"
35 #include "base/metrics/user_metrics_action.h"
36 #include "base/strings/string_piece.h"
37 #include "base/strings/string_util.h"
38 #include "base/strings/sys_string_conversions.h"
39 #include "components/bookmarks/browser/base_bookmark_model_observer.h"
40 #include "components/bookmarks/browser/bookmark_model.h"
41 #include "components/infobars/core/infobar_manager.h"
42 #include "components/prefs/pref_service.h"
43 #include "components/reading_list/core/reading_list_switches.h"
44 #include "components/reading_list/ios/reading_list_model.h"
45 #include "components/search_engines/search_engines_pref_names.h"
46 #include "components/search_engines/template_url_service.h"
47 #include "components/sessions/core/tab_restore_service_helper.h"
48 #include "components/strings/grit/components_strings.h"
49 #include "components/toolbar/toolbar_model_impl.h"
50 #include "ios/chrome/app/tests_hook.h"
51 #include "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
52 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
53 #include "ios/chrome/browser/chrome_url_constants.h"
54 #include "ios/chrome/browser/chrome_url_util.h"
55 #include "ios/chrome/browser/experimental_flags.h"
56 #import "ios/chrome/browser/favicon/favicon_loader.h"
57 #include "ios/chrome/browser/favicon/ios_chrome_favicon_loader_factory.h"
58 #import "ios/chrome/browser/find_in_page/find_in_page_controller.h"
59 #import "ios/chrome/browser/find_in_page/find_in_page_model.h"
60 #include "ios/chrome/browser/first_run/first_run.h"
61 #import "ios/chrome/browser/geolocation/omnibox_geolocation_controller.h"
62 #include "ios/chrome/browser/infobars/infobar_container_ios.h"
63 #include "ios/chrome/browser/infobars/infobar_container_view.h"
64 #import "ios/chrome/browser/metrics/new_tab_page_uma.h"
65 #include "ios/chrome/browser/metrics/tab_usage_recorder.h"
66 #import "ios/chrome/browser/native_app_launcher/native_app_navigation_controller .h"
67 #import "ios/chrome/browser/open_url_util.h"
68 #import "ios/chrome/browser/passwords/password_controller.h"
69 #import "ios/chrome/browser/payments/payment_request_manager.h"
70 #include "ios/chrome/browser/pref_names.h"
71 #include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
72 #include "ios/chrome/browser/search_engines/template_url_service_factory.h"
73 #include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
74 #include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios.h"
75 #include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios_fact ory.h"
76 #import "ios/chrome/browser/snapshots/snapshot_cache.h"
77 #import "ios/chrome/browser/snapshots/snapshot_overlay.h"
78 #import "ios/chrome/browser/snapshots/snapshot_overlay_provider.h"
79 #import "ios/chrome/browser/storekit_launcher.h"
80 #import "ios/chrome/browser/tabs/tab.h"
81 #import "ios/chrome/browser/tabs/tab_dialog_delegate.h"
82 #import "ios/chrome/browser/tabs/tab_model.h"
83 #import "ios/chrome/browser/tabs/tab_model_observer.h"
84 #import "ios/chrome/browser/tabs/tab_snapshotting_delegate.h"
85 #import "ios/chrome/browser/ui/activity_services/share_protocol.h"
86 #import "ios/chrome/browser/ui/activity_services/share_to_data.h"
87 #import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
88 #import "ios/chrome/browser/ui/authentication/re_signin_infobar_delegate.h"
89 #import "ios/chrome/browser/ui/background_generator.h"
90 #import "ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.h"
91 #import "ios/chrome/browser/ui/browser_container_view.h"
92 #import "ios/chrome/browser/ui/browser_list_ios.h"
93 #import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h"
94 #import "ios/chrome/browser/ui/chrome_web_view_factory.h"
95 #import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h"
96 #import "ios/chrome/browser/ui/commands/generic_chrome_command.h"
97 #include "ios/chrome/browser/ui/commands/ios_command_ids.h"
98 #import "ios/chrome/browser/ui/commands/open_url_command.h"
99 #import "ios/chrome/browser/ui/commands/reading_list_add_command.h"
100 #import "ios/chrome/browser/ui/commands/show_mail_composer_command.h"
101 #import "ios/chrome/browser/ui/context_menu/context_menu_coordinator.h"
102 #import "ios/chrome/browser/ui/contextual_search/contextual_search_controller.h"
103 #import "ios/chrome/browser/ui/contextual_search/contextual_search_mask_view.h"
104 #import "ios/chrome/browser/ui/contextual_search/contextual_search_metrics.h"
105 #import "ios/chrome/browser/ui/contextual_search/contextual_search_panel_protoco ls.h"
106 #import "ios/chrome/browser/ui/contextual_search/contextual_search_panel_view.h"
107 #import "ios/chrome/browser/ui/contextual_search/touch_to_search_permissions_med iator.h"
108 #import "ios/chrome/browser/ui/dialogs/dialog_presenter.h"
109 #import "ios/chrome/browser/ui/dialogs/java_script_dialog_presenter_impl.h"
110 #import "ios/chrome/browser/ui/elements/activity_overlay_coordinator.h"
111 #import "ios/chrome/browser/ui/external_file_controller.h"
112 #import "ios/chrome/browser/ui/external_file_remover.h"
113 #include "ios/chrome/browser/ui/file_locations.h"
114 #import "ios/chrome/browser/ui/find_bar/find_bar_controller_ios.h"
115 #import "ios/chrome/browser/ui/first_run/welcome_to_chrome_view_controller.h"
116 #import "ios/chrome/browser/ui/fullscreen_controller.h"
117 #import "ios/chrome/browser/ui/history/tab_history_cell.h"
118 #import "ios/chrome/browser/ui/key_commands_provider.h"
119 #import "ios/chrome/browser/ui/no_tabs/no_tabs_controller.h"
120 #import "ios/chrome/browser/ui/ntp/new_tab_page_controller.h"
121 #import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_panel_view_controller .h"
122 #include "ios/chrome/browser/ui/omnibox/page_info_model.h"
123 #import "ios/chrome/browser/ui/omnibox/page_info_view_controller.h"
124 #import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller. h"
125 #import "ios/chrome/browser/ui/page_not_available_controller.h"
126 #import "ios/chrome/browser/ui/preload_controller.h"
127 #import "ios/chrome/browser/ui/preload_controller_delegate.h"
128 #import "ios/chrome/browser/ui/print/print_controller.h"
129 #import "ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller.h"
130 #import "ios/chrome/browser/ui/reading_list/offline_page_native_content.h"
131 #import "ios/chrome/browser/ui/reading_list/reading_list_menu_notifier.h"
132 #import "ios/chrome/browser/ui/reading_list/reading_list_view_controller_builder .h"
133 #include "ios/chrome/browser/ui/rtl_geometry.h"
134 #import "ios/chrome/browser/ui/side_swipe/side_swipe_controller.h"
135 #import "ios/chrome/browser/ui/stack_view/card_view.h"
136 #import "ios/chrome/browser/ui/stack_view/page_animation_util.h"
137 #import "ios/chrome/browser/ui/static_content/static_html_native_content.h"
138 #import "ios/chrome/browser/ui/sync/sync_util.h"
139 #import "ios/chrome/browser/ui/tab_switcher/tab_switcher_controller.h"
140 #import "ios/chrome/browser/ui/tabs/tab_strip_controller.h"
141 #import "ios/chrome/browser/ui/toolbar/toolbar_controller.h"
142 #include "ios/chrome/browser/ui/toolbar/toolbar_model_delegate_ios.h"
143 #include "ios/chrome/browser/ui/toolbar/toolbar_model_ios.h"
144 #import "ios/chrome/browser/ui/tools_menu/tools_menu_context.h"
145 #import "ios/chrome/browser/ui/tools_menu/tools_menu_view_item.h"
146 #import "ios/chrome/browser/ui/tools_menu/tools_popup_controller.h"
147 #include "ios/chrome/browser/ui/ui_util.h"
148 #import "ios/chrome/browser/ui/uikit_ui_util.h"
149 #import "ios/chrome/browser/ui/voice/text_to_speech_player.h"
150 #include "ios/chrome/browser/upgrade/upgrade_center.h"
151 #import "ios/chrome/browser/web/error_page_content.h"
152 #import "ios/chrome/browser/web/passkit_dialog_provider.h"
153 #import "ios/chrome/browser/xcallback_parameters.h"
154 #import "ios/chrome/common/material_timing.h"
155 #include "ios/chrome/grit/ios_chromium_strings.h"
156 #include "ios/chrome/grit/ios_strings.h"
157 #import "ios/net/request_tracker.h"
158 #include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
159 #include "ios/public/provider/chrome/browser/ui/app_rating_prompt.h"
160 #include "ios/public/provider/chrome/browser/ui/default_ios_web_view_factory.h"
161 #import "ios/public/provider/chrome/browser/voice/voice_search_bar.h"
162 #import "ios/public/provider/chrome/browser/voice/voice_search_bar_owner.h"
163 #include "ios/public/provider/chrome/browser/voice/voice_search_controller.h"
164 #include "ios/public/provider/chrome/browser/voice/voice_search_controller_deleg ate.h"
165 #include "ios/public/provider/chrome/browser/voice/voice_search_provider.h"
166 #import "ios/third_party/material_components_ios/src/components/Snackbar/src/Mat erialSnackbar.h"
167 #import "ios/web/navigation/crw_session_controller.h"
168 #import "ios/web/navigation/crw_session_entry.h"
169 #include "ios/web/navigation/navigation_manager_impl.h"
170 #include "ios/web/public/active_state_manager.h"
171 #include "ios/web/public/image_fetcher/image_data_fetcher.h"
172 #include "ios/web/public/navigation_item.h"
173 #import "ios/web/public/navigation_manager.h"
174 #include "ios/web/public/referrer_util.h"
175 #include "ios/web/public/ssl_status.h"
176 #include "ios/web/public/url_scheme_util.h"
177 #include "ios/web/public/web_client.h"
178 #import "ios/web/public/web_state/context_menu_params.h"
179 #import "ios/web/public/web_state/crw_web_view_proxy.h"
180 #import "ios/web/public/web_state/ui/crw_native_content_provider.h"
181 #include "ios/web/public/web_state/web_state.h"
182 #import "ios/web/public/web_state/web_state_delegate_bridge.h"
183 #include "ios/web/public/web_thread.h"
184 #import "ios/web/web_state/ui/crw_web_controller.h"
185 #import "net/base/mac/url_conversions.h"
186 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
187 #include "net/ssl/ssl_info.h"
188 #include "net/url_request/url_request_context_getter.h"
189 #include "third_party/google_toolbox_for_mac/src/iPhone/GTMUIImage+Resize.h"
190 #include "ui/base/l10n/l10n_util.h"
191 #include "ui/base/l10n/l10n_util_mac.h"
192 #include "ui/base/page_transition_types.h"
193 #include "url/gurl.h"
194
195 using base::UserMetricsAction;
196 using bookmarks::BookmarkNode;
197
198 class BrowserBookmarkModelBridge;
199 class InfoBarContainerDelegateIOS;
200
201 namespace ios_internal {
202 NSString* const kPageInfoWillShowNotification =
203 @"kPageInfoWillShowNotification";
204 NSString* const kPageInfoWillHideNotification =
205 @"kPageInfoWillHideNotification";
206 NSString* const kLocationBarBecomesFirstResponderNotification =
207 @"kLocationBarBecomesFirstResponderNotification";
208 NSString* const kLocationBarResignsFirstResponderNotification =
209 @"kLocationBarResignsFirstResponderNotification";
210 } // namespace ios_internal
211
212 namespace {
213
214 typedef NS_ENUM(NSInteger, ContextMenuHistogram) {
215 // Note: these values must match the ContextMenuOption enum in histograms.xml.
216 ACTION_OPEN_IN_NEW_TAB = 0,
217 ACTION_OPEN_IN_INCOGNITO_TAB = 1,
218 ACTION_COPY_LINK_ADDRESS = 2,
219 ACTION_SAVE_IMAGE = 6,
220 ACTION_OPEN_IMAGE = 7,
221 ACTION_OPEN_IMAGE_IN_NEW_TAB = 8,
222 ACTION_SEARCH_BY_IMAGE = 11,
223 ACTION_OPEN_JAVASCRIPT = 21,
224 ACTION_READ_LATER = 22,
225 NUM_ACTIONS = 23,
226 };
227
228 void Record(NSInteger action, bool is_image, bool is_link) {
229 if (is_image) {
230 if (is_link) {
231 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.ImageLink", action,
232 NUM_ACTIONS);
233 } else {
234 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Image", action,
235 NUM_ACTIONS);
236 }
237 } else {
238 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Link", action,
239 NUM_ACTIONS);
240 }
241 }
242
243 // Duration to show or hide the No-Tabs UI.
244 const NSTimeInterval kNoTabsAnimationDuration = 0.25;
245
246 const CGFloat kVoiceSearchBarHeight = 59.0;
247
248 // Dimensions to use when downsizing an image for search-by-image.
249 const CGFloat kSearchByImageMaxImageArea = 90000.0;
250 const CGFloat kSearchByImageMaxImageWidth = 600.0;
251 const CGFloat kSearchByImageMaxImageHeight = 400.0;
252
253 // The delay, in seconds, after startup before cleaning up the files received
254 // from other applications that are not bookmarked nor referenced by an open or
255 // recently closed tab.
256 const int kExternalFilesCleanupDelaySeconds = 60;
257
258 enum HeaderBehaviour {
259 // The header moves completely out of the screen.
260 Hideable = 0,
261 // This header stays on screen and doesn't overlap with the content.
262 Visible,
263 // This header stay on screen and covers part of the content.
264 Overlap
265 };
266
267 struct HeaderDefinition {
268 // The header view.
269 base::scoped_nsobject<UIView> view;
270 // How to place the view, and its behaviour when the headers move.
271 HeaderBehaviour behaviour;
272 // Reduces the height of a header to adjust for shadows.
273 CGFloat heightAdjustement;
274 // Nudges that particular header up by this number of points.
275 CGFloat inset;
276 };
277
278 const CGFloat kIPadFindBarOverlap = 11;
279
280 bool IsURLAllowedInIncognito(const GURL& url) {
281 // Most URLs are allowed in incognito; the following are exceptions.
282 if (!url.SchemeIs(kChromeUIScheme))
283 return true;
284 std::string url_host = url.host();
285 return url_host != kChromeUIHistoryHost &&
286 url_host != kChromeUIHistoryFrameHost;
287 }
288
289 // Temporary key to use when storing native controllers vended to tabs before
290 // they are added to the tab model.
291 NSString* const kNativeControllerTemporaryKey = @"NativeControllerTemporaryKey";
292
293 NSString* const kReadingListSnackbarCategory = @"ReadingListSnackbarCategory";
294
295 } // anonymous namespace
296
297 @interface BrowserViewController ()<AppRatingPromptDelegate,
298 ContextualSearchControllerDelegate,
299 ContextualSearchPanelMotionObserver,
300 CRWNativeContentProvider,
301 CRWWebStateDelegate,
302 DialogPresenterDelegate,
303 FullScreenControllerDelegate,
304 KeyCommandsPlumbing,
305 MFMailComposeViewControllerDelegate,
306 NewTabPageControllerObserver,
307 OverscrollActionsControllerDelegate,
308 PassKitDialogProvider,
309 PreloadControllerDelegate,
310 ShareToDelegate,
311 SKStoreProductViewControllerDelegate,
312 SnapshotOverlayProvider,
313 StoreKitLauncher,
314 TabDialogDelegate,
315 TabModelObserver,
316 TabSnapshottingDelegate,
317 UIGestureRecognizerDelegate,
318 UpgradeCenterClientProtocol,
319 VoiceSearchBarDelegate,
320 VoiceSearchBarOwner> {
321 // The dependency factory passed on initialization. Used to vend objects used
322 // by the BVC.
323 base::scoped_nsobject<BrowserViewControllerDependencyFactory>
324 _dependencyFactory;
325
326 // The browser's tab model.
327 base::scoped_nsobject<TabModel> _model;
328
329 // Facade objects used by |_toolbarController|.
330 // Must outlive |_toolbarController|.
331 std::unique_ptr<ToolbarModelDelegateIOS> _toolbarModelDelegate;
332 std::unique_ptr<ToolbarModelIOS> _toolbarModelIOS;
333
334 // Preload controller. Must outlive |_toolbarController|.
335 base::scoped_nsobject<PreloadController> _preloadController;
336
337 // The WebToolbarController used to display the omnibox.
338 base::scoped_nsobject<WebToolbarController> _toolbarController;
339
340 // Controller for edge swipe gestures for page and tab navigation.
341 base::scoped_nsobject<SideSwipeController> _sideSwipeController;
342
343 // Handles displaying the context menu for all form factors.
344 base::scoped_nsobject<ContextMenuCoordinator> _contextMenuCoordinator;
345
346 // Backing object for property of the same name.
347 base::scoped_nsobject<DialogPresenter> _dialogPresenter;
348
349 // Handles presentation of JavaScript dialogs.
350 std::unique_ptr<JavaScriptDialogPresenterImpl> _javaScriptDialogPresenter;
351
352 // Keyboard commands provider. It offloads most of the keyboard commands
353 // management off of the BVC.
354 base::scoped_nsobject<KeyCommandsProvider> _keyCommandsProvider;
355
356 // Calls to |-relinquishedToolbarController| will set this to yes, and calls
357 // to |-reparentToolbarController| will reset it to NO.
358 BOOL _isToolbarControllerRelinquished;
359
360 // The controller that owns the currently relinquished toolbar controller.
361 // The reference is weak because it's possible for the toolbar owner to be
362 // deallocated mid-animation due to memory pressure or a tab being closed
363 // before the animation is finished.
364 base::WeakNSObject<id> _relinquishedToolbarOwner;
365
366 // Always present on tablet; always nil on phone.
367 base::scoped_nsobject<TabStripController> _tabStripController;
368
369 // The contextual search controller.
370 base::scoped_nsobject<ContextualSearchController> _contextualSearchController;
371
372 // The contextual search panel (always a subview of |self.view| if it exists).
373 ContextualSearchPanelView* _contextualSearchPanel;
374
375 // The contextual search mask (always a subview of |self.view| if it exists).
376 ContextualSearchMaskView* _contextualSearchMask;
377
378 // Used to inject Javascript implementing the PaymentRequest API and to
379 // display the UI.
380 base::scoped_nsobject<PaymentRequestManager> _paymentRequestManager;
381
382 // Used to display the Page Info UI. Nil if not visible.
383 base::scoped_nsobject<PageInfoViewController> _pageInfoController;
384
385 // Used to display the Voice Search UI. Nil if not visible.
386 scoped_refptr<VoiceSearchController> _voiceSearchController;
387
388 // Used to display the QR Scanner UI. Nil if not visible.
389 base::scoped_nsobject<QRScannerViewController> _qrScannerViewController;
390
391 // Used to display the Find In Page UI. Nil if not visible.
392 base::scoped_nsobject<FindBarControllerIOS> _findBarController;
393
394 // Used to display the No-Tabs UI for iPads. Nil if not visible.
395 base::scoped_nsobject<NoTabsController> _noTabsController;
396
397 // Used to display the Print UI. Nil if not visible.
398 base::scoped_nsobject<PrintController> _printController;
399
400 // Records the set of domains for which full screen alert has already been
401 // shown.
402 base::scoped_nsobject<NSMutableSet> _fullScreenAlertShown;
403
404 // Adapter to let BVC be the delegate for WebState.
405 std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
406
407 // YES if new tab is animating in.
408 BOOL _inNewTabAnimation;
409
410 // YES if Voice Search should be started when the new tab animation is
411 // finished.
412 BOOL _startVoiceSearchAfterNewTabAnimation;
413
414 // YES if the user interacts with the location bar.
415 BOOL _locationBarHasFocus;
416 // YES if a load was cancelled due to typing in the location bar.
417 BOOL _locationBarEditCancelledLoad;
418 // YES if waiting for a foreground tab due to expectNewForegroundTab.
419 BOOL _expectingForegroundTab;
420
421 // The ChromeBrowserState associated with this BVC.
422 ios::ChromeBrowserState* _browserState; // weak
423
424 // Whether or not Incognito* is enabled.
425 BOOL _isOffTheRecord;
426
427 // The last point within |_contentArea| that's received a touch.
428 CGPoint _lastTapPoint;
429
430 // The time at which |_lastTapPoint| was most recently set.
431 CFTimeInterval _lastTapTime;
432
433 // A single infobar container handles all infobars in all tabs. It keeps
434 // track of infobars for current tab (accessed via infobar helper of
435 // the current tab).
436 std::unique_ptr<InfoBarContainerIOS> _infoBarContainer;
437
438 // Bridge class to deliver container change notifications to BVC.
439 std::unique_ptr<InfoBarContainerDelegateIOS> _infoBarContainerDelegate;
440
441 // Voice search bar at the bottom of the view overlayed on |_contentArea|
442 // when displaying voice search results. Implementation is not complete.
443 // See b/7998125.
444 base::scoped_nsprotocol<UIView<VoiceSearchBar>*> _voiceSearchBar;
445
446 // The image fetcher used to save images and perform image-based searches.
447 std::unique_ptr<web::ImageDataFetcher> _imageFetcher;
448
449 // Card side swipe view.
450 base::scoped_nsobject<CardSideSwipeView> _sideSwipeView;
451
452 // Used to cache value of |hasModeToggleSwitch| if set before the tab strip
453 // controller has been created.
454 BOOL _modeToggleNeedsSetting;
455
456 // Dominant color cache. Key: (NSString*)url, val: (UIColor*)dominantColor.
457 base::scoped_nsobject<NSMutableDictionary> _dominantColorCache;
458
459 // Bridge to register for bookmark changes.
460 std::unique_ptr<BrowserBookmarkModelBridge> _bookmarkModelBridge;
461
462 // Cached pointer to the bookmarks model.
463 bookmarks::BookmarkModel* _bookmarkModel; // weak
464
465 // The controller that shows the bookmarking UI after the user taps the star
466 // button.
467 base::scoped_nsobject<BookmarkInteractionController>
468 _bookmarkInteractionController;
469
470 // Used to remove unreferenced external files.
471 std::unique_ptr<ExternalFileRemover> _externalFileRemover;
472
473 // The currently displayed "Rate This App" dialog, if one exists.
474 base::scoped_nsprotocol<id<AppRatingPrompt>> _rateThisAppDialog;
475
476 // Maps tab IDs to the most recent native content controller vended to that
477 // tab's web controller.
478 base::scoped_nsobject<NSMapTable> _nativeControllersForTabIDs;
479
480 // Notifies the toolbar menu of reading list changes.
481 base::scoped_nsobject<ReadingListMenuNotifier> _readingListMenuNotifier;
482
483 // The sender for the last received IDC_VOICE_SEARCH command.
484 base::WeakNSObject<UIView> _voiceSearchButton;
485
486 // Coordinator for displaying alerts.
487 base::scoped_nsobject<AlertCoordinator> _alertCoordinator;
488
489 // Releaser for properties that aren't backed by scoped_nsobjects.
490 base::mac::ObjCPropertyReleaser _propertyReleaser_BrowserViewController;
491 }
492
493 // The browser's side swipe controller. Lazily instantiated on the first call.
494 @property(nonatomic, retain, readonly) SideSwipeController* sideSwipeController;
495 // The browser's preload controller.
496 @property(nonatomic, retain, readonly) PreloadController* preloadController;
497 // The dialog presenter for this BVC's tab model.
498 @property(nonatomic, retain, readonly) DialogPresenter* dialogPresenter;
499 // The object that manages keyboard commands on behalf of the BVC.
500 @property(nonatomic, retain, readonly) KeyCommandsProvider* keyCommandsProvider;
501 // Whether the current tab can enable the reader mode menu item.
502 @property(nonatomic, assign, readonly) BOOL canUseReaderMode;
503 // Whether the current tab can enable the request desktop menu item.
504 @property(nonatomic, assign, readonly) BOOL canUseDesktopUserAgent;
505 // Whether the sharing menu should be enabled.
506 @property(nonatomic, assign, readonly) BOOL canShowShareMenu;
507 // Helper method to check web controller canShowFindBar method.
508 @property(nonatomic, assign, readonly) BOOL canShowFindBar;
509 // Whether the controller's view is currently available.
510 // YES from viewWillAppear to viewWillDisappear.
511 @property(nonatomic, assign, getter=isVisible) BOOL visible;
512 // Whether the controller's view is currently visible.
513 // YES from viewDidAppear to viewWillDisappear.
514 @property(nonatomic, assign) BOOL viewVisible;
515 // Whether the controller is currently dismissing a presented view controller.
516 @property(nonatomic, assign, getter=isDismissingModal) BOOL dismissingModal;
517 // Returns YES if the toolbar has not been scrolled out by fullscreen.
518 @property(nonatomic, assign, readonly, getter=isToolbarOnScreen)
519 BOOL toolbarOnScreen;
520 // Whether a new tab animation is occurring.
521 @property(nonatomic, assign, readonly, getter=isInNewTabAnimation)
522 BOOL inNewTabAnimation;
523 // Whether BVC prefers to hide the status bar. This value is used to determine
524 // the response from the |prefersStatusBarHidden| method.
525 @property(nonatomic, assign) BOOL hideStatusBar;
526 // Whether the VoiceSearchBar should be displayed.
527 @property(nonatomic, readonly) BOOL shouldShowVoiceSearchBar;
528 // Coordinator for displaying a modal overlay with activity indicator to prevent
529 // the user from interacting with the browser view.
530 @property(nonatomic, retain)
531 ActivityOverlayCoordinator* activityOverlayCoordinator;
532
533 // BVC initialization:
534 // If the BVC is initialized with a valid browser state & tab model immediately,
535 // the path is straightforward: functionality is enabled, and the UI is built
536 // when -viewDidLoad is called.
537 // If the BVC is initialized without a browser state or tab model, the tab model
538 // and browser state may or may not be provided before -viewDidLoad is called.
539 // In most cases, they will not, to improve startup performance.
540 // In order to handle this, initialization of various aspects of BVC have been
541 // broken out into the following functions, which have expectations (enforced
542 // with DCHECKs) regarding |_browserState|, |_model|, and [self isViewLoaded].
543
544 // Registers for notifications.
545 - (void)registerForNotifications;
546 // Called when a tab is starting to load. If it's a link click or form
547 // submission, the user is navigating away from any entries in the forward
548 // history. Tell the toolbar so it can update the UI appropriately.
549 // See the warning on [Tab webWillStartLoadingURL] about invocation of this
550 // method sequence by malicious pages.
551 - (void)pageLoadStarting:(NSNotification*)notify;
552 // Called when a tab actually starts loading.
553 - (void)pageLoadStarted:(NSNotification*)notify;
554 // Called when a tab finishes loading. Update the Omnibox with the url and
555 // stop any page load progess display.
556 - (void)pageLoadComplete:(NSNotification*)notify;
557 // Called when a tab is deselected in the model.
558 // This notification also occurs when a tab is closed.
559 - (void)tabDeselected:(NSNotification*)notify;
560 // Animates sliding current tab and rotate-entering new tab while new tab loads
561 // in background on the iPhone only.
562 - (void)tabWasAdded:(NSNotification*)notify;
563
564 // Updates non-view-related functionality with the given browser state and tab
565 // model.
566 // Does not matter whether or not the view has been loaded.
567 - (void)updateWithTabModel:(TabModel*)model
568 browserState:(ios::ChromeBrowserState*)browserState;
569 // On iOS7, iPad should match iOS6 status bar. Install a simple black bar under
570 // the status bar to mimic this layout.
571 - (void)installFakeStatusBar;
572 // Builds the UI parts of tab strip and the toolbar. Does not matter whether
573 // or not browser state and tab model are valid.
574 - (void)buildToolbarAndTabStrip;
575 // Updates view-related functionality with the given tab model and browser
576 // state. The view must have been loaded. Uses |_browserState| and |_model|.
577 - (void)addUIFunctionalityForModelAndBrowserState;
578 // Sets the correct frame and heirarchy for subviews and helper views.
579 - (void)setUpViewLayout;
580 // Sets the correct frame for the tab strip based on the given maximum width.
581 - (void)layoutTabStripForWidth:(CGFloat)maxWidth;
582 // Makes |tab| the currently visible tab, displaying its view. Calls
583 // -selectedTabChanged on the toolbar only if |newSelection| is YES.
584 - (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection;
585 // Initializes the bookmark interaction controller if not already initialized.
586 - (void)initializeBookmarkInteractionController;
587
588 // Shows the No-Tabs UI with animation.
589 - (void)showNoTabsUI;
590 // Dismisses the No-Tabs UI with animation.
591 - (void)dismissNoTabsUI;
592
593 // Shows the tools menu popup.
594 - (void)showToolsMenuPopup;
595 // Add all delegates to the provided |tab|.
596 - (void)installDelegatesForTab:(Tab*)tab;
597 // Closes the current tab, with animation if applicable.
598 - (void)closeCurrentTab;
599 // Returns an autoreleased share to data for |tab|.
600 - (ShareToData*)shareToDataForTab:(Tab*)tab;
601 // Shows the menu to initiate sharing |data|.
602 - (void)sharePageWithData:(ShareToData*)data;
603 // Convenience method to share the current page.
604 - (void)sharePage;
605 // Prints the web page in the current tab.
606 - (void)print;
607 // Shows the Online Help Page in a tab.
608 - (void)showHelpPage;
609 // Show the bookmarks page.
610 - (void)showAllBookmarks;
611 // Shows a panel within the New Tab Page.
612 - (void)showNTPPanel:(NewTabPage::PanelIdentifier)panel;
613 // Shows the "rate this app" dialog.
614 - (void)showRateThisAppDialog;
615 // Dismisses the "rate this app" dialog.
616 - (void)dismissRateThisAppDialog;
617 #if !defined(NDEBUG)
618 // Shows the source of the current page.
619 - (void)viewSource;
620 #endif
621 // Whether the given tab's url begins with the chrome prefix.
622 - (BOOL)isTabNativePage:(Tab*)tab;
623 // Returns the view to use when animating a page in or out, positioning it to
624 // fill the content area but not actually adding it to the view hierarchy.
625 - (UIImageView*)pageOpenCloseAnimationView;
626 // Returns the view to use when animating full screen NTP paper in, filling the
627 // entire screen but not actually adding it to the view hierarchy.
628 - (UIImageView*)pageFullScreenOpenCloseAnimationView;
629 // Updates the toolbar display based on the current tab.
630 - (void)updateToolbar;
631 // Updates |dialogPresenter|'s |active| property to account for the BVC's
632 // |active| and |visible| properties.
633 - (void)updateDialogPresenterActiveState;
634 // Dismisses popups and modal dialogs that are displayed above the BVC upon size
635 // changes (e.g. rotation, resizing,…) or when the accessibility escape gesture
636 // is performed.
637 // TODO(crbug.com/522721): Support size changes for all popups and modal
638 // dialogs.
639 - (void)dismissPopups;
640 // Create and show the find bar.
641 - (void)initFindBarForTab;
642 // Search for find bar query string.
643 - (void)searchFindInPage;
644 // Update find bar with model data. If |shouldFocus| is set to YES, the text
645 // field will become first responder.
646 - (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus;
647 // Close and disable find in page bar.
648 - (void)closeFindInPage;
649 // Hide find bar.
650 - (void)hideFindBarWithAnimation:(BOOL)animate;
651 // Shows find bar. If |selectText| is YES, all text inside the Find Bar
652 // textfield will be selected. If |shouldFocus| is set to YES, the textfield is
653 // set to be first responder.
654 - (void)showFindBarWithAnimation:(BOOL)animate
655 selectText:(BOOL)selectText
656 shouldFocus:(BOOL)shouldFocus;
657 // Show the Page Security Info.
658 - (void)showPageInfoPopupForView:(UIView*)sourceView;
659 // Hide the Page Security Info.
660 - (void)hidePageInfoPopupForView:(UIView*)sourceView;
661 // Shows the tab history popup containing the tab's backward history.
662 - (void)showTabHistoryPopupForBackwardHistory;
663 // Shows the tab history popup containing the tab's forward history.
664 - (void)showTabHistoryPopupForForwardHistory;
665 // Navigate back/forward to the selected entry in the tab's history.
666 - (void)navigateToSelectedEntry:(id)sender;
667 // The infobar state (typically height) has changed.
668 - (void)infoBarContainerStateChanged:(bool)is_animating;
669 // Adds a CardView on top of the contentArea either taking the size of the full
670 // screen or just the size of the space under the header.
671 // Returns the CardView that was added.
672 - (CardView*)addCardViewInFullscreen:(BOOL)fullScreen;
673 // Called when either a tab finishes loading or when a tab with finished content
674 // is added directly to the model via pre-rendering. The tab must be non-nil and
675 // must be a member of the tab model controlled by this BrowserViewController.
676 - (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success;
677 // Evaluates Javascript asynchronously using the current page context.
678 - (void)openJavascript:(NSString*)javascript;
679 // Sets the desktop user agent flag and reload the current page.
680 - (void)enableDesktopUserAgent;
681 // Helper methods used by ShareToDelegate methods.
682 // Shows an alert with the given title and message id.
683 - (void)showErrorAlert:(int)titleMessageId message:(int)messageId;
684 // Helper method displaying an alert with the given title and message.
685 // Dismisses previous alert if it has not been dismissed yet.
686 - (void)showErrorAlertWithStringTitle:(NSString*)title
687 message:(NSString*)message;
688 // Shows a self-dismissing snackbar displaying |message|.
689 - (void)showSnackbar:(NSString*)message;
690 // Induces an intentional crash in the browser process.
691 - (void)induceBrowserCrash;
692 // Saves the image or display error message, based on privacy settings.
693 - (void)managePermissionAndSaveImage:(NSData*)data fileName:(NSString*)fileName;
694 // Saves the image. In order to keep the metadata of the image, the image is
695 // saved as a temporary file on disk then saved in photos.
696 // This should be called on FILE thread.
697 - (void)saveImage:(NSData*)data fileName:(NSString*)fileName;
698 // Called when Chrome has been denied access to the photos or videos and the
699 // user can change it.
700 // Shows a privacy alert on the main queue, allowing the user to go to Chrome's
701 // settings. Dismiss previous alert if it has not been dismissed yet.
702 - (void)displayImageErrorAlertWithSettingsOnMainQueue;
703 // Shows a privacy alert allowing the user to go to Chrome's settings. Dismiss
704 // previous alert if it has not been dismissed yet.
705 - (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL;
706 // Called when Chrome has been denied access to the photos or videos and the
707 // user cannot change it.
708 // Shows a privacy alert on the main queue, with errorContent as the message.
709 // Dismisses previous alert if it has not been dismissed yet.
710 - (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent;
711 // Called with the results of saving a picture in the photo album. If error is
712 // nil the save succeeded.
713 - (void)finishSavingImageWithError:(NSError*)error;
714 // Provides a view that encompasses currently displayed infobar(s) or nil
715 // if no infobar is presented.
716 - (UIView*)infoBarOverlayViewForTab:(Tab*)tab;
717 // Returns a vertical infobar offset relative to the tab content.
718 - (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab;
719 // Provides a view that encompasses the voice search bar if it's displayed or
720 // nil if the voice search bar isn't displayed.
721 - (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab;
722 // Returns a vertical voice search bar offset relative to the tab content.
723 - (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab;
724 // Lazily instantiates |_voiceSearchController|.
725 - (void)ensureVoiceSearchControllerCreated;
726 // Lazily instantiates |_voiceSearchBar| and adds it to the view.
727 - (void)ensureVoiceSearchBarCreated;
728 // Shows/hides the voice search bar.
729 - (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated;
730 // The LogoAnimationControllerOwner to be used for the next logo transition
731 // animation.
732 - (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner;
733 // Returns the header views, all the chrome on top of the page, including the
734 // ones that cannot be scrolled off screen by full screen.
735 - (const std::vector<HeaderDefinition>)headerViews;
736 // Returns the footer view if one exists (e.g. the voice search bar).
737 - (UIView*)footerView;
738 // Returns the height of the header view for the tab model's current tab.
739 - (CGFloat)headerHeight;
740 // Returns the height of the header view for |tab|.
741 - (CGFloat)headerHeightForTab:(Tab*)tab;
742 // Sets the frame for the headers.
743 - (void)setFramesForHeaders:(const std::vector<HeaderDefinition>)headers
744 atOffset:(CGFloat)headerOffset;
745 // Returns the y coordinate for the footer's frame when animating the footer
746 // in/out of fullscreen.
747 - (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset;
748 // Called when the animation for setting the header view's offset is finished.
749 // |completed| should indicate if the animation finished completely or was
750 // interrupted. |offset| should indicate the header offset after the animation.
751 // |dragged| should indicate if the header moved due to the user dragging.
752 - (void)fullScreenController:(FullScreenController*)controller
753 headerAnimationCompleted:(BOOL)completed
754 offset:(CGFloat)offset;
755 // Performs a search with the image at the given url. The referrer is used to
756 // download the image.
757 - (void)searchByImageAtURL:(const GURL&)url
758 referrer:(const web::Referrer)referrer;
759 // Saves the image at the given URL on the system's album. The referrer is used
760 // to download the image.
761 - (void)saveImageAtURL:(const GURL&)url referrer:(const web::Referrer&)referrer;
762
763 // Determines the center of |sender| if it's a view or a toolbar item, and save
764 // the CGPoint and timestamp.
765 - (void)setLastTapPoint:(id)sender;
766 // Get return the last stored |_lastTapPoint| if it's been set within the past
767 // second.
768 - (CGPoint)lastTapPoint;
769 // Store the tap CGPoint in |_lastTapPoint| and the current timestamp.
770 - (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer;
771 // Returns the native controller being used by |tab|'s web controller.
772 - (id)nativeControllerForTab:(Tab*)tab;
773 // Installs the BVC as overscroll actions controller of |nativeContent| if
774 // needed. Sets the style of the overscroll actions toolbar.
775 - (void)setOverScrollActionControllerToStaticNativeContent:
776 (StaticHtmlNativeContent*)nativeContent;
777 // Whether the BVC should declare keyboard commands.
778 - (BOOL)shouldRegisterKeyboardCommands;
779 // Adds the given url to the reading list.
780 - (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title;
781 @end
782
783 class InfoBarContainerDelegateIOS
784 : public infobars::InfoBarContainer::Delegate {
785 public:
786 explicit InfoBarContainerDelegateIOS(BrowserViewController* controller)
787 : controller_(controller) {}
788
789 ~InfoBarContainerDelegateIOS() override {}
790
791 private:
792 SkColor GetInfoBarSeparatorColor() const override {
793 NOTIMPLEMENTED();
794 return SK_ColorBLACK;
795 }
796
797 int ArrowTargetHeightForInfoBar(
798 size_t index,
799 const gfx::SlideAnimation& animation) const override {
800 return 0;
801 }
802
803 void ComputeInfoBarElementSizes(const gfx::SlideAnimation& animation,
804 int arrow_target_height,
805 int bar_target_height,
806 int* arrow_height,
807 int* arrow_half_width,
808 int* bar_height) const override {
809 DCHECK_NE(-1, bar_target_height)
810 << "Infobars don't have a default height on iOS";
811 *arrow_height = 0;
812 *arrow_half_width = 0;
813 *bar_height = animation.CurrentValueBetween(0, bar_target_height);
814 }
815
816 void InfoBarContainerStateChanged(bool is_animating) override {
817 [controller_ infoBarContainerStateChanged:is_animating];
818 }
819
820 bool DrawInfoBarArrows(int* x) const override { return false; }
821
822 BrowserViewController* controller_; // weak
823 };
824
825 // Called from the BrowserBookmarkModelBridge from C++ -> ObjC.
826 @interface BrowserViewController (BookmarkBridgeMethods)
827 // If a bookmark matching the currentTab url is added or moved, update the
828 // toolbar state so the star highlight is in sync.
829 - (void)bookmarkNodeModified:(const BookmarkNode*)node;
830 - (void)allBookmarksRemoved;
831 @end
832
833 // Handle notification that bookmarks has been removed changed so we can update
834 // the bookmarked star icon.
835 class BrowserBookmarkModelBridge : public bookmarks::BookmarkModelObserver {
836 public:
837 explicit BrowserBookmarkModelBridge(BrowserViewController* owner)
838 : owner_(owner) {}
839
840 ~BrowserBookmarkModelBridge() override {}
841
842 void BookmarkNodeRemoved(bookmarks::BookmarkModel* model,
843 const BookmarkNode* parent,
844 int old_index,
845 const BookmarkNode* node,
846 const std::set<GURL>& removed_urls) override {
847 [owner_ bookmarkNodeModified:node];
848 }
849
850 void BookmarkModelLoaded(bookmarks::BookmarkModel* model,
851 bool ids_reassigned) override {}
852
853 void BookmarkNodeMoved(bookmarks::BookmarkModel* model,
854 const BookmarkNode* old_parent,
855 int old_index,
856 const BookmarkNode* new_parent,
857 int new_index) override {}
858
859 void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
860 const BookmarkNode* parent,
861 int index) override {
862 [owner_ bookmarkNodeModified:parent->GetChild(index)];
863 }
864
865 void BookmarkNodeChanged(bookmarks::BookmarkModel* model,
866 const BookmarkNode* node) override {}
867
868 void BookmarkNodeFaviconChanged(bookmarks::BookmarkModel* model,
869 const BookmarkNode* node) override {}
870
871 void BookmarkNodeChildrenReordered(bookmarks::BookmarkModel* model,
872 const BookmarkNode* node) override {}
873
874 void BookmarkAllUserNodesRemoved(
875 bookmarks::BookmarkModel* model,
876 const std::set<GURL>& removed_urls) override {
877 [owner_ allBookmarksRemoved];
878 }
879
880 private:
881 BrowserViewController* owner_; // Weak.
882 };
883
884 @implementation BrowserViewController
885
886 @synthesize contentArea = _contentArea;
887 @synthesize typingShield = _typingShield;
888 @synthesize active = _active;
889 @synthesize visible = _visible;
890 @synthesize viewVisible = _viewVisible;
891 @synthesize dismissingModal = _dismissingModal;
892 @synthesize hideStatusBar = _hideStatusBar;
893 @synthesize activityOverlayCoordinator = _activityOverlayCoordinator;
894 @synthesize presenting = _presenting;
895
896 #pragma mark - Object lifecycle
897
898 - (instancetype)initWithTabModel:(TabModel*)model
899 browserState:(ios::ChromeBrowserState*)browserState
900 dependencyFactory:
901 (BrowserViewControllerDependencyFactory*)factory {
902 self = [super initWithNibName:nil bundle:base::mac::FrameworkBundle()];
903 if (self) {
904 DCHECK(factory);
905 _propertyReleaser_BrowserViewController.Init(self,
906 [BrowserViewController class]);
907 _imageFetcher = base::MakeUnique<web::ImageDataFetcher>(
908 web::WebThread::GetBlockingPool());
909 _dependencyFactory.reset([factory retain]);
910 _nativeControllersForTabIDs.reset(
911 [[NSMapTable strongToWeakObjectsMapTable] retain]);
912 _dialogPresenter.reset([[DialogPresenter alloc] initWithDelegate:self
913 presentingViewController:self]);
914 _javaScriptDialogPresenter.reset(
915 new JavaScriptDialogPresenterImpl(_dialogPresenter));
916 _webStateDelegate.reset(new web::WebStateDelegateBridge(self));
917 // TODO(leng): Delay this.
918 [[UpgradeCenter sharedInstance] registerClient:self];
919 _inNewTabAnimation = NO;
920 BrowserListIOS::AddBrowser(self);
921 if (model && browserState)
922 [self updateWithTabModel:model browserState:browserState];
923 if ([[NSUserDefaults standardUserDefaults]
924 boolForKey:@"fullScreenShowAlert"]) {
925 _fullScreenAlertShown.reset([[NSMutableSet alloc] init]);
926 }
927 }
928 return self;
929 }
930
931 - (instancetype)initWithNibName:(NSString*)nibNameOrNil
932 bundle:(NSBundle*)nibBundleOrNil {
933 NOTREACHED();
934 return nil;
935 }
936
937 - (instancetype)initWithCoder:(NSCoder*)aDecoder {
938 NOTREACHED();
939 return nil;
940 }
941
942 - (void)dealloc {
943 _tabStripController.reset();
944 _infoBarContainer.reset();
945 _readingListMenuNotifier.reset();
946 BrowserListIOS::RemoveBrowser(self);
947 _bookmarkModel->RemoveObserver(_bookmarkModelBridge.get());
948 [_model removeObserver:self];
949 [[UpgradeCenter sharedInstance] unregisterClient:self];
950 [[NSNotificationCenter defaultCenter] removeObserver:self];
951 [_toolbarController setDelegate:nil];
952 if (_voiceSearchController.get())
953 _voiceSearchController->SetDelegate(nil);
954 [_rateThisAppDialog setDelegate:nil];
955 [_model closeAllTabs];
956 [super dealloc];
957 }
958
959 #pragma mark - Accessibility
960
961 - (BOOL)accessibilityPerformEscape {
962 [self dismissPopups];
963 return YES;
964 }
965
966 #pragma mark - Properties
967
968 // Implements |hasModeToggleSwitch| property as pass-throughs to tab strip
969 // controller and no-tabs controller. If set before the controller has been
970 // created, cache it.
971 - (void)setHasModeToggleSwitch:(BOOL)hasModeToggleSwitch {
972 if (!experimental_flags::IsTabSwitcherEnabled()) {
973 if (_tabStripController)
974 _tabStripController.get().hasModeToggleSwitch = hasModeToggleSwitch;
975 else
976 _modeToggleNeedsSetting = hasModeToggleSwitch;
977 }
978 [_noTabsController setHasModeToggleSwitch:hasModeToggleSwitch];
979 }
980
981 // Implements |hasModeToggleSwitch| property as pass-throughs to tab strip
982 // controller, unless it hasn't been created in which return the cached version.
983 - (BOOL)hasModeToggleSwitch {
984 if (_tabStripController)
985 return _tabStripController.get().hasModeToggleSwitch;
986 return _modeToggleNeedsSetting;
987 }
988
989 - (void)setActive:(BOOL)active {
990 if (_active == active) {
991 return;
992 }
993 _active = active;
994
995 // If not active, display an activity indicator overlay over the view to
996 // prevent interaction with the web page.
997 // TODO(crbug.com/637093): This coordinator should be managed by the
998 // coordinator used to present BrowserViewController, when implemented.
999 if (active) {
1000 [self.activityOverlayCoordinator stop];
1001 self.activityOverlayCoordinator = nil;
1002 } else if (!self.activityOverlayCoordinator) {
1003 self.activityOverlayCoordinator = [[[ActivityOverlayCoordinator alloc]
1004 initWithBaseViewController:self] autorelease];
1005 [self.activityOverlayCoordinator start];
1006 }
1007
1008 if (_browserState) {
1009 web::ActiveStateManager* active_state_manager =
1010 web::BrowserState::GetActiveStateManager(_browserState);
1011 active_state_manager->SetActive(active);
1012 }
1013
1014 [_model setWebUsageEnabled:active];
1015 [self updateDialogPresenterActiveState];
1016
1017 if (active) {
1018 // Make sure the tab (if any; it's possible to get here without a current
1019 // tab if the caller is about to create one) ends up on screen completely.
1020 Tab* currentTab = [_model currentTab];
1021 // Force loading the view in case it was not loaded yet.
1022 [self ensureViewCreated];
1023 if (_expectingForegroundTab)
1024 [currentTab.webController setOverlayPreviewMode:YES];
1025 if (currentTab)
1026 [self displayTab:currentTab isNewSelection:YES];
1027 }
1028 [_contextualSearchController enableContextualSearch:active];
1029 [_paymentRequestManager enablePaymentRequest:active];
1030
1031 [self setNeedsStatusBarAppearanceUpdate];
1032 }
1033
1034 - (void)setPrimary:(BOOL)primary {
1035 [_model setPrimary:primary];
1036 if (primary) {
1037 [self updateDialogPresenterActiveState];
1038 } else {
1039 self.dialogPresenter.active = false;
1040 }
1041 }
1042
1043 - (BOOL)isPlayingTTS {
1044 return _voiceSearchController && _voiceSearchController->IsPlayingAudio();
1045 }
1046
1047 - (SideSwipeController*)sideSwipeController {
1048 if (!_sideSwipeController) {
1049 _sideSwipeController.reset([[SideSwipeController alloc]
1050 initWithTabModel:_model
1051 browserState:_browserState]);
1052 [_sideSwipeController setSnapshotDelegate:self];
1053 [_sideSwipeController setSwipeDelegate:self];
1054 }
1055 return _sideSwipeController;
1056 }
1057
1058 - (PreloadController*)preloadController {
1059 return _preloadController.get();
1060 }
1061
1062 - (DialogPresenter*)dialogPresenter {
1063 return _dialogPresenter;
1064 }
1065
1066 - (BOOL)canUseReaderMode {
1067 Tab* tab = [_model currentTab];
1068 if ([self isTabNativePage:tab])
1069 return NO;
1070
1071 return [tab canSwitchToReaderMode];
1072 }
1073
1074 - (BOOL)canUseDesktopUserAgent {
1075 Tab* tab = [_model currentTab];
1076 if ([self isTabNativePage:tab])
1077 return NO;
1078
1079 // If |useDesktopUserAgent| is |NO|, allow useDesktopUserAgent.
1080 return !tab.useDesktopUserAgent;
1081 }
1082
1083 // Whether the sharing menu should be shown.
1084 - (BOOL)canShowShareMenu {
1085 Tab* tab = [_model currentTab];
1086 // TODO(shreyasv): Make it so the URL returned by the tab is always valid and
1087 // remove check on net::NSURLWithGURL(tab.url) ( http://crbug.com/400999 ).
1088 return tab && !tab.url.SchemeIs(kChromeUIScheme) &&
1089 net::NSURLWithGURL(tab.url);
1090 }
1091
1092 - (BOOL)canShowFindBar {
1093 // Make sure web controller can handle find in page.
1094 Tab* tab = [_model currentTab];
1095 if (![tab.findInPageController canFindInPage])
1096 return NO;
1097
1098 // Don't show twice.
1099 if (tab.findInPageController.findInPageModel.enabled)
1100 return NO;
1101
1102 return YES;
1103 }
1104
1105 - (void)setVisible:(BOOL)visible {
1106 if (_visible == visible)
1107 return;
1108 _visible = visible;
1109 }
1110
1111 - (void)setViewVisible:(BOOL)viewVisible {
1112 if (_viewVisible == viewVisible)
1113 return;
1114 _viewVisible = viewVisible;
1115 self.visible = viewVisible;
1116 [self updateDialogPresenterActiveState];
1117 }
1118
1119 - (BOOL)isToolbarOnScreen {
1120 return [self headerHeight] - [self currentHeaderOffset] > 0;
1121 }
1122
1123 - (BOOL)isInNewTabAnimation {
1124 return _inNewTabAnimation;
1125 }
1126
1127 - (BOOL)shouldShowVoiceSearchBar {
1128 // On iPads, the voice search bar should only be shown for regular horizontal
1129 // size class configurations. It should always be shown for voice search
1130 // results Tabs on iPhones, including configurations with regular horizontal
1131 // size classes (i.e. landscape iPhone 6 Plus).
1132 BOOL compactWidth = self.traitCollection.horizontalSizeClass ==
1133 UIUserInterfaceSizeClassCompact;
1134 return self.tabModel.currentTab.isVoiceSearchResultsTab &&
1135 (!IsIPadIdiom() || compactWidth);
1136 }
1137
1138 - (void)setHideStatusBar:(BOOL)hideStatusBar {
1139 if (_hideStatusBar == hideStatusBar)
1140 return;
1141 _hideStatusBar = hideStatusBar;
1142 [self setNeedsStatusBarAppearanceUpdate];
1143 }
1144
1145 #pragma mark - IBActions
1146
1147 - (void)shieldWasTapped:(id)sender {
1148 [_toolbarController cancelOmniboxEdit];
1149 }
1150
1151 - (void)newTab:(id)sender {
1152 [self setLastTapPoint:sender];
1153 DCHECK(self.visible || self.dismissingModal);
1154 Tab* currentTab = [_model currentTab];
1155 if (currentTab) {
1156 BOOL isChromeScheme =
1157 web::GetWebClient()->IsAppSpecificURL([currentTab url]);
1158 BOOL snapshotOnIpad =
1159 (!IsIPadIdiom() || experimental_flags::IsTabSwitcherEnabled());
1160 if (snapshotOnIpad || !isChromeScheme) {
1161 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1162 }
1163 }
1164 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
1165 transition:ui::PAGE_TRANSITION_TYPED];
1166 }
1167
1168 #pragma mark - UIViewController methods
1169
1170 // Perform additional set up after loading the view, typically from a nib.
1171 - (void)viewDidLoad {
1172 // Duplicate .xib layout for now.
1173 CGRect initialViewsRect = CGRectMake(0, 30, 320, 430);
1174 UIViewAutoresizing initialViewAutoresizing =
1175 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
1176
1177 self.contentArea = [[[BrowserContainerView alloc]
1178 initWithFrame:initialViewsRect] autorelease];
1179 self.contentArea.autoresizingMask = initialViewAutoresizing;
1180 self.typingShield =
1181 [[[UIButton alloc] initWithFrame:initialViewsRect] autorelease];
1182 self.typingShield.autoresizingMask = initialViewAutoresizing;
1183 [self.typingShield addTarget:self
1184 action:@selector(shieldWasTapped:)
1185 forControlEvents:UIControlEventTouchUpInside];
1186 self.view.bounds = CGRectMake(0, 0, 320, 460);
1187 self.view.autoresizingMask = initialViewAutoresizing;
1188 self.view.backgroundColor = [UIColor colorWithWhite:0.75 alpha:1.0];
1189 [self.view addSubview:self.contentArea];
1190 [self.view addSubview:self.typingShield];
1191 [super viewDidLoad];
1192
1193 // Install fake status bar for iPad iOS7
1194 [self installFakeStatusBar];
1195 [self buildToolbarAndTabStrip];
1196 [self setUpViewLayout];
1197 // If the tab model and browser state are valid, finish initialization.
1198 if (_model && _browserState)
1199 [self addUIFunctionalityForModelAndBrowserState];
1200
1201 // Add a tap gesture recognizer to save the last tap location for the source
1202 // location of the new tab animation.
1203 base::scoped_nsobject<UITapGestureRecognizer> tapRecognizer(
1204 [[UITapGestureRecognizer alloc]
1205 initWithTarget:self
1206 action:@selector(saveContentAreaTapLocation:)]);
1207 [tapRecognizer setDelegate:self];
1208 [tapRecognizer setCancelsTouchesInView:NO];
1209 [_contentArea addGestureRecognizer:tapRecognizer];
1210 }
1211
1212 - (void)viewDidAppear:(BOOL)animated {
1213 [super viewDidAppear:animated];
1214 self.viewVisible = YES;
1215 [self updateDialogPresenterActiveState];
1216 }
1217
1218 - (void)viewWillAppear:(BOOL)animated {
1219 [super viewWillAppear:animated];
1220
1221 // Reparent the toolbar if it's been relinquished.
1222 if (_isToolbarControllerRelinquished)
1223 [self reparentToolbarController];
1224
1225 self.visible = YES;
1226
1227 // Restore hidden infobars.
1228 if (IsIPadIdiom() && experimental_flags::IsTabSwitcherEnabled()) {
1229 _infoBarContainer->RestoreInfobars();
1230 }
1231
1232 // If the controller is suspended, or has been paged out due to low memory,
1233 // updating the view will be handled when it's displayed again.
1234 if (![_model webUsageEnabled] || !self.contentArea)
1235 return;
1236 // Update the displayed tab (if any; the switcher may not have created one
1237 // yet) in case it changed while showing the switcher.
1238 Tab* currentTab = [_model currentTab];
1239 if (currentTab)
1240 [self displayTab:currentTab isNewSelection:YES];
1241 }
1242
1243 - (void)viewWillDisappear:(BOOL)animated {
1244 self.viewVisible = NO;
1245 [self updateDialogPresenterActiveState];
1246 [[_model currentTab] updateFullscreenWithToolbarVisible:YES];
1247 [[_model currentTab] wasHidden];
1248 [_bookmarkInteractionController dismissSnackbar];
1249 if (IsIPadIdiom() && experimental_flags::IsTabSwitcherEnabled()) {
1250 _infoBarContainer->SuspendInfobars();
1251 }
1252 [super viewWillDisappear:animated];
1253 }
1254
1255 - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orient
1256 duration:(NSTimeInterval)duration {
1257 [super willRotateToInterfaceOrientation:orient duration:duration];
1258 [self dismissPopups];
1259 [self reshowFindBarIfNeededWithCoordinator:nil];
1260 }
1261
1262 - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orient {
1263 [super didRotateFromInterfaceOrientation:orient];
1264
1265 // This reinitializes the toolbar, including updating the Overlay View,
1266 // if there is one.
1267 [self updateToolbar];
1268 [self infoBarContainerStateChanged:false];
1269 }
1270
1271 - (BOOL)prefersStatusBarHidden {
1272 return self.hideStatusBar;
1273 }
1274
1275 // Called when in the foreground and the OS needs more memory. Release as much
1276 // as possible.
1277 - (void)didReceiveMemoryWarning {
1278 // Releases the view if it doesn't have a superview.
1279 [super didReceiveMemoryWarning];
1280
1281 // Release any cached data, images, etc that aren't in use.
1282 // TODO(pinkerton): This feels like it should go in the MemoryPurger class,
1283 // but since the FaviconCache uses obj-c in the header, it can't be included
1284 // there.
1285 if (_browserState) {
1286 FaviconLoader* loader =
1287 IOSChromeFaviconLoaderFactory::GetForBrowserStateIfExists(
1288 _browserState);
1289 if (loader)
1290 loader->PurgeCache();
1291 }
1292
1293 if (![self isViewLoaded]) {
1294 // Do not release |_infoBarContainer|, as this must have the same lifecycle
1295 // as the BrowserViewController.
1296 self.contentArea = nil;
1297 self.typingShield = nil;
1298 if (_voiceSearchController.get())
1299 _voiceSearchController->SetDelegate(nil);
1300 _qrScannerViewController.reset();
1301 _toolbarController.reset();
1302 _toolbarModelDelegate.reset();
1303 _toolbarModelIOS.reset();
1304 _tabStripController.reset();
1305 _noTabsController.reset();
1306 _sideSwipeController.reset();
1307 }
1308 }
1309
1310 - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
1311 [super traitCollectionDidChange:previousTraitCollection];
1312 // TODO(crbug.com/527092): - traitCollectionDidChange: is not always forwarded
1313 // because in some cases the presented view controller isn't a child of the
1314 // BVC in the view controller hierarchy (some intervening object isn't a
1315 // view controller).
1316 [self.presentedViewController
1317 traitCollectionDidChange:previousTraitCollection];
1318 [_toolbarController traitCollectionDidChange:previousTraitCollection];
1319 // Update voice search bar visibility.
1320 [self updateVoiceSearchBarVisibilityAnimated:NO];
1321 }
1322
1323 - (void)viewWillTransitionToSize:(CGSize)size
1324 withTransitionCoordinator:
1325 (id<UIViewControllerTransitionCoordinator>)coordinator {
1326 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
1327 [self dismissPopups];
1328 [self reshowFindBarIfNeededWithCoordinator:coordinator];
1329 }
1330
1331 - (void)reshowFindBarIfNeededWithCoordinator:
1332 (id<UIViewControllerTransitionCoordinator>)coordinator {
1333 if (![_findBarController isFindInPageShown])
1334 return;
1335
1336 // Record focused state.
1337 BOOL isFocusedBeforeReshow = [_findBarController isFocused];
1338
1339 [self hideFindBarWithAnimation:NO];
1340
1341 base::WeakNSObject<BrowserViewController> weakSelf(self);
1342 void (^completion)(id<UIViewControllerTransitionCoordinatorContext>) = ^(
1343 id<UIViewControllerTransitionCoordinatorContext> context) {
1344 base::scoped_nsobject<BrowserViewController> strongSelf([weakSelf retain]);
1345 if (strongSelf)
1346 [strongSelf showFindBarWithAnimation:NO
1347 selectText:NO
1348 shouldFocus:isFocusedBeforeReshow];
1349 };
1350
1351 BOOL enqueued =
1352 [coordinator animateAlongsideTransition:nil completion:completion];
1353 if (!enqueued) {
1354 completion(nil);
1355 }
1356 }
1357
1358 - (void)dismissViewControllerAnimated:(BOOL)flag
1359 completion:(void (^)())completion {
1360 self.dismissingModal = YES;
1361 base::WeakNSObject<BrowserViewController> weakSelf(self);
1362 [super dismissViewControllerAnimated:flag
1363 completion:^{
1364 base::scoped_nsobject<BrowserViewController>
1365 strongSelf([weakSelf retain]);
1366 [strongSelf setDismissingModal:NO];
1367 [strongSelf setPresenting:NO];
1368 if (completion)
1369 completion();
1370 [[strongSelf dialogPresenter] tryToPresent];
1371 }];
1372 }
1373
1374 - (void)presentViewController:(UIViewController*)viewControllerToPresent
1375 animated:(BOOL)flag
1376 completion:(void (^)())completion {
1377 base::mac::ScopedBlock<ProceduralBlock> finalCompletionHandler(
1378 [completion copy]);
1379 // TODO(crbug.com/580098) This is an interim fix for the flicker between the
1380 // launch screen and the FRE Animation. The fix is, if the FRE is about to be
1381 // presented, to show a temporary view of the launch screen and then remove it
1382 // when the controller for the FRE has been presented. This fix should be
1383 // removed when the FRE startup code is rewritten.
1384 BOOL firstRunLaunch = (FirstRun::IsChromeFirstRun() ||
1385 experimental_flags::AlwaysDisplayFirstRun()) &&
1386 !tests_hook::DisableFirstRun();
1387 // These if statements check that |presentViewController| is being called for
1388 // the FRE case.
1389 if (firstRunLaunch &&
1390 [viewControllerToPresent isKindOfClass:[UINavigationController class]]) {
1391 UINavigationController* navController =
1392 base::mac::ObjCCastStrict<UINavigationController>(
1393 viewControllerToPresent);
1394 if ([navController.topViewController
1395 isMemberOfClass:[WelcomeToChromeViewController class]]) {
1396 self.hideStatusBar = YES;
1397
1398 // Load view from Launch Screen and add it to window.
1399 NSBundle* mainBundle = base::mac::FrameworkBundle();
1400 NSArray* topObjects =
1401 [mainBundle loadNibNamed:@"LaunchScreen" owner:self options:nil];
1402 UIViewController* launchScreenController =
1403 base::mac::ObjCCastStrict<UIViewController>([topObjects lastObject]);
1404 // |launchScreenView| is loaded as an autoreleased object, and is retained
1405 // by the |completion| block below.
1406 UIView* launchScreenView = launchScreenController.view;
1407 launchScreenView.userInteractionEnabled = NO;
1408 launchScreenView.frame = self.view.window.bounds;
1409 [self.view.window addSubview:launchScreenView];
1410
1411 // Replace the completion handler sent to the superclass with one which
1412 // removes |launchScreenView| and resets the status bar. If |completion|
1413 // exists, it is called from within the new completion handler.
1414 base::WeakNSObject<BrowserViewController> weakSelf(self);
1415 finalCompletionHandler.reset([^{
1416 [launchScreenView removeFromSuperview];
1417 weakSelf.get().hideStatusBar = NO;
1418 if (completion)
1419 completion();
1420 } copy]);
1421 }
1422 }
1423
1424 self.presenting = YES;
1425
1426 [super presentViewController:viewControllerToPresent
1427 animated:flag
1428 completion:finalCompletionHandler];
1429 }
1430
1431 #pragma mark - Notification handling
1432
1433 - (void)registerForNotifications {
1434 DCHECK(_model);
1435 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
1436 [defaultCenter addObserver:self
1437 selector:@selector(pageLoadStarting:)
1438 name:kTabModelTabWillStartLoadingNotification
1439 object:_model];
1440 [defaultCenter addObserver:self
1441 selector:@selector(pageLoadStarted:)
1442 name:kTabModelTabDidStartLoadingNotification
1443 object:_model];
1444 [defaultCenter addObserver:self
1445 selector:@selector(pageLoadComplete:)
1446 name:kTabModelTabDidFinishLoadingNotification
1447 object:_model];
1448 [defaultCenter addObserver:self
1449 selector:@selector(tabDeselected:)
1450 name:kTabModelTabDeselectedNotification
1451 object:_model];
1452 [defaultCenter addObserver:self
1453 selector:@selector(tabWasAdded:)
1454 name:kTabModelNewTabWillOpenNotification
1455 object:_model];
1456 }
1457
1458 - (void)pageLoadStarting:(NSNotification*)notify {
1459 Tab* tab = notify.userInfo[kTabModelTabKey];
1460 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
1461 // Hide find bar when navigating to a new page.
1462 [self hideFindBarWithAnimation:NO];
1463 tab.findInPageController.findInPageModel.enabled = NO;
1464 if (tab == [_model currentTab]) {
1465 // TODO(pinkerton): Fill in here about hiding the forward button on
1466 // navigation.
1467 }
1468 }
1469
1470 - (void)pageLoadStarted:(NSNotification*)notify {
1471 Tab* tab = notify.userInfo[kTabModelTabKey];
1472 DCHECK(tab);
1473 if (tab == [_model currentTab]) {
1474 if (![self isTabNativePage:tab]) {
1475 [_toolbarController currentPageLoadStarted];
1476 }
1477 [self updateVoiceSearchBarVisibilityAnimated:NO];
1478 }
1479 }
1480
1481 - (void)pageLoadComplete:(NSNotification*)notify {
1482 // Update the UI, but only if the current tab.
1483 Tab* tab = notify.userInfo[kTabModelTabKey];
1484 if (tab == [_model currentTab]) {
1485 // There isn't any need to update the toolbar here. When the page finishes,
1486 // it will have already sent us |-tabModel:didChangeTab:| which will do it.
1487 }
1488
1489 BOOL loadingSucceeded = [notify.userInfo[kTabModelPageLoadSuccess] boolValue];
1490
1491 [self tabLoadComplete:tab withSuccess:loadingSucceeded];
1492 }
1493
1494 - (void)tabDeselected:(NSNotification*)notify {
1495 DCHECK(notify);
1496 Tab* tab = notify.userInfo[kTabModelTabKey];
1497 DCHECK(tab);
1498 [tab wasHidden];
1499 [_toolbarController dismissTabHistoryPopup];
1500 }
1501
1502 - (void)tabWasAdded:(NSNotification*)notify {
1503 Tab* tab = notify.userInfo[kTabModelTabKey];
1504 DCHECK(tab);
1505
1506 // Update map if a native controller was vended before the tab was added.
1507 id<CRWNativeContent> nativeController =
1508 [_nativeControllersForTabIDs objectForKey:kNativeControllerTemporaryKey];
1509 if (nativeController) {
1510 [_nativeControllersForTabIDs
1511 removeObjectForKey:kNativeControllerTemporaryKey];
1512 [_nativeControllersForTabIDs setObject:nativeController forKey:tab.tabId];
1513 }
1514
1515 // When adding new tabs, check what kind of reminder infobar should
1516 // be added to the new tab. Try to add only one of them.
1517 // This check is done when a new tab is added either through the Tools Menu
1518 // "New Tab" or through "New Tab" in Stack View Controller. This method
1519 // is called after a new tab has added and finished initial navigation.
1520 // If this is added earlier, the initial navigation may end up clearing
1521 // the infobar(s) that are just added. See http://crbug/340250 for details.
1522 [[UpgradeCenter sharedInstance] addInfoBarToManager:[tab infoBarManager]
1523 forTabId:[tab tabId]];
1524 if (!ReSignInInfoBarDelegate::Create(_browserState, tab)) {
1525 ios_internal::sync::displaySyncErrors(_browserState, tab);
1526 }
1527
1528 // The rest of this function initiates the new tab animation, which is
1529 // phone-specific.
1530 if (IsIPadIdiom())
1531 return;
1532
1533 // Do nothing if browsing is currently suspended. The BVC will set everything
1534 // up correctly when browsing resumes.
1535 if (!self.visible || ![_model webUsageEnabled])
1536 return;
1537
1538 BOOL inBackground = [notify.userInfo[kTabModelOpenInBackgroundKey] boolValue];
1539
1540 // Block that starts voice search at the end of new Tab animation if
1541 // necessary.
1542 ProceduralBlock startVoiceSearchIfNecessaryBlock = ^void() {
1543 if (_startVoiceSearchAfterNewTabAnimation) {
1544 _startVoiceSearchAfterNewTabAnimation = NO;
1545 [self startVoiceSearch];
1546 }
1547 };
1548
1549 _inNewTabAnimation = YES;
1550 if (!inBackground) {
1551 UIView* animationParentView = _contentArea;
1552 // Create the new page image, and load with the new tab page snapshot.
1553 CGFloat newPageOffset = 0;
1554 UIImageView* newPage;
1555 if (tab.url == GURL(kChromeUINewTabURL) && !_isOffTheRecord &&
1556 !IsIPadIdiom()) {
1557 animationParentView = self.view;
1558 newPage = [self pageFullScreenOpenCloseAnimationView];
1559 } else {
1560 newPage = [self pageOpenCloseAnimationView];
1561 }
1562 newPageOffset = newPage.frame.origin.y;
1563
1564 [tab view].frame = _contentArea.bounds;
1565 newPage.image = [tab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1566 [animationParentView addSubview:newPage];
1567 CGPoint origin = [self lastTapPoint];
1568 ios_internal::page_animation_util::AnimateInPaperWithAnimationAndCompletion(
1569 newPage, -newPageOffset,
1570 newPage.frame.size.height - newPage.image.size.height, origin,
1571 _isOffTheRecord, NULL, ^{
1572 [newPage removeFromSuperview];
1573 _inNewTabAnimation = NO;
1574 // Only show |tab|'s view if it is the current tab in its TabModel.
1575 // It is possible that the current Tab can be reset to a new value
1576 // before the new Tab animation finished (e.g. if another Tab shows
1577 // a dialog via |dialogPresenter|).
1578 if ([_model currentTab] == tab)
1579 [self tabSelected:tab];
1580 startVoiceSearchIfNecessaryBlock();
1581 });
1582 } else {
1583 // -updateSnapshotWithOverlay will force a screen redraw, so take the
1584 // snapshot before adding the views needed for the background animation.
1585 Tab* topTab = [_model currentTab];
1586 UIImage* image = [topTab updateSnapshotWithOverlay:YES
1587 visibleFrameOnly:self.isToolbarOnScreen];
1588 // Add three layers in order on top of the contentArea for the animation:
1589 // 1. The black "background" screen.
1590 base::scoped_nsobject<UIView> background(
1591 [[UIView alloc] initWithFrame:[_contentArea bounds]]);
1592 InstallBackgroundInView(background);
1593 [_contentArea addSubview:background];
1594
1595 // 2. A CardView displaying the data from the current tab.
1596 CardView* topCard = [self addCardViewInFullscreen:!self.isToolbarOnScreen];
1597 NSString* title = [topTab title];
1598 if (![title length])
1599 title = [topTab urlDisplayString];
1600 [topCard setTitle:title];
1601 [topCard setFavicon:[topTab favicon]];
1602 [topCard setImage:image];
1603
1604 // 3. A new, blank CardView to represent the new tab being added.
1605 // Launch the new background tab animation.
1606 ios_internal::page_animation_util::AnimateNewBackgroundPageWithCompletion(
1607 topCard, [_contentArea frame], IsPortrait(), ^{
1608 [background removeFromSuperview];
1609 [topCard removeFromSuperview];
1610 _inNewTabAnimation = NO;
1611 // Resnapshot the top card if it has its own toolbar, as the toolbar
1612 // will be captured in the new tab animation, but isn't desired for
1613 // the stack view snapshots.
1614 id nativeController = [self nativeControllerForTab:topTab];
1615 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)])
1616 [topTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1617 startVoiceSearchIfNecessaryBlock();
1618 });
1619 }
1620 }
1621
1622 #pragma mark - UI Configuration and Layout
1623
1624 - (void)updateWithTabModel:(TabModel*)model
1625 browserState:(ios::ChromeBrowserState*)browserState {
1626 DCHECK(model);
1627 DCHECK(browserState);
1628 DCHECK(!_model);
1629 DCHECK(!_browserState);
1630 _browserState = browserState;
1631 _isOffTheRecord = browserState->IsOffTheRecord() ? YES : NO;
1632 _model.reset([model retain]);
1633 [_model addObserver:self];
1634
1635 if (!_isOffTheRecord) {
1636 [DefaultIOSWebViewFactory
1637 registerWebViewFactory:[ChromeWebViewFactory class]];
1638 }
1639 NSUInteger count = [_model count];
1640 for (NSUInteger index = 0; index < count; ++index)
1641 [self installDelegatesForTab:[_model tabAtIndex:index]];
1642
1643 [self registerForNotifications];
1644
1645 _imageFetcher->SetRequestContextGetter(_browserState->GetRequestContext());
1646 _dominantColorCache.reset([[NSMutableDictionary alloc] init]);
1647
1648 // Register for bookmark changed notification.
1649 _bookmarkModelBridge.reset(new BrowserBookmarkModelBridge(self));
1650 _bookmarkModel = ios::BookmarkModelFactory::GetForBrowserState(_browserState);
1651 _bookmarkModel->AddObserver(_bookmarkModelBridge.get());
1652 }
1653
1654 - (void)ensureViewCreated {
1655 ignore_result([self view]);
1656 }
1657
1658 - (void)browserStateDestroyed {
1659 [self setActive:NO];
1660 // Reset the toolbar opacity in case it was changed for contextual search.
1661 [self updateToolbarControlsAlpha:1.0];
1662 [self updateToolbarBackgroundAlpha:1.0];
1663 [_contextualSearchController close];
1664 _contextualSearchController.reset();
1665 [_contextualSearchPanel removeFromSuperview];
1666 [_contextualSearchMask removeFromSuperview];
1667 [_paymentRequestManager close];
1668 _paymentRequestManager.reset();
1669 BrowserListIOS::RemoveBrowser(self);
1670 [_toolbarController browserStateDestroyed];
1671 [_model browserStateDestroyed];
1672 // The file remover needs the browser state, so needs to be destroyed now.
1673 _externalFileRemover.reset();
1674 _browserState = nullptr;
1675 }
1676
1677 - (void)installFakeStatusBar {
1678 if (IsIPadIdiom()) {
1679 CGFloat statusBarHeight = StatusBarHeight();
1680 CGRect statusBarFrame =
1681 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
1682 base::scoped_nsobject<UIView> statusBarView(
1683 [[UIView alloc] initWithFrame:statusBarFrame]);
1684 [statusBarView setBackgroundColor:TabStrip::BackgroundColor()];
1685 [statusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1686 [statusBarView layer].zPosition = 99;
1687 [[self view] addSubview:statusBarView];
1688 }
1689
1690 // Add a white bar on phone so that the status bar on the NTP is white.
1691 if (!IsIPadIdiom()) {
1692 CGFloat statusBarHeight = StatusBarHeight();
1693 CGRect statusBarFrame =
1694 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
1695 base::scoped_nsobject<UIView> statusBarView(
1696 [[UIView alloc] initWithFrame:statusBarFrame]);
1697 [statusBarView setBackgroundColor:[UIColor whiteColor]];
1698 [statusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1699 [self.view insertSubview:statusBarView atIndex:0];
1700 }
1701 }
1702
1703 // Create the UI elements. May or may not have valid browser state & tab model.
1704 - (void)buildToolbarAndTabStrip {
1705 DCHECK([self isViewLoaded]);
1706 DCHECK(!_toolbarModelDelegate);
1707
1708 // Create the preload controller before the toolbar controller.
1709 if (!_preloadController) {
1710 _preloadController.reset([_dependencyFactory newPreloadController]);
1711 [_preloadController setDelegate:self];
1712 }
1713
1714 // Create the toolbar model and controller.
1715 _toolbarModelDelegate.reset(new ToolbarModelDelegateIOS(_model));
1716 _toolbarModelIOS.reset([_dependencyFactory
1717 newToolbarModelIOSWithDelegate:_toolbarModelDelegate.get()]);
1718 _toolbarController.reset([_dependencyFactory
1719 newWebToolbarControllerWithDelegate:self
1720 urlLoader:self
1721 preloadProvider:_preloadController.get()]);
1722 [_toolbarController setTabCount:[_model count]];
1723 if (_voiceSearchController.get())
1724 _voiceSearchController->SetDelegate(_toolbarController);
1725
1726 // If needed, create the tabstrip.
1727 if (IsIPadIdiom()) {
1728 // Determine if it's incognito. Whether or not the toggle button is
1729 // visible is consolidated in logic elsewhere so it doesn't need to be set
1730 // here.
1731 _tabStripController.reset(
1732 [_dependencyFactory newTabStripControllerWithTabModel:_model]);
1733 _tabStripController.get().fullscreenDelegate = self;
1734 [_tabStripController setHasTabSwitcherToggleSwitch:
1735 experimental_flags::IsTabSwitcherEnabled()];
1736
1737 // If set before the views are loaded, pass the mode toggle to the
1738 // toolbar controller (only needed if YES, defaults to NO).
1739 if (_modeToggleNeedsSetting) {
1740 _tabStripController.get().hasModeToggleSwitch = _modeToggleNeedsSetting;
1741 }
1742 }
1743
1744 // Create infobar container.
1745 if (!_infoBarContainerDelegate) {
1746 _infoBarContainerDelegate.reset(new InfoBarContainerDelegateIOS(self));
1747 _infoBarContainer.reset(
1748 new InfoBarContainerIOS(_infoBarContainerDelegate.get()));
1749 }
1750 }
1751
1752 // Enable functionality that only makes sense if the views are loaded and
1753 // both browser state and tab model are valid.
1754 - (void)addUIFunctionalityForModelAndBrowserState {
1755 DCHECK(_browserState);
1756 DCHECK(_model);
1757 DCHECK([self isViewLoaded]);
1758
1759 [self.sideSwipeController addHorizontalGesturesToView:self.view];
1760
1761 infobars::InfoBarManager* infoBarManager =
1762 [[_model currentTab] infoBarManager];
1763 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
1764
1765 // Create contextual search views and controller.
1766 if ([TouchToSearchPermissionsMediator isTouchToSearchAvailableOnDevice] &&
1767 !_browserState->IsOffTheRecord()) {
1768 _contextualSearchMask =
1769 [[[ContextualSearchMaskView alloc] init] autorelease];
1770 [self.view insertSubview:_contextualSearchMask
1771 belowSubview:[_toolbarController view]];
1772 _contextualSearchPanel = [self createPanelView];
1773 [self.view insertSubview:_contextualSearchPanel
1774 aboveSubview:[_toolbarController view]];
1775 _contextualSearchController.reset([[ContextualSearchController alloc]
1776 initWithBrowserState:_browserState
1777 delegate:self]);
1778 [_contextualSearchController setPanel:_contextualSearchPanel];
1779 [_contextualSearchController setTab:[_model currentTab]];
1780 }
1781
1782 if (experimental_flags::IsPaymentRequestEnabled()) {
1783 _paymentRequestManager.reset([[PaymentRequestManager alloc]
1784 initWithBaseViewController:self
1785 browserState:_browserState]);
1786 [_paymentRequestManager setWebState:[_model currentTab].webState];
1787 }
1788 }
1789
1790 // Set the frame for the various views. View must be loaded.
1791 - (void)setUpViewLayout {
1792 DCHECK([self isViewLoaded]);
1793
1794 CGFloat widthOfView = CGRectGetWidth([self view].bounds);
1795
1796 CGFloat minY = [self headerOffset];
1797
1798 // If needed, position the tabstrip.
1799 if (IsIPadIdiom()) {
1800 [self layoutTabStripForWidth:widthOfView];
1801 [[self view] addSubview:[_tabStripController view]];
1802 minY += CGRectGetHeight([[_tabStripController view] frame]);
1803 }
1804
1805 // Position the toolbar next, either at the top of the browser view or
1806 // directly under the tabstrip.
1807 CGRect toolbarFrame = [[_toolbarController view] frame];
1808 toolbarFrame.origin = CGPointMake(0, minY);
1809 toolbarFrame.size.width = widthOfView;
1810 [[_toolbarController view] setFrame:toolbarFrame];
1811
1812 // Place the infobar container above the content area.
1813 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
1814 [self.view insertSubview:infoBarContainerView aboveSubview:_contentArea];
1815
1816 // Place the toolbar controller above the infobar container.
1817 [[self view] insertSubview:[_toolbarController view]
1818 aboveSubview:infoBarContainerView];
1819 minY += CGRectGetHeight(toolbarFrame);
1820
1821 // Account for the toolbar's drop shadow. The toolbar overlaps with the web
1822 // content slightly.
1823 minY -= [ToolbarController toolbarDropShadowHeight];
1824
1825 // Adjust the content area to be under the toolbar, for fullscreen or below
1826 // the toolbar is not fullscreen.
1827 CGRect contentFrame = [_contentArea frame];
1828 CGFloat marginWithHeader = StatusBarHeight();
1829 CGFloat overlap = [self headerHeight] != 0 ? marginWithHeader : minY;
1830 contentFrame.size.height = CGRectGetMaxY(contentFrame) - overlap;
1831 contentFrame.origin.y = overlap;
1832 [_contentArea setFrame:contentFrame];
1833
1834 // Adjust the infobar container to be either at the bottom of the screen
1835 // (iPhone) or on the lower toolbar edge (iPad).
1836 CGRect infoBarFrame = contentFrame;
1837 infoBarFrame.origin.y = CGRectGetMaxY(contentFrame);
1838 infoBarFrame.size.height = 0;
1839 [infoBarContainerView setFrame:infoBarFrame];
1840
1841 // Attach the typing shield to the content area but have it hidden.
1842 [_typingShield setFrame:[_contentArea frame]];
1843 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
1844 [_typingShield setHidden:YES];
1845 _typingShield.accessibilityIdentifier = @"Typing Shield";
1846 _typingShield.accessibilityLabel = l10n_util::GetNSString(IDS_CANCEL);
1847 }
1848
1849 - (void)layoutTabStripForWidth:(CGFloat)maxWidth {
1850 UIView* tabStripView = [_tabStripController view];
1851 CGRect tabStripFrame = [tabStripView frame];
1852 tabStripFrame.origin = CGPointZero;
1853 // TODO(crbug.com/256655): Move the origin.y below to -setUpViewLayout.
1854 // because the CGPointZero above will break reset the offset, but it's not
1855 // clear what removing that will do.
1856 tabStripFrame.origin.y = [self headerOffset];
1857 tabStripFrame.size.width = maxWidth;
1858 [tabStripView setFrame:tabStripFrame];
1859 }
1860
1861 - (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection {
1862 DCHECK(tab);
1863 // Ensure that self.view is loaded to avoid errors that can otherwise occur
1864 // when accessing |_contentArea| below.
1865 if (!_contentArea)
1866 [self ensureViewCreated];
1867
1868 DCHECK(_contentArea);
1869 if (!_inNewTabAnimation) {
1870 // Hide findbar. |updateToolbar| will restore the findbar later.
1871 [self hideFindBarWithAnimation:NO];
1872
1873 // Make new content visible, resizing it first as the orientation may
1874 // have changed from the last time it was displayed.
1875 [[tab view] setFrame:_contentArea.bounds];
1876 [_contentArea displayContentView:[tab view]];
1877 }
1878 [self updateToolbar];
1879
1880 if (newSelection)
1881 [_toolbarController selectedTabChanged];
1882
1883 // Notify the Tab that it was displayed.
1884 [tab wasShown];
1885 }
1886
1887 - (void)initializeBookmarkInteractionController {
1888 if (_bookmarkInteractionController)
1889 return;
1890 _bookmarkInteractionController.reset([[BookmarkInteractionController alloc]
1891 initWithBrowserState:_browserState
1892 loader:self
1893 parentController:self]);
1894 }
1895
1896 // Update the state of back and forward buttons, hiding the forward button if
1897 // there is nowhere to go. Assumes the model's current tab is up to date.
1898 - (void)updateToolbar {
1899 // If the BVC has been partially torn down for low memory, wait for the
1900 // view rebuild to handle toolbar updates.
1901 if (!(_toolbarModelIOS && _browserState))
1902 return;
1903
1904 Tab* tab = [_model currentTab];
1905 if (![tab navigationManager])
1906 return;
1907 [_toolbarController updateToolbarState];
1908 [_toolbarController setShareButtonEnabled:self.canShowShareMenu];
1909
1910 if (tab.isPrerenderTab && !_toolbarModelIOS->IsLoading())
1911 [_toolbarController showPrerenderingAnimation];
1912
1913 // Also update the loading state for the tools menu (that is really an
1914 // extension of the toolbar on the iPhone).
1915 if (!IsIPadIdiom())
1916 [[_toolbarController toolsPopupController]
1917 setIsTabLoading:_toolbarModelIOS->IsLoading()];
1918
1919 if (tab.findInPageController.findInPageModel.enabled)
1920 [self showFindBarWithAnimation:NO
1921 selectText:YES
1922 shouldFocus:[_findBarController isFocused]];
1923
1924 // Hide the toolbar if displaying phone NTP.
1925 if (!IsIPadIdiom()) {
1926 CRWSessionEntry* entry =
1927 [[tab navigationManager]->GetSessionController() currentEntry];
1928 BOOL hideToolbar = NO;
1929 if (entry) {
1930 GURL url = [entry navigationItem]->GetURL();
1931 BOOL isNTP = url.GetOrigin() == GURL(kChromeUINewTabURL);
1932 hideToolbar = isNTP && !_isOffTheRecord &&
1933 ![_toolbarController isOmniboxFirstResponder] &&
1934 ![_toolbarController showingOmniboxPopup];
1935 }
1936 [[_toolbarController view] setHidden:hideToolbar];
1937 }
1938 }
1939
1940 - (void)updateDialogPresenterActiveState {
1941 self.dialogPresenter.active = self.active && self.viewVisible;
1942 }
1943
1944 - (void)dismissPopups {
1945 if (_noTabsController.get())
1946 [_noTabsController dismissToolsMenuPopup];
1947 else
1948 [_toolbarController dismissToolsMenuPopup];
1949 [self hidePageInfoPopupForView:nil];
1950 [_toolbarController dismissTabHistoryPopup];
1951 [[_model currentTab].webController recordStateInHistory];
1952 }
1953
1954 #pragma mark - Tap handling
1955
1956 - (void)setLastTapPoint:(id)sender {
1957 CGPoint center;
1958 UIView* parentView = nil;
1959 if ([sender isKindOfClass:[UIView class]]) {
1960 center = [sender center];
1961 parentView = [sender superview];
1962 }
1963 if ([sender isKindOfClass:[ToolsMenuViewItem class]]) {
1964 parentView = [[sender tableViewCell] superview];
1965 center = [[sender tableViewCell] center];
1966 }
1967
1968 if (parentView) {
1969 _lastTapPoint = [parentView convertPoint:center toView:self.view];
1970 _lastTapTime = CACurrentMediaTime();
1971 }
1972 }
1973
1974 - (CGPoint)lastTapPoint {
1975 if (CACurrentMediaTime() - _lastTapTime < 1) {
1976 return _lastTapPoint;
1977 }
1978 return CGPointZero;
1979 }
1980
1981 - (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer {
1982 UIView* view = gestureRecognizer.view;
1983 CGPoint viewCoordinate = [gestureRecognizer locationInView:view];
1984 _lastTapPoint =
1985 [[view superview] convertPoint:viewCoordinate toView:self.view];
1986 _lastTapTime = CACurrentMediaTime();
1987 }
1988
1989 - (BOOL)addTabIfNoTabWithNormalBrowserState {
1990 if (![_model count]) {
1991 if (!_isOffTheRecord) {
1992 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
1993 transition:ui::PAGE_TRANSITION_TYPED];
1994 return YES;
1995 }
1996 }
1997 return NO;
1998 }
1999
2000 #pragma mark - Tab creation and selection
2001
2002 // Called when either a tab finishes loading or when a tab with finished content
2003 // is added directly to the model via pre-rendering.
2004 - (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success {
2005 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
2006
2007 // Persist the session on a delay.
2008 [_model saveSessionImmediately:NO];
2009 }
2010
2011 - (Tab*)addSelectedTabWithURL:(const GURL&)url
2012 postData:(TemplateURLRef::PostContent*)postData
2013 transition:(ui::PageTransition)transition {
2014 return [self addSelectedTabWithURL:url
2015 postData:postData
2016 atIndex:[_model count]
2017 transition:transition];
2018 }
2019
2020 - (Tab*)addSelectedTabWithURL:(const GURL&)url
2021 transition:(ui::PageTransition)transition {
2022 return [self addSelectedTabWithURL:url
2023 atIndex:[_model count]
2024 transition:transition];
2025 }
2026
2027 - (Tab*)addSelectedTabWithURL:(const GURL&)url
2028 atIndex:(NSUInteger)position
2029 transition:(ui::PageTransition)transition {
2030 return [self addSelectedTabWithURL:url
2031 postData:NULL
2032 atIndex:position
2033 transition:transition];
2034 }
2035
2036 - (Tab*)addSelectedTabWithURL:(const GURL&)URL
2037 postData:(TemplateURLRef::PostContent*)postData
2038 atIndex:(NSUInteger)position
2039 transition:(ui::PageTransition)transition {
2040 if (position == NSNotFound)
2041 position = [_model count];
2042 DCHECK(position <= [_model count]);
2043
2044 web::NavigationManager::WebLoadParams params(URL);
2045 params.transition_type = transition;
2046 if (postData) {
2047 // Extract the content type and post params from |postData| and add them
2048 // to the load params.
2049 NSString* contentType = base::SysUTF8ToNSString(postData->first);
2050 NSData* data = [NSData dataWithBytes:(void*)postData->second.data()
2051 length:postData->second.length()];
2052 params.post_data.reset([data retain]);
2053 params.extra_headers.reset([@{ @"Content-Type" : contentType } retain]);
2054 }
2055 Tab* tab = [_model insertOrUpdateTabWithLoadParams:params
2056 windowName:nil
2057 opener:nil
2058 openedByDOM:NO
2059 atIndex:position
2060 inBackground:NO];
2061 return tab;
2062 }
2063
2064 // Whether the given tab's url begins with the chrome prefix.
2065 - (BOOL)isTabNativePage:(Tab*)tab {
2066 return tab && tab.url.SchemeIs(kChromeUIScheme);
2067 }
2068
2069 - (void)expectNewForegroundTab {
2070 _expectingForegroundTab = YES;
2071 }
2072
2073 - (UIImageView*)pageFullScreenOpenCloseAnimationView {
2074 CGRect viewBounds, remainder;
2075 CGRectDivide(self.view.bounds, &remainder, &viewBounds, StatusBarHeight(),
2076 CGRectMinYEdge);
2077 return [[[UIImageView alloc] initWithFrame:viewBounds] autorelease];
2078 }
2079
2080 - (UIImageView*)pageOpenCloseAnimationView {
2081 CGRect frame = [_contentArea bounds];
2082
2083 frame.size.height = frame.size.height - [self headerHeight];
2084 frame.origin.y = [self headerHeight];
2085
2086 UIImageView* pageView =
2087 [[[UIImageView alloc] initWithFrame:frame] autorelease];
2088 CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
2089 pageView.center = center;
2090
2091 pageView.backgroundColor = [UIColor whiteColor];
2092 return pageView;
2093 }
2094
2095 - (void)installDelegatesForTab:(Tab*)tab {
2096 // We don't unregister any of this delegation.
2097 // TODO(crbug.com/375577): Unregister these delegates correctly on BVC
2098 // deallocation.
2099 tab.dialogDelegate = self;
2100 tab.snapshotOverlayProvider = self;
2101 tab.storeKitLauncher = self;
2102 tab.passKitDialogProvider = self;
2103 tab.fullScreenControllerDelegate = self;
2104 if (!IsIPadIdiom()) {
2105 tab.overscrollActionsControllerDelegate = self;
2106 }
2107 tab.tabSnapshottingDelegate = self;
2108 // Install the proper CRWWebController delegates.
2109 tab.webController.nativeProvider = self;
2110 tab.webController.swipeRecognizerProvider = self.sideSwipeController;
2111 // Delegate will remove itself on destruction.
2112 tab.webState->SetDelegate(_webStateDelegate.get());
2113 }
2114
2115 // Called when a tab is selected in the model. Make any required view changes.
2116 // The notification will not be sent when the tab is already the selected tab.
2117 - (void)tabSelected:(Tab*)tab {
2118 DCHECK(tab);
2119
2120 // Ignore changes while the tab stack view is visible (or while suspended).
2121 // The display will be refreshed when this view becomes active again.
2122 if (!self.visible || ![_model webUsageEnabled])
2123 return;
2124
2125 [self displayTab:tab isNewSelection:YES];
2126
2127 if (_expectingForegroundTab && !_inNewTabAnimation) {
2128 // Now that the new tab has been displayed, return to normal. Rather than
2129 // keep a reference to the previous tab, just turn off preview mode for all
2130 // tabs (since doing so is a no-op for the tabs that don't have it set).
2131 _expectingForegroundTab = NO;
2132 for (Tab* tab in _model.get()) {
2133 [tab.webController setOverlayPreviewMode:NO];
2134 }
2135 }
2136 }
2137
2138 #pragma mark - External files
2139
2140 - (NSSet*)referencedExternalFiles {
2141 NSSet* filesReferencedByTabs = [_model currentlyReferencedExternalFiles];
2142
2143 // TODO(noyau): this is incorrect, the caller should know that the model is
2144 // not loaded yet.
2145 if (!_bookmarkModel->loaded())
2146 return filesReferencedByTabs;
2147
2148 std::vector<bookmarks::BookmarkModel::URLAndTitle> bookmarks;
2149 _bookmarkModel->GetBookmarks(&bookmarks);
2150 NSMutableSet* bookmarkedFiles = [NSMutableSet set];
2151 for (const auto& bookmark : bookmarks) {
2152 GURL bookmarkUrl = bookmark.url;
2153 if (UrlIsExternalFileReference(bookmarkUrl)) {
2154 [bookmarkedFiles
2155 addObject:base::SysUTF8ToNSString(bookmarkUrl.ExtractFileName())];
2156 }
2157 }
2158 return [filesReferencedByTabs setByAddingObjectsFromSet:bookmarkedFiles];
2159 }
2160
2161 - (void)removeExternalFilesImmediately:(BOOL)immediately
2162 completionHandler:(ProceduralBlock)completionHandler {
2163 DCHECK_CURRENTLY_ON(web::WebThread::UI);
2164 DCHECK(!_isOffTheRecord);
2165 _externalFileRemover.reset(new ExternalFileRemover(self));
2166 // Delay the cleanup of the unreferenced files received from other apps
2167 // to not impact startup performance.
2168 int delay = immediately ? 0 : kExternalFilesCleanupDelaySeconds;
2169 _externalFileRemover->RemoveAfterDelay(
2170 base::TimeDelta::FromSeconds(delay),
2171 base::BindBlock(completionHandler ? completionHandler
2172 : ^{
2173 }));
2174 }
2175
2176 #pragma mark - SnapshotOverlayProvider methods
2177
2178 - (NSArray*)snapshotOverlaysForTab:(Tab*)tab {
2179 NSMutableArray* overlays = [NSMutableArray array];
2180 if (![_model webUsageEnabled]) {
2181 return overlays;
2182 }
2183 UIView* voiceSearchView = [self voiceSearchOverlayViewForTab:tab];
2184 if (voiceSearchView) {
2185 CGFloat voiceSearchYOffset = [self voiceSearchOverlayYOffsetForTab:tab];
2186 base::scoped_nsobject<SnapshotOverlay> voiceSearchOverlay(
2187 [[SnapshotOverlay alloc] initWithView:voiceSearchView
2188 yOffset:voiceSearchYOffset]);
2189 [overlays addObject:voiceSearchOverlay];
2190 }
2191 UIView* infoBarView = [self infoBarOverlayViewForTab:tab];
2192 if (infoBarView) {
2193 CGFloat infoBarYOffset = [self infoBarOverlayYOffsetForTab:tab];
2194 base::scoped_nsobject<SnapshotOverlay> infoBarOverlay(
2195 [[SnapshotOverlay alloc] initWithView:infoBarView
2196 yOffset:infoBarYOffset]);
2197 [overlays addObject:infoBarOverlay];
2198 }
2199 return overlays;
2200 }
2201
2202 #pragma mark -
2203
2204 - (UIView*)infoBarOverlayViewForTab:(Tab*)tab {
2205 if (IsIPadIdiom()) {
2206 // Not using overlays on iPad because the content is pushed down by
2207 // infobar and the transition between snapshot and fresh page can
2208 // cause both snapshot and real infobars to appear at the same time.
2209 return nil;
2210 }
2211 Tab* currentTab = [_model currentTab];
2212 if (tab && tab == currentTab) {
2213 infobars::InfoBarManager* infoBarManager = [currentTab infoBarManager];
2214 if (infoBarManager->infobar_count() > 0) {
2215 DCHECK(_infoBarContainer);
2216 return _infoBarContainer->view();
2217 }
2218 }
2219 return nil;
2220 }
2221
2222 - (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab {
2223 if (tab != [_model currentTab] || !_infoBarContainer.get()) {
2224 // There is no UI representation for non-current tabs or there is
2225 // no _infoBarContainer instantiated yet.
2226 // Return offset outside of tab.
2227 return CGRectGetMaxY(self.view.frame);
2228 } else if (IsIPadIdiom()) {
2229 // The infobars on iPad are display at the top of a tab.
2230 return CGRectGetMinY([[_model currentTab].webController visibleFrame]);
2231 } else {
2232 // The infobars on iPhone are displayed at the bottom of a tab.
2233 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2234 return CGRectGetMaxY(visibleFrame) -
2235 CGRectGetHeight(_infoBarContainer->view().frame);
2236 }
2237 }
2238
2239 - (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab {
2240 Tab* currentTab = [_model currentTab];
2241 if (tab && tab == currentTab && tab.isVoiceSearchResultsTab &&
2242 _voiceSearchBar && ![_voiceSearchBar isHidden]) {
2243 return _voiceSearchBar;
2244 }
2245 return nil;
2246 }
2247
2248 - (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab {
2249 if (tab != [_model currentTab] || [_voiceSearchBar isHidden]) {
2250 // There is no UI representation for non-current tabs or there is
2251 // no visible voice search. Return offset outside of tab.
2252 return CGRectGetMaxY(self.view.frame);
2253 } else {
2254 // The voice search bar on iPhone is displayed at the bottom of a tab.
2255 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2256 return CGRectGetMaxY(visibleFrame) - kVoiceSearchBarHeight;
2257 }
2258 }
2259
2260 - (void)ensureVoiceSearchControllerCreated {
2261 if (!_voiceSearchController.get()) {
2262 VoiceSearchProvider* provider =
2263 ios::GetChromeBrowserProvider()->GetVoiceSearchProvider();
2264 if (provider) {
2265 _voiceSearchController =
2266 provider->CreateVoiceSearchController(_browserState);
2267 _voiceSearchController->SetDelegate(_toolbarController);
2268 }
2269 }
2270 }
2271
2272 - (void)ensureVoiceSearchBarCreated {
2273 if (_voiceSearchBar)
2274 return;
2275
2276 CGFloat width = CGRectGetWidth([[self view] bounds]);
2277 CGFloat y = CGRectGetHeight([[self view] bounds]) - kVoiceSearchBarHeight;
2278 CGRect frame = CGRectMake(0.0, y, width, kVoiceSearchBarHeight);
2279 _voiceSearchBar.reset(ios::GetChromeBrowserProvider()
2280 ->GetVoiceSearchProvider()
2281 ->CreateVoiceSearchBar(frame));
2282 [_voiceSearchBar setVoiceSearchBarDelegate:self];
2283 [_voiceSearchBar setHidden:YES];
2284 [_voiceSearchBar setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin |
2285 UIViewAutoresizingFlexibleWidth];
2286 [self.view insertSubview:_voiceSearchBar
2287 belowSubview:_infoBarContainer->view()];
2288 }
2289
2290 - (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated {
2291 // Voice search bar exists and is shown/hidden.
2292 BOOL show = self.shouldShowVoiceSearchBar;
2293 if (_voiceSearchBar && _voiceSearchBar.get().hidden != show)
2294 return;
2295
2296 // Voice search bar doesn't exist and thus is not visible.
2297 if (!_voiceSearchBar && !show)
2298 return;
2299
2300 if (animated)
2301 [_voiceSearchBar.get() animateToBecomeVisible:show];
2302 else
2303 _voiceSearchBar.get().hidden = !show;
2304 }
2305
2306 - (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner {
2307 Protocol* ownerProtocol = @protocol(LogoAnimationControllerOwner);
2308 if ([_voiceSearchBar conformsToProtocol:ownerProtocol] &&
2309 self.shouldShowVoiceSearchBar) {
2310 // Use |_voiceSearchBar| for VoiceSearch results tab and dismissal
2311 // animations.
2312 return static_cast<id<LogoAnimationControllerOwner>>(_voiceSearchBar.get());
2313 }
2314 id currentNativeController =
2315 [self nativeControllerForTab:self.tabModel.currentTab];
2316 Protocol* possibleOwnerProtocol =
2317 @protocol(LogoAnimationControllerOwnerOwner);
2318 if ([currentNativeController conformsToProtocol:possibleOwnerProtocol] &&
2319 [currentNativeController logoAnimationControllerOwner]) {
2320 // If the current native controller is showing a GLIF view (e.g. the NTP
2321 // when there is no doodle), use that GLIFControllerOwner.
2322 return [currentNativeController logoAnimationControllerOwner];
2323 }
2324 return nil;
2325 }
2326
2327 #pragma mark - PassKitDialogProvider methods
2328
2329 - (void)presentPassKitDialog:(NSData*)data {
2330 NSError* error = nil;
2331 base::scoped_nsobject<PKPass> pass;
2332 if (data)
2333 pass.reset([[PKPass alloc] initWithData:data error:&error]);
2334 if (error || !data) {
2335 if ([_model currentTab]) {
2336 infobars::InfoBarManager* infoBarManager =
2337 [[_model currentTab] infoBarManager];
2338 // TODO(crbug.com/227994): Infobar cleanup (infoBarManager should never be
2339 // NULL, replace if with DCHECK).
2340 if (infoBarManager)
2341 [_dependencyFactory showPassKitErrorInfoBarForManager:infoBarManager];
2342 }
2343 } else {
2344 PKAddPassesViewController* passKitViewController =
2345 [_dependencyFactory newPassKitViewControllerForPass:pass];
2346 if (passKitViewController) {
2347 [self presentViewController:passKitViewController
2348 animated:YES
2349 completion:^{
2350 }];
2351 }
2352 }
2353 }
2354
2355 - (UIStatusBarStyle)preferredStatusBarStyle {
2356 return (IsIPadIdiom() || _isOffTheRecord) ? UIStatusBarStyleLightContent
2357 : UIStatusBarStyleDefault;
2358 }
2359
2360 #pragma mark - CRWWebStateDelegate methods.
2361
2362 - (void)webState:(web::WebState*)webState didChangeProgress:(double)progress {
2363 if (webState == [_model currentTab].webState) {
2364 // TODO(crbug.com/546406): It is probably possible to do something smarter,
2365 // but the fact that this is not always sent will have to be taken into
2366 // account.
2367 [self updateToolbar];
2368 }
2369 }
2370
2371 - (BOOL)webState:(web::WebState*)webState
2372 handleContextMenu:(const web::ContextMenuParams&)params {
2373 // Prevent context menu from displaying for a tab which is no longer the
2374 // current one.
2375 if (webState != [_model currentTab].webState) {
2376 return NO;
2377 }
2378
2379 // No custom context menu if no valid url is available in |params|.
2380 if (!params.link_url.is_valid() && !params.src_url.is_valid()) {
2381 return NO;
2382 }
2383
2384 DCHECK(_browserState);
2385 DCHECK([_model currentTab]);
2386
2387 _contextMenuCoordinator.reset([[ContextMenuCoordinator alloc]
2388 initWithBaseViewController:self
2389 params:params]);
2390
2391 NSString* title = nil;
2392 ProceduralBlock action = nil;
2393
2394 base::WeakNSObject<BrowserViewController> weakSelf(self);
2395 GURL link = params.link_url;
2396 bool isLink = link.is_valid();
2397 GURL imageUrl = params.src_url;
2398 bool isImage = imageUrl.is_valid();
2399
2400 if (isLink) {
2401 if (link.SchemeIs(url::kJavaScriptScheme)) {
2402 // Open
2403 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPEN);
2404 action = ^{
2405 Record(ACTION_OPEN_JAVASCRIPT, isImage, isLink);
2406 [weakSelf openJavascript:base::SysUTF8ToNSString(link.GetContent())];
2407 };
2408 [_contextMenuCoordinator addItemWithTitle:title action:action];
2409 }
2410
2411 if (web::UrlHasWebScheme(link)) {
2412 web::Referrer referrer([_model currentTab].url, params.referrer_policy);
2413
2414 if (reading_list::switches::IsReadingListEnabled()) {
2415 NSString* innerText = params.link_text;
2416 if ([innerText length] > 0) {
2417 // Add to reading list.
2418 title = l10n_util::GetNSStringWithFixup(
2419 IDS_IOS_CONTENT_CONTEXT_ADDTOREADINGLIST);
2420 action = ^{
2421 Record(ACTION_READ_LATER, isImage, isLink);
2422 [weakSelf addToReadingListURL:link title:innerText];
2423 };
2424 [_contextMenuCoordinator addItemWithTitle:title action:action];
2425 }
2426 }
2427
2428 // Open in New Tab.
2429 title = l10n_util::GetNSStringWithFixup(
2430 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB);
2431 action = ^{
2432 Record(ACTION_OPEN_IN_NEW_TAB, isImage, isLink);
2433 [weakSelf webPageOrderedOpen:link
2434 referrer:referrer
2435 windowName:nil
2436 inBackground:YES
2437 appendTo:kCurrentTab];
2438 };
2439 [_contextMenuCoordinator addItemWithTitle:title action:action];
2440 if (!_isOffTheRecord) {
2441 // Open in Incognito Tab.
2442 title = l10n_util::GetNSStringWithFixup(
2443 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB);
2444 action = ^{
2445 Record(ACTION_OPEN_IN_INCOGNITO_TAB, isImage, isLink);
2446 [weakSelf webPageOrderedOpen:link
2447 referrer:referrer
2448 windowName:nil
2449 inIncognito:YES
2450 inBackground:NO
2451 appendTo:kCurrentTab];
2452 };
2453 [_contextMenuCoordinator addItemWithTitle:title action:action];
2454 }
2455 }
2456 // Copy Link.
2457 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_COPY);
2458 action = ^{
2459 Record(ACTION_COPY_LINK_ADDRESS, isImage, isLink);
2460 NSURL* url = net::NSURLWithGURL(link);
2461 NSDictionary* item = @{
2462 (NSString*)kUTTypeURL : url,
2463 (NSString*)kUTTypeUTF8PlainText :
2464 [[url absoluteString] dataUsingEncoding:NSUTF8StringEncoding],
2465 };
2466 [[UIPasteboard generalPasteboard] setItems:@[ item ]];
2467 };
2468 [_contextMenuCoordinator addItemWithTitle:title action:action];
2469 }
2470 if (isImage) {
2471 web::Referrer referrer([_model currentTab].url, params.referrer_policy);
2472 // Save Image.
2473 if (experimental_flags::IsDownloadRenamingEnabled()) {
2474 title = l10n_util::GetNSStringWithFixup(
2475 IDS_IOS_CONTENT_CONTEXT_DOWNLOADIMAGE);
2476 } else {
2477 title =
2478 l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_SAVEIMAGE);
2479 }
2480 action = ^{
2481 Record(ACTION_SAVE_IMAGE, isImage, isLink);
2482 [weakSelf saveImageAtURL:imageUrl referrer:referrer];
2483 };
2484 [_contextMenuCoordinator addItemWithTitle:title action:action];
2485 // Open Image.
2486 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPENIMAGE);
2487 action = ^{
2488 Record(ACTION_OPEN_IMAGE, isImage, isLink);
2489 [weakSelf loadURL:imageUrl
2490 referrer:referrer
2491 transition:ui::PAGE_TRANSITION_LINK
2492 rendererInitiated:YES];
2493 };
2494 [_contextMenuCoordinator addItemWithTitle:title action:action];
2495 // Open Image In New Tab.
2496 title = l10n_util::GetNSStringWithFixup(
2497 IDS_IOS_CONTENT_CONTEXT_OPENIMAGENEWTAB);
2498 action = ^{
2499 Record(ACTION_OPEN_IMAGE_IN_NEW_TAB, isImage, isLink);
2500 [weakSelf webPageOrderedOpen:imageUrl
2501 referrer:referrer
2502 windowName:nil
2503 inBackground:true
2504 appendTo:kCurrentTab];
2505 };
2506 [_contextMenuCoordinator addItemWithTitle:title action:action];
2507
2508 TemplateURLService* service =
2509 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
2510 TemplateURL* defaultURL = service->GetDefaultSearchProvider();
2511 if (defaultURL && !defaultURL->image_url().empty() &&
2512 defaultURL->image_url_ref().IsValid(service->search_terms_data())) {
2513 title = l10n_util::GetNSStringF(IDS_IOS_CONTEXT_MENU_SEARCHWEBFORIMAGE,
2514 defaultURL->short_name());
2515 action = ^{
2516 Record(ACTION_SEARCH_BY_IMAGE, isImage, isLink);
2517 [weakSelf searchByImageAtURL:imageUrl referrer:referrer];
2518 };
2519 [_contextMenuCoordinator addItemWithTitle:title action:action];
2520 }
2521 }
2522
2523 [_contextMenuCoordinator start];
2524 return YES;
2525 }
2526
2527 - (web::JavaScriptDialogPresenter*)javaScriptDialogPresenterForWebState:
2528 (web::WebState*)webState {
2529 return _javaScriptDialogPresenter.get();
2530 }
2531
2532 #pragma mark - FullScreenControllerDelegate methods
2533
2534 - (CGFloat)headerOffset {
2535 if (IsIPadIdiom())
2536 return StatusBarHeight();
2537 return 0.0;
2538 }
2539
2540 - (const std::vector<HeaderDefinition>)headerViews {
2541 std::vector<HeaderDefinition> results;
2542 if (![self isViewLoaded])
2543 return results;
2544
2545 if (!IsIPadIdiom()) {
2546 if ([_toolbarController view]) {
2547 HeaderDefinition header = {
2548 base::scoped_nsobject<UIView>([[_toolbarController view] retain]),
2549 Hideable, [ToolbarController toolbarDropShadowHeight], 0.0,
2550 };
2551 results.push_back(header);
2552 }
2553 } else {
2554 if ([_tabStripController view]) {
2555 HeaderDefinition header = {
2556 base::scoped_nsobject<UIView>([[_tabStripController view] retain]),
2557 Hideable, 0.0, 0.0,
2558 };
2559 results.push_back(header);
2560 }
2561 if ([_toolbarController view]) {
2562 HeaderDefinition header = {
2563 base::scoped_nsobject<UIView>([[_toolbarController view] retain]),
2564 Hideable, [ToolbarController toolbarDropShadowHeight], 0.0,
2565 };
2566 results.push_back(header);
2567 }
2568 if ([_findBarController view]) {
2569 HeaderDefinition header = {
2570 base::scoped_nsobject<UIView>([[_findBarController view] retain]),
2571 Overlap, 0.0, kIPadFindBarOverlap,
2572 };
2573 results.push_back(header);
2574 }
2575 }
2576 return results;
2577 }
2578
2579 - (UIView*)footerView {
2580 return _voiceSearchBar;
2581 }
2582
2583 - (CGFloat)headerHeight {
2584 return [self headerHeightForTab:[_model currentTab]];
2585 }
2586
2587 - (CGFloat)headerHeightForTab:(Tab*)tab {
2588 id nativeController = [self nativeControllerForTab:tab];
2589 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)] &&
2590 [nativeController respondsToSelector:@selector(toolbarHeight)] &&
2591 [nativeController toolbarHeight] > 0.0 && !IsIPadIdiom()) {
2592 // On iPhone, don't add any header height for ToolbarOwner native
2593 // controllers when they're displaying their own toolbar.
2594 return 0;
2595 }
2596
2597 const std::vector<HeaderDefinition> views = [self headerViews];
2598
2599 CGFloat height = [self headerOffset];
2600 for (const auto& header : views) {
2601 if (header.view && header.behaviour == Hideable) {
2602 height += CGRectGetHeight([header.view frame]) -
2603 header.heightAdjustement - header.inset;
2604 }
2605 }
2606
2607 return height - StatusBarHeight();
2608 }
2609
2610 - (BOOL)isTabWithIDCurrent:(NSString*)sessionID {
2611 return self.visible &&
2612 [sessionID isEqualToString:[[_model currentTab] currentSessionID]];
2613 }
2614
2615 - (CGFloat)currentHeaderOffset {
2616 const std::vector<HeaderDefinition> headers = [self headerViews];
2617 if (!headers.size())
2618 return 0.0;
2619
2620 // Prerender tab does not have a toolbar, return |headerHeight| as promised by
2621 // API documentation.
2622 if ([[[self tabModel] currentTab] isPrerenderTab])
2623 return [self headerHeight];
2624
2625 UIView* topHeader = headers[0].view;
2626 return -(topHeader.frame.origin.y - [self headerOffset]);
2627 }
2628
2629 - (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset {
2630 UIView* footer = [self footerView];
2631 CGFloat headerHeight = [self headerHeight];
2632 if (!footer || headerHeight == 0)
2633 return 0.0;
2634
2635 CGFloat footerHeight = CGRectGetHeight(footer.frame);
2636 CGFloat offset = headerOffset * footerHeight / headerHeight;
2637 return std::ceil(CGRectGetHeight(self.view.bounds) - footerHeight + offset);
2638 }
2639
2640 - (void)fullScreenController:(FullScreenController*)controller
2641 headerAnimationCompleted:(BOOL)completed
2642 offset:(CGFloat)offset {
2643 if (completed)
2644 [controller setToolbarInsetsForHeaderOffset:offset forceUpdate:YES];
2645 }
2646
2647 - (void)setFramesForHeaders:(const std::vector<HeaderDefinition>)headers
2648 atOffset:(CGFloat)headerOffset {
2649 CGFloat height = [self headerOffset];
2650 for (const auto& header : headers) {
2651 CGRect frame = [header.view frame];
2652 frame.origin.y = height - headerOffset - header.inset;
2653 [header.view setFrame:frame];
2654 if (header.behaviour != Overlap)
2655 height += CGRectGetHeight(frame);
2656 }
2657 }
2658
2659 - (void)fullScreenController:(FullScreenController*)fullScreenController
2660 drawHeaderViewFromOffset:(CGFloat)headerOffset
2661 animate:(BOOL)animate {
2662 if ([_sideSwipeController inSwipe])
2663 return;
2664
2665 CGRect footerFrame = CGRectZero;
2666 UIView* footer = nil;
2667 // Only animate the voice search bar if the tab is a voice search results tab.
2668 if ([_model currentTab].isVoiceSearchResultsTab) {
2669 footer = [self footerView];
2670 footerFrame = footer.frame;
2671 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
2672 }
2673
2674 const std::vector<HeaderDefinition> headers = [self headerViews];
2675 void (^block)(void) = ^{
2676 [self setFramesForHeaders:headers atOffset:headerOffset];
2677 footer.frame = footerFrame;
2678 };
2679 void (^completion)(BOOL) = ^(BOOL finished) {
2680 [self fullScreenController:fullScreenController
2681 headerAnimationCompleted:finished
2682 offset:headerOffset];
2683 };
2684 if (animate) {
2685 [UIView animateWithDuration:ios_internal::kToolbarAnimationDuration
2686 delay:0.0
2687 options:UIViewAnimationOptionBeginFromCurrentState
2688 animations:block
2689 completion:completion];
2690 } else {
2691 block();
2692 completion(YES);
2693 }
2694 }
2695
2696 - (void)fullScreenController:(FullScreenController*)fullScreenController
2697 drawHeaderViewFromOffset:(CGFloat)headerOffset
2698 onWebViewProxy:(id<CRWWebViewProxy>)webViewProxy
2699 changeTopContentPadding:(BOOL)changeTopContentPadding
2700 scrollingToOffset:(CGFloat)contentOffset {
2701 DCHECK(webViewProxy);
2702 if ([_sideSwipeController inSwipe])
2703 return;
2704
2705 CGRect footerFrame;
2706 UIView* footer = nil;
2707 // Only animate the voice search bar if the tab is a voice search results tab.
2708 if ([_model currentTab].isVoiceSearchResultsTab) {
2709 footer = [self footerView];
2710 footerFrame = footer.frame;
2711 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
2712 }
2713
2714 const std::vector<HeaderDefinition> headers = [self headerViews];
2715 void (^block)(void) = ^{
2716 [self setFramesForHeaders:headers atOffset:headerOffset];
2717 footer.frame = footerFrame;
2718 webViewProxy.scrollViewProxy.contentOffset = CGPointMake(
2719 webViewProxy.scrollViewProxy.contentOffset.x, contentOffset);
2720 if (changeTopContentPadding)
2721 webViewProxy.topContentPadding = contentOffset;
2722 };
2723 void (^completion)(BOOL) = ^(BOOL finished) {
2724 [self fullScreenController:fullScreenController
2725 headerAnimationCompleted:finished
2726 offset:headerOffset];
2727 };
2728
2729 [UIView animateWithDuration:ios_internal::kToolbarAnimationDuration
2730 delay:0.0
2731 options:UIViewAnimationOptionBeginFromCurrentState
2732 animations:block
2733 completion:completion];
2734 }
2735
2736 #pragma mark - VoiceSearchBarOwner
2737
2738 - (id<VoiceSearchBar>)voiceSearchBar {
2739 return _voiceSearchBar;
2740 }
2741
2742 #pragma mark - Install OverScrollActionController method.
2743 - (void)setOverScrollActionControllerToStaticNativeContent:
2744 (StaticHtmlNativeContent*)nativeContent {
2745 if (!IsIPadIdiom() && !FirstRun::IsChromeFirstRun()) {
2746 OverscrollActionsController* controller =
2747 [[[OverscrollActionsController alloc]
2748 initWithScrollView:[nativeContent scrollView]] autorelease];
2749 [[nativeContent scrollView] setDelegate:controller];
2750 [controller setDelegate:self];
2751 ios_internal::OverscrollStyle style =
2752 _isOffTheRecord
2753 ? ios_internal::OverscrollStyle::REGULAR_PAGE_INCOGNITO
2754 : ios_internal::OverscrollStyle::REGULAR_PAGE_NON_INCOGNITO;
2755 controller.style = style;
2756 nativeContent.overscrollActionsController = controller;
2757 }
2758 }
2759
2760 #pragma mark - OverscrollActionsControllerDelegate methods.
2761
2762 - (void)overscrollActionsController:(OverscrollActionsController*)controller
2763 didTriggerAction:(ios_internal::OverscrollAction)action {
2764 switch (action) {
2765 case ios_internal::OverscrollAction::NEW_TAB:
2766 [self newTab:nil];
2767 break;
2768 case ios_internal::OverscrollAction::CLOSE_TAB:
2769 [self closeCurrentTab];
2770 break;
2771 case ios_internal::OverscrollAction::REFRESH:
2772 if ([[[_model currentTab] webController] loadPhase] ==
2773 web::PAGE_LOADING) {
2774 [[_model currentTab] stopLoading];
2775 }
2776 [[_model currentTab] reload];
2777 break;
2778 case ios_internal::OverscrollAction::NONE:
2779 NOTREACHED();
2780 break;
2781 }
2782 }
2783
2784 - (BOOL)shouldAllowOverscrollActions {
2785 return YES;
2786 }
2787
2788 - (UIView*)headerView {
2789 return [_toolbarController view];
2790 }
2791
2792 - (UIView*)toolbarSnapshotView {
2793 return [[_toolbarController view] snapshotViewAfterScreenUpdates:NO];
2794 }
2795
2796 - (CGFloat)overscrollActionsControllerHeaderInset:
2797 (OverscrollActionsController*)controller {
2798 if (controller == [[[self tabModel] currentTab] overscrollActionsController])
2799 return [self headerHeight];
2800 else
2801 return 0;
2802 }
2803
2804 - (CGFloat)overscrollHeaderHeight {
2805 return [self headerHeight] + StatusBarHeight();
2806 }
2807
2808 #pragma mark - TabSnapshottingDelegate methods.
2809
2810 - (CGRect)snapshotContentAreaForTab:(Tab*)tab {
2811 CGRect pageContentArea = _contentArea.bounds;
2812 if ([_model webUsageEnabled])
2813 pageContentArea = tab.view.bounds;
2814 CGFloat headerHeight = [self headerHeightForTab:tab];
2815 id nativeController = [self nativeControllerForTab:tab];
2816 if ([nativeController respondsToSelector:@selector(toolbarHeight)])
2817 headerHeight += [nativeController toolbarHeight];
2818 UIEdgeInsets contentInsets = UIEdgeInsetsMake(headerHeight, 0.0, 0.0, 0.0);
2819 return UIEdgeInsetsInsetRect(pageContentArea, contentInsets);
2820 }
2821
2822 #pragma mark - NewTabPageObserver methods.
2823
2824 - (void)selectedPanelDidChange {
2825 [self updateToolbar];
2826 }
2827
2828 #pragma mark - CRWNativeContentProvider methods
2829
2830 - (id<CRWNativeContent>)controllerForURL:(const GURL&)url
2831 withError:(NSError*)error
2832 isPost:(BOOL)isPost {
2833 ErrorPageContent* errorPageContent =
2834 [[[ErrorPageContent alloc] initWithLoader:self
2835 browserState:self.browserState
2836 url:url
2837 error:error
2838 isPost:isPost
2839 isIncognito:_isOffTheRecord] autorelease];
2840 [self setOverScrollActionControllerToStaticNativeContent:errorPageContent];
2841 return errorPageContent;
2842 }
2843
2844 - (BOOL)hasControllerForURL:(const GURL&)url {
2845 std::string host(url.host());
2846
2847 return host == kChromeUINewTabHost || host == kChromeUIBookmarksHost ||
2848 host == kChromeUITermsHost || host == kChromeUIOfflineHost;
2849 }
2850
2851 - (id<CRWNativeContent>)controllerForURL:(const GURL&)url {
2852 DCHECK(url.SchemeIs(kChromeUIScheme));
2853
2854 id<CRWNativeContent> nativeController = nil;
2855 std::string url_host = url.host();
2856 if (url_host == kChromeUINewTabHost || url_host == kChromeUIBookmarksHost) {
2857 NewTabPageController* pageController =
2858 [[[NewTabPageController alloc] initWithUrl:url
2859 loader:self
2860 focuser:_toolbarController
2861 ntpObserver:self
2862 browserState:_browserState
2863 colorCache:_dominantColorCache
2864 webToolbarDelegate:self
2865 tabModel:_model] autorelease];
2866 pageController.swipeRecognizerProvider = self.sideSwipeController;
2867
2868 // Panel is always NTP for iPhone.
2869 NewTabPage::PanelIdentifier panelType = NewTabPage::kMostVisitedPanel;
2870
2871 if (IsIPadIdiom()) {
2872 // New Tab Page can have multiple panels. Each panel is addressable
2873 // by a #fragment, e.g. chrome://newtab/#most_visited takes user to
2874 // the Most Visited page, chrome://newtab/#bookmarks takes user to
2875 // the Bookmark Manager, etc.
2876 // The utility functions NewTabPage::IdentifierFromFragment() and
2877 // FragmentFromIdentifier() map an identifier to/from a #fragment.
2878 // If the URL is chrome://bookmarks, pre-select the #bookmarks panel
2879 // without changing the URL since the URL may be chrome://bookmarks/#123.
2880 // If the URL is chrome://newtab/, pre-select the panel based on the
2881 // #fragment.
2882 panelType = url_host == kChromeUIBookmarksHost
2883 ? NewTabPage::kBookmarksPanel
2884 : NewTabPage::IdentifierFromFragment(url.ref());
2885 }
2886 [pageController selectPanel:panelType];
2887 nativeController = pageController;
2888 } else if (url_host == kChromeUITermsHost) {
2889 const std::string& filename = GetTermsOfServicePath();
2890
2891 StaticHtmlNativeContent* staticNativeController =
2892 [[[StaticHtmlNativeContent alloc]
2893 initWithResourcePathResource:base::SysUTF8ToNSString(filename)
2894 loader:self
2895 browserState:_browserState
2896 url:GURL(kChromeUITermsURL)] autorelease];
2897 [self setOverScrollActionControllerToStaticNativeContent:
2898 staticNativeController];
2899 nativeController = staticNativeController;
2900 } else if (url_host == kChromeUIOfflineHost) {
2901 StaticHtmlNativeContent* staticNativeController =
2902 [[[OfflinePageNativeContent alloc] initWithLoader:self
2903 browserState:_browserState
2904 webState:[self currentWebState]
2905 URL:url] autorelease];
2906 [self setOverScrollActionControllerToStaticNativeContent:
2907 staticNativeController];
2908 nativeController = staticNativeController;
2909 } else if (url_host == kChromeUIExternalFileHost) {
2910 // Return an instance of the |ExternalFileController| only if the file is
2911 // still in the sandbox.
2912 NSString* filePath = [ExternalFileController pathForExternalFileURL:url];
2913 if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
2914 nativeController = [[[ExternalFileController alloc]
2915 initWithURL:url
2916 browserState:_browserState] autorelease];
2917 }
2918 } else {
2919 DCHECK(![self hasControllerForURL:url]);
2920 // In any other case the PageNotAvailableController is returned.
2921 nativeController =
2922 [[[PageNotAvailableController alloc] initWithUrl:url] autorelease];
2923 if (url_host == kChromeUIHistoryFrameHost) {
2924 base::mac::ObjCCastStrict<PageNotAvailableController>(nativeController)
2925 .descriptionText = l10n_util::GetNSStringFWithFixup(
2926 IDS_IOS_HISTORY_URL_NOT_AVAILABLE,
2927 base::UTF8ToUTF16(kChromeUIHistoryURL),
2928 l10n_util::GetStringUTF16(IDS_HISTORY_SHOW_HISTORY));
2929 }
2930 }
2931 // If a native controller is vended before its tab is added to the tab model,
2932 // use the temporary key and add it under the new tab's tabId in the
2933 // TabModelObserver callback. This happens:
2934 // - when there is no current tab (occurs when vending the NTP controller for
2935 // the first tab that is opened),
2936 // - when the current tab's url doesn't match |url| (occurs when a native
2937 // controller is opened in a new tab)
2938 // - when the current tab's url matches |url| and there is already a native
2939 // controller of the appropriate type vended to it (occurs when a native
2940 // controller is opened in a new tab from a tab with a matching URL, e.g.
2941 // opening an NTP when an NTP is already displayed in the current tab).
2942 // For normal page loads, history navigations, tab restorations, and crash
2943 // recoveries, the tab will already exist in the tab model and the tabId can
2944 // be used as the native controller key.
2945 // TODO(crbug.com/498568): To reduce complexity here, refactor the flow so
2946 // that native controllers vended here always correspond to the current tab.
2947 Tab* currentTab = [_model currentTab];
2948 NSString* nativeControllerKey = currentTab.tabId;
2949 if (!currentTab || currentTab.url != url ||
2950 [[_nativeControllersForTabIDs objectForKey:nativeControllerKey]
2951 isKindOfClass:[nativeController class]]) {
2952 nativeControllerKey = kNativeControllerTemporaryKey;
2953 }
2954 DCHECK(nativeControllerKey);
2955 [_nativeControllersForTabIDs setObject:nativeController
2956 forKey:nativeControllerKey];
2957 return nativeController;
2958 }
2959
2960 - (id)nativeControllerForTab:(Tab*)tab {
2961 id nativeController = [_nativeControllersForTabIDs objectForKey:tab.tabId];
2962 if (!nativeController) {
2963 // If there is no controller, check for a native controller stored under
2964 // the temporary key.
2965 nativeController = [_nativeControllersForTabIDs
2966 objectForKey:kNativeControllerTemporaryKey];
2967 }
2968 return nativeController;
2969 }
2970
2971 #pragma mark - DialogPresenterDelegate methods
2972
2973 - (void)dialogPresenter:(DialogPresenter*)presenter
2974 willShowDialogForWebState:(web::WebState*)webState {
2975 for (Tab* iteratedTab in self.tabModel) {
2976 if ([iteratedTab webState] == webState) {
2977 self.tabModel.currentTab = iteratedTab;
2978 DCHECK([[iteratedTab view] isDescendantOfView:self.contentArea]);
2979 break;
2980 }
2981 }
2982 }
2983
2984 #pragma mark - Context menu methods
2985
2986 - (void)searchByImageAtURL:(const GURL&)url
2987 referrer:(const web::Referrer)referrer {
2988 DCHECK(url.is_valid());
2989 base::WeakNSObject<BrowserViewController> weakSelf(self);
2990 web::ImageFetchedCallback callback =
2991 ^(const GURL& original_url, int response_code, NSData* data) {
2992 GURL originalURL(original_url.spec());
2993 DCHECK(data);
2994 dispatch_async(dispatch_get_main_queue(), ^{
2995 [weakSelf searchByImageData:data atURL:originalURL];
2996 });
2997 };
2998 _imageFetcher->StartDownload(
2999 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3000 web::PolicyForNavigation(url, referrer));
3001 }
3002
3003 - (void)searchByImageData:(NSData*)data atURL:(const GURL&)imageURL {
3004 NSData* imageData = data;
3005 UIImage* image = [UIImage imageWithData:imageData];
3006 // Downsize the image if its area exceeds kSearchByImageMaxImageArea AND
3007 // (either its width exceeds kSearchByImageMaxImageWidth OR its height exceeds
3008 // kSearchByImageMaxImageHeight).
3009 if (image &&
3010 image.size.height * image.size.width > kSearchByImageMaxImageArea &&
3011 (image.size.width > kSearchByImageMaxImageWidth ||
3012 image.size.height > kSearchByImageMaxImageHeight)) {
3013 CGSize newImageSize =
3014 CGSizeMake(kSearchByImageMaxImageWidth, kSearchByImageMaxImageHeight);
3015 image = [image gtm_imageByResizingToSize:newImageSize
3016 preserveAspectRatio:YES
3017 trimToFit:NO];
3018 imageData = UIImageJPEGRepresentation(image, 1.0);
3019 }
3020
3021 char const* bytes = reinterpret_cast<const char*>([imageData bytes]);
3022 std::string byteString(bytes, [imageData length]);
3023
3024 TemplateURLService* templateUrlService =
3025 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
3026 TemplateURL* defaultURL = templateUrlService->GetDefaultSearchProvider();
3027 DCHECK(!defaultURL->image_url().empty());
3028 DCHECK(defaultURL->image_url_ref().IsValid(
3029 templateUrlService->search_terms_data()));
3030 TemplateURLRef::SearchTermsArgs search_args(base::ASCIIToUTF16(""));
3031 search_args.image_url = imageURL;
3032 search_args.image_thumbnail_content = byteString;
3033
3034 // Generate the URL and populate |post_content| with the content type and
3035 // HTTP body for the request.
3036 TemplateURLRef::PostContent post_content;
3037 GURL result(defaultURL->image_url_ref().ReplaceSearchTerms(
3038 search_args, templateUrlService->search_terms_data(), &post_content));
3039 [self addSelectedTabWithURL:result
3040 postData:&post_content
3041 transition:ui::PAGE_TRANSITION_TYPED];
3042 }
3043
3044 - (void)saveImageAtURL:(const GURL&)url
3045 referrer:(const web::Referrer&)referrer {
3046 DCHECK(url.is_valid());
3047
3048 NSString* fileName = [NSString
3049 stringWithFormat:@"%@.png",
3050 [[NSProcessInfo processInfo] globallyUniqueString]];
3051
3052 if (url.SchemeIs(base::StringPiece("data"))) {
3053 if (!base::StartsWith(url.GetContent(), "image",
3054 base::CompareCase::INSENSITIVE_ASCII)) {
3055 // Not an image, return.
3056 return;
3057 }
3058
3059 // Find the type of the image. It should be positionned like here:
3060 // "image/png;base...", between the '/' and the ';'.
3061 size_t semiColonPosition = url.GetContent().find(std::string(";"));
3062 size_t slashPosition = url.GetContent().find(std::string("/"));
3063 size_t substrLength = semiColonPosition - slashPosition - 1;
3064 if (substrLength > 1) {
3065 // Make sure there is a type.
3066 fileName = [NSString
3067 stringWithFormat:@"image.%@",
3068 base::SysUTF8ToNSString(url.GetContent().substr(
3069 slashPosition + 1, substrLength))];
3070 }
3071 } else {
3072 fileName = base::SysUTF8ToNSString(url.ExtractFileName());
3073 }
3074
3075 web::ImageFetchedCallback callback =
3076 ^(const GURL& original_url, int response_code, NSData* data) {
3077 DCHECK(data);
3078
3079 [self managePermissionAndSaveImage:data fileName:fileName];
3080 };
3081 _imageFetcher->StartDownload(
3082 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3083 web::PolicyForNavigation(url, referrer));
3084 }
3085
3086 - (void)managePermissionAndSaveImage:(NSData*)data
3087 fileName:(NSString*)fileName {
3088 switch ([PHPhotoLibrary authorizationStatus]) {
3089 // User was never asked for permission to access photos.
3090 case PHAuthorizationStatusNotDetermined:
3091 [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
3092 // Call -saveImage again to check if chrome needs to display an error or
3093 // saves the image.
3094 if (status != PHAuthorizationStatusNotDetermined)
3095 [self managePermissionAndSaveImage:data fileName:fileName];
3096 }];
3097 break;
3098
3099 // The application doesn't have permission to access photo and the user
3100 // cannot grant it.
3101 case PHAuthorizationStatusRestricted:
3102 [self displayPrivacyErrorAlertOnMainQueue:
3103 l10n_util::GetNSString(
3104 IDS_IOS_SAVE_IMAGE_RESTRICTED_PRIVACY_ALERT_MESSAGE)];
3105 break;
3106
3107 // The application doesn't have permission to access photo and the user
3108 // can grant it.
3109 case PHAuthorizationStatusDenied:
3110 [self displayImageErrorAlertWithSettingsOnMainQueue];
3111 break;
3112
3113 // The application has permission to access the photos.
3114 default: {
3115 web::WebThread::PostTask(web::WebThread::FILE, FROM_HERE,
3116 base::BindBlock(^{
3117 [self saveImage:data fileName:fileName];
3118 }));
3119 break;
3120 }
3121 }
3122 }
3123
3124 - (void)saveImage:(NSData*)data fileName:(NSString*)fileName {
3125 NSURL* fileURL =
3126 [NSURL fileURLWithPath:[NSTemporaryDirectory()
3127 stringByAppendingPathComponent:fileName]];
3128 NSError* error = nil;
3129 [data writeToURL:fileURL options:NSDataWritingAtomic error:&error];
3130
3131 // Error while writing the image to disk.
3132 if (error) {
3133 NSString* errorMessage = [NSString
3134 stringWithFormat:@"%@ (%@ %" PRIdNS ")", [error localizedDescription],
3135 [error domain], [error code]];
3136 [self displayPrivacyErrorAlertOnMainQueue:errorMessage];
3137 return;
3138 }
3139
3140 // Save the image to photos.
3141 [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
3142 [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:fileURL];
3143 }
3144 completionHandler:^(BOOL success, NSError* error) {
3145 // Callback for the image saving.
3146 [self finishSavingImageWithError:error];
3147
3148 // Cleanup the temporary file.
3149 web::WebThread::PostTask(
3150 web::WebThread::FILE, FROM_HERE, base::BindBlock(^{
3151 NSError* error = nil;
3152 [[NSFileManager defaultManager] removeItemAtURL:fileURL
3153 error:&error];
3154 }));
3155 }];
3156 }
3157
3158 - (void)displayImageErrorAlertWithSettingsOnMainQueue {
3159 NSURL* settingURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
3160 BOOL canGoToSetting =
3161 [[UIApplication sharedApplication] canOpenURL:settingURL];
3162 if (canGoToSetting) {
3163 dispatch_async(dispatch_get_main_queue(), ^{
3164 [self displayImageErrorAlertWithSettings:settingURL];
3165 });
3166 } else {
3167 [self displayPrivacyErrorAlertOnMainQueue:
3168 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE)];
3169 }
3170 }
3171
3172 - (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL {
3173 // Dismiss current alert.
3174 [_alertCoordinator stop];
3175
3176 NSString* title =
3177 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3178 NSString* message = l10n_util::GetNSString(
3179 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE_GO_TO_SETTINGS);
3180
3181 _alertCoordinator.reset([[AlertCoordinator alloc]
3182 initWithBaseViewController:self
3183 title:title
3184 message:message]);
3185
3186 [_alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
3187 action:nil
3188 style:UIAlertActionStyleCancel];
3189
3190 [_alertCoordinator
3191 addItemWithTitle:l10n_util::GetNSString(
3192 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_GO_TO_SETTINGS)
3193 action:^{
3194 OpenUrlWithCompletionHandler(settingURL, nil);
3195 }
3196 style:UIAlertActionStyleDefault];
3197
3198 [_alertCoordinator start];
3199 }
3200
3201 - (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent {
3202 dispatch_async(dispatch_get_main_queue(), ^{
3203 NSString* title =
3204 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3205 [self showErrorAlertWithStringTitle:title message:errorContent];
3206 });
3207 }
3208
3209 // This callback is triggered when the image is effectively saved onto the photo
3210 // album, or if the save failed for some reason.
3211 - (void)finishSavingImageWithError:(NSError*)error {
3212 // Was there an error?
3213 if (error) {
3214 // Saving photo failed even though user has granted access to Photos.
3215 // Display the error information from the NSError object for user.
3216 NSString* errorMessage = [NSString
3217 stringWithFormat:@"%@ (%@ %" PRIdNS ")", [error localizedDescription],
3218 [error domain], [error code]];
3219 // This code may be execute outside of the main thread. Make sure to display
3220 // the error on the main thread.
3221 [self displayPrivacyErrorAlertOnMainQueue:errorMessage];
3222 } else {
3223 // TODO(noyau): Ideally I'd like to show an infobar with a link to switch to
3224 // the photo application. The current behaviour is to create the photo there
3225 // but not providing any link to it is suboptimal. That's what Safari is
3226 // doing, and what the PM want, but it doesn't make it right.
3227 }
3228 }
3229
3230 #pragma mark - BrowserIOS methods
3231
3232 - (ios::ChromeBrowserState*)browserState {
3233 return _browserState;
3234 }
3235
3236 - (TabModel*)tabModel {
3237 return _model.get();
3238 }
3239
3240 #pragma mark - No-tabs UI methods
3241
3242 // Show the No-Tabs UI (hiding normal tab/web ui).
3243 - (void)showNoTabsUI {
3244 // The No-Tabs UI is only shown on tablet for non-incognito BVCs. (Incognito
3245 // mode does not have a No-Tabs UI; the user is simply shown the non-incognito
3246 // BVC when the last incognito tab is closed.)
3247 DCHECK(IsIPadIdiom());
3248 DCHECK(!_isOffTheRecord);
3249
3250 // The method showNoTabsUI is called asynchronously when the number of tabs
3251 // reaches zero. Do not show the no tabs UI if a tab was added in the mean
3252 // time.
3253 if ([_model count])
3254 return;
3255
3256 DCHECK([_model currentTab] == nil);
3257 DCHECK([_contentArea subviews].count == 0 ||
3258 experimental_flags::IsTabSwitcherEnabled());
3259 _noTabsController.reset([[NoTabsController alloc] initWithView:self.view]);
3260
3261 // Close the tools popup menu if it is open, as its contents/location were
3262 // specific to being in the tabs UI.
3263 [_toolbarController dismissToolsMenuPopup];
3264
3265 // Immediately hide the web, toolbar, and tabstrip.
3266 [[_toolbarController view] setHidden:YES];
3267 [[_tabStripController view] setHidden:YES];
3268
3269 // Set up the toggle switch animation, if needed.
3270 if ([self hasModeToggleSwitch]) {
3271 UIButton* animationStartButton = [_tabStripController modeToggleButton];
3272 [_noTabsController installAnimationImageForButton:animationStartButton
3273 inView:self.view
3274 show:YES];
3275 }
3276
3277 [_noTabsController prepareForShowAnimation];
3278 [UIView animateWithDuration:kNoTabsAnimationDuration
3279 animations:^{
3280 [_noTabsController showNoTabsUI];
3281 }
3282 completion:^(BOOL finished) {
3283 [_noTabsController showAnimationDidFinish];
3284 [_noTabsController setHasModeToggleSwitch:[self hasModeToggleSwitch]];
3285 }];
3286 }
3287
3288 // Hide the No-Tabs UI (restoring normal tab/web ui).
3289 - (void)dismissNoTabsUI {
3290 // The No-Tabs UI is only shown on tablet for non-incognito BVCs, so there is
3291 // no need to dismiss it for an incognito BVC.
3292 DCHECK(IsIPadIdiom());
3293 if (_isOffTheRecord)
3294 return;
3295
3296 // Set up the toggle switch animation, if needed.
3297 if ([self hasModeToggleSwitch]) {
3298 UIButton* animationEndButton = [_tabStripController modeToggleButton];
3299 [_noTabsController installAnimationImageForButton:animationEndButton
3300 inView:self.view
3301 show:NO];
3302 }
3303
3304 [_noTabsController prepareForDismissAnimation];
3305
3306 // Pull the controller out of the scoped_nsobject so the animation blocks can
3307 // retain it.
3308 NoTabsController* noTabsController = _noTabsController.get();
3309 [UIView animateWithDuration:kNoTabsAnimationDuration
3310 animations:^{
3311 [noTabsController dismissNoTabsUI];
3312 }
3313 completion:^(BOOL finished) {
3314 // When the animation is finished, remove all of the No-Tabs UI and
3315 // reshow the tabstrip, web toolbar, and web.
3316 [noTabsController dismissAnimationDidFinish];
3317 [[_toolbarController view] setHidden:NO];
3318 [[_tabStripController view] setHidden:NO];
3319 }];
3320 // Nullify the instance variable. The controller is retained by the animation.
3321 // Nullifying this variable prevents button press performed during the
3322 // animation to be routed to the noTabController.
3323 _noTabsController.reset();
3324 }
3325
3326 #pragma mark - Showing popups
3327
3328 - (void)showToolsMenuPopup {
3329 DCHECK(_browserState);
3330 DCHECK(self.visible || self.dismissingModal);
3331 DCHECK(!_noTabsController);
3332
3333 // Dismiss the omnibox (if open).
3334 [_toolbarController cancelOmniboxEdit];
3335 // Dismiss the soft keyboard (if open).
3336 [[_model currentTab].webController dismissKeyboard];
3337 // Dismiss Find in Page focus.
3338 [self updateFindBar:NO shouldFocus:NO];
3339
3340 base::scoped_nsobject<ToolsMenuContext> context(
3341 [[ToolsMenuContext alloc] initWithDisplayView:[self view]]);
3342 if ([_model count] == 0)
3343 [context setNoOpenedTabs:YES];
3344 if (_isOffTheRecord)
3345 [context setInIncognito:YES];
3346 if (reading_list::switches::IsReadingListEnabled()) {
3347 if (!_readingListMenuNotifier) {
3348 _readingListMenuNotifier.reset([[ReadingListMenuNotifier alloc]
3349 initWithReadingList:ReadingListModelFactory::GetForBrowserState(
3350 _browserState)]);
3351 }
3352 [context setReadingListMenuNotifier:_readingListMenuNotifier];
3353 }
3354
3355 [_toolbarController showToolsMenuPopupWithContext:context];
3356 ToolsPopupController* toolsPopupController =
3357 [_toolbarController toolsPopupController];
3358 if ([_model currentTab]) {
3359 BOOL isBookmarked = _toolbarModelIOS->IsCurrentTabBookmarked();
3360 [toolsPopupController setIsCurrentPageBookmarked:isBookmarked];
3361 [toolsPopupController setCanShowFindBar:self.canShowFindBar];
3362 [toolsPopupController setCanUseReaderMode:self.canUseReaderMode];
3363 [toolsPopupController
3364 setCanUseDesktopUserAgent:self.canUseDesktopUserAgent];
3365 [toolsPopupController setCanShowShareMenu:self.canShowShareMenu];
3366
3367 if (!IsIPadIdiom())
3368 [toolsPopupController setIsTabLoading:_toolbarModelIOS->IsLoading()];
3369 }
3370 }
3371
3372 - (void)showPageInfoPopupForView:(UIView*)sourceView {
3373 Tab* tab = [_model currentTab];
3374 DCHECK([tab navigationManager]);
3375 web::NavigationItem* navItem = [tab navigationManager]->GetVisibleItem();
3376
3377 // It is fully expected to have a navItem here, as showPageInfoPopup can only
3378 // be trigerred by a button enabled when a current item matches some
3379 // conditions. However a crash was seen were navItem was NULL hence this
3380 // test after a DCHECK.
3381 DCHECK(navItem);
3382 if (!navItem)
3383 return;
3384
3385 // Don't show if the page is native.
3386 if ([self isTabNativePage:tab])
3387 return;
3388
3389 base::RecordAction(UserMetricsAction("MobileToolbarPageSecurityInfo"));
3390
3391 // Dismiss the omnibox (if open).
3392 [_toolbarController cancelOmniboxEdit];
3393
3394 [[NSNotificationCenter defaultCenter]
3395 postNotificationName:ios_internal::kPageInfoWillShowNotification
3396 object:nil];
3397
3398 // TODO(rohitrao): Get rid of PageInfoModel completely.
3399 PageInfoModelBubbleBridge* bridge = new PageInfoModelBubbleBridge();
3400 PageInfoModel* pageInfoModel = new PageInfoModel(
3401 _browserState, navItem->GetURL(), navItem->GetSSL(), bridge);
3402
3403 UIView* view = [self view];
3404 _pageInfoController.reset([[PageInfoViewController alloc]
3405 initWithModel:pageInfoModel
3406 bridge:bridge
3407 sourceFrame:[sourceView convertRect:[sourceView bounds] toView:view]
3408 parentView:view]);
3409 bridge->set_controller(_pageInfoController.get());
3410 }
3411
3412 - (void)hidePageInfoPopupForView:(UIView*)sourceView {
3413 [_pageInfoController dismiss];
3414 _pageInfoController.reset();
3415 }
3416
3417 - (void)showSecurityHelpPage {
3418 [self webPageOrderedOpen:GURL(kPageInfoHelpCenterURL)
3419 referrer:web::Referrer()
3420 windowName:nil
3421 inBackground:NO
3422 appendTo:kCurrentTab];
3423 [self hidePageInfoPopupForView:nil];
3424 }
3425
3426 - (void)showTabHistoryPopupForBackwardHistory {
3427 DCHECK(self.visible || self.dismissingModal);
3428 DCHECK(!_noTabsController);
3429
3430 // Dismiss the omnibox (if open).
3431 [_toolbarController cancelOmniboxEdit];
3432 // Dismiss the soft keyboard (if open).
3433 Tab* tab = [_model currentTab];
3434 [tab.webController dismissKeyboard];
3435
3436 DCHECK([tab navigationManager]);
3437 CRWSessionController* sc = [tab navigationManager]->GetSessionController();
3438 [_toolbarController showTabHistoryPopupInView:[self view]
3439 withSessionEntries:[sc backwardEntries]
3440 forBackHistory:YES];
3441 }
3442
3443 - (void)showTabHistoryPopupForForwardHistory {
3444 DCHECK(self.visible || self.dismissingModal);
3445 DCHECK(!_noTabsController);
3446
3447 // Dismiss the omnibox (if open).
3448 [_toolbarController cancelOmniboxEdit];
3449 // Dismiss the soft keyboard (if open).
3450 Tab* tab = [_model currentTab];
3451 [tab.webController dismissKeyboard];
3452
3453 DCHECK([tab navigationManager]);
3454 CRWSessionController* sc = [tab navigationManager]->GetSessionController();
3455 [_toolbarController showTabHistoryPopupInView:[self view]
3456 withSessionEntries:[sc forwardEntries]
3457 forBackHistory:NO];
3458 }
3459
3460 - (void)navigateToSelectedEntry:(id)sender {
3461 DCHECK([sender isKindOfClass:[TabHistoryCell class]]);
3462 TabHistoryCell* selectedCell = (TabHistoryCell*)sender;
3463 [[_model currentTab] goToEntry:selectedCell.entry];
3464 [_toolbarController dismissTabHistoryPopup];
3465 }
3466
3467 - (void)print {
3468 Tab* currentTab = [_model currentTab];
3469 // The UI should prevent users from printing non-printable pages. However, a
3470 // redirection to an un-printable page can happen before it is reflected in
3471 // the UI.
3472 if (![currentTab viewForPrinting]) {
3473 [self showSnackbar:l10n_util::GetNSString(IDS_IOS_CANNOT_PRINT_PAGE_ERROR)];
3474 return;
3475 }
3476 DCHECK(_browserState);
3477 if (!_printController.get()) {
3478 _printController.reset([[PrintController alloc]
3479 initWithContextGetter:_browserState->GetRequestContext()]);
3480 }
3481 [_printController printView:[currentTab viewForPrinting]
3482 withTitle:[currentTab title]
3483 viewController:self];
3484 }
3485
3486 - (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title {
3487 if (!reading_list::switches::IsReadingListEnabled()) {
3488 return;
3489 }
3490 base::RecordAction(UserMetricsAction("MobileReadingListAdd"));
3491
3492 ReadingListModel* readingModel =
3493 ReadingListModelFactory::GetForBrowserState(_browserState);
3494 readingModel->AddEntry(URL, base::SysNSStringToUTF8(title));
3495
3496 NSString* snackBarMessage =
3497 l10n_util::GetNSString(IDS_IOS_READING_LIST_SNACKBAR_MESSAGE);
3498 MDCSnackbarMessage* message =
3499 [MDCSnackbarMessage messageWithText:snackBarMessage];
3500 message.category = kReadingListSnackbarCategory;
3501 [MDCSnackbarManager showMessage:message];
3502 }
3503
3504 #pragma mark - Keyboard commands management
3505
3506 - (BOOL)shouldRegisterKeyboardCommands {
3507 if ([self presentedViewController])
3508 return NO;
3509
3510 if (_voiceSearchController && _voiceSearchController->IsVisible())
3511 return NO;
3512
3513 // If there is no first responder, try to make the webview the first
3514 // responder.
3515 if (!GetFirstResponder()) {
3516 [_model.get().currentTab.webController.webViewProxy becomeFirstResponder];
3517 }
3518
3519 return YES;
3520 }
3521
3522 - (KeyCommandsProvider*)keyCommandsProvider {
3523 if (!_keyCommandsProvider) {
3524 _keyCommandsProvider.reset([_dependencyFactory newKeyCommandsProvider]);
3525 }
3526 return _keyCommandsProvider.get();
3527 }
3528
3529 #pragma mark - KeyCommandsPlumbing
3530
3531 - (BOOL)isOffTheRecord {
3532 return _isOffTheRecord;
3533 }
3534
3535 - (NSUInteger)tabsCount {
3536 return [_model count];
3537 }
3538
3539 - (void)focusTabAtIndex:(NSUInteger)index {
3540 if ([_model count] > index) {
3541 [_model setCurrentTab:[_model tabAtIndex:index]];
3542 }
3543 }
3544
3545 - (void)focusNextTab {
3546 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3547 NSInteger modelCount = [_model count];
3548 if (currentTabIndex < modelCount - 1) {
3549 Tab* nextTab = [_model tabAtIndex:currentTabIndex + 1];
3550 [_model setCurrentTab:nextTab];
3551 } else {
3552 [_model setCurrentTab:[_model tabAtIndex:0]];
3553 }
3554 }
3555
3556 - (void)focusPreviousTab {
3557 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3558 if (currentTabIndex > 0) {
3559 Tab* previousTab = [_model tabAtIndex:currentTabIndex - 1];
3560 [_model setCurrentTab:previousTab];
3561 } else {
3562 Tab* lastTab = [_model tabAtIndex:[_model count] - 1];
3563 [_model setCurrentTab:lastTab];
3564 }
3565 }
3566
3567 - (void)reopenClosedTab {
3568 sessions::TabRestoreService* const tabRestoreService =
3569 IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState);
3570 if (!tabRestoreService || tabRestoreService->entries().empty())
3571 return;
3572
3573 const std::unique_ptr<sessions::TabRestoreService::Entry>& entry =
3574 tabRestoreService->entries().front();
3575 // Only handle the TAB type.
3576 if (entry->type != sessions::TabRestoreService::TAB)
3577 return;
3578
3579 [self chromeExecuteCommand:[GenericChromeCommand commandWithTag:IDC_NEW_TAB]];
3580 TabRestoreServiceDelegateImplIOS* const delegate =
3581 TabRestoreServiceDelegateImplIOSFactory::GetForBrowserState(
3582 _browserState);
3583 tabRestoreService->RestoreEntryById(delegate, entry->id,
3584 WindowOpenDisposition::CURRENT_TAB);
3585 }
3586
3587 - (void)focusOmnibox {
3588 [_toolbarController focusOmnibox];
3589 }
3590
3591 #pragma mark - UIResponder
3592
3593 - (NSArray*)keyCommands {
3594 if (![self shouldRegisterKeyboardCommands]) {
3595 return nil;
3596 }
3597 return [self.keyCommandsProvider
3598 keyCommandsForConsumer:self
3599 editingText:![self isFirstResponder]];
3600 }
3601
3602 #pragma mark -
3603
3604 // Induce an intentional crash in the browser process.
3605 - (void)induceBrowserCrash {
3606 CHECK(false);
3607 // Call another function, so that the above CHECK can't be tail-call
3608 // optimized. This ensures that this method's name will show up in the stack
3609 // for easier identification.
3610 CHECK(true);
3611 }
3612
3613 - (void)loadURL:(const GURL&)url
3614 referrer:(const web::Referrer&)referrer
3615 transition:(ui::PageTransition)transition
3616 rendererInitiated:(BOOL)rendererInitiated {
3617 [[OmniboxGeolocationController sharedInstance]
3618 locationBarDidSubmitURL:url
3619 transition:transition
3620 browserState:_browserState];
3621
3622 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:YES];
3623 if (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) {
3624 new_tab_page_uma::RecordActionFromOmnibox(_browserState, url, transition);
3625 }
3626
3627 // NOTE: This check for the Crash Host URL is here to avoid the URL from
3628 // ending up in the history causign the app to crash at every subsequent
3629 // restart.
3630 if (url.host() == kChromeUIBrowserCrashHost) {
3631 [self induceBrowserCrash];
3632 // In debug the app can continue working even after the CHECK. Adding a
3633 // return avoids the crash url to be added to the history.
3634 return;
3635 }
3636
3637 if (url == [_preloadController prerenderedURL]) {
3638 Tab* oldTab = [_model currentTab];
3639 Tab* newTab = [_preloadController releasePrerenderContents];
3640 DCHECK(oldTab);
3641 DCHECK(newTab);
3642 if (oldTab && newTab) {
3643 [oldTab recordStateInHistory];
3644 DCHECK([newTab navigationManager]);
3645 CRWSessionController* newHistory =
3646 [newTab navigationManager]->GetSessionController();
3647 DCHECK([oldTab navigationManager]);
3648 CRWSessionController* oldHistory =
3649 [oldTab navigationManager]->GetSessionController();
3650 [newHistory insertStateFromSessionController:oldHistory];
3651 [[newTab nativeAppNavigationController]
3652 copyStateFrom:[oldTab nativeAppNavigationController]];
3653 [_model replaceTab:oldTab withTab:newTab keepOldTabOpen:NO];
3654
3655 // Set isPrerenderTab to NO after replacing the tab. This will allow the
3656 // BrowserViewController to detect that a pre-rendered tab is switched in,
3657 // and show the prerendering animation.
3658 newTab.isPrerenderTab = NO;
3659
3660 BOOL loadingFinished =
3661 [newTab.webController loadPhase] == web::PAGE_LOADED;
3662 [self tabLoadComplete:newTab withSuccess:loadingFinished];
3663
3664 return;
3665 }
3666 }
3667
3668 GURL urlToLoad = url;
3669 if ([_preloadController hasPrefetchedURL:url]) {
3670 // Prefetched URLs have modified URLs, so load the prefetched version of
3671 // |url| instead of the original |url|.
3672 urlToLoad = [_preloadController prefetchedURL];
3673 }
3674
3675 [_preloadController cancelPrerender];
3676
3677 // Some URLs are not allowed while in incognito. If we are in incognito and
3678 // load a disallowed URL, instead create a new tab not in the incognito state.
3679 if (_isOffTheRecord && !IsURLAllowedInIncognito(url)) {
3680 [self webPageOrderedOpen:url
3681 referrer:web::Referrer()
3682 windowName:nil
3683 inIncognito:NO
3684 inBackground:NO
3685 appendTo:kCurrentTab];
3686 return;
3687 }
3688
3689 web::NavigationManager::WebLoadParams params(urlToLoad);
3690 params.referrer = referrer;
3691 params.transition_type = transition;
3692 params.is_renderer_initiated = rendererInitiated;
3693 DCHECK([_model currentTab]);
3694 [[[_model currentTab] webController] loadWithParams:params];
3695 }
3696
3697 - (void)loadJavaScriptFromLocationBar:(NSString*)script {
3698 [_preloadController cancelPrerender];
3699 DCHECK([_model currentTab]);
3700 [[_model currentTab].webController executeUserJavaScript:script
3701 completionHandler:nil];
3702 }
3703
3704 - (web::WebState*)currentWebState {
3705 return [[_model currentTab] webState];
3706 }
3707
3708 // This is called from within an animation block.
3709 - (void)toolbarHeightChanged {
3710 if ([self headerHeight] != 0) {
3711 // Ensure full screen height is updated.
3712 Tab* currentTab = [_model currentTab];
3713 BOOL visible = self.isToolbarOnScreen;
3714 [currentTab updateFullscreenWithToolbarVisible:visible];
3715 }
3716 }
3717
3718 // Load a new URL on a new page/tab.
3719 - (void)webPageOrderedOpen:(const GURL&)URL
3720 referrer:(const web::Referrer&)referrer
3721 windowName:(NSString*)windowName
3722 inBackground:(BOOL)inBackground
3723 appendTo:(OpenPosition)appendTo {
3724 Tab* adjacentTab = nil;
3725 if (appendTo == kCurrentTab)
3726 adjacentTab = [_model currentTab];
3727 [_model insertOrUpdateTabWithURL:URL
3728 referrer:referrer
3729 transition:ui::PAGE_TRANSITION_LINK
3730 windowName:windowName
3731 opener:adjacentTab
3732 openedByDOM:NO
3733 atIndex:TabModelConstants::kTabPositionAutomatically
3734 inBackground:inBackground];
3735 }
3736
3737 - (void)webPageOrderedOpen:(const GURL&)url
3738 referrer:(const web::Referrer&)referrer
3739 windowName:(NSString*)windowName
3740 inIncognito:(BOOL)inIncognito
3741 inBackground:(BOOL)inBackground
3742 appendTo:(OpenPosition)appendTo {
3743 if (inIncognito == _isOffTheRecord) {
3744 [self webPageOrderedOpen:url
3745 referrer:referrer
3746 windowName:windowName
3747 inBackground:inBackground
3748 appendTo:appendTo];
3749 return;
3750 }
3751 // When sending an open command that switches modes, ensure the tab
3752 // ends up appended to the end of the model, not just next to what is
3753 // currently selected in the other mode. This is done with the |append|
3754 // parameter.
3755 base::scoped_nsobject<OpenUrlCommand> command([[OpenUrlCommand alloc]
3756 initWithURL:url
3757 referrer:web::Referrer() // Strip referrer when switching modes.
3758 windowName:windowName
3759 inIncognito:inIncognito
3760 inBackground:inBackground
3761 appendTo:kLastTab]);
3762 [self chromeExecuteCommand:command];
3763 }
3764
3765 - (void)loadSessionTab:(const sessions::SessionTab*)sessionTab {
3766 [[_model currentTab] loadSessionTab:sessionTab];
3767 }
3768
3769 - (void)openJavascript:(NSString*)javascript {
3770 [[_model currentTab] openJavascript:javascript];
3771 }
3772
3773 #pragma mark - WebToolbarDelegate methods
3774
3775 - (IBAction)locationBarDidBecomeFirstResponder:(id)sender {
3776 if (_locationBarHasFocus)
3777 return; // TODO(crbug.com/244366): This should not be necessary.
3778 _locationBarHasFocus = YES;
3779 [[NSNotificationCenter defaultCenter]
3780 postNotificationName:ios_internal::
3781 kLocationBarBecomesFirstResponderNotification
3782 object:nil];
3783 [_sideSwipeController setEnabled:NO];
3784 if ([[_model currentTab].webController wantsKeyboardShield]) {
3785 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
3786 [_typingShield setAlpha:0.0];
3787 [_typingShield setHidden:NO];
3788 [UIView animateWithDuration:0.3
3789 animations:^{
3790 [_typingShield setAlpha:1.0];
3791 }];
3792 }
3793 [[OmniboxGeolocationController sharedInstance]
3794 locationBarDidBecomeFirstResponder:_browserState];
3795 }
3796
3797 - (IBAction)locationBarDidResignFirstResponder:(id)sender {
3798 if (!_locationBarHasFocus)
3799 return; // TODO(crbug.com/244366): This should not be necessary.
3800 _locationBarHasFocus = NO;
3801 [_sideSwipeController setEnabled:YES];
3802 [[NSNotificationCenter defaultCenter]
3803 postNotificationName:ios_internal::
3804 kLocationBarResignsFirstResponderNotification
3805 object:nil];
3806 [UIView animateWithDuration:0.3
3807 animations:^{
3808 [_typingShield setAlpha:0.0];
3809 }
3810 completion:^(BOOL finished) {
3811 // This can happen if one quickly resigns the omnibox and then taps
3812 // on the omnibox again during this animation. If the animation is
3813 // interrupted and the toolbar controller is first responder, it's safe
3814 // to assume the |_typingShield| shouldn't be hidden here.
3815 if (!finished && [_toolbarController isOmniboxFirstResponder])
3816 return;
3817 [_typingShield setHidden:YES];
3818 }];
3819 [[OmniboxGeolocationController sharedInstance]
3820 locationBarDidResignFirstResponder:_browserState];
3821
3822 // If a load was cancelled by an omnibox edit, but nothing is loading when
3823 // editing ends (i.e., editing was cancelled), restart the cancelled load.
3824 if (_locationBarEditCancelledLoad) {
3825 _locationBarEditCancelledLoad = NO;
3826 if (!_toolbarModelIOS->IsLoading()) {
3827 [[_model currentTab] reload];
3828 }
3829 }
3830 }
3831
3832 - (IBAction)locationBarBeganEdit:(id)sender {
3833 // On handsets, if a page is currently loading it should be stopped.
3834 if (!IsIPadIdiom() && _toolbarModelIOS->IsLoading()) {
3835 base::scoped_nsobject<GenericChromeCommand> command(
3836 [[GenericChromeCommand alloc] initWithTag:IDC_STOP]);
3837 [self chromeExecuteCommand:command];
3838 _locationBarEditCancelledLoad = YES;
3839 }
3840 }
3841
3842 - (IBAction)prepareToEnterTabSwitcher:(id)sender {
3843 [[_model currentTab] updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
3844 }
3845
3846 - (ToolbarModelIOS*)toolbarModelIOS {
3847 return _toolbarModelIOS.get();
3848 }
3849
3850 - (void)updateToolbarBackgroundAlpha:(CGFloat)alpha {
3851 [_toolbarController setBackgroundAlpha:alpha];
3852 }
3853
3854 - (void)updateToolbarControlsAlpha:(CGFloat)alpha {
3855 [_toolbarController setControlsAlpha:alpha];
3856 }
3857
3858 - (void)willUpdateToolbarSnapshot {
3859 [[_model currentTab].overscrollActionsController clear];
3860 }
3861
3862 - (CardView*)addCardViewInFullscreen:(BOOL)fullScreen {
3863 CGRect frame = [_contentArea frame];
3864 if (!fullScreen) {
3865 // Changing the origin here is unnecessary, it's set in page_animation_util.
3866 frame.size.height -= [self headerHeight];
3867 }
3868
3869 CGFloat shortAxis = frame.size.width;
3870 CGFloat shortInset = kCardImageInsets.left + kCardImageInsets.right;
3871 shortAxis -= shortInset + 2 * ios_internal::page_animation_util::kCardMargin;
3872 CGFloat aspectRatio = frame.size.height / frame.size.width;
3873 CGFloat longAxis = std::floor(aspectRatio * shortAxis);
3874 CGFloat longInset = kCardImageInsets.top + kCardImageInsets.bottom;
3875 CGSize cardSize = CGSizeMake(shortAxis + shortInset, longAxis + longInset);
3876 CGRect cardFrame = {frame.origin, cardSize};
3877
3878 CardView* card =
3879 [[[CardView alloc] initWithFrame:cardFrame isIncognito:_isOffTheRecord]
3880 autorelease];
3881 card.closeButtonSide = IsPortrait() ? CardCloseButtonSide::TRAILING
3882 : CardCloseButtonSide::LEADING;
3883 [_contentArea addSubview:card];
3884 return card;
3885 }
3886
3887 #pragma mark - Command Handling
3888
3889 - (IBAction)chromeExecuteCommand:(id)sender {
3890 NSInteger command = [sender tag];
3891
3892 if (!_model || !_browserState)
3893 return;
3894
3895 switch (command) {
3896 case IDC_BACK:
3897 [[_model currentTab] goBack];
3898 break;
3899 case IDC_BOOKMARK_PAGE:
3900 [self initializeBookmarkInteractionController];
3901 [_bookmarkInteractionController
3902 presentBookmarkForTab:[_model currentTab]
3903 currentlyBookmarked:_toolbarModelIOS->IsCurrentTabBookmarkedByUser()
3904 inView:[_toolbarController bookmarkButtonView]
3905 originRect:[_toolbarController bookmarkButtonAnchorRect]];
3906 break;
3907 case IDC_CLOSE_TAB:
3908 [self closeCurrentTab];
3909 break;
3910 case IDC_FIND:
3911 [self initFindBarForTab];
3912 break;
3913 case IDC_FIND_NEXT:
3914 // TODO(crbug.com/603524): Reshow find bar if necessary.
3915 [[_model currentTab].findInPageController
3916 findNextStringInPageWithCompletionHandler:^{
3917 FindInPageModel* model =
3918 [_model currentTab].findInPageController.findInPageModel;
3919 [_findBarController updateResultsCount:model];
3920 }];
3921 break;
3922 case IDC_FIND_PREVIOUS:
3923 // TODO(crbug.com/603524): Reshow find bar if necessary.
3924 [[_model currentTab].findInPageController
3925 findPreviousStringInPageWithCompletionHandler:^{
3926 FindInPageModel* model =
3927 [_model currentTab].findInPageController.findInPageModel;
3928 [_findBarController updateResultsCount:model];
3929 }];
3930 break;
3931 case IDC_FIND_CLOSE:
3932 [self closeFindInPage];
3933 break;
3934 case IDC_FIND_UPDATE:
3935 [self searchFindInPage];
3936 break;
3937 case IDC_FORWARD:
3938 [[_model currentTab] goForward];
3939 break;
3940 case IDC_FULLSCREEN:
3941 NOTIMPLEMENTED();
3942 break;
3943 case IDC_HELP_PAGE_VIA_MENU:
3944 [self showHelpPage];
3945 break;
3946 case IDC_NEW_TAB:
3947 if (_isOffTheRecord) {
3948 // Not for this browser state, send it on its way.
3949 [super chromeExecuteCommand:sender];
3950 } else {
3951 [self newTab:sender];
3952 }
3953 break;
3954 case IDC_PRELOAD_VOICE_SEARCH:
3955 // Preload VoiceSearchController and views and view controllers needed
3956 // for voice search.
3957 [self ensureVoiceSearchControllerCreated];
3958 _voiceSearchController->PrepareToAppear();
3959 break;
3960 case IDC_NEW_INCOGNITO_TAB:
3961 if (_isOffTheRecord) {
3962 [self newTab:sender];
3963 } else {
3964 // Not for this browser state, send it on its way.
3965 [super chromeExecuteCommand:sender];
3966 }
3967 break;
3968 case IDC_RELOAD:
3969 [[_model currentTab] reload];
3970 break;
3971 case IDC_SHARE_PAGE:
3972 [self sharePage];
3973 break;
3974 case IDC_SHOW_MAIL_COMPOSER:
3975 [self showMailComposer:sender];
3976 break;
3977 case IDC_READER_MODE:
3978 [[_model currentTab] switchToReaderMode];
3979 break;
3980 case IDC_REQUEST_DESKTOP_SITE:
3981 [self enableDesktopUserAgent];
3982 break;
3983 case IDC_SHOW_TOOLS_MENU: {
3984 // TODO(blundell): Change this if/else to
3985 // |DCHECK(!_noTabsController)| if/when the no tabs controller
3986 // becomes part of the responder chain.
3987 // The no tabs controller's toolbar should open the menu when in the
3988 // no-tabs UI.
3989 if (_noTabsController.get())
3990 [_noTabsController showToolsMenuPopup];
3991 else
3992 [self showToolsMenuPopup];
3993 break;
3994 }
3995 case IDC_SHOW_BOOKMARK_MANAGER: {
3996 if (IsIPadIdiom()) {
3997 [self showAllBookmarks];
3998 } else {
3999 [self initializeBookmarkInteractionController];
4000 [_bookmarkInteractionController presentBookmarks];
4001 }
4002 break;
4003 }
4004 case IDC_SHOW_OTHER_DEVICES: {
4005 if (IsIPadIdiom()) {
4006 [self showNTPPanel:NewTabPage::kOpenTabsPanel];
4007 } else {
4008 UIViewController* controller = [RecentTabsPanelViewController
4009 controllerToPresentForBrowserState:_browserState
4010 loader:self];
4011 controller.modalPresentationStyle = UIModalPresentationFormSheet;
4012 controller.modalPresentationCapturesStatusBarAppearance = YES;
4013 [self presentViewController:controller animated:YES completion:nil];
4014 }
4015 break;
4016 }
4017 case IDC_STOP:
4018 [[_model currentTab] stopLoading];
4019 break;
4020 #if !defined(NDEBUG)
4021 case IDC_VIEW_SOURCE:
4022 [self viewSource];
4023 break;
4024 #endif
4025 case IDC_SHOW_PAGE_INFO:
4026 DCHECK([sender isKindOfClass:[UIButton class]]);
4027 [self showPageInfoPopupForView:sender];
4028 break;
4029 case IDC_HIDE_PAGE_INFO:
4030 [[NSNotificationCenter defaultCenter]
4031 postNotificationName:ios_internal::kPageInfoWillHideNotification
4032 object:nil];
4033 [self hidePageInfoPopupForView:sender];
4034 break;
4035 case IDC_SHOW_SECURITY_HELP:
4036 [self showSecurityHelpPage];
4037 break;
4038 case IDC_SHOW_BACK_HISTORY:
4039 [self showTabHistoryPopupForBackwardHistory];
4040 break;
4041 case IDC_SHOW_FORWARD_HISTORY:
4042 [self showTabHistoryPopupForForwardHistory];
4043 break;
4044 case IDC_BACK_FORWARD_IN_TAB_HISTORY:
4045 DCHECK([sender isKindOfClass:[TabHistoryCell class]]);
4046 [self navigateToSelectedEntry:sender];
4047 break;
4048 case IDC_PRINT:
4049 [self print];
4050 break;
4051 case IDC_ADD_READING_LIST: {
4052 DCHECK(reading_list::switches::IsReadingListEnabled());
4053 ReadingListAddCommand* command =
4054 base::mac::ObjCCastStrict<ReadingListAddCommand>(sender);
4055 [self addToReadingListURL:[command URL] title:[command title]];
4056 break;
4057 }
4058 case IDC_RATE_THIS_APP:
4059 [self showRateThisAppDialog];
4060 break;
4061 case IDC_SHOW_READING_LIST:
4062 DCHECK(reading_list::switches::IsReadingListEnabled());
4063 [self showReadingList];
4064 break;
4065 case IDC_VOICE_SEARCH:
4066 // If the voice search command is coming from a UIView sender, store it
4067 // before sending the command up the responder chain.
4068 if ([sender isKindOfClass:[UIView class]])
4069 _voiceSearchButton.reset(sender);
4070 [super chromeExecuteCommand:sender];
4071 break;
4072 case IDC_SHOW_QR_SCANNER:
4073 if (experimental_flags::IsQRCodeReaderEnabled()) {
4074 [self showQRScanner];
4075 }
4076 break;
4077 default:
4078 // Unknown commands get sent up the responder chain.
4079 [super chromeExecuteCommand:sender];
4080 break;
4081 }
4082 }
4083
4084 - (void)closeCurrentTab {
4085 Tab* currentTab = [_model currentTab];
4086 NSUInteger tabIndex = [_model indexOfTab:currentTab];
4087 if (tabIndex == NSNotFound)
4088 return;
4089
4090 // Take snapshot on iPad only if Tab switcher is enabled, if not just close
4091 // the tab.
4092 if (IsIPadIdiom() && !experimental_flags::IsTabSwitcherEnabled()) {
4093 [_model closeTabAtIndex:tabIndex];
4094 return;
4095 }
4096
4097 // Create image of tab for close animation.
4098 UIImageView* exitingPage = [self pageOpenCloseAnimationView];
4099 exitingPage.image =
4100 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4101
4102 // Close the actual tab, and add its image as a subview.
4103 [_model closeTabAtIndex:tabIndex];
4104
4105 // Do not animate close in iPad.
4106 if (!IsIPadIdiom()) {
4107 [_contentArea addSubview:exitingPage];
4108 ios_internal::page_animation_util::AnimateOutWithCompletion(
4109 exitingPage, 0, YES, IsPortrait(), ^{
4110 [exitingPage removeFromSuperview];
4111 });
4112 }
4113 }
4114
4115 - (ShareToData*)shareToDataForTab:(Tab*)tab {
4116 // For crash documented in crbug.com/503955, tab.url which is being passed
4117 // as a reference parameter caused a crash due to invalid address which
4118 // which suggests that |tab| may be deallocated along the way. Check that
4119 // tab is still valid by checking webState which would be deallocated if
4120 // tab is being closed.
4121 if (!tab.webState)
4122 return nil;
4123 DCHECK(tab);
4124 // If the original page title exists, it is expected to match the tab title.
4125 // If this ever changes, then a decision has to be made on which one should
4126 // be used for sharing.
4127 DCHECK(!tab.originalTitle || [tab.originalTitle isEqualToString:tab.title]);
4128 BOOL isPagePrintable = [tab viewForPrinting] != nil;
4129 return [[[ShareToData alloc] initWithURL:tab.url
4130 title:tab.title
4131 isOriginalTitle:(tab.originalTitle != nil)
4132 isPagePrintable:isPagePrintable] autorelease];
4133 }
4134
4135 - (void)sharePage {
4136 ShareToData* data = [self shareToDataForTab:[_model currentTab]];
4137 if (data)
4138 [self sharePageWithData:data];
4139 }
4140
4141 - (void)sharePageWithData:(ShareToData*)data {
4142 id<ShareProtocol> controller = [_dependencyFactory shareControllerInstance];
4143 if ([controller isActive])
4144 return;
4145 CGRect fromRect = [_toolbarController shareButtonAnchorRect];
4146 UIView* inView = [_toolbarController shareButtonView];
4147 [controller shareWithData:data
4148 controller:self
4149 browserState:_browserState
4150 shareToDelegate:self
4151 fromRect:fromRect
4152 inView:inView];
4153 }
4154
4155 - (void)clearPresentedStateWithCompletion:(ProceduralBlock)completion {
4156 [[_dependencyFactory shareControllerInstance] cancelShareAnimated:NO];
4157 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:NO];
4158 [_bookmarkInteractionController dismissSnackbar];
4159 [_toolbarController cancelOmniboxEdit];
4160 [_dialogPresenter cancelAllDialogs];
4161 [self hidePageInfoPopupForView:nil];
4162 if (_voiceSearchController)
4163 _voiceSearchController->DismissMicPermissionsHelp();
4164 [[_model currentTab] dismissModals];
4165 [[_model currentTab].findInPageController
4166 disableFindInPageWithCompletionHandler:^{
4167 [self updateFindBar:NO shouldFocus:NO];
4168 }];
4169 [_contextualSearchController movePanelOffscreen];
4170
4171 [_paymentRequestManager cancelRequest];
4172
4173 [_printController dismissAnimated:YES];
4174 _printController.reset();
4175 if (_noTabsController.get())
4176 [_noTabsController dismissToolsMenuPopup];
4177 else
4178 [_toolbarController dismissToolsMenuPopup];
4179 [_contextMenuCoordinator stop];
4180 [self dismissRateThisAppDialog];
4181
4182 if (self.presentedViewController) {
4183 // Dismisses any other modal controllers that may be present, e.g. Recent
4184 // Tabs.
4185 // Note that currently, some controllers like the bookmark ones were already
4186 // dismissed (in this example in -dismissBookmarkModalControllerAnimated:),
4187 // but are still reported as the presentedViewController. The result is that
4188 // this will call -dismissViewControllerAnimated:completion: a second time
4189 // on it. It is not per se an issue, as it is a no-op. The problem is that
4190 // in such a case, the completion block is not called.
4191 // To ensure the completion is called, nil is passed here, and the
4192 // completion is called below.
4193 [self dismissViewControllerAnimated:NO completion:nil];
4194 // Dismissed controllers will be so after a delay. Queue the completion
4195 // callback after that.
4196 if (completion) {
4197 dispatch_after(
4198 dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)),
4199 dispatch_get_main_queue(), ^{
4200 completion();
4201 });
4202 }
4203 } else if (completion) {
4204 // If no view controllers are presented, we should be ok with dispatching
4205 // the completion block directly.
4206 dispatch_async(dispatch_get_main_queue(), completion);
4207 }
4208 }
4209
4210 - (void)showHelpPage {
4211 GURL helpUrl(l10n_util::GetStringUTF16(IDS_IOS_TOOLS_MENU_HELP_URL));
4212 [self webPageOrderedOpen:helpUrl
4213 referrer:web::Referrer()
4214 windowName:nil
4215 inBackground:NO
4216 appendTo:kCurrentTab];
4217 }
4218
4219 - (void)enableDesktopUserAgent {
4220 [[_model currentTab] enableDesktopUserAgent];
4221 [[_model currentTab] reloadForDesktopUserAgent];
4222 }
4223
4224 - (void)resetAllWebViews {
4225 [_model resetAllWebViews];
4226 }
4227
4228 #pragma mark - Find Bar
4229
4230 - (void)hideFindBarWithAnimation:(BOOL)animate {
4231 [_findBarController hideFindBarView:animate];
4232 }
4233
4234 - (void)showFindBarWithAnimation:(BOOL)animate
4235 selectText:(BOOL)selectText
4236 shouldFocus:(BOOL)shouldFocus {
4237 DCHECK(_findBarController);
4238 Tab* tab = [_model currentTab];
4239 DCHECK(tab);
4240 CRWWebController* webController = tab.webController;
4241
4242 CGRect referenceFrame = CGRectZero;
4243 if (IsIPadIdiom()) {
4244 referenceFrame = webController.visibleFrame;
4245 referenceFrame.origin.y -= kIPadFindBarOverlap;
4246 } else {
4247 referenceFrame = _contentArea.frame;
4248 }
4249
4250 CGRect omniboxFrame = [_toolbarController visibleOmniboxFrame];
4251 [_findBarController addFindBarView:animate
4252 intoView:self.view
4253 withFrame:referenceFrame
4254 alignWithFrame:omniboxFrame
4255 selectText:selectText];
4256 [self updateFindBar:YES shouldFocus:shouldFocus];
4257 }
4258
4259 // Create find bar controller and pass it to the web controller.
4260 - (void)initFindBarForTab {
4261 if (!self.canShowFindBar)
4262 return;
4263
4264 if (!_findBarController)
4265 _findBarController.reset(
4266 [[FindBarControllerIOS alloc] initWithIncognito:_isOffTheRecord]);
4267
4268 Tab* tab = [_model currentTab];
4269 DCHECK(!tab.findInPageController.findInPageModel.enabled);
4270 tab.findInPageController.findInPageModel.enabled = YES;
4271 [self showFindBarWithAnimation:YES selectText:YES shouldFocus:YES];
4272 }
4273
4274 - (void)searchFindInPage {
4275 FindInPageController* findInPageController =
4276 [[_model currentTab] findInPageController];
4277 base::WeakNSObject<BrowserViewController> weakSelf(self);
4278 [findInPageController
4279 findStringInPage:[_findBarController searchTerm]
4280 completionHandler:^{
4281 FindInPageModel* model =
4282 [_model currentTab].findInPageController.findInPageModel;
4283 [_findBarController updateResultsCount:model];
4284 }];
4285 if (!_isOffTheRecord)
4286 [findInPageController saveSearchTerm];
4287 }
4288
4289 - (void)closeFindInPage {
4290 base::WeakNSObject<BrowserViewController> weakSelf(self);
4291 [[_model currentTab].findInPageController
4292 disableFindInPageWithCompletionHandler:^{
4293 [weakSelf updateFindBar:NO shouldFocus:NO];
4294 }];
4295 }
4296
4297 - (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus {
4298 FindInPageModel* model =
4299 [_model currentTab].findInPageController.findInPageModel;
4300 if (model.enabled) {
4301 if (initialUpdate && !_isOffTheRecord) {
4302 [[_model currentTab].findInPageController restoreSearchTerm];
4303 }
4304
4305 [self setFramesForHeaders:[self headerViews]
4306 atOffset:[self currentHeaderOffset]];
4307 [_findBarController updateView:model
4308 initialUpdate:initialUpdate
4309 focusTextfield:shouldFocus];
4310 } else {
4311 [self hideFindBarWithAnimation:YES];
4312 }
4313 }
4314
4315 - (void)showAllBookmarks {
4316 DCHECK(self.visible || self.dismissingModal);
4317 GURL URL(kChromeUIBookmarksURL);
4318 Tab* tab = [_model currentTab];
4319 web::NavigationManager::WebLoadParams params(URL);
4320 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
4321 [[tab webController] loadWithParams:params];
4322 }
4323
4324 - (void)showReadingList {
4325 DCHECK(reading_list::switches::IsReadingListEnabled());
4326 UIViewController* vc = [ReadingListViewControllerBuilder
4327 readingListViewControllerInBrowserState:self.browserState
4328 tabModel:_model];
4329 [self presentViewController:vc animated:YES completion:nil];
4330 }
4331
4332 - (void)showQRScanner {
4333 _qrScannerViewController.reset(
4334 [[QRScannerViewController alloc] initWithDelegate:_toolbarController]);
4335 [self presentViewController:[_qrScannerViewController
4336 getViewControllerToPresent]
4337 animated:YES
4338 completion:nil];
4339 }
4340
4341 - (void)showNTPPanel:(NewTabPage::PanelIdentifier)panel {
4342 DCHECK(self.visible || self.dismissingModal);
4343 GURL url(kChromeUINewTabURL);
4344 std::string fragment(NewTabPage::FragmentFromIdentifier(panel));
4345 if (fragment != "") {
4346 GURL::Replacements replacement;
4347 replacement.SetRefStr(fragment);
4348 url = url.ReplaceComponents(replacement);
4349 }
4350 Tab* tab = [_model currentTab];
4351 web::NavigationManager::WebLoadParams params(url);
4352 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
4353 [[tab webController] loadWithParams:params];
4354 }
4355
4356 - (void)showRateThisAppDialog {
4357 DCHECK(!_rateThisAppDialog.get());
4358
4359 // Store the current timestamp whenever this dialog is shown.
4360 _browserState->GetPrefs()->SetInt64(prefs::kRateThisAppDialogLastShownTime,
4361 base::Time::Now().ToInternalValue());
4362
4363 // Some versions of iOS7 do not support linking directly to the "Ratings and
4364 // Reviews" appstore page. For iOS7 fall back to an alternative URL that
4365 // links to the main appstore page for the Chrome app.
4366 NSURL* storeURL =
4367 [NSURL URLWithString:(@"itms-apps://itunes.apple.com/WebObjects/"
4368 @"MZStore.woa/wa/"
4369 @"viewContentsUserReviews?type=Purple+Software&id="
4370 @"535886823&pt=9008&ct=rating")];
4371
4372 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDialogShown"));
4373 [self clearPresentedStateWithCompletion:nil];
4374
4375 _rateThisAppDialog.reset(
4376 ios::GetChromeBrowserProvider()->CreateAppRatingPrompt());
4377 [_rateThisAppDialog setAppStoreURL:storeURL];
4378 [_rateThisAppDialog setDelegate:self];
4379 [_rateThisAppDialog show];
4380 }
4381
4382 - (void)dismissRateThisAppDialog {
4383 if (_rateThisAppDialog.get()) {
4384 base::RecordAction(base::UserMetricsAction(
4385 "IOSRateThisAppDialogDismissedProgramatically"));
4386 [_rateThisAppDialog dismiss];
4387 _rateThisAppDialog.reset();
4388 }
4389 }
4390
4391 #if !defined(NDEBUG)
4392 - (void)viewSource {
4393 Tab* tab = [_model currentTab];
4394 DCHECK(tab);
4395 CRWWebController* webController = tab.webController;
4396 NSString* script = @"document.documentElement.outerHTML;";
4397 base::WeakNSObject<Tab> weakTab(tab);
4398 web::JavaScriptResultBlock completionHandlerBlock = ^(id result, NSError*) {
4399 base::scoped_nsobject<Tab> strongTab([weakTab retain]);
4400 if (!strongTab)
4401 return;
4402 if (![result isKindOfClass:[NSString class]])
4403 result = @"Not an HTML page";
4404 std::string base64HTML;
4405 base::Base64Encode(base::SysNSStringToUTF8(result), &base64HTML);
4406 GURL URL(std::string("data:text/plain;charset=utf-8;base64,") + base64HTML);
4407 web::Referrer referrer([strongTab url], web::ReferrerPolicyDefault);
4408 [strongTab webPageOrderedOpen:URL
4409 referrer:referrer
4410 windowName:nil
4411 inBackground:NO];
4412 };
4413 [webController executeJavaScript:script
4414 completionHandler:completionHandlerBlock];
4415 }
4416 #endif // !defined(NDEBUG)
4417
4418 - (void)startVoiceSearch {
4419 // Delay Voice Search until new tab animations have finished.
4420 if (_inNewTabAnimation) {
4421 _startVoiceSearchAfterNewTabAnimation = YES;
4422 return;
4423 }
4424
4425 // Keyboard shouldn't overlay the ecoutez window, so dismiss find in page and
4426 // dismiss the keyboard.
4427 [self closeFindInPage];
4428 [[_model currentTab].webController dismissKeyboard];
4429
4430 // Ensure that voice search objects are created.
4431 [self ensureVoiceSearchControllerCreated];
4432 [self ensureVoiceSearchBarCreated];
4433
4434 // Present voice search.
4435 [_voiceSearchBar prepareToPresentVoiceSearch];
4436 _voiceSearchController->StartRecognition(self, [_model currentTab]);
4437 [_toolbarController cancelOmniboxEdit];
4438 }
4439
4440 #pragma mark - ToolbarOwner
4441
4442 - (ToolbarController*)relinquishedToolbarController {
4443 if (_isToolbarControllerRelinquished)
4444 return nil;
4445
4446 ToolbarController* relinquishedToolbarController = nil;
4447 if ([_toolbarController view].hidden) {
4448 Tab* currentTab = [_model currentTab];
4449 if (currentTab && UrlHasChromeScheme(currentTab.url)) {
4450 // Use the native content controller's toolbar when the BVC's is hidden.
4451 id nativeController = [self nativeControllerForTab:currentTab];
4452 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)]) {
4453 relinquishedToolbarController =
4454 [nativeController relinquishedToolbarController];
4455 _relinquishedToolbarOwner.reset(nativeController);
4456 }
4457 }
4458 } else {
4459 relinquishedToolbarController = _toolbarController.get();
4460 }
4461 _isToolbarControllerRelinquished = (relinquishedToolbarController != nil);
4462 return relinquishedToolbarController;
4463 }
4464
4465 - (void)reparentToolbarController {
4466 if (_isToolbarControllerRelinquished) {
4467 if ([[_toolbarController view] isDescendantOfView:self.view]) {
4468 // A native content controller's toolbar has been relinquished.
4469 [_relinquishedToolbarOwner reparentToolbarController];
4470 _relinquishedToolbarOwner.reset();
4471 } else if ([_findBarController isFindInPageShown]) {
4472 [self.view insertSubview:[_toolbarController view]
4473 belowSubview:[_findBarController view]];
4474 } else {
4475 [self.view addSubview:[_toolbarController view]];
4476 }
4477 if (_contextualSearchPanel) {
4478 // Move panel back into its correct place.
4479 [self.view insertSubview:_contextualSearchPanel
4480 aboveSubview:[_toolbarController view]];
4481 }
4482 _isToolbarControllerRelinquished = NO;
4483 }
4484 }
4485
4486 #pragma mark - TabModelObserver methods
4487
4488 // Observer method, tab inserted.
4489 - (void)tabModel:(TabModel*)model
4490 didInsertTab:(Tab*)tab
4491 atIndex:(NSUInteger)modelIndex
4492 inForeground:(BOOL)fg {
4493 DCHECK(tab);
4494 [self installDelegatesForTab:tab];
4495
4496 if (fg) {
4497 [_contextualSearchController setTab:tab];
4498 [_paymentRequestManager setWebState:tab.webState];
4499 }
4500 }
4501
4502 // Observer method, active tab changed.
4503 - (void)tabModel:(TabModel*)model
4504 didChangeActiveTab:(Tab*)newTab
4505 previousTab:(Tab*)previousTab
4506 atIndex:(NSUInteger)index {
4507 // TODO(rohitrao): tabSelected expects to always be called with a non-nil tab.
4508 // Currently this observer method is always called with a non-nil |newTab|,
4509 // but that may change in the future. Remove this DCHECK when it does.
4510 DCHECK(newTab);
4511 if (_infoBarContainer.get()) {
4512 infobars::InfoBarManager* infoBarManager = [newTab infoBarManager];
4513 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4514 }
4515 [self updateVoiceSearchBarVisibilityAnimated:NO];
4516
4517 [_contextualSearchController setTab:newTab];
4518 [_paymentRequestManager setWebState:newTab.webState];
4519
4520 [self tabSelected:newTab];
4521 DCHECK_EQ(newTab, [model currentTab]);
4522 [self installDelegatesForTab:newTab];
4523 }
4524
4525 // Observer method, tab changed.
4526 - (void)tabModel:(TabModel*)model didChangeTab:(Tab*)tab {
4527 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
4528 if (tab == [_model currentTab]) {
4529 [self updateToolbar];
4530 // Disable contextual search when |tab| is a voice search result tab.
4531 BOOL enableContextualSearch = self.active && !tab.isVoiceSearchResultsTab;
4532 [_contextualSearchController enableContextualSearch:enableContextualSearch];
4533 }
4534 }
4535
4536 // A tab has been removed, remove its views from display if necessary.
4537 - (void)tabModel:(TabModel*)model
4538 didRemoveTab:(Tab*)tab
4539 atIndex:(NSUInteger)index {
4540 // Remove stored native controllers for the tab.
4541 [_nativeControllersForTabIDs removeObjectForKey:tab.tabId];
4542
4543 // Ignore changes while the tab stack view is visible (or while suspended).
4544 // The display will be refreshed when this view becomes active again.
4545 if (!self.visible || !model.webUsageEnabled)
4546 return;
4547
4548 // Remove the find bar for now.
4549 [self hideFindBarWithAnimation:NO];
4550 }
4551
4552 - (void)tabModel:(TabModel*)model willRemoveTab:(Tab*)tab {
4553 if (tab == [model currentTab]) {
4554 [_contentArea displayContentView:nil];
4555 [_toolbarController selectedTabChanged];
4556 }
4557
4558 [[UpgradeCenter sharedInstance] tabWillClose:tab.tabId];
4559 if ([model count] == 1) { // About to remove the last tab.
4560 [_contextualSearchController setTab:nil];
4561 [_paymentRequestManager setWebState:nil];
4562 }
4563 }
4564
4565 // Called when the number of tabs changes. Update the toolbar accordingly.
4566 - (void)tabModelDidChangeTabCount:(TabModel*)model {
4567 DCHECK(model == _model);
4568 if ([_model count] == 1 && _noTabsController.get()) {
4569 [self dismissNoTabsUI];
4570 }
4571
4572 [_toolbarController setTabCount:[_model count]];
4573 // If the iPad tab switcher feature is enabled, the tab switcher is shown
4574 // instead of the no tabs UI. Showing the tab switcher in that case is
4575 // done from the main controller.
4576 // If the iPad tab switcher feature is disabled and the number of tabs is
4577 // zero, ensure the no-tabs UI is visible (such as if the last tab was closed
4578 // programmatically from JS). This is done on a delay because the
4579 // notification happens while the tab is going away and trying to trigger a
4580 // change during teardown causes problems.
4581 BOOL showNoTabsUIOnIPad =
4582 IsIPadIdiom() && !experimental_flags::IsTabSwitcherEnabled();
4583 if (![_model count] && showNoTabsUIOnIPad && !_isOffTheRecord) {
4584 [self performSelector:@selector(showNoTabsUI)
4585 withObject:nil
4586 afterDelay:0.01];
4587 }
4588 }
4589
4590 #pragma mark - Upgrade Detection
4591
4592 - (void)showUpgrade:(UpgradeCenter*)center {
4593 // Add an infobar on all the open tabs.
4594 for (Tab* tab in _model.get()) {
4595 NSString* tabId = tab.tabId;
4596 DCHECK([tab infoBarManager]);
4597 [center addInfoBarToManager:[tab infoBarManager] forTabId:tabId];
4598 }
4599 }
4600
4601 #pragma mark - ContextualSearchControllerDelegate
4602
4603 - (void)createTabFromContextualSearchController:(const GURL&)url {
4604 Tab* currentTab = [_model currentTab];
4605 DCHECK(currentTab);
4606 NSUInteger index = [_model indexOfTab:currentTab];
4607 [self addSelectedTabWithURL:url
4608 atIndex:index + 1
4609 transition:ui::PAGE_TRANSITION_LINK];
4610 }
4611
4612 - (void)promotePanelToTabProvidedBy:(id<ContextualSearchTabProvider>)tabProvider
4613 focusInput:(BOOL)focusInput {
4614 // Tell the panel it will be promoted.
4615 ContextualSearchPanelView* promotingPanel = _contextualSearchPanel;
4616 [promotingPanel prepareForPromotion];
4617
4618 // Make a new panel and tell the controller about it.
4619 _contextualSearchPanel = [self createPanelView];
4620 [self.view insertSubview:_contextualSearchPanel belowSubview:promotingPanel];
4621 [_contextualSearchController setPanel:_contextualSearchPanel];
4622
4623 // Figure out vertical offset.
4624 CGFloat offset = StatusBarHeight();
4625 if (IsIPadIdiom()) {
4626 offset = MAX(offset, CGRectGetMaxY([_tabStripController view].frame));
4627 }
4628
4629 // Transition steps: Animate the panel position, fade in the toolbar and
4630 // tab strip.
4631 ProceduralBlock transition = ^{
4632 [promotingPanel promoteToMatchSuperviewWithVerticalOffset:offset];
4633 [self updateToolbarControlsAlpha:1.0];
4634 [self updateToolbarBackgroundAlpha:1.0];
4635 [_tabStripController view].alpha = 1.0;
4636 };
4637
4638 // After the transition animation completes, add the tab to the tab model
4639 // (on iPad this triggers the tab strip animation too), then fade out the
4640 // transitioning panel and remove it.
4641 void (^completion)(BOOL) = ^(BOOL finished) {
4642 _contextualSearchMask.alpha = 0;
4643 Tab* newTab = [tabProvider releaseTab];
4644 DCHECK(newTab);
4645 DCHECK([newTab navigationManager]);
4646 // Add the new tab to the tab model.
4647 [newTab setParentTabModel:_model];
4648 // Insert the new tab one after the current tab.
4649 Tab* currentTab = [_model currentTab];
4650 NSUInteger index = [_model indexOfTab:currentTab];
4651 [_model insertTab:newTab atIndex:index + 1];
4652
4653 // Set isPrerenderTab to NO after inserting the tab. This will allow the
4654 // BrowserViewController to detect that a pre-rendered tab is switched in,
4655 // and show the prerendering animation. This needs to happen before the
4656 // tab is made the current tab.
4657 // This also enables contextual search (if otherwise applicable) on
4658 // |newTab|.
4659 newTab.isPrerenderTab = NO;
4660 [_model setCurrentTab:newTab];
4661
4662 BOOL loadingFinished = [newTab.webController loadPhase] == web::PAGE_LOADED;
4663 if (loadingFinished)
4664 [self tabLoadComplete:newTab withSuccess:YES];
4665
4666 if (focusInput) {
4667 [_toolbarController focusOmnibox];
4668 }
4669 _infoBarContainer->RestoreInfobars();
4670
4671 [UIView animateWithDuration:ios::material::kDuration2
4672 animations:^{
4673 promotingPanel.alpha = 0;
4674 }
4675 completion:^(BOOL finished) {
4676 [promotingPanel removeFromSuperview];
4677 }];
4678 };
4679
4680 [UIView animateWithDuration:ios::material::kDuration3
4681 animations:transition
4682 completion:completion];
4683 }
4684
4685 - (ContextualSearchPanelView*)createPanelView {
4686 PanelConfiguration* config;
4687 CGSize panelContainerSize = self.view.bounds.size;
4688 if (IsIPadIdiom()) {
4689 config = [PadPanelConfiguration
4690 configurationForContainerSize:panelContainerSize
4691 horizontalSizeClass:self.traitCollection.horizontalSizeClass];
4692 } else {
4693 config = [PhonePanelConfiguration
4694 configurationForContainerSize:panelContainerSize
4695 horizontalSizeClass:self.traitCollection.horizontalSizeClass];
4696 }
4697 ContextualSearchPanelView* newPanel = [[[ContextualSearchPanelView alloc]
4698 initWithConfiguration:config] autorelease];
4699 [newPanel addMotionObserver:self];
4700 [newPanel addMotionObserver:_contextualSearchMask];
4701 return newPanel;
4702 }
4703
4704 #pragma mark - ContextualSearchPanelMotionObserver
4705
4706 - (void)panel:(ContextualSearchPanelView*)panel
4707 didMoveWithMotion:(ContextualSearch::PanelMotion)motion {
4708 // If the header is offset, it's offscreen (or moving offscreen) and the
4709 // toolbar shouldn't be opacity-adjusted by the contextual search panel.
4710 if ([self currentHeaderOffset] != 0)
4711 return;
4712
4713 CGFloat toolbarAlpha;
4714
4715 if (motion.state == ContextualSearch::PREVIEWING) {
4716 // As the panel moves past the previewing position, the toolbar should
4717 // become more transparent.
4718 toolbarAlpha = 1 - motion.gradation;
4719 } else if (motion.state == ContextualSearch::COVERING) {
4720 // The toolbar should be totally transparent when the panel is covering.
4721 toolbarAlpha = 0.0;
4722 } else {
4723 return;
4724 }
4725
4726 // On iPad, the toolbar doesn't go fully transparent, so map |toolbarAlpha|'s
4727 // [0-1.0] range to [0.5-1.0].
4728 if (IsIPadIdiom()) {
4729 toolbarAlpha = 0.5 + (toolbarAlpha * 0.5);
4730 [_tabStripController view].alpha = toolbarAlpha;
4731 }
4732
4733 [self updateToolbarControlsAlpha:toolbarAlpha];
4734 [self updateToolbarBackgroundAlpha:toolbarAlpha];
4735 }
4736
4737 - (void)panel:(ContextualSearchPanelView*)panel
4738 didChangeToState:(ContextualSearch::PanelState)toState
4739 fromState:(ContextualSearch::PanelState)fromState {
4740 if (toState == ContextualSearch::DISMISSED) {
4741 // Panel has become hidden.
4742 _infoBarContainer->RestoreInfobars();
4743 [self updateToolbarControlsAlpha:1.0];
4744 [self updateToolbarBackgroundAlpha:1.0];
4745 [_tabStripController view].alpha = 1.0;
4746 } else if (fromState == ContextualSearch::DISMISSED) {
4747 // Panel has become visible.
4748 _infoBarContainer->SuspendInfobars();
4749 }
4750 }
4751
4752 - (void)panelWillPromote:(ContextualSearchPanelView*)panel {
4753 [panel removeMotionObserver:self];
4754 }
4755
4756 - (CGFloat)currentHeaderHeight {
4757 return [self headerHeight] - [self currentHeaderOffset];
4758 }
4759
4760 #pragma mark - InfoBarControllerDelegate
4761
4762 - (void)infoBarContainerStateChanged:(bool)isAnimating {
4763 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
4764 DCHECK(infoBarContainerView);
4765 CGRect containerFrame = infoBarContainerView.frame;
4766 CGFloat height = [infoBarContainerView topmostVisibleInfoBarHeight];
4767 containerFrame.origin.y = CGRectGetMaxY(_contentArea.frame) - height;
4768 containerFrame.size.height = height;
4769 BOOL isViewVisible = self.visible;
4770 [UIView animateWithDuration:0.1
4771 animations:^{
4772 [infoBarContainerView setFrame:containerFrame];
4773 }
4774 completion:^(BOOL finished) {
4775 if (!isViewVisible)
4776 return;
4777 UIAccessibilityPostNotification(
4778 UIAccessibilityLayoutChangedNotification, infoBarContainerView);
4779 }];
4780 }
4781
4782 - (BOOL)shouldAutorotate {
4783 if (_voiceSearchController && _voiceSearchController->IsVisible()) {
4784 // Don't rotate if a voice search is being presented or dismissed. Once the
4785 // transition animations finish, only the Voice Search UIViewController's
4786 // |-shouldAutorotate| will be called.
4787 return NO;
4788 } else if (_sideSwipeController && ![_sideSwipeController shouldAutorotate]) {
4789 // Don't auto rotate if side swipe controller view says not to.
4790 return NO;
4791 } else {
4792 return [super shouldAutorotate];
4793 }
4794 }
4795
4796 // Always return yes, as this tap should work with various recognizers,
4797 // including UITextTapRecognizer, UILongPressGestureRecognizer,
4798 // UIScrollViewPanGestureRecognizer and others.
4799 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
4800 shouldRecognizeSimultaneouslyWithGestureRecognizer:
4801 (UIGestureRecognizer*)otherGestureRecognizer {
4802 return YES;
4803 }
4804
4805 // Tap gestures should only be recognized within |_contentArea|.
4806 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gesture {
4807 CGPoint location = [gesture locationInView:self.view];
4808
4809 // Only allow touches on descendant views of |_contentArea|.
4810 UIView* hitView = [self.view hitTest:location withEvent:nil];
4811 return (![hitView isDescendantOfView:_contentArea]) ? NO : YES;
4812 }
4813
4814 #pragma mark - SideSwipeController Delegate Methods
4815
4816 - (void)sideSwipeViewDismissAnimationDidEnd:(UIView*)sideSwipeView {
4817 DCHECK(!IsIPadIdiom());
4818 // Update frame incase orientation changed while |_contentArea| was out of
4819 // the view hierarchy.
4820 [_contentArea setFrame:[sideSwipeView frame]];
4821
4822 [self.view insertSubview:_contentArea atIndex:0];
4823 [self updateVoiceSearchBarVisibilityAnimated:NO];
4824 [self updateToolbar];
4825
4826 // Reset horizontal stack view.
4827 [sideSwipeView removeFromSuperview];
4828 [_sideSwipeController setInSwipe:NO];
4829 [_infoBarContainer->view() setHidden:NO];
4830 }
4831
4832 - (UIView*)contentView {
4833 return _contentArea;
4834 }
4835
4836 - (TabStripController*)tabStripController {
4837 return _tabStripController;
4838 }
4839
4840 - (WebToolbarController*)toolbarController {
4841 return _toolbarController;
4842 }
4843
4844 - (BOOL)preventSideSwipe {
4845 if ([_toolbarController toolsPopupController])
4846 return YES;
4847
4848 if (_voiceSearchController && _voiceSearchController->IsVisible())
4849 return YES;
4850
4851 if ([_contextualSearchPanel state] >= ContextualSearch::PEEKING)
4852 return YES;
4853
4854 if (!self.active)
4855 return YES;
4856
4857 return NO;
4858 }
4859
4860 - (void)updateAccessoryViewsForSideSwipeWithVisibility:(BOOL)visible {
4861 if (visible) {
4862 [self updateVoiceSearchBarVisibilityAnimated:NO];
4863 [self updateToolbar];
4864 [_infoBarContainer->view() setHidden:NO];
4865 } else {
4866 // Hide UI accessories such as find bar and first visit overlays
4867 // for welcome page.
4868 [self hideFindBarWithAnimation:NO];
4869 [_infoBarContainer->view() setHidden:YES];
4870 [_voiceSearchBar setHidden:YES];
4871 }
4872 }
4873
4874 - (BOOL)verifyToolbarViewPlacementInView:(UIView*)views {
4875 BOOL seenToolbar = NO;
4876 BOOL seenInfoBarContainer = NO;
4877 BOOL seenContentArea = NO;
4878 for (UIView* view in views.subviews) {
4879 if (view == [_toolbarController view])
4880 seenToolbar = YES;
4881 else if (view == _infoBarContainer->view())
4882 seenInfoBarContainer = YES;
4883 else if (view == _contentArea)
4884 seenContentArea = YES;
4885 if ((seenToolbar && !seenInfoBarContainer) ||
4886 (seenInfoBarContainer && !seenContentArea))
4887 return NO;
4888 }
4889 return YES;
4890 }
4891
4892 #pragma mark - PreloadControllerDelegate methods
4893
4894 - (BOOL)shouldUseDesktopUserAgent {
4895 return [_model currentTab].useDesktopUserAgent;
4896 }
4897
4898 - (CRWSessionEntry*)currentSessionEntry {
4899 Tab* tab = [_model currentTab];
4900 if (![tab navigationManager])
4901 return nil;
4902 return [[tab navigationManager]->GetSessionController() currentEntry];
4903 }
4904
4905 #pragma mark - BookmarkBridgeMethods
4906
4907 // If an added or removed bookmark is the same as the current url, update the
4908 // toolbar so the star highlight is kept in sync.
4909 - (void)bookmarkNodeModified:(const BookmarkNode*)node {
4910 if ([_model currentTab] && node->url() == [_model currentTab].url)
4911 [self updateToolbar];
4912 }
4913
4914 // If all bookmarks are removed, update the toolbar so the star highlight is
4915 // kept in sync.
4916 - (void)allBookmarksRemoved {
4917 [self updateToolbar];
4918 }
4919
4920 #pragma mark - ShareToDelegate methods
4921
4922 - (void)shareDidComplete:(ShareTo::ShareResult)shareStatus
4923 successMessage:(NSString*)message {
4924 // The shareTo dialog dismisses itself instead of through
4925 // |-dismissViewControllerAnimated:completion:| so we must reset the
4926 // presenting state here.
4927 self.presenting = NO;
4928 [self.dialogPresenter tryToPresent];
4929
4930 switch (shareStatus) {
4931 case ShareTo::SHARE_SUCCESS:
4932 if ([message length])
4933 [self showSnackbar:message];
4934 break;
4935 case ShareTo::SHARE_ERROR:
4936 [self showErrorAlert:IDS_IOS_SHARE_TO_ERROR_ALERT_TITLE
4937 message:IDS_IOS_SHARE_TO_ERROR_ALERT];
4938 break;
4939 case ShareTo::SHARE_NETWORK_FAILURE:
4940 [self showErrorAlert:IDS_IOS_SHARE_TO_NETWORK_ERROR_ALERT_TITLE
4941 message:IDS_IOS_SHARE_TO_NETWORK_ERROR_ALERT];
4942 break;
4943 case ShareTo::SHARE_SIGN_IN_FAILURE:
4944 [self showErrorAlert:IDS_IOS_SHARE_TO_SIGN_IN_ERROR_ALERT_TITLE
4945 message:IDS_IOS_SHARE_TO_SIGN_IN_ERROR_ALERT];
4946 break;
4947 case ShareTo::SHARE_CANCEL:
4948 case ShareTo::SHARE_UNKNOWN_RESULT:
4949 break;
4950 }
4951 }
4952
4953 - (void)passwordAppExDidFinish:(ShareTo::ShareResult)shareStatus
4954 username:(NSString*)username
4955 password:(NSString*)password
4956 successMessage:(NSString*)message {
4957 switch (shareStatus) {
4958 case ShareTo::SHARE_SUCCESS: {
4959 PasswordController* passwordController =
4960 [[_model currentTab] passwordController];
4961 __block BOOL shown = NO;
4962 [passwordController findAndFillPasswordForms:username
4963 password:password
4964 completionHandler:^(BOOL completed) {
4965 if (shown || !completed || ![message length])
4966 return;
4967 [self showSnackbar:message];
4968 shown = YES;
4969 }];
4970 break;
4971 }
4972 default:
4973 break;
4974 }
4975 }
4976
4977 - (void)showErrorAlert:(int)titleMessageId message:(int)messageId {
4978 NSString* title = l10n_util::GetNSString(titleMessageId);
4979 NSString* message = l10n_util::GetNSString(messageId);
4980 [self showErrorAlertWithStringTitle:title message:message];
4981 }
4982
4983 - (void)showErrorAlertWithStringTitle:(NSString*)title
4984 message:(NSString*)message {
4985 // Dismiss current alert.
4986 [_alertCoordinator stop];
4987
4988 _alertCoordinator.reset(
4989 [[_dependencyFactory alertCoordinatorWithTitle:title
4990 message:message
4991 viewController:self] retain]);
4992 [_alertCoordinator start];
4993 }
4994
4995 - (void)showSnackbar:(NSString*)message {
4996 [_dependencyFactory showSnackbarWithMessage:message];
4997 }
4998
4999 #pragma mark - Show Mail Composer methods
5000
5001 - (void)showMailComposer:(id)sender {
5002 ShowMailComposerCommand* command = (ShowMailComposerCommand*)sender;
5003 if (![MFMailComposeViewController canSendMail]) {
5004 NSString* alertTitle =
5005 l10n_util::GetNSString([command emailNotConfiguredAlertTitleId]);
5006 NSString* alertMessage =
5007 l10n_util::GetNSString([command emailNotConfiguredAlertMessageId]);
5008 [self showErrorAlertWithStringTitle:alertTitle message:alertMessage];
5009 return;
5010 }
5011 base::scoped_nsobject<MFMailComposeViewController> mailViewController(
5012 [[MFMailComposeViewController alloc] init]);
5013 [mailViewController setModalPresentationStyle:UIModalPresentationFormSheet];
5014 [mailViewController setToRecipients:[command toRecipients]];
5015 [mailViewController setSubject:[command subject]];
5016 [mailViewController setMessageBody:[command body] isHTML:NO];
5017
5018 const base::FilePath& textFile = [command textFileToAttach];
5019 if (!textFile.empty()) {
5020 NSString* filename = base::SysUTF8ToNSString(textFile.value());
5021 NSData* data = [NSData dataWithContentsOfFile:filename];
5022 if (data) {
5023 NSString* displayName =
5024 base::SysUTF8ToNSString(textFile.BaseName().value());
5025 [mailViewController addAttachmentData:data
5026 mimeType:@"text/plain"
5027 fileName:displayName];
5028 }
5029 }
5030
5031 [mailViewController setMailComposeDelegate:self];
5032 [self presentViewController:mailViewController animated:YES completion:nil];
5033 }
5034
5035 #pragma mark - MFMailComposeViewControllerDelegate methods
5036
5037 - (void)mailComposeController:(MFMailComposeViewController*)controller
5038 didFinishWithResult:(MFMailComposeResult)result
5039 error:(NSError*)error {
5040 [self dismissViewControllerAnimated:YES completion:nil];
5041 }
5042
5043 #pragma mark - StoreKitLauncher methods
5044
5045 - (void)productViewControllerDidFinish:
5046 (SKStoreProductViewController*)viewController {
5047 [self dismissViewControllerAnimated:YES completion:nil];
5048 }
5049
5050 - (void)openAppStore:(NSString*)appId {
5051 if (![appId length])
5052 return;
5053 NSDictionary* product =
5054 @{SKStoreProductParameterITunesItemIdentifier : appId};
5055 base::scoped_nsobject<SKStoreProductViewController> storeViewController(
5056 [[SKStoreProductViewController alloc] init]);
5057 [storeViewController setDelegate:self];
5058 [storeViewController loadProductWithParameters:product completionBlock:nil];
5059 [self presentViewController:storeViewController animated:YES completion:nil];
5060 }
5061
5062 #pragma mark - TabDialogDelegate methods
5063
5064 - (void)tab:(Tab*)tab
5065 runAuthDialogForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
5066 proposedCredential:(NSURLCredential*)credential
5067 completionHandler:
5068 (void (^)(NSString* user, NSString* password))handler {
5069 [self.dialogPresenter runAuthDialogForProtectionSpace:protectionSpace
5070 proposedCredential:credential
5071 webState:tab.webState
5072 completionHandler:handler];
5073 }
5074
5075 - (void)cancelDialogForTab:(Tab*)tab {
5076 [self.dialogPresenter cancelDialogForWebState:tab.webState];
5077 }
5078
5079 #pragma mark - FKFeedbackPromptDelegate methods
5080
5081 - (void)userTappedRateApp:(UIView*)view {
5082 base::RecordAction(base::UserMetricsAction("IOSRateThisAppRateChosen"));
5083 _rateThisAppDialog.reset();
5084 }
5085
5086 - (void)userTappedSendFeedback:(UIView*)view {
5087 base::RecordAction(base::UserMetricsAction("IOSRateThisAppFeedbackChosen"));
5088 _rateThisAppDialog.reset();
5089 base::scoped_nsobject<GenericChromeCommand> command(
5090 [[GenericChromeCommand alloc] initWithTag:IDC_REPORT_AN_ISSUE]);
5091 [self chromeExecuteCommand:command];
5092 }
5093
5094 - (void)userTappedDismiss:(UIView*)view {
5095 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDismissChosen"));
5096 _rateThisAppDialog.reset();
5097 }
5098
5099 #pragma mark - VoiceSearchBarDelegate
5100
5101 - (BOOL)isTTSEnabledForVoiceSearchBar:(id<VoiceSearchBar>)voiceSearchBar {
5102 DCHECK_EQ(_voiceSearchBar.get(), voiceSearchBar);
5103 [self ensureVoiceSearchControllerCreated];
5104 return _voiceSearchController->IsTextToSpeechEnabled() &&
5105 _voiceSearchController->IsTextToSpeechSupported();
5106 }
5107
5108 - (void)voiceSearchBarDidUpdateButtonState:(id<VoiceSearchBar>)voiceSearchBar {
5109 DCHECK_EQ(_voiceSearchBar.get(), voiceSearchBar);
5110 [self.tabModel.currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
5111 }
5112
5113 #pragma mark - VoiceSearchPresenter
5114
5115 - (UIView*)voiceSearchButton {
5116 return _voiceSearchButton;
5117 }
5118
5119 - (id<LogoAnimationControllerOwner>)logoAnimationControllerOwner {
5120 return [self currentLogoAnimationControllerOwner];
5121 }
5122
5123 @end
OLDNEW
« no previous file with comments | « ios/chrome/browser/ui/browser_view_controller.h ('k') | ios/chrome/browser/ui/browser_view_controller_dependency_factory.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698