blob: 6735a71654ff7c22f16674edab2ef1539d62a816 [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_controller.h"
#import <AssetsLibrary/AssetsLibrary.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import <PassKit/PassKit.h>
#import <QuartzCore/QuartzCore.h>
#include <stdint.h>
#include <cmath>
#include <memory>
#include "base/base64.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/i18n/rtl.h"
#include "base/ios/block_types.h"
#include "base/ios/ios_util.h"
#include "base/logging.h"
#include "base/mac/bind_objc_block.h"
#include "base/mac/bundle_locations.h"
#include "base/mac/foundation_util.h"
#include "base/macros.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"
#include "base/task_scheduler/post_task.h"
#include "base/threading/sequenced_worker_pool.h"
#include "components/bookmarks/browser/base_bookmark_model_observer.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/favicon/ios/web_favicon_driver.h"
#include "components/feature_engagement/public/event_constants.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/feature_engagement/public/tracker.h"
#include "components/image_fetcher/ios/ios_image_data_fetcher_wrapper.h"
#include "components/infobars/core/infobar_manager.h"
#import "components/language/ios/browser/ios_language_detection_tab_helper.h"
#include "components/payments/core/features.h"
#include "components/prefs/pref_service.h"
#include "components/reading_list/core/reading_list_model.h"
#include "components/search_engines/search_engines_pref_names.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/toolbar/toolbar_model_impl.h"
#include "ios/chrome/app/tests_hook.h"
#include "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#include "ios/chrome/browser/chrome_url_constants.h"
#include "ios/chrome/browser/chrome_url_util.h"
#import "ios/chrome/browser/download/pass_kit_tab_helper.h"
#include "ios/chrome/browser/experimental_flags.h"
#import "ios/chrome/browser/favicon/favicon_loader.h"
#include "ios/chrome/browser/favicon/ios_chrome_favicon_loader_factory.h"
#include "ios/chrome/browser/feature_engagement/tracker_factory.h"
#include "ios/chrome/browser/feature_engagement/tracker_util.h"
#import "ios/chrome/browser/find_in_page/find_in_page_controller.h"
#import "ios/chrome/browser/find_in_page/find_in_page_model.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_container_delegate_ios.h"
#include "ios/chrome/browser/infobars/infobar_container_ios.h"
#import "ios/chrome/browser/infobars/infobar_container_state_delegate.h"
#include "ios/chrome/browser/infobars/infobar_container_view.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/passwords/password_controller.h"
#include "ios/chrome/browser/passwords/password_tab_helper.h"
#include "ios/chrome/browser/pref_names.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/offline_url_utils.h"
#include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
#include "ios/chrome/browser/search_engines/template_url_service_factory.h"
#include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
#include "ios/chrome/browser/sessions/session_util.h"
#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios.h"
#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios_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_cache.h"
#import "ios/chrome/browser/snapshots/snapshot_generator_delegate.h"
#import "ios/chrome/browser/snapshots/snapshot_overlay.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"
#import "ios/chrome/browser/store_kit/store_kit_tab_helper.h"
#import "ios/chrome/browser/tabs/legacy_tab_helper.h"
#import "ios/chrome/browser/tabs/tab.h"
#import "ios/chrome/browser/tabs/tab_dialog_delegate.h"
#import "ios/chrome/browser/tabs/tab_headers_delegate.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/translate/language_selection_handler.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/alert_coordinator/repost_form_coordinator.h"
#import "ios/chrome/browser/ui/authentication/re_signin_infobar_delegate.h"
#import "ios/chrome/browser/ui/background_generator.h"
#import "ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.h"
#include "ios/chrome/browser/ui/bookmarks/bookmark_model_bridge_observer.h"
#import "ios/chrome/browser/ui/browser_container_view.h"
#import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h"
#import "ios/chrome/browser/ui/bubble/bubble_util.h"
#import "ios/chrome/browser/ui/bubble/bubble_view_controller_presenter.h"
#import "ios/chrome/browser/ui/chrome_web_view_factory.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/open_url_command.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/snackbar_commands.h"
#import "ios/chrome/browser/ui/commands/start_voice_search_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/legacy_download_manager_controller.h"
#import "ios/chrome/browser/ui/download/pass_kit_coordinator.h"
#import "ios/chrome/browser/ui/elements/activity_overlay_coordinator.h"
#import "ios/chrome/browser/ui/external_file_controller.h"
#import "ios/chrome/browser/ui/external_search/external_search_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_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_foreground_animator.h"
#import "ios/chrome/browser/ui/fullscreen/fullscreen_scroll_end_animator.h"
#import "ios/chrome/browser/ui/fullscreen/fullscreen_scroll_to_top_animator.h"
#import "ios/chrome/browser/ui/fullscreen/fullscreen_ui_element.h"
#import "ios/chrome/browser/ui/fullscreen/fullscreen_ui_updater.h"
#import "ios/chrome/browser/ui/fullscreen/legacy_fullscreen_controller.h"
#import "ios/chrome/browser/ui/history_popup/requirements/tab_history_presentation.h"
#import "ios/chrome/browser/ui/history_popup/tab_history_legacy_coordinator.h"
#import "ios/chrome/browser/ui/image_util/image_saver.h"
#import "ios/chrome/browser/ui/key_commands_provider.h"
#import "ios/chrome/browser/ui/location_bar_notification_names.h"
#import "ios/chrome/browser/ui/main/main_feature_flags.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/new_foreground_tab_fullscreen_disabler.h"
#import "ios/chrome/browser/ui/ntp/new_tab_page_controller.h"
#import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_handset_coordinator.h"
#import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
#import "ios/chrome/browser/ui/page_info/page_info_legacy_coordinator.h"
#import "ios/chrome/browser/ui/page_info/requirements/page_info_presentation.h"
#import "ios/chrome/browser/ui/page_not_available_controller.h"
#import "ios/chrome/browser/ui/payments/payment_request_manager.h"
#import "ios/chrome/browser/ui/presenters/vertical_animation_container.h"
#import "ios/chrome/browser/ui/print/print_controller.h"
#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_legacy_coordinator.h"
#import "ios/chrome/browser/ui/qr_scanner/requirements/qr_scanner_presenting.h"
#import "ios/chrome/browser/ui/reading_list/offline_page_native_content.h"
#import "ios/chrome/browser/ui/reading_list/reading_list_coordinator.h"
#import "ios/chrome/browser/ui/reading_list/reading_list_menu_notifier.h"
#include "ios/chrome/browser/ui/rtl_geometry.h"
#import "ios/chrome/browser/ui/sad_tab/sad_tab_legacy_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/signin_interaction/public/signin_presenter.h"
#import "ios/chrome/browser/ui/snackbar/snackbar_coordinator.h"
#import "ios/chrome/browser/ui/stack_view/card_view.h"
#import "ios/chrome/browser/ui/stack_view/page_animation_util.h"
#import "ios/chrome/browser/ui/static_content/static_html_native_content.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_controller.h"
#import "ios/chrome/browser/ui/tabs/requirements/tab_strip_constants.h"
#import "ios/chrome/browser/ui/tabs/requirements/tab_strip_presentation.h"
#import "ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.h"
#import "ios/chrome/browser/ui/toolbar/adaptive/adaptive_toolbar_coordinator.h"
#import "ios/chrome/browser/ui/toolbar/adaptive/adaptive_toolbar_view_controller.h"
#import "ios/chrome/browser/ui/toolbar/adaptive/primary_toolbar_coordinator.h"
#import "ios/chrome/browser/ui/toolbar/adaptive/secondary_toolbar_coordinator.h"
#import "ios/chrome/browser/ui/toolbar/adaptive/toolbar_coordinator_adaptor.h"
#include "ios/chrome/browser/ui/toolbar/legacy_toolbar_coordinator.h"
#import "ios/chrome/browser/ui/toolbar/legacy_toolbar_ui_updater.h"
#import "ios/chrome/browser/ui/toolbar/public/primary_toolbar_coordinator.h"
#import "ios/chrome/browser/ui/toolbar/public/toolbar_controller_base_feature.h"
#include "ios/chrome/browser/ui/toolbar/toolbar_model_delegate_ios.h"
#include "ios/chrome/browser/ui/toolbar/toolbar_model_ios.h"
#import "ios/chrome/browser/ui/toolbar/toolbar_snapshot_providing.h"
#import "ios/chrome/browser/ui/toolbar/toolbar_ui.h"
#import "ios/chrome/browser/ui/toolbar/toolbar_ui_broadcasting_util.h"
#import "ios/chrome/browser/ui/toolbar/web_toolbar_controller.h"
#import "ios/chrome/browser/ui/tools_menu/public/tools_menu_configuration_provider.h"
#import "ios/chrome/browser/ui/tools_menu/public/tools_menu_presentation_provider.h"
#import "ios/chrome/browser/ui/tools_menu/tools_menu_configuration.h"
#import "ios/chrome/browser/ui/tools_menu/tools_menu_view_item.h"
#import "ios/chrome/browser/ui/translate/language_selection_coordinator.h"
#include "ios/chrome/browser/ui/ui_util.h"
#import "ios/chrome/browser/ui/uikit_ui_util.h"
#import "ios/chrome/browser/ui/util/named_guide.h"
#import "ios/chrome/browser/ui/util/pasteboard_util.h"
#import "ios/chrome/browser/ui/voice/text_to_speech_player.h"
#include "ios/chrome/browser/upgrade/upgrade_center.h"
#import "ios/chrome/browser/web/blocked_popup_tab_helper.h"
#import "ios/chrome/browser/web/error_page_content.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/passkit_dialog_provider.h"
#include "ios/chrome/browser/web/print_tab_helper.h"
#import "ios/chrome/browser/web/repost_form_tab_helper.h"
#import "ios/chrome/browser/web/repost_form_tab_helper_delegate.h"
#import "ios/chrome/browser/web/sad_tab_tab_helper.h"
#import "ios/chrome/browser/web/tab_id_tab_helper.h"
#include "ios/chrome/browser/web/web_state_printer.h"
#import "ios/chrome/browser/web_state_list/web_state_list.h"
#import "ios/chrome/browser/web_state_list/web_state_opener.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"
#include "ios/chrome/grit/ios_chromium_strings.h"
#include "ios/chrome/grit/ios_strings.h"
#import "ios/net/request_tracker.h"
#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
#include "ios/public/provider/chrome/browser/ui/app_rating_prompt.h"
#include "ios/public/provider/chrome/browser/ui/default_ios_web_view_factory.h"
#import "ios/public/provider/chrome/browser/voice/voice_search_bar.h"
#import "ios/public/provider/chrome/browser/voice/voice_search_bar_owner.h"
#include "ios/public/provider/chrome/browser/voice/voice_search_controller.h"
#include "ios/public/provider/chrome/browser/voice/voice_search_controller_delegate.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/public/navigation_item.h"
#import "ios/web/public/navigation_manager.h"
#include "ios/web/public/referrer_util.h"
#include "ios/web/public/ssl_status.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/ui/crw_native_content_provider.h"
#import "ios/web/public/web_state/ui/crw_web_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_thread.h"
#import "ios/web/web_state/ui/crw_web_controller.h"
#import "net/base/mac/url_conversions.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "net/ssl/ssl_info.h"
#include "net/url_request/url_request_context_getter.h"
#include "third_party/google_toolbox_for_mac/src/iPhone/GTMUIImage+Resize.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/l10n/l10n_util_mac.h"
#include "ui/base/page_transition_types.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;
class InfoBarContainerDelegateIOS;
namespace {
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_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);
}
}
// Returns the status bar background color.
UIColor* StatusBarBackgroundColor() {
return [UIColor colorWithRed:0.149 green:0.149 blue:0.164 alpha:1];
}
// Duration of the toolbar animation.
const NSTimeInterval kLegacyFullscreenControllerToolbarAnimationDuration = 0.3;
const CGFloat kVoiceSearchBarHeight = 59.0;
// Dimensions to use when downsizing an image for search-by-image.
const CGFloat kSearchByImageMaxImageArea = 90000.0;
const CGFloat kSearchByImageMaxImageWidth = 600.0;
const CGFloat kSearchByImageMaxImageHeight = 400.0;
enum HeaderBehaviour {
// The header moves completely out of the screen.
Hideable = 0,
// This header stays on screen and doesn't overlap with the content.
Visible,
// This header stay on screen and covers part of the content.
Overlap
};
const CGFloat kIPadFindBarOverlap = 11;
bool IsURLAllowedInIncognito(const GURL& url) {
// Most URLs are allowed in incognito; the following is an exception.
return !(url.SchemeIs(kChromeUIScheme) && url.host() == kChromeUIHistoryHost);
}
// Snackbar category for browser view controller.
NSString* const kBrowserViewControllerSnackbarCategory =
@"BrowserViewControllerSnackbarCategory";
} // namespace
#pragma mark - HeaderDefinition helper
@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;
// Reduces the height of a header to adjust for shadows.
@property(nonatomic, assign) CGFloat heightAdjustement;
// Nudges that particular header up by this number of points.
@property(nonatomic, assign) CGFloat inset;
- (instancetype)initWithView:(UIView*)view
headerBehaviour:(HeaderBehaviour)behaviour
heightAdjustment:(CGFloat)heightAdjustment
inset:(CGFloat)inset;
+ (instancetype)definitionWithView:(UIView*)view
headerBehaviour:(HeaderBehaviour)behaviour
heightAdjustment:(CGFloat)heightAdjustment
inset:(CGFloat)inset;
@end
@implementation HeaderDefinition
@synthesize view = _view;
@synthesize behaviour = _behaviour;
@synthesize heightAdjustement = _heightAdjustement;
@synthesize inset = _inset;
+ (instancetype)definitionWithView:(UIView*)view
headerBehaviour:(HeaderBehaviour)behaviour
heightAdjustment:(CGFloat)heightAdjustment
inset:(CGFloat)inset {
return [[self alloc] initWithView:view
headerBehaviour:behaviour
heightAdjustment:heightAdjustment
inset:inset];
}
- (instancetype)initWithView:(UIView*)view
headerBehaviour:(HeaderBehaviour)behaviour
heightAdjustment:(CGFloat)heightAdjustment
inset:(CGFloat)inset {
self = [super init];
if (self) {
_view = view;
_behaviour = behaviour;
_heightAdjustement = heightAdjustment;
_inset = inset;
}
return self;
}
@end
#pragma mark - BVC
@interface BrowserViewController ()<ActivityServicePresentation,
AppRatingPromptDelegate,
BookmarkModelBridgeObserver,
CaptivePortalDetectorTabHelperDelegate,
CRWNativeContentProvider,
CRWWebStateDelegate,
DialogPresenterDelegate,
FullscreenUIElement,
LegacyFullscreenControllerDelegate,
InfobarContainerStateDelegate,
KeyCommandsPlumbing,
MainContentUI,
ManageAccountsDelegate,
MFMailComposeViewControllerDelegate,
NetExportTabHelperDelegate,
OverscrollActionsControllerDelegate,
PageInfoPresentation,
PassKitDialogProvider,
PasswordControllerDelegate,
PreloadControllerDelegate,
QRScannerPresenting,
RepostFormTabHelperDelegate,
SideSwipeControllerDelegate,
SigninPresenter,
SKStoreProductViewControllerDelegate,
SnapshotGeneratorDelegate,
StoreKitLauncher,
TabDialogDelegate,
TabHeadersDelegate,
TabHistoryPresentation,
TabModelObserver,
TabStripPresentation,
ToolsMenuConfigurationProvider,
UIGestureRecognizerDelegate,
UpgradeCenterClient,
VoiceSearchBarDelegate,
VoiceSearchBarOwner,
WebStatePrinter> {
// The dependency factory passed on initialization. Used to vend objects used
// by the BVC.
BrowserViewControllerDependencyFactory* _dependencyFactory;
// The browser's tab model.
TabModel* _model;
// Facade objects used by |_toolbarCoordinator|.
// Must outlive |_toolbarCoordinator|.
std::unique_ptr<ToolbarModelDelegateIOS> _toolbarModelDelegate;
std::unique_ptr<ToolbarModelIOS> _toolbarModelIOS;
// 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;
// Handles command dispatching.
CommandDispatcher* _dispatcher;
// 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 Reading List.
ReadingListCoordinator* _readingListCoordinator;
// Used to display the Find In Page UI. Nil if not visible.
FindBarControllerIOS* _findBarController;
// Used to display the Print UI. Nil if not visible.
PrintController* _printController;
// 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 the user interacts with the location bar.
BOOL _locationBarHasFocus;
// 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;
// A single infobar container handles all infobars in all tabs. It keeps
// track of infobars for current tab (accessed via infobar helper of
// the current tab).
std::unique_ptr<InfoBarContainerIOS> _infoBarContainer;
// Bridge class to deliver container change notifications to BVC.
std::unique_ptr<InfoBarContainerDelegateIOS> _infoBarContainerDelegate;
// Voice search bar at the bottom of the view overlayed on |_contentArea|
// when displaying voice search results.
UIView<VoiceSearchBar>* _voiceSearchBar;
// The image fetcher used to save images and perform image-based searches.
std::unique_ptr<image_fetcher::IOSImageDataFetcherWrapper> _imageFetcher;
// Bridge to register for bookmark changes.
std::unique_ptr<bookmarks::BookmarkModelBridge> _bookmarkModelBridge;
// Cached pointer to the bookmarks model.
bookmarks::BookmarkModel* _bookmarkModel; // weak
// The controller that shows the bookmarking UI after the user taps the star
// button.
BookmarkInteractionController* _bookmarkInteractionController;
// The currently displayed "Rate This App" dialog, if one exists.
id<AppRatingPrompt> _rateThisAppDialog;
// Native controller vended to tab before Tab is added to the tab model.
__weak id _temporaryNativeController;
// Notifies the toolbar menu of reading list changes.
ReadingListMenuNotifier* _readingListMenuNotifier;
// The view used by the voice search presentation animation.
__weak UIView* _voiceSearchButton;
// Coordinator for the share menu (Activity Services).
ActivityServiceLegacyCoordinator* _activityServiceCoordinator;
// Coordinator for displaying alerts.
AlertCoordinator* _alertCoordinator;
// Coordinator for the QR scanner.
QRScannerLegacyCoordinator* _qrScannerCoordinator;
// Coordinator for Tab History Popup.
LegacyTabHistoryCoordinator* _tabHistoryCoordinator;
// Coordinator for displaying Sad Tab.
SadTabLegacyCoordinator* _sadTabCoordinator;
// Coordinator for Page Info UI.
PageInfoLegacyCoordinator* _pageInfoCoordinator;
// Coordinator for displaying Repost Form dialog.
RepostFormCoordinator* _repostFormCoordinator;
// Coordinator for displaying snackbars.
SnackbarCoordinator* _snackbarCoordinator;
// Coordinator for the toolbar.
LegacyToolbarCoordinator* _toolbarCoordinator;
// 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;
// The fullscreen disabler for the new foreground tab animation.
std::unique_ptr<NewForegroundTabFullscreenDisabler>
_foregroundTabAnimationFullscreenDisabler;
// Coordinator for the External Search UI.
ExternalSearchCoordinator* _externalSearchCoordinator;
// Coordinator for the language selection UI.
LanguageSelectionCoordinator* _languageSelectionCoordinator;
// Coordinator for the PassKit UI presentation.
PassKitCoordinator* _passKitCoordinator;
// Fake status bar view used to blend the toolbar into the status bar.
UIView* _fakeStatusBarView;
// Stores whether the Tab currently inserted was a pre-rendered Tab. This
// is used to determine whether the pre-rendering animation should be played
// or not.
BOOL _insertedTabWasPrerenderedTab;
}
// 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;
// Whether the current tab can enable the request desktop menu item.
@property(nonatomic, assign, readonly) BOOL canUseDesktopUserAgent;
// Whether the sharing menu should be enabled.
@property(nonatomic, assign, readonly) BOOL canShowShareMenu;
// 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;
// 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;
// Whether the VoiceSearchBar should be displayed.
@property(nonatomic, readonly) BOOL shouldShowVoiceSearchBar;
// 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 Recent Tabs.
@property(nonatomic, strong)
RecentTabsHandsetCoordinator* recentTabsCoordinator;
// Coordinator for tablet tab strip.
@property(nonatomic, strong) TabStripLegacyCoordinator* tabStripCoordinator;
// 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;
// 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;
// Used to display the new tab tip in-product help promotion bubble. |nil| if
// the new tab tip bubble has not yet been presented. Once the bubble is
// dismissed, it remains allocated so that |userEngaged| remains accessible.
@property(nonatomic, strong)
BubbleViewControllerPresenter* tabTipBubblePresenter;
// Used to display the new incognito tab tip in-product help promotion bubble.
@property(nonatomic, strong)
BubbleViewControllerPresenter* incognitoTabTipBubblePresenter;
// Primary toolbar.
@property(nonatomic, strong) id<PrimaryToolbarCoordinator>
primaryToolbarCoordinator;
// Secondary toolbar.
@property(nonatomic, strong)
AdaptiveToolbarCoordinator* secondaryToolbarCoordinator;
// Interface object with the toolbars.
@property(nonatomic, strong)
id<ToolbarCoordinating, ToolsMenuPresentationStateProvider>
toolbarInterface;
// TODO(crbug.com/788705): Removes this property and associated calls.
// Returns the LegacyToolbarCoordinator. This property is here to separate
// methods which will be removed during cleanup to other methods. Uses this
// property only for deprecated methods.
@property(nonatomic, readonly) id<LegacyToolbarCoordinator>
legacyToolbarCoordinator;
// Vertical offset for fullscreen toolbar.
@property(nonatomic, strong) NSLayoutConstraint* primaryToolbarOffsetConstraint;
// Height constraint for toolbar.
@property(nonatomic, strong) NSLayoutConstraint* primaryToolbarHeightConstraint;
// Y-dimension offset for placement of the header.
@property(nonatomic, readonly) CGFloat headerOffset;
// Height of the header view for the tab model's current tab.
@property(nonatomic, readonly) CGFloat headerHeight;
// The webState of the active tab.
@property(nonatomic, readonly) web::WebState* currentWebState;
// 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|, |_model|, 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 |_model|.
- (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. Calls
// -selectedTabChanged on the toolbar only if |newSelection| is YES.
- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection;
// 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 whether |tab| is scrolled to the top.
- (BOOL)isTabScrolledToTop:(Tab*)tab;
// Returns the footer view if one exists (e.g. the voice search bar).
- (UIView*)footerView;
// Returns the header height needed for |tab|.
- (CGFloat)headerHeightForTab:(Tab*)tab;
// Sets the frame for the headers.
- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
atOffset:(CGFloat)headerOffset;
// Adds a CardView on top of the contentArea either taking the size of the full
// screen or just the size of the space under the header.
// Returns the CardView that was added.
- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen;
// Showing and Dismissing child UI
// -------------------------------
// Dismisses the "rate this app" dialog.
- (void)dismissRateThisAppDialog;
// Bubble Views
// ------------
// Returns a bubble associated with an in-product help promotion if
// it is valid to show the promotion and |nil| otherwise. |feature| is the
// base::Feature object associated with the given promotion. |direction| is the
// direction the bubble's arrow is pointing. |alignment| is the alignment of the
// arrow on the button. |text| is the text displayed by the bubble. This method
// requires that |self.browserState| is not NULL.
- (BubbleViewControllerPresenter*)
bubblePresenterForFeature:(const base::Feature&)feature
direction:(BubbleArrowDirection)direction
alignment:(BubbleAlignment)alignment
text:(NSString*)text;
// Waits to present a bubble associated with the new tab tip in-product help
// promotion until the feature engagement tracker database is fully initialized.
// Does not present the bubble if |tabTipBubblePresenter.userEngaged| is |YES|
// to prevent resetting |tabTipBubblePresenter| and affecting the value of
// |userEngaged|. Does not present the bubble if the feature engagement tracker
// determines it is not valid to present it. This method requires that
// |self.browserState| is not NULL.
- (void)presentNewTabTipBubbleOnInitialized;
// Optionally presents a bubble associated with the new tab tip in-product help
// promotion. If the feature engagement tracker determines it is valid to show
// the new tab tip, then it initializes |tabTipBubblePresenter| and presents
// the bubble. If it is not valid to show the new tab tip,
// |tabTipBubblePresenter| is set to |nil| and no bubble is shown. This method
// requires that |self.browserState| is not NULL.
- (void)presentNewTabTipBubble;
// Waits to present a bubble associated with the new incognito tab tip
// in-product help promotion until the feature engagement tracker database is
// fully initialized. This method requires that |self.browserState| is
// not NULL.
- (void)presentNewIncognitoTabTipBubbleOnInitialized;
// Presents a bubble associated with the new incognito tab tip in-product help
// promotion. This method requires that |self.browserState| is not NULL.
- (void)presentNewIncognitoTabTipBubble;
// 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;
// Redisplays the find bar if necessary furing a view controller size change,
// using the transition coordinator |coordinator|.
- (void)reshowFindBarIfNeededWithCoordinator:
(id<UIViewControllerTransitionCoordinator>)coordinator;
// 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)setLastTapPoint:(OpenNewTabCommand*)command;
// 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
// --------------------------
// Called when either a tab finishes loading or when a tab with finished content
// is added directly to the model via pre-rendering. The tab must be non-nil and
// must be a member of the tab model controlled by this BrowserViewController.
- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success;
// Adds a new tab with |url| and |postData| at the end of the model, and make it
// the selected tab and return it.
- (Tab*)addSelectedTabWithURL:(const GURL&)url
postData:(TemplateURLRef::PostContent*)postData
transition:(ui::PageTransition)transition;
// Internal method that all of the similar public and private methods call.
// Adds a new tab with |url| and |postData| (if not null) at |position| in the
// tab model (or at the end if |position is NSNotFound|, with |transition| as
// the page transition type. If |tabAddedCompletion| is nonnull, it's called
// synchronously after the tab is added.
- (Tab*)addSelectedTabWithURL:(const GURL&)url
postData:(TemplateURLRef::PostContent*)postData
atIndex:(NSUInteger)position
transition:(ui::PageTransition)transition
tabAddedCompletion:(ProceduralBlock)tabAddedCompletion;
// Whether the given tab's URL is an application specific URL.
- (BOOL)isTabNativePage:(Tab*)tab;
// Returns the view to use when animating a page in or out, positioning it to
// fill the content area but not actually adding it to the view hierarchy.
- (UIImageView*)pageOpenCloseAnimationView;
// 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;
// Lazily instantiates |_voiceSearchBar| and adds it to the view.
- (void)ensureVoiceSearchBarCreated;
// Shows/hides the voice search bar.
- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated;
// Reading List
// ------------
// Adds the given url to the reading list.
- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title;
@end
@implementation BrowserViewController
// Public synthesized propeties.
@synthesize contentArea = _contentArea;
@synthesize typingShield = _typingShield;
@synthesize active = _active;
// Private synthesized properties
@synthesize visible = _visible;
@synthesize viewVisible = _viewVisible;
@synthesize broadcasting = _broadcasting;
@synthesize dismissingModal = _dismissingModal;
@synthesize hideStatusBar = _hideStatusBar;
@synthesize activityOverlayCoordinator = _activityOverlayCoordinator;
@synthesize foregroundTabWasAddedCompletionBlock =
_foregroundTabWasAddedCompletionBlock;
@synthesize recentTabsCoordinator = _recentTabsCoordinator;
@synthesize tabStripCoordinator = _tabStripCoordinator;
@synthesize tabStripView = _tabStripView;
@synthesize tabTipBubblePresenter = _tabTipBubblePresenter;
@synthesize incognitoTabTipBubblePresenter = _incognitoTabTipBubblePresenter;
@synthesize primaryToolbarCoordinator = _primaryToolbarCoordinator;
@synthesize secondaryToolbarCoordinator = _secondaryToolbarCoordinator;
@synthesize primaryToolbarOffsetConstraint = _primaryToolbarOffsetConstraint;
@synthesize primaryToolbarHeightConstraint = _primaryToolbarHeightConstraint;
@synthesize toolbarInterface = _toolbarInterface;
@synthesize imageSaver = _imageSaver;
// DialogPresenterDelegate property
@synthesize dialogPresenterDelegateIsPresenting =
_dialogPresenterDelegateIsPresenting;
#pragma mark - Object lifecycle
- (instancetype)
initWithTabModel:(TabModel*)model
browserState:(ios::ChromeBrowserState*)browserState
dependencyFactory:(BrowserViewControllerDependencyFactory*)factory
applicationCommandEndpoint:(id<ApplicationCommands>)applicationCommandEndpoint {
self = [super initWithNibName:nil bundle:base::mac::FrameworkBundle()];
if (self) {
DCHECK(factory);
_dependencyFactory = factory;
_dialogPresenter = [[DialogPresenter alloc] initWithDelegate:self
presentingViewController:self];
_dispatcher = [[CommandDispatcher alloc] init];
[_dispatcher startDispatchingToTarget:self
forProtocol:@protocol(UrlLoader)];
[_dispatcher startDispatchingToTarget:self
forProtocol:@protocol(WebToolbarDelegate)];
[_dispatcher startDispatchingToTarget:self
forProtocol:@protocol(BrowserCommands)];
[_dispatcher 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)]);
[_dispatcher
startDispatchingToTarget:applicationCommandEndpoint
forProtocol:@protocol(ApplicationSettingsCommands)];
_snackbarCoordinator = [[SnackbarCoordinator alloc] init];
_snackbarCoordinator.dispatcher = _dispatcher;
[_snackbarCoordinator start];
_languageSelectionCoordinator =
[[LanguageSelectionCoordinator alloc] initWithBaseViewController:self];
_languageSelectionCoordinator.presenter =
[[VerticalAnimationContainer alloc] init];
_passKitCoordinator =
[[PassKitCoordinator alloc] initWithBaseViewController:self];
_javaScriptDialogPresenter.reset(
new JavaScriptDialogPresenterImpl(_dialogPresenter));
_webStateDelegate.reset(new web::WebStateDelegateBridge(self));
// TODO(leng): Delay this.
[[UpgradeCenter sharedInstance] registerClient:self
withDispatcher:self.dispatcher];
_inNewTabAnimation = NO;
_footerFullscreenProgress = 1.0;
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,
FakeboxFocuser,
SnackbarCommands,
ToolbarCommands,
UrlLoader,
WebToolbarDelegate>)dispatcher {
return static_cast<
id<ApplicationCommands, BrowserCommands, OmniboxFocuser, FakeboxFocuser,
SnackbarCommands, ToolbarCommands, UrlLoader, WebToolbarDelegate>>(
_dispatcher);
}
- (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);
}
[_model setWebUsageEnabled:active];
[self updateDialogPresenterActiveState];
[self updateBroadcastState];
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.
Tab* currentTab = [_model currentTab];
// Force loading the view in case it was not loaded yet.
[self loadViewIfNeeded];
if (currentTab && _expectingForegroundTab) {
PagePlaceholderTabHelper::FromWebState(currentTab.webState)
->AddPlaceholderForNextNavigation();
}
if (currentTab)
[self displayTab:currentTab isNewSelection:YES];
} else {
[_dialogPresenter cancelAllDialogs];
}
[_paymentRequestManager enablePaymentRequest:active];
[self setNeedsStatusBarAppearanceUpdate];
}
- (BOOL)isPlayingTTS {
return _voiceSearchController && _voiceSearchController->IsPlayingAudio();
}
- (TabModel*)tabModel {
return _model;
}
- (ios::ChromeBrowserState*)browserState {
return _browserState;
}
#pragma mark - Private Properties
- (SideSwipeController*)sideSwipeController {
if (!_sideSwipeController) {
_sideSwipeController =
[[SideSwipeController alloc] initWithTabModel:_model
browserState:_browserState];
[_sideSwipeController setSnapshotDelegate:self];
_sideSwipeController.toolbarInteractionHandler =
self.primaryToolbarCoordinator;
[_sideSwipeController setSwipeDelegate:self];
[_sideSwipeController setTabStripDelegate:self.tabStripCoordinator];
}
return _sideSwipeController;
}
- (DialogPresenter*)dialogPresenter {
return _dialogPresenter;
}
- (KeyCommandsProvider*)keyCommandsProvider {
if (!_keyCommandsProvider) {
_keyCommandsProvider = [_dependencyFactory newKeyCommandsProvider];
}
return _keyCommandsProvider;
}
- (BOOL)canUseDesktopUserAgent {
Tab* tab = [_model currentTab];
if ([self isTabNativePage:tab])
return NO;
// If |useDesktopUserAgent| is |NO|, allow useDesktopUserAgent.
return !tab.usesDesktopUserAgent;
}
// Whether the sharing menu should be shown.
- (BOOL)canShowShareMenu {
const GURL& URL = [_model currentTab].webState->GetLastCommittedURL();
return URL.is_valid() && !web::GetWebClient()->IsAppSpecificURL(URL);
}
- (BOOL)canShowFindBar {
// Make sure web controller can handle find in page.
Tab* tab = [_model currentTab];
if (!tab) {
return NO;
}
auto* helper = FindTabHelper::FromWebState(tab.webState);
return (helper && helper->CurrentPageSupportsFindInPage() &&
!helper->IsFindUIActive());
}
- (web::UserAgentType)userAgentType {
web::WebState* webState = [_model 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;
if (base::FeatureList::IsEnabled(fullscreen::features::kNewFullscreen)) {
// 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) {
_toolbarUIUpdater = [[LegacyToolbarUIUpdater alloc]
initWithToolbarUI:[[ToolbarUIState alloc] init]
toolbarOwner:self
webStateList:[_model webStateList]];
[_toolbarUIUpdater startUpdating];
StartBroadcastingToolbarUI(_toolbarUIUpdater.toolbarUI, broadcaster);
_mainContentUIUpdater = [[MainContentUIStateUpdater alloc]
initWithState:[[MainContentUIState alloc] init]];
_webMainContentUIForwarder = [[WebScrollViewMainContentUIForwarder alloc]
initWithUpdater:_mainContentUIUpdater
webStateList:[_model webStateList]];
StartBroadcastingMainContentUI(self, broadcaster);
fullscreenController->AddObserver(_fullscreenUIUpdater.get());
} else {
StopBroadcastingToolbarUI(broadcaster);
StopBroadcastingMainContentUI(broadcaster);
[_toolbarUIUpdater stopUpdating];
_toolbarUIUpdater = nil;
_mainContentUIUpdater = nil;
[_webMainContentUIForwarder disconnect];
_webMainContentUIForwarder = nil;
fullscreenController->RemoveObserver(_fullscreenUIUpdater.get());
}
}
}
- (BOOL)isToolbarOnScreen {
return self.headerHeight - [self currentHeaderOffset] > 0;
}
- (void)setInNewTabAnimation:(BOOL)inNewTabAnimation {
if (_inNewTabAnimation == inNewTabAnimation)
return;
_inNewTabAnimation = inNewTabAnimation;
[self updateDialogPresenterActiveState];
[self updateBroadcastState];
}
- (BOOL)isInNewTabAnimation {
return _inNewTabAnimation;
}
- (BOOL)shouldShowVoiceSearchBar {
// On iPads, the voice search bar should only be shown for regular horizontal
// size class configurations. It should always be shown for voice search
// results Tabs on iPhones, including configurations with regular horizontal
// size classes (i.e. landscape iPhone 6 Plus).
BOOL compactWidth = self.traitCollection.horizontalSizeClass ==
UIUserInterfaceSizeClassCompact;
return self.tabModel.currentTab.isVoiceSearchResultsTab &&
(!IsIPadIdiom() || compactWidth);
}
- (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 (!IsIPadIdiom()) {
if (self.primaryToolbarCoordinator.viewController.view) {
[results addObject:[HeaderDefinition
definitionWithView:self.primaryToolbarCoordinator
.viewController.view
headerBehaviour:Hideable
heightAdjustment:0.0
inset:0.0]];
}
} else {
if (self.tabStripView) {
[results addObject:[HeaderDefinition definitionWithView:self.tabStripView
headerBehaviour:Hideable
heightAdjustment:0.0
inset:0.0]];
}
if (self.primaryToolbarCoordinator.viewController.view) {
[results addObject:[HeaderDefinition
definitionWithView:self.primaryToolbarCoordinator
.viewController.view
headerBehaviour:Hideable
heightAdjustment:0.0
inset:0.0]];
}
if ([_findBarController view]) {
[results addObject:[HeaderDefinition
definitionWithView:[_findBarController view]
headerBehaviour:Overlap
heightAdjustment:0.0
inset:kIPadFindBarOverlap]];
}
}
return [results copy];
}
- (CGFloat)headerOffset {
if (IsIPadIdiom())
return StatusBarHeight();
return 0.0;
}
- (CGFloat)headerHeight {
return [self headerHeightForTab:[_model currentTab]];
}
- (LegacyToolbarCoordinator*)legacyToolbarCoordinator {
return _toolbarCoordinator;
}
- (web::WebState*)currentWebState {
return [[_model currentTab] webState];
}
#pragma mark - Public methods
- (void)setPrimary:(BOOL)primary {
[_model setPrimary:primary];
if (primary) {
[self updateDialogPresenterActiveState];
[self updateBroadcastState];
} else {
self.dialogPresenter.active = false;
}
}
- (void)shieldWasTapped:(id)sender {
[self.primaryToolbarCoordinator cancelOmniboxEdit];
}
- (void)userEnteredTabSwitcher {
if ([self.tabTipBubblePresenter isUserEngaged]) {
base::RecordAction(UserMetricsAction("NewTabTipTargetSelected"));
}
}
- (void)presentBubblesIfEligible {
[self presentNewTabTipBubbleOnInitialized];
[self presentNewIncognitoTabTipBubbleOnInitialized];
}
- (void)browserStateDestroyed {
[self setActive:NO];
[_paymentRequestManager close];
_paymentRequestManager = nil;
[self.legacyToolbarCoordinator browserStateDestroyed];
[_model browserStateDestroyed];
if (base::FeatureList::IsEnabled(fullscreen::features::kNewFullscreen)) {
FullscreenController* fullscreenController =
FullscreenControllerFactory::GetInstance()->GetForBrowserState(
_browserState);
_foregroundTabAnimationFullscreenDisabler->Disconnect();
_foregroundTabAnimationFullscreenDisabler = nullptr;
fullscreenController->RemoveObserver(_fullscreenUIUpdater.get());
_fullscreenUIUpdater = nullptr;
fullscreenController->SetWebStateList(nullptr);
}
// Disconnect child coordinators.
[_activityServiceCoordinator disconnect];
[_qrScannerCoordinator disconnect];
[_tabHistoryCoordinator disconnect];
[_pageInfoCoordinator disconnect];
[_externalSearchCoordinator disconnect];
[self.tabStripCoordinator stop];
self.tabStripCoordinator = nil;
self.tabStripView = nil;
_browserState = nullptr;
[_dispatcher stopDispatchingToTarget:self];
_dispatcher = nil;
}
- (Tab*)addSelectedTabWithURL:(const GURL&)url
transition:(ui::PageTransition)transition {
return [self addSelectedTabWithURL:url
atIndex:[_model count]
transition:transition];
}
- (Tab*)addSelectedTabWithURL:(const GURL&)url
atIndex:(NSUInteger)position
transition:(ui::PageTransition)transition {
return [self addSelectedTabWithURL:url
atIndex:position
transition:transition
tabAddedCompletion:nil];
}
- (Tab*)addSelectedTabWithURL:(const GURL&)url
atIndex:(NSUInteger)position
transition:(ui::PageTransition)transition
tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
return [self addSelectedTabWithURL:url
postData:NULL
atIndex:position
transition:transition
tabAddedCompletion:tabAddedCompletion];
}
- (void)expectNewForegroundTab {
_expectingForegroundTab = YES;
}
- (void)startVoiceSearchWithOriginView:(UIView*)originView {
_voiceSearchButton = originView;
// 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];
[[_model currentTab].webController dismissKeyboard];
// Ensure that voice search objects are created.
[self ensureVoiceSearchControllerCreated];
[self ensureVoiceSearchBarCreated];
// Present voice search.
[_voiceSearchBar prepareToPresentVoiceSearch];
_voiceSearchController->StartRecognition(self, [_model currentTab]);
[self.primaryToolbarCoordinator cancelOmniboxEdit];
}
- (void)clearPresentedStateWithCompletion:(ProceduralBlock)completion
dismissOmnibox:(BOOL)dismissOmnibox {
[_activityServiceCoordinator cancelShare];
[_bookmarkInteractionController dismissBookmarkModalControllerAnimated:NO];
[_bookmarkInteractionController dismissSnackbar];
if (dismissOmnibox) {
[self.primaryToolbarCoordinator cancelOmniboxEdit];
}
[_dialogPresenter cancelAllDialogs];
[self.dispatcher hidePageInfo];
[self.tabTipBubblePresenter dismissAnimated:NO];
[self.incognitoTabTipBubblePresenter dismissAnimated:NO];
if (_voiceSearchController)
_voiceSearchController->DismissMicPermissionsHelp();
Tab* currentTab = [_model currentTab];
[currentTab dismissModals];
if (currentTab) {
auto* findHelper = FindTabHelper::FromWebState(currentTab.webState);
if (findHelper) {
findHelper->StopFinding(^{
[self updateFindBar:NO shouldFocus:NO];
});
}
}
[_paymentRequestManager cancelRequest];
[_printController dismissAnimated:YES];
_printController = nil;
[self.dispatcher dismissToolsMenu];
[_contextMenuCoordinator stop];
[self dismissRateThisAppDialog];
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 (!TabSwitcherPresentsBVCEnabled() || !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);
}
}
- (UIView<TabStripFoldAnimation>*)tabStripPlaceholderView {
return [self.tabStripCoordinator placeholderView];
}
- (void)shutdown {
DCHECK(!_isShutdown);
_isShutdown = YES;
[self.tabStripCoordinator stop];
self.tabStripCoordinator = nil;
[_toolbarCoordinator stop];
_toolbarCoordinator = nil;
[self.primaryToolbarCoordinator stop];
self.primaryToolbarCoordinator = nil;
[self.secondaryToolbarCoordinator stop];
self.secondaryToolbarCoordinator = nil;
self.toolbarInterface = nil;
self.tabStripView = nil;
_infoBarContainer = nil;
_readingListMenuNotifier = nil;
_bookmarkModelBridge.reset();
[_model removeObserver:self];
[[UpgradeCenter sharedInstance] unregisterClient:self];
if (_voiceSearchController)
_voiceSearchController->SetDelegate(nil);
[_rateThisAppDialog setDelegate:nil];
[_model closeAllTabs];
[_paymentRequestManager setActiveWebState:nullptr];
}
#pragma mark - NSObject
- (BOOL)accessibilityPerformEscape {
[self dismissPopups];
return YES;
}
#pragma mark - UIResponder
- (NSArray*)keyCommands {
if (![self shouldRegisterKeyboardCommands]) {
return nil;
}
return [self.keyCommandsProvider
keyCommandsForConsumer:self
baseViewController:self
dispatcher:self.dispatcher
editingText:![self isFirstResponder]];
}
#pragma mark - UIResponder helpers
// Whether the BVC should declare keyboard commands.
- (BOOL)shouldRegisterKeyboardCommands {
if ([self presentedViewController])
return NO;
if (_voiceSearchController && _voiceSearchController->IsVisible())
return NO;
// If there is no first responder, try to make the webview the first
// responder.
if (!GetFirstResponder()) {
web::WebState* webState = _model.currentTab.webState;
if (webState)
[webState->GetWebViewProxy() becomeFirstResponder];
}
return YES;
}
#pragma mark - UIViewController
// Perform additional set up after loading the view, typically from a nib.
- (void)viewDidLoad {
CGRect initialViewsRect = self.view.bounds;
initialViewsRect.origin.y += StatusBarHeight();
initialViewsRect.size.height -= StatusBarHeight();
UIViewAutoresizing initialViewAutoresizing =
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.contentArea =
[[BrowserContainerView alloc] initWithFrame:initialViewsRect];
self.contentArea.autoresizingMask = initialViewAutoresizing;
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.view addSubview:self.contentArea];
[self.view addSubview:self.typingShield];
[super viewDidLoad];
// Install fake status bar for iPad iOS7
[self installFakeStatusBar];
[self buildToolbarAndTabStrip];
[self setUpViewLayout:YES];
if (IsSafeAreaCompatibleToolbarEnabled()) {
[self addConstraintsToToolbar];
}
// If the tab model and browser state are valid, finish initialization.
if (_model && _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];
[_contentArea addGestureRecognizer:tapRecognizer];
}
- (void)viewSafeAreaInsetsDidChange {
[super viewSafeAreaInsetsDidChange];
// Gate this behind iPhone X, since it's currently the only device that
// needs layout updates here after startup.
if (IsIPhoneX()) {
[self setUpViewLayout:NO];
}
// Update the toolbar height to account for the new top inset.
self.primaryToolbarHeightConstraint.constant =
[self primaryToolbarHeightWithInset];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
// Update the toolbar height to account for |topLayoutGuide| changes.
self.primaryToolbarHeightConstraint.constant =
[self primaryToolbarHeightWithInset];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.viewVisible = YES;
[self updateDialogPresenterActiveState];
[self updateBroadcastState];
// |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;
// Restore hidden infobars.
if (IsIPadIdiom() && _infoBarContainer) {
_infoBarContainer->RestoreInfobars();
}
// 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 (![_model 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 = [_model currentTab];
if (currentTab)
[self displayTab:currentTab isNewSelection:YES];
}
- (void)viewWillDisappear:(BOOL)animated {
self.viewVisible = NO;
[self updateDialogPresenterActiveState];
[self updateBroadcastState];
[[_model currentTab] wasHidden];
[_bookmarkInteractionController dismissSnackbar];
if (IsIPadIdiom() && _infoBarContainer) {
_infoBarContainer->SuspendInfobars();
}
[super viewWillDisappear:animated];
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orient
duration:(NSTimeInterval)duration {
[super willRotateToInterfaceOrientation:orient duration:duration];
[self dismissPopups];
[self reshowFindBarIfNeededWithCoordinator:nil];
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orient {
[super didRotateFromInterfaceOrientation:orient];
// This reinitializes the toolbar, including updating the Overlay View,
// if there is one.
[self updateToolbar];
[self infoBarContainerStateDidChangeAnimated:NO];
}
- (BOOL)prefersStatusBarHidden {
BOOL defaultValue = NO;
if (IsAdaptiveToolbarEnabled()) {
defaultValue = [super prefersStatusBarHidden];
}
return self.hideStatusBar || defaultValue;
}
// 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];
// Release any cached data, images, etc that aren't in use.
// TODO(pinkerton): This feels like it should go in the MemoryPurger class,
// but since the FaviconCache uses obj-c in the header, it can't be included
// there.
if (_browserState) {
FaviconLoader* loader =
IOSChromeFaviconLoaderFactory::GetForBrowserStateIfExists(
_browserState);
if (loader)
loader->PurgeCache();
}
if (![self isViewLoaded]) {
// Do not release |_infoBarContainer|, as this must have the same lifecycle
// as the BrowserViewController.
self.contentArea = nil;
self.typingShield = nil;
if (_voiceSearchController)
_voiceSearchController->SetDelegate(nil);
_readingListCoordinator = nil;
self.recentTabsCoordinator = nil;
_toolbarCoordinator = nil;
self.primaryToolbarCoordinator = nil;
self.secondaryToolbarCoordinator = nil;
self.toolbarInterface = nil;
[_toolbarUIUpdater stopUpdating];
_toolbarUIUpdater = nil;
_toolbarModelDelegate = nil;
_toolbarModelIOS = nil;
[self.tabStripCoordinator stop];
self.tabStripCoordinator = nil;
self.tabStripView = nil;
_sideSwipeController = nil;
}
}
- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
// 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];
// Update voice search bar visibility.
[self updateVoiceSearchBarVisibilityAnimated:NO];
}
- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:
(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[self dismissPopups];
[self reshowFindBarIfNeededWithCoordinator:coordinator];
}
- (void)dismissViewControllerAnimated:(BOOL)flag
completion:(void (^)())completion {
if (TabSwitcherPresentsBVCEnabled() && !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. When the BVC is displayed using VC containment, multiple
// calls are effectively idempotent because only the first call has any effect
// and subsequent calls do nothing. However, when the BVC is presented,
// subsequent calls end up dismissing the BVC itself. This is never what we
// want, 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 (TabSwitcherPresentsBVCEnabled() &&
(self.dismissingModal || self.presentedViewController.isBeingDismissed)) {
return;
}
self.dismissingModal = YES;
__weak BrowserViewController* weakSelf = self;
[super dismissViewControllerAnimated:flag
completion:^{
BrowserViewController* strongSelf = weakSelf;
strongSelf.dismissingModal = NO;
strongSelf.dialogPresenterDelegateIsPresenting =
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();
};
}
}
self.dialogPresenterDelegateIsPresenting = YES;
if ([self.sideSwipeController inSwipe]) {
[self.sideSwipeController resetContentView];
}
[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 {
return (IsIPadIdiom() || _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(!_model);
DCHECK(!_browserState);
_browserState = browserState;
_isOffTheRecord = browserState->IsOffTheRecord() ? YES : NO;
_model = model;
[_model addObserver:self];
if (!_isOffTheRecord) {
[DefaultIOSWebViewFactory
registerWebViewFactory:[ChromeWebViewFactory class]];
}
NSUInteger count = [_model count];
for (NSUInteger index = 0; index < count; ++index)
[self installDelegatesForTab:[_model tabAtIndex:index]];
_imageFetcher = std::make_unique<image_fetcher::IOSImageDataFetcherWrapper>(
_browserState->GetRequestContext());
self.imageSaver = [[ImageSaver alloc] initWithBaseViewController:self];
// Register for bookmark changed notification (BookmarkModel may be null
// during testing, so explicitly support this).
_bookmarkModel = ios::BookmarkModelFactory::GetForBrowserState(_browserState);
if (_bookmarkModel) {
_bookmarkModelBridge.reset(
new bookmarks::BookmarkModelBridge(self, _bookmarkModel));
}
if (base::FeatureList::IsEnabled(fullscreen::features::kNewFullscreen)) {
// Add a FullscreenUIUpdater for self.
FullscreenController* controller =
FullscreenControllerFactory::GetInstance()->GetForBrowserState(
_browserState);
_fullscreenUIUpdater = std::make_unique<FullscreenUIUpdater>(self);
// Crate the disabler for any new foreground tab animations in the tab model.
_foregroundTabAnimationFullscreenDisabler =
std::make_unique<NewForegroundTabFullscreenDisabler>(_model.webStateList,
controller);
// Set the FullscreenController's WebStateList.
controller->SetWebStateList(_model.webStateList);
}
}
- (void)installFakeStatusBar {
CGFloat statusBarHeight = StatusBarHeight();
CGRect statusBarFrame =
CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
_fakeStatusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
[_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
if (IsIPadIdiom()) {
[_fakeStatusBarView setBackgroundColor:StatusBarBackgroundColor()];
[_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
[_fakeStatusBarView layer].zPosition = 99;
[[self view] addSubview:_fakeStatusBarView];
} else {
// Add a white bar on phone so that the status bar on the NTP is white.
[_fakeStatusBarView setBackgroundColor:[UIColor whiteColor]];
[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(!_toolbarModelDelegate);
// Initialize the prerender service before creating the toolbar controller.
PrerenderService* prerenderService =
PrerenderServiceFactory::GetForBrowserState(self.browserState);
if (prerenderService) {
prerenderService->SetDelegate(self);
}
// Create the toolbar model and controller.
_toolbarModelDelegate.reset(
new ToolbarModelDelegateIOS([_model webStateList]));
_toolbarModelIOS.reset([_dependencyFactory
newToolbarModelIOSWithDelegate:_toolbarModelDelegate.get()]);
if (IsAdaptiveToolbarEnabled()) {
PrimaryToolbarCoordinator* topToolbarCoordinator =
[[PrimaryToolbarCoordinator alloc] initWithBrowserState:_browserState];
self.primaryToolbarCoordinator = topToolbarCoordinator;
topToolbarCoordinator.delegate = self;
topToolbarCoordinator.URLLoader = self;
topToolbarCoordinator.webStateList = [_model webStateList];
topToolbarCoordinator.dispatcher = self.dispatcher;
[topToolbarCoordinator start];
SecondaryToolbarCoordinator* bottomToolbarCoordinator = [
[SecondaryToolbarCoordinator alloc] initWithBrowserState:_browserState];
self.secondaryToolbarCoordinator = bottomToolbarCoordinator;
bottomToolbarCoordinator.webStateList = [_model webStateList];
bottomToolbarCoordinator.dispatcher = self.dispatcher;
[bottomToolbarCoordinator start];
ToolbarCoordinatorAdaptor* adaptor = [[ToolbarCoordinatorAdaptor alloc]
initWithToolsMenuConfigurationProvider:self
dispatcher:self.dispatcher];
self.toolbarInterface = adaptor;
[adaptor addToolbarCoordinator:topToolbarCoordinator];
// TODO(crbug.com/800330): Add secondary toolbar.
} else {
_toolbarCoordinator = [[LegacyToolbarCoordinator alloc]
initWithBaseViewController:self
toolsMenuConfigurationProvider:self
dispatcher:self.dispatcher
browserState:_browserState];
self.primaryToolbarCoordinator = _toolbarCoordinator;
self.toolbarInterface = _toolbarCoordinator;
[_toolbarCoordinator
setToolbarController:
[_dependencyFactory
newToolbarControllerWithDelegate:self
urlLoader:self
dispatcher:self.dispatcher]];
[_toolbarCoordinator start];
}
self.sideSwipeController.toolbarInteractionHandler =
self.primaryToolbarCoordinator;
[_dispatcher startDispatchingToTarget:self.primaryToolbarCoordinator
forProtocol:@protocol(OmniboxFocuser)];
[_dispatcher startDispatchingToTarget:self.primaryToolbarCoordinator
forProtocol:@protocol(FakeboxFocuser)];
[self.legacyToolbarCoordinator setTabCount:[_model count]];
[self updateBroadcastState];
if (_voiceSearchController)
_voiceSearchController->SetDelegate(
[self.primaryToolbarCoordinator voiceSearchDelegate]);
if (IsIPadIdiom()) {
self.tabStripCoordinator =
[[TabStripLegacyCoordinator alloc] initWithBaseViewController:self];
self.tabStripCoordinator.browserState = _browserState;
self.tabStripCoordinator.dispatcher = _dispatcher;
self.tabStripCoordinator.tabModel = _model;
self.tabStripCoordinator.presentationProvider = self;
self.tabStripCoordinator.animationWaitDuration =
kLegacyFullscreenControllerToolbarAnimationDuration;
[self.tabStripCoordinator start];
}
// Create infobar container.
if (!_infoBarContainerDelegate) {
_infoBarContainerDelegate.reset(new InfoBarContainerDelegateIOS(self));
_infoBarContainer.reset(
new InfoBarContainerIOS(_infoBarContainerDelegate.get()));
}
}
// 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 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 = 0.0;
if (@available(iOS 11, *)) {
unsafeHeight = self.view.safeAreaInsets.top;
} else {
unsafeHeight = self.topLayoutGuide.length;
}
// The topmost header is laid out |headerOffset| from the top of |view|, so
// subtract that from the unsafe height.
unsafeHeight -= self.headerOffset;
return primaryToolbar.intrinsicContentSize.height + unsafeHeight;
}
- (void)addConstraintsToToolbar {
NSLayoutYAxisAnchor* topAnchor;
// On iPad, the toolbar is underneath the tab strip.
// On iPhone, it is underneath the top of the screen.
if (IsIPadIdiom()) {
topAnchor = self.tabStripView.bottomAnchor;
} else {
topAnchor = [self view].topAnchor;
}
[self.legacyToolbarCoordinator adjustToolbarHeight];
// 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]];
[NSLayoutConstraint activateConstraints:@[
self.primaryToolbarOffsetConstraint,
self.primaryToolbarHeightConstraint,
[self.primaryToolbarCoordinator.viewController.view.leadingAnchor
constraintEqualToAnchor:[self view].leadingAnchor],
[self.primaryToolbarCoordinator.viewController.view.trailingAnchor
constraintEqualToAnchor:[self view].trailingAnchor],
]];
if (self.secondaryToolbarCoordinator) {
[NSLayoutConstraint activateConstraints:@[
[self.secondaryToolbarCoordinator.viewController.view.leadingAnchor
constraintEqualToAnchor:[self view].leadingAnchor],
[self.secondaryToolbarCoordinator.viewController.view.trailingAnchor
constraintEqualToAnchor:[self view].trailingAnchor],
[self.secondaryToolbarCoordinator.viewController.view.bottomAnchor
constraintEqualToAnchor:[self view].bottomAnchor],
]];
}
[[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(_toolbarModelIOS);
DCHECK(_model);
DCHECK([self isViewLoaded]);
[self.sideSwipeController addHorizontalGesturesToView:self.view];
infobars::InfoBarManager* infoBarManager = nullptr;
if (_model.currentTab) {
DCHECK(_model.currentTab.webState);
infoBarManager =
InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
}
_infoBarContainer->ChangeInfoBarManager(infoBarManager);
// Create child coordinators.
_activityServiceCoordinator = [[ActivityServiceLegacyCoordinator alloc]
initWithBaseViewController:self];
_activityServiceCoordinator.dispatcher = _dispatcher;
_activityServiceCoordinator.tabModel = _model;
_activityServiceCoordinator.browserState = _browserState;
_activityServiceCoordinator.positionProvider =
[self.primaryToolbarCoordinator activityServicePositioner];
_activityServiceCoordinator.presentationProvider = self;
_qrScannerCoordinator =
[[QRScannerLegacyCoordinator alloc] initWithBaseViewController:self];
_qrScannerCoordinator.dispatcher = _dispatcher;
_qrScannerCoordinator.loadProvider =
[self.primaryToolbarCoordinator QRScannerResultLoader];
_qrScannerCoordinator.presentationProvider = self;
_tabHistoryCoordinator = [[LegacyTabHistoryCoordinator alloc]
initWithBaseViewController:self
browserState:_browserState];
_tabHistoryCoordinator.dispatcher = _dispatcher;
_tabHistoryCoordinator.positionProvider =
[self.legacyToolbarCoordinator tabHistoryPositioner];
_tabHistoryCoordinator.tabModel = _model;
_tabHistoryCoordinator.presentationProvider = self;
_tabHistoryCoordinator.tabHistoryUIUpdater =
[self.primaryToolbarCoordinator tabHistoryUIUpdater];
_sadTabCoordinator = [[SadTabLegacyCoordinator alloc] init];
_sadTabCoordinator.baseViewController = self;
_sadTabCoordinator.dispatcher = self.dispatcher;
// If there are any existing SadTabHelpers in |_model|, update the helpers
// delegate with the new |_sadTabCoordinator|.
for (NSUInteger i = 0; i < _model.count; i++) {
SadTabTabHelper* sadTabHelper =
SadTabTabHelper::FromWebState([_model tabAtIndex:i].webState);
DCHECK(sadTabHelper);
if (sadTabHelper) {
sadTabHelper->SetDelegate(_sadTabCoordinator);
}
}
_pageInfoCoordinator = [[PageInfoLegacyCoordinator alloc]
initWithBaseViewController:self
browserState:_browserState];
_pageInfoCoordinator.dispatcher = _dispatcher;
_pageInfoCoordinator.loader = self;
_pageInfoCoordinator.presentationProvider = self;
_pageInfoCoordinator.tabModel = _model;
_externalSearchCoordinator = [[ExternalSearchCoordinator alloc] init];
_externalSearchCoordinator.dispatcher = _dispatcher;
if (base::FeatureList::IsEnabled(payments::features::kWebPayments)) {
_paymentRequestManager = [[PaymentRequestManager alloc]
initWithBaseViewController:self
browserState:_browserState
dispatcher:self.dispatcher];
[_paymentRequestManager setToolbarModel:_toolbarModelIOS.get()];
[_paymentRequestManager setActiveWebState:[_model currentTab].webState];
}
}
// Set the frame for the various views. View must be loaded.
- (void)setUpViewLayout:(BOOL)initialLayout {
DCHECK([self isViewLoaded]);
CGFloat widthOfView = CGRectGetWidth([self view].bounds);
// Update the fake toolbar background height.
CGRect fakeStatusBarFrame = _fakeStatusBarView.frame;
fakeStatusBarFrame.size.height = StatusBarHeight();
_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)
[self addChildViewController:self.secondaryToolbarCoordinator
.viewController];
}
if (!IsSafeAreaCompatibleToolbarEnabled()) {
CGFloat minY = self.headerOffset;
if (self.tabStripView) {
minY += CGRectGetHeight([self.tabStripView frame]);
}
CGRect toolbarFrame = _toolbarCoordinator.viewController.view.frame;
toolbarFrame.origin = CGPointMake(0, minY);
toolbarFrame.size.width = widthOfView;
[_toolbarCoordinator.viewController.view setFrame:toolbarFrame];
}
// Place the infobar container above the content area.
InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
if (initialLayout)
[self.view insertSubview:infoBarContainerView aboveSubview:_contentArea];
// Place the toolbar controller above the infobar container and adds the
// layout guides.
if (initialLayout) {
[[self view]
insertSubview:self.primaryToolbarCoordinator.viewController.view
aboveSubview:infoBarContainerView];
if (self.secondaryToolbarCoordinator) {
[[self view]
insertSubview:self.secondaryToolbarCoordinator.viewController.view
aboveSubview:infoBarContainerView];
}
AddNamedGuide(kOmniboxGuide, self.view);
AddNamedGuide(kBackButtonGuide, self.view);
AddNamedGuide(kForwardButtonGuide, self.view);
AddNamedGuide(kToolsMenuGuide, self.view);
AddNamedGuide(kTabSwitcherGuide, self.view);
}
if (initialLayout) {
[self.primaryToolbarCoordinator.viewController
didMoveToParentViewController:self];
if (self.secondaryToolbarCoordinator) {
[self.secondaryToolbarCoordinator.viewController
didMoveToParentViewController:self];
}
}
// Adjust the content area to be under the toolbar, for fullscreen or below
// the toolbar is not fullscreen.
CGRect contentFrame = [_contentArea frame];
CGFloat marginWithHeader = StatusBarHeight();
contentFrame.size.height = CGRectGetMaxY(contentFrame) - marginWithHeader;
contentFrame.origin.y = marginWithHeader;
[_contentArea setFrame:contentFrame];
// Adjust the infobar container to be either at the bottom of the screen
// (iPhone) or on the lower toolbar edge (iPad).
CGRect infoBarFrame = contentFrame;
infoBarFrame.origin.y = CGRectGetMaxY(contentFrame);
infoBarFrame.size.height = 0;
[infoBarContainerView setFrame:infoBarFrame];
// Attach the typing shield to the content area but have it hidden.
[self.typingShield setFrame:[_contentArea frame]];
if (initialLayout) {
[[self view] insertSubview:self.typingShield aboveSubview:_contentArea];
[self.typingShield setHidden:YES];
}
}
- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection {
DCHECK(tab);
[self loadViewIfNeeded];
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.
[[tab view] setFrame:_contentArea.bounds];
[_contentArea displayContentView:[tab view]];
}
[self updateToolbar];
if (newSelection)
[self.legacyToolbarCoordinator selectedTabChanged];
// Notify the Tab that it was displayed.
[tab wasShown];
}
- (void)initializeBookmarkInteractionController {
if (_bookmarkInteractionController)
return;
_bookmarkInteractionController = [[BookmarkInteractionController alloc]
initWithBrowserState:_browserState
loader:self
parentController:self
dispatcher:self.dispatcher];
}
- (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 (!(_toolbarModelIOS && _browserState))
return;
Tab* tab = [_model currentTab];
if (![tab navigationManager])
return;
[self.toolbarInterface updateToolbarState];
[self.legacyToolbarCoordinator setShareButtonEnabled:self.canShowShareMenu];
if (_insertedTabWasPrerenderedTab && !_toolbarModelIOS->IsLoading())
[self.primaryToolbarCoordinator showPrerenderingAnimation];
auto* findHelper = FindTabHelper::FromWebState(tab.webState);
if (findHelper && findHelper->IsFindUIActive()) {
[self showFindBarWithAnimation:NO
selectText:YES
shouldFocus:[_findBarController isFocused]];
}
// Hide the toolbar if displaying phone NTP.
if (!IsIPadIdiom()) {
web::NavigationItem* item = [tab navigationManager]->GetVisibleItem();
BOOL hideToolbar = NO;
if (item) {
GURL url = item->GetURL();
BOOL isNTP = url.GetOrigin() == GURL(kChromeUINewTabURL);
hideToolbar = isNTP && !_isOffTheRecord &&
![self.primaryToolbarCoordinator isOmniboxFirstResponder] &&
![self.primaryToolbarCoordinator showingOmniboxPopup];
}
[self.primaryToolbarCoordinator.viewController.view setHidden:hideToolbar];
}
}
- (void)updateBroadcastState {
self.broadcasting =
self.active && self.viewVisible && !self.inNewTabAnimation;
}
- (void)updateDialogPresenterActiveState {
self.dialogPresenter.active =
self.active && self.viewVisible && !self.inNewTabAnimation;
}
- (void)dismissPopups {
[self.dispatcher dismissToolsMenu];
[self.dispatcher hidePageInfo];
[_tabHistoryCoordinator dismissHistoryPopup];
[self.tabTipBubblePresenter dismissAnimated:NO];
[self.incognitoTabTipBubblePresenter dismissAnimated:NO];
}
- (BOOL)isTabScrolledToTop:(Tab*)tab {
CGPoint scrollOffset =
tab.webState->GetWebViewProxy().scrollViewProxy.contentOffset;
// If there is a native controller, use the native controller's scroll offset.
id nativeController = [self nativeControllerForTab:tab];
if ([nativeController conformsToProtocol:@protocol(CRWNativeContent)] &&
[nativeController respondsToSelector:@selector(scrollOffset)]) {
scrollOffset = [nativeController scrollOffset];
}
return CGPointEqualToPoint(scrollOffset, CGPointZero);
}
- (UIView*)footerView {
return _voiceSearchBar;
}
- (CGFloat)headerHeightForTab:(Tab*)tab {
id nativeController = [self nativeControllerForTab:tab];
if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)] &&
[nativeController respondsToSelector:@selector(toolbarHeight)] &&
[nativeController toolbarHeight] > 0.0 && !IsIPadIdiom()) {
// On iPhone, don't add any header height for ToolbarOwner native
// controllers when they're displaying their own toolbar.
return 0;
}
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]) -
header.heightAdjustement - header.inset;
}
}
return height - StatusBarHeight();
}
- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
atOffset:(CGFloat)headerOffset {
CGFloat height = self.headerOffset;
for (HeaderDefinition* header in headers) {
CGFloat yOrigin = height - headerOffset - header.inset;
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 (IsSafeAreaCompatibleToolbarEnabled() && isPrimaryToolbar &&
!IsIPadIdiom()) {
self.primaryToolbarOffsetConstraint.constant = yOrigin;
}
CGRect frame = [header.view frame];
frame.origin.y = yOrigin;
[header.view setFrame:frame];
if (header.behaviour != Overlap)
height += CGRectGetHeight(frame);
}
}
- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen {
CGRect frame = [_contentArea frame];
if (!fullScreen) {
// Changing the origin here is unnecessary, it's set in page_animation_util.
frame.size.height -= self.headerHeight;
}
CGFloat shortAxis = frame.size.width;
CGFloat shortInset = kCardImageInsets.left + kCardImageInsets.right;
shortAxis -= shortInset + 2 * page_animation_util::kCardMargin;
CGFloat aspectRatio = frame.size.height / frame.size.width;
CGFloat longAxis = std::floor(aspectRatio * shortAxis);
CGFloat longInset = kCardImageInsets.top + kCardImageInsets.bottom;
CGSize cardSize = CGSizeMake(shortAxis + shortInset, longAxis + longInset);
CGRect cardFrame = {frame.origin, cardSize};
CardView* card =
[[CardView alloc] initWithFrame:cardFrame isIncognito:_isOffTheRecord];
card.closeButtonSide = IsPortrait() ? CardCloseButtonSide::TRAILING
: CardCloseButtonSide::LEADING;
[_contentArea addSubview:card];
return card;
}
#pragma mark - Private Methods: Showing and Dismissing Child UI
- (void)dismissRateThisAppDialog {
if (_rateThisAppDialog) {
base::RecordAction(base::UserMetricsAction(
"IOSRateThisAppDialogDismissedProgramatically"));
[_rateThisAppDialog dismiss];
_rateThisAppDialog = nil;
}
}
#pragma mark - Private Methods: Bubble views
- (BubbleViewControllerPresenter*)
bubblePresenterForFeature:(const base::Feature&)feature
direction:(BubbleArrowDirection)direction
alignment:(BubbleAlignment)alignment
text:(NSString*)text {
DCHECK(self.browserState);
if (!feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
->ShouldTriggerHelpUI(feature)) {
return nil;
}
// Capture |weakSelf| instead of the feature engagement tracker object
// because |weakSelf| will safely become |nil| if it is deallocated, whereas
// the feature engagement tracker will remain pointing to invalid memory if
// its owner (the ChromeBrowserState) is deallocated.
__weak BrowserViewController* weakSelf = self;
void (^dismissalCallback)(void) = ^() {
BrowserViewController* strongSelf = weakSelf;
if (strongSelf) {
feature_engagement::TrackerFactory::GetForBrowserState(
strongSelf.browserState)
->Dismissed(feature);
}
};
BubbleViewControllerPresenter* bubbleViewControllerPresenter =
[[BubbleViewControllerPresenter alloc] initWithText:text
arrowDirection:direction
alignment:alignment
dismissalCallback:dismissalCallback];
return bubbleViewControllerPresenter;
}
- (void)presentNewTabTipBubbleOnInitialized {
DCHECK(self.browserState);
// If the tab tip bubble has already been presented and the user is still
// considered engaged, it can't be overwritten or set to |nil| or else it will
// reset the |userEngaged| property. Once the user is not engaged, the bubble
// can be safely overwritten or set to |nil|.
if (!self.tabTipBubblePresenter.isUserEngaged) {
__weak BrowserViewController* weakSelf = self;
void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
[weakSelf presentNewTabTipBubble];
};
// Because the new tab tip occurs on startup, the feature engagement
// tracker's database is not guaranteed to be loaded by this time. For the
// bubble to appear properly, a callback is used to guarantee the event data
// is loaded before the check to see if the promotion should be displayed.
feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
}
}
- (void)presentNewTabTipBubble {
DCHECK(self.browserState);
// If the BVC is not visible, do not present the bubble.
if (!self.viewVisible)
return;
// Do not present the bubble if there is no current tab or if the current tab
// is the NTP.
Tab* currentTab = [self.tabModel currentTab];
if (!currentTab)
return;
if (currentTab.webState->GetVisibleURL() == kChromeUINewTabURL)
return;
// Do not present the bubble if the tab is not scrolled to the top.
if (![self isTabScrolledToTop:currentTab])
return;
NSString* text =
l10n_util::GetNSStringWithFixup(IDS_IOS_NEW_TAB_IPH_PROMOTION_TEXT);
CGPoint tabSwitcherAnchor;
if (IsIPadIdiom()) {
DCHECK([self.tabStripCoordinator
respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
tabSwitcherAnchor = [self.tabStripCoordinator
anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
} else {
if (base::FeatureList::IsEnabled(kCleanToolbar)) {
UILayoutGuide* guide = FindNamedGuide(kTabSwitcherGuide, self.view);
CGPoint anchorPoint =
bubble_util::AnchorPoint(guide.layoutFrame, BubbleArrowDirectionUp);
tabSwitcherAnchor =
[guide.owningView convertPoint:anchorPoint
toView:guide.owningView.window];
} else {
DCHECK([self.legacyToolbarCoordinator
respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
tabSwitcherAnchor = [self.legacyToolbarCoordinator
anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
}
}
// If the feature engagement tracker does not consider it valid to display
// the new tab tip, then end early to prevent the potential reassignment
// of the existing |tabTipBubblePresenter| to nil.
BubbleViewControllerPresenter* presenter =
[self bubblePresenterForFeature:feature_engagement::kIPHNewTabTipFeature
direction:BubbleArrowDirectionUp
alignment:BubbleAlignmentTrailing
text:text];
if (!presenter)
return;
self.tabTipBubblePresenter = presenter;
[self.tabTipBubblePresenter presentInViewController:self
view:self.view
anchorPoint:tabSwitcherAnchor];
}
- (void)presentNewIncognitoTabTipBubbleOnInitialized {
DCHECK(self.browserState);
// Do not override |incognitoTabtipBubblePresenter| or set it to nil if the
// user is still considered engaged.
if (!self.incognitoTabTipBubblePresenter.isUserEngaged) {
__weak BrowserViewController* weakSelf = self;
void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
[weakSelf presentNewIncognitoTabTipBubble];
};
// Use a callback in case the new incognito tab tip should be shown on
// startup. This ensures that the tracker's database will be fully loaded
// before checking if the promotion should be displayed.
feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
}
}
- (void)presentNewIncognitoTabTipBubble {
DCHECK(self.browserState);
// If the BVC is not visible, do not present the bubble.
if (!self.viewVisible)
return;
// Do not present the bubble if there is no current tab.
Tab* currentTab = [self.tabModel currentTab];
if (!currentTab)
return;
// Do not present the bubble if the tab is not scrolled to the top.
if (![self isTabScrolledToTop:currentTab])
return;
NSString* text = l10n_util::GetNSStringWithFixup(
IDS_IOS_NEW_INCOGNITO_TAB_IPH_PROMOTION_TEXT);
CGPoint toolsButtonAnchor;
if (base::FeatureList::IsEnabled(kCleanToolbar)) {
UILayoutGuide* guide = FindNamedGuide(kToolsMenuGuide, self.view);
CGPoint anchorPoint =
bubble_util::AnchorPoint(guide.layoutFrame, BubbleArrowDirectionUp);
toolsButtonAnchor = [guide.owningView convertPoint:anchorPoint
toView:guide.owningView.window];
} else {
DCHECK([self.legacyToolbarCoordinator
respondsToSelector:@selector(anchorPointForToolsMenuButton:)]);
toolsButtonAnchor = [self.legacyToolbarCoordinator
anchorPointForToolsMenuButton:BubbleArrowDirectionUp];
}
// If the feature engagement tracker does not consider it valid to display
// the incognito tab tip, then end early to prevent the potential reassignment
// of the existing |incognitoTabTipBubblePresenter| to nil.
BubbleViewControllerPresenter* presenter =
[self bubblePresenterForFeature:feature_engagement::
kIPHNewIncognitoTabTipFeature
direction:BubbleArrowDirectionUp
alignment:BubbleAlignmentTrailing
text:text];
if (!presenter)
return;
self.incognitoTabTipBubblePresenter = presenter;
[self.incognitoTabTipBubblePresenter
presentInViewController:self
view:self.view
anchorPoint:toolsButtonAnchor];
[self.dispatcher triggerToolsMenuButtonAnimation];
}
#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);
Tab* tab = [_model currentTab];
DCHECK(tab);
CRWWebController* webController = tab.webController;
CGRect referenceFrame = CGRectZero;
if (IsIPadIdiom()) {
referenceFrame = webController.visibleFrame;
referenceFrame.origin.y -= kIPadFindBarOverlap;
} else {
referenceFrame = _contentArea.frame;
}
CGRect omniboxFrame;
if (base::FeatureList::IsEnabled(kCleanToolbar)) {
omniboxFrame = FindNamedGuide(kOmniboxGuide, self.view).layoutFrame;
} else {
omniboxFrame = [self.legacyToolbarCoordinator visibleOmniboxFrame];
}
[_findBarController addFindBarView:animate
intoView:self.view
withFrame:referenceFrame
alignWithFrame:omniboxFrame
selectText:selectText];
[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 (![_model currentTab]) {
return;
}
auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
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];
}
}
- (void)reshowFindBarIfNeededWithCoordinator:
(id<UIViewControllerTransitionCoordinator>)coordinator {
if (![_findBarController isFindInPageShown])
return;
// Record focused state.
BOOL isFocusedBeforeReshow = [_findBarController isFocused];
[self hideFindBarWithAnimation:NO];
__weak BrowserViewController* weakSelf = self;
void (^completion)(id<UIViewControllerTransitionCoordinatorContext>) =
^(id<UIViewControllerTransitionCoordinatorContext> context) {
BrowserViewController* strongSelf = weakSelf;
if (strongSelf)
[strongSelf showFindBarWithAnimation:NO
selectText:NO
shouldFocus:isFocusedBeforeReshow];
};
BOOL enqueued =
[coordinator animateAlongsideTransition:nil completion:completion];
if (!enqueued) {
completion(nil);
}
}
#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];
}