blob: a42e98cf5ac1e0def5870a9168b300a229d97693 [file] [log] [blame]
// 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+private.h"
#import <MessageUI/MessageUI.h>
#include "base/base64.h"
#include "base/bind.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/ios/block_types.h"
#include "base/ios/ios_util.h"
#include "base/logging.h"
#include "base/mac/bundle_locations.h"
#include "base/mac/foundation_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#import "components/language/ios/browser/ios_language_detection_tab_helper.h"
#include "components/omnibox/browser/location_bar_model_impl.h"
#include "components/reading_list/core/reading_list_model.h"
#include "components/search_engines/template_url_service.h"
#include "components/sessions/core/session_types.h"
#include "components/sessions/core/tab_restore_service_helper.h"
#include "components/signin/core/browser/account_reconcilor.h"
#include "components/signin/core/browser/signin_metrics.h"
#import "components/signin/ios/browser/account_consistency_service.h"
#include "components/signin/ios/browser/active_state_manager.h"
#include "components/strings/grit/components_strings.h"
#include "components/translate/core/browser/translate_manager.h"
#include "components/unified_consent/feature.h"
#include "ios/chrome/app/tests_hook.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#include "ios/chrome/browser/chrome_url_constants.h"
#import "ios/chrome/browser/download/download_manager_tab_helper.h"
#include "ios/chrome/browser/feature_engagement/tracker_util.h"
#import "ios/chrome/browser/find_in_page/find_tab_helper.h"
#include "ios/chrome/browser/first_run/first_run.h"
#import "ios/chrome/browser/geolocation/omnibox_geolocation_controller.h"
#include "ios/chrome/browser/infobars/infobar_manager_impl.h"
#import "ios/chrome/browser/language/url_language_histogram_factory.h"
#import "ios/chrome/browser/metrics/new_tab_page_uma.h"
#import "ios/chrome/browser/metrics/size_class_recorder.h"
#include "ios/chrome/browser/metrics/tab_usage_recorder.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"
#include "ios/chrome/browser/passwords/password_tab_helper.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"
#include "ios/chrome/browser/reading_list/features.h"
#import "ios/chrome/browser/reading_list/offline_page_tab_helper.h"
#include "ios/chrome/browser/reading_list/offline_url_utils.h"
#include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
#include "ios/chrome/browser/search_engines/search_engines_util.h"
#include "ios/chrome/browser/search_engines/template_url_service_factory.h"
#include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
#import "ios/chrome/browser/signin/account_consistency_service_factory.h"
#include "ios/chrome/browser/signin/account_reconcilor_factory.h"
#import "ios/chrome/browser/snapshots/snapshot_generator_delegate.h"
#import "ios/chrome/browser/snapshots/snapshot_tab_helper.h"
#import "ios/chrome/browser/ssl/captive_portal_detector_tab_helper.h"
#import "ios/chrome/browser/ssl/captive_portal_detector_tab_helper_delegate.h"
#include "ios/chrome/browser/system_flags.h"
#import "ios/chrome/browser/tabs/legacy_tab_helper.h"
#import "ios/chrome/browser/tabs/tab.h"
#import "ios/chrome/browser/tabs/tab_model.h"
#import "ios/chrome/browser/tabs/tab_model_observer.h"
#import "ios/chrome/browser/tabs/tab_private.h"
#import "ios/chrome/browser/translate/chrome_ios_translate_client.h"
#import "ios/chrome/browser/ui/activity_services/activity_service_legacy_coordinator.h"
#import "ios/chrome/browser/ui/activity_services/requirements/activity_service_presentation.h"
#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.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/application_commands.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/open_new_tab_command.h"
#import "ios/chrome/browser/ui/commands/popup_menu_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/toolbar_commands.h"
#import "ios/chrome/browser/ui/content_suggestions/ntp_home_constant.h"
#import "ios/chrome/browser/ui/context_menu/context_menu_coordinator.h"
#import "ios/chrome/browser/ui/dialogs/dialog_presenter.h"
#import "ios/chrome/browser/ui/dialogs/java_script_dialog_presenter_impl.h"
#import "ios/chrome/browser/ui/download/download_manager_coordinator.h"
#import "ios/chrome/browser/ui/elements/activity_overlay_coordinator.h"
#import "ios/chrome/browser/ui/find_bar/find_bar_controller_ios.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_controller.h"
#import "ios/chrome/browser/ui/fullscreen/fullscreen_controller_factory.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/image_util/image_copier.h"
#import "ios/chrome/browser/ui/image_util/image_saver.h"
#import "ios/chrome/browser/ui/infobars/infobar_container_coordinator.h"
#import "ios/chrome/browser/ui/infobars/infobar_feature.h"
#import "ios/chrome/browser/ui/infobars/infobar_positioner.h"
#include "ios/chrome/browser/ui/location_bar/location_bar_model_delegate_ios.h"
#import "ios/chrome/browser/ui/location_bar/location_bar_notification_names.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_controller.h"
#import "ios/chrome/browser/ui/ntp/new_tab_page_coordinator.h"
#import "ios/chrome/browser/ui/ntp/new_tab_page_owning.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/overscroll_actions/overscroll_actions_controller.h"
#import "ios/chrome/browser/ui/payments/payment_request_manager.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/reading_list/offline_page_native_content.h"
#import "ios/chrome/browser/ui/sad_tab/sad_tab_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/signin_interaction/public/signin_presenter.h"
#import "ios/chrome/browser/ui/static_content/static_html_native_content.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_legacy_coordinator.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/legacy_toolbar_ui_updater.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/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"
#include "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/pasteboard_util.h"
#include "ios/chrome/browser/ui/util/ui_util.h"
#import "ios/chrome/browser/ui/util/uikit_ui_util.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"
#include "ios/chrome/browser/upgrade/upgrade_center.h"
#import "ios/chrome/browser/url_loading/url_loading_notifier.h"
#import "ios/chrome/browser/url_loading/url_loading_notifier_factory.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_service.h"
#import "ios/chrome/browser/url_loading/url_loading_service_factory.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/blocked_popup_tab_helper.h"
#import "ios/chrome/browser/web/image_fetch_tab_helper.h"
#import "ios/chrome/browser/web/load_timing_tab_helper.h"
#import "ios/chrome/browser/web/page_placeholder_tab_helper.h"
#import "ios/chrome/browser/web/repost_form_tab_helper.h"
#import "ios/chrome/browser/web/sad_tab_tab_helper.h"
#import "ios/chrome/browser/web/tab_id_tab_helper.h"
#import "ios/chrome/browser/web/web_navigation_util.h"
#include "ios/chrome/browser/web_state_list/all_web_state_observation_forwarder.h"
#import "ios/chrome/browser/web_state_list/web_state_list.h"
#import "ios/chrome/browser/web_state_list/web_usage_enabler/web_state_list_web_usage_enabler.h"
#import "ios/chrome/browser/web_state_list/web_usage_enabler/web_state_list_web_usage_enabler_factory.h"
#import "ios/chrome/browser/webui/net_export_tab_helper.h"
#import "ios/chrome/browser/webui/net_export_tab_helper_delegate.h"
#import "ios/chrome/browser/webui/show_mail_composer_context.h"
#import "ios/chrome/common/ui_util/constraints_ui_util.h"
#include "ios/chrome/grit/ios_strings.h"
#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
#import "ios/public/provider/chrome/browser/ui/fullscreen_provider.h"
#include "ios/public/provider/chrome/browser/voice/voice_search_controller.h"
#include "ios/public/provider/chrome/browser/voice/voice_search_provider.h"
#import "ios/third_party/material_components_ios/src/components/Snackbar/src/MaterialSnackbar.h"
#include "ios/web/common/features.h"
#include "ios/web/common/referrer_util.h"
#include "ios/web/public/navigation_item.h"
#import "ios/web/public/navigation_manager.h"
#include "ios/web/public/url_scheme_util.h"
#include "ios/web/public/user_agent.h"
#include "ios/web/public/web_client.h"
#import "ios/web/public/web_state/context_menu_params.h"
#import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
#import "ios/web/public/web_state/navigation_context.h"
#import "ios/web/public/web_state/ui/crw_native_content_provider.h"
#import "ios/web/public/web_state/ui/crw_web_view_proxy.h"
#import "ios/web/public/web_state/ui/crw_web_view_scroll_view_proxy.h"
#import "ios/web/public/web_state/web_state.h"
#import "ios/web/public/web_state/web_state_delegate_bridge.h"
#include "ios/web/public/web_state/web_state_observer_bridge.h"
#include "ios/web/public/web_thread.h"
#import "ios/web/web_state/ui/crw_web_controller.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/l10n/l10n_util_mac.h"
#include "ui/base/page_transition_types.h"
#import "ui/gfx/image/image_util.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using base::UserMetricsAction;
using bookmarks::BookmarkNode;
namespace {
const size_t kMaxURLDisplayChars = 32 * 1024;
typedef NS_ENUM(NSInteger, ContextMenuHistogram) {
// Note: these values must match the ContextMenuOption enum in histograms.xml.
ACTION_OPEN_IN_NEW_TAB = 0,
ACTION_OPEN_IN_INCOGNITO_TAB = 1,
ACTION_COPY_LINK_ADDRESS = 2,
ACTION_SAVE_IMAGE = 6,
ACTION_OPEN_IMAGE = 7,
ACTION_OPEN_IMAGE_IN_NEW_TAB = 8,
ACTION_COPY_IMAGE = 9,
ACTION_SEARCH_BY_IMAGE = 11,
ACTION_OPEN_JAVASCRIPT = 21,
ACTION_READ_LATER = 22,
NUM_ACTIONS = 23,
};
void Record(ContextMenuHistogram action, bool is_image, bool is_link) {
if (is_image) {
if (is_link) {
UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.ImageLink", action,
NUM_ACTIONS);
} else {
UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Image", action,
NUM_ACTIONS);
}
} else {
UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Link", action,
NUM_ACTIONS);
}
}
// Histogram that tracks user actions related to the WKWebView 3D touch link
// preview API. These values are persisted to logs. Entries should not be
// renumbered and numeric values should never be reused.
enum class WKWebViewLinkPreviewAction {
kPreviewAttempted = 0,
kMaxValue = kPreviewAttempted,
};
// Records 3D touch link preview action histograms.
void Record(WKWebViewLinkPreviewAction action) {
UMA_HISTOGRAM_ENUMERATION("IOS.WKWebViewLinkPreview", action);
}
// Returns the status bar background color.
UIColor* StatusBarBackgroundColor() {
return [UIColor colorWithRed:0.11 green:0.11 blue:0.11 alpha:1.0];
}
// 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
@interface BrowserViewController () <ActivityServicePresentation,
BubblePresenterDelegate,
CaptivePortalDetectorTabHelperDelegate,
CRWNativeContentProvider,
CRWWebStateDelegate,
CRWWebStateObserver,
DialogPresenterDelegate,
FullscreenUIElement,
InfobarPositioner,
KeyCommandsPlumbing,
MainContentUI,
ManageAccountsDelegate,
MFMailComposeViewControllerDelegate,
NetExportTabHelperDelegate,
NewTabPageTabHelperDelegate,
OmniboxPopupPresenterDelegate,
OverscrollActionsControllerDelegate,
PasswordControllerDelegate,
PreloadControllerDelegate,
SideSwipeControllerDelegate,
SigninPresenter,
SnapshotGeneratorDelegate,
TabModelObserver,
TabStripPresentation,
ToolbarHeightProviderForFullscreen,
UIGestureRecognizerDelegate,
URLLoadingObserver> {
// The dependency factory passed on initialization. Used to vend objects used
// by the BVC.
BrowserViewControllerDependencyFactory* _dependencyFactory;
// Backing ivar for the public property, strong even though the property is
// weak, because things explode otherwise.
// Do not directly access this ivar outside of object initialization; use the
// -tabModel property.
TabModel* _strongTabModel;
// 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;
// Handles displaying the context menu for all form factors.
ContextMenuCoordinator* _contextMenuCoordinator;
// Backing object for property of the same name.
DialogPresenter* _dialogPresenter;
// Handles presentation of JavaScript dialogs.
std::unique_ptr<JavaScriptDialogPresenterImpl> _javaScriptDialogPresenter;
// Keyboard commands provider. It offloads most of the keyboard commands
// management off of the BVC.
KeyCommandsProvider* _keyCommandsProvider;
// Used to inject Javascript implementing the PaymentRequest API and to
// display the UI.
PaymentRequestManager* _paymentRequestManager;
// Used to display the Voice Search UI. Nil if not visible.
scoped_refptr<VoiceSearchController> _voiceSearchController;
// Used to display the Find In Page UI. Nil if not visible.
FindBarControllerIOS* _findBarController;
// Adapter to let BVC be the delegate for WebState.
std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
// 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 a load was cancelled due to typing in the location bar.
BOOL _locationBarEditCancelledLoad;
// YES if waiting for a foreground tab due to expectNewForegroundTab.
BOOL _expectingForegroundTab;
// Whether or not -shutdown has been called.
BOOL _isShutdown;
// The ChromeBrowserState associated with this BVC.
ios::ChromeBrowserState* _browserState; // weak
// Whether or not Incognito* is enabled.
BOOL _isOffTheRecord;
// 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;
// Native controller vended to tab before Tab is added to the tab model.
__weak id _temporaryNativeController;
// Coordinator for the share menu (Activity Services).
ActivityServiceLegacyCoordinator* _activityServiceCoordinator;
// Coordinator for displaying alerts.
AlertCoordinator* _alertCoordinator;
// Coordinator for displaying Sad Tab.
SadTabCoordinator* _sadTabCoordinator;
ToolbarCoordinatorAdaptor* _toolbarCoordinatorAdaptor;
// The toolbar UI updater for the toolbar managed by |_toolbarCoordinator|.
LegacyToolbarUIUpdater* _toolbarUIUpdater;
// 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.
DownloadManagerCoordinator* _downloadManagerCoordinator;
// A map associating webStates with their NTP coordinators.
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;
}
// 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 tab model, so generally
// an inactive BVC should not be visible.
@property(nonatomic, assign, getter=isActive) BOOL active;
// Browser container view controller.
@property(nonatomic, strong)
BrowserContainerViewController* browserContainerViewController;
// Command dispatcher.
@property(nonatomic, weak) CommandDispatcher* commandDispatcher;
// The browser's side swipe controller. Lazily instantiated on the first call.
@property(nonatomic, strong, readonly) SideSwipeController* sideSwipeController;
// The dialog presenter for this BVC's tab model.
@property(nonatomic, strong, readonly) DialogPresenter* dialogPresenter;
// The object that manages keyboard commands on behalf of the BVC.
@property(nonatomic, strong, readonly) KeyCommandsProvider* keyCommandsProvider;
// Helper method to check web controller canShowFindBar method.
@property(nonatomic, assign, readonly) BOOL canShowFindBar;
// 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;
// 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.tabModel|.
@property(nonatomic, assign, getter=isWebUsageEnabled) BOOL webUsageEnabled;
// Returns YES if the toolbar has not been scrolled out by fullscreen.
@property(nonatomic, assign, readonly, getter=isToolbarOnScreen)
BOOL toolbarOnScreen;
// 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;
// Coordinator for displaying a modal overlay with activity indicator to prevent
// the user from interacting with the browser view.
@property(nonatomic, strong)
ActivityOverlayCoordinator* activityOverlayCoordinator;
// 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* tabStripCoordinator;
// Coordinator for Infobars.
@property(nonatomic, strong)
InfobarContainerCoordinator* infobarContainerCoordinator;
// A weak reference to the view of the tab strip on tablet.
@property(nonatomic, weak) UIView* tabStripView;
// Helper for saving images.
@property(nonatomic, strong) ImageSaver* imageSaver;
// Helper for copying images.
@property(nonatomic, strong) ImageCopier* imageCopier;
// Helper for the bvc.
@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 or visible page is a native
// page.
@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;
// 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;
// Whether the safe area insets should be used to adjust the viewport.
@property(nonatomic, readonly) BOOL usesSafeInsetsForViewportAdjustments;
// Whether the keyboard observer helper is viewed
@property(nonatomic, strong) KeyboardObserverHelper* observer;
// BVC initialization
// ------------------
// If the BVC is initialized with a valid browser state & tab model 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 tab model, the tab model
// 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 |_browserState|, |self.tabModel|, and [self
// isViewLoaded].
// Updates non-view-related functionality with the given browser state and tab
// model.
// Does not matter whether or not the view has been loaded.
- (void)updateWithTabModel:(TabModel*)model
browserState:(ios::ChromeBrowserState*)browserState;
// On iOS7, iPad should match iOS6 status bar. Install a simple black bar under
// the status bar to mimic this layout.
- (void)installFakeStatusBar;
// Builds the UI parts of tab strip and the toolbar. Does not matter whether
// or not browser state and tab model are valid.
- (void)buildToolbarAndTabStrip;
// Sets up the constraints on the toolbar.
- (void)addConstraintsToToolbar;
// Updates view-related functionality with the given tab model and browser
// state. The view must have been loaded. Uses |_browserState| and
// |self.tabModel|.
- (void)addUIFunctionalityForModelAndBrowserState;
// Sets the correct frame and hierarchy for subviews and helper views. Only
// insert views on |initialLayout|.
- (void)setUpViewLayout:(BOOL)initialLayout;
// Makes |tab| the currently visible tab, displaying its view.
- (void)displayTab:(Tab*)tab;
// Initializes the bookmark interaction controller if not already initialized.
- (void)initializeBookmarkInteractionController;
// Installs the BVC as overscroll actions controller of |nativeContent| if
// needed. Sets the style of the overscroll actions toolbar.
- (void)setOverScrollActionControllerToStaticNativeContent:
(StaticHtmlNativeContent*)nativeContent;
// UI Configuration, update and Layout
// -----------------------------------
// Updates the toolbar display based on the current tab.
- (void)updateToolbar;
// Starts or stops broadcasting the toolbar UI and main content UI depending on
// whether the BVC is visible and active.
- (void)updateBroadcastState;
// Updates |dialogPresenter|'s |active| property to account for the BVC's
// |active|, |visible|, and |inNewTabAnimation| properties.
- (void)updateDialogPresenterActiveState;
// 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;
// Returns the footer view if one exists (e.g. the voice search bar).
- (UIView*)footerView;
// Returns the appropriate frame for the NTP.
// TODO(crbug.com/826369): Most of this method's implementation details can be
// unwound when BVC fullscreen and NTP experiments are enabled by default.
- (CGRect)ntpFrameForWebState:(web::WebState*)webState;
// Returns web contents frame without including primary toolbar.
- (CGRect)visibleFrameForTab:(Tab*)tab;
// Sets the frame for the headers.
- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
atOffset:(CGFloat)headerOffset;
// Find Bar UI
// -----------
// Update find bar with model data. If |shouldFocus| is set to YES, the text
// field will become first responder.
- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus;
// Hide find bar.
- (void)hideFindBarWithAnimation:(BOOL)animate;
// Shows find bar. If |selectText| is YES, all text inside the Find Bar
// textfield will be selected. If |shouldFocus| is set to YES, the textfield is
// set to be first responder.
- (void)showFindBarWithAnimation:(BOOL)animate
selectText:(BOOL)selectText
shouldFocus:(BOOL)shouldFocus;
// Alerts
// ------
// Shows a self-dismissing snackbar displaying |message|.
- (void)showSnackbar:(NSString*)message;
// Shows an alert dialog with |title| and |message|.
- (void)showErrorAlertWithStringTitle:(NSString*)title
message:(NSString*)message;
// Tap Handling
// ------------
// Record the last tap point based on the |originPoint| (if any) passed in
// command.
- (void)setLastTapPointFromCommand:(CGPoint)originPoint;
// Returns the last stored |_lastTapPoint| if it's been set within the past
// second.
- (CGPoint)lastTapPoint;
// Store the tap CGPoint in |_lastTapPoint| and the current timestamp.
- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer;
// Tab creation and selection
// --------------------------
// Whether the given tab's URL is an application specific URL.
- (BOOL)isTabNativePage:(Tab*)tab;
// Add all delegates to the provided |tab|.
- (void)installDelegatesForTab:(Tab*)tab;
// Remove delegates from the provided |tab|.
- (void)uninstallDelegatesForTab:(Tab*)tab;
// Called when a tab is selected in the model. Make any required view changes.
// The notification will not be sent when the tab is already the selected tab.
// |notifyToolbar| indicates whether the toolbar is notified that the tab has
// changed.
- (void)tabSelected:(Tab*)tab notifyToolbar:(BOOL)notifyToolbar;
// Returns the native controller being used by |tab|'s web controller.
- (id)nativeControllerForTab:(Tab*)tab;
// Voice Search
// ------------
// Lazily instantiates |_voiceSearchController|.
- (void)ensureVoiceSearchControllerCreated;
// Reading List
// ------------
// Adds the given url to the reading list.
- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title;
@end
@implementation BrowserViewController
#pragma mark - Object lifecycle
- (instancetype)initWithTabModel:(TabModel*)model
browserState:(ios::ChromeBrowserState*)browserState
dependencyFactory:
(BrowserViewControllerDependencyFactory*)factory
applicationCommandEndpoint:
(id<ApplicationCommands>)applicationCommandEndpoint
commandDispatcher:(CommandDispatcher*)commandDispatcher
browserContainerViewController:
(BrowserContainerViewController*)browserContainerViewController {
self = [super initWithNibName:nil bundle:base::mac::FrameworkBundle()];
if (self) {
DCHECK(factory);
_browserContainerViewController = browserContainerViewController;
_dependencyFactory = factory;
_dialogPresenter = [[DialogPresenter alloc] initWithDelegate:self
presentingViewController:self];
self.commandDispatcher = commandDispatcher;
[self.commandDispatcher
startDispatchingToTarget:self
forProtocol:@protocol(BrowserCommands)];
[self.commandDispatcher
startDispatchingToTarget:applicationCommandEndpoint
forProtocol:@protocol(ApplicationCommands)];
// -startDispatchingToTarget:forProtocol: doesn't pick up protocols the
// passed protocol conforms to, so ApplicationSettingsCommands is explicitly
// dispatched to the endpoint as well. Since this is potentially
// fragile, DCHECK that it should still work (if the endpoint is nonnull).
DCHECK(!applicationCommandEndpoint ||
[applicationCommandEndpoint
conformsToProtocol:@protocol(ApplicationSettingsCommands)]);
[self.commandDispatcher
startDispatchingToTarget:applicationCommandEndpoint
forProtocol:@protocol(ApplicationSettingsCommands)];
// -startDispatchingToTarget:forProtocol: doesn't pick up protocols the
// passed protocol conforms to, so BrowsingDataCommands is explicitly
// dispatched to the endpoint as well. Since this is potentially
// fragile, DCHECK that it should still work (if the endpoint is nonnull).
DCHECK(!applicationCommandEndpoint ||
[applicationCommandEndpoint
conformsToProtocol:@protocol(BrowsingDataCommands)]);
[self.commandDispatcher
startDispatchingToTarget:applicationCommandEndpoint
forProtocol:@protocol(BrowsingDataCommands)];
_toolbarCoordinatorAdaptor =
[[ToolbarCoordinatorAdaptor alloc] initWithDispatcher:self.dispatcher];
self.toolbarInterface = _toolbarCoordinatorAdaptor;
_downloadManagerCoordinator = [[DownloadManagerCoordinator alloc]
initWithBaseViewController:_browserContainerViewController];
_downloadManagerCoordinator.presenter =
[[VerticalAnimationContainer alloc] init];
_javaScriptDialogPresenter.reset(
new JavaScriptDialogPresenterImpl(_dialogPresenter));
_webStateDelegate.reset(new web::WebStateDelegateBridge(self));
_inNewTabAnimation = NO;
_footerFullscreenProgress = 1.0;
_observer = [[KeyboardObserverHelper alloc] init];
if (model && browserState)
[self updateWithTabModel:model browserState:browserState];
}
return self;
}
- (instancetype)initWithNibName:(NSString*)nibNameOrNil
bundle:(NSBundle*)nibBundleOrNil {
NOTREACHED();
return nil;
}
- (instancetype)initWithCoder:(NSCoder*)aDecoder {
NOTREACHED();
return nil;
}
- (void)dealloc {
DCHECK(_isShutdown) << "-shutdown must be called before dealloc.";
}
#pragma mark - Public Properties
- (id<ApplicationCommands,
BrowserCommands,
OmniboxFocuser,
PopupMenuCommands,
FakeboxFocuser,
SnackbarCommands,
ToolbarCommands>)dispatcher {
return static_cast<
id<ApplicationCommands, BrowserCommands, OmniboxFocuser,
PopupMenuCommands, FakeboxFocuser, SnackbarCommands, ToolbarCommands>>(
self.commandDispatcher);
}
- (UIView*)contentArea {
return self.browserContainerViewController.view;
}
- (BOOL)isPlayingTTS {
return _voiceSearchController && _voiceSearchController->IsPlayingAudio();
}
- (TabModel*)tabModel {
return _strongTabModel;
}
- (ios::ChromeBrowserState*)browserState {
return _browserState;
}
#pragma mark - Private Properties
- (SideSwipeController*)sideSwipeController {
if (!_sideSwipeController) {
_sideSwipeController =
[[SideSwipeController alloc] initWithTabModel:self.tabModel
browserState:_browserState];
[_sideSwipeController setSnapshotDelegate:self];
_sideSwipeController.toolbarInteractionHandler = self.toolbarInterface;
_sideSwipeController.primaryToolbarSnapshotProvider =
self.primaryToolbarCoordinator;
_sideSwipeController.secondaryToolbarSnapshotProvider =
self.secondaryToolbarCoordinator;
[_sideSwipeController setSwipeDelegate:self];
[_sideSwipeController setTabStripDelegate:self.tabStripCoordinator];
}
return _sideSwipeController;
}
- (DialogPresenter*)dialogPresenter {
return _dialogPresenter;
}
- (KeyCommandsProvider*)keyCommandsProvider {
if (!_keyCommandsProvider) {
_keyCommandsProvider = [_dependencyFactory newKeyCommandsProvider];
}
return _keyCommandsProvider;
}
- (BOOL)canShowFindBar {
// Make sure web controller can handle find in page.
Tab* tab = [self.tabModel currentTab];
if (!tab) {
return NO;
}
auto* helper = FindTabHelper::FromWebState(tab.webState);
return (helper && helper->CurrentPageSupportsFindInPage() &&
!helper->IsFindUIActive());
}
- (BOOL)canShowTabStrip {
return IsRegularXRegularSizeClass(self);
}
- (web::UserAgentType)userAgentType {
web::WebState* webState = [self.tabModel currentTab].webState;
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 updateDialogPresenterActiveState];
[self updateBroadcastState];
}
- (void)setBroadcasting:(BOOL)broadcasting {
if (_broadcasting == broadcasting)
return;
_broadcasting = broadcasting;
// TODO(crbug.com/790886): Use the Browser's broadcaster once Browsers are
// supported.
FullscreenController* fullscreenController =
FullscreenControllerFactory::GetInstance()->GetForBrowserState(
_browserState);
ChromeBroadcaster* broadcaster = fullscreenController->broadcaster();
if (_broadcasting) {
fullscreenController->SetWebStateList(self.tabModel.webStateList);
_toolbarUIUpdater = [[LegacyToolbarUIUpdater alloc]
initWithToolbarUI:[[ToolbarUIState alloc] init]
toolbarOwner:self
webStateList:self.tabModel.webStateList];
[_toolbarUIUpdater startUpdating];
StartBroadcastingToolbarUI(_toolbarUIUpdater.toolbarUI, broadcaster);
_mainContentUIUpdater = [[MainContentUIStateUpdater alloc]
initWithState:[[MainContentUIState alloc] init]];
_webMainContentUIForwarder = [[WebScrollViewMainContentUIForwarder alloc]
initWithUpdater:_mainContentUIUpdater
webStateList:self.tabModel.webStateList];
StartBroadcastingMainContentUI(self, broadcaster);
_fullscreenUIUpdater = std::make_unique<FullscreenUIUpdater>(self);
fullscreenController->AddObserver(_fullscreenUIUpdater.get());
[self updateForFullscreenProgress:fullscreenController->GetProgress()];
} else {
StopBroadcastingToolbarUI(broadcaster);
StopBroadcastingMainContentUI(broadcaster);
[_toolbarUIUpdater stopUpdating];
_toolbarUIUpdater = nil;
_mainContentUIUpdater = nil;
[_webMainContentUIForwarder disconnect];
_webMainContentUIForwarder = nil;
fullscreenController->RemoveObserver(_fullscreenUIUpdater.get());
_fullscreenUIUpdater = nullptr;
fullscreenController->SetWebStateList(nullptr);
}
}
- (BOOL)isWebUsageEnabled {
return _browserState && !_isShutdown &&
WebStateListWebUsageEnablerFactory::GetInstance()
->GetForBrowserState(_browserState)
->IsWebUsageEnabled();
}
- (void)setWebUsageEnabled:(BOOL)webUsageEnabled {
if (!_browserState || _isShutdown)
return;
WebStateListWebUsageEnablerFactory::GetInstance()
->GetForBrowserState(_browserState)
->SetWebUsageEnabled(webUsageEnabled);
}
- (BOOL)isToolbarOnScreen {
return [self nonFullscreenToolbarHeight] - [self currentHeaderOffset] > 0;
}
- (void)setInNewTabAnimation:(BOOL)inNewTabAnimation {
if (_inNewTabAnimation == inNewTabAnimation)
return;
_inNewTabAnimation = inNewTabAnimation;
[self updateDialogPresenterActiveState];
[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 ([_findBarController view]) {
[results addObject:[HeaderDefinition
definitionWithView:[_findBarController view]
headerBehaviour:Overlap]];
}
}
return [results copy];
}
- (CGFloat)headerOffset {
CGFloat headerOffset = 0;
headerOffset = self.view.safeAreaInsets.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.tabModel.currentTab.webState;
}
- (BOOL)usesSafeInsetsForViewportAdjustments {
fullscreen::features::ViewportAdjustmentExperiment viewportExperiment =
fullscreen::features::GetActiveViewportExperiment();
return viewportExperiment ==
fullscreen::features::ViewportAdjustmentExperiment::SAFE_AREA ||
viewportExperiment ==
fullscreen::features::ViewportAdjustmentExperiment::HYBRID;
}
- (BubblePresenter*)bubblePresenter {
if (!_bubblePresenter) {
_bubblePresenter =
[[BubblePresenter alloc] initWithBrowserState:self.browserState
delegate:self
rootViewController:self];
_bubblePresenter.dispatcher = self.dispatcher;
self.popupMenuCoordinator.bubblePresenter = _bubblePresenter;
}
return _bubblePresenter;
}
#pragma mark - Public methods
- (void)setPrimary:(BOOL)primary {
[self.tabModel setPrimary:primary];
if (primary) {
[self updateDialogPresenterActiveState];
[self updateBroadcastState];
} else {
self.dialogPresenter.active = false;
}
}
- (void)shieldWasTapped:(id)sender {
[self.dispatcher cancelOmniboxEdit];
}
- (void)userEnteredTabSwitcher {
[self.bubblePresenter userEnteredTabSwitcher];
}
- (void)presentBubblesIfEligible {
[self.bubblePresenter presentBubblesIfEligible];
}
- (void)openNewTabFromOriginPoint:(CGPoint)originPoint
focusOmnibox:(BOOL)focusOmnibox {
NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
BOOL offTheRecord = self.isOffTheRecord;
ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
self.foregroundTabWasAddedCompletionBlock;
__weak BrowserViewController* weakSelf = self;
self.foregroundTabWasAddedCompletionBlock = ^{
if (oldForegroundTabWasAddedCompletionBlock) {
oldForegroundTabWasAddedCompletionBlock();
}
double duration = [NSDate timeIntervalSinceReferenceDate] - startTime;
base::TimeDelta timeDelta = base::TimeDelta::FromSecondsD(duration);
if (offTheRecord) {
UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewIncognitoTabPresentationDuration",
timeDelta);
} else {
UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewTabPresentationDuration", timeDelta);
}
if (focusOmnibox) {
[weakSelf.dispatcher 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;
UrlLoadingServiceFactory::GetForBrowserState(self.browserState)->Load(params);
}
- (void)appendTabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
if (tabAddedCompletion) {
if (self.foregroundTabWasAddedCompletionBlock) {
ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
self.foregroundTabWasAddedCompletionBlock;
self.foregroundTabWasAddedCompletionBlock = ^{
oldForegroundTabWasAddedCompletionBlock();
tabAddedCompletion();
};
} else {
self.foregroundTabWasAddedCompletionBlock = tabAddedCompletion;
}
}
}
- (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 closeFindInPage];
[[self viewForTab:self.tabModel.currentTab] endEditing:NO];
// Ensure that voice search objects are created.
[self ensureVoiceSearchControllerCreated];
// Present voice search.
_voiceSearchController->StartRecognition(self, self.tabModel.currentTab);
[self.dispatcher cancelOmniboxEdit];
}
#pragma mark - browser_view_controller+private.h
- (void)setActive:(BOOL)active {
if (_active == active) {
return;
}
_active = active;
// If not active, display an activity indicator overlay over the view to
// prevent interaction with the web page.
// TODO(crbug.com/637093): This coordinator should be managed by the
// coordinator used to present BrowserViewController, when implemented.
if (active) {
[self.activityOverlayCoordinator stop];
self.activityOverlayCoordinator = nil;
} else if (!self.activityOverlayCoordinator) {
self.activityOverlayCoordinator =
[[ActivityOverlayCoordinator alloc] initWithBaseViewController:self];
[self.activityOverlayCoordinator start];
}
if (_browserState) {
ActiveStateManager* active_state_manager =
ActiveStateManager::FromBrowserState(_browserState);
active_state_manager->SetActive(active);
TextToSpeechPlaybackControllerFactory::GetInstance()
->GetForBrowserState(_browserState)
->SetEnabled(_active);
}
self.webUsageEnabled = active;
[self updateDialogPresenterActiveState];
[self updateBroadcastState];
// Stop the NTP on web usage toggle. This happens when clearing browser
// data, and forces the NTP to be recreated in -displayTab below.
// TODO(crbug.com/906199): Move this to the NewTabPageTabHelper when
// WebStateObserver has a webUsage callback.
if (!active) {
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];
if (self.currentWebState && _expectingForegroundTab) {
PagePlaceholderTabHelper::FromWebState(self.currentWebState)
->AddPlaceholderForNextNavigation();
}
Tab* currentTab = self.tabModel.currentTab;
if (currentTab)
[self displayTab:currentTab];
} else {
[_dialogPresenter cancelAllDialogs];
}
[_paymentRequestManager enablePaymentRequest:active];
[self setNeedsStatusBarAppearanceUpdate];
}
- (void)clearPresentedStateWithCompletion:(ProceduralBlock)completion
dismissOmnibox:(BOOL)dismissOmnibox {
[_activityServiceCoordinator cancelShare];
[_bookmarkInteractionController dismissBookmarkModalControllerAnimated:NO];
[_bookmarkInteractionController dismissSnackbar];
if (dismissOmnibox) {
[self.dispatcher cancelOmniboxEdit];
}
[_dialogPresenter cancelAllDialogs];
[self.dispatcher hidePageInfo];
[self.bubblePresenter dismissBubbles];
if (_voiceSearchController)
_voiceSearchController->DismissMicPermissionsHelp();
web::WebState* webState = self.currentWebState;
[self.tabModel.currentTab dismissModals];
if (webState) {
NewTabPageTabHelper* NTPHelper =
NewTabPageTabHelper::FromWebState(webState);
if (NTPHelper && NTPHelper->IsActive()) {
[_ntpCoordinatorsForWebStates[webState] dismissModals];
}
auto* findHelper = FindTabHelper::FromWebState(webState);
if (findHelper) {
findHelper->StopFinding(^{
[self updateFindBar:NO shouldFocus:NO];
});
}
}
[_paymentRequestManager cancelRequest];
[self.dispatcher dismissPopupMenuAnimated:NO];
[_contextMenuCoordinator stop];
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.
FullscreenControllerFactory::GetInstance()
->GetForBrowserState(_browserState)
->ExitFullscreen();
const CGFloat kAnimatedViewSize = 50;
BackgroundTabAnimationView* animatedView =
[[BackgroundTabAnimationView alloc]
initWithFrame:CGRectMake(0, 0, kAnimatedViewSize,
kAnimatedViewSize)];
__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];
[_paymentRequestManager close];
_paymentRequestManager = nil;
if (_browserState) {
TextToSpeechPlaybackController* controller =
TextToSpeechPlaybackControllerFactory::GetInstance()
->GetForBrowserState(_browserState);
if (controller)
controller->SetWebStateList(nullptr);
WebStateListWebUsageEnabler* webUsageEnabler =
WebStateListWebUsageEnablerFactory::GetInstance()->GetForBrowserState(
_browserState);
if (webUsageEnabler)
webUsageEnabler->SetWebStateList(nullptr);
UrlLoadingNotifier* urlLoadingNotifier =
UrlLoadingNotifierFactory::GetForBrowserState(_browserState);
if (urlLoadingNotifier)
urlLoadingNotifier->RemoveObserver(_URLLoadingObserverBridge.get());
}
// Uninstall delegates so that any delegate callbacks triggered by subsequent
// WebStateDestroyed() signals are not handled.
for (NSUInteger index = 0; index < self.tabModel.count; ++index)
[self uninstallDelegatesForTab:[self.tabModel tabAtIndex:index]];
// Disconnect child coordinators.
[_activityServiceCoordinator disconnect];
[self.popupMenuCoordinator stop];
[self.tabStripCoordinator stop];
self.tabStripCoordinator = nil;
self.tabStripView = nil;
_browserState = nullptr;
[self.commandDispatcher stopDispatchingToTarget:self];
self.commandDispatcher = nil;
[self.tabStripCoordinator stop];
self.tabStripCoordinator = nil;
[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;
self.tabStripView = nil;
[self.infobarContainerCoordinator stop];
self.infobarContainerCoordinator = nil;
// SideSwipeController is a tab model observer, so it needs to stop observing
// before self.tabModel is released.
_sideSwipeController = nil;
[self.tabModel removeObserver:self];
_allWebStateObservationForwarder = nullptr;
if (_voiceSearchController)
_voiceSearchController->SetDispatcher(nil);
[_paymentRequestManager setActiveWebState:nullptr];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - NSObject
- (BOOL)accessibilityPerformEscape {
[self dismissPopups];
return YES;
}
#pragma mark - UIResponder
- (NSArray*)keyCommands {
if (![self shouldRegisterKeyboardCommands]) {
return nil;
}
UIResponder* firstResponder = GetFirstResponder();
return [self.keyCommandsProvider
keyCommandsForConsumer:self
baseViewController:self
dispatcher:self.dispatcher
editingText:[firstResponder
isKindOfClass:[UITextField class]] ||
[firstResponder
isKindOfClass:[UITextView class]] ||
[self.observer isKeyboardOnScreen]];
}
#pragma mark - UIResponder helpers
// Whether the BVC should declare keyboard commands.
- (BOOL)shouldRegisterKeyboardCommands {
if ([self presentedViewController])
return NO;
if (_voiceSearchController && _voiceSearchController->IsVisible())
return NO;
return YES;
}
#pragma mark - UIViewController
- (void)viewDidLoad {
CGRect initialViewsRect = self.view.bounds;
UIViewAutoresizing initialViewAutoresizing =
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
// Clip the content to the bounds of the view. This prevents the WebView to
// overflow outside of the BVC, which is particularly visible during rotation.
// The WebView is overflowing its bounds to be displayed below the toolbars.
self.view.clipsToBounds = YES;
self.contentArea.frame = initialViewsRect;
self.typingShield = [[UIButton alloc] initWithFrame:initialViewsRect];
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.view.backgroundColor = [UIColor colorWithWhite:0.75 alpha:1.0];
[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];
[self buildToolbarAndTabStrip];
[self setUpViewLayout:YES];
[self addConstraintsToToolbar];
// If the tab model and browser state are valid, finish initialization.
if (self.tabModel && _browserState)
[self addUIFunctionalityForModelAndBrowserState];
// Add a tap gesture recognizer to save the last tap location for the source
// location of the new tab animation.
UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self
action:@selector(saveContentAreaTapLocation:)];
[tapRecognizer setDelegate:self];
[tapRecognizer setCancelsTouchesInView:NO];
[self.contentArea addGestureRecognizer:tapRecognizer];
}
- (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];
// Native content pages depend on |self.view|'s safeArea. If the BVC is
// presented underneath another view (such as the first time welcome view),
// the BVC has no safe area set during webController's layout initial, and
// won't automatically get another layout without forcing it here.
Tab* currentTab = self.tabModel.currentTab;
if ([self isTabNativePage:currentTab]) {
[currentTab.webController.view setNeedsLayout];
}
// 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.currentWebState && self.webUsageEnabled) {
NewTabPageTabHelper* NTPHelper =
NewTabPageTabHelper::FromWebState(self.currentWebState);
if (NTPHelper && NTPHelper->IsActive()) {
_ntpCoordinatorsForWebStates[self.currentWebState]
.viewController.view.frame =
[self ntpFrameForWebState:self.currentWebState];
}
}
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.viewVisible = YES;
[self updateDialogPresenterActiveState];
[self updateBroadcastState];
[_toolbarUIUpdater updateState];
// |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.
if (self.browserState) {
[self presentBubblesIfEligible];
}
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.visible = YES;
if (self.transitionCoordinator.animated) {
// The transition coordinator is animated only when presented from the
// TabGrid (when presented at the app start up, it is not animated). In that
// case, display the Long Press InProductHelp if needed.
auto completion =
^(id<UIViewControllerTransitionCoordinatorContext> context) {
[self.bubblePresenter presentLongPressBubbleIfEligible];
};
[self.transitionCoordinator animateAlongsideTransition:nil
completion:completion];
}
// 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 tab (if any; the switcher may not have created one
// yet) in case it changed while showing the switcher.
Tab* currentTab = self.tabModel.currentTab;
if (currentTab)
[self displayTab:currentTab];
}
- (void)viewWillDisappear:(BOOL)animated {
self.viewVisible = NO;
[self updateDialogPresenterActiveState];
[self updateBroadcastState];
web::WebState* activeWebState =
self.tabModel.webStateList->GetActiveWebState();
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]) {
// Do not release |_infobarContainerCoordinator|, as this must have the same
// lifecycle as the BrowserViewController.
self.typingShield = nil;
if (_voiceSearchController)
_voiceSearchController->SetDispatcher(nil);
self.primaryToolbarCoordinator = nil;
self.secondaryToolbarContainerCoordinator = nil;
self.secondaryToolbarCoordinator = nil;
self.toolbarInterface = nil;
[_toolbarUIUpdater stopUpdating];
_toolbarUIUpdater = nil;
_locationBarModelDelegate = nil;
_locationBarModel = nil;
self.helper = nil;
[self.tabStripCoordinator stop];
self.tabStripCoordinator = nil;
self.tabStripView = nil;
_sideSwipeController = nil;
}
}
- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
FullscreenControllerFactory::GetInstance()
->GetForBrowserState(_browserState)
->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.usesSafeInsetsForViewportAdjustments && self.currentWebState) {
UIEdgeInsets contentPadding =
self.currentWebState->GetWebViewProxy().contentInset;
contentPadding.bottom = AlignValueToPixel(
self.footerFullscreenProgress * [self secondaryToolbarHeightWithInset]);
self.currentWebState->GetWebViewProxy().contentInset = contentPadding;
}
[_toolbarUIUpdater updateState];
// 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 hideFindBarWithAnimation:NO];
}
// Update the toolbar visibility.
[self updateToolbar];
// Update the tab strip visibility.
if (self.tabStripView) {
[self showTabStripView:self.tabStripView];
[self.tabStripCoordinator hideTabStrip:![self canShowTabStrip]];
_fakeStatusBarView.hidden = ![self canShowTabStrip];
[self addConstraintsToPrimaryToolbar];
}
[self setNeedsStatusBarAppearanceUpdate];
FullscreenControllerFactory::GetInstance()
->GetForBrowserState(_browserState)
->BrowserTraitCollectionChangedEnd();
}
- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:
(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[self dismissPopups];
[coordinator
animateAlongsideTransition:^(
id<UIViewControllerTransitionCoordinatorContext> context) {
// Force updates of the toolbar updater as the toolbar height might
// change on rotation.
[_toolbarUIUpdater updateState];
}
completion:nil];
}
- (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();
[strongSelf.dialogPresenter tryToPresent];
}];
}
- (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.
BOOL firstRunLaunch = (FirstRun::IsChromeFirstRun() ||
experimental_flags::AlwaysDisplayFirstRun()) &&
!tests_hook::DisableFirstRun();
// 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]]) {
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;
UIWindow* window = UIApplication.sharedApplication.keyWindow;
launchScreenView.frame = window.bounds;
[window addSubview:launchScreenView];
// 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];
}
// An Infobar message is currently the only presented controller that allows
// interaction with the rest of the App while its being presented. Dismiss it
// in case the user or system has triggered another presentation.
if (IsInfobarUIRebootEnabled() &&
[self.infobarContainerCoordinator isPresentingInfobarBanner]) {
[self.infobarContainerCoordinator
dismissInfobarBannerAnimated:NO
completion:^{
[super
presentViewController:viewControllerToPresent
animated:flag
completion:finalCompletionHandler];
}];
} else {
[super presentViewController:viewControllerToPresent
animated:flag
completion:finalCompletionHandler];
}
}
- (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) {
return self.tabStripView.frame.origin.y < kTabStripAppearanceOffset
? UIStatusBarStyleDefault
: UIStatusBarStyleLightContent;
}
return _isOffTheRecord ? UIStatusBarStyleLightContent
: UIStatusBarStyleDefault;
}
#pragma mark - ** Private BVC Methods **
#pragma mark - Private Methods: BVC Initialization
- (void)updateWithTabModel:(TabModel*)model
browserState:(ios::ChromeBrowserState*)browserState {
DCHECK(model);
DCHECK(browserState);
DCHECK(!self.tabModel);
DCHECK(!_browserState);
_browserState = browserState;
_isOffTheRecord = browserState->IsOffTheRecord() ? YES : NO;
_strongTabModel = model;
WebStateListWebUsageEnablerFactory::GetInstance()
->GetForBrowserState(_browserState)
->SetWebStateList(self.tabModel.webStateList);
[self.tabModel addObserver:self];
_webStateObserverBridge = std::make_unique<web::WebStateObserverBridge>(self);
_allWebStateObservationForwarder =
std::make_unique<AllWebStateObservationForwarder>(
self.tabModel.webStateList, _webStateObserverBridge.get());
_URLLoadingObserverBridge = std::make_unique<UrlLoadingObserverBridge>(self);
UrlLoadingNotifier* urlLoadingNotifier =
UrlLoadingNotifierFactory::GetForBrowserState(_browserState);
urlLoadingNotifier->AddObserver(_URLLoadingObserverBridge.get());
NSUInteger count = self.tabModel.count;
for (NSUInteger index = 0; index < count; ++index)
[self installDelegatesForTab:[self.tabModel tabAtIndex:index]];
self.imageSaver = [[ImageSaver alloc] initWithBaseViewController:self];
self.imageCopier = [[ImageCopier alloc] initWithBaseViewController:self];
// Set the TTS playback controller's WebStateList.
TextToSpeechPlaybackControllerFactory::GetInstance()
->GetForBrowserState(_browserState)
->SetWebStateList(self.tabModel.webStateList);
// 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;
}
}
- (void)installFakeStatusBar {
CGRect statusBarFrame = CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 0);
_fakeStatusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
[_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
if (IsIPadIdiom()) {
[_fakeStatusBarView setBackgroundColor:StatusBarBackgroundColor()];
[_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
[[self view] addSubview:_fakeStatusBarView];
} else {
// Add a white bar on phone so that the status bar on the NTP is white.
[_fakeStatusBarView setBackgroundColor:ntp_home::kNTPBackgroundColor()];
[self.view insertSubview:_fakeStatusBarView atIndex:0];
}
}
// Create the UI elements. May or may not have valid browser state & tab model.
- (void)buildToolbarAndTabStrip {
DCHECK([self isViewLoaded]);
DCHECK(!_locationBarModelDelegate);
// Initialize the prerender service before creating the toolbar controller.
PrerenderService* prerenderService =
PrerenderServiceFactory::GetForBrowserState(self.browserState);
if (prerenderService) {
prerenderService->SetDelegate(self);
}
// Create the location bar model and controller.
_locationBarModelDelegate.reset(
new LocationBarModelDelegateIOS(self.tabModel.webStateList));
_locationBarModel = std::make_unique<LocationBarModelImpl>(
_locationBarModelDelegate.get(), kMaxURLDisplayChars);
self.helper = [_dependencyFactory newBrowserViewControllerHelper];
PrimaryToolbarCoordinator* topToolbarCoordinator =
[[PrimaryToolbarCoordinator alloc] initWithBrowserState:_browserState];
self.primaryToolbarCoordinator = topToolbarCoordinator;
topToolbarCoordinator.delegate = self;
topToolbarCoordinator.popupPresenterDelegate = self;
topToolbarCoordinator.webStateList = self.tabModel.webStateList;
topToolbarCoordinator.dispatcher = self.dispatcher;
topToolbarCoordinator.commandDispatcher = self.commandDispatcher;
topToolbarCoordinator.longPressDelegate = self.popupMenuCoordinator;
[topToolbarCoordinator start];
SecondaryToolbarCoordinator* bottomToolbarCoordinator =
[[SecondaryToolbarCoordinator alloc] initWithBrowserState:_browserState];
self.secondaryToolbarCoordinator = bottomToolbarCoordinator;
bottomToolbarCoordinator.webStateList = self.tabModel.webStateList;
bottomToolbarCoordinator.dispatcher = self.dispatcher;
bottomToolbarCoordinator.longPressDelegate = self.popupMenuCoordinator;
if (base::FeatureList::IsEnabled(
toolbar_container::kToolbarContainerEnabled)) {
self.secondaryToolbarContainerCoordinator =
[[ToolbarContainerCoordinator alloc]
initWithBrowserState:self.browserState
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)
_voiceSearchController->SetDispatcher(
static_cast<id<LoadQueryCommands>>(self.commandDispatcher));
if (IsIPadIdiom()) {
self.tabStripCoordinator =
[[TabStripLegacyCoordinator alloc] initWithBaseViewController:self];
self.tabStripCoordinator.browserState = _browserState;
self.tabStripCoordinator.dispatcher = self.commandDispatcher;
self.tabStripCoordinator.tabModel = self.tabModel;
self.tabStripCoordinator.presentationProvider = self;
self.tabStripCoordinator.animationWaitDuration =
kLegacyFullscreenControllerToolbarAnimationDuration;
self.tabStripCoordinator.longPressDelegate = self.popupMenuCoordinator;
UILayoutGuide* guide =
[[NamedGuide alloc] initWithName:kTabStripTabSwitcherGuide];
[self.view addLayoutGuide:guide];
[self.tabStripCoordinator start];
}
// Create the Infobar Container Coordinator.
self.infobarContainerCoordinator = [[InfobarContainerCoordinator alloc]
initWithBaseViewController:self
browserState:_browserState
webStateList:self.tabModel.webStateList];
self.infobarContainerCoordinator.commandDispatcher = self.dispatcher;
self.infobarContainerCoordinator.positioner = self;
self.infobarContainerCoordinator.syncPresenter = self;
[self.infobarContainerCoordinator 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()) {
// 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.view.safeAreaInsets.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.view.safeAreaInsets.bottom;
return secondaryToolbar.intrinsicContentSize.height + unsafeHeight;
}
- (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);
NamedGuide* guide =
[[NamedGuide alloc] initWithName:kSecondaryToolbarNoFullscreenGuide];
[self.view addLayoutGuide:guide];
guide.constrainedView = containerView;
}
}
// 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);
NamedGuide* guide =
[[NamedGuide alloc] initWithName:kSecondaryToolbarNoFullscreenGuide];
[self.view addLayoutGuide:guide];
guide.constrainedView = containerView;
}
// Adds constraints to the primary and secondary toolbars, anchoring them to the
// top and bottom of the browser view.
- (void)addConstraintsToToolbar {
[self addConstraintsToPrimaryToolbar];
if (base::FeatureList::IsEnabled(
toolbar_container::kToolbarContainerEnabled)) {
[self addConstraintsToSecondaryToolbarContainer];
} else {
[self addConstraintsToSecondaryToolbar];
}
[[self view] layoutIfNeeded];
}
// Enable functionality that only makes sense if the views are loaded and
// both browser state and tab model are valid.
- (void)addUIFunctionalityForModelAndBrowserState {
DCHECK(_browserState);
DCHECK(_locationBarModel);
DCHECK(self.tabModel);
DCHECK([self isViewLoaded]);
[self.sideSwipeController addHorizontalGesturesToView:self.view];
// Create child coordinators.
_activityServiceCoordinator = [[ActivityServiceLegacyCoordinator alloc]
initWithBaseViewController:self];
_activityServiceCoordinator.dispatcher = self.commandDispatcher;
_activityServiceCoordinator.tabModel = self.tabModel;
_activityServiceCoordinator.browserState = _browserState;
_activityServiceCoordinator.positionProvider =
[self.primaryToolbarCoordinator activityServicePositioner];
_activityServiceCoordinator.presentationProvider = self;
// DownloadManagerCoordinator is already created.
DCHECK(_downloadManagerCoordinator);
_downloadManagerCoordinator.webStateList = self.tabModel.webStateList;
_downloadManagerCoordinator.bottomMarginHeightAnchor =
[NamedGuide guideWithName:kSecondaryToolbarGuide view:self.contentArea]
.heightAnchor;
self.popupMenuCoordinator = [[PopupMenuCoordinator alloc]
initWithBaseViewController:self
browserState:self.browserState];
self.popupMenuCoordinator.bubblePresenter = self.bubblePresenter;
self.popupMenuCoordinator.dispatcher = self.commandDispatcher;
self.popupMenuCoordinator.webStateList = self.tabModel.webStateList;
self.popupMenuCoordinator.UIUpdater = _toolbarCoordinatorAdaptor;
[self.popupMenuCoordinator start];
self.primaryToolbarCoordinator.longPressDelegate = self.popupMenuCoordinator;
self.secondaryToolbarCoordinator.longPressDelegate =
self.popupMenuCoordinator;
self.tabStripCoordinator.longPressDelegate = self.popupMenuCoordinator;
_sadTabCoordinator = [[SadTabCoordinator alloc]
initWithBaseViewController:self.browserContainerViewController
browserState:_browserState];
_sadTabCoordinator.dispatcher = self.dispatcher;
_sadTabCoordinator.overscrollDelegate = self;
// If there are any existing SadTabHelpers in |self.tabModel|, update the
// helpers delegate with the new |_sadTabCoordinator|.
DCHECK(_sadTabCoordinator);
for (NSUInteger i = 0; i < self.tabModel.count; i++) {
SadTabTabHelper* sadTabHelper =
SadTabTabHelper::FromWebState([self.tabModel tabAtIndex:i].webState);
sadTabHelper->SetDelegate(_sadTabCoordinator);
}
_paymentRequestManager = [[PaymentRequestManager alloc]
initWithBaseViewController:self
browserState:_browserState
dispatcher:self.dispatcher];
[_paymentRequestManager setLocationBarModel:_locationBarModel.get()];
[_paymentRequestManager setActiveWebState:self.currentWebState];
}
// Set the frame for the various views. View must be loaded.
- (void)setUpViewLayout:(BOOL)initialLayout {
DCHECK([self isViewLoaded]);
CGFloat topInset = self.view.safeAreaInsets.top;
// Update the fake toolbar background height.
CGRect fakeStatusBarFrame = _fakeStatusBarView.frame;
fakeStatusBarFrame.size.height = topInset;
_fakeStatusBarView.frame = fakeStatusBarFrame;
// Position the toolbar next, either at the top of the browser view or
// directly under the tabstrip.
if (initialLayout) {
[self addChildViewController:self.primaryToolbarCoordinator.viewController];
if (self.secondaryToolbarCoordinator) {
if (base::FeatureList::IsEnabled(
toolbar_container::kToolbarContainerEnabled)) {
[self addChildViewController:self.secondaryToolbarContainerCoordinator
.viewController];
} else {
[self addChildViewController:self.secondaryToolbarCoordinator
.viewController];
}
}
}
// Place the toolbar controller above the infobar container and adds the
// layout guides.
if (initialLayout) {
UIView* bottomView =
IsIPadIdiom() ? _fakeStatusBarView
: [self.infobarContainerCoordinator legacyContainerView];
[[self view]
insertSubview:self.primaryToolbarCoordinator.viewController.view
aboveSubview:bottomView];
if (self.secondaryToolbarCoordinator) {
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:self.primaryToolbarCoordinator.viewController.view];
} 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:self.primaryToolbarCoordinator.viewController.view];
self.secondaryToolbarContainerView = container;
}
}
NSArray<GuideName*>* guideNames = @[
kContentAreaGuide,
kOmniboxGuide,
kOmniboxLeadingImageGuide,
kOmniboxTextFieldGuide,
kBackButtonGuide,
kForwardButtonGuide,
kToolsMenuGuide,
kTabSwitcherGuide,
kTranslateInfobarOptionsGuide,
kSearchButtonGuide,
kSecondaryToolbarGuide,
kVoiceSearchButtonGuide,
];
AddNamedGuidesToView(guideNames, self.view);
// Configure the content area guide.
NamedGuide* contentAreaGuide = [NamedGuide guideWithName:kContentAreaGuide
view:self.view];
// Constrain top to bottom of top toolbar.
UIView* primaryToolbarView =
self.primaryToolbarCoordinator.viewController.view;
[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);
}
if (initialLayout) {
[self.primaryToolbarCoordinator.viewController
didMoveToParentViewController:self];
if (self.secondaryToolbarCoordinator) {
if (base::FeatureList::IsEnabled(
toolbar_container::kToolbarContainerEnabled)) {
[self.secondaryToolbarContainerCoordinator.viewController
didMoveToParentViewController:self];
} else {
[self.secondaryToolbarCoordinator.viewController
didMoveToParentViewController:self];
}
}
}
// Attach the typing shield to the content area but have it hidden.
self.typingShield.frame = self.contentArea.frame;
if (initialLayout) {
[self.view insertSubview:self.typingShield aboveSubview:self.contentArea];
[self.typingShield setHidden:YES];
}
}
- (void)displayTab:(Tab*)tab {
DCHECK(tab);
[self loadViewIfNeeded];
// Set this before triggering any of the possible page loads below.
tab.webState->SetKeepRenderProcessAlive(true);
if (!self.inNewTabAnimation) {
// Hide findbar. |updateToolbar| will restore the findbar later.
[self hideFindBarWithAnimation:NO];
// Make new content visible, resizing it first as the orientation may
// have changed from the last time it was displayed.
[self viewForTab:tab].frame = self.contentArea.bounds;
[_toolbarUIUpdater updateState];
NewTabPageTabHelper* NTPHelper =
NewTabPageTabHelper::FromWebState(tab.webState);
if (NTPHelper && NTPHelper->IsActive()) {
UIViewController* viewController =
_ntpCoordinatorsForWebStates[tab.webState].viewController;
viewController.view.frame = [self ntpFrameForWebState:tab.webState];
// TODO(crbug.com/873729): For a newly created WebState, the session will
// not be restored until LoadIfNecessary call. Remove when fixed.
tab.webState->GetNavigationManager()->LoadIfNecessary();
// Always show the webState view under the NTP, to work around
// crbug.com/848789
if (base::FeatureList::IsEnabled(kBrowserContainerKeepsContentView)) {
if (self.browserContainerViewController.contentView !=
tab.webState->GetView()) {
self.browserContainerViewController.contentView =
tab.webState->GetView();
}
self.browserContainerViewController.contentView.frame =
self.contentArea.bounds;
} else {
self.browserContainerViewController.contentView = nil;
}
self.browserContainerViewController.contentViewController =
viewController;
} else {
self.browserContainerViewController.contentView = [self viewForTab:tab];
}
}
[self updateToolbar];
// Notify the WebState that it was displayed.
DCHECK(tab.webState);
tab.webState->WasShown();
}
- (void)initializeBookmarkInteractionController {
if (_bookmarkInteractionController)
return;
_bookmarkInteractionController = [[BookmarkInteractionController alloc]
initWithBrowserState:_browserState
parentController:self
dispatcher:self.dispatcher
webStateList:self.tabModel.webStateList];
}
- (void)setOverScrollActionControllerToStaticNativeContent:
(StaticHtmlNativeContent*)nativeContent {
if (!IsIPadIdiom()) {
OverscrollActionsController* controller =
[[OverscrollActionsController alloc]
initWithScrollView:[nativeContent scrollView]];
[controller setDelegate:self];
OverscrollStyle style = _isOffTheRecord
? OverscrollStyle::REGULAR_PAGE_INCOGNITO
: OverscrollStyle::REGULAR_PAGE_NON_INCOGNITO;
controller.style = style;
nativeContent.overscrollActionsController = controller;
}
}
#pragma mark - Private Methods: UI Configuration, update and Layout
// 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 && _browserState))
return;
web::WebState* webState = self.currentWebState;
if (!webState)
return;
PrerenderService* prerenderService =
PrerenderServiceFactory::GetForBrowserState(self.browserState);
BOOL isPrerendered =
(prerenderService && prerenderService->IsLoadingPrerender());
if (isPrerendered && ![self.helper isToolbarLoading:self.currentWebState])
[self.primaryToolbarCoordinator showPrerenderingAnimation];
auto* findHelper = FindTabHelper::FromWebState(webState);
if (findHelper && findHelper->IsFindUIActive()) {
[self showFindBarWithAnimation:NO
selectText:YES
shouldFocus:[_findBarController isFocused]];
}
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.
BOOL isNTP = false;
if (base::FeatureList::IsEnabled(kBrowserContainerContainsNTP)) {
NewTabPageTabHelper* NTPHelper =
NewTabPageTabHelper::FromWebState(webState);
isNTP = NTPHelper && NTPHelper->IsActive();
} else {
isNTP = IsVisibleURLNewTabPage(webState);
}
// 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];
}
- (void)updateBroadcastState {
self.broadcasting = self.active && self.viewVisible;
}
- (void)updateDialogPresenterActiveState {
self.dialogPresenter.active =
self.active && self.viewVisible && !self.inNewTabAnimation;
}
- (void)dismissPopups {
// The dispatcher may not be fully connected during shutdown, so selectors may
// be unrecognized.
if (_isShutdown)
return;
[self.dispatcher hidePageInfo];
[self.dispatcher dismissPopupMenuAnimated:NO];
[self.bubblePresenter dismissBubbles];
}
- (UIView*)footerView {
return self.secondaryToolbarCoordinator.viewController.view;
}
- (CGRect)ntpFrameForWebState:(web::WebState*)webState {
NewTabPageTabHelper* NTPHelper = NewTabPageTabHelper::FromWebState(webState);
if (!NTPHelper || !NTPHelper->IsActive())
return CGRectZero;
if (!IsRegularXRegularSizeClass())
return self.contentArea.bounds;
// NTP expects to be laid out behind the bottom toolbar. It uses
// |contentInset| to push content above the toolbar.
UIEdgeInsets viewportInsets = [self viewportInsetsForView:self.contentArea];
viewportInsets.bottom = 0.0;
return UIEdgeInsetsInsetRect(self.contentArea.bounds, viewportInsets);
}
- (CGRect)visibleFrameForTab:(Tab*)tab {
UIView* tabView = [self viewForTab:tab];
return UIEdgeInsetsInsetRect(tabView.bounds,
[self viewportInsetsForView:tabView]);
}
- (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*)viewForTab:(Tab*)tab {
DCHECK(tab);
if (!tab.webState)
return nil;
web::WebState* webState = tab.webState;
NewTabPageTabHelper* NTPHelper = NewTabPageTabHelper::FromWebState(webState);
if (NTPHelper && NTPHelper->IsActive()) {
return _ntpCoordinatorsForWebStates[webState].viewController.view;
}
DCHECK([self.tabModel indexOfTab:tab] != NSNotFound);
// TODO(crbug.com/904588): Move |RecordPageLoadStart| to TabUsageRecorder.
if (webState->IsEvicted() && [self.tabModel tabUsageRecorder]) {
[self.tabModel tabUsageRecorder] -> 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: Find Bar UI
- (void)hideFindBarWithAnimation:(BOOL)animate {
[_findBarController hideFindBarView:animate];
}
- (void)showFindBarWithAnimation:(BOOL)animate
selectText:(BOOL)selectText
shouldFocus:(BOOL)shouldFocus {
DCHECK(_findBarController);
[_findBarController
addFindBarViewToParentView:self.view
usingToolbarView:_primaryToolbarCoordinator.viewController.view
selectText:selectText
animated:animate];
[self updateFindBar:YES shouldFocus:shouldFocus];
}
- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus {
// TODO(crbug.com/731045): This early return temporarily replaces a DCHECK.
// For unknown reasons, this DCHECK sometimes was hit in the wild, resulting
// in a crash.
if (!self.currentWebState) {
return;
}
auto* helper = FindTabHelper::FromWebState(self.currentWebState);
if (helper && helper->IsFindUIActive()) {
if (initialUpdate && !_isOffTheRecord) {
helper->RestoreSearchTerm();
}
[self setFramesForHeaders:[self headerViews]
atOffset:[self currentHeaderOffset]];
[_findBarController updateView:helper->GetFindResult()
initialUpdate:initialUpdate
focusTextfield:shouldFocus];
} else {
[self hideFindBarWithAnimation:YES];
}
}
#pragma mark - Private Methods: Alerts
- (void)showErrorAlertWithStringTitle:(NSString*)title
message:(NSString*)message {
// Dismiss current alert.
[_alertCoordinator stop];
_alertCoordinator = [_dependencyFactory alertCoordinatorWithTitle:title
message:message
viewController:self];
[_alertCoordinator start];
}
- (void)showSnackbar:(NSString*)text {
MDCSnackbarMessage* message = [MDCSnackbarMessage messageWithText:text];
message.accessibilityLabel = text;
message.duration = 2.0;
message.category = kBrowserViewControllerSnackbarCategory;
[self.dispatcher showSnackbarMessage:message];
}
#pragma mark - Private Methods: Tap handling
- (void)setLastTapPointFromCommand:(CGPoint)originPoint {
if (CGPointEqualToPoint(originPoint, CGPointZero)) {
_lastTapPoint = CGPointZero;
} else {
_lastTapPoint = [self.view.window convertPoint:originPoint
toView:self.view];
}
_lastTapTime = CACurrentMediaTime();
}
- (CGPoint)lastTapPoint {
if (CACurrentMediaTime() - _lastTapTime < 1) {
return _lastTapPoint;
}
return CGPointZero;
}
- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer {
UIView* view = gestureRecognizer.view;
CGPoint viewCoordinate = [gestureRecognizer locationInView:view];
_lastTapPoint = [[view superview] convertPoint:viewCoordinate
toView:self.view];
_lastTapTime = CACurrentMediaTime();
}
#pragma mark - Private Methods: Tab creation and selection
- (BOOL)isTabNativePage:(Tab*)tab {
web::WebState* webState = tab.webState;
if (!webState)
return NO;
web::NavigationItem* visibleItem =
webState->GetNavigationManager()->GetVisibleItem();
if (!visibleItem)
return NO;
return web::GetWebClient()->IsAppSpecificURL(visibleItem->GetURL());
}
- (void)installDelegatesForTab:(Tab*)tab {
// Unregistration happens when the Tab is removed from the TabModel.
DCHECK_NE(tab.webState->GetDelegate(), _webStateDelegate.get());
// There should be no pre-rendered Tabs in TabModel.
PrerenderService* prerenderService =
PrerenderServiceFactory::GetForBrowserState(_browserState);
DCHECK(!prerenderService ||
!prerenderService->IsWebStatePrerendered(tab.webState));
SnapshotTabHelper::FromWebState(tab.webState)->SetDelegate(self);
// TODO(crbug.com/777557): do not pass the dispatcher to PasswordTabHelper.
if (PasswordTabHelper* passwordTabHelper =
PasswordTabHelper::FromWebState(tab.webState)) {
passwordTabHelper->SetBaseViewController(self);
passwordTabHelper->SetDispatcher(self.dispatcher);
passwordTabHelper->SetPasswordControllerDelegate(self);
}
if (!IsIPadIdiom()) {
OverscrollActionsTabHelper::FromWebState(tab.webState)->SetDelegate(self);
}
// Install the proper CRWWebController delegates.
tab.webController.nativeProvider = self;
tab.webController.swipeRecognizerProvider = self.sideSwipeController;
tab.webState->SetDelegate(_webStateDelegate.get());
SadTabTabHelper::FromWebState(tab.webState)->SetDelegate(_sadTabCoordinator);
NetExportTabHelper::CreateForWebState(tab.webState, self);
CaptivePortalDetectorTabHelper::CreateForWebState(tab.webState, self);
if (reading_list::IsOfflinePageWithoutNativeContentEnabled()) {
OfflinePageTabHelper::CreateForWebState(
tab.webState,
ReadingListModelFactory::GetForBrowserState(_browserState));
}
// DownloadManagerTabHelper cannot function without delegate.
DCHECK(_downloadManagerCoordinator);
DownloadManagerTabHelper::CreateForWebState(tab.webState,
_downloadManagerCoordinator);
if (base::FeatureList::IsEnabled(kBrowserContainerContainsNTP)) {
NewTabPageTabHelper::CreateForWebState(tab.webState, self);
}
// The language detection helper accepts a callback from the translate
// client, so must be created after it.
// This will explode if the webState doesn't have a JS injection manager