OLD | NEW |
(Empty) | |
| 1 // Copyright 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import "ios/chrome/browser/ui/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 |
OLD | NEW |