Index: ios/chrome/app/main_controller.mm |
diff --git a/ios/chrome/app/main_controller.mm b/ios/chrome/app/main_controller.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..fa6b767e738bfb702c5823a6613f363274a00263 |
--- /dev/null |
+++ b/ios/chrome/app/main_controller.mm |
@@ -0,0 +1,2757 @@ |
+// 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/app/main_controller.h" |
+ |
+#include <memory> |
+#include <string> |
+ |
+#import <CoreSpotlight/CoreSpotlight.h> |
+#import <objc/objc.h> |
+#import <objc/runtime.h> |
+ |
+#include "base/bind.h" |
+#include "base/callback_helpers.h" |
+#include "base/files/file_path.h" |
+#include "base/ios/block_types.h" |
+#import "base/mac/bind_objc_block.h" |
+#include "base/mac/bundle_locations.h" |
+#include "base/mac/foundation_util.h" |
+#include "base/mac/objc_property_releaser.h" |
+#import "base/mac/scoped_nsobject.h" |
+#include "base/macros.h" |
+#include "base/path_service.h" |
+#include "base/strings/sys_string_conversions.h" |
+#include "base/time/time.h" |
+#include "components/component_updater/component_updater_service.h" |
+#include "components/content_settings/core/browser/host_content_settings_map.h" |
+#include "components/metrics/metrics_pref_names.h" |
+#include "components/metrics/metrics_service.h" |
+#include "components/prefs/pref_change_registrar.h" |
+#include "components/reading_list/core/reading_list_switches.h" |
+#include "components/signin/core/browser/signin_manager.h" |
+#include "components/url_formatter/url_formatter.h" |
+#include "components/web_resource/web_resource_pref_names.h" |
+#import "ios/chrome/app/application_delegate/app_state.h" |
+#import "ios/chrome/app/application_delegate/background_activity.h" |
+#import "ios/chrome/app/application_delegate/metrics_mediator.h" |
+#import "ios/chrome/app/application_delegate/url_opener.h" |
+#include "ios/chrome/app/application_mode.h" |
+#include "ios/chrome/app/chrome_app_startup_parameters.h" |
+#import "ios/chrome/app/deferred_initialization_runner.h" |
+#import "ios/chrome/app/main_controller_private.h" |
+#import "ios/chrome/app/memory_monitor.h" |
+#import "ios/chrome/app/safe_mode_crashing_modules_config.h" |
+#import "ios/chrome/app/spotlight/spotlight_manager.h" |
+#import "ios/chrome/app/spotlight/spotlight_util.h" |
+#include "ios/chrome/app/startup/chrome_main_starter.h" |
+#include "ios/chrome/app/startup/client_registration.h" |
+#include "ios/chrome/app/startup/ios_chrome_main.h" |
+#include "ios/chrome/app/startup/network_stack_setup.h" |
+#include "ios/chrome/app/startup/provider_registration.h" |
+#include "ios/chrome/app/startup/register_experimental_settings.h" |
+#include "ios/chrome/app/startup/setup_debugging.h" |
+#import "ios/chrome/app/startup_tasks.h" |
+#include "ios/chrome/app/tests_hook.h" |
+#import "ios/chrome/browser/app_startup_parameters.h" |
+#include "ios/chrome/browser/application_context.h" |
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
+#include "ios/chrome/browser/browser_state/chrome_browser_state_manager.h" |
+#include "ios/chrome/browser/browser_state/chrome_browser_state_removal_controller.h" |
+#import "ios/chrome/browser/browsing_data/browsing_data_removal_controller.h" |
+#include "ios/chrome/browser/browsing_data/ios_chrome_browsing_data_remover.h" |
+#include "ios/chrome/browser/callback_counter.h" |
+#include "ios/chrome/browser/chrome_paths.h" |
+#include "ios/chrome/browser/chrome_url_constants.h" |
+#import "ios/chrome/browser/chrome_url_util.h" |
+#include "ios/chrome/browser/content_settings/host_content_settings_map_factory.h" |
+#include "ios/chrome/browser/crash_loop_detection_util.h" |
+#include "ios/chrome/browser/crash_report/breakpad_helper.h" |
+#import "ios/chrome/browser/crash_report/crash_report_background_uploader.h" |
+#import "ios/chrome/browser/crash_report/crash_restore_helper.h" |
+#include "ios/chrome/browser/experimental_flags.h" |
+#include "ios/chrome/browser/file_metadata_util.h" |
+#import "ios/chrome/browser/first_run/first_run.h" |
+#include "ios/chrome/browser/geolocation/omnibox_geolocation_controller.h" |
+#include "ios/chrome/browser/ios_chrome_io_thread.h" |
+#import "ios/chrome/browser/memory/memory_debugger_manager.h" |
+#include "ios/chrome/browser/metrics/first_user_action_recorder.h" |
+#import "ios/chrome/browser/metrics/previous_session_info.h" |
+#import "ios/chrome/browser/net/cookie_util.h" |
+#include "ios/chrome/browser/net/crl_set_fetcher.h" |
+#include "ios/chrome/browser/pref_names.h" |
+#include "ios/chrome/browser/prefs/pref_observer_bridge.h" |
+#import "ios/chrome/browser/reading_list/reading_list_download_service.h" |
+#import "ios/chrome/browser/reading_list/reading_list_download_service_factory.h" |
+#include "ios/chrome/browser/search_engines/search_engines_util.h" |
+#include "ios/chrome/browser/search_engines/template_url_service_factory.h" |
+#import "ios/chrome/browser/share_extension/share_extension_service.h" |
+#import "ios/chrome/browser/share_extension/share_extension_service_factory.h" |
+#include "ios/chrome/browser/signin/authentication_service.h" |
+#include "ios/chrome/browser/signin/authentication_service_factory.h" |
+#include "ios/chrome/browser/signin/signin_manager_factory.h" |
+#import "ios/chrome/browser/snapshots/snapshot_cache.h" |
+#import "ios/chrome/browser/tabs/tab.h" |
+#import "ios/chrome/browser/tabs/tab_model.h" |
+#import "ios/chrome/browser/tabs/tab_model_observer.h" |
+#import "ios/chrome/browser/ui/authentication/signed_in_accounts_view_controller.h" |
+#import "ios/chrome/browser/ui/authentication/signin_interaction_controller.h" |
+#import "ios/chrome/browser/ui/browser_view_controller.h" |
+#import "ios/chrome/browser/ui/chrome_web_view_factory.h" |
+#import "ios/chrome/browser/ui/commands/clear_browsing_data_command.h" |
+#include "ios/chrome/browser/ui/commands/ios_command_ids.h" |
+#import "ios/chrome/browser/ui/commands/open_url_command.h" |
+#import "ios/chrome/browser/ui/commands/show_signin_command.h" |
+#import "ios/chrome/browser/ui/contextual_search/contextual_search_metrics.h" |
+#import "ios/chrome/browser/ui/contextual_search/touch_to_search_permissions_mediator.h" |
+#import "ios/chrome/browser/ui/downloads/download_manager_controller.h" |
+#import "ios/chrome/browser/ui/first_run/first_run_util.h" |
+#import "ios/chrome/browser/ui/first_run/welcome_to_chrome_view_controller.h" |
+#import "ios/chrome/browser/ui/fullscreen_controller.h" |
+#import "ios/chrome/browser/ui/history/history_panel_view_controller.h" |
+#import "ios/chrome/browser/ui/main/browser_view_wrangler.h" |
+#import "ios/chrome/browser/ui/main/main_coordinator.h" |
+#import "ios/chrome/browser/ui/main/main_view_controller.h" |
+#import "ios/chrome/browser/ui/orientation_limiting_navigation_controller.h" |
+#import "ios/chrome/browser/ui/promos/signin_promo_view_controller.h" |
+#import "ios/chrome/browser/ui/settings/settings_navigation_controller.h" |
+#import "ios/chrome/browser/ui/stack_view/stack_view_controller.h" |
+#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_controller.h" |
+#import "ios/chrome/browser/ui/tabs/tab_strip_controller+tab_switcher_animation.h" |
+#include "ios/chrome/browser/ui/ui_util.h" |
+#import "ios/chrome/browser/ui/uikit_ui_util.h" |
+#import "ios/chrome/browser/ui/util/top_view_controller.h" |
+#import "ios/chrome/browser/ui/webui/chrome_web_ui_ios_controller_factory.h" |
+#include "ios/chrome/browser/xcallback_parameters.h" |
+#include "ios/chrome/grit/ios_strings.h" |
+#include "ios/net/cookies/cookie_store_ios.h" |
+#import "ios/net/crn_http_protocol_handler.h" |
+#include "ios/public/provider/chrome/browser/chrome_browser_provider.h" |
+#include "ios/public/provider/chrome/browser/distribution/app_distribution_provider.h" |
+#import "ios/public/provider/chrome/browser/native_app_launcher/native_app_whitelist_manager.h" |
+#include "ios/public/provider/chrome/browser/signin/chrome_identity_service.h" |
+#import "ios/public/provider/chrome/browser/user_feedback/user_feedback_provider.h" |
+#import "ios/third_party/material_components_ios/src/components/Typography/src/MaterialTypography.h" |
+#import "ios/third_party/material_roboto_font_loader_ios/src/src/MaterialRobotoFontLoader.h" |
+#include "ios/web/net/request_tracker_factory_impl.h" |
+#include "ios/web/net/request_tracker_impl.h" |
+#include "ios/web/net/web_http_protocol_handler_delegate.h" |
+#include "ios/web/public/web_capabilities.h" |
+#include "ios/web/public/web_state/web_state.h" |
+#import "ios/web/public/web_view_creation_util.h" |
+#include "ios/web/public/webui/web_ui_ios_controller_factory.h" |
+#import "ios/web/web_state/ui/crw_web_controller.h" |
+#include "mojo/edk/embedder/embedder.h" |
+#import "net/base/mac/url_conversions.h" |
+#include "net/url_request/url_request_context.h" |
+#include "ui/base/l10n/l10n_util.h" |
+ |
+namespace { |
+ |
+// Preference key used to store which profile is current. |
+NSString* kIncognitoCurrentKey = @"IncognitoActive"; |
+ |
+// Constants for deferred initialization of preferences observer. |
+NSString* const kPrefObserverInit = @"PrefObserverInit"; |
+ |
+// Constants for deferring notifying the AuthenticationService of a new cold |
+// start. |
+NSString* const kAuthenticationServiceNotification = |
+ @"AuthenticationServiceNotification"; |
+ |
+// Constants for deferring reseting the startup attempt count (to give the app |
+// a little while to make sure it says alive). |
+NSString* const kStartupAttemptReset = @"StartupAttempReset"; |
+ |
+// Constants for deferring memory debugging tools startup. |
+NSString* const kMemoryDebuggingToolsStartup = @"MemoryDebuggingToolsStartup"; |
+ |
+// Constants for deferring memory monitoring startup. |
+NSString* const kMemoryMonitoring = @"MemoryMonitoring"; |
+ |
+// Constants for deferred check if it is necessary to send pings to |
+// Chrome distribution related services. |
+NSString* const kSendInstallPingIfNecessary = @"SendInstallPingIfNecessary"; |
+ |
+// Constants for deferring check of native iOS apps installed. |
+NSString* const kCheckNativeApps = @"CheckNativeApps"; |
+ |
+// Constants for deferred deletion of leftover user downloaded files. |
+NSString* const kDeleteDownloads = @"DeleteDownloads"; |
+ |
+// Constants for deferred sending of queued feedback. |
+NSString* const kSendQueuedFeedback = @"SendQueuedFeedback"; |
+ |
+// Constants for deferring the deletion of pre-upgrade crash reports. |
+NSString* const kCleanupCrashReports = @"CleanupCrashReports"; |
+ |
+// Constants for deferring the deletion of old snapshots. |
+NSString* const kPurgeSnapshots = @"PurgeSnapshots"; |
+ |
+// Constants for deferring startup Spotlight bookmark indexing. |
+NSString* const kStartSpotlightBookmarksIndexing = |
+ @"StartSpotlightBookmarksIndexing"; |
+ |
+// Constants for deferred initialization of dynamic application shortcut items. |
+NSString* const kAddApplicationShortcutItems = @"AddApplicationShortcutItems"; |
+ |
+// Constants for deferred promo display. |
+const NSTimeInterval kDisplayPromoDelay = 0.1; |
+ |
+// A rough estimate of the expected duration of a view controller transition |
+// animation. It's used to temporarily disable mutally exclusive chrome |
+// commands that trigger a view controller presentation. |
+const int64_t kExpectedTransitionDurationInNanoSeconds = 0.2 * NSEC_PER_SEC; |
+ |
+// Adapted from chrome/browser/ui/browser_init.cc. |
+void RegisterComponentsForUpdate() { |
+ component_updater::ComponentUpdateService* cus = |
+ GetApplicationContext()->GetComponentUpdateService(); |
+ DCHECK(cus); |
+ base::FilePath path; |
+ const bool success = PathService::Get(ios::DIR_USER_DATA, &path); |
+ DCHECK(success); |
+ // CRLSetFetcher attempts to load a CRL set from either the local disk or |
+ // network. |
+ GetApplicationContext()->GetCRLSetFetcher()->StartInitialLoad(cus, path); |
+} |
+ |
+// Unsynchronizes the cookie store associated to |browserState| on the IO |
+// thread. |
+void UnSynchronizeCookieStore(ios::ChromeBrowserState* browserState) { |
+ DCHECK(browserState); |
+ scoped_refptr<net::URLRequestContextGetter> getter = |
+ browserState->GetRequestContext(); |
+ web::WebThread::PostTask( |
+ web::WebThread::IO, FROM_HERE, base::BindBlock(^{ |
+ net::CookieStoreIOS* store = static_cast<net::CookieStoreIOS*>( |
+ getter->GetURLRequestContext()->cookie_store()); |
+ store->UnSynchronize(); |
+ })); |
+} |
+ |
+// Returns YES if |url| matches chrome://newtab. |
+BOOL IsURLNtp(const GURL& url) { |
+ return UrlHasChromeScheme(url) && url.host() == kChromeUINewTabHost; |
+} |
+ |
+// Used to update the current BVC mode if a new tab is added while the stack |
+// view is being dimissed. This is different than ApplicationMode in that it |
+// can be set to |NONE| when not in use. |
+enum class StackViewDismissalMode { NONE, NORMAL, INCOGNITO }; |
+ |
+} // namespace |
+ |
+// TODO(crbug.com/673904): Remove once MDFRobotoFontLoader declares it directly. |
+// MDFRobotoFontLoader implicitly implements MDCTypographyFontLoading but can't |
+// declare it until MDC is public. |
+@interface MDFRobotoFontLoader (MDCTypography)<MDCTypographyFontLoading> |
+@end |
+ |
+@interface MainController ()<BrowserStateStorageSwitching, |
+ BrowsingDataRemovalControllerDelegate, |
+ PrefObserverDelegate, |
+ SettingsNavigationControllerDelegate, |
+ TabModelObserver, |
+ TabSwitcherDelegate, |
+ UserFeedbackDataSource> { |
+ IBOutlet UIWindow* _window; |
+ |
+ // Weak; owned by the ChromeBrowserProvider. |
+ ios::ChromeBrowserStateManager* _browserStateManager; |
+ |
+ // The object that drives the Chrome startup/shutdown logic. |
+ std::unique_ptr<IOSChromeMain> _chromeMain; |
+ |
+ // The ChromeBrowserState associated with the main (non-OTR) browsing mode. |
+ ios::ChromeBrowserState* _mainBrowserState; // Weak. |
+ |
+ // Coordinators used to run the Chrome UI; there will be one of these active |
+ // at any given time, usually |_mainCoordinator|. |
+ // Main coordinator, backing object for the property of the same name, which |
+ // lazily initializes on access. |
+ base::scoped_nsobject<MainCoordinator> _mainCoordinator; |
+ |
+ // Wrangler to handle BVC and tab model creation, access, and related logic. |
+ // Implements faetures exposed from this object through the |
+ // BrowserViewInformation protocol. |
+ base::scoped_nsobject<BrowserViewWrangler> _browserViewWrangler; |
+ |
+ // Parameters received at startup time when the app is launched from another |
+ // app. |
+ base::scoped_nsobject<AppStartupParameters> _startupParameters; |
+ |
+ // Whether Voice Search should be started upon tab switcher dismissal. |
+ BOOL _startVoiceSearchAfterTabSwitcherDismissal; |
+ |
+ // Whether the QR Scanner should be started upon tab switcher dismissal. |
+ BOOL _startQRScannerAfterTabSwitcherDismissal; |
+ |
+ // Navigation View controller for the settings. |
+ base::scoped_nsobject<SettingsNavigationController> |
+ _settingsNavigationController; |
+ |
+ // View controller for switching tabs. |
+ base::scoped_nsobject<UIViewController<TabSwitcher>> _tabSwitcherController; |
+ |
+ // Controller to display the re-authentication flow. |
+ base::scoped_nsobject<SigninInteractionController> |
+ _signinInteractionController; |
+ |
+ // The number of memory warnings that have been received in this |
+ // foreground session. |
+ int _foregroundMemoryWarningCount; |
+ |
+ // The time at which to reset the OOM crash flag in the user defaults. This |
+ // is used to handle receiving multiple memory warnings in short succession. |
+ CFAbsoluteTime _outOfMemoryResetTime; |
+ |
+ // YES while animating the dismissal of stack view. |
+ BOOL _dismissingStackView; |
+ |
+ // If not NONE, the current BVC should be switched to this BVC on completion |
+ // of stack view dismissal. |
+ StackViewDismissalMode _modeToDisplayOnStackViewDismissal; |
+ |
+ // If YES, the tab switcher is currently active. |
+ BOOL _tabSwitcherIsActive; |
+ |
+ // True if the current session began from a cold start. False if the app has |
+ // entered the background at least once since start up. |
+ BOOL _isColdStart; |
+ |
+ // Keeps track of the restore state during startup. |
+ base::scoped_nsobject<CrashRestoreHelper> _restoreHelper; |
+ |
+ // An object to record metrics related to the user's first action. |
+ std::unique_ptr<FirstUserActionRecorder> _firstUserActionRecorder; |
+ |
+ // RequestTrackerFactory to customize the behavior of the network stack. |
+ std::unique_ptr<web::RequestTrackerFactoryImpl> _requestTrackerFactory; |
+ |
+ // Configuration for the HTTP protocol handler. |
+ std::unique_ptr<web::WebHTTPProtocolHandlerDelegate> |
+ _httpProtocolHandlerDelegate; |
+ |
+ // True if First Run UI (terms of service & sync sign-in) is being presented |
+ // in a modal dialog. |
+ BOOL _isPresentingFirstRunUI; |
+ |
+ // The tab switcher command and the voice search commands can be sent by views |
+ // that reside in a different UIWindow leading to the fact that the exclusive |
+ // touch property will be ineffective and a command for processing both |
+ // commands may be sent in the same run of the runloop leading to |
+ // inconsistencies. Those two boolean indicate if one of those commands have |
+ // been processed in the last 200ms in order to only allow processing one at |
+ // a time. |
+ // TODO(crbug.com/560296): Provide a general solution for handling mutually |
+ // exclusive chrome commands sent at nearly the same time. |
+ BOOL _isProcessingTabSwitcherCommand; |
+ BOOL _isProcessingVoiceSearchCommand; |
+ |
+ // Bridge to listen to pref changes. |
+ std::unique_ptr<PrefObserverBridge> _localStatePrefObserverBridge; |
+ |
+ // Registrar for pref changes notifications to the local state. |
+ PrefChangeRegistrar _localStatePrefChangeRegistrar; |
+ |
+ // Clears browsing data from ChromeBrowserStates. |
+ base::scoped_nsobject<BrowsingDataRemovalController> |
+ _browsingDataRemovalController; |
+ |
+ // The class in charge of showing/hiding the memory debugger when the |
+ // appropriate pref changes. |
+ base::scoped_nsobject<MemoryDebuggerManager> _memoryDebuggerManager; |
+ |
+ base::mac::ObjCPropertyReleaser _propertyReleaser_MainController; |
+ |
+ // Responsible for indexing chrome links (such as bookmarks, most likely...) |
+ // in system Spotlight index. |
+ base::scoped_nsobject<SpotlightManager> _spotlightManager; |
+ |
+ // Cached launchOptions from -didFinishLaunchingWithOptions. |
+ base::scoped_nsobject<NSDictionary> _launchOptions; |
+ |
+ // View controller for displaying the history panel. |
+ base::scoped_nsobject<UIViewController> _historyPanelViewController; |
+ |
+ // Variable backing metricsMediator property. |
+ base::WeakNSObject<MetricsMediator> _metricsMediator; |
+ |
+ // Hander for the startup tasks, deferred or not. |
+ base::scoped_nsobject<StartupTasks> _startupTasks; |
+} |
+ |
+// Pointer to the main view controller, always owned by the main window. |
+@property(nonatomic, readonly) MainViewController* mainViewController; |
+ |
+// The main coordinator, lazily created the first time it is accessed. Manages |
+// the MainViewController. This property should not be accessed before the |
+// browser has started up to the FOREGROUND stage. |
+@property(nonatomic, readonly) MainCoordinator* mainCoordinator; |
+ |
+// A property to track whether the QR Scanner should be started upon tab |
+// switcher dismissal. It can only be YES if the QR Scanner experiment is |
+// enabled. |
+@property(nonatomic, readwrite) BOOL startQRScannerAfterTabSwitcherDismissal; |
+ |
+// Activates browsing and enables web views if |enabled| is YES. |
+// Disables browsing and purges web views if |enabled| is NO. |
+// Must be called only on the main thread. |
+- (void)setWebUsageEnabled:(BOOL)enabled; |
+// Activates self.currentBVC iff the self.currentBVC can be made active. |
+- (void)activateCurrentBVC; |
+// Sets |currentBVC| as the root view controller for the window. |
+- (void)displayCurrentBVC; |
+// Shows the settings UI. |
+- (void)showSettings; |
+// Shows the accounts settings UI. |
+- (void)showAccountsSettings; |
+// Shows the Sync settings UI. |
+- (void)showSyncSettings; |
+// Shows the Save Passwords settings. |
+- (void)showSavePasswordsSettings; |
+// Invokes the sign in flow with the specified authentication operation and |
+// invokes |callback| when finished. |
+- (void)showSignInWithOperation:(AuthenticationOperation)operation |
+ signInAccessPoint:(signin_metrics::AccessPoint)signInAccessPoint |
+ callback:(ShowSigninCommandCompletionCallback)callback; |
+// Wraps a callback with one that first checks if sign-in was completed |
+// successfully and the profile wasn't swapped before invoking. |
+- (ShowSigninCommandCompletionCallback)successfulSigninCompletion: |
+ (ProceduralBlock)callback; |
+// Shows the Sync encryption passphrase (part of Settings). |
+- (void)showSyncEncryptionPassphrase; |
+// Shows the Native Apps Settings UI (part of Settings). |
+- (void)showNativeAppsSettings; |
+// Shows the Clear Browsing Data Settings UI (part of Settings). |
+- (void)showClearBrowsingDataSettingsController; |
+// Shows the Contextual search UI (part of Settings). |
+- (void)showContextualSearchSettingsController; |
+// Shows the tab switcher UI. |
+- (void)showTabSwitcher; |
+// Starts a voice search on the current BVC. |
+- (void)startVoiceSearch; |
+// Dismisses the tab switcher UI without animation into the given model. |
+- (void)dismissTabSwitcherWithoutAnimationInModel:(TabModel*)tabModel; |
+// Dismisses and clears |signinInteractionController|. |
+- (void)dismissSigninInteractionController; |
+// Called when the last incognito tab was closed. |
+- (void)lastIncognitoTabClosed; |
+// Called when the last regular tab was closed. |
+- (void)lastRegularTabClosed; |
+// Post a notification with name |notificationName| on the first available |
+// run loop cycle. |
+- (void)postNotificationOnNextRunLoopCycle:(NSString*)notificationName; |
+// Opens a tab in the target BVC, and switches to it in a way that's appropriate |
+// to the current UI, based on the |dismissModals| flag: |
+// - If a modal dialog is showing and |dismissModals| is NO, the selected tab of |
+// the main tab model will change in the background, but the view won't change. |
+// - Otherwise, any modal view will be dismissed, the stack view will animate |
+// out if it is showing, the target BVC will become active, and the new tab will |
+// be shown. |
+// If the current tab in |targetMode| is a NTP, it can be reused to open URL. |
+- (Tab*)openSelectedTabInMode:(ApplicationMode)targetMode |
+ withURL:(const GURL&)url |
+ transition:(ui::PageTransition)transition; |
+// Checks the target BVC's current tab's URL. If this URL is chrome://newtab, |
+// loads |url| in this tab. Otherwise, open |url| in a new tab in the target |
+// BVC. |
+- (Tab*)openOrReuseTabInMode:(ApplicationMode)targetMode |
+ withURL:(const GURL&)url |
+ transition:(ui::PageTransition)transition; |
+// Returns whether the restore infobar should be displayed. |
+- (bool)mustShowRestoreInfobar; |
+// Begins the process of dismissing the stack view with the given current model, |
+// switching which BVC is suspended if necessary, but not updating the UI. |
+- (void)beginDismissingStackViewWithCurrentModel:(TabModel*)tabModel; |
+// Completes the process of dismissing the stack view, removing it from the |
+// screen and showing the appropriate BVC. |
+- (void)finishDismissingStackView; |
+// Sets up self.currentBVC for testing by closing existing tabs. |
+- (void)setUpCurrentBVCForTesting; |
+// Opens an url. |
+- (void)openUrl:(OpenUrlCommand*)command; |
+// Opens an url from a link in the settings UI. |
+- (void)openUrlFromSettings:(OpenUrlCommand*)command; |
+// Switch all global states for the given mode (normal or incognito). |
+- (void)switchGlobalStateToMode:(ApplicationMode)mode; |
+// Updates the local storage, cookie store, and sets the global state. |
+- (void)changeStorageFromBrowserState:(ios::ChromeBrowserState*)oldState |
+ toBrowserState:(ios::ChromeBrowserState*)newState; |
+// Returns the set of the sessions ids of the tabs in the given |tabModel|. |
+- (NSMutableSet*)liveSessionsForTabModel:(TabModel*)tabModel; |
+// Purge the unused snapshots. |
+- (void)purgeSnapshots; |
+// Sets a LocalState pref marking the TOS EULA as accepted. |
+- (void)markEulaAsAccepted; |
+// Sends any feedback that happens to still be on local storage. |
+- (void)sendQueuedFeedback; |
+// Sets the iOS cookie policy to match that of the given browser state. |
+- (void)setInitialCookiesPolicy:(ios::ChromeBrowserState*)browserState; |
+// Called whenever an orientation change is received. |
+- (void)orientationDidChange:(NSNotification*)notification; |
+// Register to receive orientation change notification to update breakpad |
+// report. |
+- (void)registerForOrientationChangeNotifications; |
+// Asynchronously creates the pref observers. |
+- (void)schedulePrefObserverInitialization; |
+// Asynchronously schedules a check for what other native iOS apps are currently |
+// installed. |
+- (void)scheduleCheckNativeApps; |
+// Asynchronously schedules pings to distribution services. |
+- (void)scheduleAppDistributionPings; |
+// Asynchronously schedule the init of the memoryDebuggerManager. |
+- (void)scheduleMemoryDebuggingTools; |
+// Asynchronously kick off regular free memory checks. |
+- (void)scheduleFreeMemoryMonitoring; |
+// Asynchronously schedules the notification of the AuthenticationService. |
+- (void)scheduleAuthenticationServiceNotification; |
+// Asynchronously schedules the reset of the failed startup attempt counter. |
+- (void)scheduleStartupAttemptReset; |
+// Asynchronously schedules the cleanup of crash reports. |
+- (void)scheduleCrashReportCleanup; |
+// Asynchronously schedules the deletion of old snapshots. |
+- (void)scheduleSnapshotPurge; |
+// Schedules various cleanup tasks that are performed after launch. |
+- (void)scheduleStartupCleanupTasks; |
+// Schedules various tasks to be performed after the application becomes active. |
+- (void)scheduleLowPriorityStartupTasks; |
+// Schedules tasks that require a fully-functional BVC to be performed. |
+- (void)scheduleTasksRequiringBVCWithBrowserState; |
+// Schedules the deletion of user downloaded files that might be leftover |
+// from the last time Chrome was run. |
+- (void)scheduleDeleteDownloadsDirectory; |
+// Returns whether or not the app can launch in incognito mode. |
+- (BOOL)canLaunchInIncognito; |
+// Determines which UI should be shown on startup, and shows it. |
+- (void)createInitialUI:(ApplicationMode)launchMode; |
+// Initializes the first run UI and presents it to the user. |
+- (void)showFirstRunUI; |
+// Schedules presentation of the first eligible promo found, if any. |
+- (void)scheduleShowPromo; |
+// Crashes the application if requested. |
+- (void)crashIfRequested; |
+// Returns the BrowsingDataRemovalController. Lazily creates one if necessary. |
+- (BrowsingDataRemovalController*)browsingDataRemovalController; |
+// Clears incognito data that is specific to iOS and won't be cleared by |
+// deleting the browser state. |
+- (void)clearIOSSpecificIncognitoData; |
+// Deletes the incognito browser state. |
+- (void)deleteIncognitoBrowserState; |
+// Handles the notification that first run modal dialog UI is about to complete. |
+- (void)handleFirstRunUIWillFinish; |
+// Handles the notification that first run modal dialog UI completed. |
+- (void)handleFirstRunUIDidFinish; |
+// Performs synchronous browser state initialization steps. |
+- (void)initializeBrowserState:(ios::ChromeBrowserState*)browserState; |
+// Helper methods to initialize the application to a specific stage. |
+// Setting |_browserInitializationStage| to a specific stage requires the |
+// corresponding function to return YES. |
+// Initializes the application to INITIALIZATION_STAGE_BASIC, which is the |
+// minimum initialization needed in all cases. |
+- (void)startUpBrowserBasicInitialization; |
+// Initializes the application to INITIALIZATION_STAGE_BACKGROUND, which is |
+// needed by background handlers. |
+- (void)startUpBrowserBackgroundInitialization; |
+// Initializes the application to INITIALIZATION_STAGE_FOREGROUND, which is |
+// needed when application runs in foreground. |
+- (void)startUpBrowserForegroundInitialization; |
+// Swaps the UI between Incognito and normal modes. |
+- (void)swapBrowserModes; |
+@end |
+ |
+@implementation MainController |
+ |
+@synthesize appState = _appState; |
+@synthesize appLaunchTime = _appLaunchTime; |
+@synthesize browserInitializationStage = _browserInitializationStage; |
+@synthesize window = _window; |
+@synthesize isPresentingFirstRunUI = _isPresentingFirstRunUI; |
+@synthesize isColdStart = _isColdStart; |
+ |
+#pragma mark - Application lifecycle |
+ |
+- (instancetype)init { |
+ if ((self = [super init])) { |
+ _propertyReleaser_MainController.Init(self, [MainController class]); |
+ _startupTasks.reset([[StartupTasks alloc] init]); |
+ } |
+ return self; |
+} |
+ |
+- (void)dealloc { |
+ [[NSNotificationCenter defaultCenter] removeObserver:self]; |
+ net::HTTPProtocolHandlerDelegate::SetInstance(nullptr); |
+ net::RequestTracker::SetRequestTrackerFactory(nullptr); |
+ [NSObject cancelPreviousPerformRequestsWithTarget:self]; |
+ [super dealloc]; |
+} |
+ |
+// This function starts up to only what is needed at each stage of the |
+// initialization. It is possible to continue initialization later. |
+- (void)startUpBrowserToStage:(BrowserInitializationStageType)stage { |
+ if (_browserInitializationStage < INITIALIZATION_STAGE_BASIC && |
+ stage >= INITIALIZATION_STAGE_BASIC) { |
+ [self startUpBrowserBasicInitialization]; |
+ _browserInitializationStage = INITIALIZATION_STAGE_BASIC; |
+ } |
+ |
+ if (_browserInitializationStage < INITIALIZATION_STAGE_BACKGROUND && |
+ stage >= INITIALIZATION_STAGE_BACKGROUND) { |
+ [self startUpBrowserBackgroundInitialization]; |
+ _browserInitializationStage = INITIALIZATION_STAGE_BACKGROUND; |
+ } |
+ |
+ if (_browserInitializationStage < INITIALIZATION_STAGE_FOREGROUND && |
+ stage >= INITIALIZATION_STAGE_FOREGROUND) { |
+ // When adding a new initialization flow, consider setting |
+ // |_appState.userInteracted| at the appropriate time. |
+ DCHECK(_appState.userInteracted); |
+ [self startUpBrowserForegroundInitialization]; |
+ _browserInitializationStage = INITIALIZATION_STAGE_FOREGROUND; |
+ } |
+} |
+ |
+- (void)startUpBrowserBasicInitialization { |
+ _appLaunchTime = base::TimeTicks::Now(); |
+ _isColdStart = YES; |
+ |
+ [SetupDebugging setUpDebuggingOptions]; |
+ |
+ // Register all providers before calling any Chromium code. |
+ [ProviderRegistration registerProviders]; |
+} |
+ |
+- (void)startUpBrowserBackgroundInitialization { |
+ DCHECK(![self.appState isInSafeMode]); |
+ |
+ NSBundle* baseBundle = base::mac::OuterBundle(); |
+ base::mac::SetBaseBundleID( |
+ base::SysNSStringToUTF8([baseBundle bundleIdentifier]).c_str()); |
+ |
+ // Register default values for experimental settings (Application Preferences) |
+ // and set the "Version" key in the UserDefaults. |
+ [RegisterExperimentalSettings |
+ registerExperimentalSettingsWithUserDefaults:[NSUserDefaults |
+ standardUserDefaults] |
+ bundle:base::mac:: |
+ FrameworkBundle()]; |
+ |
+ // Register all clients before calling any web code. |
+ [ClientRegistration registerClients]; |
+ |
+ _chromeMain = [ChromeMainStarter startChromeMain]; |
+ |
+ // Initialize the ChromeBrowserProvider. |
+ ios::GetChromeBrowserProvider()->Initialize(); |
+ |
+ // If the user is interacting, crashes affect the user experience. Start |
+ // reporting as soon as possible. |
+ // TODO(crbug.com/507633): Call this even sooner. |
+ if (_appState.userInteracted) |
+ GetApplicationContext()->GetMetricsService()->OnAppEnterForeground(); |
+ |
+ web::WebUIIOSControllerFactory::RegisterFactory( |
+ ChromeWebUIIOSControllerFactory::GetInstance()); |
+ |
+ // TODO(crbug.com/546171): Audit all the following code to see if some of it |
+ // should move into BrowserMainParts or BrowserProcess. |
+ |
+ [NetworkStackSetup setUpChromeNetworkStack:&_requestTrackerFactory |
+ httpProtocolHandlerDelegate:&_httpProtocolHandlerDelegate]; |
+} |
+ |
+- (void)startUpBrowserForegroundInitialization { |
+ // Give tests a chance to prepare for testing. |
+ tests_hook::SetUpTestsIfPresent(); |
+ |
+ GetApplicationContext()->OnAppEnterForeground(); |
+ |
+ // TODO(crbug.com/546171): Audit all the following code to see if some of it |
+ // should move into BrowserMainParts or BrowserProcess. |
+ NSUserDefaults* standardDefaults = [NSUserDefaults standardUserDefaults]; |
+ |
+ // Although this duplicates some metrics_service startup logic also in |
+ // IOSChromeMain(), this call does additional work, checking for wifi-only |
+ // and setting up the required support structures. |
+ [_metricsMediator updateMetricsStateBasedOnPrefsUserTriggered:NO]; |
+ |
+ // Resets the number of crash reports that have been uploaded since the |
+ // previous Foreground initialization. |
+ [CrashReportBackgroundUploader resetReportsUploadedInBackgroundCount]; |
+ |
+ // Resets the interval stats between two background fetch as this value may be |
+ // obsolete. |
+ [BackgroundActivity foregroundStarted]; |
+ |
+ // Crash the app during startup if requested but only after we have enabled |
+ // uploading crash reports. |
+ [self crashIfRequested]; |
+ |
+ RegisterComponentsForUpdate(); |
+ |
+ if (experimental_flags::IsAlertOnBackgroundUploadEnabled()) { |
+ if ([UIApplication instancesRespondToSelector: |
+ @selector(registerUserNotificationSettings:)]) { |
+ [[UIApplication sharedApplication] |
+ registerUserNotificationSettings: |
+ [UIUserNotificationSettings |
+ settingsForTypes:UIUserNotificationTypeAlert | |
+ UIUserNotificationTypeBadge | |
+ UIUserNotificationTypeSound |
+ categories:nil]]; |
+ } |
+ } |
+ |
+ // Remove the extra browser states as Chrome iOS is single profile in M48+. |
+ ChromeBrowserStateRemovalController::GetInstance() |
+ ->RemoveBrowserStatesIfNecessary(); |
+ |
+ _browserStateManager = |
+ GetApplicationContext()->GetChromeBrowserStateManager(); |
+ ios::ChromeBrowserState* chromeBrowserState = |
+ _browserStateManager->GetLastUsedBrowserState(); |
+ |
+ // The CrashRestoreHelper must clean up the old browser state information |
+ // before the tabModels can be created. |_restoreHelper| must be kept alive |
+ // until the BVC receives the browser state and tab model. |
+ BOOL postCrashLaunch = [self mustShowRestoreInfobar]; |
+ if (postCrashLaunch) { |
+ _restoreHelper.reset( |
+ [[CrashRestoreHelper alloc] initWithBrowserState:chromeBrowserState]); |
+ [_restoreHelper moveAsideSessionInformation]; |
+ } |
+ |
+ // Initialize and set the main browser state. |
+ [self initializeBrowserState:chromeBrowserState]; |
+ _mainBrowserState = chromeBrowserState; |
+ _browserViewWrangler.reset([[BrowserViewWrangler alloc] |
+ initWithBrowserState:_mainBrowserState |
+ tabModelObserver:self]); |
+ // Ensure the main tab model is created. |
+ ignore_result([_browserViewWrangler mainTabModel]); |
+ |
+ [self createSpotlightManager]; |
+ |
+ if (reading_list::switches::IsReadingListEnabled()) { |
+ ShareExtensionService* service = |
+ ShareExtensionServiceFactory::GetForBrowserState(_mainBrowserState); |
+ service->Initialize(); |
+ } |
+ |
+ // Before bringing up the UI, make sure the launch mode is correct, and |
+ // check for previous crashes. |
+ BOOL startInIncognito = [standardDefaults boolForKey:kIncognitoCurrentKey]; |
+ BOOL switchFromIncognito = startInIncognito && ![self canLaunchInIncognito]; |
+ |
+ if (postCrashLaunch || switchFromIncognito) { |
+ [self clearIOSSpecificIncognitoData]; |
+ if (switchFromIncognito) |
+ [self switchGlobalStateToMode:ApplicationMode::NORMAL]; |
+ } |
+ if (switchFromIncognito) |
+ startInIncognito = NO; |
+ |
+ [self createInitialUI:(startInIncognito ? ApplicationMode::INCOGNITO |
+ : ApplicationMode::NORMAL)]; |
+ |
+ [self scheduleStartupCleanupTasks]; |
+ [MetricsMediator logLaunchMetricsWithStartupInformation:self |
+ browserViewInformation:_browserViewWrangler]; |
+ |
+ [self scheduleLowPriorityStartupTasks]; |
+ |
+ [_browserViewWrangler updateDeviceSharingManager]; |
+ |
+ [MDCTypography setFontLoader:[MDFRobotoFontLoader sharedInstance]]; |
+ |
+ [self openTabFromLaunchOptions:_launchOptions |
+ startupInformation:self |
+ appState:self.appState]; |
+ _launchOptions.reset(); |
+ |
+ mojo::edk::Init(); |
+ |
+ if (!_startupParameters) { |
+ // The startup parameters may create new tabs or navigations. If the restore |
+ // infobar is displayed now, it may be dismissed immediately and the user |
+ // will never be able to restore the session. |
+ [_restoreHelper showRestoreIfNeeded:[self currentTabModel]]; |
+ _restoreHelper.reset(); |
+ } |
+ |
+ [self scheduleTasksRequiringBVCWithBrowserState]; |
+ |
+ // Now that everything is properly set up, run the tests. |
+ tests_hook::RunTestsIfPresent(); |
+} |
+ |
+- (void)initializeBrowserState:(ios::ChromeBrowserState*)browserState { |
+ DCHECK(!browserState->IsOffTheRecord()); |
+ [self setInitialCookiesPolicy:browserState]; |
+ search_engines::UpdateSearchEnginesIfNeeded( |
+ browserState->GetPrefs(), |
+ ios::TemplateURLServiceFactory::GetForBrowserState(browserState)); |
+ |
+ if ([TouchToSearchPermissionsMediator isTouchToSearchAvailableOnDevice]) { |
+ base::scoped_nsobject<TouchToSearchPermissionsMediator> |
+ touchToSearchPermissions([[TouchToSearchPermissionsMediator alloc] |
+ initWithBrowserState:browserState]); |
+ if (experimental_flags::IsForceResetContextualSearchEnabled()) { |
+ [touchToSearchPermissions setPreferenceState:TouchToSearch::UNDECIDED]; |
+ } |
+ ContextualSearch::RecordPreferenceState( |
+ [touchToSearchPermissions preferenceState]); |
+ } |
+} |
+ |
+- (void)handleFirstRunUIWillFinish { |
+ DCHECK(_isPresentingFirstRunUI); |
+ _isPresentingFirstRunUI = NO; |
+ [[NSNotificationCenter defaultCenter] |
+ removeObserver:self |
+ name:kChromeFirstRunUIWillFinishNotification |
+ object:nil]; |
+ |
+ [self markEulaAsAccepted]; |
+ |
+ if (_startupParameters.get()) { |
+ [self dismissModalsAndOpenSelectedTabInMode:ApplicationMode::NORMAL |
+ withURL:[_startupParameters externalURL] |
+ transition:ui::PAGE_TRANSITION_LINK |
+ completion:nil]; |
+ _startupParameters.reset(); |
+ } |
+} |
+ |
+- (void)handleFirstRunUIDidFinish { |
+ [[NSNotificationCenter defaultCenter] |
+ removeObserver:self |
+ name:kChromeFirstRunUIDidFinishNotification |
+ object:nil]; |
+ |
+ // As soon as First Run has finished, give OmniboxGeolocationController an |
+ // opportunity to present the iOS system location alert. |
+ [[OmniboxGeolocationController sharedInstance] |
+ triggerSystemPromptForNewUser:YES]; |
+} |
+ |
+- (void)clearIOSSpecificIncognitoData { |
+ DCHECK(_mainBrowserState->HasOffTheRecordChromeBrowserState()); |
+ ios::ChromeBrowserState* otrBrowserState = |
+ _mainBrowserState->GetOffTheRecordChromeBrowserState(); |
+ int removeAllMask = ~0; |
+ [self.browsingDataRemovalController |
+ removeIOSSpecificIncognitoBrowsingDataFromBrowserState:otrBrowserState |
+ mask:removeAllMask |
+ completionHandler:^{ |
+ [self activateCurrentBVC]; |
+ }]; |
+} |
+ |
+- (void)deleteIncognitoBrowserState { |
+ BOOL otrBVCIsCurrent = (self.currentBVC == self.otrBVC); |
+ |
+ const BOOL isOnIPadWithTabSwitcherEnabled = |
+ IsIPadIdiom() && experimental_flags::IsTabSwitcherEnabled(); |
+ |
+ // If the current BVC is the otr BVC, then the user should be in the card |
+ // stack, this is not true for the iPad tab switcher. |
+ DCHECK(isOnIPadWithTabSwitcherEnabled || |
+ (!otrBVCIsCurrent || _tabSwitcherIsActive)); |
+ |
+ // We always clear the otr tab model on iPad. |
+ // Notify the _tabSwitcherController that its otrBVC will be destroyed. |
+ if (isOnIPadWithTabSwitcherEnabled || _tabSwitcherIsActive) |
+ [_tabSwitcherController setOtrTabModel:nil]; |
+ |
+ [_browserViewWrangler |
+ deleteIncognitoTabModelState:self.browsingDataRemovalController]; |
+ |
+ if (otrBVCIsCurrent) { |
+ [self activateCurrentBVC]; |
+ } |
+ |
+ // Always set the new otr tab model on iPad with tab switcher enabled. |
+ // Notify the _tabSwitcherController with the new otrBVC. |
+ if (isOnIPadWithTabSwitcherEnabled || _tabSwitcherIsActive) |
+ [_tabSwitcherController setOtrTabModel:self.otrTabModel]; |
+} |
+ |
+- (BrowsingDataRemovalController*)browsingDataRemovalController { |
+ if (!_browsingDataRemovalController) { |
+ _browsingDataRemovalController.reset( |
+ [[BrowsingDataRemovalController alloc] initWithDelegate:self]); |
+ } |
+ return _browsingDataRemovalController; |
+} |
+ |
+- (void)setWebUsageEnabled:(BOOL)enabled { |
+ DCHECK([NSThread isMainThread]); |
+ if (enabled) { |
+ [self activateCurrentBVC]; |
+ } else { |
+ [self.currentBVC setActive:enabled]; |
+ } |
+} |
+ |
+- (void)activateCurrentBVC { |
+ // If there are pending removal operations, the activation will be deferred |
+ // until the callback for |removeBrowsingDataFromBrowserState:| is received. |
+ if (![self.browsingDataRemovalController |
+ hasPendingRemovalOperations:self.currentBrowserState]) { |
+ [self.currentBVC setActive:YES]; |
+ [self.currentBVC setPrimary:YES]; |
+ } |
+} |
+ |
+#pragma mark - BrowserLauncher implementation. |
+ |
+- (NSDictionary*)launchOptions { |
+ return _launchOptions; |
+} |
+ |
+- (void)setLaunchOptions:(NSDictionary*)launchOptions { |
+ _launchOptions.reset([launchOptions retain]); |
+} |
+ |
+#pragma mark - Property implementation. |
+ |
+- (id<BrowserViewInformation>)browserViewInformation { |
+ return _browserViewWrangler; |
+} |
+ |
+- (AppStartupParameters*)startupParameters { |
+ return _startupParameters; |
+} |
+ |
+- (void)setStartupParameters:(AppStartupParameters*)startupParameters { |
+ _startupParameters.reset([startupParameters retain]); |
+} |
+ |
+- (MainViewController*)mainViewController { |
+ return self.mainCoordinator.mainViewController; |
+} |
+ |
+- (MainCoordinator*)mainCoordinator { |
+ if (_browserInitializationStage == INITIALIZATION_STAGE_BASIC) { |
+ NOTREACHED() << "mainCoordinator accessed too early in initialization."; |
+ return nil; |
+ } |
+ if (!_mainCoordinator) { |
+ // Lazily create the main coordinator. |
+ _mainCoordinator.reset( |
+ [[MainCoordinator alloc] initWithWindow:self.window]); |
+ } |
+ return _mainCoordinator; |
+} |
+ |
+- (BOOL)isFirstLaunchAfterUpgrade { |
+ return [[PreviousSessionInfo sharedInstance] isFirstSessionAfterUpgrade]; |
+} |
+ |
+- (MetricsMediator*)metricsMediator { |
+ return _metricsMediator; |
+} |
+ |
+- (void)setMetricsMediator:(MetricsMediator*)metricsMediator { |
+ _metricsMediator.reset(metricsMediator); |
+} |
+ |
+- (SettingsNavigationController*)settingsNavigationController { |
+ return _settingsNavigationController; |
+} |
+ |
+- (void)setSettingsNavigationController: |
+ (SettingsNavigationController*)settingsNavigationController { |
+ _settingsNavigationController.reset([settingsNavigationController retain]); |
+} |
+ |
+- (BOOL)startQRScannerAfterTabSwitcherDismissal { |
+ return (experimental_flags::IsQRCodeReaderEnabled() && |
+ _startQRScannerAfterTabSwitcherDismissal); |
+} |
+ |
+- (void)setStartQRScannerAfterTabSwitcherDismissal:(BOOL)startQRScanner { |
+ _startQRScannerAfterTabSwitcherDismissal = startQRScanner; |
+} |
+ |
+#pragma mark - StartupInformation implementation. |
+ |
+- (FirstUserActionRecorder*)firstUserActionRecorder { |
+ return _firstUserActionRecorder.get(); |
+} |
+ |
+- (void)resetFirstUserActionRecorder { |
+ _firstUserActionRecorder.reset(); |
+} |
+ |
+- (void)expireFirstUserActionRecorderAfterDelay:(NSTimeInterval)delay { |
+ [self performSelector:@selector(expireFirstUserActionRecorder) |
+ withObject:nil |
+ afterDelay:delay]; |
+} |
+ |
+- (void)activateFirstUserActionRecorderWithBackgroundTime: |
+ (NSTimeInterval)backgroundTime { |
+ base::TimeDelta delta = base::TimeDelta::FromSeconds(backgroundTime); |
+ _firstUserActionRecorder.reset(new FirstUserActionRecorder(delta)); |
+} |
+ |
+- (void)stopChromeMain { |
+ _chromeMain.reset(); |
+} |
+ |
+- (BOOL)isTabSwitcherActive { |
+ return _tabSwitcherIsActive; |
+} |
+ |
+#pragma mark - BrowserViewInformation implementation. |
+ |
+- (void)haltAllTabs { |
+ [_browserViewWrangler haltAllTabs]; |
+} |
+ |
+- (void)cleanDeviceSharingManager { |
+ [_browserViewWrangler cleanDeviceSharingManager]; |
+} |
+ |
+#pragma mark - BrowsingDataRemovalControllerDelegate methods |
+ |
+- (void)removeExternalFilesForBrowserState: |
+ (ios::ChromeBrowserState*)browserState |
+ completionHandler:(ProceduralBlock)completionHandler { |
+ // TODO(crbug.com/648940): Move this logic from BVC into |
+ // BrowsingDataRemovalController thereby eliminating the need for |
+ // BrowsingDataRemovalControllerDelegate. . |
+ if (_mainBrowserState == browserState) { |
+ [self.mainBVC removeExternalFilesImmediately:YES |
+ completionHandler:completionHandler]; |
+ } else if (completionHandler) { |
+ dispatch_async(dispatch_get_main_queue(), completionHandler); |
+ } |
+} |
+ |
+#pragma mark - Startup tasks |
+ |
+- (void)sendQueuedFeedback { |
+ [[DeferredInitializationRunner sharedInstance] |
+ enqueueBlockNamed:kSendQueuedFeedback |
+ block:^{ |
+ ios::GetChromeBrowserProvider() |
+ ->GetUserFeedbackProvider() |
+ ->Synchronize(); |
+ }]; |
+} |
+ |
+- (void)setInitialCookiesPolicy:(ios::ChromeBrowserState*)browserState { |
+ DCHECK(browserState); |
+ net::CookieStoreIOS::CookiePolicy policy = net::CookieStoreIOS::BLOCK; |
+ |
+ auto settingsFactory = |
+ ios::HostContentSettingsMapFactory::GetForBrowserState(browserState); |
+ DCHECK(settingsFactory); |
+ ContentSetting cookieSetting = settingsFactory->GetDefaultContentSetting( |
+ CONTENT_SETTINGS_TYPE_COOKIES, nullptr); |
+ |
+ if (!web::IsAcceptCookieControlSupported()) { |
+ // Override the Accept Cookie policy as ALLOW is the only policy |
+ // supported by //web. |
+ policy = net::CookieStoreIOS::ALLOW; |
+ if (cookieSetting == CONTENT_SETTING_BLOCK) { |
+ settingsFactory->SetDefaultContentSetting(CONTENT_SETTINGS_TYPE_COOKIES, |
+ CONTENT_SETTING_ALLOW); |
+ } |
+ } else { |
+ switch (cookieSetting) { |
+ case CONTENT_SETTING_ALLOW: |
+ policy = net::CookieStoreIOS::ALLOW; |
+ break; |
+ case CONTENT_SETTING_BLOCK: |
+ policy = net::CookieStoreIOS::BLOCK; |
+ break; |
+ default: |
+ NOTREACHED() << "Unsupported cookie policy."; |
+ break; |
+ } |
+ } |
+ |
+ web::WebThread::PostTask( |
+ web::WebThread::IO, FROM_HERE, |
+ base::Bind(&net::CookieStoreIOS::SetCookiePolicy, policy)); |
+} |
+ |
+- (void)orientationDidChange:(NSNotification*)notification { |
+ breakpad_helper::SetCurrentOrientation( |
+ [[UIApplication sharedApplication] statusBarOrientation], |
+ [[UIDevice currentDevice] orientation]); |
+} |
+ |
+- (void)registerForOrientationChangeNotifications { |
+ // Register to both device orientation and UI orientation did change |
+ // notification as these two events may be triggered independantely. |
+ [[NSNotificationCenter defaultCenter] |
+ addObserver:self |
+ selector:@selector(orientationDidChange:) |
+ name:UIDeviceOrientationDidChangeNotification |
+ object:nil]; |
+ [[NSNotificationCenter defaultCenter] |
+ addObserver:self |
+ selector:@selector(orientationDidChange:) |
+ name:UIApplicationDidChangeStatusBarOrientationNotification |
+ object:nil]; |
+} |
+ |
+- (void)schedulePrefObserverInitialization { |
+ [[DeferredInitializationRunner sharedInstance] |
+ enqueueBlockNamed:kPrefObserverInit |
+ block:^{ |
+ // Track changes to local state prefs. |
+ _localStatePrefObserverBridge.reset( |
+ new PrefObserverBridge(self)); |
+ _localStatePrefChangeRegistrar.Init( |
+ GetApplicationContext()->GetLocalState()); |
+ _localStatePrefObserverBridge->ObserveChangesForPreference( |
+ metrics::prefs::kMetricsReportingEnabled, |
+ &_localStatePrefChangeRegistrar); |
+ _localStatePrefObserverBridge->ObserveChangesForPreference( |
+ prefs::kMetricsReportingWifiOnly, |
+ &_localStatePrefChangeRegistrar); |
+ |
+ // Calls the onPreferenceChanged function in case there was |
+ // a |
+ // change to the observed preferences before the observer |
+ // bridge was set up. |
+ [self onPreferenceChanged:metrics::prefs:: |
+ kMetricsReportingEnabled]; |
+ [self onPreferenceChanged:prefs::kMetricsReportingWifiOnly]; |
+ }]; |
+} |
+ |
+- (void)scheduleCheckNativeApps { |
+ void (^checkInstalledApps)(void) = ^{ |
+ [ios::GetChromeBrowserProvider()->GetNativeAppWhitelistManager() |
+ checkInstalledApps]; |
+ }; |
+ [[DeferredInitializationRunner sharedInstance] |
+ enqueueBlockNamed:kCheckNativeApps |
+ block:checkInstalledApps]; |
+} |
+ |
+- (void)scheduleAppDistributionPings { |
+ [[DeferredInitializationRunner sharedInstance] |
+ enqueueBlockNamed:kSendInstallPingIfNecessary |
+ block:^{ |
+ net::URLRequestContextGetter* context = |
+ _mainBrowserState->GetRequestContext(); |
+ bool is_first_run = FirstRun::IsChromeFirstRun(); |
+ ios::GetChromeBrowserProvider() |
+ ->GetAppDistributionProvider() |
+ ->ScheduleDistributionNotifications(context, |
+ is_first_run); |
+ }]; |
+} |
+ |
+- (void)scheduleAuthenticationServiceNotification { |
+ [[DeferredInitializationRunner sharedInstance] |
+ enqueueBlockNamed:kAuthenticationServiceNotification |
+ block:^{ |
+ DCHECK(_mainBrowserState); |
+ // Force an obvious initialization of the |
+ // AuthenticationService. |
+ // This is done for clarity purpose only, and should be |
+ // removed |
+ // alongside the delayed initialization. See |
+ // crbug.com/464306. |
+ AuthenticationServiceFactory::GetForBrowserState( |
+ _mainBrowserState); |
+ if (![self currentBrowserState]) { |
+ // Active browser state should have been set before |
+ // scheduling |
+ // any authentication service notification. |
+ NOTREACHED(); |
+ return; |
+ } |
+ if ([SignedInAccountsViewController |
+ shouldBePresentedForBrowserState: |
+ [self currentBrowserState]]) { |
+ [self |
+ presentSignedInAccountsViewControllerForBrowserState: |
+ [self currentBrowserState]]; |
+ } |
+ }]; |
+} |
+ |
+- (void)scheduleStartupAttemptReset { |
+ [[DeferredInitializationRunner sharedInstance] |
+ enqueueBlockNamed:kStartupAttemptReset |
+ block:^{ |
+ crash_util::ResetFailedStartupAttemptCount(); |
+ }]; |
+} |
+ |
+- (void)scheduleCrashReportCleanup { |
+ [[DeferredInitializationRunner sharedInstance] |
+ enqueueBlockNamed:kCleanupCrashReports |
+ block:^{ |
+ breakpad_helper::CleanupCrashReports(); |
+ }]; |
+} |
+ |
+- (void)scheduleSnapshotPurge { |
+ [[DeferredInitializationRunner sharedInstance] |
+ enqueueBlockNamed:kPurgeSnapshots |
+ block:^{ |
+ [self purgeSnapshots]; |
+ }]; |
+} |
+ |
+- (void)scheduleStartupCleanupTasks { |
+ // Cleanup crash reports if this is the first run after an update. |
+ if ([self isFirstLaunchAfterUpgrade]) { |
+ [self scheduleCrashReportCleanup]; |
+ } |
+ |
+ // ClearSessionCookies() is not synchronous. |
+ if (cookie_util::ShouldClearSessionCookies()) { |
+ cookie_util::ClearSessionCookies( |
+ _mainBrowserState->GetOriginalChromeBrowserState()); |
+ if (![self.otrTabModel isEmpty]) { |
+ cookie_util::ClearSessionCookies( |
+ _mainBrowserState->GetOffTheRecordChromeBrowserState()); |
+ } |
+ } |
+ |
+ // If the user chooses to restore their session, some cached snapshots may |
+ // be needed. Otherwise, purge the cached snapshots. |
+ if (![self mustShowRestoreInfobar]) { |
+ [self scheduleSnapshotPurge]; |
+ } |
+} |
+ |
+- (void)scheduleMemoryDebuggingTools { |
+ if (experimental_flags::IsMemoryDebuggingEnabled()) { |
+ [[DeferredInitializationRunner sharedInstance] |
+ enqueueBlockNamed:kMemoryDebuggingToolsStartup |
+ block:^{ |
+ _memoryDebuggerManager.reset( |
+ [[MemoryDebuggerManager alloc] |
+ initWithView:self.window |
+ prefs:GetApplicationContext() |
+ ->GetLocalState()]); |
+ }]; |
+ } |
+} |
+ |
+- (void)scheduleFreeMemoryMonitoring { |
+ // TODO(crbug.com/649338): See if this method cannot call PostBlockingPoolTask |
+ // directly instead of enqueueing a block. |
+ [[DeferredInitializationRunner sharedInstance] |
+ enqueueBlockNamed:kMemoryMonitoring |
+ block:^{ |
+ web::WebThread::PostBlockingPoolTask( |
+ FROM_HERE, |
+ base::Bind( |
+ &ios_internal::AsynchronousFreeMemoryMonitor)); |
+ }]; |
+} |
+ |
+- (void)scheduleLowPriorityStartupTasks { |
+ [_startupTasks initializeOmaha]; |
+ [_startupTasks registerForApplicationWillResignActiveNotification]; |
+ [self registerForOrientationChangeNotifications]; |
+ |
+ // Deferred tasks. |
+ [self schedulePrefObserverInitialization]; |
+ [self scheduleMemoryDebuggingTools]; |
+ [_startupTasks scheduleDeferredBrowserStateInitialization:_mainBrowserState]; |
+ [self scheduleAuthenticationServiceNotification]; |
+ [self sendQueuedFeedback]; |
+ [self scheduleSpotlightResync]; |
+ [self scheduleDeleteDownloadsDirectory]; |
+ [self scheduleStartupAttemptReset]; |
+ [self scheduleFreeMemoryMonitoring]; |
+ [self scheduleAddApplicationShortcutItems]; |
+ [self scheduleAppDistributionPings]; |
+ [self scheduleCheckNativeApps]; |
+} |
+ |
+- (void)scheduleTasksRequiringBVCWithBrowserState { |
+ if (GetApplicationContext()->WasLastShutdownClean()) |
+ [self.mainBVC removeExternalFilesImmediately:NO completionHandler:nil]; |
+ |
+ [self scheduleShowPromo]; |
+} |
+ |
+- (void)scheduleDeleteDownloadsDirectory { |
+ [[DeferredInitializationRunner sharedInstance] |
+ enqueueBlockNamed:kDeleteDownloads |
+ block:^{ |
+ [DownloadManagerController clearDownloadsDirectory]; |
+ }]; |
+} |
+ |
+- (void)createSpotlightManager { |
+ if (spotlight::IsSpotlightAvailable()) { |
+ _spotlightManager.reset([[SpotlightManager |
+ spotlightManagerWithBrowserState:_mainBrowserState] retain]); |
+ } |
+} |
+ |
+- (void)scheduleSpotlightResync { |
+ if (!_spotlightManager) { |
+ return; |
+ } |
+ ProceduralBlock block = ^{ |
+ [_spotlightManager resyncIndex]; |
+ }; |
+ [[DeferredInitializationRunner sharedInstance] |
+ enqueueBlockNamed:kStartSpotlightBookmarksIndexing |
+ block:block]; |
+} |
+ |
+- (void)scheduleAddApplicationShortcutItems { |
+ ProceduralBlock block = ^{ |
+ if (experimental_flags::IsQRCodeReaderEnabled()) { |
+ UIApplicationShortcutItem* qrScannerShortcutItem = [[ |
+ [UIApplicationShortcutItem alloc] |
+ initWithType:@"OpenQRScanner" |
+ localizedTitle:l10n_util::GetNSString( |
+ IDS_IOS_APPLICATION_SHORTCUT_QR_SCANNER_TITLE) |
+ localizedSubtitle:nil |
+ icon:[UIApplicationShortcutIcon |
+ iconWithTemplateImageName: |
+ @"quick_action_qr_scanner"] |
+ userInfo:nil] autorelease]; |
+ // Note: The following only affects dynamic shortcut items defined |
+ // programmatically, and not static shortcut items set in the Info.plist |
+ // file. |
+ [[UIApplication sharedApplication] |
+ setShortcutItems:@[ qrScannerShortcutItem ]]; |
+ } else { |
+ [[UIApplication sharedApplication] setShortcutItems:nil]; |
+ } |
+ }; |
+ |
+ [[DeferredInitializationRunner sharedInstance] |
+ enqueueBlockNamed:kAddApplicationShortcutItems |
+ block:block]; |
+} |
+ |
+- (void)expireFirstUserActionRecorder { |
+ // Clear out any scheduled calls to this method. For example, the app may have |
+ // been backgrounded before the |kFirstUserActionTimeout| expired. |
+ [NSObject |
+ cancelPreviousPerformRequestsWithTarget:self |
+ selector:@selector( |
+ expireFirstUserActionRecorder) |
+ object:nil]; |
+ |
+ if (_firstUserActionRecorder) { |
+ _firstUserActionRecorder->Expire(); |
+ _firstUserActionRecorder.reset(); |
+ } |
+} |
+ |
+- (BOOL)canLaunchInIncognito { |
+ NSUserDefaults* standardDefaults = [NSUserDefaults standardUserDefaults]; |
+ if (![standardDefaults boolForKey:kIncognitoCurrentKey]) |
+ return NO; |
+ // If the application crashed in incognito mode, don't stay in incognito |
+ // mode, since the prompt to restore should happen in non-incognito |
+ // context. |
+ if ([self mustShowRestoreInfobar]) |
+ return NO; |
+ // If there are no incognito tabs, then ensure the app starts in normal mode, |
+ // since the UI isn't supposed to ever put the user in incognito mode without |
+ // any incognito tabs. |
+ return ![self.otrTabModel isEmpty]; |
+} |
+ |
+- (void)createInitialUI:(ApplicationMode)launchMode { |
+ DCHECK(_mainBrowserState); |
+ |
+ // In order to correctly set the mode switch icon, we need to know how many |
+ // tabs are in the other tab model. That means loading both models. They |
+ // may already be loaded. |
+ // TODO(crbug.com/546203): Find a way to handle this that's closer to the |
+ // point where it is necessary. |
+ TabModel* mainTabModel = self.mainTabModel; |
+ TabModel* otrTabModel = self.otrTabModel; |
+ |
+ // MainCoordinator shouldn't have been initialized yet. |
+ DCHECK(!_mainCoordinator); |
+ |
+ // Enables UI initializations to query the keyWindow's size. |
+ [self.window makeKeyAndVisible]; |
+ |
+ // Lazy init of mainCoordinator. |
+ [self.mainCoordinator start]; |
+ |
+ // Decide if the First Run UI needs to run. |
+ BOOL firstRun = (FirstRun::IsChromeFirstRun() || |
+ experimental_flags::AlwaysDisplayFirstRun()) && |
+ !tests_hook::DisableFirstRun(); |
+ |
+ ios::ChromeBrowserState* browserState = |
+ (launchMode == ApplicationMode::INCOGNITO) |
+ ? _mainBrowserState->GetOffTheRecordChromeBrowserState() |
+ : _mainBrowserState; |
+ [self changeStorageFromBrowserState:nullptr toBrowserState:browserState]; |
+ |
+ TabModel* tabModel; |
+ if (launchMode == ApplicationMode::INCOGNITO) { |
+ tabModel = otrTabModel; |
+ self.currentBVC = self.otrBVC; |
+ } else { |
+ tabModel = mainTabModel; |
+ self.currentBVC = self.mainBVC; |
+ } |
+ if (_tabSwitcherIsActive) |
+ [self dismissTabSwitcherWithoutAnimationInModel:self.mainTabModel]; |
+ if (firstRun || [self shouldOpenNTPTabOnActivationOfTabModel:tabModel]) { |
+ [self.currentBVC newTab:nil]; |
+ } |
+ |
+ if (firstRun) { |
+ [self showFirstRunUI]; |
+ // Do not ever show the 'restore' infobar during first run. |
+ _restoreHelper.reset(); |
+ } |
+} |
+ |
+- (void)showFirstRunUI { |
+ // Register for notification when First Run is completed. |
+ // Some initializations are held back until First Run modal dialog |
+ // is dismissed. |
+ [[NSNotificationCenter defaultCenter] |
+ addObserver:self |
+ selector:@selector(handleFirstRunUIWillFinish) |
+ name:kChromeFirstRunUIWillFinishNotification |
+ object:nil]; |
+ [[NSNotificationCenter defaultCenter] |
+ addObserver:self |
+ selector:@selector(handleFirstRunUIDidFinish) |
+ name:kChromeFirstRunUIDidFinishNotification |
+ object:nil]; |
+ |
+ base::scoped_nsobject<WelcomeToChromeViewController> welcomeToChrome( |
+ [[WelcomeToChromeViewController alloc] |
+ initWithBrowserState:_mainBrowserState |
+ tabModel:self.mainTabModel]); |
+ base::scoped_nsobject<UINavigationController> navController( |
+ [[OrientationLimitingNavigationController alloc] |
+ initWithRootViewController:welcomeToChrome]); |
+ [navController setModalTransitionStyle:UIModalTransitionStyleCrossDissolve]; |
+ CGRect appFrame = [[UIScreen mainScreen] bounds]; |
+ [[navController view] setFrame:appFrame]; |
+ _isPresentingFirstRunUI = YES; |
+ [self.mainBVC presentViewController:navController animated:NO completion:nil]; |
+} |
+ |
+- (void)crashIfRequested { |
+ if (experimental_flags::IsStartupCrashEnabled()) { |
+ // Flush out the value cached for |
+ // ios_internal::breakpad::SetUploadingEnabled(). |
+ [[NSUserDefaults standardUserDefaults] synchronize]; |
+ |
+ int* x = NULL; |
+ *x = 0; |
+ } |
+} |
+ |
+#pragma mark - Promo support |
+ |
+- (void)scheduleShowPromo { |
+ // Don't show promos if first run is shown. (Note: This flag is only YES |
+ // while the first run UI is visible. However, as this function is called |
+ // immediately after the UI is shown, it's a safe check.) |
+ if (_isPresentingFirstRunUI) |
+ return; |
+ // Don't show promos in Incognito mode. |
+ if (self.currentBVC == self.otrBVC) |
+ return; |
+ // Don't show promos if the app was launched from a URL. |
+ if (_startupParameters) |
+ return; |
+ |
+ // This array should contain Class objects - one for each promo class. |
+ // New PromoViewController subclasses should be added here. |
+ // Note that ordering matters -- only the first promo in the array that |
+ // returns true to +shouldBePresentedForProfile: will be shown. |
+ // TODO(crbug.com/516154): Now that there's only one promo class, this |
+ // implementation is overkill. |
+ NSArray* possiblePromos = @[ [SigninPromoViewController class] ]; |
+ for (id promoController in possiblePromos) { |
+ Class promoClass = (Class)promoController; |
+ DCHECK(class_isMetaClass(object_getClass(promoClass))); |
+ DCHECK(class_getClassMethod(object_getClass(promoClass), |
+ @selector(shouldBePresentedForBrowserState:))); |
+ if ([promoClass shouldBePresentedForBrowserState:_mainBrowserState]) { |
+ UIViewController* promoController = |
+ [promoClass controllerToPresentForBrowserState:_mainBrowserState]; |
+ |
+ dispatch_after( |
+ dispatch_time(DISPATCH_TIME_NOW, |
+ (int64_t)(kDisplayPromoDelay * NSEC_PER_SEC)), |
+ dispatch_get_main_queue(), ^{ |
+ [self showPromo:promoController]; |
+ }); |
+ |
+ break; |
+ } |
+ } |
+} |
+ |
+- (void)showPromo:(UIViewController*)promo { |
+ // Make sure we have the BVC here with a valid profile. |
+ DCHECK([self.currentBVC browserState]); |
+ |
+ base::scoped_nsobject<OrientationLimitingNavigationController> navController( |
+ [[OrientationLimitingNavigationController alloc] |
+ initWithRootViewController:promo]); |
+ |
+ // Avoid presenting the promo if the current device orientation is not |
+ // supported. The promo will be presented at a later moment, when the device |
+ // orientation is supported. |
+ UIInterfaceOrientation orientation = |
+ [UIApplication sharedApplication].statusBarOrientation; |
+ NSUInteger supportedOrientationsMask = |
+ [navController supportedInterfaceOrientations]; |
+ if (!((1 << orientation) & supportedOrientationsMask)) |
+ return; |
+ |
+ [navController setModalTransitionStyle:[promo modalTransitionStyle]]; |
+ [navController setNavigationBarHidden:YES]; |
+ [[navController view] setFrame:[[UIScreen mainScreen] bounds]]; |
+ |
+ [self.mainBVC presentViewController:navController |
+ animated:YES |
+ completion:nil]; |
+} |
+ |
+#pragma mark - chromeExecuteCommand |
+ |
+- (IBAction)chromeExecuteCommand:(id)sender { |
+ NSInteger command = [sender tag]; |
+ |
+ switch (command) { |
+ case IDC_NEW_TAB: |
+ [self createNewTabInBVC:self.mainBVC sender:sender]; |
+ break; |
+ case IDC_NEW_INCOGNITO_TAB: |
+ [self createNewTabInBVC:self.otrBVC sender:sender]; |
+ break; |
+ case IDC_OPEN_URL: |
+ [self openUrl:base::mac::ObjCCast<OpenUrlCommand>(sender)]; |
+ break; |
+ case IDC_SWITCH_BROWSER_MODES: |
+ DCHECK(IsIPadIdiom()); |
+ [self swapBrowserModes]; |
+ break; |
+ case IDC_OPTIONS: |
+ [self showSettings]; |
+ break; |
+ case IDC_REPORT_AN_ISSUE: |
+ dispatch_async(dispatch_get_main_queue(), ^{ |
+ [self showReportAnIssue]; |
+ }); |
+ break; |
+ case IDC_SHOW_SIGNIN_IOS: { |
+ ShowSigninCommand* command = |
+ base::mac::ObjCCastStrict<ShowSigninCommand>(sender); |
+ if (command.operation == AUTHENTICATION_OPERATION_DISMISS) { |
+ [self dismissSigninInteractionController]; |
+ } else { |
+ [self showSignInWithOperation:command.operation |
+ signInAccessPoint:command.signInAccessPoint |
+ callback:command.callback]; |
+ } |
+ break; |
+ } |
+ case IDC_SHOW_ACCOUNTS_SETTINGS: { |
+ [self showAccountsSettings]; |
+ break; |
+ } |
+ case IDC_SHOW_SYNC_SETTINGS: |
+ [self showSyncSettings]; |
+ break; |
+ case IDC_SHOW_SYNC_PASSPHRASE_SETTINGS: |
+ [self showSyncEncryptionPassphrase]; |
+ break; |
+ case IDC_SHOW_SAVE_PASSWORDS_SETTINGS: |
+ [self showSavePasswordsSettings]; |
+ break; |
+ case IDC_SHOW_HISTORY: |
+ [self showHistory]; |
+ break; |
+ case IDC_TOGGLE_TAB_SWITCHER: |
+ DCHECK(!_tabSwitcherIsActive); |
+ if ((!IsIPadIdiom() || experimental_flags::IsTabSwitcherEnabled()) && |
+ !_isProcessingVoiceSearchCommand) { |
+ [self showTabSwitcher]; |
+ _isProcessingTabSwitcherCommand = YES; |
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, |
+ kExpectedTransitionDurationInNanoSeconds), |
+ dispatch_get_main_queue(), ^{ |
+ _isProcessingTabSwitcherCommand = NO; |
+ }); |
+ } |
+ break; |
+ case IDC_PRELOAD_VOICE_SEARCH: |
+ [self.currentBVC chromeExecuteCommand:sender]; |
+ break; |
+ case IDC_VOICE_SEARCH: |
+ if (!_isProcessingTabSwitcherCommand) { |
+ [self startVoiceSearch]; |
+ _isProcessingVoiceSearchCommand = YES; |
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, |
+ kExpectedTransitionDurationInNanoSeconds), |
+ dispatch_get_main_queue(), ^{ |
+ _isProcessingVoiceSearchCommand = NO; |
+ }); |
+ } |
+ break; |
+ case IDC_CLEAR_BROWSING_DATA_IOS: { |
+ // Clear both the main browser state and the associated incognito |
+ // browser state. |
+ ClearBrowsingDataCommand* command = |
+ base::mac::ObjCCastStrict<ClearBrowsingDataCommand>(sender); |
+ ios::ChromeBrowserState* browserState = |
+ [command browserState]->GetOriginalChromeBrowserState(); |
+ int mask = [command mask]; |
+ browsing_data::TimePeriod timePeriod = [command timePeriod]; |
+ [self removeBrowsingDataFromBrowserState:browserState |
+ mask:mask |
+ timePeriod:timePeriod |
+ completionHandler:nil]; |
+ |
+ if (mask & IOSChromeBrowsingDataRemover::REMOVE_COOKIES) { |
+ base::Time beginDeleteTime = |
+ browsing_data::CalculateBeginDeleteTime(timePeriod); |
+ [ChromeWebViewFactory clearExternalCookies:browserState |
+ fromTime:beginDeleteTime |
+ toTime:base::Time::Max()]; |
+ } |
+ break; |
+ } |
+ case IDC_RESET_ALL_WEBVIEWS: |
+ [self.currentBVC resetAllWebViews]; |
+ break; |
+ case IDC_SHOW_GOOGLE_APPS_SETTINGS: |
+ [self showNativeAppsSettings]; |
+ break; |
+ case IDC_SHOW_CLEAR_BROWSING_DATA_SETTINGS: |
+ [self showClearBrowsingDataSettingsController]; |
+ break; |
+ case IDC_SHOW_CONTEXTUAL_SEARCH_SETTINGS: |
+ [self showContextualSearchSettingsController]; |
+ break; |
+ case IDC_CLOSE_MODALS: |
+ [self dismissModalDialogsWithCompletion:nil]; |
+ break; |
+ case IDC_SHOW_ADD_ACCOUNT: |
+ [self showAddAccount]; |
+ break; |
+ default: |
+ // Unknown commands get dropped with a warning. |
+ NOTREACHED() << "Unknown command id " << command; |
+ LOG(WARNING) << "Unknown command id " << command; |
+ break; |
+ } |
+} |
+ |
+#pragma mark - chromeExecuteCommand helpers |
+ |
+- (void)openUrl:(OpenUrlCommand*)command { |
+ if ([command fromChrome]) { |
+ [self dismissModalsAndOpenSelectedTabInMode:ApplicationMode::NORMAL |
+ withURL:[command url] |
+ transition:ui::PAGE_TRANSITION_TYPED |
+ completion:nil]; |
+ } else { |
+ [self dismissModalDialogsWithCompletion:^{ |
+ self.currentBVC = [command inIncognito] ? self.otrBVC : self.mainBVC; |
+ [self.currentBVC webPageOrderedOpen:[command url] |
+ referrer:[command referrer] |
+ windowName:[command windowName] |
+ inBackground:[command inBackground] |
+ appendTo:[command appendTo]]; |
+ }]; |
+ } |
+} |
+ |
+- (void)openUrlFromSettings:(OpenUrlCommand*)command { |
+ DCHECK([command fromChrome]); |
+ ProceduralBlock completion = ^{ |
+ [self dismissModalsAndOpenSelectedTabInMode:ApplicationMode::NORMAL |
+ withURL:[command url] |
+ transition:ui::PAGE_TRANSITION_TYPED |
+ completion:nil]; |
+ }; |
+ [self closeSettingsAnimated:YES completion:completion]; |
+} |
+ |
+- (void)startVoiceSearch { |
+ // If the background (non-current) BVC is playing TTS audio, call |
+ // -startVoiceSearch on it to stop the TTS. |
+ BrowserViewController* backgroundBVC = |
+ self.mainBVC == self.currentBVC ? self.otrBVC : self.mainBVC; |
+ if (backgroundBVC.playingTTS) |
+ [backgroundBVC startVoiceSearch]; |
+ else |
+ [self.currentBVC startVoiceSearch]; |
+} |
+ |
+#pragma mark - Preferences Management |
+ |
+- (void)onPreferenceChanged:(const std::string&)preferenceName { |
+ // Turn on or off metrics & crash reporting when either preference changes. |
+ if (preferenceName == metrics::prefs::kMetricsReportingEnabled || |
+ preferenceName == prefs::kMetricsReportingWifiOnly) { |
+ [_metricsMediator updateMetricsStateBasedOnPrefsUserTriggered:YES]; |
+ } |
+} |
+ |
+#pragma mark - BrowserViewInformation properties |
+ |
+- (BrowserViewController*)mainBVC { |
+ DCHECK(_browserViewWrangler); |
+ return [_browserViewWrangler mainBVC]; |
+} |
+ |
+- (void)setMainBVC:(BrowserViewController*)mainBVC { |
+ DCHECK(_browserViewWrangler); |
+ [_browserViewWrangler setMainBVC:mainBVC]; |
+} |
+ |
+- (TabModel*)mainTabModel { |
+ DCHECK(_browserViewWrangler); |
+ return [_browserViewWrangler mainTabModel]; |
+} |
+ |
+- (void)setMainTabModel:(TabModel*)mainTabModel { |
+ DCHECK(_browserViewWrangler); |
+ [_browserViewWrangler setMainTabModel:mainTabModel]; |
+} |
+ |
+- (BrowserViewController*)otrBVC { |
+ DCHECK(_browserViewWrangler); |
+ return [_browserViewWrangler otrBVC]; |
+} |
+ |
+- (void)setOtrBVC:(BrowserViewController*)otrBVC { |
+ DCHECK(_browserViewWrangler); |
+ [_browserViewWrangler setOtrBVC:otrBVC]; |
+} |
+ |
+- (TabModel*)otrTabModel { |
+ DCHECK(_browserViewWrangler); |
+ return [_browserViewWrangler otrTabModel]; |
+} |
+ |
+- (void)setOtrTabModel:(TabModel*)otrTabModel { |
+ DCHECK(_browserViewWrangler); |
+ [_browserViewWrangler setOtrTabModel:otrTabModel]; |
+} |
+ |
+- (BrowserViewController*)currentBVC { |
+ DCHECK(_browserViewWrangler); |
+ return [_browserViewWrangler currentBVC]; |
+} |
+ |
+// Note that the current tab of |bvc| will normally be reloaded by this method. |
+// If a new tab is about to be added, call expectNewForegroundTab on the BVC |
+// first to avoid extra work and possible page load side-effects for the tab |
+// being replaced. |
+- (void)setCurrentBVC:(BrowserViewController*)bvc { |
+ DCHECK(bvc != nil); |
+ if (self.currentBVC == bvc) |
+ return; |
+ |
+ DCHECK(_browserViewWrangler); |
+ [_browserViewWrangler setCurrentBVC:bvc storageSwitcher:self]; |
+ |
+ if (!_dismissingStackView) |
+ [self displayCurrentBVC]; |
+ |
+ // Tell the BVC that was made current that it can use the web. |
+ [self activateCurrentBVC]; |
+} |
+ |
+#pragma mark - Tab closure handlers |
+ |
+- (void)lastIncognitoTabClosed { |
+ DCHECK(_mainBrowserState->HasOffTheRecordChromeBrowserState()); |
+ [self clearIOSSpecificIncognitoData]; |
+ UnSynchronizeCookieStore( |
+ _mainBrowserState->GetOffTheRecordChromeBrowserState()); |
+ |
+ // OffTheRecordProfileIOData cannot be deleted before all the requests are |
+ // deleted. All of the request trackers associated with the closed OTR tabs |
+ // will have posted CancelRequest calls to the IO thread by now; this just |
+ // waits for those calls to run before calling |deleteIncognitoBrowserState|. |
+ web::RequestTrackerImpl::RunAfterRequestsCancel(base::BindBlock(^{ |
+ [self deleteIncognitoBrowserState]; |
+ })); |
+ |
+ // a) The first condition can happen when the last incognito tab is closed |
+ // from the tab switcher. |
+ // b) The second condition can happen if some other code (like JS) triggers |
+ // closure of tabs from the otr tab model when it's not current. |
+ // Nothing to do here. The next user action (like clicking on an existing |
+ // regular tab or creating a new incognito tab from the settings menu) will |
+ // take care of the logic to mode switch. |
+ if (_tabSwitcherIsActive || ![self.currentTabModel isOffTheRecord]) { |
+ return; |
+ } |
+ |
+ if (IsIPadIdiom()) { |
+ if (experimental_flags::IsTabSwitcherEnabled()) { |
+ [self showTabSwitcher]; |
+ } else { |
+ // Mode switch if not in regular mode. |
+ [self swapBrowserModes]; |
+ } |
+ } else { |
+ self.currentBVC = self.mainBVC; |
+ if ([self.currentTabModel count] == 0U) { |
+ [self showTabSwitcher]; |
+ } |
+ } |
+} |
+ |
+- (void)lastRegularTabClosed { |
+ // a) The first condition can happen when the last regular tab is closed from |
+ // the tab switcher. |
+ // b) The second condition can happen if some other code (like JS) triggers |
+ // closure of tabs from the main tab model when the main tab model is not |
+ // current. |
+ // Nothing to do here. |
+ if (_tabSwitcherIsActive || [self.currentTabModel isOffTheRecord]) { |
+ return; |
+ } |
+ |
+ if (IsIPadIdiom()) { |
+ if (experimental_flags::IsTabSwitcherEnabled()) { |
+ [self showTabSwitcher]; |
+ } |
+ } else { |
+ [self showTabSwitcher]; |
+ } |
+} |
+ |
+#pragma mark - Mode Switching |
+ |
+- (void)switchGlobalStateToMode:(ApplicationMode)mode { |
+ const BOOL incognito = (mode == ApplicationMode::INCOGNITO); |
+ // Write the state to disk of what is "active". |
+ NSUserDefaults* standardDefaults = [NSUserDefaults standardUserDefaults]; |
+ [standardDefaults setBool:incognito forKey:kIncognitoCurrentKey]; |
+ // Save critical state information for switching between normal and |
+ // incognito. |
+ [standardDefaults synchronize]; |
+} |
+ |
+- (void)changeStorageFromBrowserState:(ios::ChromeBrowserState*)oldState |
+ toBrowserState:(ios::ChromeBrowserState*)newState { |
+ ApplicationMode mode = newState->IsOffTheRecord() ? ApplicationMode::INCOGNITO |
+ : ApplicationMode::NORMAL; |
+ [self switchGlobalStateToMode:mode]; |
+} |
+ |
+// Set |bvc| as the current BVC and then creates a new tab. |
+- (void)createNewTabInBVC:(BrowserViewController*)bvc sender:(id)sender { |
+ DCHECK(bvc); |
+ [bvc expectNewForegroundTab]; |
+ self.currentBVC = bvc; |
+ [self.currentBVC newTab:sender]; |
+} |
+ |
+- (void)displayCurrentBVC { |
+ self.mainViewController.activeViewController = self.currentBVC; |
+} |
+ |
+- (TabModel*)currentTabModel { |
+ return self.currentBVC.tabModel; |
+} |
+ |
+- (ios::ChromeBrowserState*)currentBrowserState { |
+ return self.currentBVC.browserState; |
+} |
+ |
+- (void)swapBrowserModes { |
+ if (self.mainBVC == self.currentBVC) |
+ self.currentBVC = self.otrBVC; |
+ else |
+ self.currentBVC = self.mainBVC; |
+ // Make sure there is at least one tab open. |
+ if ([self shouldOpenNTPTabOnActivationOfTabModel:[self currentTabModel]]) |
+ [self.currentBVC newTab:nil]; |
+ [_browserViewWrangler updateModeToggle]; |
+} |
+ |
+// NOTE: If you change this function, it may have an effect on the performance |
+// of opening the stack view. Please make sure you also change the corresponding |
+// code in StackViewControllerPerfTest::MainControllerShowTabSwitcher(). |
+- (void)showTabSwitcher { |
+ BrowserViewController* currentBVC = self.currentBVC; |
+ Tab* currentTab = [[currentBVC tabModel] currentTab]; |
+ |
+ // In order to generate the transition between the current browser view |
+ // controller and the tab switcher controller it's possible that multiple |
+ // screenshots of the same tab are taken. Since taking a screenshot is |
+ // expensive we activate snapshot coalescing in the scope of this function |
+ // which will cache the first snapshot for the tab and reuse it instead of |
+ // regenerating a new one each time. |
+ [currentTab setSnapshotCoalescingEnabled:YES]; |
+ base::ScopedClosureRunner runner(base::BindBlock(^{ |
+ [currentTab setSnapshotCoalescingEnabled:NO]; |
+ })); |
+ |
+ if (experimental_flags::IsTabSwitcherEnabled()) |
+ [currentBVC prepareToEnterTabSwitcher:nil]; |
+ |
+ if (!_tabSwitcherController.get()) { |
+ if (IsIPadIdiom()) { |
+ _tabSwitcherController.reset([[TabSwitcherController alloc] |
+ initWithBrowserState:_mainBrowserState |
+ mainTabModel:self.mainTabModel |
+ otrTabModel:self.otrTabModel |
+ activeTabModel:self.currentTabModel]); |
+ } else { |
+ _tabSwitcherController.reset([[StackViewController alloc] |
+ initWithMainTabModel:self.mainTabModel |
+ otrTabModel:self.otrTabModel |
+ activeTabModel:self.currentTabModel]); |
+ } |
+ } else { |
+ // The StackViewController is kept in memory to avoid the performance hit of |
+ // loading from the nib on next showing, but clears out its card models to |
+ // release memory. The tab models are required to rebuild the card stacks. |
+ [_tabSwitcherController |
+ restoreInternalStateWithMainTabModel:self.mainTabModel |
+ otrTabModel:self.otrTabModel |
+ activeTabModel:self.currentTabModel]; |
+ } |
+ _tabSwitcherIsActive = YES; |
+ [_tabSwitcherController setDelegate:self]; |
+ if (IsIPadIdiom() && experimental_flags::IsTabSwitcherEnabled()) { |
+ TabSwitcherTransitionContext* transitionContext = |
+ [TabSwitcherTransitionContext |
+ tabSwitcherTransitionContextWithCurrent:currentBVC |
+ mainBVC:self.mainBVC |
+ otrBVC:self.otrBVC]; |
+ [_tabSwitcherController setTransitionContext:transitionContext]; |
+ self.mainViewController.activeViewController = _tabSwitcherController; |
+ [_tabSwitcherController showWithSelectedTabAnimation]; |
+ } else { |
+ // User interaction is disabled when the stack controller is dismissed. |
+ [[_tabSwitcherController view] setUserInteractionEnabled:YES]; |
+ self.mainViewController.activeViewController = _tabSwitcherController; |
+ [_tabSwitcherController showWithSelectedTabAnimation]; |
+ } |
+} |
+ |
+- (BOOL)shouldOpenNTPTabOnActivationOfTabModel:(TabModel*)tabModel { |
+ if (_settingsNavigationController) { |
+ return false; |
+ } |
+ if (_tabSwitcherIsActive) { |
+ // Only attempt to dismiss the tab switcher and open a new tab if: |
+ // - there are no tabs open in either tab model, and |
+ // - the tab switcher controller is not directly or indirectly presenting |
+ // another view controller. |
+ if (![self.mainTabModel isEmpty] || ![self.otrTabModel isEmpty]) |
+ return NO; |
+ |
+ UIViewController* viewController = [self topPresentedViewController]; |
+ while (viewController) { |
+ if ([viewController.presentingViewController |
+ isEqual:_tabSwitcherController]) { |
+ return NO; |
+ } |
+ viewController = viewController.presentingViewController; |
+ } |
+ return YES; |
+ } |
+ return ![tabModel count] && [tabModel browserState] && |
+ ![tabModel browserState]->IsOffTheRecord(); |
+} |
+ |
+#pragma mark - TabSwitching implementation. |
+ |
+- (BOOL)openNewTabFromTabSwitcher { |
+ if (!_tabSwitcherController) |
+ return NO; |
+ |
+ [_tabSwitcherController |
+ dismissWithNewTabAnimationToModel:self.mainTabModel |
+ withURL:GURL(kChromeUINewTabURL) |
+ atIndex:NSNotFound |
+ transition:ui::PAGE_TRANSITION_TYPED]; |
+ return YES; |
+} |
+ |
+- (void)dismissTabSwitcherWithoutAnimationInModel:(TabModel*)tabModel { |
+ DCHECK(_tabSwitcherIsActive); |
+ DCHECK(!_dismissingStackView); |
+ if ([_tabSwitcherController |
+ respondsToSelector:@selector(tabSwitcherDismissWithModel: |
+ animated:)]) { |
+ [self dismissModalDialogsWithCompletion:nil]; |
+ [_tabSwitcherController tabSwitcherDismissWithModel:tabModel animated:NO]; |
+ } else { |
+ [self beginDismissingStackViewWithCurrentModel:tabModel]; |
+ [self finishDismissingStackView]; |
+ } |
+} |
+ |
+#pragma mark - TabSwitcherDelegate Implementation |
+ |
+- (void)tabSwitcher:(id<TabSwitcher>)tabSwitcher |
+ dismissTransitionWillStartWithActiveModel:(TabModel*)tabModel { |
+ [self beginDismissingStackViewWithCurrentModel:tabModel]; |
+} |
+ |
+- (void)tabSwitcherDismissTransitionDidEnd:(id<TabSwitcher>)tabSwitcher { |
+ [self finishDismissingStackView]; |
+} |
+ |
+- (void)beginDismissingStackViewWithCurrentModel:(TabModel*)tabModel { |
+ DCHECK(experimental_flags::IsTabSwitcherEnabled() || !IsIPadIdiom()); |
+ DCHECK(tabModel == self.mainTabModel || tabModel == self.otrTabModel); |
+ |
+ _dismissingStackView = YES; |
+ // Prevent wayward touches from wreaking havoc while the stack view is being |
+ // dismissed. |
+ [[_tabSwitcherController view] setUserInteractionEnabled:NO]; |
+ BrowserViewController* targetBVC = |
+ (tabModel == self.mainTabModel) ? self.mainBVC : self.otrBVC; |
+ self.currentBVC = targetBVC; |
+} |
+ |
+- (void)finishDismissingStackView { |
+ DCHECK(!IsIPadIdiom() || experimental_flags::IsTabSwitcherEnabled()); |
+ DCHECK_EQ(self.mainViewController.activeViewController, |
+ _tabSwitcherController.get()); |
+ |
+ if (_modeToDisplayOnStackViewDismissal == StackViewDismissalMode::NORMAL) { |
+ self.currentBVC = self.mainBVC; |
+ } else if (_modeToDisplayOnStackViewDismissal == |
+ StackViewDismissalMode::INCOGNITO) { |
+ self.currentBVC = self.otrBVC; |
+ } |
+ |
+ _modeToDisplayOnStackViewDismissal = StackViewDismissalMode::NONE; |
+ |
+ // Displaying the current BVC dismisses the stack view. |
+ [self displayCurrentBVC]; |
+ |
+ // Start Voice Search or QR Scanner now that they can be presented from the |
+ // current BVC. |
+ if (_startVoiceSearchAfterTabSwitcherDismissal) { |
+ _startVoiceSearchAfterTabSwitcherDismissal = NO; |
+ [self.currentBVC startVoiceSearch]; |
+ } else if ([self startQRScannerAfterTabSwitcherDismissal]) { |
+ [self setStartQRScannerAfterTabSwitcherDismissal:NO]; |
+ [self.currentBVC showQRScanner]; |
+ } |
+ |
+ [_tabSwitcherController setDelegate:nil]; |
+ |
+ _tabSwitcherIsActive = NO; |
+ _dismissingStackView = NO; |
+} |
+ |
+- (void)tabSwitcherPresentationTransitionDidEnd:(id<TabSwitcher>)tabSwitcher { |
+} |
+ |
+- (id<ToolbarOwner>)tabSwitcherTransitionToolbarOwner { |
+ // Request the view to ensure that the view has been loaded and initialized, |
+ // since it may never have been loaded (or have been swapped out). |
+ [self.currentBVC ensureViewCreated]; |
+ return self.currentBVC; |
+} |
+ |
+#pragma mark - Browsing data clearing |
+ |
+- (void)removeBrowsingDataFromBrowserState: |
+ (ios::ChromeBrowserState*)browserState |
+ mask:(int)mask |
+ timePeriod:(browsing_data::TimePeriod)timePeriod |
+ completionHandler:(ProceduralBlock)completionHandler { |
+ // TODO(crbug.com/632772): Remove web usage disabling once |
+ // https://bugs.webkit.org/show_bug.cgi?id=149079 has been fixed. |
+ if (browserState == self.currentBrowserState && |
+ (mask & IOSChromeBrowsingDataRemover::REMOVE_COOKIES)) { |
+ [self setWebUsageEnabled:NO]; |
+ } |
+ |
+ ProceduralBlock browsingDataRemoved = ^{ |
+ [self setWebUsageEnabled:YES]; |
+ if (completionHandler) { |
+ completionHandler(); |
+ } |
+ }; |
+ [self.browsingDataRemovalController |
+ removeBrowsingDataFromBrowserState:browserState |
+ mask:mask |
+ timePeriod:timePeriod |
+ completionHandler:browsingDataRemoved]; |
+} |
+ |
+#pragma mark - Navigation Controllers |
+ |
+- (void)presentSignedInAccountsViewControllerForBrowserState: |
+ (ios::ChromeBrowserState*)browserState { |
+ base::scoped_nsobject<UIViewController> accountsViewController( |
+ [[SignedInAccountsViewController alloc] |
+ initWithBrowserState:browserState]); |
+ [[self topPresentedViewController] |
+ presentViewController:accountsViewController |
+ animated:YES |
+ completion:nil]; |
+} |
+ |
+- (void)showSettings { |
+ if (_settingsNavigationController) |
+ return; |
+ [[DeferredInitializationRunner sharedInstance] |
+ runBlockIfNecessary:kPrefObserverInit]; |
+ DCHECK(_localStatePrefObserverBridge); |
+ _settingsNavigationController.reset([SettingsNavigationController |
+ newSettingsMainControllerWithMainBrowserState:_mainBrowserState |
+ currentBrowserState:self.currentBrowserState |
+ delegate:self]); |
+ [[self topPresentedViewController] |
+ presentViewController:_settingsNavigationController |
+ animated:YES |
+ completion:nil]; |
+} |
+ |
+- (void)showAccountsSettings { |
+ if (_settingsNavigationController) |
+ return; |
+ if ([self currentBrowserState]->IsOffTheRecord()) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ _settingsNavigationController.reset([SettingsNavigationController |
+ newAccountsController:self.currentBrowserState |
+ delegate:self]); |
+ [[self topPresentedViewController] |
+ presentViewController:_settingsNavigationController |
+ animated:YES |
+ completion:nil]; |
+} |
+ |
+- (void)showSyncSettings { |
+ if (_settingsNavigationController) |
+ return; |
+ _settingsNavigationController.reset([SettingsNavigationController |
+ newSyncController:_mainBrowserState |
+ allowSwitchSyncAccount:YES |
+ delegate:self]); |
+ [[self topPresentedViewController] |
+ presentViewController:_settingsNavigationController |
+ animated:YES |
+ completion:nil]; |
+} |
+ |
+- (void)showSavePasswordsSettings { |
+ if (_settingsNavigationController) |
+ return; |
+ _settingsNavigationController.reset([SettingsNavigationController |
+ newSavePasswordsController:_mainBrowserState |
+ delegate:self]); |
+ [[self topPresentedViewController] |
+ presentViewController:_settingsNavigationController |
+ animated:YES |
+ completion:nil]; |
+} |
+ |
+- (void)showReportAnIssue { |
+ if (_settingsNavigationController) |
+ return; |
+ _settingsNavigationController.reset([SettingsNavigationController |
+ newUserFeedbackController:_mainBrowserState |
+ delegate:self |
+ feedbackDataSource:self]); |
+ [[self topPresentedViewController] |
+ presentViewController:_settingsNavigationController |
+ animated:YES |
+ completion:nil]; |
+} |
+ |
+- (void)showSyncEncryptionPassphrase { |
+ if (_settingsNavigationController) |
+ return; |
+ _settingsNavigationController.reset([SettingsNavigationController |
+ newSyncEncryptionPassphraseController:_mainBrowserState |
+ delegate:self]); |
+ [[self topPresentedViewController] |
+ presentViewController:_settingsNavigationController |
+ animated:YES |
+ completion:nil]; |
+} |
+ |
+- (void)showClearBrowsingDataSettingsController { |
+ if (_settingsNavigationController) |
+ return; |
+ _settingsNavigationController.reset([SettingsNavigationController |
+ newClearBrowsingDataController:_mainBrowserState |
+ delegate:self]); |
+ [[self topPresentedViewController] |
+ presentViewController:_settingsNavigationController |
+ animated:YES |
+ completion:nil]; |
+} |
+ |
+- (void)showContextualSearchSettingsController { |
+ if (_settingsNavigationController) |
+ return; |
+ _settingsNavigationController.reset([SettingsNavigationController |
+ newContextualSearchController:_mainBrowserState |
+ delegate:self]); |
+ [[self topPresentedViewController] |
+ presentViewController:_settingsNavigationController |
+ animated:YES |
+ completion:nil]; |
+} |
+ |
+- (void)showSignInWithOperation:(AuthenticationOperation)operation |
+ signInAccessPoint:(signin_metrics::AccessPoint)signInAccessPoint |
+ callback:(ShowSigninCommandCompletionCallback)callback { |
+ DCHECK_NE(AUTHENTICATION_OPERATION_DISMISS, operation); |
+ |
+ if (_signinInteractionController) { |
+ // Avoid showing the sign in screen if there is already a sign-in operation |
+ // in progress. |
+ return; |
+ } |
+ |
+ BOOL areSettingsPresented = _settingsNavigationController != NULL; |
+ _signinInteractionController.reset([[SigninInteractionController alloc] |
+ initWithBrowserState:_mainBrowserState |
+ presentingViewController:[self topPresentedViewController] |
+ isPresentedOnSettings:areSettingsPresented |
+ signInAccessPoint:signInAccessPoint]); |
+ |
+ signin_ui::CompletionCallback completion = ^(BOOL success) { |
+ _signinInteractionController.reset(); |
+ if (callback) |
+ callback(success); |
+ }; |
+ |
+ switch (operation) { |
+ case AUTHENTICATION_OPERATION_DISMISS: |
+ // Special case handled above. |
+ NOTREACHED(); |
+ break; |
+ case AUTHENTICATION_OPERATION_REAUTHENTICATE: |
+ [_signinInteractionController |
+ reAuthenticateWithCompletion:completion |
+ viewController:self.mainViewController]; |
+ break; |
+ case AUTHENTICATION_OPERATION_SIGNIN: |
+ [_signinInteractionController |
+ signInWithCompletion:completion |
+ viewController:self.mainViewController]; |
+ break; |
+ } |
+} |
+ |
+- (void)showAddAccount { |
+ if (_signinInteractionController) { |
+ // Avoid showing the sign in screen if there is already a sign-in operation |
+ // in progress. |
+ return; |
+ } |
+ |
+ BOOL areSettingsPresented = _settingsNavigationController != NULL; |
+ _signinInteractionController.reset([[SigninInteractionController alloc] |
+ initWithBrowserState:_mainBrowserState |
+ presentingViewController:[self topPresentedViewController] |
+ isPresentedOnSettings:areSettingsPresented |
+ signInAccessPoint:signin_metrics::AccessPoint:: |
+ ACCESS_POINT_UNKNOWN]); |
+ |
+ [_signinInteractionController |
+ addAccountWithCompletion:^(BOOL success) { |
+ _signinInteractionController.reset(); |
+ } |
+ viewController:self.mainViewController]; |
+} |
+ |
+- (void)showHistory { |
+ _historyPanelViewController.reset([[HistoryPanelViewController |
+ controllerToPresentForBrowserState:_mainBrowserState |
+ loader:self.currentBVC] retain]); |
+ [self.currentBVC presentViewController:_historyPanelViewController |
+ animated:YES |
+ completion:nil]; |
+} |
+ |
+- (void)dismissSigninInteractionController { |
+ // The sign-in interaction controller is destroyed as a result of calling |
+ // |cancelAndDismiss|. Destroying it here may lead to a missing call of the |
+ // |ShowSigninCommandCompletionCallback| passed when starting a show sign-in |
+ // operation. |
+ [_signinInteractionController cancelAndDismiss]; |
+} |
+ |
+- (ShowSigninCommandCompletionCallback)successfulSigninCompletion: |
+ (ProceduralBlock)callback { |
+ return [[^(BOOL successful) { |
+ ios::ChromeBrowserState* browserState = [self currentBrowserState]; |
+ if (browserState->IsOffTheRecord()) { |
+ NOTREACHED() |
+ << "Ignore call to |handleSignInFinished| when in incognito."; |
+ return; |
+ } |
+ DCHECK_EQ(self.mainBVC, self.currentBVC); |
+ SigninManager* signinManager = |
+ ios::SigninManagerFactory::GetForBrowserState(browserState); |
+ if (signinManager->IsAuthenticated()) |
+ callback(); |
+ } copy] autorelease]; |
+} |
+ |
+- (void)showNativeAppsSettings { |
+ if (_settingsNavigationController) |
+ return; |
+ _settingsNavigationController.reset([SettingsNavigationController |
+ newNativeAppsController:_mainBrowserState |
+ delegate:self]); |
+ [[self topPresentedViewController] |
+ presentViewController:_settingsNavigationController |
+ animated:YES |
+ completion:nil]; |
+} |
+ |
+- (void)closeSettingsAnimated:(BOOL)animated |
+ completion:(ProceduralBlock)completion { |
+ DCHECK(_settingsNavigationController); |
+ [_settingsNavigationController settingsWillBeDismissed]; |
+ UIViewController* presentingViewController = |
+ [_settingsNavigationController presentingViewController]; |
+ DCHECK(presentingViewController); |
+ [presentingViewController dismissViewControllerAnimated:animated |
+ completion:^{ |
+ if (completion) |
+ completion(); |
+ }]; |
+ _settingsNavigationController.reset(); |
+} |
+ |
+#pragma mark - TabModelObserver |
+ |
+// Called when the number of tabs changes. Triggers the switcher view when |
+// the last tab is closed on a device that uses the switcher. |
+- (void)tabModelDidChangeTabCount:(TabModel*)notifiedTabModel { |
+ TabModel* currentTabModel = [self currentTabModel]; |
+ // Do nothing on initialization. |
+ if (!currentTabModel) |
+ return; |
+ |
+ if (notifiedTabModel.count == 0U) { |
+ if ([notifiedTabModel isOffTheRecord]) { |
+ [self lastIncognitoTabClosed]; |
+ } else { |
+ [self lastRegularTabClosed]; |
+ } |
+ } |
+} |
+ |
+#pragma mark - Tab opening utility methods. |
+ |
+- (Tab*)openOrReuseTabInMode:(ApplicationMode)targetMode |
+ withURL:(const GURL&)url |
+ transition:(ui::PageTransition)transition { |
+ BrowserViewController* targetBVC = |
+ targetMode == ApplicationMode::NORMAL ? self.mainBVC : self.otrBVC; |
+ GURL currentURL; |
+ |
+ Tab* currentTabInTargetBVC = [[targetBVC tabModel] currentTab]; |
+ if (currentTabInTargetBVC) |
+ currentURL = [currentTabInTargetBVC url]; |
+ |
+ if (!(currentTabInTargetBVC && IsURLNtp(currentURL))) { |
+ return [targetBVC addSelectedTabWithURL:url |
+ atIndex:NSNotFound |
+ transition:transition]; |
+ } |
+ |
+ Tab* newTab = currentTabInTargetBVC; |
+ // Don't call loadWithParams for chrome://newtab, it's already loaded. |
+ if (!(IsURLNtp(url))) { |
+ web::NavigationManager::WebLoadParams params(url); |
+ [[newTab webController] loadWithParams:params]; |
+ } |
+ return newTab; |
+} |
+ |
+- (Tab*)openSelectedTabInMode:(ApplicationMode)targetMode |
+ withURL:(const GURL&)url |
+ transition:(ui::PageTransition)transition { |
+ BrowserViewController* targetBVC = |
+ targetMode == ApplicationMode::NORMAL ? self.mainBVC : self.otrBVC; |
+ NSUInteger tabIndex = NSNotFound; |
+ |
+ Tab* tab = nil; |
+ if (_tabSwitcherIsActive) { |
+ // If the stack view is already being dismissed, simply add the tab and |
+ // note that when the stack view finishes dismissing, the current BVC should |
+ // be switched to be the main BVC if necessary. |
+ if (_dismissingStackView) { |
+ _modeToDisplayOnStackViewDismissal = |
+ targetMode == ApplicationMode::NORMAL |
+ ? StackViewDismissalMode::NORMAL |
+ : StackViewDismissalMode::INCOGNITO; |
+ tab = [targetBVC addSelectedTabWithURL:url |
+ atIndex:tabIndex |
+ transition:transition]; |
+ } else { |
+ tab = [_tabSwitcherController |
+ dismissWithNewTabAnimationToModel:targetBVC.tabModel |
+ withURL:url |
+ atIndex:tabIndex |
+ transition:transition]; |
+ } |
+ } else { |
+ if (!self.currentBVC.presentedViewController) { |
+ [targetBVC expectNewForegroundTab]; |
+ } |
+ self.currentBVC = targetBVC; |
+ tab = [self openOrReuseTabInMode:targetMode |
+ withURL:url |
+ transition:transition]; |
+ } |
+ |
+ if ([_startupParameters launchVoiceSearch]) { |
+ if (_tabSwitcherIsActive || _dismissingStackView) { |
+ // Since VoiceSearch is presented by the BVC, it must be started after the |
+ // Tab Switcher dismissal completes and the BVC's view is in the |
+ // hiararchy. |
+ _startVoiceSearchAfterTabSwitcherDismissal = YES; |
+ } else { |
+ // When starting the application from the Notification center, |
+ // ApplicationWillResignActive is sent just after startup. |
+ // If the voice search is triggered synchronously, it is immediately |
+ // dismissed. Start it asynchronously instead. |
+ dispatch_async(dispatch_get_main_queue(), ^{ |
+ [self startVoiceSearch]; |
+ }); |
+ } |
+ } else if ([_startupParameters launchQRScanner]) { |
+ if (_tabSwitcherIsActive || _dismissingStackView) { |
+ // QR Scanner is presented by the BVC, similarly to VoiceSearch. It must |
+ // also be started after the BVC's view is in the hierarchy. |
+ [self setStartQRScannerAfterTabSwitcherDismissal:YES]; |
+ } else { |
+ // Start the QR Scanner asynchronously to prevent the application from |
+ // dismissing the modal view if QR Scanner is started from the |
+ // Notification center. |
+ dispatch_async(dispatch_get_main_queue(), ^{ |
+ [self.currentBVC showQRScanner]; |
+ }); |
+ } |
+ } |
+ |
+ if (_restoreHelper) { |
+ // Now that all the operations on the tabs have been done, display the |
+ // restore infobar if needed. |
+ dispatch_async(dispatch_get_main_queue(), ^{ |
+ [_restoreHelper showRestoreIfNeeded:[self currentTabModel]]; |
+ _restoreHelper.reset(); |
+ }); |
+ } |
+ |
+ return tab; |
+} |
+ |
+- (void)dismissModalDialogsWithCompletion:(ProceduralBlock)completion { |
+ // Immediately hide modals from the provider (alert views, action sheets, |
+ // popovers). They will be ultimately dismissed by their owners, but at least, |
+ // they are not visible. |
+ ios::GetChromeBrowserProvider()->HideModalViewStack(); |
+ |
+ // ChromeIdentityService is responsible for the dialogs displayed by the |
+ // services it wraps. |
+ ios::GetChromeBrowserProvider()->GetChromeIdentityService()->DismissDialogs(); |
+ |
+ // Cancel interaction with SSO. |
+ // First, cancel the signin interaction. |
+ [_signinInteractionController cancel]; |
+ |
+ // Then, depending on what the SSO view controller is presented on, dismiss |
+ // it. |
+ ProceduralBlock completionWithBVC = ^{ |
+ // This will dismiss the SSO view controller. |
+ [self.currentBVC clearPresentedStateWithCompletion:completion]; |
+ }; |
+ ProceduralBlock completionWithoutBVC = ^{ |
+ // This will dismiss the SSO view controller. |
+ [self dismissSigninInteractionController]; |
+ if (completion) |
+ completion(); |
+ }; |
+ |
+ // As a top level rule, if the settings are showing, they need to be |
+ // dismissed. Then, based on whether the BVC is present or not, a different |
+ // completion callback is called. |
+ if (self.currentBVC && _settingsNavigationController) { |
+ // In this case, the settings are up and the BVC is showing. Close the |
+ // settings then call the BVC completion. |
+ [self closeSettingsAnimated:NO completion:completionWithBVC]; |
+ } else if (_settingsNavigationController) { |
+ // In this case, the settings are up but the BVC is not showing. Close the |
+ // settings then call the no-BVC completion. |
+ [self closeSettingsAnimated:NO completion:completionWithoutBVC]; |
+ } else if (self.currentBVC) { |
+ // In this case, the settings are not shown but the BVC is showing. Call the |
+ // BVC completion. |
+ completionWithBVC(); |
+ } else { |
+ // In this case, neither the settings nor the BVC are shown. Call the no-BVC |
+ // completion. |
+ completionWithoutBVC(); |
+ } |
+ |
+ // Verify that no modal views are left presented. |
+ ios::GetChromeBrowserProvider()->LogIfModalViewsArePresented(); |
+} |
+ |
+// iOS does not guarantee the order in which the observers are notified by |
+// the notification center. There are different parts of the application that |
+// register for UIApplication notifications so recording them in order to |
+// measure the performance of the app being moved to the foreground / background |
+// is not reliable. Instead we prefer using designated notifications that are |
+// posted to the observers on the first available run loop cycle, which |
+// guarantees that they are delivered to the observer only after UIApplication |
+// notifications have been treated. |
+- (void)postNotificationOnNextRunLoopCycle:(NSString*)notificationName { |
+ dispatch_async(dispatch_get_main_queue(), ^{ |
+ [[NSNotificationCenter defaultCenter] postNotificationName:notificationName |
+ object:self]; |
+ }); |
+} |
+ |
+- (bool)mustShowRestoreInfobar { |
+ if ([self isFirstLaunchAfterUpgrade]) |
+ return false; |
+ return !GetApplicationContext()->WasLastShutdownClean(); |
+} |
+ |
+- (NSMutableSet*)liveSessionsForTabModel:(TabModel*)tabModel { |
+ NSMutableSet* result = [NSMutableSet setWithCapacity:[tabModel count]]; |
+ for (size_t i = 0; i < [tabModel count]; ++i) |
+ [result addObject:[[tabModel tabAtIndex:i] currentSessionID]]; |
+ return result; |
+} |
+ |
+- (void)purgeSnapshots { |
+ NSMutableSet* liveSessions = [self liveSessionsForTabModel:self.mainTabModel]; |
+ [liveSessions unionSet:[self liveSessionsForTabModel:self.otrTabModel]]; |
+ // Keep snapshots that are less than one minute old, to prevent a concurrency |
+ // issue if they are created while the purge is running. |
+ [[SnapshotCache sharedInstance] |
+ purgeCacheOlderThan:(base::Time::Now() - base::TimeDelta::FromMinutes(1)) |
+ keeping:liveSessions]; |
+} |
+ |
+- (void)markEulaAsAccepted { |
+ PrefService* prefs = GetApplicationContext()->GetLocalState(); |
+ if (!prefs->GetBoolean(prefs::kEulaAccepted)) |
+ prefs->SetBoolean(prefs::kEulaAccepted, true); |
+ prefs->CommitPendingWrite(); |
+} |
+ |
+#pragma mark - TabOpening implementation. |
+ |
+- (void)dismissModalsAndOpenSelectedTabInMode:(ApplicationMode)targetMode |
+ withURL:(const GURL&)url |
+ transition:(ui::PageTransition)transition |
+ completion:(ProceduralBlock)handler { |
+ GURL copyOfURL = url; |
+ [self dismissModalDialogsWithCompletion:^{ |
+ [self openSelectedTabInMode:targetMode |
+ withURL:copyOfURL |
+ transition:transition]; |
+ if (handler) |
+ handler(); |
+ }]; |
+} |
+ |
+- (void)openTabFromLaunchOptions:(NSDictionary*)launchOptions |
+ startupInformation:(id<StartupInformation>)startupInformation |
+ appState:(AppState*)appState { |
+ if (launchOptions) { |
+ BOOL applicationIsActive = |
+ [[UIApplication sharedApplication] applicationState] == |
+ UIApplicationStateActive; |
+ |
+ [URLOpener handleLaunchOptions:launchOptions |
+ applicationActive:applicationIsActive |
+ tabOpener:self |
+ startupInformation:startupInformation |
+ appState:appState]; |
+ } |
+} |
+ |
+#pragma mark - SettingsNavigationControllerDelegate |
+ |
+- (void)closeSettings { |
+ [self closeSettingsAnimated:YES completion:NULL]; |
+} |
+ |
+// Handle a close settings and open URL command. |
+- (void)closeSettingsAndOpenUrl:(OpenUrlCommand*)command { |
+ [self openUrlFromSettings:command]; |
+} |
+ |
+- (void)closeSettingsAndOpenNewIncognitoTab { |
+ [self closeSettingsAnimated:NO |
+ completion:^{ |
+ [self createNewTabInBVC:self.otrBVC sender:nil]; |
+ }]; |
+} |
+ |
+#pragma mark - UserFeedbackDataSource |
+ |
+- (NSString*)currentPageDisplayURL { |
+ if (_tabSwitcherIsActive) |
+ return nil; |
+ web::WebState* webState = [[[self currentTabModel] currentTab] webState]; |
+ if (!webState) |
+ return nil; |
+ // Returns URL of browser tab that is currently showing. |
+ GURL url = webState->GetVisibleURL(); |
+ base::string16 urlText = url_formatter::FormatUrl(url); |
+ return base::SysUTF16ToNSString(urlText); |
+} |
+ |
+- (UIImage*)currentPageScreenshot { |
+ UIView* lastView = self.mainViewController.view; |
+ DCHECK(lastView); |
+ CGFloat scale = 0.0; |
+ // For screenshots of the Stack View we need to use a scale of 1.0 to avoid |
+ // spending too much time since the Stack View can have lots of subviews. |
+ if (_tabSwitcherIsActive) |
+ scale = 1.0; |
+ return CaptureView(lastView, scale); |
+} |
+ |
+- (BOOL)currentPageIsIncognito { |
+ return [self currentBrowserState]->IsOffTheRecord(); |
+} |
+ |
+- (NSString*)currentPageSyncedUserName { |
+ ios::ChromeBrowserState* browserState = [self currentBrowserState]; |
+ if (browserState->IsOffTheRecord()) |
+ return nil; |
+ SigninManager* signin_manager = |
+ ios::SigninManagerFactory::GetForBrowserState(browserState); |
+ std::string username = signin_manager->GetAuthenticatedAccountInfo().email; |
+ return username.empty() ? nil : base::SysUTF8ToNSString(username); |
+} |
+ |
+#pragma mark - UI Automation Testing |
+ |
+- (void)setUpCurrentBVCForTesting { |
+ // Notify that the set up will close all tabs. |
+ if (!IsIPadIdiom()) { |
+ [[NSNotificationCenter defaultCenter] |
+ postNotificationName:kSetupForTestingWillCloseAllTabsNotification |
+ object:self]; |
+ } |
+ |
+ [self.otrTabModel closeAllTabs]; |
+ [self.mainTabModel closeAllTabs]; |
+} |
+ |
+@end |
+ |
+#pragma mark - TestingOnly |
+ |
+@implementation MainController (TestingOnly) |
+ |
+- (DeviceSharingManager*)deviceSharingManager { |
+ return [_browserViewWrangler deviceSharingManager]; |
+} |
+ |
+- (UIViewController<TabSwitcher>*)tabSwitcherController { |
+ return _tabSwitcherController.get(); |
+} |
+ |
+- (void)setTabSwitcherController:(UIViewController<TabSwitcher>*)controller { |
+ _tabSwitcherController.reset([controller retain]); |
+} |
+ |
+- (SigninInteractionController*)signinInteractionController { |
+ return _signinInteractionController.get(); |
+} |
+ |
+- (UIViewController*)topPresentedViewController { |
+ return top_view_controller::TopPresentedViewControllerFrom( |
+ self.mainViewController); |
+} |
+ |
+- (void)setTabSwitcherActive:(BOOL)active { |
+ _tabSwitcherIsActive = active; |
+} |
+ |
+- (BOOL)dismissingTabSwitcher { |
+ return _dismissingStackView; |
+} |
+ |
+- (void)setStartupParametersWithURL:(const GURL&)launchURL { |
+ NSString* sourceApplication = @"Fake App"; |
+ _startupParameters.reset([[ChromeAppStartupParameters |
+ newChromeAppStartupParametersWithURL:net::NSURLWithGURL(launchURL) |
+ fromSourceApplication:sourceApplication] retain]); |
+} |
+ |
+- (void)setUpAsForegrounded { |
+ _isColdStart = NO; |
+ _browserInitializationStage = INITIALIZATION_STAGE_FOREGROUND; |
+ // Create a BrowserViewWrangler with a null browser state. This will trigger |
+ // assertions if the BrowserViewWrangler is asked to create any BVC or |
+ // tabModel objects, but it will accept assignments to them. |
+ _browserViewWrangler.reset([[BrowserViewWrangler alloc] |
+ initWithBrowserState:nullptr |
+ tabModelObserver:self]); |
+ // This is a test utility method that bypasses the ususal setup steps, so |
+ // verify that the main coordinator hasn't been created yet, then start it |
+ // via lazy initialization. |
+ DCHECK(!_mainCoordinator); |
+ [self.mainCoordinator start]; |
+} |
+ |
+- (void)setUpForTestingWithCompletionHandler: |
+ (ProceduralBlock)completionHandler { |
+ self.currentBVC = self.mainBVC; |
+ |
+ int removeAllMask = ~0; |
+ scoped_refptr<CallbackCounter> callbackCounter = |
+ new CallbackCounter(base::BindBlock(^{ |
+ [self setUpCurrentBVCForTesting]; |
+ if (completionHandler) { |
+ completionHandler(); |
+ } |
+ })); |
+ id decrementCallbackCounterCount = ^{ |
+ callbackCounter->DecrementCount(); |
+ }; |
+ |
+ callbackCounter->IncrementCount(); |
+ [self removeBrowsingDataFromBrowserState:_mainBrowserState |
+ mask:removeAllMask |
+ timePeriod:browsing_data::ALL_TIME |
+ completionHandler:decrementCallbackCounterCount]; |
+} |
+ |
+@end |