| // Copyright 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/chrome/browser/ui/browser_view/browser_view_controller.h" |
| #import "ios/chrome/browser/ui/browser_view/browser_view_controller+delegates.h" |
| #import "ios/chrome/browser/ui/browser_view/browser_view_controller+private.h" |
| |
| #import <MaterialComponents/MaterialSnackbar.h> |
| |
| #import "base/mac/bundle_locations.h" |
| #import "base/mac/foundation_util.h" |
| #import "base/metrics/histogram_macros.h" |
| #import "base/metrics/user_metrics.h" |
| #import "base/metrics/user_metrics_action.h" |
| #import "base/strings/sys_string_conversions.h" |
| #import "components/feature_engagement/public/event_constants.h" |
| #import "components/feature_engagement/public/tracker.h" |
| #import "components/omnibox/browser/location_bar_model_impl.h" |
| #import "components/reading_list/core/reading_list_model.h" |
| #import "components/sessions/core/tab_restore_service_helper.h" |
| #import "components/signin/ios/browser/active_state_manager.h" |
| #import "components/strings/grit/components_strings.h" |
| #import "components/translate/core/browser/translate_manager.h" |
| #import "components/ukm/ios/ukm_url_recorder.h" |
| #import "ios/chrome/app/application_delegate/app_state.h" |
| #import "ios/chrome/browser/application_context.h" |
| #import "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| #import "ios/chrome/browser/chrome_url_constants.h" |
| #import "ios/chrome/browser/crash_report/crash_keys_helper.h" |
| #import "ios/chrome/browser/discover_feed/feed_constants.h" |
| #import "ios/chrome/browser/feature_engagement/tracker_factory.h" |
| #import "ios/chrome/browser/feature_engagement/tracker_util.h" |
| #import "ios/chrome/browser/find_in_page/find_tab_helper.h" |
| #import "ios/chrome/browser/infobars/infobar_manager_impl.h" |
| #import "ios/chrome/browser/main/browser.h" |
| #import "ios/chrome/browser/metrics/new_tab_page_uma.h" |
| #import "ios/chrome/browser/metrics/tab_usage_recorder_browser_agent.h" |
| #import "ios/chrome/browser/ntp/new_tab_page_tab_helper.h" |
| #import "ios/chrome/browser/ntp/new_tab_page_tab_helper_delegate.h" |
| #import "ios/chrome/browser/overscroll_actions/overscroll_actions_tab_helper.h" |
| #import "ios/chrome/browser/passwords/password_controller.h" |
| #import "ios/chrome/browser/prerender/preload_controller_delegate.h" |
| #import "ios/chrome/browser/prerender/prerender_service.h" |
| #import "ios/chrome/browser/prerender/prerender_service_factory.h" |
| #import "ios/chrome/browser/reading_list/offline_page_tab_helper.h" |
| #import "ios/chrome/browser/reading_list/reading_list_model_factory.h" |
| #import "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h" |
| #import "ios/chrome/browser/sessions/session_restoration_browser_agent.h" |
| #import "ios/chrome/browser/signin/authentication_service.h" |
| #import "ios/chrome/browser/signin/authentication_service_factory.h" |
| #import "ios/chrome/browser/snapshots/snapshot_tab_helper.h" |
| #import "ios/chrome/browser/ssl/captive_portal_tab_helper.h" |
| #import "ios/chrome/browser/ssl/captive_portal_tab_helper_delegate.h" |
| #import "ios/chrome/browser/tabs/tab_title_util.h" |
| #import "ios/chrome/browser/translate/chrome_ios_translate_client.h" |
| #import "ios/chrome/browser/ui/authentication/re_signin_infobar_delegate.h" |
| #import "ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.h" |
| #import "ios/chrome/browser/ui/browser_container/browser_container_view_controller.h" |
| #import "ios/chrome/browser/ui/browser_view/browser_view_controller_dependency_factory.h" |
| #import "ios/chrome/browser/ui/browser_view/browser_view_controller_helper.h" |
| #import "ios/chrome/browser/ui/browser_view/key_commands_provider.h" |
| #import "ios/chrome/browser/ui/bubble/bubble_presenter.h" |
| #import "ios/chrome/browser/ui/bubble/bubble_presenter_delegate.h" |
| #import "ios/chrome/browser/ui/commands/browser_commands.h" |
| #import "ios/chrome/browser/ui/commands/command_dispatcher.h" |
| #import "ios/chrome/browser/ui/commands/help_commands.h" |
| #import "ios/chrome/browser/ui/commands/load_query_commands.h" |
| #import "ios/chrome/browser/ui/commands/reading_list_add_command.h" |
| #import "ios/chrome/browser/ui/commands/show_signin_command.h" |
| #import "ios/chrome/browser/ui/commands/text_zoom_commands.h" |
| #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_feature.h" |
| #import "ios/chrome/browser/ui/content_suggestions/ntp_home_constant.h" |
| #import "ios/chrome/browser/ui/default_promo/default_browser_promo_non_modal_scheduler.h" |
| #import "ios/chrome/browser/ui/default_promo/default_promo_non_modal_presentation_delegate.h" |
| #import "ios/chrome/browser/ui/download/download_manager_coordinator.h" |
| #import "ios/chrome/browser/ui/first_run/first_run_util.h" |
| #import "ios/chrome/browser/ui/first_run/welcome_to_chrome_view_controller.h" |
| #import "ios/chrome/browser/ui/fullscreen/fullscreen_animator.h" |
| #import "ios/chrome/browser/ui/fullscreen/fullscreen_features.h" |
| #import "ios/chrome/browser/ui/fullscreen/fullscreen_ui_element.h" |
| #import "ios/chrome/browser/ui/fullscreen/fullscreen_ui_updater.h" |
| #import "ios/chrome/browser/ui/fullscreen/scoped_fullscreen_disabler.h" |
| #import "ios/chrome/browser/ui/gestures/view_revealing_vertical_pan_handler.h" |
| #import "ios/chrome/browser/ui/incognito_reauth/incognito_reauth_commands.h" |
| #import "ios/chrome/browser/ui/incognito_reauth/incognito_reauth_scene_agent.h" |
| #import "ios/chrome/browser/ui/incognito_reauth/incognito_reauth_view.h" |
| #import "ios/chrome/browser/ui/infobars/infobar_positioner.h" |
| #import "ios/chrome/browser/ui/location_bar/location_bar_model_delegate_ios.h" |
| #import "ios/chrome/browser/ui/main/scene_state.h" |
| #import "ios/chrome/browser/ui/main/scene_state_browser_agent.h" |
| #import "ios/chrome/browser/ui/main_content/main_content_ui.h" |
| #import "ios/chrome/browser/ui/main_content/main_content_ui_broadcasting_util.h" |
| #import "ios/chrome/browser/ui/main_content/main_content_ui_state.h" |
| #import "ios/chrome/browser/ui/main_content/web_scroll_view_main_content_ui_forwarder.h" |
| #import "ios/chrome/browser/ui/ntp/new_tab_page_coordinator.h" |
| #import "ios/chrome/browser/ui/ntp/ntp_util.h" |
| #import "ios/chrome/browser/ui/omnibox/popup/omnibox_popup_presenter.h" |
| #import "ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.h" |
| #import "ios/chrome/browser/ui/presenters/vertical_animation_container.h" |
| #import "ios/chrome/browser/ui/send_tab_to_self/send_tab_to_self_coordinator.h" |
| #import "ios/chrome/browser/ui/settings/sync/utils/sync_util.h" |
| #import "ios/chrome/browser/ui/side_swipe/side_swipe_controller.h" |
| #import "ios/chrome/browser/ui/side_swipe/swipe_view.h" |
| #import "ios/chrome/browser/ui/start_surface/start_surface_features.h" |
| #import "ios/chrome/browser/ui/sync/utils/features.h" |
| #import "ios/chrome/browser/ui/tab_switcher/tab_strip/tab_strip_coordinator.h" |
| #import "ios/chrome/browser/ui/tabs/background_tab_animation_view.h" |
| #import "ios/chrome/browser/ui/tabs/foreground_tab_animation_view.h" |
| #import "ios/chrome/browser/ui/tabs/requirements/tab_strip_presentation.h" |
| #import "ios/chrome/browser/ui/tabs/switch_to_tab_animation_view.h" |
| #import "ios/chrome/browser/ui/tabs/tab_strip_constants.h" |
| #import "ios/chrome/browser/ui/tabs/tab_strip_containing.h" |
| #import "ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.h" |
| #import "ios/chrome/browser/ui/toolbar/accessory/toolbar_accessory_presenter.h" |
| #import "ios/chrome/browser/ui/toolbar/adaptive_toolbar_coordinator.h" |
| #import "ios/chrome/browser/ui/toolbar/adaptive_toolbar_view_controller.h" |
| #import "ios/chrome/browser/ui/toolbar/fullscreen/toolbar_ui.h" |
| #import "ios/chrome/browser/ui/toolbar/fullscreen/toolbar_ui_broadcasting_util.h" |
| #import "ios/chrome/browser/ui/toolbar/primary_toolbar_coordinator.h" |
| #import "ios/chrome/browser/ui/toolbar/public/toolbar_constants.h" |
| #import "ios/chrome/browser/ui/toolbar/public/toolbar_utils.h" |
| #import "ios/chrome/browser/ui/toolbar/secondary_toolbar_coordinator.h" |
| #import "ios/chrome/browser/ui/toolbar/toolbar_coordinator_adaptor.h" |
| #import "ios/chrome/browser/ui/toolbar_container/toolbar_container_coordinator.h" |
| #import "ios/chrome/browser/ui/toolbar_container/toolbar_container_features.h" |
| #import "ios/chrome/browser/ui/ui_feature_flags.h" |
| #import "ios/chrome/browser/ui/util/keyboard_observer_helper.h" |
| #import "ios/chrome/browser/ui/util/named_guide.h" |
| #import "ios/chrome/browser/ui/util/named_guide_util.h" |
| #import "ios/chrome/browser/ui/util/page_animation_util.h" |
| #import "ios/chrome/browser/ui/util/uikit_ui_util.h" |
| #import "ios/chrome/browser/ui/util/url_with_title.h" |
| #import "ios/chrome/browser/ui/voice/text_to_speech_playback_controller.h" |
| #import "ios/chrome/browser/ui/voice/text_to_speech_playback_controller_factory.h" |
| #import "ios/chrome/browser/upgrade/upgrade_center.h" |
| #import "ios/chrome/browser/url_loading/url_loading_browser_agent.h" |
| #import "ios/chrome/browser/url_loading/url_loading_notifier_browser_agent.h" |
| #import "ios/chrome/browser/url_loading/url_loading_observer_bridge.h" |
| #import "ios/chrome/browser/url_loading/url_loading_params.h" |
| #import "ios/chrome/browser/url_loading/url_loading_util.h" |
| #import "ios/chrome/browser/voice/voice_search_navigations_tab_helper.h" |
| #import "ios/chrome/browser/web/page_placeholder_tab_helper.h" |
| #import "ios/chrome/browser/web/sad_tab_tab_helper.h" |
| #import "ios/chrome/browser/web/web_navigation_browser_agent.h" |
| #import "ios/chrome/browser/web/web_navigation_util.h" |
| #import "ios/chrome/browser/web_state_list/all_web_state_observation_forwarder.h" |
| #import "ios/chrome/browser/web_state_list/tab_insertion_browser_agent.h" |
| #import "ios/chrome/browser/web_state_list/web_state_list.h" |
| #import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h" |
| #import "ios/chrome/browser/web_state_list/web_usage_enabler/web_usage_enabler_browser_agent.h" |
| #import "ios/chrome/browser/webui/show_mail_composer_context.h" |
| #import "ios/chrome/common/ui/colors/semantic_color_names.h" |
| #import "ios/chrome/common/ui/promo_style/promo_style_view_controller.h" |
| #import "ios/chrome/common/ui/util/constraints_ui_util.h" |
| #import "ios/chrome/grit/ios_strings.h" |
| #import "ios/public/provider/chrome/browser/voice_search/voice_search_api.h" |
| #import "ios/public/provider/chrome/browser/voice_search/voice_search_controller.h" |
| #import "ios/web/public/deprecated/crw_js_injection_receiver.h" |
| #import "ios/web/public/navigation/navigation_item.h" |
| #import "ios/web/public/ui/crw_web_view_proxy.h" |
| #import "ios/web/public/web_state_observer_bridge.h" |
| #import "net/base/mac/url_conversions.h" |
| #import "services/metrics/public/cpp/ukm_builders.h" |
| #import "ui/base/device_form_factor.h" |
| #import "ui/base/l10n/l10n_util.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| using base::UserMetricsAction; |
| |
| namespace { |
| |
| const size_t kMaxURLDisplayChars = 32 * 1024; |
| |
| // Duration of the toolbar animation. |
| const NSTimeInterval kLegacyFullscreenControllerToolbarAnimationDuration = 0.3; |
| |
| // When the tab strip moves beyond this origin offset, switch the status bar |
| // appearance from light to dark. |
| const CGFloat kTabStripAppearanceOffset = -29; |
| |
| enum HeaderBehaviour { |
| // The header moves completely out of the screen. |
| Hideable = 0, |
| // This header stay on screen and covers part of the content. |
| Overlap |
| }; |
| |
| // Snackbar category for browser view controller. |
| NSString* const kBrowserViewControllerSnackbarCategory = |
| @"BrowserViewControllerSnackbarCategory"; |
| |
| } // namespace |
| |
| #pragma mark - ToolbarContainerView |
| |
| // TODO(crbug.com/880672): This is a temporary solution. This logic should be |
| // handled by ToolbarContainerViewController. |
| @interface LegacyToolbarContainerView : UIView |
| @end |
| |
| @implementation LegacyToolbarContainerView |
| |
| - (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event { |
| // Don't receive events that don't occur within a subview. This is necessary |
| // because the container view overlaps with web content and the default |
| // behavior will intercept touches meant for the web page when the toolbars |
| // are collapsed. |
| for (UIView* subview in self.subviews) { |
| if (CGRectContainsPoint(subview.frame, point)) |
| return [super hitTest:point withEvent:event]; |
| } |
| return nil; |
| } |
| |
| @end |
| |
| #pragma mark - HeaderDefinition helper |
| |
| // Class used to define a header, an object displayed at the top of the browser. |
| @interface HeaderDefinition : NSObject |
| |
| // The header view. |
| @property(nonatomic, strong) UIView* view; |
| // How to place the view, and its behaviour when the headers move. |
| @property(nonatomic, assign) HeaderBehaviour behaviour; |
| |
| - (instancetype)initWithView:(UIView*)view |
| headerBehaviour:(HeaderBehaviour)behaviour; |
| |
| + (instancetype)definitionWithView:(UIView*)view |
| headerBehaviour:(HeaderBehaviour)behaviour; |
| |
| @end |
| |
| @implementation HeaderDefinition |
| @synthesize view = _view; |
| @synthesize behaviour = _behaviour; |
| |
| + (instancetype)definitionWithView:(UIView*)view |
| headerBehaviour:(HeaderBehaviour)behaviour { |
| return [[self alloc] initWithView:view headerBehaviour:behaviour]; |
| } |
| |
| - (instancetype)initWithView:(UIView*)view |
| headerBehaviour:(HeaderBehaviour)behaviour { |
| self = [super init]; |
| if (self) { |
| _view = view; |
| _behaviour = behaviour; |
| } |
| return self; |
| } |
| |
| @end |
| |
| #pragma mark - BVC |
| |
| // Note other delegates defined in the Delegates category header. |
| @interface BrowserViewController () <CaptivePortalTabHelperDelegate, |
| CRWWebStateObserver, |
| FindBarPresentationDelegate, |
| FullscreenUIElement, |
| InfobarPositioner, |
| KeyCommandsPlumbing, |
| MainContentUI, |
| OmniboxPopupPresenterDelegate, |
| SideSwipeControllerDelegate, |
| TabStripPresentation, |
| UIGestureRecognizerDelegate, |
| URLLoadingObserver, |
| ViewRevealingAnimatee, |
| WebStateListObserving> { |
| // The dependency factory passed on initialization. Used to vend objects used |
| // by the BVC. |
| BrowserViewControllerDependencyFactory* _dependencyFactory; |
| |
| // Identifier for each animation of an NTP opening. |
| NSInteger _NTPAnimationIdentifier; |
| |
| // Facade objects used by |_toolbarCoordinator|. |
| // Must outlive |_toolbarCoordinator|. |
| std::unique_ptr<LocationBarModelDelegateIOS> _locationBarModelDelegate; |
| std::unique_ptr<LocationBarModel> _locationBarModel; |
| |
| // Controller for edge swipe gestures for page and tab navigation. |
| SideSwipeController* _sideSwipeController; |
| |
| // Keyboard commands provider. It offloads most of the keyboard commands |
| // management off of the BVC. |
| KeyCommandsProvider* _keyCommandsProvider; |
| |
| // TODO(crbug.com/1328039): Remove all use of the prerender service from BVC |
| PrerenderService* _prerenderService; |
| |
| // Used to display the Voice Search UI. Nil if not visible. |
| // TODO(crbug.com/1329104): Move voice search controller/coordinator to |
| // BrowserCoordinator |
| id<VoiceSearchController> _voiceSearchController; |
| |
| // YES if new tab is animating in. |
| BOOL _inNewTabAnimation; |
| |
| // YES if Voice Search should be started when the new tab animation is |
| // finished. |
| BOOL _startVoiceSearchAfterNewTabAnimation; |
| // YES if waiting for a foreground tab due to expectNewForegroundTab. |
| // TODO(crbug.com/1329109): Move this to a browser agent or web event |
| // mediator. |
| BOOL _expectingForegroundTab; |
| |
| // Whether or not -shutdown has been called. |
| BOOL _isShutdown; |
| |
| // Whether or not Incognito* is enabled. |
| // TODO(crbug.com/1329092): Set this in the init. |
| BOOL _isOffTheRecord; |
| // Whether the current content is incognito and requires biometric |
| // authentication from the user before it can be accessed. |
| BOOL _itemsRequireAuthentication; |
| |
| // The last point within |contentArea| that's received a touch. |
| CGPoint _lastTapPoint; |
| |
| // The time at which |_lastTapPoint| was most recently set. |
| CFTimeInterval _lastTapTime; |
| |
| // The controller that shows the bookmarking UI after the user taps the star |
| // button. |
| BookmarkInteractionController* _bookmarkInteractionController; |
| |
| // TODO(crbug.com/1329110): Move this out of the BVC; inject it as the |
| // ToolbarInterface as an interim step if needed. |
| ToolbarCoordinatorAdaptor* _toolbarCoordinatorAdaptor; |
| |
| // Toolbar state that broadcasts changes to min and max heights. |
| ToolbarUIState* _toolbarUIState; |
| |
| // The main content UI updater for the content displayed by this BVC. |
| MainContentUIStateUpdater* _mainContentUIUpdater; |
| |
| // The forwarder for web scroll view interation events. |
| WebScrollViewMainContentUIForwarder* _webMainContentUIForwarder; |
| |
| // The updater that adjusts the toolbar's layout for fullscreen events. |
| std::unique_ptr<FullscreenUIUpdater> _fullscreenUIUpdater; |
| |
| // Coordinator for the Download Manager UI. |
| // TODO(crbug.com/1272495): Move DownloadManagerCoordinator to |
| // BrowserCoordinator. |
| DownloadManagerCoordinator* _downloadManagerCoordinator; |
| |
| // A map associating webStates with their NTP coordinators. |
| // TODO(crbug.com/1300911): Factor NTPCoordinator ownership out of the BVC |
| std::map<web::WebState*, NewTabPageCoordinator*> _ntpCoordinatorsForWebStates; |
| |
| // Fake status bar view used to blend the toolbar into the status bar. |
| UIView* _fakeStatusBarView; |
| |
| // Forwards observer methods for all WebStates in the WebStateList to this |
| // BrowserViewController object. |
| std::unique_ptr<AllWebStateObservationForwarder> |
| _allWebStateObservationForwarder; |
| |
| // Bridges C++ WebStateObserver methods to this BrowserViewController. |
| std::unique_ptr<web::WebStateObserverBridge> _webStateObserverBridge; |
| |
| std::unique_ptr<UrlLoadingObserverBridge> _URLLoadingObserverBridge; |
| |
| // Bridges C++ WebStateListObserver methods to this BrowserViewController. |
| std::unique_ptr<WebStateListObserverBridge> _webStateListObserver; |
| |
| // The disabler that prevents the toolbar from being scrolled offscreen when |
| // the thumb strip is visible. |
| std::unique_ptr<ScopedFullscreenDisabler> _fullscreenDisabler; |
| |
| // For thumb strip, when YES, fullscreen disabler is reset only when web view |
| // dragging stops, to avoid closing thumb strip and going fullscreen in |
| // one single drag gesture. When NO, full screen disabler is reset when |
| // the thumb strip animation ends. |
| BOOL _deferEndFullscreenDisabler; |
| } |
| |
| // Activates/deactivates the object. This will enable/disable the ability for |
| // this object to browse, and to have live UIWebViews associated with it. While |
| // not active, the UI will not react to changes in the active web state, so |
| // generally an inactive BVC should not be visible. |
| @property(nonatomic, assign, getter=isActive) BOOL active; |
| // The Browser whose UI is managed by this instance. |
| @property(nonatomic, assign) Browser* browser; |
| // Browser container view controller. |
| @property(nonatomic, strong) |
| BrowserContainerViewController* browserContainerViewController; |
| // Invisible button used to dismiss the keyboard. |
| @property(nonatomic, strong) UIButton* typingShield; |
| // The object that manages keyboard commands on behalf of the BVC. |
| @property(nonatomic, strong, readonly) KeyCommandsProvider* keyCommandsProvider; |
| // Whether the controller's view is currently available. |
| // YES from viewWillAppear to viewWillDisappear. |
| @property(nonatomic, assign, getter=isVisible) BOOL visible; |
| // Whether the controller's view is currently visible. |
| // YES from viewDidAppear to viewWillDisappear. |
| @property(nonatomic, assign) BOOL viewVisible; |
| // Whether the controller should broadcast its UI. |
| @property(nonatomic, assign, getter=isBroadcasting) BOOL broadcasting; |
| // A view to obscure incognito content when the user isn't authorized to |
| // see it. |
| @property(nonatomic, strong) IncognitoReauthView* blockingView; |
| // Whether the controller is currently dismissing a presented view controller. |
| @property(nonatomic, assign, getter=isDismissingModal) BOOL dismissingModal; |
| // Whether web usage is enabled for the WebStates in |self.browser|. |
| @property(nonatomic, assign, getter=isWebUsageEnabled) BOOL webUsageEnabled; |
| // Whether a new tab animation is occurring. |
| @property(nonatomic, assign, getter=isInNewTabAnimation) BOOL inNewTabAnimation; |
| // Whether BVC prefers to hide the status bar. This value is used to determine |
| // the response from the |prefersStatusBarHidden| method. |
| @property(nonatomic, assign) BOOL hideStatusBar; |
| // Whether the BVC is positioned at the bottom of the window, for example after |
| // switching from thumb strip to tab grid. |
| @property(nonatomic, assign) BOOL bottomPosition; |
| // A block to be run when the |tabWasAdded:| method completes the animation |
| // for the presentation of a new tab. Can be used to record performance metrics. |
| @property(nonatomic, strong, nullable) |
| ProceduralBlock foregroundTabWasAddedCompletionBlock; |
| // Coordinator for tablet tab strip. |
| @property(nonatomic, strong) |
| TabStripLegacyCoordinator* legacyTabStripCoordinator; |
| // Coordinator for the new tablet tab strip. |
| @property(nonatomic, strong) TabStripCoordinator* tabStripCoordinator; |
| // A weak reference to the view of the tab strip on tablet. |
| @property(nonatomic, weak) UIView<TabStripContaining>* tabStripView; |
| // A snapshot of the tab strip used on the thumb strip reveal/hide animation. |
| @property(nonatomic, strong) UIView* tabStripSnapshot; |
| |
| // Helper for the bvc. |
| // TODO(crbug.com/1329102): Remove this property. |
| @property(nonatomic, strong) BrowserViewControllerHelper* helper; |
| |
| // The user agent type used to load the currently visible page. User agent |
| // type is NONE if there is no visible page. |
| // TODO(crbug.com/1272534): Move BubblePresenter to BrowserCoordinator. |
| @property(nonatomic, assign, readonly) web::UserAgentType userAgentType; |
| |
| // Returns the header views, all the chrome on top of the page, including the |
| // ones that cannot be scrolled off screen by full screen. |
| @property(nonatomic, strong, readonly) NSArray<HeaderDefinition*>* headerViews; |
| |
| // Coordinator for the popup menus. |
| @property(nonatomic, strong) PopupMenuCoordinator* popupMenuCoordinator; |
| |
| @property(nonatomic, strong) BubblePresenter* bubblePresenter; |
| |
| // Command handler for text zoom commands |
| @property(nonatomic, weak) id<TextZoomCommands> textZoomHandler; |
| |
| // Command handler for help commands |
| @property(nonatomic, weak) id<HelpCommands> helpHandler; |
| |
| // Command handler for omnibox commands |
| @property(nonatomic, weak) id<OmniboxCommands> omniboxHandler; |
| |
| // The FullscreenController. |
| @property(nonatomic, assign) FullscreenController* fullscreenController; |
| |
| // Primary toolbar. |
| @property(nonatomic, strong) |
| PrimaryToolbarCoordinator* primaryToolbarCoordinator; |
| // Secondary toolbar. |
| @property(nonatomic, strong) |
| AdaptiveToolbarCoordinator* secondaryToolbarCoordinator; |
| // The container view for the secondary toolbar. |
| // TODO(crbug.com/880656): Convert to a container coordinator. |
| @property(nonatomic, strong) UIView* secondaryToolbarContainerView; |
| // Coordinator used to manage the secondary toolbar view. |
| @property(nonatomic, strong) |
| ToolbarContainerCoordinator* secondaryToolbarContainerCoordinator; |
| // Interface object with the toolbars. |
| @property(nonatomic, strong) id<ToolbarCoordinating> toolbarInterface; |
| |
| // Vertical offset for the primary toolbar, used for fullscreen. |
| @property(nonatomic, strong) NSLayoutConstraint* primaryToolbarOffsetConstraint; |
| // Height constraint for the primary toolbar. |
| @property(nonatomic, strong) NSLayoutConstraint* primaryToolbarHeightConstraint; |
| // Height constraint for the secondary toolbar. |
| @property(nonatomic, strong) |
| NSLayoutConstraint* secondaryToolbarHeightConstraint; |
| // Height constraint for the frame the secondary toolbar would have if |
| // fullscreen was disabled. |
| @property(nonatomic, strong) |
| NSLayoutConstraint* secondaryToolbarNoFullscreenHeightConstraint; |
| // Current Fullscreen progress for the footers. |
| @property(nonatomic, assign) CGFloat footerFullscreenProgress; |
| // Y-dimension offset for placement of the header. |
| @property(nonatomic, readonly) CGFloat headerOffset; |
| // Height of the header view. |
| @property(nonatomic, readonly) CGFloat headerHeight; |
| |
| // The webState of the active tab. |
| @property(nonatomic, readonly) web::WebState* currentWebState; |
| |
| // The NewTabPageCoordinator associated with a WebState. |
| - (NewTabPageCoordinator*)ntpCoordinatorForWebState:(web::WebState*)webState; |
| |
| // Whether the keyboard observer helper is viewed |
| @property(nonatomic, strong) KeyboardObserverHelper* observer; |
| |
| // The coordinator that shows the Send Tab To Self UI. |
| @property(nonatomic, strong) SendTabToSelfCoordinator* sendTabToSelfCoordinator; |
| |
| // Whether the view has been translated for thumb strip usage when smooth |
| // scrolling has been enabled. This allows the correct setup to be done when |
| // displaying a new web state. |
| @property(nonatomic, assign) BOOL viewTranslatedForSmoothScrolling; |
| |
| // A gesture recognizer to track the last tapped window and the coordinates of |
| // the last tap. |
| @property(nonatomic, strong) UIGestureRecognizer* contentAreaGestureRecognizer; |
| |
| // The coordinator for all NTPs in the BVC. Only used if kSingleNtp is enabled. |
| @property(nonatomic, strong) NewTabPageCoordinator* ntpCoordinator; |
| |
| // The thumb strip's pan gesture handler that will be added to the toolbar and |
| // tab strip. |
| @property(nonatomic, weak) |
| ViewRevealingVerticalPanHandler* thumbStripPanHandler; |
| |
| @end |
| |
| @implementation BrowserViewController |
| |
| @synthesize thumbStripEnabled = _thumbStripEnabled; |
| |
| #pragma mark - Object lifecycle |
| |
| - (instancetype)initWithBrowser:(Browser*)browser |
| dependencyFactory: |
| (BrowserViewControllerDependencyFactory*)factory |
| browserContainerViewController: |
| (BrowserContainerViewController*)browserContainerViewController |
| dispatcher:(CommandDispatcher*)dispatcher |
| prerenderService:(PrerenderService*)prerenderService |
| bubblePresenter:(BubblePresenter*)bubblePresenter { |
| self = [super initWithNibName:nil bundle:base::mac::FrameworkBundle()]; |
| if (self) { |
| DCHECK(factory); |
| // TODO(crbug.com/1272524): DCHECK that |browser| is non-null. |
| |
| _commandDispatcher = dispatcher; |
| _browserContainerViewController = browserContainerViewController; |
| _dependencyFactory = factory; |
| // TODO(crbug.com/1328039): Remove all use of the prerender service from BVC |
| _prerenderService = prerenderService; |
| _bubblePresenter = bubblePresenter; |
| // TODO(crbug.com/1329089): Inject this handler. |
| self.textZoomHandler = |
| HandlerForProtocol(self.commandDispatcher, TextZoomCommands); |
| // TODO(crbug.com/1329090): Have BrowserCoordinator set up dispatch to the |
| // BVC for these commands. |
| [self.commandDispatcher |
| startDispatchingToTarget:self |
| forProtocol:@protocol(BrowserCommands)]; |
| [self.commandDispatcher |
| startDispatchingToTarget:self |
| forProtocol:@protocol(NewTabPageCommands)]; |
| |
| _toolbarCoordinatorAdaptor = |
| [[ToolbarCoordinatorAdaptor alloc] initWithDispatcher:self.dispatcher]; |
| self.toolbarInterface = _toolbarCoordinatorAdaptor; |
| |
| // TODO(crbug.com/1272495): Move DownloadManagerCoordinator to |
| // BrowserCoordinator. |
| _downloadManagerCoordinator = [[DownloadManagerCoordinator alloc] |
| initWithBaseViewController:_browserContainerViewController |
| browser:browser]; |
| _downloadManagerCoordinator.presenter = |
| [[VerticalAnimationContainer alloc] init]; |
| |
| _inNewTabAnimation = NO; |
| |
| _fullscreenController = FullscreenController::FromBrowser(browser); |
| |
| _footerFullscreenProgress = 1.0; |
| |
| _observer = [[KeyboardObserverHelper alloc] init]; |
| if (browser) |
| [self updateWithBrowser:browser]; |
| } |
| return self; |
| } |
| |
| - (void)dealloc { |
| DCHECK(_isShutdown) << "-shutdown must be called before dealloc."; |
| } |
| |
| #pragma mark - Public Properties |
| |
| // TODO(crbug.com/1323764): This uses PopupMenuCommands via inclusion in |
| // BrowserCommands. This is also not a public property. Instead of using |
| // |self.dispatcher| internally, this should use the currect pattern for handler |
| // injection into a view controller with a dedicated id<PopupMenuCommands> |
| // public property set externally. |
| // TODO(crbug.com/1323778): This uses SnackbarCommands via inclusion in |
| // BrowserCommands. Instead a a dedicated id<SnackbarCommands> property should |
| // be injected. |
| - (id<ApplicationCommands, |
| BrowserCommands, |
| FindInPageCommands, |
| PasswordBreachCommands, |
| ToolbarCommands>)dispatcher { |
| return static_cast< |
| id<ApplicationCommands, BrowserCommands, FindInPageCommands, |
| PasswordBreachCommands, ToolbarCommands>>(self.commandDispatcher); |
| } |
| |
| - (UIView*)contentArea { |
| return self.browserContainerViewController.view; |
| } |
| |
| // TODO(crbug.com/1329104): Move voice search controller/coordinator to |
| // BrowserCoordinator, remove this as a public property. |
| - (BOOL)isPlayingTTS { |
| return _voiceSearchController.audioPlaying; |
| } |
| |
| // TODO(crbug.com/1329093): Remove this property. Also not a public property. |
| - (ChromeBrowserState*)browserState { |
| return self.browser ? self.browser->GetBrowserState() : nullptr; |
| } |
| |
| - (void)setInfobarBannerOverlayContainerViewController: |
| (UIViewController*)infobarBannerOverlayContainerViewController { |
| if (_infobarBannerOverlayContainerViewController == |
| infobarBannerOverlayContainerViewController) { |
| return; |
| } |
| |
| _infobarBannerOverlayContainerViewController = |
| infobarBannerOverlayContainerViewController; |
| if (!_infobarBannerOverlayContainerViewController) |
| return; |
| |
| DCHECK_EQ(_infobarBannerOverlayContainerViewController.parentViewController, |
| self); |
| DCHECK_EQ(_infobarBannerOverlayContainerViewController.view.superview, |
| self.view); |
| [self updateOverlayContainerOrder]; |
| } |
| |
| - (void)setInfobarModalOverlayContainerViewController: |
| (UIViewController*)infobarModalOverlayContainerViewController { |
| if (_infobarModalOverlayContainerViewController == |
| infobarModalOverlayContainerViewController) { |
| return; |
| } |
| |
| _infobarModalOverlayContainerViewController = |
| infobarModalOverlayContainerViewController; |
| if (!_infobarModalOverlayContainerViewController) |
| return; |
| |
| DCHECK_EQ(_infobarModalOverlayContainerViewController.parentViewController, |
| self); |
| DCHECK_EQ(_infobarModalOverlayContainerViewController.view.superview, |
| self.view); |
| [self updateOverlayContainerOrder]; |
| } |
| |
| #pragma mark - Delegates Category Properties |
| |
| // Lazily creates the SideSwipeController on first access. |
| - (SideSwipeController*)sideSwipeController { |
| if (!_sideSwipeController) { |
| _sideSwipeController = |
| [[SideSwipeController alloc] initWithBrowser:self.browser]; |
| [_sideSwipeController setSnapshotDelegate:self]; |
| _sideSwipeController.toolbarInteractionHandler = self.toolbarInterface; |
| _sideSwipeController.primaryToolbarSnapshotProvider = |
| self.primaryToolbarCoordinator; |
| _sideSwipeController.secondaryToolbarSnapshotProvider = |
| self.secondaryToolbarCoordinator; |
| [_sideSwipeController setSwipeDelegate:self]; |
| if (!base::FeatureList::IsEnabled(kModernTabStrip)) { |
| [_sideSwipeController setTabStripDelegate:self.legacyTabStripCoordinator]; |
| } |
| } |
| return _sideSwipeController; |
| } |
| |
| // TODO(crbug.com/1272495): Move DownloadManagerCoordinator to |
| // BrowserCoordinator. |
| - (DownloadManagerCoordinator*)downloadManagerCoordinator { |
| return _downloadManagerCoordinator; |
| } |
| |
| #pragma mark - Private Properties |
| |
| - (KeyCommandsProvider*)keyCommandsProvider { |
| if (!_keyCommandsProvider) { |
| _keyCommandsProvider = [_dependencyFactory newKeyCommandsProvider]; |
| } |
| return _keyCommandsProvider; |
| } |
| |
| - (BOOL)canShowTabStrip { |
| return IsRegularXRegularSizeClass(self); |
| } |
| |
| - (web::UserAgentType)userAgentType { |
| web::WebState* webState = self.currentWebState; |
| if (!webState) |
| return web::UserAgentType::NONE; |
| web::NavigationItem* visibleItem = |
| webState->GetNavigationManager()->GetVisibleItem(); |
| if (!visibleItem) |
| return web::UserAgentType::NONE; |
| |
| return visibleItem->GetUserAgentType(); |
| } |
| |
| - (void)setVisible:(BOOL)visible { |
| if (_visible == visible) |
| return; |
| |
| _visible = visible; |
| } |
| |
| - (void)setViewVisible:(BOOL)viewVisible { |
| if (_viewVisible == viewVisible) |
| return; |
| _viewVisible = viewVisible; |
| self.visible = viewVisible; |
| [self updateBroadcastState]; |
| } |
| |
| - (void)setBroadcasting:(BOOL)broadcasting { |
| if (_broadcasting == broadcasting) |
| return; |
| _broadcasting = broadcasting; |
| |
| ChromeBroadcaster* broadcaster = self.fullscreenController->broadcaster(); |
| if (_broadcasting) { |
| _toolbarUIState = [[ToolbarUIState alloc] init]; |
| // Must update _toolbarUIState with current toolbar height state before |
| // starting broadcasting. |
| [self updateToolbarState]; |
| StartBroadcastingToolbarUI(_toolbarUIState, broadcaster); |
| |
| _mainContentUIUpdater = [[MainContentUIStateUpdater alloc] |
| initWithState:[[MainContentUIState alloc] init]]; |
| _webMainContentUIForwarder = [[WebScrollViewMainContentUIForwarder alloc] |
| initWithUpdater:_mainContentUIUpdater |
| webStateList:self.browser->GetWebStateList()]; |
| StartBroadcastingMainContentUI(self, broadcaster); |
| |
| _fullscreenUIUpdater = |
| std::make_unique<FullscreenUIUpdater>(self.fullscreenController, self); |
| [self updateForFullscreenProgress:self.fullscreenController->GetProgress()]; |
| } else { |
| StopBroadcastingToolbarUI(broadcaster); |
| StopBroadcastingMainContentUI(broadcaster); |
| _mainContentUIUpdater = nil; |
| _toolbarUIState = nil; |
| [_webMainContentUIForwarder disconnect]; |
| _webMainContentUIForwarder = nil; |
| |
| _fullscreenUIUpdater = nullptr; |
| } |
| } |
| |
| // TODO(crbug.com/1272516): Change webUsageEnabled to be a regular BOOL ivar. |
| - (BOOL)isWebUsageEnabled { |
| return self.browserState && !_isShutdown && |
| WebUsageEnablerBrowserAgent::FromBrowser(self.browser) |
| ->IsWebUsageEnabled(); |
| } |
| |
| // TODO(crbug.com/1272516): Change webUsageEnabled to be a regular BOOL ivar. |
| // BrowserCoordinator should update the WebUsageEnablerBrowserAgent. |
| - (void)setWebUsageEnabled:(BOOL)webUsageEnabled { |
| if (!self.browserState || _isShutdown) |
| return; |
| WebUsageEnablerBrowserAgent::FromBrowser(self.browser) |
| ->SetWebUsageEnabled(webUsageEnabled); |
| } |
| |
| - (void)setInNewTabAnimation:(BOOL)inNewTabAnimation { |
| if (_inNewTabAnimation == inNewTabAnimation) |
| return; |
| _inNewTabAnimation = inNewTabAnimation; |
| [self updateBroadcastState]; |
| } |
| |
| - (BOOL)isInNewTabAnimation { |
| return _inNewTabAnimation; |
| } |
| |
| - (void)setHideStatusBar:(BOOL)hideStatusBar { |
| if (_hideStatusBar == hideStatusBar) |
| return; |
| _hideStatusBar = hideStatusBar; |
| [self setNeedsStatusBarAppearanceUpdate]; |
| } |
| |
| - (NSArray<HeaderDefinition*>*)headerViews { |
| NSMutableArray<HeaderDefinition*>* results = [[NSMutableArray alloc] init]; |
| if (![self isViewLoaded]) |
| return results; |
| |
| if (![self canShowTabStrip]) { |
| if (self.primaryToolbarCoordinator.viewController.view) { |
| [results addObject:[HeaderDefinition |
| definitionWithView:self.primaryToolbarCoordinator |
| .viewController.view |
| headerBehaviour:Hideable]]; |
| } |
| } else { |
| if (self.tabStripView) { |
| [results addObject:[HeaderDefinition definitionWithView:self.tabStripView |
| headerBehaviour:Hideable]]; |
| } |
| if (self.primaryToolbarCoordinator.viewController.view) { |
| [results addObject:[HeaderDefinition |
| definitionWithView:self.primaryToolbarCoordinator |
| .viewController.view |
| headerBehaviour:Hideable]]; |
| } |
| if (self.toolbarAccessoryPresenter.isPresenting) { |
| [results addObject:[HeaderDefinition |
| definitionWithView:self.toolbarAccessoryPresenter |
| .backgroundView |
| headerBehaviour:Overlap]]; |
| } |
| } |
| return [results copy]; |
| } |
| |
| // Returns the safeAreaInsets of the root window for self.view. In some cases, |
| // the self.view.safeAreaInsets are cleared when the view has moved (like with |
| // thumbstrip, starting with iOS 15) or if it is unattached ( for example on the |
| // incognito BVC when the normal BVC is the one active or vice versa). Attached |
| // or unttached, going to the window through the SceneState for the self.browser |
| // solves both issues. |
| - (UIEdgeInsets)rootSafeAreaInsets { |
| if (_isShutdown) { |
| return UIEdgeInsetsZero; |
| } |
| // TODO(crbug.com/1329096): Create an external provider thingy for this. |
| UIView* view = |
| SceneStateBrowserAgent::FromBrowser(self.browser)->GetSceneState().window; |
| return view ? view.safeAreaInsets : self.view.safeAreaInsets; |
| } |
| |
| - (CGFloat)headerOffset { |
| CGFloat headerOffset = self.rootSafeAreaInsets.top; |
| return [self canShowTabStrip] ? headerOffset : 0.0; |
| } |
| |
| - (CGFloat)headerHeight { |
| NSArray<HeaderDefinition*>* views = [self headerViews]; |
| |
| CGFloat height = self.headerOffset; |
| for (HeaderDefinition* header in views) { |
| if (header.view && header.behaviour == Hideable) { |
| height += CGRectGetHeight([header.view frame]); |
| } |
| } |
| |
| CGFloat statusBarOffset = 0; |
| return height - statusBarOffset; |
| } |
| |
| - (web::WebState*)currentWebState { |
| return self.browser ? self.browser->GetWebStateList()->GetActiveWebState() |
| : nullptr; |
| } |
| |
| // TODO(crbug.com/1265565): Remove once kSingleNtp feature is launched and |
| // directly reference |self.ntpCoordinator|. |
| - (NewTabPageCoordinator*)ntpCoordinatorForWebState:(web::WebState*)webState { |
| if (IsSingleNtpEnabled()) { |
| return _ntpCoordinator; |
| } |
| auto found = _ntpCoordinatorsForWebStates.find(webState); |
| if (found != _ntpCoordinatorsForWebStates.end()) |
| return found->second; |
| return nil; |
| } |
| |
| #pragma mark - Public methods |
| |
| - (id<ActivityServicePositioner>)activityServicePositioner { |
| return [self.primaryToolbarCoordinator activityServicePositioner]; |
| } |
| |
| - (void)setPrimary:(BOOL)primary { |
| TabUsageRecorderBrowserAgent* tabUsageRecorder = |
| TabUsageRecorderBrowserAgent::FromBrowser(_browser); |
| if (tabUsageRecorder) { |
| tabUsageRecorder->RecordPrimaryBrowserChange( |
| primary, _browser->GetWebStateList()->GetActiveWebState()); |
| } |
| if (primary) { |
| [self updateBroadcastState]; |
| } |
| } |
| |
| - (void)shieldWasTapped:(id)sender { |
| [self.omniboxHandler cancelOmniboxEdit]; |
| } |
| |
| - (void)userEnteredTabSwitcher { |
| [_bubblePresenter userEnteredTabSwitcher]; |
| } |
| |
| - (void)openNewTabFromOriginPoint:(CGPoint)originPoint |
| focusOmnibox:(BOOL)focusOmnibox |
| inheritOpener:(BOOL)inheritOpener { |
| NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate]; |
| BOOL offTheRecord = self.isOffTheRecord; |
| ProceduralBlock oldForegroundTabWasAddedCompletionBlock = |
| self.foregroundTabWasAddedCompletionBlock; |
| id<OmniboxCommands> omniboxCommandHandler = self.omniboxHandler; |
| self.foregroundTabWasAddedCompletionBlock = ^{ |
| if (oldForegroundTabWasAddedCompletionBlock) { |
| oldForegroundTabWasAddedCompletionBlock(); |
| } |
| double duration = [NSDate timeIntervalSinceReferenceDate] - startTime; |
| base::TimeDelta timeDelta = base::Seconds(duration); |
| if (offTheRecord) { |
| UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewIncognitoTabPresentationDuration", |
| timeDelta); |
| } else { |
| UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewTabPresentationDuration", timeDelta); |
| } |
| if (focusOmnibox) { |
| [omniboxCommandHandler focusOmnibox]; |
| } |
| }; |
| |
| [self setLastTapPointFromCommand:originPoint]; |
| // The new tab can be opened before BVC has been made visible onscreen. Test |
| // for this case by checking if the parent container VC is currently in the |
| // process of being presented. |
| DCHECK(self.visible || self.dismissingModal || |
| self.parentViewController.isBeingPresented); |
| |
| // In most cases, we want to take a snapshot of the current tab before opening |
| // a new tab. However, if the current tab is not fully visible (did not finish |
| // |-viewDidAppear:|, then we must not take an empty snapshot, replacing an |
| // existing snapshot for the tab. This can happen when a new regular tab is |
| // opened from an incognito tab. A different BVC is displayed, which may not |
| // have enough time to finish appearing before a snapshot is requested. |
| if (self.currentWebState && self.viewVisible) { |
| SnapshotTabHelper::FromWebState(self.currentWebState) |
| ->UpdateSnapshotWithCallback(nil); |
| } |
| |
| UrlLoadParams params = UrlLoadParams::InNewTab(GURL(kChromeUINewTabURL)); |
| params.web_params.transition_type = ui::PAGE_TRANSITION_TYPED; |
| params.in_incognito = self.isOffTheRecord; |
| params.inherit_opener = inheritOpener; |
| UrlLoadingBrowserAgent::FromBrowser(self.browser)->Load(params); |
| } |
| |
| - (void)appendTabAddedCompletion:(ProceduralBlock)tabAddedCompletion { |
| if (tabAddedCompletion) { |
| if (self.foregroundTabWasAddedCompletionBlock) { |
| ProceduralBlock oldForegroundTabWasAddedCompletionBlock = |
| self.foregroundTabWasAddedCompletionBlock; |
| self.foregroundTabWasAddedCompletionBlock = ^{ |
| oldForegroundTabWasAddedCompletionBlock(); |
| tabAddedCompletion(); |
| }; |
| } else { |
| self.foregroundTabWasAddedCompletionBlock = tabAddedCompletion; |
| } |
| } |
| } |
| |
| // TODO(crbug.com/1329109): Move this to a browser agent or web event mediator. |
| - (void)expectNewForegroundTab { |
| _expectingForegroundTab = YES; |
| } |
| |
| - (void)startVoiceSearch { |
| // Delay Voice Search until new tab animations have finished. |
| if (self.inNewTabAnimation) { |
| _startVoiceSearchAfterNewTabAnimation = YES; |
| return; |
| } |
| |
| // Keyboard shouldn't overlay the ecoutez window, so dismiss find in page and |
| // dismiss the keyboard. |
| [self.dispatcher closeFindInPage]; |
| [self.textZoomHandler closeTextZoom]; |
| [[self viewForWebState:self.currentWebState] endEditing:NO]; |
| |
| // Ensure that voice search objects are created. |
| [self ensureVoiceSearchControllerCreated]; |
| |
| // Present voice search. |
| [_voiceSearchController |
| startRecognitionOnViewController:self |
| webState:self.currentWebState]; |
| [self.omniboxHandler cancelOmniboxEdit]; |
| } |
| |
| // TODO(crbug.com/1329099): Get rid of this (move it to MetricsMediator). Looks |
| // like its busted for kSingleNTP in any case? |
| - (int)liveNTPCount { |
| NSUInteger count = 0; |
| WebStateList* webStateList = self.browser->GetWebStateList(); |
| for (int i = 0; i < webStateList->count(); i++) { |
| web::WebState* webState = webStateList->GetWebStateAt(i); |
| auto found = _ntpCoordinatorsForWebStates.find(webState); |
| if (found != _ntpCoordinatorsForWebStates.end() && |
| [found->second isStarted]) { |
| count++; |
| } |
| } |
| return count; |
| } |
| |
| #pragma mark - browser_view_controller+private.h |
| |
| - (void)setActive:(BOOL)active { |
| if (_active == active) { |
| return; |
| } |
| _active = active; |
| |
| // TODO(crbug.com/1272524): Move these updates to BrowserCoordinator. |
| if (self.browserState) { |
| // TODO(crbug.com/1272520): Refactor ActiveStateManager for multiwindow. |
| ActiveStateManager* active_state_manager = |
| ActiveStateManager::FromBrowserState(self.browserState); |
| active_state_manager->SetActive(active); |
| |
| TextToSpeechPlaybackControllerFactory::GetInstance() |
| ->GetForBrowserState(self.browserState) |
| ->SetEnabled(_active); |
| } |
| |
| self.webUsageEnabled = active; |
| [self updateBroadcastState]; |
| |
| // Stop the NTP on web usage toggle. This happens when clearing browser |
| // data, and forces the NTP to be recreated in -displayWebState below. |
| // TODO(crbug.com/906199): Move this to the NewTabPageTabHelper when |
| // WebStateObserver has a webUsage callback. |
| if (!active) { |
| if (IsSingleNtpEnabled()) { |
| [self stopNTP]; |
| } else { |
| for (const auto& element : _ntpCoordinatorsForWebStates) |
| [element.second stop]; |
| } |
| } |
| |
| if (active) { |
| // Make sure the tab (if any; it's possible to get here without a current |
| // tab if the caller is about to create one) ends up on screen completely. |
| // Force loading the view in case it was not loaded yet. |
| [self loadViewIfNeeded]; |
| // TODO(crbug.com/1329109): Move this to a browser agent or web event |
| // mediator. |
| if (self.currentWebState && _expectingForegroundTab) { |
| PagePlaceholderTabHelper::FromWebState(self.currentWebState) |
| ->AddPlaceholderForNextNavigation(); |
| } |
| if (self.currentWebState) |
| [self displayWebState:self.currentWebState]; |
| } |
| |
| [self setNeedsStatusBarAppearanceUpdate]; |
| } |
| |
| // TODO(crbug.com/1329111): Federate ClearPresentedState. |
| - (void)clearPresentedStateWithCompletion:(ProceduralBlock)completion |
| dismissOmnibox:(BOOL)dismissOmnibox { |
| [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:NO]; |
| [_bookmarkInteractionController dismissSnackbar]; |
| if (dismissOmnibox) { |
| [self.omniboxHandler cancelOmniboxEdit]; |
| } |
| [self.helpHandler hideAllHelpBubbles]; |
| [_voiceSearchController dismissMicPermissionHelp]; |
| |
| web::WebState* webState = self.currentWebState; |
| |
| if (webState) { |
| [self.dispatcher closeFindInPage]; |
| [self.textZoomHandler closeTextZoom]; |
| } |
| |
| // TODO(crbug.com/1323764): This will need to be called on the |
| // PopupMenuCommands handler. |
| [self.dispatcher dismissPopupMenuAnimated:NO]; |
| |
| if (self.presentedViewController) { |
| // Dismisses any other modal controllers that may be present, e.g. Recent |
| // Tabs. |
| // |
| // Note that currently, some controllers like the bookmark ones were already |
| // dismissed (in this example in -dismissBookmarkModalControllerAnimated:), |
| // but are still reported as the presentedViewController. Calling |
| // |dismissViewControllerAnimated:completion:| again would dismiss the BVC |
| // itself, so instead check the value of |self.dismissingModal| and only |
| // call dismiss if one of the above calls has not already triggered a |
| // dismissal. |
| // |
| // To ensure the completion is called, nil is passed to the call to dismiss, |
| // and the completion is called explicitly below. |
| if (!self.dismissingModal) { |
| [self dismissViewControllerAnimated:NO completion:nil]; |
| } |
| // Dismissed controllers will be so after a delay. Queue the completion |
| // callback after that. |
| if (completion) { |
| dispatch_after( |
| dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), |
| dispatch_get_main_queue(), ^{ |
| completion(); |
| }); |
| } |
| } else if (completion) { |
| // If no view controllers are presented, we should be ok with dispatching |
| // the completion block directly. |
| dispatch_async(dispatch_get_main_queue(), completion); |
| } |
| } |
| |
| - (void)animateOpenBackgroundTabFromOriginPoint:(CGPoint)originPoint |
| completion:(void (^)())completion { |
| if ([self canShowTabStrip] || CGPointEqualToPoint(originPoint, CGPointZero)) { |
| completion(); |
| } else { |
| self.inNewTabAnimation = YES; |
| // Exit fullscreen if needed. |
| self.fullscreenController->ExitFullscreen(); |
| const CGFloat kAnimatedViewSize = 50; |
| BackgroundTabAnimationView* animatedView = |
| [[BackgroundTabAnimationView alloc] |
| initWithFrame:CGRectMake(0, 0, kAnimatedViewSize, kAnimatedViewSize) |
| incognito:self.isOffTheRecord]; |
| __weak UIView* weakAnimatedView = animatedView; |
| auto completionBlock = ^() { |
| self.inNewTabAnimation = NO; |
| [weakAnimatedView removeFromSuperview]; |
| completion(); |
| }; |
| [self.view addSubview:animatedView]; |
| [animatedView animateFrom:originPoint |
| toTabGridButtonWithCompletion:completionBlock]; |
| } |
| } |
| |
| - (void)shutdown { |
| DCHECK(!_isShutdown); |
| _isShutdown = YES; |
| |
| [self setActive:NO]; |
| |
| // TODO(crbug.com/1272524): Move these updates to BrowserCoordinator. |
| if (self.browserState) { |
| TextToSpeechPlaybackController* controller = |
| TextToSpeechPlaybackControllerFactory::GetInstance() |
| ->GetForBrowserState(self.browserState); |
| if (controller) |
| controller->SetWebStateList(nullptr); |
| |
| UrlLoadingNotifierBrowserAgent* notifier = |
| UrlLoadingNotifierBrowserAgent::FromBrowser(self.browser); |
| if (notifier) |
| notifier->RemoveObserver(_URLLoadingObserverBridge.get()); |
| } |
| |
| // Uninstall delegates so that any delegate callbacks triggered by subsequent |
| // WebStateDestroyed() signals are not handled. |
| WebStateList* webStateList = self.browser->GetWebStateList(); |
| for (int index = 0; index < webStateList->count(); ++index) |
| [self uninstallDelegatesForWebState:webStateList->GetWebStateAt(index)]; |
| |
| // Disconnect child coordinators. |
| [self.popupMenuCoordinator stop]; |
| if (base::FeatureList::IsEnabled(kModernTabStrip)) { |
| [self.tabStripCoordinator stop]; |
| self.tabStripCoordinator = nil; |
| } else { |
| [self.legacyTabStripCoordinator stop]; |
| self.legacyTabStripCoordinator = nil; |
| self.tabStripView = nil; |
| } |
| |
| [self.commandDispatcher stopDispatchingToTarget:_bubblePresenter]; |
| [_bubblePresenter stop]; |
| _bubblePresenter = nil; |
| |
| [self.commandDispatcher stopDispatchingToTarget:self]; |
| self.browser->GetWebStateList()->RemoveObserver(_webStateListObserver.get()); |
| self.browser = nullptr; |
| |
| [self.contentArea removeGestureRecognizer:self.contentAreaGestureRecognizer]; |
| |
| [self.primaryToolbarCoordinator stop]; |
| self.primaryToolbarCoordinator = nil; |
| [self.secondaryToolbarContainerCoordinator stop]; |
| self.secondaryToolbarContainerCoordinator = nil; |
| [self.secondaryToolbarCoordinator stop]; |
| self.secondaryToolbarCoordinator = nil; |
| [_downloadManagerCoordinator stop]; |
| _downloadManagerCoordinator = nil; |
| self.toolbarInterface = nil; |
| _sideSwipeController = nil; |
| _webStateListObserver.reset(); |
| _allWebStateObservationForwarder = nullptr; |
| [_voiceSearchController disconnect]; |
| _voiceSearchController = nil; |
| _fullscreenDisabler = nullptr; |
| [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| |
| [_bookmarkInteractionController shutdown]; |
| _bookmarkInteractionController = nil; |
| } |
| |
| #pragma mark - NSObject |
| |
| - (BOOL)accessibilityPerformEscape { |
| [self dismissPopups]; |
| return YES; |
| } |
| |
| #pragma mark - UIResponder |
| |
| - (BOOL)canBecomeFirstResponder { |
| return YES; |
| } |
| |
| - (NSArray*)keyCommands { |
| if (![self shouldRegisterKeyboardCommands]) { |
| return nil; |
| } |
| |
| UIResponder* firstResponder = GetFirstResponder(); |
| // TODO(crbug.com/1329100): Inject the key commands provider. |
| WebNavigationBrowserAgent* navigationAgent = |
| WebNavigationBrowserAgent::FromBrowser(self.browser); |
| return [self.keyCommandsProvider |
| keyCommandsForConsumer:self |
| baseViewController:self |
| dispatcher:self.dispatcher |
| navigationAgent:navigationAgent |
| omniboxHandler:self.omniboxHandler |
| editingText:[firstResponder |
| isKindOfClass:[UITextField class]] || |
| [firstResponder |
| isKindOfClass:[UITextView class]] || |
| [self.observer isKeyboardOnScreen]]; |
| } |
| |
| #pragma mark - UIResponder helpers |
| |
| // Whether the BVC should declare keyboard commands. |
| // Since |-keyCommands| can be called by UIKit at any time, no assumptions |
| // about the state of |self| can be made; accordingly, if there's anything |
| // not initialized (or being torn down), this method should return NO. |
| - (BOOL)shouldRegisterKeyboardCommands { |
| if (_isShutdown) |
| return NO; |
| |
| if (!self.browser) |
| return NO; |
| |
| if ([self presentedViewController]) |
| return NO; |
| |
| if (_voiceSearchController.visible) |
| return NO; |
| |
| if (self.bottomPosition) |
| return NO; |
| |
| return YES; |
| } |
| |
| #pragma mark - UIViewController |
| |
| - (void)viewDidLoad { |
| DCHECK(self.browser); |
| |
| CGRect initialViewsRect = self.view.bounds; |
| UIViewAutoresizing initialViewAutoresizing = |
| UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; |
| |
| self.contentArea.frame = initialViewsRect; |
| |
| // Create the typing shield. It is initially hidden, and is made visible when |
| // the keyboard appears. |
| self.typingShield = [[UIButton alloc] initWithFrame:initialViewsRect]; |
| self.typingShield.hidden = YES; |
| self.typingShield.autoresizingMask = initialViewAutoresizing; |
| self.typingShield.accessibilityIdentifier = @"Typing Shield"; |
| self.typingShield.accessibilityLabel = l10n_util::GetNSString(IDS_CANCEL); |
| |
| [self.typingShield addTarget:self |
| action:@selector(shieldWasTapped:) |
| forControlEvents:UIControlEventTouchUpInside]; |
| self.view.autoresizingMask = initialViewAutoresizing; |
| |
| [self addChildViewController:self.browserContainerViewController]; |
| [self.view addSubview:self.contentArea]; |
| [self.browserContainerViewController didMoveToParentViewController:self]; |
| [self.view addSubview:self.typingShield]; |
| [super viewDidLoad]; |
| |
| // Install fake status bar for iPad iOS7 |
| [self installFakeStatusBar]; |
| |
| _bubblePresenter.delegate = self; |
| _bubblePresenter.rootViewController = self; |
| _bubblePresenter.toolbarHandler = |
| HandlerForProtocol(self.browser->GetCommandDispatcher(), ToolbarCommands); |
| [self.browser->GetCommandDispatcher() |
| startDispatchingToTarget:_bubblePresenter |
| forProtocol:@protocol(HelpCommands)]; |
| |
| [self buildToolbarAndTabStrip]; |
| [self setUpViewLayout:YES]; |
| [self addConstraintsToToolbar]; |
| |
| // Finish initialization. |
| [self addUIFunctionalityForBrowserAndBrowserState]; |
| |
| // Add a tap gesture recognizer to save the last tap location for the source |
| // location of the new tab animation. |
| self.contentAreaGestureRecognizer = [[UITapGestureRecognizer alloc] |
| initWithTarget:self |
| action:@selector(saveContentAreaTapLocation:)]; |
| [self.contentAreaGestureRecognizer setDelegate:self]; |
| [self.contentAreaGestureRecognizer setCancelsTouchesInView:NO]; |
| [self.contentArea addGestureRecognizer:self.contentAreaGestureRecognizer]; |
| |
| self.view.backgroundColor = [UIColor colorNamed:kBackgroundColor]; |
| } |
| |
| - (void)viewSafeAreaInsetsDidChange { |
| [super viewSafeAreaInsetsDidChange]; |
| [self setUpViewLayout:NO]; |
| // Update the heights of the toolbars to account for the new insets. |
| self.primaryToolbarHeightConstraint.constant = |
| [self primaryToolbarHeightWithInset]; |
| self.secondaryToolbarHeightConstraint.constant = |
| [self secondaryToolbarHeightWithInset]; |
| self.secondaryToolbarNoFullscreenHeightConstraint.constant = |
| [self secondaryToolbarHeightWithInset]; |
| |
| // Update the tab strip placement. |
| if (self.tabStripView) { |
| [self showTabStripView:self.tabStripView]; |
| } |
| } |
| |
| - (void)viewDidLayoutSubviews { |
| [super viewDidLayoutSubviews]; |
| // Update the toolbar height to account for |topLayoutGuide| changes. |
| self.primaryToolbarHeightConstraint.constant = |
| [self primaryToolbarHeightWithInset]; |
| |
| if (self.isNTPActiveForCurrentWebState && self.webUsageEnabled) { |
| [self ntpCoordinatorForWebState:self.currentWebState] |
| .viewController.view.frame = |
| [self ntpFrameForWebState:self.currentWebState]; |
| } |
| } |
| |
| - (void)viewDidAppear:(BOOL)animated { |
| [super viewDidAppear:animated]; |
| self.viewVisible = YES; |
| [self updateBroadcastState]; |
| [self updateToolbarState]; |
| |
| // |viewDidAppear| can be called after |browserState| is destroyed. Since |
| // |presentBubblesIfEligible| requires that |self.browserState| is not NULL, |
| // check for |self.browserState| before calling the presenting the bubbles. |
| // TODO(crbug.com/1329091): determine if this check is still needed? |
| if (self.browserState) { |
| [self.helpHandler showHelpBubbleIfEligible]; |
| [self.helpHandler showLongPressHelpBubbleIfEligible]; |
| } |
| } |
| |
| - (void)viewWillAppear:(BOOL)animated { |
| [super viewWillAppear:animated]; |
| |
| self.visible = YES; |
| |
| // If the controller is suspended, or has been paged out due to low memory, |
| // updating the view will be handled when it's displayed again. |
| if (!self.webUsageEnabled || !self.contentArea) |
| return; |
| // Update the displayed WebState (if any; the switcher may not have created |
| // one yet) in case it changed while showing the switcher. |
| if (self.currentWebState) |
| [self displayWebState:self.currentWebState]; |
| } |
| |
| - (void)viewWillDisappear:(BOOL)animated { |
| self.viewVisible = NO; |
| [self updateBroadcastState]; |
| web::WebState* activeWebState = |
| self.browser ? self.browser->GetWebStateList()->GetActiveWebState() |
| : nullptr; |
| if (activeWebState) { |
| activeWebState->WasHidden(); |
| if (!self.presentedViewController) |
| activeWebState->SetKeepRenderProcessAlive(false); |
| } |
| |
| [_bookmarkInteractionController dismissSnackbar]; |
| [super viewWillDisappear:animated]; |
| } |
| |
| - (BOOL)prefersStatusBarHidden { |
| return self.hideStatusBar || [super prefersStatusBarHidden]; |
| } |
| |
| // Called when in the foreground and the OS needs more memory. Release as much |
| // as possible. |
| - (void)didReceiveMemoryWarning { |
| // Releases the view if it doesn't have a superview. |
| [super didReceiveMemoryWarning]; |
| |
| if (![self isViewLoaded]) { |
| self.typingShield = nil; |
| _voiceSearchController.dispatcher = nil; |
| [self.primaryToolbarCoordinator stop]; |
| self.primaryToolbarCoordinator = nil; |
| [self.secondaryToolbarContainerCoordinator stop]; |
| self.secondaryToolbarContainerCoordinator = nil; |
| [self.secondaryToolbarCoordinator stop]; |
| self.secondaryToolbarCoordinator = nil; |
| self.toolbarInterface = nil; |
| _toolbarUIState = nil; |
| _locationBarModelDelegate = nil; |
| _locationBarModel = nil; |
| self.helper = nil; |
| if (base::FeatureList::IsEnabled(kModernTabStrip)) { |
| [self.tabStripCoordinator stop]; |
| self.tabStripCoordinator = nil; |
| } else { |
| [self.legacyTabStripCoordinator stop]; |
| self.legacyTabStripCoordinator = nil; |
| self.tabStripView = nil; |
| } |
| _sideSwipeController = nil; |
| } |
| } |
| |
| - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection { |
| [super traitCollectionDidChange:previousTraitCollection]; |
| |
| // After |-shutdown| is called, |self.browserState| is invalid and will cause |
| // a crash. |
| if (!self.browserState || _isShutdown) |
| return; |
| |
| self.fullscreenController->BrowserTraitCollectionChangedBegin(); |
| |
| // TODO(crbug.com/527092): - traitCollectionDidChange: is not always forwarded |
| // because in some cases the presented view controller isn't a child of the |
| // BVC in the view controller hierarchy (some intervening object isn't a |
| // view controller). |
| [self.presentedViewController |
| traitCollectionDidChange:previousTraitCollection]; |
| // Change the height of the secondary toolbar to show/hide it. |
| self.secondaryToolbarHeightConstraint.constant = |
| [self secondaryToolbarHeightWithInset]; |
| self.secondaryToolbarNoFullscreenHeightConstraint.constant = |
| [self secondaryToolbarHeightWithInset]; |
| [self updateFootersForFullscreenProgress:self.footerFullscreenProgress]; |
| if (self.currentWebState) { |
| UIEdgeInsets contentPadding = |
| self.currentWebState->GetWebViewProxy().contentInset; |
| contentPadding.bottom = AlignValueToPixel( |
| self.footerFullscreenProgress * [self secondaryToolbarHeightWithInset]); |
| self.currentWebState->GetWebViewProxy().contentInset = contentPadding; |
| } |
| |
| [self updateToolbarState]; |
| |
| // If the device's size class has changed from RegularXRegular to another and |
| // vice-versa, the find bar should switch between regular mode and compact |
| // mode accordingly. Hide the findbar here and it will be reshown in [self |
| // updateToobar]; |
| if (ShouldShowCompactToolbar(previousTraitCollection) != |
| ShouldShowCompactToolbar(self)) { |
| [self.dispatcher hideFindUI]; |
| [self.textZoomHandler hideTextZoomUI]; |
| } |
| |
| // Update the toolbar visibility. |
| // TODO(crbug.com/1329087): Move this update to the toolbar view |
| // controller(s)? |
| [self updateToolbar]; |
| |
| // Update the tab strip visibility. |
| if (self.tabStripView) { |
| [self showTabStripView:self.tabStripView]; |
| [self.tabStripView layoutSubviews]; |
| if (base::FeatureList::IsEnabled(kModernTabStrip)) { |
| [self.tabStripCoordinator hideTabStrip:![self canShowTabStrip]]; |
| } else { |
| [self.legacyTabStripCoordinator hideTabStrip:![self canShowTabStrip]]; |
| } |
| _fakeStatusBarView.hidden = ![self canShowTabStrip]; |
| [self addConstraintsToPrimaryToolbar]; |
| // If tabstrip is coming back due to a window resize or screen rotation, |
| // reset the full screen controller to adjust the tabstrip position. |
| if (ShouldShowCompactToolbar(previousTraitCollection) && |
| !ShouldShowCompactToolbar(self)) { |
| [self |
| updateForFullscreenProgress:self.fullscreenController->GetProgress()]; |
| } |
| } |
| |
| [self setNeedsStatusBarAppearanceUpdate]; |
| |
| self.fullscreenController->BrowserTraitCollectionChangedEnd(); |
| } |
| |
| - (void)viewWillTransitionToSize:(CGSize)size |
| withTransitionCoordinator: |
| (id<UIViewControllerTransitionCoordinator>)coordinator { |
| [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; |
| |
| // After |-shutdown| is called, |self.browser| is invalid and will cause |
| // a crash. |
| if (_isShutdown) |
| return; |
| |
| [self dismissPopups]; |
| |
| __weak BrowserViewController* weakSelf = self; |
| |
| [coordinator |
| animateAlongsideTransition:^( |
| id<UIViewControllerTransitionCoordinatorContext>) { |
| [weakSelf animateTransition]; |
| } |
| completion:^(id<UIViewControllerTransitionCoordinatorContext>) { |
| [weakSelf completedTransition]; |
| }]; |
| |
| if (self.currentWebState) { |
| id<CRWWebViewProxy> webViewProxy = self.currentWebState->GetWebViewProxy(); |
| [webViewProxy surfaceSizeChanged]; |
| } |
| |
| crash_keys::SetCurrentOrientation(GetInterfaceOrientation(), |
| [[UIDevice currentDevice] orientation]); |
| } |
| |
| - (void)animateTransition { |
| // Force updates of the toolbar state as the toolbar height might |
| // change on rotation. |
| [self updateToolbarState]; |
| // Resize horizontal viewport if Smooth Scrolling is on. |
| if (fullscreen::features::ShouldUseSmoothScrolling()) { |
| self.fullscreenController->ResizeHorizontalViewport(); |
| } |
| } |
| |
| - (void)completedTransition { |
| if (!base::FeatureList::IsEnabled(kModernTabStrip)) { |
| if (self.tabStripView) { |
| [self.legacyTabStripCoordinator tabStripSizeDidChange]; |
| } |
| } |
| } |
| |
| - (void)dismissViewControllerAnimated:(BOOL)flag |
| completion:(void (^)())completion { |
| if (!self.presentedViewController) { |
| // TODO(crbug.com/801165): On iOS10, UIDocumentMenuViewController and |
| // WKFileUploadPanel somehow combine to call dismiss twice instead of once. |
| // The second call would dismiss the BVC itself, so look for that case and |
| // return early. |
| // |
| // TODO(crbug.com/811671): A similar bug exists on all iOS versions with |
| // WKFileUploadPanel and UIDocumentPickerViewController. |
| // |
| // To make M65 as safe as possible, return early whenever this method is |
| // invoked but no VC appears to be presented. These cases will always end |
| // up dismissing the BVC itself, which would put the app into an |
| // unresponsive state. |
| return; |
| } |
| |
| // Some calling code invokes |dismissViewControllerAnimated:completion:| |
| // multiple times. Because the BVC is presented, subsequent calls end up |
| // dismissing the BVC itself. This is never what should happen, so check for |
| // this case and return early. It is not enough to check |
| // |self.dismissingModal| because some dismissals do not go through |
| // -[BrowserViewController dismissViewControllerAnimated:completion:|. |
| // TODO(crbug.com/782338): Fix callers and remove this early return. |
| if (self.dismissingModal || self.presentedViewController.isBeingDismissed) { |
| return; |
| } |
| |
| self.dismissingModal = YES; |
| __weak BrowserViewController* weakSelf = self; |
| [super dismissViewControllerAnimated:flag |
| completion:^{ |
| BrowserViewController* strongSelf = weakSelf; |
| strongSelf.dismissingModal = NO; |
| if (completion) |
| completion(); |
| }]; |
| } |
| |
| // The BVC does not define its own presentation context, so any presentation |
| // here ultimately travels up the chain for presentation. |
| - (void)presentViewController:(UIViewController*)viewControllerToPresent |
| animated:(BOOL)flag |
| completion:(void (^)())completion { |
| ProceduralBlock finalCompletionHandler = [completion copy]; |
| // TODO(crbug.com/580098) This is an interim fix for the flicker between the |
| // launch screen and the FRE Animation. The fix is, if the FRE is about to be |
| // presented, to show a temporary view of the launch screen and then remove it |
| // when the controller for the FRE has been presented. This fix should be |
| // removed when the FRE startup code is rewritten. |
| const bool firstRunLaunch = ShouldPresentFirstRunExperience(); |
| // These if statements check that |presentViewController| is being called for |
| // the FRE case. |
| if (firstRunLaunch && |
| [viewControllerToPresent isKindOfClass:[UINavigationController class]]) { |
| UINavigationController* navController = |
| base::mac::ObjCCastStrict<UINavigationController>( |
| viewControllerToPresent); |
| if ([navController.topViewController |
| isMemberOfClass:[WelcomeToChromeViewController class]] || |
| [navController.topViewController |
| isKindOfClass:[PromoStyleViewController class]]) { |
| self.hideStatusBar = YES; |
| |
| // Load view from Launch Screen and add it to window. |
| NSBundle* mainBundle = base::mac::FrameworkBundle(); |
| NSArray* topObjects = [mainBundle loadNibNamed:@"LaunchScreen" |
| owner:self |
| options:nil]; |
| UIViewController* launchScreenController = |
| base::mac::ObjCCastStrict<UIViewController>([topObjects lastObject]); |
| // |launchScreenView| is loaded as an autoreleased object, and is retained |
| // by the |completion| block below. |
| UIView* launchScreenView = launchScreenController.view; |
| launchScreenView.userInteractionEnabled = NO; |
| // TODO(crbug.com/1011155): Displaying the launch screen is a hack to hide |
| // the build up of the UI from the user. To implement the hack, this view |
| // controller uses information that it should not know or care about: this |
| // BVC is contained and its parent bounds to the full screen. |
| launchScreenView.frame = self.parentViewController.view.bounds; |
| [self.parentViewController.view addSubview:launchScreenView]; |
| [launchScreenView setNeedsLayout]; |
| [launchScreenView layoutIfNeeded]; |
| |
| // Replace the completion handler sent to the superclass with one which |
| // removes |launchScreenView| and resets the status bar. If |completion| |
| // exists, it is called from within the new completion handler. |
| __weak BrowserViewController* weakSelf = self; |
| finalCompletionHandler = ^{ |
| [launchScreenView removeFromSuperview]; |
| weakSelf.hideStatusBar = NO; |
| if (completion) |
| completion(); |
| }; |
| } |
| } |
| |
| if ([self.sideSwipeController inSwipe]) { |
| [self.sideSwipeController resetContentView]; |
| } |
| |
| void (^superCall)() = ^{ |
| [super presentViewController:viewControllerToPresent |
| animated:flag |
| completion:finalCompletionHandler]; |
| }; |
| // TODO(crbug.com/965688): The Default Browser Promo is |
| // currently the only presented controller that allows interaction with the |
| // rest of the App while they are being presented. Dismiss it in case the user |
| // or system has triggered another presentation. |
| if ([self.nonModalPromoPresentationDelegate defaultNonModalPromoIsShowing]) { |
| [self.nonModalPromoPresentationDelegate |
| dismissDefaultNonModalPromoAnimated:NO |
| completion:superCall]; |
| |
| } else { |
| superCall(); |
| } |
| } |
| |
| - (BOOL)shouldAutorotate { |
| if (self.presentedViewController.beingPresented || |
| self.presentedViewController.beingDismissed) { |
| // Don't rotate while a presentation or dismissal animation is occurring. |
| return NO; |
| } else if (_sideSwipeController && |
| ![self.sideSwipeController shouldAutorotate]) { |
| // Don't auto rotate if side swipe controller view says not to. |
| return NO; |
| } else { |
| return [super shouldAutorotate]; |
| } |
| } |
| |
| - (UIStatusBarStyle)preferredStatusBarStyle { |
| if ([self canShowTabStrip] && !_isOffTheRecord && |
| !base::FeatureList::IsEnabled(kModernTabStrip)) { |
| return self.tabStripView.frame.origin.y < kTabStripAppearanceOffset |
| ? UIStatusBarStyleDefault |
| : UIStatusBarStyleLightContent; |
| } |
| return _isOffTheRecord ? UIStatusBarStyleLightContent |
| : UIStatusBarStyleDefault; |
| } |
| |
| #pragma mark - ** Private BVC Methods ** |
| |
| #pragma mark - Private Methods: BVC Initialization |
| // BVC initialization |
| // ------------------ |
| // If the BVC is initialized with a valid browser state & browser immediately, |
| // the path is straightforward: functionality is enabled, and the UI is built |
| // when -viewDidLoad is called. |
| // If the BVC is initialized without a browser state or browser, the browser |
| // and browser state may or may not be provided before -viewDidLoad is called. |
| // In most cases, they will not, to improve startup performance. |
| // In order to handle this, initialization of various aspects of BVC have been |
| // broken out into the following functions, which have expectations (enforced |
| // with DCHECKs) regarding |self.browserState|, |self.browser|, and [self |
| // isViewLoaded]. |
| |
| // Updates non-view-related functionality with the given browser and tab |
| // model. |
| // Does not matter whether or not the view has been loaded. |
| // TODO(crbug.com/1272524): Move this all into the init. Update the rest of the |
| // code to assume that if the BVC isn't shutdown, it has a valid Browser. Update |
| // the comments above to reflect reality. |
| - (void)updateWithBrowser:(Browser*)browser { |
| DCHECK(browser); |
| DCHECK(!self.browser); |
| self.browser = browser; |
| _isOffTheRecord = self.browserState->IsOffTheRecord(); |
| |
| _webStateObserverBridge = std::make_unique<web::WebStateObserverBridge>(self); |
| _allWebStateObservationForwarder = |
| std::make_unique<AllWebStateObservationForwarder>( |
| self.browser->GetWebStateList(), _webStateObserverBridge.get()); |
| |
| _webStateListObserver = std::make_unique<WebStateListObserverBridge>(self); |
| self.browser->GetWebStateList()->AddObserver(_webStateListObserver.get()); |
| _URLLoadingObserverBridge = std::make_unique<UrlLoadingObserverBridge>(self); |
| UrlLoadingNotifierBrowserAgent::FromBrowser(self.browser) |
| ->AddObserver(_URLLoadingObserverBridge.get()); |
| |
| WebStateList* webStateList = self.browser->GetWebStateList(); |
| for (int index = 0; index < webStateList->count(); ++index) |
| [self installDelegatesForWebState:webStateList->GetWebStateAt(index)]; |
| |
| // Set the TTS playback controller's WebStateList. |
| // TODO(crbug.com/1272528): Move this somewhere else -- BrowserCoordinator at |
| // least. |
| TextToSpeechPlaybackControllerFactory::GetInstance() |
| ->GetForBrowserState(self.browserState) |
| ->SetWebStateList(self.browser->GetWebStateList()); |
| |
| // When starting the browser with an open tab, it is necessary to reset the |
| // clipsToBounds property of the WKWebView so the page can bleed behind the |
| // toolbar. |
| if (self.currentWebState) { |
| self.currentWebState->GetWebViewProxy().scrollViewProxy.clipsToBounds = NO; |
| } |
| } |
| |
| // On iOS7, iPad should match iOS6 status bar. Install a simple black bar under |
| // the status bar to mimic this layout. |
| - (void)installFakeStatusBar { |
| // This method is called when the view is loaded and when the thumb strip is |
| // installed via addAnimatee -> didAnimateViewRevealFromState -> |
| // installFakeStatusBar. |
| |
| // Remove the _fakeStatusBarView if present. |
| [_fakeStatusBarView removeFromSuperview]; |
| _fakeStatusBarView = nil; |
| |
| if (self.thumbStripEnabled && |
| !fullscreen::features::ShouldUseSmoothScrolling()) { |
| // A fake status bar on the browser view is not necessary when the thumb |
| // strip feature is enabled because the view behind the browser view already |
| // has a dark background. Adding a fake status bar would block the |
| // visibility of the thumb strip thumbnails when moving the browser view. |
| // However, if the Fullscreen Provider is used, then the web content extends |
| // up to behind the tab strip, making the fake status bar necessary. |
| return; |
| } |
| |
| CGRect statusBarFrame = CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 0); |
| _fakeStatusBarView = [[UIView alloc] initWithFrame:statusBarFrame]; |
| [_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth]; |
| if (ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET) { |
| _fakeStatusBarView.backgroundColor = UIColor.blackColor; |
| _fakeStatusBarView.autoresizingMask = UIViewAutoresizingFlexibleWidth; |
| DCHECK(self.contentArea); |
| [self.view insertSubview:_fakeStatusBarView aboveSubview:self.contentArea]; |
| } else { |
| // Add a white bar when there is no tab strip so that the status bar on the |
| // NTP is white. |
| _fakeStatusBarView.backgroundColor = ntp_home::kNTPBackgroundColor(); |
| [self.view insertSubview:_fakeStatusBarView atIndex:0]; |
| } |
| } |
| |
| // Builds the UI parts of tab strip and the toolbar. Does not matter whether |
| // or not browser state and browser are valid. |
| - (void)buildToolbarAndTabStrip { |
| DCHECK([self isViewLoaded]); |
| DCHECK(!_locationBarModelDelegate); |
| |
| // Create the location bar model and controller. |
| // TODO(crbug.com/1329101): Move all of this to the coordinator which owns the |
| // location bar. |
| _locationBarModelDelegate.reset( |
| new LocationBarModelDelegateIOS(self.browser->GetWebStateList())); |
| _locationBarModel = std::make_unique<LocationBarModelImpl>( |
| _locationBarModelDelegate.get(), kMaxURLDisplayChars); |
| |
| // TODO(crbug.com/1329102): Remove |self.helper|. |
| self.helper = [_dependencyFactory newBrowserViewControllerHelper]; |
| |
| // TODO(crbug.com/1329094): Move this coordinator to BrowserCoordinator |
| self.popupMenuCoordinator = |
| [[PopupMenuCoordinator alloc] initWithBaseViewController:self |
| browser:self.browser]; |
| self.popupMenuCoordinator.bubblePresenter = _bubblePresenter; |
| self.popupMenuCoordinator.UIUpdater = _toolbarCoordinatorAdaptor; |
| [self.popupMenuCoordinator start]; |
| |
| // TODO(crbug.com/1329095): Inject this coordinator instead of creating it. |
| PrimaryToolbarCoordinator* topToolbarCoordinator = |
| [[PrimaryToolbarCoordinator alloc] initWithBrowser:self.browser]; |
| self.primaryToolbarCoordinator = topToolbarCoordinator; |
| topToolbarCoordinator.delegate = self; |
| topToolbarCoordinator.popupPresenterDelegate = self; |
| topToolbarCoordinator.longPressDelegate = self.popupMenuCoordinator; |
| [topToolbarCoordinator start]; |
| // TODO(crbug.com/1329095): Inject this coordinator instead of creating it. |
| SecondaryToolbarCoordinator* bottomToolbarCoordinator = |
| [[SecondaryToolbarCoordinator alloc] initWithBrowser:self.browser]; |
| self.secondaryToolbarCoordinator = bottomToolbarCoordinator; |
| bottomToolbarCoordinator.longPressDelegate = self.popupMenuCoordinator; |
| |
| // TODO(crbug.com/880672): Finish ToolbarContainer work. |
| if (base::FeatureList::IsEnabled( |
| toolbar_container::kToolbarContainerEnabled)) { |
| self.secondaryToolbarContainerCoordinator = |
| [[ToolbarContainerCoordinator alloc] |
| initWithBrowser:self.browser |
| type:ToolbarContainerType::kSecondary]; |
| self.secondaryToolbarContainerCoordinator.toolbarCoordinators = |
| @[ bottomToolbarCoordinator ]; |
| [self.secondaryToolbarContainerCoordinator start]; |
| } else { |
| [bottomToolbarCoordinator start]; |
| } |
| |
| [_toolbarCoordinatorAdaptor addToolbarCoordinator:topToolbarCoordinator]; |
| [_toolbarCoordinatorAdaptor addToolbarCoordinator:bottomToolbarCoordinator]; |
| |
| self.sideSwipeController.toolbarInteractionHandler = self.toolbarInterface; |
| self.sideSwipeController.primaryToolbarSnapshotProvider = |
| self.primaryToolbarCoordinator; |
| self.sideSwipeController.secondaryToolbarSnapshotProvider = |
| self.secondaryToolbarCoordinator; |
| |
| [self updateBroadcastState]; |
| if (_voiceSearchController) { |
| // TODO(crbug.com/1329089): Inject LoadQueryCommands as a handler and pass |
| // into the voice search controller. |
| _voiceSearchController.dispatcher = |
| HandlerForProtocol(self.commandDispatcher, LoadQueryCommands); |
| } |
| |
| // TODO(crbug.com/1329097): Move tab strip setup to BrowserCoordinator. |
| // Potentially inject these coordinators as a stopgap. |
| if (ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET) { |
| if (base::FeatureList::IsEnabled(kModernTabStrip)) { |
| self.tabStripCoordinator = |
| [[TabStripCoordinator alloc] initWithBrowser:self.browser]; |
| [self.tabStripCoordinator start]; |
| } else { |
| self.legacyTabStripCoordinator = [[TabStripLegacyCoordinator alloc] |
| initWithBaseViewController:self |
| browser:self.browser]; |
| self.legacyTabStripCoordinator.presentationProvider = self; |
| self.legacyTabStripCoordinator.animationWaitDuration = |
| kLegacyFullscreenControllerToolbarAnimationDuration; |
| self.legacyTabStripCoordinator.longPressDelegate = |
| self.popupMenuCoordinator; |
| |
| [self.legacyTabStripCoordinator start]; |
| } |
| } |
| } |
| |
| // Called by NSNotificationCenter when the view's window becomes key to account |
| // for topLayoutGuide length updates. |
| - (void)updateToolbarHeightForKeyWindow { |
| // Update the toolbar height to account for |topLayoutGuide| changes. |
| self.primaryToolbarHeightConstraint.constant = |
| [self primaryToolbarHeightWithInset]; |
| // Stop listening for the key window notification. |
| [[NSNotificationCenter defaultCenter] |
| removeObserver:self |
| name:UIWindowDidBecomeKeyNotification |
| object:self.view.window]; |
| } |
| |
| // The height of the primary toolbar with the top safe area inset included. |
| - (CGFloat)primaryToolbarHeightWithInset { |
| UIView* primaryToolbar = self.primaryToolbarCoordinator.viewController.view; |
| CGFloat intrinsicHeight = primaryToolbar.intrinsicContentSize.height; |
| if (!IsSplitToolbarMode(self)) { |
| // When the adaptive toolbar is unsplit, add a margin. |
| intrinsicHeight += kTopToolbarUnsplitMargin; |
| } |
| // If the primary toolbar is not the topmost header, it does not overlap with |
| // the unsafe area. |
| // TODO(crbug.com/806437): Update implementation such that this calculates the |
| // topmost header's height. |
| UIView* topmostHeader = [self.headerViews firstObject].view; |
| if (primaryToolbar != topmostHeader) |
| return intrinsicHeight; |
| // If the primary toolbar is topmost, subtract the height of the portion of |
| // the unsafe area. |
| CGFloat unsafeHeight = self.rootSafeAreaInsets.top; |
| |
| // The topmost header is laid out |headerOffset| from the top of |view|, so |
| // subtract that from the unsafe height. |
| unsafeHeight -= self.headerOffset; |
| return intrinsicHeight + unsafeHeight; |
| } |
| |
| // The height of the secondary toolbar with the bottom safe area inset included. |
| // Returns 0 if the toolbar should be hidden. |
| - (CGFloat)secondaryToolbarHeightWithInset { |
| if (!IsSplitToolbarMode(self)) |
| return 0; |
| |
| UIView* secondaryToolbar = |
| self.secondaryToolbarCoordinator.viewController.view; |
| // Add the safe area inset to the toolbar height. |
| CGFloat unsafeHeight = self.rootSafeAreaInsets.bottom; |
| return secondaryToolbar.intrinsicContentSize.height + unsafeHeight; |
| } |
| |
| - (void)addConstraintsToTabStrip { |
| if (!base::FeatureList::IsEnabled(kModernTabStrip)) |
| return; |
| |
| self.tabStripView.translatesAutoresizingMaskIntoConstraints = NO; |
| [NSLayoutConstraint activateConstraints:@[ |
| [self.view.safeAreaLayoutGuide.topAnchor |
| constraintEqualToAnchor:self.tabStripView.topAnchor], |
| [self.view.safeAreaLayoutGuide.leadingAnchor |
| constraintEqualToAnchor:self.tabStripView.leadingAnchor], |
| [self.view.safeAreaLayoutGuide.trailingAnchor |
| constraintEqualToAnchor:self.tabStripView.trailingAnchor], |
| [self.tabStripView.heightAnchor constraintEqualToConstant:kTabStripHeight], |
| ]]; |
| } |
| |
| // Sets up the constraints on the toolbar. |
| - (void)addConstraintsToPrimaryToolbar { |
| NSLayoutYAxisAnchor* topAnchor; |
| // On iPad, the toolbar is underneath the tab strip. |
| // On iPhone, it is underneath the top of the screen. |
| if ([self canShowTabStrip]) { |
| topAnchor = self.tabStripView.bottomAnchor; |
| } else { |
| topAnchor = [self view].topAnchor; |
| } |
| |
| // Only add leading and trailing constraints once as they are never updated. |
| // This uses the existence of |primaryToolbarOffsetConstraint| as a proxy for |
| // whether we've already added the leading and trailing constraints. |
| if (!self.primaryToolbarOffsetConstraint) { |
| [NSLayoutConstraint activateConstraints:@[ |
| [self.primaryToolbarCoordinator.viewController.view.leadingAnchor |
| constraintEqualToAnchor:[self view].leadingAnchor], |
| [self.primaryToolbarCoordinator.viewController.view.trailingAnchor |
| constraintEqualToAnchor:[self view].trailingAnchor], |
| ]]; |
| } |
| |
| // Offset and Height can be updated, so reset first. |
| self.primaryToolbarOffsetConstraint.active = NO; |
| self.primaryToolbarHeightConstraint.active = NO; |
| |
| // Create a constraint for the vertical positioning of the toolbar. |
| UIView* primaryView = self.primaryToolbarCoordinator.viewController.view; |
| self.primaryToolbarOffsetConstraint = |
| [primaryView.topAnchor constraintEqualToAnchor:topAnchor]; |
| |
| // Create a constraint for the height of the toolbar to include the unsafe |
| // area height. |
| self.primaryToolbarHeightConstraint = [primaryView.heightAnchor |
| constraintEqualToConstant:[self primaryToolbarHeightWithInset]]; |
| |
| self.primaryToolbarOffsetConstraint.active = YES; |
| self.primaryToolbarHeightConstraint.active = YES; |
| } |
| |
| - (void)addConstraintsToSecondaryToolbar { |
| if (self.secondaryToolbarCoordinator) { |
| // Create a constraint for the height of the toolbar to include the unsafe |
| // area height. |
| UIView* toolbarView = self.secondaryToolbarCoordinator.viewController.view; |
| self.secondaryToolbarHeightConstraint = [toolbarView.heightAnchor |
| constraintEqualToConstant:[self secondaryToolbarHeightWithInset]]; |
| self.secondaryToolbarHeightConstraint.active = YES; |
| AddSameConstraintsToSides( |
| self.secondaryToolbarContainerView, toolbarView, |
| LayoutSides::kBottom | LayoutSides::kLeading | LayoutSides::kTrailing); |
| |
| // Constrain the container view to the bottom of self.view, and add a |
| // constant height constraint such that the container's frame is equal to |
| // that of the secondary toolbar at a fullscreen progress of 1.0. |
| UIView* containerView = self.secondaryToolbarContainerView; |
| self.secondaryToolbarNoFullscreenHeightConstraint = |
| [containerView.heightAnchor |
| constraintEqualToConstant:[self secondaryToolbarHeightWithInset]]; |
| self.secondaryToolbarNoFullscreenHeightConstraint.active = YES; |
| AddSameConstraintsToSides( |
| self.view, containerView, |
| LayoutSides::kBottom | LayoutSides::kLeading | LayoutSides::kTrailing); |
| } |
| } |
| |
| // Adds constraints to the secondary toolbar container anchoring it to the |
| // bottom of the browser view. |
| - (void)addConstraintsToSecondaryToolbarContainer { |
| if (!self.secondaryToolbarContainerCoordinator) |
| return; |
| |
| // Constrain the container to the bottom of the view. |
| UIView* containerView = |
| self.secondaryToolbarContainerCoordinator.viewController.view; |
| AddSameConstraintsToSides( |
| self.view, containerView, |
| LayoutSides::kBottom | LayoutSides::kLeading | LayoutSides::kTrailing); |
| } |
| |
| // Adds constraints to the primary and secondary toolbars, anchoring them to the |
| // top and bottom of the browser view. |
| - (void)addConstraintsToToolbar { |
| [self addConstraintsToPrimaryToolbar]; |
| // TODO(crbug.com/880672): Finish ToolbarContainer work. |
| if (base::FeatureList::IsEnabled( |
| toolbar_container::kToolbarContainerEnabled)) { |
| [self addConstraintsToSecondaryToolbarContainer]; |
| } else { |
| [self addConstraintsToSecondaryToolbar]; |
| } |
| [[self view] layoutIfNeeded]; |
| } |
| |
| // Updates view-related functionality with the given browser and browser |
| // state. The view must have been loaded. Uses |self.browserState| and |
| // |self.browser|. |
| - (void)addUIFunctionalityForBrowserAndBrowserState { |
| DCHECK(self.browserState); |
| DCHECK(_locationBarModel); |
| DCHECK(self.browser); |
| DCHECK([self isViewLoaded]); |
| |
| [self.sideSwipeController addHorizontalGesturesToView:self.view]; |
| |
| // DownloadManagerCoordinator is already created. |
| DCHECK(_downloadManagerCoordinator); |
| _downloadManagerCoordinator.bottomMarginHeightAnchor = |
| [NamedGuide guideWithName:kSecondaryToolbarGuide view:self.contentArea] |
| .heightAnchor; |
| |
| // TODO(crbug.com/1329089): Inject this handler. |
| self.helpHandler = |
| HandlerForProtocol(self.browser->GetCommandDispatcher(), HelpCommands); |
| |
| // TODO(crbug.com/1329098): Assuming all of the coordinators are in |
| // BrowserCoordinator, move this setup there as well. |
| self.primaryToolbarCoordinator.longPressDelegate = self.popupMenuCoordinator; |
| self.secondaryToolbarCoordinator.longPressDelegate = |
| self.popupMenuCoordinator; |
| if (!base::FeatureList::IsEnabled(kModernTabStrip)) { |
| self.legacyTabStripCoordinator.longPressDelegate = |
| self.popupMenuCoordinator; |
| } |
| |
| // TODO(crbug.com/1329089): Inject this handler. |
| self.omniboxHandler = |
| HandlerForProtocol(self.browser->GetCommandDispatcher(), OmniboxCommands); |
| } |
| |
| // Sets up the frame for the fake status bar. View must be loaded. |
| - (void)setupStatusBarLayout { |
| CGFloat topInset = self.rootSafeAreaInsets.top; |
| |
| // Update the fake toolbar background height. |
| CGRect fakeStatusBarFrame = _fakeStatusBarView.frame; |
| fakeStatusBarFrame.size.height = topInset; |
| _fakeStatusBarView.frame = fakeStatusBarFrame; |
| } |
| |
| // Sets the correct frame and hierarchy for subviews and helper views. Only |
| // insert views on |initialLayout|. |
| - (void)setUpViewLayout:(BOOL)initialLayout { |
| DCHECK([self isViewLoaded]); |
| |
| [self setupStatusBarLayout]; |
| |
| if (initialLayout) { |
| // Add the toolbars as child view controllers. |
| [self addChildViewController:self.primaryToolbarCoordinator.viewController]; |
| if (self.secondaryToolbarCoordinator) { |
| // TODO(crbug.com/880672): Finish ToolbarContainer work. |
| if (base::FeatureList::IsEnabled( |
| toolbar_container::kToolbarContainerEnabled)) { |
| [self addChildViewController:self.secondaryToolbarContainerCoordinator |
| .viewController]; |
| } else { |
| [self addChildViewController:self.secondaryToolbarCoordinator |
| .viewController]; |
| } |
| } |
| |
| // Add the primary toolbar. On iPad, it should be in front of the tab strip |
| // because the tab strip slides behind it when showing the thumb strip. |
| UIView* primaryToolbarView = |
| self.primaryToolbarCoordinator.viewController.view; |
| if (ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET) { |
| if (base::FeatureList::IsEnabled(kModernTabStrip) && |
| self.tabStripCoordinator) { |
| [self addChildViewController:self.tabStripCoordinator.viewController]; |
| self.tabStripView = self.tabStripCoordinator.view; |
| [self.view addSubview:self.tabStripView]; |
| [self addConstraintsToTabStrip]; |
| } |
| [self.view insertSubview:primaryToolbarView |
| aboveSubview:self.tabStripView]; |
| } else { |
| [self.view addSubview:primaryToolbarView]; |
| } |
| |
| // Add the secondary toolbar. |
| if (self.secondaryToolbarCoordinator) { |
| // TODO(crbug.com/880672): Finish ToolbarContainer work. |
| if (base::FeatureList::IsEnabled( |
| toolbar_container::kToolbarContainerEnabled)) { |
| // Add the container view to the hierarchy. |
| UIView* containerView = |
| self.secondaryToolbarContainerCoordinator.viewController.view; |
| [self.view insertSubview:containerView aboveSubview:primaryToolbarView]; |
| } else { |
| // Create the container view for the secondary toolbar and add it to |
| // the hierarchy |
| UIView* container = [[LegacyToolbarContainerView alloc] init]; |
| container.translatesAutoresizingMaskIntoConstraints = NO; |
| [container |
| addSubview:self.secondaryToolbarCoordinator.viewController.view]; |
| [self.view insertSubview:container aboveSubview:primaryToolbarView]; |
| self.secondaryToolbarContainerView = container; |
| } |
| } |
| |
| // Create the NamedGuides and add them to the browser view. |
| NSArray<GuideName*>* guideNames = @[ |
| kContentAreaGuide, |
| kPrimaryToolbarGuide, |
| kBadgeOverflowMenuGuide, |
| kOmniboxGuide, |
| kOmniboxLeadingImageGuide, |
| kOmniboxTextFieldGuide, |
| kBackButtonGuide, |
| kForwardButtonGuide, |
| kToolsMenuGuide, |
| kTabSwitcherGuide, |
| kNewTabButtonGuide, |
| kSecondaryToolbarGuide, |
| kVoiceSearchButtonGuide, |
| kDiscoverFeedHeaderMenuGuide, |
| kPrimaryToolbarLocationViewGuide, |
| ]; |
| AddNamedGuidesToView(guideNames, self.view); |
| |
| // Configure the content area guide. |
| NamedGuide* contentAreaGuide = [NamedGuide guideWithName:kContentAreaGuide |
| view:self.view]; |
| |
| // TODO(crbug.com/1136765): Sometimes, |contentAreaGuide| and |
| // |primaryToolbarView| aren't in the same view hierarchy; this seems to be |
| // impossible, but it does still happen. This will cause an exception in |
| // when activiating these constraints. To gather more information about this |
| // state, explciitly check the view hierarchy roots. Local variables are |
| // used so that the CHECK message is cleared. |
| UIView* rootViewForToolbar = ViewHierarchyRootForView(primaryToolbarView); |
| UIView* rootViewForContentGuide = |
| ViewHierarchyRootForView(contentAreaGuide.owningView); |
| CHECK_EQ(rootViewForToolbar, rootViewForContentGuide); |
| |
| // Constrain top to bottom of top toolbar. |
| [contentAreaGuide.topAnchor |
| constraintEqualToAnchor:primaryToolbarView.bottomAnchor] |
| .active = YES; |
| |
| LayoutSides contentSides = LayoutSides::kLeading | LayoutSides::kTrailing; |
| if (self.secondaryToolbarCoordinator) { |
| // If there's a bottom toolbar, the content area guide is constrained to |
| // its top. |
| UIView* secondaryToolbarView = |
| self.secondaryToolbarCoordinator.viewController.view; |
| [contentAreaGuide.bottomAnchor |
| constraintEqualToAnchor:secondaryToolbarView.topAnchor] |
| .active = YES; |
| } else { |
| // Otherwise, the content area guide is constrained to self.view's bootom |
| // along with its sides; |
| contentSides = contentSides | LayoutSides::kBottom; |
| } |
| AddSameConstraintsToSides(self.view, contentAreaGuide, contentSides); |
| |
| // Complete child UIViewController containment flow now that the views are |
| // finished being added. |
| [self.tabStripCoordinator.viewController |
| didMoveToParentViewController:self]; |
| [self.primaryToolbarCoordinator.viewController |
| didMoveToParentViewController:self]; |
| if (self.secondaryToolbarCoordinator) { |
| // TODO(crbug.com/880672): Finish ToolbarContainer work. |
| if (base::FeatureList::IsEnabled( |
| toolbar_container::kToolbarContainerEnabled)) { |
| [self.secondaryToolbarContainerCoordinator.viewController |
| didMoveToParentViewController:self]; |
| } else { |
| [self.secondaryToolbarCoordinator.viewController |
| didMoveToParentViewController:self]; |
| } |
| } |
| } |
| |
| // Resize the typing shield to cover the entire browser view and bring it to |
| // the front. |
| self.typingShield.frame = self.contentArea.frame; |
| [self.view bringSubviewToFront:self.typingShield]; |
| |
| // Move the overlay containers in front of the hierarchy. |
| [self updateOverlayContainerOrder]; |
| } |
| |
| // TODO(crbug.com/1329088): Have a mediator inject the view to be displayed, not |
| // a webstate. Makes |webState| the currently visible WebState, displaying its |
| // view. |
| - (void)displayWebState:(web::WebState*)webState { |
| DCHECK(webState); |
| [self loadViewIfNeeded]; |
| if (IsSingleNtpEnabled()) { |
| self.ntpCoordinator.webState = webState; |
| } |
| |
| // Set this before triggering any of the possible page loads below. |
| webState->SetKeepRenderProcessAlive(true); |
| |
| if (!self.inNewTabAnimation) { |
| // TODO(crbug.com/1329087): -updateToolbar will move out of the BVC; make |
| // sure this comment remains accurate. Hide findbar. |updateToolbar| will |
| // restore the findbar later. |
| [self.dispatcher hideFindUI]; |
| [self.textZoomHandler hideTextZoomUI]; |
| |
| // Make new content visible, resizing it first as the orientation may |
| // have changed from the last time it was displayed. |
| CGRect webStateViewFrame = self.contentArea.bounds; |
| if (fullscreen::features::ShouldUseSmoothScrolling()) { |
| // If the view was translated for the thumb strip, make sure to re-apply |
| // that translation here. |
| if (self.viewTranslatedForSmoothScrolling) { |
| CGFloat toolbarHeight = [self expandedTopToolbarHeight]; |
| webStateViewFrame = UIEdgeInsetsInsetRect( |
| webStateViewFrame, UIEdgeInsetsMake(toolbarHeight, 0, 0, 0)); |
| } |
| } else { |
| // If the Smooth Scrolling is on, the WebState view is not |
| // resized, and should always match the bounds of the content area. When |
| // the provider is not initialized, viewport insets resize the webview, so |
| // they should be accounted for here to prevent animation jitter. |
| UIEdgeInsets viewportInsets = |
| self.fullscreenController->GetCurrentViewportInsets(); |
| webStateViewFrame = |
| UIEdgeInsetsInsetRect(webStateViewFrame, viewportInsets); |
| } |
| [self viewForWebState:webState].frame = webStateViewFrame; |
| |
| [self updateToolbarState]; |
| NewTabPageTabHelper* NTPHelper = |
| NewTabPageTabHelper::FromWebState(webState); |
| if (NTPHelper && NTPHelper->IsActive()) { |
| NewTabPageCoordinator* coordinator = |
| [self ntpCoordinatorForWebState:webState]; |
| UIViewController* viewController = coordinator.viewController; |
| [coordinator ntpDidChangeVisibility:YES]; |
| viewController.view.frame = [self ntpFrameForWebState:webState]; |
| [viewController.view layoutIfNeeded]; |
| // TODO(crbug.com/873729): For a newly created WebState, the session will |
| // not be restored until LoadIfNecessary call. Remove when fixed. |
| webState->GetNavigationManager()->LoadIfNecessary(); |
| self.browserContainerViewController.contentView = nil; |
| self.browserContainerViewController.contentViewController = |
| viewController; |
| [coordinator constrainDiscoverHeaderMenuButtonNamedGuide]; |
| } else { |
| self.browserContainerViewController.contentView = |
| [self viewForWebState:webState]; |
| } |
| // Resize horizontal viewport if Smooth Scrolling is on. |
| if (fullscreen::features::ShouldUseSmoothScrolling()) { |
| self.fullscreenController->ResizeHorizontalViewport(); |
| } |
| } |
| // TODO(crbug.com/1329087): Move this update to the toolbar coordinator, |
| // somehow. |
| [self updateToolbar]; |
| |
| // TODO(crbug.com/971364): The webState is not necessarily added to the view |
| // hierarchy, even though the bookkeeping says that the WebState is visible. |
| // Do not DCHECK([webState->GetView() window]) here since this is a known |
| // issue. |
| // TODO(crbug.com/1329088): This update should happen in the mediator, not |
| // here. |
| webState->WasShown(); |
| } |
| |
| // Initializes the bookmark interaction controller if not already initialized. |
| - (void)initializeBookmarkInteractionController { |
| if (_bookmarkInteractionController) |
| return; |
| // TODO(crbug.com/1329103): Remove BookmarkInteractionController from BVC. |
| _bookmarkInteractionController = |
| [[BookmarkInteractionController alloc] initWithBrowser:self.browser |
| parentController:self]; |
| } |
| |
| - (void)updateOverlayContainerOrder { |
| // Both infobar overlay container views should exist in front of the entire |
| // browser UI, and the banner container should appear behind the modal |
| // container. |
| [self bringOverlayContainerToFront: |
| self.infobarBannerOverlayContainerViewController]; |
| [self bringOverlayContainerToFront: |
| self.infobarModalOverlayContainerViewController]; |
| } |
| |
| - (void)bringOverlayContainerToFront: |
| (UIViewController*)containerViewController { |
| [self.view bringSubviewToFront:containerViewController.view]; |
| // If |containerViewController| is presenting a view over its current context, |
| // its presentation container view is added as a sibling to |
| // |containerViewController|'s view. This presented view should be brought in |
| // front of the container view. |
| UIView* presentedContainerView = |
| containerViewController.presentedViewController.presentationController |
| .containerView; |
| if (presentedContainerView.superview == self.view) |
| [self.view bringSubviewToFront:presentedContainerView]; |
| } |
| |
| #pragma mark - Private Methods: UI Configuration, update and Layout |
| |
| // TODO(crbug.com/1329087): Move this update to primaryToolbarCoordinator, |
| // triggered by mediators as apprpriate. Update the state of back and forward |
| // buttons, hiding the forward button if there is nowhere to go. Assumes the |
| // model's current tab is up to date. |
| - (void)updateToolbar { |
| // If the BVC has been partially torn down for low memory, wait for the |
| // view rebuild to handle toolbar updates. |
| if (!(self.helper && self.browserState)) |
| return; |
| |
| web::WebState* webState = self.currentWebState; |
| if (!webState) |
| return; |
| |
| // TODO(crbug.com/1328039): Remove all use of the prerender service from BVC |
| BOOL isPrerendered = |
| (_prerenderService && _prerenderService->IsLoadingPrerender()); |
| // TODO(crbug.com/1329102): Change -isToolbarLoading method to a free |
| // function. |
| if (isPrerendered && ![self.helper isToolbarLoading:self.currentWebState]) |
| [self.primaryToolbarCoordinator showPrerenderingAnimation]; |
| |
| [self.dispatcher showFindUIIfActive]; |
| [self.textZoomHandler showTextZoomUIIfActive]; |
| |
| BOOL hideToolbar = NO; |
| if (webState) { |
| // There are times when the NTP can be hidden but before the visibleURL |
| // changes. This can leave the BVC in a blank state where only the bottom |
| // toolbar is visible. Instead, if possible, use the NewTabPageTabHelper |
| // IsActive() value rather than checking -IsVisibleURLNewTabPage. |
| NewTabPageTabHelper* NTPHelper = |
| NewTabPageTabHelper::FromWebState(webState); |
| BOOL isNTP = NTPHelper && NTPHelper->IsActive(); |
| // Hide the toolbar when displaying content suggestions without the tab |
| // strip, without the focused omnibox, and for UI Refresh, only when in |
| // split toolbar mode. |
| hideToolbar = isNTP && !_isOffTheRecord && |
| ![self.primaryToolbarCoordinator isOmniboxFirstResponder] && |
| ![self.primaryToolbarCoordinator showingOmniboxPopup] && |
| ![self canShowTabStrip] && IsSplitToolbarMode(self); |
| } |
| [self.primaryToolbarCoordinator.viewController.view setHidden:hideToolbar]; |
| } |
| |
| // Starts or stops broadcasting the toolbar UI and main content UI depending on |
| // whether the BVC is visible and active. |
| - (void)updateBroadcastState { |
| self.broadcasting = self.active && self.viewVisible; |
| } |
| |
| // Dismisses popups and modal dialogs that are displayed above the BVC upon size |
| // changes (e.g. rotation, resizing,…) or when the accessibility escape gesture |
| // is performed. |
| // TODO(crbug.com/522721): Support size changes for all popups and modal |
| // dialogs. |
| - (void)dismissPopups { |
| // The dispatcher may not be fully connected during shutdown, so selectors may |
| // be unrecognized. |
| if (_isShutdown) |
| return; |
| |
| // TODO(crbug.com/1323764): This will need to be called on the |
| // PopupMenuCommands handler. |
| [self.dispatcher dismissPopupMenuAnimated:NO]; |
| [self.helpHandler hideAllHelpBubbles]; |
| } |
| |
| // Returns the footer view if one exists (e.g. the voice search bar). |
| - (UIView*)footerView { |
| return self.secondaryToolbarCoordinator.viewController.view; |
| } |
| |
| // Returns the appropriate frame for the NTP. |
| - (CGRect)ntpFrameForWebState:(web::WebState*)webState { |
| NewTabPageTabHelper* NTPHelper = NewTabPageTabHelper::FromWebState(webState); |
| DCHECK(NTPHelper && NTPHelper->IsActive()); |
| // NTP is laid out only in the visible part of the screen. |
| UIEdgeInsets viewportInsets = UIEdgeInsetsZero; |
| if (!IsRegularXRegularSizeClass(self)) { |
| viewportInsets.bottom = [self secondaryToolbarHeightWithInset]; |
| } |
| |
| // Add toolbar margin to the frame for every scenario except compact-width |
| // non-otr, as that is the only case where there isn't a primary toolbar. |
| // (see crbug.com/1063173) |
| if (!IsSplitToolbarMode(self) || self.isOffTheRecord) { |
| viewportInsets.top = [self expandedTopToolbarHeight]; |
| } |
| return UIEdgeInsetsInsetRect(self.contentArea.bounds, viewportInsets); |
| } |
| |
| // Sets the frame for the headers. |
| - (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers |
| atOffset:(CGFloat)headerOffset { |
| CGFloat height = self.headerOffset; |
| for (HeaderDefinition* header in headers) { |
| CGFloat yOrigin = height - headerOffset; |
| BOOL isPrimaryToolbar = |
| header.view == self.primaryToolbarCoordinator.viewController.view; |
| // Make sure the toolbarView's constraints are also updated. Leaving the |
| // -setFrame call to minimize changes in this CL -- otherwise the way |
| // toolbar_view manages it's alpha changes would also need to be updated. |
| // TODO(crbug.com/778822): This can be cleaned up when the new fullscreen |
| // is enabled. |
| if (isPrimaryToolbar && ![self canShowTabStrip]) { |
| self.primaryToolbarOffsetConstraint.constant = yOrigin; |
| } |
| CGRect frame = [header.view frame]; |
| frame.origin.y = yOrigin; |
| [header.view setFrame:frame]; |
| if (header.behaviour != Overlap) |
| height += CGRectGetHeight(frame); |
| |
| if (header.view == self.tabStripView) |
| [self setNeedsStatusBarAppearanceUpdate]; |
| } |
| } |
| |
| - (UIView*)viewForWebState:(web::WebState*)webState { |
| if (!webState) |
| return nil; |
| NewTabPageTabHelper* NTPHelper = NewTabPageTabHelper::FromWebState(webState); |
| if (NTPHelper && NTPHelper->IsActive()) { |
| return [self ntpCoordinatorForWebState:webState].viewController.view; |
| } |
| DCHECK(self.browser->GetWebStateList()->GetIndexOfWebState(webState) != |
| WebStateList::kInvalidIndex); |
| TabUsageRecorderBrowserAgent* tabUsageRecoder = |
| TabUsageRecorderBrowserAgent::FromBrowser(_browser); |
| // TODO(crbug.com/904588): Move |RecordPageLoadStart| to TabUsageRecorder. |
| if (webState->IsEvicted() && tabUsageRecoder) { |
| tabUsageRecoder->RecordPageLoadStart(webState); |
| } |
| if (!webState->IsCrashed()) { |
| // Load the page if it was evicted by browsing data clearing logic. |
| webState->GetNavigationManager()->LoadIfNecessary(); |
| } |
| return webState->GetView(); |
| } |
| |
| #pragma mark - Private Methods: Tap handling |
| |
| // Record the last tap point based on the |originPoint| (if any) passed in |
| // command. |
| - (void)setLastTapPointFromCommand:(CGPoint)originPoint { |
| if (CGPointEqualToPoint(originPoint, CGPointZero)) { |
| _lastTapPoint = CGPointZero; |
| } else { |
| _lastTapPoint = [self.view.window convertPoint:originPoint |
| toView:self.view]; |
| } |
| _lastTapTime = CACurrentMediaTime(); |
| } |
| |
| // Returns the last stored |_lastTapPoint| if it's been set within the past |
| // second. |
| - (CGPoint)lastTapPoint { |
| if (CACurrentMediaTime() - _lastTapTime < 1) { |
| return _lastTapPoint; |
| } |
| return CGPointZero; |
| } |
| |
| // Store the tap CGPoint in |_lastTapPoint| and the current timestamp. |
| - (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer { |
| if (_isShutdown) { |
| return; |
| } |
| UIView* view = gestureRecognizer.view; |
| CGPoint viewCoordinate = [gestureRecognizer locationInView:view]; |
| _lastTapPoint = [[view superview] convertPoint:viewCoordinate |
| toView:self.view]; |
| _lastTapTime = CACurrentMediaTime(); |
| |
| // This is a workaround for a bug in iOS multiwindow, in which you can touch a |
| // webView without the window getting the keyboard focus. |
| // The result is that a field in the new window gains focus, but keyboard |
| // typing continue to happen in the other window. |
| // TODO(crbug.com/1109124): Remove this workaround. |
| SceneStateBrowserAgent::FromBrowser(self.browser) |
| ->GetSceneState() |
| .appState.lastTappedWindow = view.window; |
| } |
| |
| #pragma mark - Private Methods: Tab creation and selection |
| |
| // DEPRECATED -- Do not add further logic to this method. |
| // Add all delegates to the provided |webState|. |
| // Unregistration happens when the WebState is removed from the WebStateList. |
| // TODO(crbug.com/1290819): Remove this method. |
| - (void)installDelegatesForWebState:(web::WebState*)webState { |
| // If the WebState is unrealized, don't install the delegate. Instead they |
| // will be installed when -webStateRealized: method is called. |
| if (!webState->IsRealized()) |
| return; |
| |
| // TODO(crbug.com/1328039): Remove all use of the prerender service from BVC |
| // There should be no pre-rendered Tabs for this BrowserState. |
| DCHECK(!_prerenderService || |
| !_prerenderService->IsWebStatePrerendered(webState)); |
| |
| // TODO(crbug.com/1272473): Remove CaptivePortalTabHelper from the BVC. |
| CaptivePortalTabHelper::CreateForWebState(webState, self); |
| |
| NewTabPageTabHelper::FromWebState(webState)->SetDelegate(self); |
| } |
| |
| // DEPRECATED -- Do not add further logic to this method. |
| // Remove delegates from the provided |webState|. |
| // TODO(crbug.com/1290819): Remove this method. |
| - (void)uninstallDelegatesForWebState:(web::WebState*)webState { |
| // If the WebState is unrealized, then the delegate had not been installed |
| // and thus don't need to be uninstalled. |
| if (!webState->IsRealized()) |
| return; |
| |
| // TODO(crbug.com/1300911): Have BrowserCoordinator manage the NTP. |
| // No need to stop _ntpCoordinator with Single NTP enabled since shutdown will |
| // do that. In addition, uninstallDelegatesForWebState: is called for |
| // individual WebState removals, which should not trigger a stop. |
| if (!IsSingleNtpEnabled()) { |
| auto iterator = _ntpCoordinatorsForWebStates.find(webState); |
| if (iterator != _ntpCoordinatorsForWebStates.end()) { |
| [iterator->second stop]; |
| _ntpCoordinatorsForWebStates.erase(iterator); |
| } |
| } |
| NewTabPageTabHelper::FromWebState(webState)->SetDelegate(nil); |
| } |
| |
| // Called when a |webState| is selected in the WebStateList. Make any required |
| // view changes. The notification will not be sent when the |webState| is |
| // already the selected WebState. |notifyToolbar| indicates whether the toolbar |
| // is notified that the webState has changed. |
| - (void)webStateSelected:(web::WebState*)webState |
| notifyToolbar:(BOOL)notifyToolbar { |
| DCHECK(webState); |
| |
| // Ignore changes while the tab stack view is visible (or while suspended). |
| // The display will be refreshed when this view becomes active again. |
| if (!self.visible || !self.webUsageEnabled) |
| return; |
| |
| // TODO(crbug.com/1329088): Trigger this update from the mediator, or (as an |
| // interm step) pass the view to be displayed instead. |
| [self displayWebState:webState]; |
| |
| // TODO(crbug.com/1329109): Move this to a browser agent or web event |
| // mediator. |
| if (_expectingForegroundTab && !self.inNewTabAnimation) { |
| // Now that the new tab has been displayed, return to normal. Rather than |
| // keep a reference to the previous tab, just turn off preview mode for all |
| // tabs (since doing so is a no-op for the tabs that don't have it set). |
| _expectingForegroundTab = NO; |
| |
| WebStateList* webStateList = self.browser->GetWebStateList(); |
| for (int index = 0; index < webStateList->count(); ++index) { |
| web::WebState* webState = webStateList->GetWebStateAt(index); |
| PagePlaceholderTabHelper::FromWebState(webState) |
| ->CancelPlaceholderForNextNavigation(); |
| } |
| } |
| } |
| |
| #pragma mark - Private Methods: Voice Search |
| |
| // Lazily instantiates |_voiceSearchController|. |
| - (void)ensureVoiceSearchControllerCreated { |
| if (_voiceSearchController) |
| return; |
| |
| // TODO(crbug.com/1329104): Move voice search controller to |
| // BrowserCoordinator, potentially refactoring to a coordinator. |
| _voiceSearchController = |
| ios::provider::CreateVoiceSearchController(self.browser); |
| if (self.primaryToolbarCoordinator) { |
| _voiceSearchController.dispatcher = |
| HandlerForProtocol(self.commandDispatcher, LoadQueryCommands); |
| } |
| } |
| |
| #pragma mark - Private Methods: Reading List |
| // TODO(crbug.com/1272540): Remove these methods from the BVC. |
| |
| // Adds the given urls to the reading list. |
| - (void)addURLsToReadingList:(NSArray<URLWithTitle*>*)URLs { |
| for (URLWithTitle* urlWithTitle in URLs) { |
| [self addURLToReadingList:urlWithTitle.URL withTitle:urlWithTitle.title]; |
| } |
| |
| [self.dispatcher triggerToolsMenuButtonAnimation]; |
| |
| TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess); |
| |
| NSString* text = |
| l10n_util::GetNSString(IDS_IOS_READING_LIST_SNACKBAR_MESSAGE); |
| MDCSnackbarMessage* message = [MDCSnackbarMessage messageWithText:text]; |
| message.accessibilityLabel = text; |
| message.duration = 2.0; |
| message.category = kBrowserViewControllerSnackbarCategory; |
| |
| // TODO(crbug.com/1323778): This will need to be called on the |
| // SnackbarCommands handler. |
| [self.dispatcher showSnackbarMessage:message]; |
| } |
| |
| - (void)addURLToReadingList:(const GURL&)URL withTitle:(NSString*)title { |
| if (self.currentWebState && |
| self.currentWebState->GetVisibleURL().spec() == URL.spec()) { |
| // Log UKM if the current page is being added to Reading List. |
| ukm::SourceId sourceID = |
| ukm::GetSourceIdForWebStateDocument(self.currentWebState); |
| if (sourceID != ukm::kInvalidSourceId) { |
| ukm::builders::IOS_PageAddedToReadingList(sourceID) |
| .SetAddedFromMessages(false) |
| .Record(ukm::UkmRecorder::Get()); |
| } |
| } |
| |
| base::RecordAction(UserMetricsAction("MobileReadingListAdd")); |
| |
| ReadingListModel* readingModel = |
| ReadingListModelFactory::GetForBrowserState(self.browserState); |
| readingModel->AddEntry(URL, base::SysNSStringToUTF8(title), |
| reading_list::ADDED_VIA_CURRENT_APP); |
| } |
| |
| #pragma mark - Private SingleNTP feature helper methods |
| |
| // Checks if there are any WebStates showing an NTP at this time. If not, then |
| // deconstructs |ntpCoordinator|. |
| - (void)stopNTPIfNeeded { |
| DCHECK(IsSingleNtpEnabled()); |
| BOOL activeNTP = NO; |
| WebStateList* webStateList = self.browser->GetWebStateList(); |
| for (int i = 0; i < webStateList->count(); i++) { |
| NewTabPageTabHelper* iterNtpHelper = |
| NewTabPageTabHelper::FromWebState(webStateList->GetWebStateAt(i)); |
| if (iterNtpHelper->IsActive()) { |
| activeNTP = YES; |
| } |
| } |
| if (!activeNTP) { |
| [self stopNTP]; |
| } |
| } |
| |
| - (void)stopNTP { |
| [_ntpCoordinator stop]; |
| _ntpCoordinator = nullptr; |
| } |
| |
| #pragma mark - ** Protocol Implementations and Helpers ** |
| |
| #pragma mark - ThumbStripSupporting |
| |
| - (void)thumbStripEnabledWithPanHandler: |
| (ViewRevealingVerticalPanHandler*)panHandler { |
| DCHECK(![self isThumbStripEnabled]); |
| DCHECK(panHandler); |
| _thumbStripEnabled = YES; |
| |
| self.thumbStripPanHandler = panHandler; |
| |
| // Add self as animatee first to make sure that the BVC's view is loaded for |
| // the rest of setup |
| [panHandler addAnimatee:self]; |
| |
| DCHECK([self isViewLoaded]); |
| DCHECK(self.primaryToolbarCoordinator.animatee); |
| |
| [panHandler addAnimatee:self.primaryToolbarCoordinator.animatee]; |
| |
| self.primaryToolbarCoordinator.panGestureHandler = panHandler; |
| if (!base::FeatureList::IsEnabled(kModernTabStrip)) { |
| self.legacyTabStripCoordinator.panGestureHandler = panHandler; |
| } |
| |
| self.view.backgroundColor = UIColor.clearColor; |
| |
| CGRect webStateViewFrame = self.contentArea.bounds; |
| if (self.thumbStripPanHandler.currentState == ViewRevealState::Revealed) { |
| CGFloat toolbarHeight = [self expandedTopToolbarHeight]; |
| webStateViewFrame = UIEdgeInsetsInsetRect( |
| webStateViewFrame, UIEdgeInsetsMake(toolbarHeight, 0, 0, 0)); |
| } |
| UIView* webStateView = [self viewForWebState:self.currentWebState]; |
| webStateView.frame = webStateViewFrame; |
| |
| if (IsSingleNtpEnabled()) { |
| [self.ntpCoordinator.thumbStripSupporting |
| thumbStripEnabledWithPanHandler:panHandler]; |
| } else {<
|