Upstream Chrome on iOS source code [1/11].

Upstream part of Chrome on iOS source code. Nothing is built yet,
just new files added. The files will be added to the build as part
of the last CL to avoid breaking the downstream tree.

BUG=653086

Review-Url: https://codereview.chromium.org/2580363002
Cr-Commit-Position: refs/heads/master@{#439455}
diff --git a/ios/chrome/app/README.md b/ios/chrome/app/README.md
new file mode 100644
index 0000000..f28e8c4
--- /dev/null
+++ b/ios/chrome/app/README.md
@@ -0,0 +1,13 @@
+# App
+
+-----
+**Some of the files in this directory are only used in the new iOS Chrome
+architecture:**
+
+* `app_delegate.h` and `.mm`
+* `application_phase.h`
+* `application_state.h` and `.mm`
+* `application_step.h`
+* `main.mm`
+
+-----
diff --git a/ios/chrome/app/app_delegate.h b/ios/chrome/app/app_delegate.h
new file mode 100644
index 0000000..8990eccd
--- /dev/null
+++ b/ios/chrome/app/app_delegate.h
@@ -0,0 +1,27 @@
+// Copyright 2016 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.
+
+// ======                        New Architecture                         =====
+// =         This code is only used in the new iOS Chrome architecture.       =
+// ============================================================================
+
+#ifndef IOS_CHROME_APP_APP_DELEGATE_H_
+#define IOS_CHROME_APP_APP_DELEGATE_H_
+
+#import <UIKit/UIKit.h>
+
+// The main delegate of the application.
+// This class is intended solely to handle the numerous UIApplicationDelegate
+// methods and route to other application objects as appropriate. While this
+// object does create and own other objects, including the ApplicationState
+// object, it shouldn't perform significant manipulation or configuration of
+// them. Nor should any of the method implementations in this class contain
+// significant logic or any specific feature support; that should all be
+// implemented in subsidiary objects this class makes use of.
+@interface AppDelegate : NSObject<UIApplicationDelegate>
+// Do not add any public API to this class. Other code in Chrome shouldn't
+// need to access instances of this class for any purpose.
+@end
+
+#endif  // IOS_CHROME_APP_APP_DELEGATE_H_
diff --git a/ios/chrome/app/app_delegate.mm b/ios/chrome/app/app_delegate.mm
new file mode 100644
index 0000000..2f2b3aa2
--- /dev/null
+++ b/ios/chrome/app/app_delegate.mm
@@ -0,0 +1,121 @@
+// Copyright 2016 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.
+
+// ======                        New Architecture                         =====
+// =         This code is only used in the new iOS Chrome architecture.       =
+// ============================================================================
+
+#import "ios/chrome/app/app_delegate.h"
+
+#import "ios/chrome/app/application_state.h"
+#import "ios/chrome/app/steps/launch_to_background.h"
+#import "ios/chrome/app/steps/launch_to_basic.h"
+#import "ios/chrome/app/steps/launch_to_foreground.h"
+#import "ios/chrome/app/steps/tab_grid_coordinator+application_step.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@interface AppDelegate ()
+@property(nonatomic, strong) ApplicationState* applicationState;
+@end
+
+@implementation AppDelegate
+
+@synthesize applicationState = _applicationState;
+
+#pragma mark - UIApplicationDelegate (app state changes and system events)
+
+- (BOOL)application:(UIApplication*)application
+    didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
+  self.applicationState = [[ApplicationState alloc] init];
+  self.applicationState.application = application;
+  [self configureApplicationState];
+
+  [self.applicationState launchWithOptions:launchOptions];
+  return YES;
+}
+
+- (void)applicationDidBecomeActive:(UIApplication*)application {
+}
+
+- (void)applicationWillResignActive:(UIApplication*)application {
+}
+
+- (void)applicationDidEnterBackground:(UIApplication*)application {
+}
+
+- (void)applicationWillEnterForeground:(UIApplication*)application {
+}
+
+- (void)applicationWillTerminate:(UIApplication*)application {
+}
+
+- (void)applicationDidReceiveMemoryWarning:(UIApplication*)application {
+}
+
+#pragma mark - UIApplicationDelegate (background dowloading)
+
+- (void)application:(UIApplication*)application
+    performFetchWithCompletionHandler:
+        (void (^)(UIBackgroundFetchResult))completionHandler {
+}
+
+- (void)application:(UIApplication*)application
+    handleEventsForBackgroundURLSession:(NSString*)identifier
+                      completionHandler:(void (^)())completionHandler {
+}
+
+#pragma mark - UIApplicationDelegate (user activity and quick actions)
+
+- (BOOL)application:(UIApplication*)application
+    willContinueUserActivityWithType:(NSString*)userActivityType {
+  return NO;
+}
+
+- (BOOL)application:(UIApplication*)application
+    continueUserActivity:(NSUserActivity*)userActivity
+      restorationHandler:(void (^)(NSArray*))restorationHandler {
+  return NO;
+}
+
+- (void)application:(UIApplication*)application
+    performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
+               completionHandler:(void (^)(BOOL))completionHandler {
+}
+
+#pragma mark - UIApplicationDelegate (opening URL-specified resources)
+
+- (BOOL)application:(UIApplication*)application
+            openURL:(NSURL*)url
+            options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
+  [self.applicationState.URLOpener openURL:url];
+  return YES;
+}
+
+#pragma mark - Private methods
+
+// Configures the application state for application launch by setting the launch
+// steps.
+// Future architecture/refactoring note: configuring the application state in
+// this way is outside the scope of responsibility of the object as defined in
+// the header file. The correct solution is probably a helper object that can
+// perform all of the configuration necessary, and that can be adjusted as
+// needed.
+- (void)configureApplicationState {
+  [self.applicationState.launchSteps addObjectsFromArray:@[
+    [[ProviderInitializer alloc] init],
+    [[SetupBundleAndUserDefaults alloc] init],
+    [[StartChromeMain alloc] init],
+    [[SetBrowserState alloc] init],
+    [[BeginForegrounding alloc] init],
+    [[BrowserStateInitializer alloc] init],
+    [[PrepareForUI alloc] init],
+    [[CompleteForegrounding alloc] init],
+    [[TabGridCoordinator alloc] init],
+  ]];
+}
+
+@end
diff --git a/ios/chrome/app/application_delegate/app_navigation.h b/ios/chrome/app/application_delegate/app_navigation.h
new file mode 100644
index 0000000..321f1a1
--- /dev/null
+++ b/ios/chrome/app/application_delegate/app_navigation.h
@@ -0,0 +1,37 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_APPLICATION_DELEGATE_APP_NAVIGATION_H_
+#define IOS_CHROME_APP_APPLICATION_DELEGATE_APP_NAVIGATION_H_
+
+#import <Foundation/Foundation.h>
+
+#include "base/ios/block_types.h"
+
+namespace ios {
+class ChromeBrowserState;
+}  // namespace ios
+
+@class SettingsNavigationController;
+
+// Handles the navigation through the application.
+@protocol AppNavigation<NSObject>
+
+// Navigation View controller for the settings.
+@property(nonatomic, retain)
+    SettingsNavigationController* settingsNavigationController;
+
+// Presents a SignedInAccountsViewController for |browserState| on the top view
+// controller.
+- (void)presentSignedInAccountsViewControllerForBrowserState:
+    (ios::ChromeBrowserState*)browserState;
+
+// Closes the settings UI with or without animation, with an optional
+// completion block.
+- (void)closeSettingsAnimated:(BOOL)animated
+                   completion:(ProceduralBlock)completion;
+
+@end
+
+#endif  // IOS_CHROME_APP_APPLICATION_DELEGATE_APP_NAVIGATION_H_
diff --git a/ios/chrome/app/application_delegate/app_state.h b/ios/chrome/app/application_delegate/app_state.h
new file mode 100644
index 0000000..52aff1d
--- /dev/null
+++ b/ios/chrome/app/application_delegate/app_state.h
@@ -0,0 +1,91 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_APPLICATION_DELEGATE_APP_STATE_H_
+#define IOS_CHROME_APP_APPLICATION_DELEGATE_APP_STATE_H_
+
+#import <UIKit/UIKit.h>
+
+@protocol AppNavigation;
+@protocol BrowserLauncher;
+@protocol BrowserViewInformation;
+@protocol TabOpening;
+@protocol TabSwitching;
+@protocol StartupInformation;
+@class DeviceSharingManager;
+@class MainApplicationDelegate;
+@class MemoryWarningHelper;
+@class MetricsMediator;
+@class TabModel;
+
+// Represents the application state and responds to application state changes
+// and system events.
+@interface AppState : NSObject
+
+- (instancetype)init NS_UNAVAILABLE;
+
+- (instancetype)
+initWithBrowserLauncher:(id<BrowserLauncher>)browserLauncher
+     startupInformation:(id<StartupInformation>)startupInformation
+    applicationDelegate:(MainApplicationDelegate*)applicationDelegate
+    NS_DESIGNATED_INITIALIZER;
+
+// YES if the user has ever interacted with the application. May be NO if the
+// application has been woken up by the system for background work.
+@property(nonatomic, readonly) BOOL userInteracted;
+
+// Window for the application, it is not set during the initialization method.
+// Set the property before calling methods related to it.
+@property(nonatomic, assign) UIWindow* window;
+
+// Saves the launchOptions to be used from -newTabFromLaunchOptions. If the
+// application is in background, initialize the browser to basic. If not, launch
+// the browser.
+// Returns whether additional delegate handling should be performed (call to
+// -performActionForShortcutItem or -openURL by the system for example)
+- (BOOL)requiresHandlingAfterLaunchWithOptions:(NSDictionary*)launchOptions
+                               stateBackground:(BOOL)stateBackground;
+
+// Whether the application is in Safe Mode.
+- (BOOL)isInSafeMode;
+
+// Logs duration of the session in the main tab model and records that chrome is
+// no longer in cold start.
+- (void)willResignActiveTabModel;
+
+// Called when the application is getting terminated. It stops all outgoing
+// requests, config updates, clears the device sharing manager and stops the
+// mainChrome instance.
+- (void)applicationWillTerminate:(UIApplication*)application
+           applicationNavigation:(id<AppNavigation>)appNavigation;
+
+// Resumes the session: reinitializing metrics and opening new tab if necessary.
+// User sessions are defined in terms of BecomeActive/ResignActive so that
+// session boundaries include things like turning the screen off or getting a
+// phone call, not just switching apps.
+- (void)resumeSessionWithTabOpener:(id<TabOpening>)tabOpener
+                       tabSwitcher:(id<TabSwitching>)tabSwitcher;
+
+// Called when going into the background. iOS already broadcasts, so
+// stakeholders can register for it directly.
+- (void)applicationDidEnterBackground:(UIApplication*)application
+                         memoryHelper:(MemoryWarningHelper*)memoryHelper
+                  tabSwitcherIsActive:(BOOL)tabSwitcherIsActive;
+
+// Called when returning to the foreground. Resets and uploads the metrics.
+// Starts the browser to foreground if needed.
+- (void)applicationWillEnterForeground:(UIApplication*)application
+                       metricsMediator:(MetricsMediator*)metricsMediator
+                          memoryHelper:(MemoryWarningHelper*)memoryHelper
+                             tabOpener:(id<TabOpening>)tabOpener
+                         appNavigation:(id<AppNavigation>)appNavigation;
+
+// Sets the return value for -didFinishLaunchingWithOptions that determines if
+// UIKit should make followup delegate calls such as
+// -performActionForShortcutItem or -openURL.
+- (void)launchFromURLHandled:(BOOL)URLHandled;
+
+@end
+
+#endif  // IOS_CHROME_APP_APPLICATION_DELEGATE_APP_STATE_H_
diff --git a/ios/chrome/app/application_delegate/app_state.mm b/ios/chrome/app/application_delegate/app_state.mm
new file mode 100644
index 0000000..593da25
--- /dev/null
+++ b/ios/chrome/app/application_delegate/app_state.mm
@@ -0,0 +1,499 @@
+// Copyright 2016 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/application_delegate/app_state.h"
+
+#include "base/critical_closure.h"
+#import "base/mac/bind_objc_block.h"
+#import "base/mac/scoped_nsobject.h"
+#include "components/metrics/metrics_service.h"
+#import "ios/chrome/app/application_delegate/app_navigation.h"
+#import "ios/chrome/app/application_delegate/browser_launcher.h"
+#import "ios/chrome/app/application_delegate/memory_warning_helper.h"
+#import "ios/chrome/app/application_delegate/metrics_mediator.h"
+#import "ios/chrome/app/application_delegate/startup_information.h"
+#import "ios/chrome/app/application_delegate/tab_opening.h"
+#import "ios/chrome/app/application_delegate/tab_switching.h"
+#import "ios/chrome/app/application_delegate/user_activity_handler.h"
+#import "ios/chrome/app/deferred_initialization_runner.h"
+#import "ios/chrome/app/safe_mode/safe_mode_coordinator.h"
+#import "ios/chrome/app/safe_mode_crashing_modules_config.h"
+#include "ios/chrome/browser/application_context.h"
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#include "ios/chrome/browser/chrome_constants.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/device_sharing/device_sharing_manager.h"
+#import "ios/chrome/browser/geolocation/omnibox_geolocation_config.h"
+#import "ios/chrome/browser/metrics/previous_session_info.h"
+#import "ios/chrome/browser/ui/authentication/signed_in_accounts_view_controller.h"
+#include "ios/chrome/browser/ui/background_generator.h"
+#import "ios/chrome/browser/ui/browser_view_controller.h"
+#import "ios/chrome/browser/ui/main/browser_view_information.h"
+#include "ios/net/cookies/cookie_store_ios.h"
+#include "ios/net/cookies/system_cookie_util.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/user_feedback/user_feedback_provider.h"
+#include "ios/web/net/request_tracker_impl.h"
+#include "net/url_request/url_request_context.h"
+
+namespace {
+// Helper method to post |closure| on the UI thread.
+void PostTaskOnUIThread(const base::Closure& closure) {
+  web::WebThread::PostTask(web::WebThread::UI, FROM_HERE, closure);
+}
+NSString* const kStartupAttemptReset = @"StartupAttempReset";
+}  // namespace
+
+@interface AppState ()<SafeModeCoordinatorDelegate> {
+  // Container for startup information.
+  base::WeakNSProtocol<id<StartupInformation>> _startupInformation;
+  // Browser launcher to launch browser in different states.
+  base::WeakNSProtocol<id<BrowserLauncher>> _browserLauncher;
+  // UIApplicationDelegate for the application.
+  base::WeakNSObject<MainApplicationDelegate> _mainApplicationDelegate;
+  // Window for the application.
+  base::WeakNSObject<UIWindow> _window;
+
+  // Variables backing properties of same name.
+  base::scoped_nsobject<SafeModeCoordinator> _safeModeCoordinator;
+
+  // Start of the current session, used for UMA.
+  base::TimeTicks _sessionStartTime;
+  // YES if the app is currently in the process of terminating.
+  BOOL _appIsTerminating;
+  // Indicates if an NTP tab should be opened once the application has become
+  // active.
+  BOOL _shouldOpenNTPTabOnActive;
+  // Interstitial view used to block any incognito tabs after backgrounding.
+  base::scoped_nsobject<UIView> _incognitoBlocker;
+  // Whether the application is currently in the background.
+  // This is a workaround for rdar://22392526 where
+  // -applicationDidEnterBackground: can be called twice.
+  // TODO(crbug.com/546196): Remove this once rdar://22392526 is fixed.
+  BOOL _applicationInBackground;
+  // YES if cookies are currently being flushed to disk.
+  BOOL _savingCookies;
+}
+
+// Safe mode coordinator. If this is non-nil, the app is displaying the safe
+// mode UI.
+@property(nonatomic, retain) SafeModeCoordinator* safeModeCoordinator;
+
+// Return value for -requiresHandlingAfterLaunchWithOptions that determines if
+// UIKit should make followup delegate calls such as
+// -performActionForShortcutItem or -openURL.
+@property(nonatomic, assign) BOOL shouldPerformAdditionalDelegateHandling;
+
+// This method is the first to be called when user launches the application.
+// Depending on the background tasks history, the state of the application is
+// either INITIALIZATION_STAGE_BASIC or INITIALIZATION_STAGE_BACKGROUND so this
+// step cannot be included in the |startUpBrowserToStage:| method.
+- (void)initializeUI;
+// Saves the current launch details to user defaults.
+- (void)saveLaunchDetailsToDefaults;
+
+@end
+
+@implementation AppState
+
+@synthesize shouldPerformAdditionalDelegateHandling =
+    _shouldPerformAdditionalDelegateHandling;
+@synthesize userInteracted = _userInteracted;
+
+- (instancetype)init {
+  NOTREACHED();
+  return nil;
+}
+
+- (instancetype)
+initWithBrowserLauncher:(id<BrowserLauncher>)browserLauncher
+     startupInformation:(id<StartupInformation>)startupInformation
+    applicationDelegate:(MainApplicationDelegate*)applicationDelegate {
+  self = [super init];
+  if (self) {
+    _startupInformation.reset(startupInformation);
+    _browserLauncher.reset(browserLauncher);
+    _mainApplicationDelegate.reset(applicationDelegate);
+  }
+  return self;
+}
+
+#pragma mark - Properties implementation
+
+- (SafeModeCoordinator*)safeModeCoordinator {
+  return _safeModeCoordinator;
+}
+
+- (void)setSafeModeCoordinator:(SafeModeCoordinator*)safeModeCoordinator {
+  _safeModeCoordinator.reset([safeModeCoordinator retain]);
+}
+
+- (void)setWindow:(UIWindow*)window {
+  _window.reset(window);
+}
+
+- (UIWindow*)window {
+  return _window;
+}
+
+#pragma mark - Public methods.
+
+- (void)applicationDidEnterBackground:(UIApplication*)application
+                         memoryHelper:(MemoryWarningHelper*)memoryHelper
+                  tabSwitcherIsActive:(BOOL)tabSwitcherIsActive {
+  if ([self isInSafeMode]) {
+    // Force a crash when backgrounding and in safe mode, so users don't get
+    // stuck in safe mode.
+    breakpad_helper::SetEnabled(false);
+    exit(0);
+    return;
+  }
+
+  if (_applicationInBackground) {
+    return;
+  }
+  _applicationInBackground = YES;
+
+  breakpad_helper::SetCurrentlyInBackground(true);
+
+  if ([_browserLauncher browserInitializationStage] <
+      INITIALIZATION_STAGE_FOREGROUND) {
+    // The clean-up done in |-applicationDidEnterBackground:| is only valid for
+    // the case when the application is started in foreground, so there is
+    // nothing to clean up as the application was not initialized for foregound.
+    //
+    // From the stack trace of the crash bug http://crbug.com/437307 , it
+    // seems that |-applicationDidEnterBackground:| may be called when the app
+    // is started in background and before the initialization for background
+    // stage is done. Note that the crash bug could not be reproduced though.
+    return;
+  }
+
+  [MetricsMediator
+      applicationDidEnterBackground:[memoryHelper
+                                        foregroundMemoryWarningCount]];
+
+  [_startupInformation expireFirstUserActionRecorder];
+
+  // If the current BVC is incognito, or if we are in the tab switcher and there
+  // are incognito tabs visible, place a full screen view containing the
+  // switcher background to hide any incognito content.
+  if (([[_browserLauncher browserViewInformation] currentBrowserState] &&
+       [[_browserLauncher browserViewInformation] currentBrowserState]
+           ->IsOffTheRecord()) ||
+      (tabSwitcherIsActive &&
+       ![[[_browserLauncher browserViewInformation] otrTabModel] isEmpty])) {
+    // Cover the largest area potentially shown in the app switcher, in case the
+    // screenshot is reused in a different orientation or size class.
+    CGRect screenBounds = [[UIScreen mainScreen] bounds];
+    CGFloat maxDimension =
+        std::max(CGRectGetWidth(screenBounds), CGRectGetHeight(screenBounds));
+    _incognitoBlocker.reset([[UIView alloc]
+        initWithFrame:CGRectMake(0, 0, maxDimension, maxDimension)]);
+    InstallBackgroundInView(_incognitoBlocker);
+    [_window addSubview:_incognitoBlocker];
+  }
+
+  // Do not save cookies if it is already in progress.
+  if ([[_browserLauncher browserViewInformation] currentBVC].browserState &&
+      !_savingCookies) {
+    // Save cookies to disk. The empty critical closure guarantees that the task
+    // will be run before backgrounding.
+    scoped_refptr<net::URLRequestContextGetter> getter =
+        [[_browserLauncher browserViewInformation] currentBVC]
+            .browserState->GetRequestContext();
+    _savingCookies = YES;
+    base::Closure criticalClosure = base::MakeCriticalClosure(base::BindBlock(^{
+      DCHECK_CURRENTLY_ON(web::WebThread::UI);
+      _savingCookies = NO;
+    }));
+    web::WebThread::PostTask(
+        web::WebThread::IO, FROM_HERE, base::BindBlock(^{
+          net::CookieStoreIOS* store = static_cast<net::CookieStoreIOS*>(
+              getter->GetURLRequestContext()->cookie_store());
+          // FlushStore() runs its callback on any thread. Jump back to UI.
+          store->FlushStore(base::Bind(&PostTaskOnUIThread, criticalClosure));
+        }));
+  }
+
+  // Mark the startup as clean if it hasn't already been.
+  [[DeferredInitializationRunner sharedInstance]
+      runBlockIfNecessary:kStartupAttemptReset];
+  // Set date/time that the background fetch handler was called in the user
+  // defaults.
+  [MetricsMediator logDateInUserDefaults];
+  // Clear the memory warning flag since the app is now safely in background.
+  [[PreviousSessionInfo sharedInstance] resetMemoryWarningFlag];
+
+  // Turn off uploading of crash reports and metrics, in case the method of
+  // communication changes while in the background.
+  [MetricsMediator disableReporting];
+
+  GetApplicationContext()->OnAppEnterBackground();
+  if (![[CrashReportBackgroundUploader sharedInstance]
+          hasPendingCrashReportsToUploadAtStartup]) {
+    [application setMinimumBackgroundFetchInterval:
+                     UIApplicationBackgroundFetchIntervalNever];
+  }
+}
+
+- (void)applicationWillEnterForeground:(UIApplication*)application
+                       metricsMediator:(MetricsMediator*)metricsMediator
+                          memoryHelper:(MemoryWarningHelper*)memoryHelper
+                             tabOpener:(id<TabOpening>)tabOpener
+                         appNavigation:(id<AppNavigation>)appNavigation {
+  if ([_browserLauncher browserInitializationStage] <
+      INITIALIZATION_STAGE_FOREGROUND) {
+    // The application has been launched in background and the initialization
+    // is not complete.
+    [self initializeUI];
+    return;
+  }
+  if ([self isInSafeMode])
+    return;
+
+  _applicationInBackground = NO;
+
+  [_incognitoBlocker removeFromSuperview];
+  _incognitoBlocker.reset();
+
+  breakpad_helper::SetCurrentlyInBackground(false);
+
+  // Update the state of metrics and crash reporting, as the method of
+  // communication may have changed while the app was in the background.
+  [metricsMediator updateMetricsStateBasedOnPrefsUserTriggered:NO];
+
+  // Send any feedback that might be still on temporary storage.
+  ios::GetChromeBrowserProvider()->GetUserFeedbackProvider()->Synchronize();
+
+  GetApplicationContext()->OnAppEnterForeground();
+
+  [MetricsMediator
+      logLaunchMetricsWithStartupInformation:_startupInformation
+                      browserViewInformation:[_browserLauncher
+                                                 browserViewInformation]];
+  [memoryHelper resetForegroundMemoryWarningCount];
+
+  // Check if a NTP tab should be opened; the tab will actually be opened in
+  // |applicationDidBecomeActive| after the application gets prepared to
+  // record user actions.
+  // TODO(crbug.com/623491): opening a tab when the application is launched
+  // without a tab should not be counted as a user action. Revisit the way tab
+  // creation is counted.
+  _shouldOpenNTPTabOnActive = [tabOpener
+      shouldOpenNTPTabOnActivationOfTabModel:[[_browserLauncher
+                                                 browserViewInformation]
+                                                 currentTabModel]];
+
+  ios::ChromeBrowserState* currentBrowserState =
+      [[_browserLauncher browserViewInformation] currentBrowserState];
+  if ([SignedInAccountsViewController
+          shouldBePresentedForBrowserState:currentBrowserState]) {
+    [appNavigation presentSignedInAccountsViewControllerForBrowserState:
+                       currentBrowserState];
+  }
+
+  // If the current browser state is not OTR, check for cookie loss.
+  if (currentBrowserState && !currentBrowserState->IsOffTheRecord() &&
+      currentBrowserState->GetOriginalChromeBrowserState()
+              ->GetStatePath()
+              .BaseName()
+              .value() == kIOSChromeInitialBrowserState) {
+    NSUInteger cookie_count =
+        [[[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies] count];
+    UMA_HISTOGRAM_COUNTS_10000("CookieIOS.CookieCountOnForegrounding",
+                               cookie_count);
+    net::CheckForCookieLoss(cookie_count,
+                            net::COOKIES_APPLICATION_FOREGROUNDED);
+  }
+}
+
+- (void)resumeSessionWithTabOpener:(id<TabOpening>)tabOpener
+                       tabSwitcher:(id<TabSwitching>)tabSwitcher {
+  [_incognitoBlocker removeFromSuperview];
+  _incognitoBlocker.reset();
+
+  DCHECK([_browserLauncher browserInitializationStage] ==
+         INITIALIZATION_STAGE_FOREGROUND);
+  _sessionStartTime = base::TimeTicks::Now();
+  [[[_browserLauncher browserViewInformation] mainTabModel]
+      resetSessionMetrics];
+
+  if ([_startupInformation startupParameters]) {
+    [UserActivityHandler
+        handleStartupParametersWithTabOpener:tabOpener
+                          startupInformation:_startupInformation
+                      browserViewInformation:[_browserLauncher
+                                                 browserViewInformation]];
+  } else if (_shouldOpenNTPTabOnActive) {
+    if (![tabSwitcher openNewTabFromTabSwitcher]) {
+      [[[_browserLauncher browserViewInformation] currentBVC] newTab:nil];
+    }
+    _shouldOpenNTPTabOnActive = NO;
+  }
+
+  [MetricsMediator logStartupDuration:_startupInformation];
+}
+
+- (void)applicationWillTerminate:(UIApplication*)application
+           applicationNavigation:(id<AppNavigation>)appNavigation {
+  if (_appIsTerminating) {
+    // Previous handling of this method spun the runloop, resulting in
+    // recursive calls; this does not appear to happen with the new shutdown
+    // flow, but this is here to ensure that if it can happen, it gets noticed
+    // and fixed.
+    CHECK(false);
+  }
+  _appIsTerminating = YES;
+
+  // Dismiss any UI that is presented on screen and that is listening for
+  // profile notifications.
+  if ([appNavigation settingsNavigationController])
+    [appNavigation closeSettingsAnimated:NO completion:nil];
+
+  // Clean up the device sharing manager before the main browser state is shut
+  // down.
+  if ([_browserLauncher browserInitializationStage] >=
+      INITIALIZATION_STAGE_FOREGROUND) {
+    [[_browserLauncher browserViewInformation] cleanDeviceSharingManager];
+  }
+
+  // Cancel any in-flight distribution notifications.
+  ios::GetChromeBrowserProvider()
+      ->GetAppDistributionProvider()
+      ->CancelDistributionNotifications();
+
+  // Halt the tabs, so any outstanding requests get cleaned up, without actually
+  // closing the tabs.
+  if ([_browserLauncher browserInitializationStage] >=
+      INITIALIZATION_STAGE_FOREGROUND) {
+    [[_browserLauncher browserViewInformation] haltAllTabs];
+  }
+
+  // TODO(crbug.com/585700): remove this.
+  web::RequestTrackerImpl::BlockUntilTrackersShutdown();
+
+  [_startupInformation stopChromeMain];
+
+  if (![[CrashReportBackgroundUploader sharedInstance]
+          hasPendingCrashReportsToUploadAtStartup]) {
+    [application setMinimumBackgroundFetchInterval:
+                     UIApplicationBackgroundFetchIntervalNever];
+  }
+}
+
+- (void)willResignActiveTabModel {
+  if ([_browserLauncher browserInitializationStage] <
+      INITIALIZATION_STAGE_FOREGROUND) {
+    // If the application did not pass the foreground initialization stage,
+    // there is no active tab model to resign.
+    return;
+  }
+
+  // Set [_startupInformation isColdStart] to NO in anticipation of the next
+  // time the app becomes active.
+  [_startupInformation setIsColdStart:NO];
+
+  base::TimeDelta duration = base::TimeTicks::Now() - _sessionStartTime;
+  UMA_HISTOGRAM_LONG_TIMES("Session.TotalDuration", duration);
+  [[[_browserLauncher browserViewInformation] mainTabModel]
+      recordSessionMetrics];
+}
+
+- (BOOL)requiresHandlingAfterLaunchWithOptions:(NSDictionary*)launchOptions
+                               stateBackground:(BOOL)stateBackground {
+  [_browserLauncher setLaunchOptions:launchOptions];
+  self.shouldPerformAdditionalDelegateHandling = YES;
+
+  [_browserLauncher startUpBrowserToStage:INITIALIZATION_STAGE_BASIC];
+  if (!stateBackground) {
+    [self initializeUI];
+  }
+
+  return self.shouldPerformAdditionalDelegateHandling;
+}
+
+- (BOOL)isInSafeMode {
+  return self.safeModeCoordinator != nil;
+}
+
+- (void)launchFromURLHandled:(BOOL)URLHandled {
+  self.shouldPerformAdditionalDelegateHandling = !URLHandled;
+}
+
+#pragma mark - SafeModeCoordinatorDelegate Implementation
+
+- (void)coordinatorDidExitSafeMode:(nonnull SafeModeCoordinator*)coordinator {
+  self.safeModeCoordinator = nil;
+  [_browserLauncher startUpBrowserToStage:INITIALIZATION_STAGE_FOREGROUND];
+  [_mainApplicationDelegate
+      applicationDidBecomeActive:[UIApplication sharedApplication]];
+}
+
+#pragma mark - Internal methods.
+
+- (void)initializeUI {
+  _userInteracted = YES;
+  [self saveLaunchDetailsToDefaults];
+
+  DCHECK([_window rootViewController] == nil);
+  if ([SafeModeCoordinator shouldStart]) {
+    SafeModeCoordinator* safeModeCoordinator =
+        [[[SafeModeCoordinator alloc] initWithWindow:_window] autorelease];
+
+    self.safeModeCoordinator = safeModeCoordinator;
+    [self.safeModeCoordinator setDelegate:self];
+
+    // Activate the main window, which will prompt the views to load.
+    [_window makeKeyAndVisible];
+
+    [self.safeModeCoordinator start];
+    return;
+  }
+
+  // Don't add code here. Add it in MainController's
+  // -startUpBrowserForegroundInitialization.
+  DCHECK([_startupInformation isColdStart]);
+  [_browserLauncher startUpBrowserToStage:INITIALIZATION_STAGE_FOREGROUND];
+}
+
+- (void)saveLaunchDetailsToDefaults {
+  // Reset the failure count on first launch, increment it on other launches.
+  if ([[PreviousSessionInfo sharedInstance] isFirstSessionAfterUpgrade])
+    crash_util::ResetFailedStartupAttemptCount();
+  else
+    crash_util::IncrementFailedStartupAttemptCount(false);
+
+  // The startup failure count *must* be synchronized now, since the crashes it
+  // is trying to count are during startup.
+  // -[PreviousSessionInfo beginRecordingCurrentSession] calls |synchronize| on
+  // the user defaults, so leverage that to prevent calling it twice.
+
+  // Start recording info about this session.
+  [[PreviousSessionInfo sharedInstance] beginRecordingCurrentSession];
+}
+
+@end
+
+@implementation AppState (Testing)
+
+- (instancetype)
+initWithBrowserLauncher:(id<BrowserLauncher>)browserLauncher
+     startupInformation:(id<StartupInformation>)startupInformation
+    applicationDelegate:(MainApplicationDelegate*)applicationDelegate
+                 window:(UIWindow*)window
+          shouldOpenNTP:(BOOL)shouldOpenNTP {
+  self = [self initWithBrowserLauncher:browserLauncher
+                    startupInformation:startupInformation
+                   applicationDelegate:applicationDelegate];
+  if (self) {
+    _shouldOpenNTPTabOnActive = shouldOpenNTP;
+  }
+  return self;
+}
+
+@end
diff --git a/ios/chrome/app/application_delegate/app_state_testing.h b/ios/chrome/app/application_delegate/app_state_testing.h
new file mode 100644
index 0000000..2e51472
--- /dev/null
+++ b/ios/chrome/app/application_delegate/app_state_testing.h
@@ -0,0 +1,28 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_APPLICATION_DELEGATE_APP_STATE_TESTING_H_
+#define IOS_CHROME_APP_APPLICATION_DELEGATE_APP_STATE_TESTING_H_
+
+#import "ios/chrome/app/application_delegate/app_state.h"
+
+@class SafeModeCoordinator;
+
+// Exposes methods for testing.
+@interface AppState (Testing)
+
+@property(nonatomic, retain) SafeModeCoordinator* safeModeCoordinator;
+
+- (instancetype)
+initWithBrowserLauncher:(id<BrowserLauncher>)browserLauncher
+     startupInformation:(id<StartupInformation>)startupInformation
+    applicationDelegate:(MainApplicationDelegate*)applicationDelegate
+                 window:(UIWindow*)window
+          shouldOpenNTP:(BOOL)shouldOpenNTP;
+
+- (void)disableReporting;
+
+@end
+
+#endif  // IOS_CHROME_APP_APPLICATION_DELEGATE_APP_STATE_TESTING_H_
diff --git a/ios/chrome/app/application_delegate/app_state_unittest.mm b/ios/chrome/app/application_delegate/app_state_unittest.mm
new file mode 100644
index 0000000..f04cc58
--- /dev/null
+++ b/ios/chrome/app/application_delegate/app_state_unittest.mm
@@ -0,0 +1,933 @@
+// Copyright 2016 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/application_delegate/app_state.h"
+
+#import <QuartzCore/QuartzCore.h>
+
+#include "base/ios/block_types.h"
+#include "base/mac/scoped_block.h"
+#import "base/mac/scoped_nsobject.h"
+#include "base/memory/ptr_util.h"
+#include "base/synchronization/lock.h"
+#import "ios/chrome/app/application_delegate/app_navigation.h"
+#import "ios/chrome/app/application_delegate/app_state_testing.h"
+#import "ios/chrome/app/application_delegate/browser_launcher.h"
+#import "ios/chrome/app/application_delegate/fake_startup_information.h"
+#import "ios/chrome/app/application_delegate/memory_warning_helper.h"
+#import "ios/chrome/app/application_delegate/metrics_mediator.h"
+#import "ios/chrome/app/application_delegate/mock_tab_opener.h"
+#import "ios/chrome/app/application_delegate/startup_information.h"
+#import "ios/chrome/app/application_delegate/tab_switching.h"
+#import "ios/chrome/app/application_delegate/user_activity_handler.h"
+#import "ios/chrome/app/main_application_delegate.h"
+#import "ios/chrome/app/safe_mode/safe_mode_coordinator.h"
+#import "ios/chrome/app/safe_mode_crashing_modules_config.h"
+#import "ios/chrome/browser/app_startup_parameters.h"
+#include "ios/chrome/browser/chrome_url_constants.h"
+#import "ios/chrome/browser/device_sharing/device_sharing_manager.h"
+#import "ios/chrome/browser/geolocation/omnibox_geolocation_config.h"
+#import "ios/chrome/browser/tabs/tab_model.h"
+#import "ios/chrome/browser/ui/browser_view_controller.h"
+#import "ios/chrome/browser/ui/main/browser_view_information.h"
+#import "ios/chrome/browser/ui/settings/settings_navigation_controller.h"
+#import "ios/chrome/test/base/scoped_block_swizzler.h"
+#include "ios/chrome/test/ios_chrome_scoped_testing_chrome_browser_provider.h"
+#include "ios/public/provider/chrome/browser/distribution/app_distribution_provider.h"
+#include "ios/public/provider/chrome/browser/test_chrome_browser_provider.h"
+#include "ios/public/provider/chrome/browser/user_feedback/test_user_feedback_provider.h"
+#import "ios/testing/ocmock_complex_type_helper.h"
+#include "ios/web/net/request_tracker_impl.h"
+#import "ios/web/public/test/test_web_thread_bundle.h"
+#include "testing/platform_test.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
+#include "third_party/ocmock/gtest_support.h"
+
+#pragma mark - Class definition.
+
+namespace {
+
+// A block that takes self as argument and return a BOOL.
+typedef BOOL (^DecisionBlock)(id self);
+// A block that takes the arguments of UserActivityHandler's
+// +handleStartupParametersWithTabOpener.
+typedef void (^HandleStartupParam)(
+    id self,
+    id<TabOpening> tabOpener,
+    id<StartupInformation> startupInformation,
+    id<BrowserViewInformation> browserViewInformation);
+
+class FakeAppDistributionProvider : public AppDistributionProvider {
+ public:
+  FakeAppDistributionProvider() : cancel_called_(false) {}
+  ~FakeAppDistributionProvider() override {}
+
+  void CancelDistributionNotifications() override { cancel_called_ = true; }
+  bool cancel_called() { return cancel_called_; }
+
+ private:
+  bool cancel_called_;
+  DISALLOW_COPY_AND_ASSIGN(FakeAppDistributionProvider);
+};
+
+class FakeUserFeedbackProvider : public TestUserFeedbackProvider {
+ public:
+  FakeUserFeedbackProvider() : synchronize_called_(false) {}
+  ~FakeUserFeedbackProvider() override {}
+  void Synchronize() override { synchronize_called_ = true; }
+  bool synchronize_called() { return synchronize_called_; }
+
+ private:
+  bool synchronize_called_;
+  DISALLOW_COPY_AND_ASSIGN(FakeUserFeedbackProvider);
+};
+
+class FakeChromeBrowserProvider : public ios::TestChromeBrowserProvider {
+ public:
+  FakeChromeBrowserProvider()
+      : app_distribution_provider_(
+            base::MakeUnique<FakeAppDistributionProvider>()),
+        user_feedback_provider_(base::MakeUnique<FakeUserFeedbackProvider>()) {}
+  ~FakeChromeBrowserProvider() override {}
+
+  AppDistributionProvider* GetAppDistributionProvider() const override {
+    return app_distribution_provider_.get();
+  }
+
+  UserFeedbackProvider* GetUserFeedbackProvider() const override {
+    return user_feedback_provider_.get();
+  }
+
+ private:
+  std::unique_ptr<FakeAppDistributionProvider> app_distribution_provider_;
+  std::unique_ptr<FakeUserFeedbackProvider> user_feedback_provider_;
+  DISALLOW_COPY_AND_ASSIGN(FakeChromeBrowserProvider);
+};
+
+}  // namespace
+
+class AppStateTest : public PlatformTest {
+ protected:
+  AppStateTest() {
+    browser_launcher_mock_ =
+        [OCMockObject mockForProtocol:@protocol(BrowserLauncher)];
+    startup_information_mock_ =
+        [OCMockObject mockForProtocol:@protocol(StartupInformation)];
+    main_application_delegate_ =
+        [OCMockObject mockForClass:[MainApplicationDelegate class]];
+    window_ = [OCMockObject mockForClass:[UIWindow class]];
+    browser_view_information_ =
+        [OCMockObject mockForProtocol:@protocol(BrowserViewInformation)];
+  }
+
+  void initializeIncognitoBlocker(UIWindow* window) {
+    id application = [OCMockObject niceMockForClass:[UIApplication class]];
+    id memoryHelper = [OCMockObject mockForClass:[MemoryWarningHelper class]];
+    id browserViewInformation =
+        [OCMockObject mockForProtocol:@protocol(BrowserViewInformation)];
+    id tabModel = [OCMockObject mockForClass:[TabModel class]];
+
+    [[startup_information_mock_ stub] expireFirstUserActionRecorder];
+    [[[memoryHelper stub] andReturnValue:@0] foregroundMemoryWarningCount];
+    [[[tabModel stub] andReturnValue:@NO] isEmpty];
+    [[[browser_view_information_ stub] andReturn:tabModel] otrTabModel];
+    stubNullCurrentBrowserState(browserViewInformation);
+    [[[browser_view_information_ stub] andReturn:nil] currentBVC];
+
+    swizzleMetricsMediatorDisableReporting();
+
+    [app_state_ applicationDidEnterBackground:application
+                                 memoryHelper:memoryHelper
+                          tabSwitcherIsActive:YES];
+
+    metrics_mediator_called_ = NO;
+  }
+
+  void stubNullCurrentBrowserState(id browserViewInformation) {
+    [[[browserViewInformation stub] andDo:^(NSInvocation* invocation) {
+      ios::ChromeBrowserState* browserState = nullptr;
+      [invocation setReturnValue:&browserState];
+    }] currentBrowserState];
+  }
+
+  void stubNullBrowserState(id BVC) {
+    [[[BVC stub] andDo:^(NSInvocation* invocation) {
+      ios::ChromeBrowserState* browserState = nullptr;
+      [invocation setReturnValue:&browserState];
+    }] browserState];
+  }
+
+  void swizzleSafeModeShouldStart(BOOL shouldStart) {
+    safe_mode_swizzle_block_.reset([^BOOL(id self) {
+      return shouldStart;
+    } copy]);
+    safe_mode_swizzler_.reset(new ScopedBlockSwizzler(
+        [SafeModeCoordinator class], @selector(shouldStart),
+        safe_mode_swizzle_block_));
+  }
+
+  void swizzleMetricsMediatorDisableReporting() {
+    metrics_mediator_called_ = NO;
+
+    metrics_mediator_swizzle_block_.reset([^(id self) {
+      metrics_mediator_called_ = YES;
+    } copy]);
+
+    metrics_mediator_swizzler_.reset(new ScopedBlockSwizzler(
+        [MetricsMediator class], @selector(disableReporting),
+        metrics_mediator_swizzle_block_));
+  }
+
+  void swizzleHandleStartupParameters(
+      id<TabOpening> expectedTabOpener,
+      id<BrowserViewInformation> expectedBrowserViewInformation) {
+    handle_startup_swizzle_block_.reset(
+        ^(id self, id<TabOpening> tabOpener,
+          id<StartupInformation> startupInformation,
+          id<BrowserViewInformation> browserViewInformation) {
+          ASSERT_EQ(startup_information_mock_, startupInformation);
+          ASSERT_EQ(expectedTabOpener, tabOpener);
+          ASSERT_EQ(expectedBrowserViewInformation, browserViewInformation);
+        },
+        base::scoped_policy::RETAIN);
+
+    handle_startup_swizzler_.reset(new ScopedBlockSwizzler(
+        [UserActivityHandler class],
+        @selector(handleStartupParametersWithTabOpener:
+                                    startupInformation:
+                                browserViewInformation:),
+        handle_startup_swizzle_block_));
+  }
+
+  AppState* getAppStateWithOpenNTPAndIncognitoBlock(BOOL shouldOpenNTP,
+                                                    UIWindow* window) {
+    AppState* appState = getAppStateWithRealWindow(window);
+
+    id application = [OCMockObject mockForClass:[UIApplication class]];
+    id metricsMediator = [OCMockObject mockForClass:[MetricsMediator class]];
+    id memoryHelper = [OCMockObject mockForClass:[MemoryWarningHelper class]];
+    id tabOpener = [OCMockObject mockForProtocol:@protocol(TabOpening)];
+    id appNavigation = [OCMockObject mockForProtocol:@protocol(AppNavigation)];
+    id tabModel = [OCMockObject mockForClass:[TabModel class]];
+
+    [[[browser_view_information_ stub] andReturn:tabModel] currentTabModel];
+    [[metricsMediator stub] updateMetricsStateBasedOnPrefsUserTriggered:NO];
+    [[memoryHelper stub] resetForegroundMemoryWarningCount];
+    [[[memoryHelper stub] andReturnValue:@0] foregroundMemoryWarningCount];
+    [[[tabOpener stub] andReturnValue:@(shouldOpenNTP)]
+        shouldOpenNTPTabOnActivationOfTabModel:tabModel];
+
+    stubNullCurrentBrowserState(browser_view_information_);
+
+    void (^swizzleBlock)() = ^() {
+    };
+
+    ScopedBlockSwizzler swizzler(
+        [MetricsMediator class],
+        @selector(logLaunchMetricsWithStartupInformation:
+                                  browserViewInformation:),
+        swizzleBlock);
+
+    [appState applicationWillEnterForeground:application
+                             metricsMediator:metricsMediator
+                                memoryHelper:memoryHelper
+                                   tabOpener:tabOpener
+                               appNavigation:appNavigation];
+
+    initializeIncognitoBlocker(window);
+
+    return appState;
+  }
+
+  AppState* getAppStateWithMock() {
+    if (!app_state_) {
+      app_state_.reset([[AppState alloc]
+          initWithBrowserLauncher:browser_launcher_mock_
+               startupInformation:startup_information_mock_
+              applicationDelegate:main_application_delegate_]);
+      [app_state_ setWindow:window_];
+    }
+    return app_state_;
+  }
+
+  AppState* getAppStateWithRealWindow(UIWindow* window) {
+    if (!app_state_) {
+      app_state_.reset([[AppState alloc]
+          initWithBrowserLauncher:browser_launcher_mock_
+               startupInformation:startup_information_mock_
+              applicationDelegate:main_application_delegate_]);
+      [app_state_ setWindow:window];
+    }
+    return app_state_;
+  }
+
+  id getBrowserLauncherMock() { return browser_launcher_mock_; }
+  id getStartupInformationMock() { return startup_information_mock_; }
+  id getApplicationDelegateMock() { return main_application_delegate_; }
+  id getWindowMock() { return window_; }
+  id getBrowserViewInformationMock() { return browser_view_information_; }
+
+  BOOL metricsMediatorHasBeenCalled() { return metrics_mediator_called_; }
+
+ private:
+  base::scoped_nsobject<AppState> app_state_;
+  id browser_launcher_mock_;
+  id startup_information_mock_;
+  id main_application_delegate_;
+  id window_;
+  id browser_view_information_;
+  base::mac::ScopedBlock<DecisionBlock> safe_mode_swizzle_block_;
+  base::mac::ScopedBlock<HandleStartupParam> handle_startup_swizzle_block_;
+  base::mac::ScopedBlock<ProceduralBlock> metrics_mediator_swizzle_block_;
+  std::unique_ptr<ScopedBlockSwizzler> safe_mode_swizzler_;
+  std::unique_ptr<ScopedBlockSwizzler> handle_startup_swizzler_;
+  std::unique_ptr<ScopedBlockSwizzler> metrics_mediator_swizzler_;
+  __block BOOL metrics_mediator_called_;
+};
+
+// TODO(crbug.com/585700): remove this.
+// Creates a requestTracker, needed for teardown.
+void createTracker(BOOL* created, base::Lock* lock) {
+  web::RequestTrackerImpl::GetTrackerForRequestGroupID(@"test");
+  base::AutoLock scoped_lock(*lock);
+  *created = YES;
+}
+
+// Used to have a thread handling the closing of the IO threads.
+class AppStateWithThreadTest : public PlatformTest {
+ protected:
+  AppStateWithThreadTest()
+      : thread_bundle_(web::TestWebThreadBundle::REAL_IO_THREAD) {
+    BOOL created = NO;
+    base::Lock* lock = new base::Lock;
+
+    web::WebThread::PostTask(web::WebThread::IO, FROM_HERE,
+                             base::Bind(&createTracker, &created, lock));
+
+    CFTimeInterval start = CACurrentMediaTime();
+
+    // Poll for at most 1s, waiting for the Tracker creation.
+    while (1) {
+      base::AutoLock scoped_lock(*lock);
+      if (created)
+        return;
+      if (CACurrentMediaTime() - start > 1.0) {
+        trackerCreationFailed();
+        return;
+      }
+      // Ensure that other threads have a chance to run even on a single-core
+      // devices.
+      pthread_yield_np();
+    }
+  }
+
+  void trackerCreationFailed() {
+    FAIL() << "Tracker creation took too much time.";
+  }
+
+ private:
+  web::TestWebThreadBundle thread_bundle_;
+};
+
+#pragma mark - Tests.
+
+// Tests -isInSafeMode returns true if there is a SafeModeController.
+TEST_F(AppStateTest, isInSafeModeTest) {
+  // Setup.
+  id safeModeContollerMock =
+      [OCMockObject mockForClass:[SafeModeCoordinator class]];
+
+  AppState* appState = getAppStateWithMock();
+
+  appState.safeModeCoordinator = nil;
+  ASSERT_FALSE([appState isInSafeMode]);
+  [appState setSafeModeCoordinator:safeModeContollerMock];
+
+  // Action.
+  BOOL result = [appState isInSafeMode];
+
+  // Test.
+  EXPECT_TRUE(result);
+}
+
+// Tests that if the application is in background
+// -requiresHandlingAfterLaunchWithOptions saves the launchOptions and returns
+// YES (to handle the launch options later).
+TEST_F(AppStateTest, requiresHandlingAfterLaunchWithOptionsBackground) {
+  // Setup.
+  NSString* sourceApplication = @"com.apple.mobilesafari";
+  NSDictionary* launchOptions =
+      @{UIApplicationLaunchOptionsSourceApplicationKey : sourceApplication};
+
+  AppState* appState = getAppStateWithMock();
+
+  id browserLauncherMock = getBrowserLauncherMock();
+  BrowserInitializationStageType stageBasic = INITIALIZATION_STAGE_BASIC;
+  [[browserLauncherMock expect] startUpBrowserToStage:stageBasic];
+  [[browserLauncherMock expect] setLaunchOptions:launchOptions];
+
+  // Action.
+  BOOL result = [appState requiresHandlingAfterLaunchWithOptions:launchOptions
+                                                 stateBackground:YES];
+
+  // Test.
+  EXPECT_TRUE(result);
+  EXPECT_OCMOCK_VERIFY(browserLauncherMock);
+}
+
+// Tests that if the application is active and Safe Mode should be activated
+// -requiresHandlingAfterLaunchWithOptions save the launch options and activate
+// the Safe Mode.
+TEST_F(AppStateTest, requiresHandlingAfterLaunchWithOptionsForegroundSafeMode) {
+  // Setup.
+  NSString* sourceApplication = @"com.apple.mobilesafari";
+  NSDictionary* launchOptions =
+      @{UIApplicationLaunchOptionsSourceApplicationKey : sourceApplication};
+
+  id windowMock = getWindowMock();
+  [[[windowMock stub] andReturn:nil] rootViewController];
+  [[windowMock expect] setRootViewController:[OCMArg any]];
+  [[windowMock expect] makeKeyAndVisible];
+
+  AppState* appState = getAppStateWithMock();
+
+  id browserLauncherMock = getBrowserLauncherMock();
+  BrowserInitializationStageType stageBasic = INITIALIZATION_STAGE_BASIC;
+  [[browserLauncherMock expect] startUpBrowserToStage:stageBasic];
+  [[browserLauncherMock expect] setLaunchOptions:launchOptions];
+
+  swizzleSafeModeShouldStart(YES);
+
+  ASSERT_FALSE([appState isInSafeMode]);
+
+  // Action.
+  BOOL result = [appState requiresHandlingAfterLaunchWithOptions:launchOptions
+                                                 stateBackground:NO];
+
+  // Test.
+  EXPECT_TRUE(result);
+  EXPECT_TRUE([appState isInSafeMode]);
+  EXPECT_OCMOCK_VERIFY(browserLauncherMock);
+  EXPECT_OCMOCK_VERIFY(windowMock);
+}
+
+// Tests that if the application is active
+// -requiresHandlingAfterLaunchWithOptions saves the launchOptions and start the
+// application in foreground.
+TEST_F(AppStateTest, requiresHandlingAfterLaunchWithOptionsForeground) {
+  // Setup.
+  NSString* sourceApplication = @"com.apple.mobilesafari";
+  NSDictionary* launchOptions =
+      @{UIApplicationLaunchOptionsSourceApplicationKey : sourceApplication};
+
+  [[[getStartupInformationMock() stub] andReturnValue:@YES] isColdStart];
+
+  [[[getWindowMock() stub] andReturn:nil] rootViewController];
+
+  AppState* appState = getAppStateWithMock();
+
+  id browserLauncherMock = getBrowserLauncherMock();
+  BrowserInitializationStageType stageBasic = INITIALIZATION_STAGE_BASIC;
+  [[browserLauncherMock expect] startUpBrowserToStage:stageBasic];
+  BrowserInitializationStageType stageForeground =
+      INITIALIZATION_STAGE_FOREGROUND;
+  [[browserLauncherMock expect] startUpBrowserToStage:stageForeground];
+  [[browserLauncherMock expect] setLaunchOptions:launchOptions];
+
+  swizzleSafeModeShouldStart(NO);
+
+  // Action.
+  BOOL result = [appState requiresHandlingAfterLaunchWithOptions:launchOptions
+                                                 stateBackground:NO];
+
+  // Test.
+  EXPECT_TRUE(result);
+  EXPECT_OCMOCK_VERIFY(browserLauncherMock);
+}
+
+// Test that -willResignActive set cold start to NO and launch record.
+TEST(AppStateNoFixtureTest, willResignActive) {
+  // Setup.
+  id tabModel = [OCMockObject mockForClass:[TabModel class]];
+  [[tabModel expect] recordSessionMetrics];
+
+  id browserViewInformation =
+      [OCMockObject mockForProtocol:@protocol(BrowserViewInformation)];
+
+  [[[browserViewInformation stub] andReturn:tabModel] mainTabModel];
+
+  id browserLauncher =
+      [OCMockObject mockForProtocol:@protocol(BrowserLauncher)];
+  [[[browserLauncher stub] andReturnValue:@(INITIALIZATION_STAGE_FOREGROUND)]
+      browserInitializationStage];
+  [[[browserLauncher stub] andReturn:browserViewInformation]
+      browserViewInformation];
+
+  id applicationDelegate =
+      [OCMockObject mockForClass:[MainApplicationDelegate class]];
+  id window = [OCMockObject mockForClass:[UIWindow class]];
+
+  base::scoped_nsobject<FakeStartupInformation> startupInformation(
+      [[FakeStartupInformation alloc] init]);
+  [startupInformation setIsColdStart:YES];
+
+  base::scoped_nsobject<AppState> appState([[AppState alloc]
+      initWithBrowserLauncher:browserLauncher
+           startupInformation:startupInformation
+          applicationDelegate:applicationDelegate]);
+  [appState setWindow:window];
+
+  ASSERT_TRUE([startupInformation isColdStart]);
+
+  // Action.
+  [appState willResignActiveTabModel];
+
+  // Test.
+  EXPECT_FALSE([startupInformation isColdStart]);
+  EXPECT_OCMOCK_VERIFY(tabModel);
+}
+
+// Test that -applicationWillTerminate clears everything.
+TEST_F(AppStateWithThreadTest, willTerminate) {
+  // Setup.
+  IOSChromeScopedTestingChromeBrowserProvider provider_(
+      base::MakeUnique<FakeChromeBrowserProvider>());
+
+  id browserLauncher =
+      [OCMockObject mockForProtocol:@protocol(BrowserLauncher)];
+  id applicationDelegate =
+      [OCMockObject mockForClass:[MainApplicationDelegate class]];
+  id window = [OCMockObject mockForClass:[UIWindow class]];
+  id browserViewInformation =
+      [OCMockObject mockForProtocol:@protocol(BrowserViewInformation)];
+  [[[browserLauncher stub] andReturnValue:@(INITIALIZATION_STAGE_FOREGROUND)]
+      browserInitializationStage];
+  [[[browserLauncher stub] andReturn:browserViewInformation]
+      browserViewInformation];
+
+  id settingsNavigationController =
+      [OCMockObject mockForClass:[SettingsNavigationController class]];
+
+  id appNavigation = [OCMockObject mockForProtocol:@protocol(AppNavigation)];
+  [[[appNavigation stub] andReturn:settingsNavigationController]
+      settingsNavigationController];
+  [[appNavigation expect] closeSettingsAnimated:NO completion:nil];
+
+  [[browserViewInformation expect] cleanDeviceSharingManager];
+  [[browserViewInformation expect] haltAllTabs];
+
+  id startupInformation =
+      [OCMockObject mockForProtocol:@protocol(StartupInformation)];
+  [[startupInformation expect] stopChromeMain];
+
+  base::scoped_nsobject<AppState> appState([[AppState alloc]
+      initWithBrowserLauncher:browserLauncher
+           startupInformation:startupInformation
+          applicationDelegate:applicationDelegate]);
+  [appState setWindow:window];
+
+  id application = [OCMockObject mockForClass:[UIApplication class]];
+  [[application expect] setMinimumBackgroundFetchInterval:
+                            UIApplicationBackgroundFetchIntervalNever];
+
+  // Action.
+  [appState applicationWillTerminate:application
+               applicationNavigation:appNavigation];
+
+  // Test.
+  EXPECT_OCMOCK_VERIFY(startupInformation);
+  EXPECT_OCMOCK_VERIFY(appNavigation);
+  EXPECT_OCMOCK_VERIFY(browserViewInformation);
+  EXPECT_OCMOCK_VERIFY(application);
+  FakeAppDistributionProvider* provider =
+      static_cast<FakeAppDistributionProvider*>(
+          ios::GetChromeBrowserProvider()->GetAppDistributionProvider());
+  EXPECT_TRUE(provider->cancel_called());
+}
+
+// Test that -resumeSessionWithTabOpener removes incognito blocker,
+// restart metrics and launchs from StartupParameters if they exist.
+TEST_F(AppStateTest, resumeSessionWithStartupParameters) {
+  // Setup.
+
+  // BrowserLauncher.
+  id browserViewInformation = getBrowserViewInformationMock();
+  [[[getBrowserLauncherMock() stub]
+      andReturnValue:@(INITIALIZATION_STAGE_FOREGROUND)]
+      browserInitializationStage];
+  [[[getBrowserLauncherMock() stub] andReturn:browserViewInformation]
+      browserViewInformation];
+
+  // StartupInformation.
+  id appStartupParameters =
+      [OCMockObject mockForClass:[AppStartupParameters class]];
+  [[[getStartupInformationMock() stub] andReturn:appStartupParameters]
+      startupParameters];
+  [[[getStartupInformationMock() stub] andReturnValue:@NO] isColdStart];
+
+  // TabOpening.
+  id tabOpener = [OCMockObject mockForProtocol:@protocol(TabOpening)];
+  // TabSwitcher.
+  id tabSwitcher = [OCMockObject mockForProtocol:@protocol(TabSwitching)];
+
+  // BrowserViewInformation.
+  id mainTabModel = [OCMockObject mockForClass:[TabModel class]];
+  [[mainTabModel expect] resetSessionMetrics];
+  [[[browserViewInformation stub] andReturn:mainTabModel] mainTabModel];
+
+  // Swizzle Startup Parameters.
+  swizzleHandleStartupParameters(tabOpener, browserViewInformation);
+
+  UIWindow* window = [[[UIWindow alloc] init] autorelease];
+  AppState* appState = getAppStateWithOpenNTPAndIncognitoBlock(NO, window);
+
+  ASSERT_EQ(NSUInteger(1), [window subviews].count);
+
+  // Action.
+  [appState resumeSessionWithTabOpener:tabOpener tabSwitcher:tabSwitcher];
+
+  // Test.
+  EXPECT_EQ(NSUInteger(0), [window subviews].count);
+  EXPECT_OCMOCK_VERIFY(mainTabModel);
+}
+
+// Test that -resumeSessionWithTabOpener removes incognito blocker,
+// restart metrics and creates a new tab from tab switcher if shouldOpenNTP is
+// YES.
+TEST_F(AppStateTest, resumeSessionShouldOpenNTPTabSwitcher) {
+  // Setup.
+  // BrowserLauncher.
+  id browserViewInformation = getBrowserViewInformationMock();
+  [[[getBrowserLauncherMock() stub]
+      andReturnValue:@(INITIALIZATION_STAGE_FOREGROUND)]
+      browserInitializationStage];
+  [[[getBrowserLauncherMock() stub] andReturn:browserViewInformation]
+      browserViewInformation];
+
+  // StartupInformation.
+  [[[getStartupInformationMock() stub] andReturn:nil] startupParameters];
+  [[[getStartupInformationMock() stub] andReturnValue:@NO] isColdStart];
+
+  // TabOpening.
+  id tabOpener = [OCMockObject mockForProtocol:@protocol(TabOpening)];
+
+  // BrowserViewInformation.
+  id mainTabModel = [OCMockObject mockForClass:[TabModel class]];
+  [[mainTabModel expect] resetSessionMetrics];
+  [[[browserViewInformation stub] andReturn:mainTabModel] mainTabModel];
+
+  // TabSwitcher.
+  id tabSwitcher = [OCMockObject mockForProtocol:@protocol(TabSwitching)];
+  [[[tabSwitcher stub] andReturnValue:@YES] openNewTabFromTabSwitcher];
+
+  UIWindow* window = [[[UIWindow alloc] init] autorelease];
+  AppState* appState = getAppStateWithOpenNTPAndIncognitoBlock(YES, window);
+
+  ASSERT_EQ(NSUInteger(1), [window subviews].count);
+
+  // Action.
+  [appState resumeSessionWithTabOpener:tabOpener tabSwitcher:tabSwitcher];
+
+  // Test.
+  EXPECT_EQ(NSUInteger(0), [window subviews].count);
+  EXPECT_OCMOCK_VERIFY(mainTabModel);
+}
+
+// Test that -resumeSessionWithTabOpener removes incognito blocker,
+// restart metrics and creates a new tab if shouldOpenNTP is YES.
+TEST_F(AppStateTest, resumeSessionShouldOpenNTPNoTabSwitcher) {
+  // Setup.
+  // BrowserLauncher.
+  id browserViewInformation = getBrowserViewInformationMock();
+  [[[getBrowserLauncherMock() stub]
+      andReturnValue:@(INITIALIZATION_STAGE_FOREGROUND)]
+      browserInitializationStage];
+  [[[getBrowserLauncherMock() stub] andReturn:browserViewInformation]
+      browserViewInformation];
+
+  // StartupInformation.
+  [[[getStartupInformationMock() stub] andReturn:nil] startupParameters];
+  [[[getStartupInformationMock() stub] andReturnValue:@NO] isColdStart];
+
+  // TabOpening.
+  id tabOpener = [OCMockObject mockForProtocol:@protocol(TabOpening)];
+
+  // BrowserViewInformation.
+  id mainTabModel = [OCMockObject mockForClass:[TabModel class]];
+  [[mainTabModel expect] resetSessionMetrics];
+
+  id currentBVC = [OCMockObject mockForClass:[BrowserViewController class]];
+  [[currentBVC expect] newTab:nil];
+  stubNullBrowserState(currentBVC);
+
+  [[[browserViewInformation stub] andReturn:mainTabModel] mainTabModel];
+  [[[browserViewInformation stub] andReturn:currentBVC] currentBVC];
+
+  // TabSwitcher.
+  id tabSwitcher = [OCMockObject mockForProtocol:@protocol(TabSwitching)];
+  [[[tabSwitcher stub] andReturnValue:@NO] openNewTabFromTabSwitcher];
+
+  UIWindow* window = [[[UIWindow alloc] init] autorelease];
+  AppState* appState = getAppStateWithOpenNTPAndIncognitoBlock(YES, window);
+
+  // incognitoBlocker.
+  ASSERT_EQ(NSUInteger(1), [window subviews].count);
+
+  // Action.
+  [appState resumeSessionWithTabOpener:tabOpener tabSwitcher:tabSwitcher];
+
+  // Test.
+  EXPECT_EQ(NSUInteger(0), [window subviews].count);
+  EXPECT_OCMOCK_VERIFY(mainTabModel);
+  EXPECT_OCMOCK_VERIFY(currentBVC);
+}
+
+// Tests that -applicationWillEnterForeground resets components as needed.
+TEST_F(AppStateTest, applicationWillEnterForeground) {
+  // Setup.
+  IOSChromeScopedTestingChromeBrowserProvider provider_(
+      base::MakeUnique<FakeChromeBrowserProvider>());
+  id application = [OCMockObject mockForClass:[UIApplication class]];
+  id metricsMediator = [OCMockObject mockForClass:[MetricsMediator class]];
+  id memoryHelper = [OCMockObject mockForClass:[MemoryWarningHelper class]];
+  id browserViewInformation = getBrowserViewInformationMock();
+  id tabOpener = [OCMockObject mockForProtocol:@protocol(TabOpening)];
+  id appNavigation = [OCMockObject mockForProtocol:@protocol(AppNavigation)];
+  id tabModel = [OCMockObject mockForClass:[TabModel class]];
+
+  BrowserInitializationStageType stage = INITIALIZATION_STAGE_FOREGROUND;
+  [[[getBrowserLauncherMock() stub] andReturnValue:@(stage)]
+      browserInitializationStage];
+  [[[getBrowserLauncherMock() stub] andReturn:browserViewInformation]
+      browserViewInformation];
+  [[[browserViewInformation stub] andReturn:tabModel] currentTabModel];
+  [[metricsMediator expect] updateMetricsStateBasedOnPrefsUserTriggered:NO];
+  [[memoryHelper expect] resetForegroundMemoryWarningCount];
+  [[[memoryHelper stub] andReturnValue:@0] foregroundMemoryWarningCount];
+  [[[tabOpener stub] andReturnValue:@YES]
+      shouldOpenNTPTabOnActivationOfTabModel:tabModel];
+
+  stubNullCurrentBrowserState(browserViewInformation);
+
+  void (^swizzleBlock)() = ^() {
+  };
+
+  ScopedBlockSwizzler swizzler(
+      [MetricsMediator class],
+      @selector(logLaunchMetricsWithStartupInformation:browserViewInformation:),
+      swizzleBlock);
+
+  // Actions.
+  [getAppStateWithMock() applicationWillEnterForeground:application
+                                        metricsMediator:metricsMediator
+                                           memoryHelper:memoryHelper
+                                              tabOpener:tabOpener
+                                          appNavigation:appNavigation];
+
+  // Tests.
+  EXPECT_OCMOCK_VERIFY(metricsMediator);
+  EXPECT_OCMOCK_VERIFY(memoryHelper);
+  FakeUserFeedbackProvider* user_feedback_provider =
+      static_cast<FakeUserFeedbackProvider*>(
+          ios::GetChromeBrowserProvider()->GetUserFeedbackProvider());
+  EXPECT_TRUE(user_feedback_provider->synchronize_called());
+}
+
+// Tests that -applicationWillEnterForeground starts the browser if the
+// application is in background.
+TEST_F(AppStateTest, applicationWillEnterForegroundFromBackground) {
+  // Setup.
+  id application = [OCMockObject mockForClass:[UIApplication class]];
+  id metricsMediator = [OCMockObject mockForClass:[MetricsMediator class]];
+  id memoryHelper = [OCMockObject mockForClass:[MemoryWarningHelper class]];
+  id tabOpener = [OCMockObject mockForProtocol:@protocol(TabOpening)];
+  id appNavigation = [OCMockObject mockForProtocol:@protocol(AppNavigation)];
+
+  BrowserInitializationStageType stage = INITIALIZATION_STAGE_BACKGROUND;
+  [[[getBrowserLauncherMock() stub] andReturnValue:@(stage)]
+      browserInitializationStage];
+
+  [[[getWindowMock() stub] andReturn:nil] rootViewController];
+  swizzleSafeModeShouldStart(NO);
+
+  [[[getStartupInformationMock() stub] andReturnValue:@YES] isColdStart];
+  [[getBrowserLauncherMock() expect]
+      startUpBrowserToStage:INITIALIZATION_STAGE_FOREGROUND];
+
+  // Actions.
+  [getAppStateWithMock() applicationWillEnterForeground:application
+                                        metricsMediator:metricsMediator
+                                           memoryHelper:memoryHelper
+                                              tabOpener:tabOpener
+                                          appNavigation:appNavigation];
+
+  // Tests.
+  EXPECT_OCMOCK_VERIFY(getBrowserLauncherMock());
+}
+
+// Tests that -applicationWillEnterForeground starts the safe mode if the
+// application is in background.
+TEST_F(AppStateTest,
+       applicationWillEnterForegroundFromBackgroundShouldStartSafeMode) {
+  // Setup.
+  id application = [OCMockObject mockForClass:[UIApplication class]];
+  id metricsMediator = [OCMockObject mockForClass:[MetricsMediator class]];
+  id memoryHelper = [OCMockObject mockForClass:[MemoryWarningHelper class]];
+  id tabOpener = [OCMockObject mockForProtocol:@protocol(TabOpening)];
+  id appNavigation = [OCMockObject mockForProtocol:@protocol(AppNavigation)];
+
+  id window = getWindowMock();
+
+  BrowserInitializationStageType stage = INITIALIZATION_STAGE_BACKGROUND;
+  [[[getBrowserLauncherMock() stub] andReturnValue:@(stage)]
+      browserInitializationStage];
+
+  [[[window stub] andReturn:nil] rootViewController];
+  [[window expect] makeKeyAndVisible];
+  [[window stub] setRootViewController:[OCMArg any]];
+  swizzleSafeModeShouldStart(YES);
+
+  // Actions.
+  [getAppStateWithMock() applicationWillEnterForeground:application
+                                        metricsMediator:metricsMediator
+                                           memoryHelper:memoryHelper
+                                              tabOpener:tabOpener
+                                          appNavigation:appNavigation];
+
+  // Tests.
+  EXPECT_OCMOCK_VERIFY(window);
+  EXPECT_TRUE([getAppStateWithMock() isInSafeMode]);
+}
+
+// Tests that -applicationWillEnterForeground returns directly if the
+// application is in safe mode and in foreground
+TEST_F(AppStateTest, applicationWillEnterForegroundFromForegroundSafeMode) {
+  // Setup.
+  id application = [OCMockObject mockForClass:[UIApplication class]];
+  id metricsMediator = [OCMockObject mockForClass:[MetricsMediator class]];
+  id memoryHelper = [OCMockObject mockForClass:[MemoryWarningHelper class]];
+  id tabOpener = [OCMockObject mockForProtocol:@protocol(TabOpening)];
+  id appNavigation = [OCMockObject mockForProtocol:@protocol(AppNavigation)];
+
+  BrowserInitializationStageType stage = INITIALIZATION_STAGE_FOREGROUND;
+  [[[getBrowserLauncherMock() stub] andReturnValue:@(stage)]
+      browserInitializationStage];
+
+  AppState* appState = getAppStateWithMock();
+
+  UIWindow* window = [[[UIWindow alloc] init] autorelease];
+  appState.safeModeCoordinator =
+      [[[SafeModeCoordinator alloc] initWithWindow:window] autorelease];
+
+  ASSERT_TRUE([appState isInSafeMode]);
+
+  // Actions.
+  [appState applicationWillEnterForeground:application
+                           metricsMediator:metricsMediator
+                              memoryHelper:memoryHelper
+                                 tabOpener:tabOpener
+                             appNavigation:appNavigation];
+}
+
+// Tests that -applicationDidEnterBackground creates an incognito blocker.
+TEST_F(AppStateTest, applicationDidEnterBackgroundIncognito) {
+  // Setup.
+  UIWindow* window = [[[UIWindow alloc] init] autorelease];
+  id application = [OCMockObject niceMockForClass:[UIApplication class]];
+  id memoryHelper = [OCMockObject mockForClass:[MemoryWarningHelper class]];
+  id browserViewInformation = getBrowserViewInformationMock();
+  id tabModel = [OCMockObject mockForClass:[TabModel class]];
+  id startupInformation = getStartupInformationMock();
+  id browserLauncher = getBrowserLauncherMock();
+  BrowserInitializationStageType stage = INITIALIZATION_STAGE_FOREGROUND;
+
+  AppState* appState = getAppStateWithRealWindow(window);
+
+  [[startupInformation expect] expireFirstUserActionRecorder];
+  [[[memoryHelper stub] andReturnValue:@0] foregroundMemoryWarningCount];
+  [[[tabModel stub] andReturnValue:@NO] isEmpty];
+  [[[browserViewInformation stub] andReturn:tabModel] otrTabModel];
+  [[[browserViewInformation stub] andReturn:nil] currentBVC];
+  stubNullCurrentBrowserState(browserViewInformation);
+  [[[browserLauncher stub] andReturnValue:@(stage)] browserInitializationStage];
+  [[[browserLauncher stub] andReturn:browserViewInformation]
+      browserViewInformation];
+
+  swizzleMetricsMediatorDisableReporting();
+
+  ASSERT_EQ(NSUInteger(0), [window subviews].count);
+
+  // Action.
+  [appState applicationDidEnterBackground:application
+                             memoryHelper:memoryHelper
+                      tabSwitcherIsActive:YES];
+
+  // Tests.
+  EXPECT_OCMOCK_VERIFY(startupInformation);
+  EXPECT_TRUE(metricsMediatorHasBeenCalled());
+  EXPECT_EQ(NSUInteger(1), [window subviews].count);
+}
+
+// Tests that -applicationDidEnterBackground do nothing if the application has
+// never been in a Foreground stage.
+TEST_F(AppStateTest, applicationDidEnterBackgroundStageBackground) {
+  // Setup.
+  UIWindow* window = [[[UIWindow alloc] init] autorelease];
+  id application = [OCMockObject mockForClass:[UIApplication class]];
+  id memoryHelper = [OCMockObject mockForClass:[MemoryWarningHelper class]];
+  id browserLauncher = getBrowserLauncherMock();
+  BrowserInitializationStageType stage = INITIALIZATION_STAGE_BACKGROUND;
+
+  [[[browserLauncher stub] andReturnValue:@(stage)] browserInitializationStage];
+
+  ASSERT_EQ(NSUInteger(0), [window subviews].count);
+
+  // Action.
+  [getAppStateWithRealWindow(window) applicationDidEnterBackground:application
+                                                      memoryHelper:memoryHelper
+                                               tabSwitcherIsActive:YES];
+
+  // Tests.
+  EXPECT_EQ(NSUInteger(0), [window subviews].count);
+}
+
+// Tests that -applicationDidEnterBackground does not create an incognito
+// blocker if there is no incognito tab.
+TEST_F(AppStateTest, applicationDidEnterBackgroundNoIncognitoBlocker) {
+  // Setup.
+  UIWindow* window = [[[UIWindow alloc] init] autorelease];
+  id application = [OCMockObject niceMockForClass:[UIApplication class]];
+  id memoryHelper = [OCMockObject mockForClass:[MemoryWarningHelper class]];
+  id browserViewInformation = getBrowserViewInformationMock();
+  id tabModel = [OCMockObject mockForClass:[TabModel class]];
+  id startupInformation = getStartupInformationMock();
+  id browserLauncher = getBrowserLauncherMock();
+  BrowserInitializationStageType stage = INITIALIZATION_STAGE_FOREGROUND;
+
+  AppState* appState = getAppStateWithRealWindow(window);
+
+  [[startupInformation expect] expireFirstUserActionRecorder];
+  [[[memoryHelper stub] andReturnValue:@0] foregroundMemoryWarningCount];
+  [[[tabModel stub] andReturnValue:@YES] isEmpty];
+  [[[browserViewInformation stub] andReturn:tabModel] otrTabModel];
+  [[[browserViewInformation stub] andReturn:nil] currentBVC];
+  stubNullCurrentBrowserState(browserViewInformation);
+  [[[browserLauncher stub] andReturnValue:@(stage)] browserInitializationStage];
+  [[[browserLauncher stub] andReturn:browserViewInformation]
+      browserViewInformation];
+
+  swizzleMetricsMediatorDisableReporting();
+
+  ASSERT_EQ(NSUInteger(0), [window subviews].count);
+
+  // Action.
+  [appState applicationDidEnterBackground:application
+                             memoryHelper:memoryHelper
+                      tabSwitcherIsActive:YES];
+
+  // Tests.
+  EXPECT_OCMOCK_VERIFY(startupInformation);
+  EXPECT_TRUE(metricsMediatorHasBeenCalled());
+  EXPECT_EQ(NSUInteger(0), [window subviews].count);
+}
diff --git a/ios/chrome/app/application_delegate/background_activity.h b/ios/chrome/app/application_delegate/background_activity.h
new file mode 100644
index 0000000..2b705f6
--- /dev/null
+++ b/ios/chrome/app/application_delegate/background_activity.h
@@ -0,0 +1,39 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_APPLICATION_DELEGATE_BACKGROUND_ACTIVITY_H_
+#define IOS_CHROME_APP_APPLICATION_DELEGATE_BACKGROUND_ACTIVITY_H_
+
+#import <UIKit/UIKit.h>
+
+@protocol BrowserLauncher;
+@class MetricsMediator;
+
+// Handles the data-related methods of the ApplicationDelegate. This class has
+// only class methods and should not be instantiated.
+@interface BackgroundActivity : NSObject
+
+// Class methods only.
+- (instancetype)init NS_UNAVAILABLE;
+
+// Handler for the application delegate |performFetchWithCompletionHandler|
+// message. Sends the next breakpad report if available and the user permits it.
++ (void)application:(UIApplication*)application
+    performFetchWithCompletionHandler:
+        (void (^)(UIBackgroundFetchResult))completionHandler
+                      metricsMediator:(MetricsMediator*)metricsMediator
+                      browserLauncher:(id<BrowserLauncher>)browserLauncher;
+
+// Handles Events for Background.
++ (void)handleEventsForBackgroundURLSession:(NSString*)identifier
+                          completionHandler:(void (^)(void))completionHandler
+                            browserLauncher:
+                                (id<BrowserLauncher>)browserLauncher;
+
+// Once the application is in foreground, reset last fetch in background time in
+// NSUserDefaults.
++ (void)foregroundStarted;
+@end
+
+#endif  // IOS_CHROME_APP_APPLICATION_DELEGATE_BACKGROUND_ACTIVITY_H_
diff --git a/ios/chrome/app/application_delegate/background_activity.mm b/ios/chrome/app/application_delegate/background_activity.mm
new file mode 100644
index 0000000..f5c55250
--- /dev/null
+++ b/ios/chrome/app/application_delegate/background_activity.mm
@@ -0,0 +1,94 @@
+// Copyright 2016 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/application_delegate/background_activity.h"
+
+#import "base/ios/weak_nsobject.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/user_metrics.h"
+#include "base/metrics/user_metrics_action.h"
+#include "base/time/time.h"
+#import "ios/chrome/app/application_delegate/browser_launcher.h"
+#import "ios/chrome/app/application_delegate/metrics_mediator.h"
+#import "ios/chrome/browser/crash_report/crash_report_background_uploader.h"
+#include "ios/chrome/browser/metrics/first_user_action_recorder.h"
+#import "ios/chrome/browser/metrics/previous_session_info.h"
+#include "url/gurl.h"
+
+namespace {
+// Key of the UMA Startup.BackgroundFetchTimeInterval histogram.
+const char* const kUMAMobileBackgroundFetchTimeInterval =
+    "Startup.BackgroundFetchTimeInterval";
+// Key in the UserDefaults to store the date/time that the application was last
+// launched for a background fetch.
+NSString* const kLastBackgroundFetch = @"LastBackgroundFetch";
+}  // namespace
+
+@implementation BackgroundActivity
+
+- (instancetype)init {
+  NOTREACHED();
+  return nil;
+}
+
+#pragma mark - Public class methods.
+
++ (void)application:(UIApplication*)application
+    performFetchWithCompletionHandler:
+        (void (^)(UIBackgroundFetchResult))completionHandler
+                      metricsMediator:(MetricsMediator*)metricsMediator
+                      browserLauncher:(id<BrowserLauncher>)browserLauncher {
+  [browserLauncher startUpBrowserToStage:INITIALIZATION_STAGE_BACKGROUND];
+
+  // TODO(crbug.com/616436): Check if this can be moved to MetricsMediator.
+  base::RecordAction(base::UserMetricsAction("BackgroundFetchCalled"));
+  NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
+  base::Time lastTime =
+      base::Time::FromDoubleT([userDefaults doubleForKey:kLastBackgroundFetch]);
+  base::Time now = base::Time::Now();
+  if (!lastTime.is_null()) {
+    UMA_HISTOGRAM_LONG_TIMES_100(kUMAMobileBackgroundFetchTimeInterval,
+                                 now - lastTime);
+  }
+  [userDefaults setDouble:now.ToDoubleT() forKey:kLastBackgroundFetch];
+
+  // The crashes concerning an old version are obsolete. To reduce data usage,
+  // these crash reports are not uploaded.
+  if (![metricsMediator areMetricsEnabled] ||
+      [[PreviousSessionInfo sharedInstance] isFirstSessionAfterUpgrade]) {
+    [application setMinimumBackgroundFetchInterval:
+                     UIApplicationBackgroundFetchIntervalNever];
+    completionHandler(UIBackgroundFetchResultNoData);
+    return;
+  }
+
+  if (![metricsMediator isUploadingEnabled]) {
+    completionHandler(UIBackgroundFetchResultFailed);
+    return;
+  }
+  [CrashReportBackgroundUploader
+      performFetchWithCompletionHandler:completionHandler];
+}
+
++ (void)handleEventsForBackgroundURLSession:(NSString*)identifier
+                          completionHandler:(void (^)(void))completionHandler
+                            browserLauncher:
+                                (id<BrowserLauncher>)browserLauncher {
+  [browserLauncher startUpBrowserToStage:INITIALIZATION_STAGE_BACKGROUND];
+  if ([CrashReportBackgroundUploader
+          canHandleBackgroundURLSession:identifier]) {
+    [CrashReportBackgroundUploader
+        handleEventsForBackgroundURLSession:identifier
+                          completionHandler:completionHandler];
+  } else {
+    completionHandler();
+  }
+}
+
++ (void)foregroundStarted {
+  [[NSUserDefaults standardUserDefaults]
+      removeObjectForKey:kLastBackgroundFetch];
+}
+
+@end
diff --git a/ios/chrome/app/application_delegate/background_activity_unittest.mm b/ios/chrome/app/application_delegate/background_activity_unittest.mm
new file mode 100644
index 0000000..d9f4788
--- /dev/null
+++ b/ios/chrome/app/application_delegate/background_activity_unittest.mm
@@ -0,0 +1,73 @@
+// Copyright 2016 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/application_delegate/background_activity.h"
+
+#import "ios/chrome/app/application_delegate/browser_launcher.h"
+#import "ios/chrome/app/application_delegate/metrics_mediator.h"
+#import "ios/chrome/browser/crash_report/crash_report_background_uploader.h"
+#import "ios/chrome/browser/metrics/previous_session_info.h"
+#import "ios/chrome/browser/metrics/previous_session_info_private.h"
+#import "ios/chrome/test/base/scoped_block_swizzler.h"
+#include "testing/platform_test.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
+#import "third_party/ocmock/gtest_support.h"
+
+// Verifies that -application:performFetchWithCompletionHandler: calls the
+// browser launcher in background state and uploads the report.
+TEST(BackgroundActivityTest, performFetchWithCompletionHandler) {
+  // Setup.
+  [[PreviousSessionInfo sharedInstance] setIsFirstSessionAfterUpgrade:NO];
+
+  // MetricsMediator mock.
+  id metrics_mediator_mock =
+      [OCMockObject mockForClass:[MetricsMediator class]];
+  [[[metrics_mediator_mock stub] andReturnValue:@YES] areMetricsEnabled];
+  [[[metrics_mediator_mock stub] andReturnValue:@YES] isUploadingEnabled];
+
+  // BrowserLauncher mock.
+  id browser_launcher =
+      [OCMockObject mockForProtocol:@protocol(BrowserLauncher)];
+  [[browser_launcher expect]
+      startUpBrowserToStage:INITIALIZATION_STAGE_BACKGROUND];
+
+  // CrashReportBackgroundUploader swizzle.
+  __block BOOL crash_report_completion_handler_has_been_called = NO;
+  id implementation_block = ^(id self) {
+    crash_report_completion_handler_has_been_called = YES;
+  };
+  ScopedBlockSwizzler crash_report_completion_handler_swizzler(
+      [CrashReportBackgroundUploader class],
+      @selector(performFetchWithCompletionHandler:), implementation_block);
+
+  // Test.
+  [BackgroundActivity application:nil
+      performFetchWithCompletionHandler:nil
+                        metricsMediator:metrics_mediator_mock
+                        browserLauncher:browser_launcher];
+
+  // Check.
+  EXPECT_OCMOCK_VERIFY(browser_launcher);
+  EXPECT_TRUE(crash_report_completion_handler_has_been_called);
+}
+
+// Verifies that -handleEventsForBackgroundURLSession:completionHandler: calls
+// the browser launcher in background state.
+TEST(BackgroundActivityTest, handleEventsForBackgroundURLSession) {
+  // Setup.
+  // BrowserLauncher mock.
+  id browser_launcher =
+      [OCMockObject mockForProtocol:@protocol(BrowserLauncher)];
+  [[browser_launcher expect]
+      startUpBrowserToStage:INITIALIZATION_STAGE_BACKGROUND];
+
+  // Test.
+  [BackgroundActivity handleEventsForBackgroundURLSession:nil
+                                        completionHandler:^{
+                                        }
+                                          browserLauncher:browser_launcher];
+
+  // Check.
+  EXPECT_OCMOCK_VERIFY(browser_launcher);
+}
diff --git a/ios/chrome/app/application_delegate/browser_launcher.h b/ios/chrome/app/application_delegate/browser_launcher.h
new file mode 100644
index 0000000..cbcbab6
--- /dev/null
+++ b/ios/chrome/app/application_delegate/browser_launcher.h
@@ -0,0 +1,43 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_APPLICATION_DELEGATE_BROWSER_LAUNCHER_H_
+#define IOS_CHROME_APP_APPLICATION_DELEGATE_BROWSER_LAUNCHER_H_
+
+#import "ios/chrome/browser/ui/main/browser_view_information.h"
+
+// Possible stages of the browser initialization. These states will be reached
+// in sequence, each stage is a requiremant for the following one.
+enum BrowserInitializationStageType {
+  // This state is before any initialization in MainController.
+  INITIALIZATION_STAGE_NONE = 0,
+  // Initialization state after |didFinishLaunchingWithOptions|.
+  INITIALIZATION_STAGE_BASIC,
+  // Initialization state needed by background handlers.
+  INITIALIZATION_STAGE_BACKGROUND,
+  // Full initialization of the browser.
+  INITIALIZATION_STAGE_FOREGROUND,
+  BROWSER_INITIALIZATION_STAGE_TYPE_COUNT,
+};
+
+// This protocol defines the startup method for the application.
+@protocol BrowserLauncher<NSObject>
+
+// Cached launchOptions from AppState's -didFinishLaunchingWithOptions.
+@property(nonatomic, retain) NSDictionary* launchOptions;
+
+// Highest initialization stage reached by the browser.
+@property(nonatomic, readonly)
+    BrowserInitializationStageType browserInitializationStage;
+
+// Browser view information created during startup.
+@property(nonatomic, readonly) id<BrowserViewInformation>
+    browserViewInformation;
+
+// Initializes the application up to |stage|.
+- (void)startUpBrowserToStage:(BrowserInitializationStageType)stage;
+
+@end
+
+#endif  // IOS_CHROME_APP_APPLICATION_DELEGATE_BROWSER_LAUNCHER_H_
diff --git a/ios/chrome/app/application_delegate/fake_startup_information.h b/ios/chrome/app/application_delegate/fake_startup_information.h
new file mode 100644
index 0000000..3d7857d
--- /dev/null
+++ b/ios/chrome/app/application_delegate/fake_startup_information.h
@@ -0,0 +1,17 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_APPLICATION_DELEGATE_FAKE_STARTUP_INFORMATION_H_
+#define IOS_CHROME_APP_APPLICATION_DELEGATE_FAKE_STARTUP_INFORMATION_H_
+
+#include <UIKit/UIKit.h>
+
+#include "ios/chrome/app/application_delegate/startup_information.h"
+
+// Fakes a class adopting the StartupInformation protocol. It only synthetizes
+// the properties.
+@interface FakeStartupInformation : NSObject<StartupInformation>
+
+@end
+#endif  // IOS_CHROME_APP_APPLICATION_DELEGATE_FAKE_STARTUP_INFORMATION_H_
diff --git a/ios/chrome/app/application_delegate/fake_startup_information.mm b/ios/chrome/app/application_delegate/fake_startup_information.mm
new file mode 100644
index 0000000..2374800
--- /dev/null
+++ b/ios/chrome/app/application_delegate/fake_startup_information.mm
@@ -0,0 +1,65 @@
+// Copyright 2016 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.
+
+#include "ios/chrome/app/application_delegate/fake_startup_information.h"
+
+#import "base/mac/scoped_nsobject.h"
+#include "base/time/time.h"
+#include "ios/chrome/browser/app_startup_parameters.h"
+
+@interface FakeStartupInformation () {
+  base::scoped_nsobject<AppStartupParameters> _startupParameters;
+}
+
+@end
+
+@implementation FakeStartupInformation
+
+@synthesize appLaunchTime = _appLaunchTime;
+@synthesize isPresentingFirstRunUI = _isPresentingFirstRunUI;
+@synthesize isColdStart = _isColdStart;
+
+- (AppStartupParameters*)startupParameters {
+  return _startupParameters;
+}
+
+- (void)setStartupParameters:(AppStartupParameters*)startupParameters {
+  _startupParameters.reset([startupParameters retain]);
+}
+
+- (FirstUserActionRecorder*)firstUserActionRecorder {
+  // Stub.
+  return nil;
+}
+
+- (void)resetFirstUserActionRecorder {
+  // Stub.
+}
+
+- (void)expireFirstUserActionRecorderAfterDelay:(NSTimeInterval)delay {
+  // Stub.
+}
+
+- (void)activateFirstUserActionRecorderWithBackgroundTime:
+    (NSTimeInterval)backgroundTime {
+  // Stub.
+}
+
+- (void)expireFirstUserActionRecorder {
+  // Stub.
+}
+
+- (void)launchFromURLHandled:(BOOL)URLHandled {
+  // Stub.
+}
+
+- (void)stopChromeMain {
+  // Stub.
+}
+
+- (void)startChromeMain {
+  // Stub.
+}
+
+@end
diff --git a/ios/chrome/app/application_delegate/metrics_mediator.h b/ios/chrome/app/application_delegate/metrics_mediator.h
new file mode 100644
index 0000000..8c77b8c
--- /dev/null
+++ b/ios/chrome/app/application_delegate/metrics_mediator.h
@@ -0,0 +1,54 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_APPLICATION_DELEGATE_METRICS_MEDIATOR_H_
+#define IOS_CHROME_APP_APPLICATION_DELEGATE_METRICS_MEDIATOR_H_
+
+#import <UIKit/UIKit.h>
+
+@protocol StartupInformation;
+
+@protocol BrowserViewInformation;
+@protocol StartupInformation;
+
+namespace metrics_mediator {
+// Key in the UserDefaults to store the date/time that the background fetch
+// handler was called.
+extern NSString* const kAppEnteredBackgroundDateKey;
+}  // namespace metrics_mediator
+
+// Deals with metrics, checking and updating them accordingly to to the user
+// preferences.
+@interface MetricsMediator : NSObject
+// Returns YES if the metrics pref is enabled.  Does not take into account the
+// wifi-only option or wwan state.
+- (BOOL)areMetricsEnabled;
+// Return YES if uploading is allowed, based on user preferences.
+- (BOOL)isUploadingEnabled;
+// Starts or stops the metrics service and crash report recording and/or
+// uploading, based on the current user preferences. Makes sure helper
+// mechanisms and the wwan state observer are set up if necessary. Called both
+// on initialization and after user triggered preference change.
+// |isUserTriggered| is used to distinguish between those cases.
+- (void)updateMetricsStateBasedOnPrefsUserTriggered:(BOOL)isUserTriggered;
+// Logs the duration of the cold start startup. Does nothing if there isn't a
+// cold start.
++ (void)logStartupDuration:(id<StartupInformation>)startupInformation;
+// Logs the number of tabs open and the start type.
++ (void)logLaunchMetricsWithStartupInformation:
+            (id<StartupInformation>)startupInformation
+                        browserViewInformation:
+                            (id<BrowserViewInformation>)browserViewInformation;
+// Logs in UserDefaults the current date with kAppEnteredBackgroundDateKey as
+// key.
++ (void)logDateInUserDefaults;
+// Disables reporting in breakpad and metrics service.
++ (void)disableReporting;
+// Logs that the application is in background and the number of memory warnings
+// for this session.
++ (void)applicationDidEnterBackground:(NSInteger)memoryWarningCount;
+
+@end
+
+#endif  // IOS_CHROME_APP_APPLICATION_DELEGATE_METRICS_MEDIATOR_H_
diff --git a/ios/chrome/app/application_delegate/metrics_mediator.mm b/ios/chrome/app/application_delegate/metrics_mediator.mm
new file mode 100644
index 0000000..074ff8fc2
--- /dev/null
+++ b/ios/chrome/app/application_delegate/metrics_mediator.mm
@@ -0,0 +1,394 @@
+// Copyright 2016 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/application_delegate/metrics_mediator.h"
+
+#include "base/ios/weak_nsobject.h"
+#include "base/mac/bind_objc_block.h"
+#include "base/metrics/user_metrics_action.h"
+#include "base/strings/sys_string_conversions.h"
+#include "components/crash/core/common/crash_keys.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_service.h"
+#include "components/prefs/pref_service.h"
+#import "ios/chrome/app/application_delegate/startup_information.h"
+#include "ios/chrome/browser/application_context.h"
+#include "ios/chrome/browser/chrome_url_constants.h"
+#include "ios/chrome/browser/crash_report/breakpad_helper.h"
+#import "ios/chrome/browser/crash_report/crash_report_background_uploader.h"
+#include "ios/chrome/browser/experimental_flags.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/connection_type_observer_bridge.h"
+#include "ios/chrome/browser/pref_names.h"
+#import "ios/chrome/browser/tabs/tab.h"
+#import "ios/chrome/browser/tabs/tab_model.h"
+#import "ios/chrome/browser/ui/main/browser_view_information.h"
+#include "ios/chrome/common/app_group/app_group_metrics_mainapp.h"
+#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
+#include "ios/public/provider/chrome/browser/distribution/app_distribution_provider.h"
+#include "ios/web/public/web_thread.h"
+#include "url/gurl.h"
+
+namespace {
+// The amount of time (in seconds) between two background fetch calls.
+// TODO(crbug.com/496172): Re-enable background fetch.
+const NSTimeInterval kBackgroundFetchIntervalDelay =
+    UIApplicationBackgroundFetchIntervalNever;
+// The amount of time (in seconds) to wait for the user to start a new task.
+const NSTimeInterval kFirstUserActionTimeout = 30.0;
+}  // namespace
+
+namespace metrics_mediator {
+NSString* const kAppEnteredBackgroundDateKey = @"kAppEnteredBackgroundDate";
+}  // namespace metrics_mediator_constants
+
+using metrics_mediator::kAppEnteredBackgroundDateKey;
+
+@interface MetricsMediator ()<CRConnectionTypeObserverBridge> {
+  // Whether or not the crash reports present at startup have been processed to
+  // determine if the last app lifetime ended in an OOM crash.
+  BOOL _hasProcessedCrashReportsPresentAtStartup;
+
+  // Observer for the connection type.  Contains a valid object only if the
+  // metrics setting is set to wifi-only.
+  std::unique_ptr<ConnectionTypeObserverBridge> connectionTypeObserverBridge_;
+}
+
+// Starts or stops metrics recording and/or uploading.
+- (void)setMetricsEnabled:(BOOL)enabled withUploading:(BOOL)allowUploading;
+// Sets variables needed by the app_group application to collect UMA data.
+// Process the pending logs produced by extensions.
+// Called on start (cold and warm) and UMA settings change to update the
+// collecting settings in extensions.
+- (void)setAppGroupMetricsEnabled:(BOOL)enabled;
+// Processes crash reports present at startup.
+- (void)processCrashReportsPresentAtStartup;
+// Starts or stops crash recording and/or uploading.
+- (void)setBreakpadEnabled:(BOOL)enabled withUploading:(BOOL)allowUploading;
+// Starts or stops watching for wwan events.
+- (void)setWatchWWANEnabled:(BOOL)enabled;
+// Enable/disable transmission of accumulated logs and crash reports (dumps).
+- (void)setReporting:(BOOL)enableReporting;
+// Enable/Disable uploading crash reports.
+- (void)setBreakpadUploadingEnabled:(BOOL)enableUploading;
+// Returns YES if the metrics are enabled and the reporting is wifi-only.
+- (BOOL)isMetricsReportingEnabledWifiOnly;
+// Update metrics prefs on a permission (opt-in/out) change. When opting out,
+// this clears various client ids. When opting in, this resets saving crash
+// prefs, so as not to trigger upload of various stale data.
+// Mirrors the function in metrics_reporting_state.cc.
+- (void)updateMetricsPrefsOnPermissionChange:(BOOL)enabled;
+// Logs the number of tabs with UMAHistogramCount100 and allows testing.
++ (void)recordNumTabAtStartup:(int)numTabs;
+// Logs the number of tabs with UMAHistogramCount100 and allows testing.
++ (void)recordNumTabAtResume:(int)numTabs;
+
+@end
+
+@implementation MetricsMediator
+
+#pragma mark - Public methods.
+
++ (void)logStartupDuration:(id<StartupInformation>)startupInformation {
+  if (![startupInformation isColdStart])
+    return;
+
+  base::TimeDelta startDuration =
+      base::TimeTicks::Now() - [startupInformation appLaunchTime];
+  if ([startupInformation startupParameters]) {
+    UMA_HISTOGRAM_TIMES("Startup.ColdStartWithExternalURLTime", startDuration);
+  } else {
+    UMA_HISTOGRAM_TIMES("Startup.ColdStartWithoutExternalURLTime",
+                        startDuration);
+  }
+}
+
++ (void)logDateInUserDefaults {
+  [[NSUserDefaults standardUserDefaults]
+      setObject:[NSDate date]
+         forKey:metrics_mediator::kAppEnteredBackgroundDateKey];
+}
+
++ (void)logLaunchMetricsWithStartupInformation:
+            (id<StartupInformation>)startupInformation
+                        browserViewInformation:
+                            (id<BrowserViewInformation>)browserViewInformation {
+  int numTabs = static_cast<int>([[browserViewInformation mainTabModel] count]);
+  if (startupInformation.isColdStart) {
+    [self recordNumTabAtStartup:numTabs];
+  } else {
+    [self recordNumTabAtResume:numTabs];
+  }
+
+  if (UIAccessibilityIsVoiceOverRunning()) {
+    base::RecordAction(
+        base::UserMetricsAction("MobileVoiceOverActiveOnLaunch"));
+  }
+
+  // Create the first user action recorder and schedule a task to expire it
+  // after some timeout. If unable to determine the last time the app entered
+  // the background (i.e. either first run or restore after crash), don't bother
+  // recording the first user action since fresh start wouldn't be triggered.
+  NSDate* lastAppClose = [[NSUserDefaults standardUserDefaults]
+      objectForKey:kAppEnteredBackgroundDateKey];
+  if (lastAppClose) {
+    NSTimeInterval interval = -[lastAppClose timeIntervalSinceNow];
+    [startupInformation
+        activateFirstUserActionRecorderWithBackgroundTime:interval];
+    GURL ntpUrl = GURL(kChromeUINewTabURL);
+
+    Tab* currentTab = [[browserViewInformation currentTabModel] currentTab];
+    if (currentTab && [currentTab url] == ntpUrl) {
+      startupInformation.firstUserActionRecorder->RecordStartOnNTP();
+      [startupInformation resetFirstUserActionRecorder];
+    } else {
+      [startupInformation
+          expireFirstUserActionRecorderAfterDelay:kFirstUserActionTimeout];
+    }
+    // Remove the value so it's not reused if the app crashes.
+    [[NSUserDefaults standardUserDefaults]
+        removeObjectForKey:kAppEnteredBackgroundDateKey];
+  }
+}
+
+- (void)updateMetricsStateBasedOnPrefsUserTriggered:(BOOL)isUserTriggered {
+  BOOL optIn = [self areMetricsEnabled];
+  BOOL allowUploading = [self isUploadingEnabled];
+  BOOL wifiOnly = GetApplicationContext()->GetLocalState()->GetBoolean(
+      prefs::kMetricsReportingWifiOnly);
+
+  if (isUserTriggered)
+    [self updateMetricsPrefsOnPermissionChange:optIn];
+  [self setMetricsEnabled:optIn withUploading:allowUploading];
+  [self setBreakpadEnabled:optIn withUploading:allowUploading];
+  [self setWatchWWANEnabled:(optIn && wifiOnly)];
+}
+
+- (BOOL)areMetricsEnabled {
+// If this if-def changes, it needs to be changed in
+// IOSChromeMainParts::IsMetricsReportingEnabled and settings_egtest.mm.
+#if defined(GOOGLE_CHROME_BUILD)
+  BOOL optIn = GetApplicationContext()->GetLocalState()->GetBoolean(
+      metrics::prefs::kMetricsReportingEnabled);
+#else
+  // If a startup crash has been requested, then pretend that metrics have been
+  // enabled, so that the app will go into recovery mode.
+  BOOL optIn = experimental_flags::IsStartupCrashEnabled();
+#endif
+  return optIn;
+}
+
+- (BOOL)isUploadingEnabled {
+  BOOL optIn = [self areMetricsEnabled];
+  BOOL wifiOnly = GetApplicationContext()->GetLocalState()->GetBoolean(
+      prefs::kMetricsReportingWifiOnly);
+  BOOL allowUploading = optIn;
+  if (optIn && wifiOnly) {
+    BOOL usingWWAN = net::NetworkChangeNotifier::IsConnectionCellular(
+        net::NetworkChangeNotifier::GetConnectionType());
+    allowUploading = !usingWWAN;
+  }
+  return allowUploading;
+}
+
+#pragma mark - Internal methods.
+
+- (void)setMetricsEnabled:(BOOL)enabled withUploading:(BOOL)allowUploading {
+  metrics::MetricsService* metrics =
+      GetApplicationContext()->GetMetricsService();
+  DCHECK(metrics);
+  if (!metrics)
+    return;
+  if (enabled) {
+    [[UIApplication sharedApplication]
+        setMinimumBackgroundFetchInterval:kBackgroundFetchIntervalDelay];
+    if (!metrics->recording_active())
+      metrics->Start();
+
+    if (allowUploading)
+      metrics->EnableReporting();
+    else
+      metrics->DisableReporting();
+  } else {
+    if (metrics->recording_active())
+      metrics->Stop();
+    [[UIApplication sharedApplication]
+        setMinimumBackgroundFetchInterval:
+            UIApplicationBackgroundFetchIntervalNever];
+  }
+}
+
+- (void)setAppGroupMetricsEnabled:(BOOL)enabled {
+  metrics::MetricsService* metrics =
+      GetApplicationContext()->GetMetricsService();
+  if (enabled) {
+    PrefService* prefs = GetApplicationContext()->GetLocalState();
+    NSString* brandCode =
+        base::SysUTF8ToNSString(ios::GetChromeBrowserProvider()
+                                    ->GetAppDistributionProvider()
+                                    ->GetDistributionBrandCode());
+    app_group::main_app::EnableMetrics(
+        base::SysUTF8ToNSString(metrics->GetClientId()), brandCode,
+        prefs->GetInt64(metrics::prefs::kInstallDate),
+        prefs->GetInt64(metrics::prefs::kMetricsReportingEnabledTimestamp));
+  } else {
+    app_group::main_app::DisableMetrics();
+  }
+
+  // If metrics are enabled, process the logs. Otherwise, just delete them.
+  base::mac::ScopedBlock<app_group::ProceduralBlockWithData> callback;
+  if (enabled) {
+    callback.reset(
+        ^(NSData* log_content) {
+          std::string log(static_cast<const char*>([log_content bytes]),
+                          static_cast<size_t>([log_content length]));
+          web::WebThread::PostTask(web::WebThread::UI, FROM_HERE,
+                                   base::BindBlock(^{
+                                     metrics->PushExternalLog(log);
+                                   }));
+        },
+        base::scoped_policy::RETAIN);
+  }
+
+  web::WebThread::PostTask(
+      web::WebThread::FILE, FROM_HERE,
+      base::Bind(&app_group::main_app::ProcessPendingLogs, callback));
+}
+
+- (void)processCrashReportsPresentAtStartup {
+  _hasProcessedCrashReportsPresentAtStartup = YES;
+
+  breakpad_helper::GetCrashReportCount(^(int crashReportCount) {
+    [[CrashReportBackgroundUploader sharedInstance]
+        setHasPendingCrashReportsToUploadAtStartup:(crashReportCount > 0)];
+  });
+}
+
+- (void)setBreakpadEnabled:(BOOL)enabled withUploading:(BOOL)allowUploading {
+  if (enabled) {
+    breakpad_helper::SetEnabled(true);
+
+    // Do some processing of the crash reports present at startup. Note that
+    // this processing must be done before uploading is enabled because once
+    // uploading starts the number of crash reports present will begin to
+    // decrease as they are uploaded. The ordering is ensured here because both
+    // the crash report processing and the upload enabling are handled by
+    // posting blocks to a single |dispath_queue_t| in BreakpadController.
+    if (!_hasProcessedCrashReportsPresentAtStartup && allowUploading) {
+      [self processCrashReportsPresentAtStartup];
+    }
+    [self setBreakpadUploadingEnabled:(![[PreviousSessionInfo sharedInstance]
+                                           isFirstSessionAfterUpgrade] &&
+                                       allowUploading)];
+  } else {
+    breakpad_helper::SetEnabled(false);
+  }
+}
+
+- (void)setWatchWWANEnabled:(BOOL)enabled {
+  if (!enabled) {
+    connectionTypeObserverBridge_.reset();
+    return;
+  }
+
+  if (!connectionTypeObserverBridge_) {
+    connectionTypeObserverBridge_.reset(new ConnectionTypeObserverBridge(self));
+  }
+}
+
+- (void)updateMetricsPrefsOnPermissionChange:(BOOL)enabled {
+  // TODO(crbug.com/635669): Consolidate with metrics_reporting_state.cc
+  // function.
+  metrics::MetricsService* metrics =
+      GetApplicationContext()->GetMetricsService();
+  DCHECK(metrics);
+  if (!metrics)
+    return;
+  if (enabled) {
+    // When a user opts in to the metrics reporting service, the previously
+    // collected data should be cleared to ensure that nothing is reported
+    // before a user opts in and all reported data is accurate.
+    if (!metrics->recording_active())
+      metrics->ClearSavedStabilityMetrics();
+  } else {
+    // Clear the client id pref when opting out.
+    // Note: Clearing client id will not affect the running state (e.g. field
+    // trial randomization), as the pref is only read on startup.
+    GetApplicationContext()->GetLocalState()->ClearPref(
+        metrics::prefs::kMetricsClientID);
+    GetApplicationContext()->GetLocalState()->ClearPref(
+        metrics::prefs::kMetricsReportingEnabledTimestamp);
+    crash_keys::ClearMetricsClientId();
+  }
+}
+
++ (void)disableReporting {
+  breakpad_helper::SetUploadingEnabled(false);
+  metrics::MetricsService* metrics =
+      GetApplicationContext()->GetMetricsService();
+  DCHECK(metrics);
+  metrics->DisableReporting();
+}
+
++ (void)applicationDidEnterBackground:(NSInteger)memoryWarningCount {
+  base::RecordAction(base::UserMetricsAction("MobileEnteredBackground"));
+  UMA_HISTOGRAM_COUNTS_100("MemoryWarning.OccurrencesPerSession",
+                           memoryWarningCount);
+}
+
+#pragma mark - CRConnectionTypeObserverBridge implementation
+
+- (void)connectionTypeChanged:(net::NetworkChangeNotifier::ConnectionType)type {
+  BOOL wwanEnabled = net::NetworkChangeNotifier::IsConnectionCellular(type);
+  // Currently the MainController only cares about WWAN state for the metrics
+  // reporting preference.  If it's disabled, or the wifi-only preference is
+  // not set, we don't care.  In fact, we should not even be getting this call.
+  DCHECK([self isMetricsReportingEnabledWifiOnly]);
+  // |wwanEnabled| is true if a cellular connection such as EDGE or GPRS is
+  // used. Otherwise, either there is no connection available, or another link
+  // (such as WiFi) is used.
+  if (wwanEnabled) {
+    // If WWAN mode is on, wifi-only prefs should be disabled.
+    // For the crash reporter, we still want to record the crashes.
+    [self setBreakpadUploadingEnabled:NO];
+    [self setReporting:NO];
+  } else if ([self areMetricsEnabled]) {
+    // Double-check that the metrics reporting preference is enabled.
+    if (![[PreviousSessionInfo sharedInstance] isFirstSessionAfterUpgrade])
+      [self setBreakpadUploadingEnabled:YES];
+    [self setReporting:YES];
+  }
+}
+
+#pragma mark - interfaces methods
+
++ (void)recordNumTabAtStartup:(int)numTabs {
+  UMA_HISTOGRAM_COUNTS_100("Tabs.CountAtStartup", numTabs);
+}
+
++ (void)recordNumTabAtResume:(int)numTabs {
+  UMA_HISTOGRAM_COUNTS_100("Tabs.CountAtResume", numTabs);
+}
+
+- (void)setBreakpadUploadingEnabled:(BOOL)enableUploading {
+  breakpad_helper::SetUploadingEnabled(enableUploading);
+}
+
+- (void)setReporting:(BOOL)enableReporting {
+  if (enableReporting) {
+    GetApplicationContext()->GetMetricsService()->EnableReporting();
+  } else {
+    GetApplicationContext()->GetMetricsService()->DisableReporting();
+  }
+}
+
+- (BOOL)isMetricsReportingEnabledWifiOnly {
+  return GetApplicationContext()->GetLocalState()->GetBoolean(
+             metrics::prefs::kMetricsReportingEnabled) &&
+         GetApplicationContext()->GetLocalState()->GetBoolean(
+             prefs::kMetricsReportingWifiOnly);
+}
+
+@end
diff --git a/ios/chrome/app/application_delegate/metrics_mediator_testing.h b/ios/chrome/app/application_delegate/metrics_mediator_testing.h
new file mode 100644
index 0000000..81791a6
--- /dev/null
+++ b/ios/chrome/app/application_delegate/metrics_mediator_testing.h
@@ -0,0 +1,18 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_APPLICATION_DELEGATE_METRICS_MEDIATOR_TESTING_H_
+#define IOS_CHROME_APP_APPLICATION_DELEGATE_METRICS_MEDIATOR_TESTING_H_
+
+#include "net/base/network_change_notifier.h"
+
+@interface MetricsMediator (TestingAddition)
+- (void)processCrashReportsPresentAtStartup;
+- (void)connectionTypeChanged:(net::NetworkChangeNotifier::ConnectionType)type;
+- (void)setBreakpadUploadingEnabled:(BOOL)enableUploading;
+- (void)setReporting:(BOOL)enableReporting;
+- (BOOL)isMetricsReportingEnabledWifiOnly;
+@end
+
+#endif  // IOS_CHROME_APP_APPLICATION_DELEGATE_METRICS_MEDIATOR_TESTING_H_
diff --git a/ios/chrome/app/application_delegate/metrics_mediator_unittest.mm b/ios/chrome/app/application_delegate/metrics_mediator_unittest.mm
new file mode 100644
index 0000000..67b1ca2
--- /dev/null
+++ b/ios/chrome/app/application_delegate/metrics_mediator_unittest.mm
@@ -0,0 +1,276 @@
+// Copyright 2016 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/application_delegate/metrics_mediator.h"
+#import "ios/chrome/app/application_delegate/metrics_mediator_testing.h"
+
+#import <Foundation/Foundation.h>
+
+#include "base/mac/scoped_block.h"
+#include "base/mac/scoped_nsobject.h"
+#import "breakpad/src/client/ios/BreakpadController.h"
+#include "components/metrics/metrics_service.h"
+#import "ios/chrome/app/application_delegate/startup_information.h"
+#include "ios/chrome/browser/application_context.h"
+#import "ios/chrome/browser/metrics/previous_session_info.h"
+#import "ios/chrome/browser/metrics/previous_session_info_private.h"
+#import "ios/chrome/browser/tabs/tab_model.h"
+#import "ios/chrome/browser/ui/main/browser_view_information.h"
+#import "ios/chrome/test/base/scoped_block_swizzler.h"
+#import "ios/chrome/test/ocmock/OCMockObject+BreakpadControllerTesting.h"
+#include "net/base/network_change_notifier.h"
+#include "testing/platform_test.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
+#include "third_party/ocmock/gtest_support.h"
+
+#pragma mark - connectionTypeChanged tests.
+
+// Mock class for testing MetricsMediator.
+@interface MetricsMediatorMock : MetricsMediator
+@property(nonatomic) NSInteger reportingValue;
+@property(nonatomic) NSInteger breakpadUpload;
+- (void)reset;
+- (void)setReporting:(BOOL)enableReporting;
+@end
+
+@implementation MetricsMediatorMock
+@synthesize reportingValue = _reportingValue;
+@synthesize breakpadUpload = _breakpadUpload;
+
+- (void)reset {
+  _reportingValue = -1;
+  _breakpadUpload = -1;
+}
+- (void)setBreakpadUploadingEnabled:(BOOL)enableUploading {
+  _breakpadUpload = enableUploading ? 1 : 0;
+}
+- (void)setReporting:(BOOL)enableReporting {
+  _reportingValue = enableReporting ? 1 : 0;
+}
+- (BOOL)isMetricsReportingEnabledWifiOnly {
+  return YES;
+}
+- (BOOL)areMetricsEnabled {
+  return YES;
+}
+
+@end
+
+// Gives the differents net::NetworkChangeNotifier::ConnectionType based on
+// scenario number.
+net::NetworkChangeNotifier::ConnectionType getConnectionType(int number) {
+  switch (number) {
+    case 0:
+      return net::NetworkChangeNotifier::CONNECTION_UNKNOWN;
+    case 1:
+      return net::NetworkChangeNotifier::CONNECTION_ETHERNET;
+    case 2:
+      return net::NetworkChangeNotifier::CONNECTION_WIFI;
+    case 3:
+      return net::NetworkChangeNotifier::CONNECTION_2G;
+    case 4:
+      return net::NetworkChangeNotifier::CONNECTION_3G;
+    case 5:
+      return net::NetworkChangeNotifier::CONNECTION_4G;
+    case 6:
+      return net::NetworkChangeNotifier::CONNECTION_NONE;
+    case 7:
+      return net::NetworkChangeNotifier::CONNECTION_BLUETOOTH;
+    default:
+      return net::NetworkChangeNotifier::CONNECTION_UNKNOWN;
+  }
+}
+
+// Gives the differents expected value based on scenario number.
+int getExpectedValue(int number) {
+  if (number > 2 && number < 6)
+    return 0;
+  return 1;
+}
+
+// Verifies that connectionTypeChanged correctly enables or disables the
+// uploading in the breakpad and in the metrics service.
+TEST(MetricsMediatorTest, connectionTypeChanged) {
+  [[PreviousSessionInfo sharedInstance] setIsFirstSessionAfterUpgrade:NO];
+  base::scoped_nsobject<MetricsMediatorMock> mock_metrics_helper(
+      [[MetricsMediatorMock alloc] init]);
+
+  // Checks all different scenarios.
+  for (int i = 0; i < 8; ++i) {
+    [mock_metrics_helper reset];
+    [mock_metrics_helper connectionTypeChanged:getConnectionType(i)];
+    EXPECT_EQ(getExpectedValue(i), [mock_metrics_helper reportingValue]);
+    EXPECT_EQ(getExpectedValue(i), [mock_metrics_helper breakpadUpload]);
+  }
+
+  // Checks that no new ConnectionType has been added.
+  EXPECT_EQ(net::NetworkChangeNotifier::CONNECTION_BLUETOOTH,
+            net::NetworkChangeNotifier::CONNECTION_LAST);
+}
+
+#pragma mark - logLaunchMetrics tests.
+
+// A block that takes as arguments the caller and the arguments from
+// UserActivityHandler +handleStartupParameters and returns nothing.
+typedef void (^logLaunchMetricsBlock)(id, const char*, int);
+
+class MetricsMediatorLogLaunchTest : public PlatformTest {
+ protected:
+  MetricsMediatorLogLaunchTest() : has_been_called_(FALSE) {}
+
+  void initiateMetricsMediator(BOOL coldStart, int tabCount) {
+    id mainTabModel = [OCMockObject mockForClass:[TabModel class]];
+    [[[mainTabModel stub] andReturnValue:@(tabCount)] count];
+    browser_view_information_ =
+        [OCMockObject mockForProtocol:@protocol(BrowserViewInformation)];
+    [[[browser_view_information_ stub] andReturn:mainTabModel] mainTabModel];
+
+    swizzle_block_.reset([^(id self, int numTab) {
+      has_been_called_ = YES;
+      // Tests.
+      EXPECT_EQ(tabCount, numTab);
+    } copy]);
+    if (coldStart) {
+      uma_histogram_swizzler_.reset(new ScopedBlockSwizzler(
+          [MetricsMediator class], @selector(recordNumTabAtStartup:),
+          swizzle_block_));
+    } else {
+      uma_histogram_swizzler_.reset(new ScopedBlockSwizzler(
+          [MetricsMediator class], @selector(recordNumTabAtResume:),
+          swizzle_block_));
+    }
+  }
+
+  void verifySwizzleHasBeenCalled() { EXPECT_TRUE(has_been_called_); }
+
+  id getBrowserViewInformation() { return browser_view_information_; }
+
+ private:
+  id browser_view_information_;
+  __block BOOL has_been_called_;
+  base::mac::ScopedBlock<logLaunchMetricsBlock> swizzle_block_;
+  std::unique_ptr<ScopedBlockSwizzler> uma_histogram_swizzler_;
+};
+
+// Verifies that the log of the number of open tabs is sent and verifies
+TEST_F(MetricsMediatorLogLaunchTest,
+       logLaunchMetricsWithEnteredBackgroundDate) {
+  // Setup.
+  BOOL coldStart = YES;
+  initiateMetricsMediator(coldStart, 23);
+
+  const NSTimeInterval kFirstUserActionTimeout = 30.0;
+
+  id startupInformation =
+      [OCMockObject niceMockForProtocol:@protocol(StartupInformation)];
+  [[[startupInformation stub] andReturnValue:@(coldStart)] isColdStart];
+  [[startupInformation expect]
+      expireFirstUserActionRecorderAfterDelay:kFirstUserActionTimeout];
+
+  id currentTabModel = [OCMockObject mockForClass:[TabModel class]];
+  [[[currentTabModel stub] andReturn:nil] currentTab];
+
+  id browserViewInformation = getBrowserViewInformation();
+  [[[browserViewInformation stub] andReturn:currentTabModel] currentTabModel];
+
+  [[NSUserDefaults standardUserDefaults]
+      setObject:[NSDate date]
+         forKey:metrics_mediator::kAppEnteredBackgroundDateKey];
+
+  // Action.
+  [MetricsMediator
+      logLaunchMetricsWithStartupInformation:startupInformation
+                      browserViewInformation:getBrowserViewInformation()];
+
+  // Tests.
+  NSDate* dateStored = [[NSUserDefaults standardUserDefaults]
+      objectForKey:metrics_mediator::kAppEnteredBackgroundDateKey];
+  EXPECT_EQ(nil, dateStored);
+  verifySwizzleHasBeenCalled();
+  EXPECT_OCMOCK_VERIFY(startupInformation);
+}
+
+// Verifies that +logLaunchMetrics logs of the number of open tabs and nothing
+// more if the background date is not set;
+TEST_F(MetricsMediatorLogLaunchTest, logLaunchMetricsNoBackgroundDate) {
+  // Setup.
+  BOOL coldStart = NO;
+  initiateMetricsMediator(coldStart, 32);
+
+  id startupInformation =
+      [OCMockObject mockForProtocol:@protocol(StartupInformation)];
+  [[[startupInformation stub] andReturnValue:@(coldStart)] isColdStart];
+
+  [[NSUserDefaults standardUserDefaults]
+      removeObjectForKey:metrics_mediator::kAppEnteredBackgroundDateKey];
+
+  // Action.
+  [MetricsMediator
+      logLaunchMetricsWithStartupInformation:startupInformation
+                      browserViewInformation:getBrowserViewInformation()];
+
+  // Tests.
+  verifySwizzleHasBeenCalled();
+}
+
+// Tests that +logDateInUserDefaults logs the date in UserDefaults.
+TEST(MetricsMediatorNoFixtureTest, logDateInUserDefaultsTest) {
+  // Setup.
+  [[NSUserDefaults standardUserDefaults]
+      removeObjectForKey:metrics_mediator::kAppEnteredBackgroundDateKey];
+
+  NSDate* lastAppClose = [[NSUserDefaults standardUserDefaults]
+      objectForKey:metrics_mediator::kAppEnteredBackgroundDateKey];
+
+  ASSERT_EQ(nil, lastAppClose);
+
+  // Action.
+  [MetricsMediator logDateInUserDefaults];
+
+  // Setup.
+  lastAppClose = [[NSUserDefaults standardUserDefaults]
+      objectForKey:metrics_mediator::kAppEnteredBackgroundDateKey];
+  EXPECT_NE(nil, lastAppClose);
+}
+
+#pragma mark - processCrashReportsPresentAtStartup tests.
+
+class MetricsMediatorShutdownTypeTest : public testing::TestWithParam<int> {};
+
+// Verifies that the Breakpad controller gets called appropriately when
+// processCrashReportsPresentAtStartup is invoked.
+//
+// This parameterized test receives an int parameter that is the crash log
+// count waiting to be processed.
+TEST_P(MetricsMediatorShutdownTypeTest, ProcessCrashReportsPresentAtStartup) {
+  // Create a MainController.
+  base::scoped_nsobject<MetricsMediator> metric_helper(
+      [[MetricsMediator alloc] init]);
+
+  // Create a mock for BreakpadController and swizzle
+  // +[BreakpadController sharedInstance] to return the mock instead of the
+  // normal singleton instance.
+  base::scoped_nsobject<id> mock_breakpad_controller(
+      [[OCMockObject mockForClass:[BreakpadController class]] retain]);
+
+  id implementation_block = ^BreakpadController*(id self) {
+    return mock_breakpad_controller.get();
+  };
+  ScopedBlockSwizzler breakpad_controller_shared_instance_swizzler(
+      [BreakpadController class], @selector(sharedInstance),
+      implementation_block);
+
+  // Create the mock expectation for calling
+  // -[BreakpadController getCrashReportCount].
+  [mock_breakpad_controller cr_expectGetCrashReportCount:GetParam()];
+
+  // Now call the method under test and verify that the Breakpad controller got
+  // called appropriately.
+  [metric_helper processCrashReportsPresentAtStartup];
+  EXPECT_OCMOCK_VERIFY(mock_breakpad_controller.get());
+}
+
+INSTANTIATE_TEST_CASE_P(/* No InstantiationName */,
+                        MetricsMediatorShutdownTypeTest,
+                        ::testing::Values(0, 1, 42));
diff --git a/ios/chrome/app/application_delegate/mock_tab_opener.h b/ios/chrome/app/application_delegate/mock_tab_opener.h
new file mode 100644
index 0000000..fd1e8be
--- /dev/null
+++ b/ios/chrome/app/application_delegate/mock_tab_opener.h
@@ -0,0 +1,27 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_APPLICATION_DELEGATE_MOCK_TAB_OPENER_H_
+#define IOS_CHROME_APP_APPLICATION_DELEGATE_MOCK_TAB_OPENER_H_
+
+#import <Foundation/Foundation.h>
+
+#import "ios/chrome/app/application_delegate/tab_opening.h"
+
+class GURL;
+
+// Mocks a class adopting the TabOpening protocol. It save the arguments of
+// -dismissModalsAndOpenSelectedTabInMode:withURL:transition:completion:.
+@interface MockTabOpener : NSObject<TabOpening>
+// Arguments for
+// -dismissModalsAndOpenSelectedTabInMode:withURL:transition:completion:.
+@property(nonatomic, readonly) GURL url;
+@property(nonatomic, readonly) ApplicationMode applicationMode;
+@property(nonatomic, readonly) void (^completionBlock)(void);
+
+// Clear the URL.
+- (void)resetURL;
+@end
+
+#endif  // IOS_CHROME_APP_APPLICATION_DELEGATE_MOCK_TAB_OPENER_H_
diff --git a/ios/chrome/app/application_delegate/mock_tab_opener.mm b/ios/chrome/app/application_delegate/mock_tab_opener.mm
new file mode 100644
index 0000000..1aaf927
--- /dev/null
+++ b/ios/chrome/app/application_delegate/mock_tab_opener.mm
@@ -0,0 +1,52 @@
+// Copyright 2016 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.
+
+#include "ios/chrome/app/application_delegate/mock_tab_opener.h"
+
+#include "base/ios/block_types.h"
+#include "base/mac/scoped_block.h"
+#include "ios/chrome/app/application_mode.h"
+#include "ui/base/page_transition_types.h"
+#include "url/gurl.h"
+
+@interface MockTabOpener () {
+  base::mac::ScopedBlock<ProceduralBlock> _completionBlock;
+}
+
+@end
+
+@implementation MockTabOpener
+
+@synthesize url = _url;
+@synthesize applicationMode = _applicationMode;
+
+- (void)dismissModalsAndOpenSelectedTabInMode:(ApplicationMode)targetMode
+                                      withURL:(const GURL&)url
+                                   transition:(ui::PageTransition)transition
+                                   completion:(ProceduralBlock)handler {
+  _url = url;
+  _applicationMode = targetMode;
+  _completionBlock.reset([handler copy]);
+}
+
+- (void)resetURL {
+  _url = _url.EmptyGURL();
+}
+
+- (void (^)())completionBlock {
+  return _completionBlock;
+}
+
+- (void)openTabFromLaunchOptions:(NSDictionary*)launchOptions
+              startupInformation:(id<StartupInformation>)startupInformation
+                        appState:(AppState*)appState {
+  // Stub.
+}
+
+- (BOOL)shouldOpenNTPTabOnActivationOfTabModel:(TabModel*)tabModel {
+  // Stub.
+  return YES;
+}
+
+@end
diff --git a/ios/chrome/app/application_delegate/startup_information.h b/ios/chrome/app/application_delegate/startup_information.h
new file mode 100644
index 0000000..0f4c0ea
--- /dev/null
+++ b/ios/chrome/app/application_delegate/startup_information.h
@@ -0,0 +1,52 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_APPLICATION_DELEGATE_STARTUP_INFORMATION_H_
+#define IOS_CHROME_APP_APPLICATION_DELEGATE_STARTUP_INFORMATION_H_
+
+class FirstUserActionRecorder;
+
+namespace base {
+class TimeTicks;
+}
+
+@class AppStartupParameters;
+
+// Contains information about the startup.
+@protocol StartupInformation<NSObject>
+
+// Whether First Run UI (terms of service & sync sign-in) is being presented
+// in a modal dialog.
+@property(nonatomic) BOOL isPresentingFirstRunUI;
+// Whether the current session began from a cold start. NO if the app has
+// entered the background at least once since start up.
+@property(nonatomic) BOOL isColdStart;
+// Parameters received at startup time when the app is launched from another
+// app.
+@property(nonatomic, retain) AppStartupParameters* startupParameters;
+// Start of the application, used for UMA.
+@property(nonatomic, assign) base::TimeTicks appLaunchTime;
+// An object to record metrics related to the user's first action.
+@property(nonatomic, readonly) FirstUserActionRecorder* firstUserActionRecorder;
+
+// Disables the FirstUserActionRecorder.
+- (void)resetFirstUserActionRecorder;
+
+// Expire the FirstUserActionRecorder and disable it.
+- (void)expireFirstUserActionRecorder;
+
+// Expire the FirstUserActionRecorder and disable it after a delay.
+- (void)expireFirstUserActionRecorderAfterDelay:(NSTimeInterval)delay;
+
+// Enable the FirstUserActionRecorder with the time spent in background.
+- (void)activateFirstUserActionRecorderWithBackgroundTime:
+    (NSTimeInterval)backgroundTime;
+
+// Teardown that is needed by common Chrome code. This should not be called if
+// Chrome code is still on the stack.
+- (void)stopChromeMain;
+
+@end
+
+#endif  // IOS_CHROME_APP_APPLICATION_DELEGATE_STARTUP_INFORMATION_H_
diff --git a/ios/chrome/app/application_delegate/tab_opening.h b/ios/chrome/app/application_delegate/tab_opening.h
new file mode 100644
index 0000000..93bf745
--- /dev/null
+++ b/ios/chrome/app/application_delegate/tab_opening.h
@@ -0,0 +1,38 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_APPLICATION_DELEGATE_TAB_OPENING_H_
+#define IOS_CHROME_APP_APPLICATION_DELEGATE_TAB_OPENING_H_
+
+#include "base/ios/block_types.h"
+#include "ios/chrome/app/application_mode.h"
+#include "ui/base/page_transition_types.h"
+
+@class AppState;
+@class TabModel;
+@protocol StartupInformation;
+class GURL;
+
+// Protocol for object that can open new tabs during application launch.
+@protocol TabOpening<NSObject>
+
+// Dismisses any modal view then opens either a normal or incognito tab with
+// |url|. After opening |url|, run completion |handler| if it is not nil.
+- (void)dismissModalsAndOpenSelectedTabInMode:(ApplicationMode)targetMode
+                                      withURL:(const GURL&)url
+                                   transition:(ui::PageTransition)transition
+                                   completion:(ProceduralBlock)handler;
+
+// Creates a new tab if the launch options are not null.
+- (void)openTabFromLaunchOptions:(NSDictionary*)launchOptions
+              startupInformation:(id<StartupInformation>)startupInformation
+                        appState:(AppState*)appState;
+
+// Returns whether an NTP tab should be opened when the specified tabModel is
+// made current.
+- (BOOL)shouldOpenNTPTabOnActivationOfTabModel:(TabModel*)tabModel;
+
+@end
+
+#endif  // IOS_CHROME_APP_APPLICATION_DELEGATE_TAB_OPENING_H_
diff --git a/ios/chrome/app/application_delegate/tab_switching.h b/ios/chrome/app/application_delegate/tab_switching.h
new file mode 100644
index 0000000..b3bca34
--- /dev/null
+++ b/ios/chrome/app/application_delegate/tab_switching.h
@@ -0,0 +1,21 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_APPLICATION_DELEGATE_TAB_SWITCHING_H_
+#define IOS_CHROME_APP_APPLICATION_DELEGATE_TAB_SWITCHING_H_
+
+#import <UIKit/UIKit.h>
+
+@protocol TabSwitcher;
+
+// Handles TabSwitcher interactions.
+@protocol TabSwitching<NSObject>
+
+// Opens a new tab with animation if presenting the tab switcher.
+// Returns whether it opened a new tab.
+- (BOOL)openNewTabFromTabSwitcher;
+
+@end
+
+#endif  // IOS_CHROME_APP_APPLICATION_DELEGATE_TAB_SWITCHING_H_
diff --git a/ios/chrome/app/application_delegate/url_opener.h b/ios/chrome/app/application_delegate/url_opener.h
new file mode 100644
index 0000000..2238023
--- /dev/null
+++ b/ios/chrome/app/application_delegate/url_opener.h
@@ -0,0 +1,39 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_APPLICATION_DELEGATE_URL_OPENER_H_
+#define IOS_CHROME_APP_APPLICATION_DELEGATE_URL_OPENER_H_
+
+#import <UIKit/UIKit.h>
+
+@class AppState;
+@protocol StartupInformation;
+@protocol TabOpening;
+
+// Handles the URL-opening methods of the ApplicationDelegate. This class has
+// only class methods and should not be instantiated.
+@interface URLOpener : NSObject
+
+- (instancetype)init NS_UNAVAILABLE;
+
+// Handles open URL. The registered URL Schemes are defined in project
+// variables ${CHROMIUM_URL_SCHEME_x}.
+// The url can either be empty, in which case the app is simply opened or
+// can contain an URL that will be opened in a new tab.
+// Returns YES if the url can be opened, NO otherwise.
++ (BOOL)openURL:(NSURL*)url
+     applicationActive:(BOOL)applicationActive
+               options:(NSDictionary<NSString*, id>*)options
+             tabOpener:(id<TabOpening>)tabOpener
+    startupInformation:(id<StartupInformation>)startupInformation;
+
+// Handles launch options: converts them to open URL options and opens them.
++ (void)handleLaunchOptions:(NSDictionary*)launchOptions
+          applicationActive:(BOOL)applicationActive
+                  tabOpener:(id<TabOpening>)tabOpener
+         startupInformation:(id<StartupInformation>)startupInformation
+                   appState:(AppState*)appState;
+@end
+
+#endif  // IOS_CHROME_APP_APPLICATION_DELEGATE_URL_OPENER_H_
diff --git a/ios/chrome/app/application_delegate/url_opener.mm b/ios/chrome/app/application_delegate/url_opener.mm
new file mode 100644
index 0000000..718d470
--- /dev/null
+++ b/ios/chrome/app/application_delegate/url_opener.mm
@@ -0,0 +1,97 @@
+// Copyright 2016 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/application_delegate/url_opener.h"
+
+#import <Foundation/Foundation.h>
+
+#import "base/ios/weak_nsobject.h"
+#import "base/mac/scoped_nsobject.h"
+#include "base/metrics/histogram_macros.h"
+#import "ios/chrome/app/application_delegate/app_state.h"
+#import "ios/chrome/app/application_delegate/startup_information.h"
+#import "ios/chrome/app/application_delegate/tab_opening.h"
+#include "ios/chrome/app/chrome_app_startup_parameters.h"
+
+namespace {
+// Key of the UMA Startup.MobileSessionStartFromApps histogram.
+const char* const kUMAMobileSessionStartFromAppsHistogram =
+    "Startup.MobileSessionStartFromApps";
+}  // namespace
+
+@implementation URLOpener
+
+- (instancetype)init {
+  NOTREACHED();
+  return nil;
+}
+
+#pragma mark - ApplicationDelegate - URL Opening methods
+
++ (BOOL)openURL:(NSURL*)url
+     applicationActive:(BOOL)applicationActive
+               options:(NSDictionary<NSString*, id>*)options
+             tabOpener:(id<TabOpening>)tabOpener
+    startupInformation:(id<StartupInformation>)startupInformation {
+  NSString* sourceApplication =
+      options[UIApplicationOpenURLOptionsSourceApplicationKey];
+  base::scoped_nsobject<ChromeAppStartupParameters> params(
+      [ChromeAppStartupParameters
+          newChromeAppStartupParametersWithURL:url
+                         fromSourceApplication:sourceApplication]);
+
+  MobileSessionCallerApp callerApp = [params callerApp];
+
+  UMA_HISTOGRAM_ENUMERATION(kUMAMobileSessionStartFromAppsHistogram, callerApp,
+                            MOBILE_SESSION_CALLER_APP_COUNT);
+
+  if (startupInformation.isPresentingFirstRunUI) {
+    UMA_HISTOGRAM_ENUMERATION("FirstRun.LaunchSource", [params launchSource],
+                              first_run::LAUNCH_SIZE);
+  }
+
+  if (applicationActive) {
+    // The app is already active so the applicationDidBecomeActive: method will
+    // never be called. Open the requested URL immediately and return YES if
+    // the parsed URL was valid.
+    if (params) {
+      [tabOpener dismissModalsAndOpenSelectedTabInMode:ApplicationMode::NORMAL
+                                               withURL:[params externalURL]
+                                            transition:ui::PAGE_TRANSITION_LINK
+                                            completion:nil];
+      return YES;
+    }
+    return NO;
+  }
+
+  // Don't record the first user action.
+  [startupInformation resetFirstUserActionRecorder];
+
+  startupInformation.startupParameters = params;
+  return startupInformation.startupParameters != nil;
+}
+
++ (void)handleLaunchOptions:(NSDictionary*)launchOptions
+          applicationActive:(BOOL)applicationActive
+                  tabOpener:(id<TabOpening>)tabOpener
+         startupInformation:(id<StartupInformation>)startupInformation
+                   appState:(AppState*)appState {
+  NSURL* url = launchOptions[UIApplicationLaunchOptionsURLKey];
+  NSString* sourceApplication =
+      launchOptions[UIApplicationLaunchOptionsSourceApplicationKey];
+
+  if (url && sourceApplication) {
+    NSDictionary<NSString*, id>* options =
+        @{UIApplicationOpenURLOptionsSourceApplicationKey : sourceApplication};
+
+    BOOL openURLResult = [URLOpener openURL:url
+                          applicationActive:applicationActive
+                                    options:options
+                                  tabOpener:tabOpener
+                         startupInformation:startupInformation];
+    [appState launchFromURLHandled:openURLResult];
+  }
+}
+
+@end
diff --git a/ios/chrome/app/application_delegate/url_opener_unittest.mm b/ios/chrome/app/application_delegate/url_opener_unittest.mm
new file mode 100644
index 0000000..60ff610
--- /dev/null
+++ b/ios/chrome/app/application_delegate/url_opener_unittest.mm
@@ -0,0 +1,482 @@
+// Copyright 2016 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.
+
+#include "ios/chrome/app/application_delegate/url_opener.h"
+
+#import <Foundation/Foundation.h>
+
+#include "base/mac/scoped_nsobject.h"
+#include "ios/chrome/app/application_delegate/app_state.h"
+#include "ios/chrome/app/application_delegate/app_state_testing.h"
+#include "ios/chrome/app/application_delegate/mock_tab_opener.h"
+#include "ios/chrome/app/chrome_app_startup_parameters.h"
+#include "ios/chrome/app/main_application_delegate.h"
+#include "ios/chrome/app/main_controller.h"
+#include "ios/chrome/app/main_controller_private.h"
+#include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
+#import "ios/chrome/browser/tabs/tab.h"
+#import "ios/chrome/browser/ui/browser_view_controller.h"
+#import "ios/chrome/test/base/scoped_block_swizzler.h"
+#import "ios/testing/ocmock_complex_type_helper.h"
+#import "net/base/mac/url_conversions.h"
+#include "testing/platform_test.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
+#include "third_party/ocmock/gtest_support.h"
+
+#pragma mark - Tab Switcher Mock
+
+// This mocks either a iPad tab switcher controller or a iPhone stack view
+// controller.
+@interface URLOpenerOCMockComplexTypeHandler : OCMockComplexTypeHelper
+@end
+
+@implementation URLOpenerOCMockComplexTypeHandler
+
+typedef Tab* (^mock_gurl_nsuinteger_pagetransition)(const GURL&,
+                                                    NSUInteger,
+                                                    ui::PageTransition);
+
+- (Tab*)dismissWithNewTabAnimationToModel:(TabModel*)targetModel
+                                  withURL:(const GURL&)url
+                                  atIndex:(NSUInteger)position
+                               transition:(ui::PageTransition)transition {
+  static_cast<mock_gurl_nsuinteger_pagetransition>(
+      [self blockForSelector:_cmd])(url, position, transition);
+  id mockTab = [OCMockObject mockForClass:[Tab class]];
+  return mockTab;
+}
+
+- (Tab*)addSelectedTabWithURL:(const GURL&)url
+                      atIndex:(NSUInteger)position
+                   transition:(ui::PageTransition)transition {
+  static_cast<mock_gurl_nsuinteger_pagetransition>(
+      [self blockForSelector:_cmd])(url, position, transition);
+  id mockTab = [OCMockObject mockForClass:[Tab class]];
+  return mockTab;
+}
+
+@end
+
+#pragma mark - BrowserViewController Mock
+
+// Mock BVC class to use for test cases where OCMock gets handled incorrectly
+// by UIViewController.
+@interface URLOpenerMockBVC : UIViewController
+@property(nonatomic, assign) ios::ChromeBrowserState* browserState;
+@property(nonatomic, assign) GURL tabURL;
+@property(nonatomic, assign) NSUInteger position;
+@property(nonatomic, assign) ui::PageTransition transition;
+
+- (Tab*)addSelectedTabWithURL:(const GURL&)url
+                      atIndex:(NSUInteger)position
+                   transition:(ui::PageTransition)transition;
+- (void)expectNewForegroundTab;
+- (void)setActive:(BOOL)active;
+- (TabModel*)tabModel;
+@end
+
+@implementation URLOpenerMockBVC
+@synthesize browserState = _browserState;
+@synthesize tabURL = _tabURL;
+@synthesize position = _position;
+@synthesize transition = _transition;
+
+- (Tab*)addSelectedTabWithURL:(const GURL&)url
+                      atIndex:(NSUInteger)position
+                   transition:(ui::PageTransition)transition {
+  self.tabURL = url;
+  self.position = position;
+  self.transition = transition;
+  return nil;
+}
+
+- (void)expectNewForegroundTab {
+  // no-op.
+}
+
+- (void)setActive:(BOOL)active {
+  // no-op
+}
+
+- (void)setPrimary:(BOOL)primary {
+  // no-op
+}
+
+- (TabModel*)tabModel {
+  return nil;
+}
+
+@end
+
+class URLOpenerTest : public PlatformTest {
+ protected:
+  MainController* GetMainController() {
+    if (!main_controller_.get()) {
+      main_controller_.reset([[MainController alloc] init]);
+      [main_controller_ setUpAsForegrounded];
+      id mainTabModel = [OCMockObject mockForClass:[TabModel class]];
+      [[mainTabModel stub] resetSessionMetrics];
+      [[mainTabModel stub] browserStateDestroyed];
+      [[mainTabModel stub] addObserver:[OCMArg any]];
+      [[mainTabModel stub] removeObserver:[OCMArg any]];
+      [[main_controller_ browserViewInformation] setMainTabModel:mainTabModel];
+    }
+    return main_controller_.get();
+  }
+
+ private:
+  base::scoped_nsobject<MainController> main_controller_;
+};
+
+TEST_F(URLOpenerTest, HandleOpenURLWithNoOpenTab) {
+  // The tab switcher controller should be dismissed with a new tab containing
+  // the external URL.
+  NSURL* url = [NSURL URLWithString:@"chromium://www.google.com"];
+  base::scoped_nsobject<ChromeAppStartupParameters> params(
+      [ChromeAppStartupParameters newChromeAppStartupParametersWithURL:url
+                                                 fromSourceApplication:nil]);
+
+  base::scoped_nsobject<id> bvcMock([[URLOpenerMockBVC alloc] init]);
+
+  base::scoped_nsobject<id> tabSwitcherController;
+  tabSwitcherController.reset([[URLOpenerOCMockComplexTypeHandler alloc]
+      initWithRepresentedObject:[OCMockObject
+                                    mockForProtocol:@protocol(UrlLoader)]]);
+
+  base::scoped_nsobject<id> block([(id) ^ (const GURL& url, NSUInteger position,
+                                           ui::PageTransition transition) {
+    EXPECT_EQ(url, [params externalURL]);
+    EXPECT_EQ(NSNotFound, static_cast<NSInteger>(position));
+    EXPECT_TRUE(PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_LINK));
+  } copy]);
+  SEL dismissSelector =
+      @selector(dismissWithNewTabAnimationToModel:withURL:atIndex:transition:);
+  [tabSwitcherController onSelector:dismissSelector callBlockExpectation:block];
+
+  // Setup main controller.
+  MainController* controller = GetMainController();
+  controller.browserViewInformation.mainBVC = bvcMock;
+  controller.tabSwitcherController = tabSwitcherController;
+  controller.tabSwitcherActive = YES;
+
+  id mainApplicationDelegate =
+      [OCMockObject mockForClass:[MainApplicationDelegate class]];
+
+  AppState* appState =
+      [[[AppState alloc] initWithBrowserLauncher:controller
+                              startupInformation:controller
+                             applicationDelegate:mainApplicationDelegate
+                                          window:controller.window
+                                   shouldOpenNTP:YES] autorelease];
+  controller.appState = appState;
+
+  NSDictionary<NSString*, id>* options = nil;
+  [URLOpener openURL:url
+       applicationActive:YES
+                 options:options
+               tabOpener:controller
+      startupInformation:controller];
+
+  EXPECT_OCMOCK_VERIFY(tabSwitcherController);
+}
+
+TEST_F(URLOpenerTest, HandleOpenURLWithOpenTabs) {
+  // The URL should be routed to the main BVC; the OTR BVC shouldn't get calls.
+  NSURL* url = [NSURL URLWithString:@"chromium://www.google.com"];
+  id otrBVCMock = [OCMockObject mockForClass:[BrowserViewController class]];
+  base::scoped_nsobject<id> bvcMock([[URLOpenerMockBVC alloc] init]);
+  TestChromeBrowserState::Builder mainBrowserStateBuilder;
+  std::unique_ptr<TestChromeBrowserState> chrome_browser_state =
+      mainBrowserStateBuilder.Build();
+  ((URLOpenerMockBVC*)bvcMock).browserState = chrome_browser_state.get();
+
+  // Setup main controller.
+  MainController* controller = GetMainController();
+  controller.browserViewInformation.mainBVC = bvcMock;
+  controller.browserViewInformation.otrBVC = otrBVCMock;
+
+  NSDictionary<NSString*, id>* options = nil;
+  [URLOpener openURL:url
+       applicationActive:YES
+                 options:options
+               tabOpener:controller
+      startupInformation:controller];
+
+  EXPECT_EQ(GURL("http://www.google.com/"),
+            [(URLOpenerMockBVC*)bvcMock tabURL]);
+  EXPECT_EQ(NSNotFound,
+            static_cast<NSInteger>([(URLOpenerMockBVC*)bvcMock position]));
+  EXPECT_TRUE(PageTransitionCoreTypeIs([(URLOpenerMockBVC*)bvcMock transition],
+                                       ui::PAGE_TRANSITION_LINK));
+  EXPECT_OCMOCK_VERIFY(otrBVCMock);
+}
+
+TEST_F(URLOpenerTest, HandleOpenURL) {
+  // A set of tests for robustness of
+  // application:openURL:options:tabOpener:startupInformation:
+  // It verifies that the function handles correctly differents URL parsed by
+  // ChromeAppStartupParameters.
+  MainController* controller = GetMainController();
+
+  // The array with the different states to tests (active, not active).
+  NSArray* applicationStatesToTest = @[ @YES, @NO ];
+
+  // Mock of TabOpening, preventing the creation of a new tab.
+  base::scoped_nsobject<MockTabOpener> tabOpener([[MockTabOpener alloc] init]);
+
+  // The keys for this dictionary is the URL to call openURL:. The value
+  // from the key is either YES or NO to indicate if this is a valid URL
+  // or not.
+  NSNumber* kIsValid = [NSNumber numberWithBool:YES];
+  NSNumber* kNotValid = [NSNumber numberWithBool:NO];
+  NSDictionary* urlsToTest = [NSDictionary
+      dictionaryWithObjectsAndKeys:
+          kNotValid, [NSNull null],
+          // Tests for http, googlechrome, and chromium scheme URLs.
+          kNotValid, @"", kIsValid, @"http://www.google.com/", kIsValid,
+          @"https://www.google.com/settings/account/", kIsValid,
+          @"googlechrome://www.google.com/", kIsValid,
+          @"googlechromes://www.google.com/settings/account/", kIsValid,
+          @"chromium://www.google.com/", kIsValid,
+          @"chromiums://www.google.com/settings/account/",
+          // Google search results page URLs.
+          kIsValid, @"https://www.google.com/search?q=pony&"
+                     "sugexp=chrome,mod=7&sourceid=chrome&ie=UTF-8",
+          kIsValid, @"googlechromes://www.google.com/search?q=pony&"
+                     "sugexp=chrome,mod=7&sourceid=chrome&ie=UTF-8",
+          // Other protocols.
+          kIsValid, @"chromium-x-callback://x-callback-url/open?url=https://"
+                     "www.google.com&x-success=http://success",
+          kIsValid, @"file://localhost/path/to/file.pdf",
+          // Invalid format input URL will be ignored.
+          kNotValid, @"this.is.not.a.valid.url",
+          // Valid format but invalid data.
+          kIsValid, @"this://is/garbage/but/valid", nil];
+  NSArray* sourcesToTest = [NSArray
+      arrayWithObjects:[NSNull null], @"", @"com.google.GoogleMobile",
+                       @"com.google.GooglePlus", @"com.google.SomeOtherProduct",
+                       @"com.apple.mobilesafari",
+                       @"com.othercompany.otherproduct", nil];
+  // See documentation for |annotation| property in
+  // UIDocumentInteractionController Class Reference.  The following values are
+  // mostly to detect garbage-in situations and ensure that the app won't crash
+  // or garbage out.
+  NSArray* annotationsToTest = [NSArray
+      arrayWithObjects:[NSNull null],
+                       [NSArray arrayWithObjects:@"foo", @"bar", nil],
+                       [NSDictionary dictionaryWithObject:@"bar" forKey:@"foo"],
+                       @"a string annotation object", nil];
+  for (id urlString in [urlsToTest allKeys]) {
+    for (id source in sourcesToTest) {
+      for (id annotation in annotationsToTest) {
+        for (NSNumber* applicationActive in applicationStatesToTest) {
+          BOOL applicationIsActive = [applicationActive boolValue];
+
+          controller.startupParameters = nil;
+          [tabOpener resetURL];
+          NSURL* testUrl = urlString == [NSNull null]
+                               ? nil
+                               : [NSURL URLWithString:urlString];
+          BOOL isValid = [[urlsToTest objectForKey:urlString] boolValue];
+          base::scoped_nsobject<NSMutableDictionary> options(
+              [[NSMutableDictionary alloc] init]);
+          if (source != [NSNull null]) {
+            [options setObject:source
+                        forKey:UIApplicationOpenURLOptionsSourceApplicationKey];
+          }
+          if (annotation != [NSNull null]) {
+            [options setObject:annotation
+                        forKey:UIApplicationOpenURLOptionsAnnotationKey];
+          }
+          base::scoped_nsobject<ChromeAppStartupParameters> params(
+              [ChromeAppStartupParameters
+                  newChromeAppStartupParametersWithURL:testUrl
+                                 fromSourceApplication:nil]);
+
+          // Action.
+          BOOL result = [URLOpener openURL:testUrl
+                         applicationActive:applicationIsActive
+                                   options:options
+                                 tabOpener:tabOpener
+                        startupInformation:controller];
+
+          // Tests.
+          EXPECT_EQ(isValid, result);
+          if (!applicationIsActive) {
+            if (result)
+              EXPECT_EQ([params externalURL],
+                        controller.startupParameters.externalURL);
+            else
+              EXPECT_EQ(nil, controller.startupParameters);
+          } else if (result) {
+            EXPECT_EQ(nil, controller.startupParameters);
+            EXPECT_EQ([params externalURL], [tabOpener url]);
+          }
+        }
+      }
+    }
+  }
+}
+
+// Tests that -handleApplication set startup parameters as expected.
+TEST_F(URLOpenerTest, VerifyLaunchOptions) {
+  // Setup.
+  NSURL* url = [NSURL URLWithString:@"chromium://www.google.com"];
+  NSDictionary* launchOptions = @{
+    UIApplicationLaunchOptionsURLKey : url,
+    UIApplicationLaunchOptionsSourceApplicationKey : @"com.apple.mobilesafari"
+  };
+
+  id tabOpenerMock = [OCMockObject mockForProtocol:@protocol(TabOpening)];
+
+  id startupInformationMock =
+      [OCMockObject mockForProtocol:@protocol(StartupInformation)];
+
+  id appStateMock = [OCMockObject mockForClass:[AppState class]];
+  [[appStateMock expect] launchFromURLHandled:YES];
+
+  __block BOOL hasBeenCalled = NO;
+
+  id implementation_block = ^BOOL(
+      id self, NSURL* urlArg, BOOL applicationActive, NSDictionary* options,
+      id<TabOpening> tabOpener, id<StartupInformation> startupInformation) {
+    hasBeenCalled = YES;
+    EXPECT_EQ([url absoluteString], [urlArg absoluteString]);
+    EXPECT_EQ(@"com.apple.mobilesafari",
+              options[UIApplicationOpenURLOptionsSourceApplicationKey]);
+    EXPECT_EQ(startupInformationMock, startupInformation);
+    EXPECT_EQ(tabOpenerMock, tabOpener);
+    return YES;
+  };
+  ScopedBlockSwizzler URL_opening_openURL_swizzler([URLOpener class],
+                                                   @selector(openURL:
+                                                        applicationActive:
+                                                                  options:
+                                                                tabOpener:
+                                                       startupInformation:),
+                                                   implementation_block);
+
+  // Action.
+  [URLOpener handleLaunchOptions:launchOptions
+               applicationActive:NO
+                       tabOpener:tabOpenerMock
+              startupInformation:startupInformationMock
+                        appState:appStateMock];
+
+  // Test.
+  EXPECT_TRUE(hasBeenCalled);
+  EXPECT_OCMOCK_VERIFY(startupInformationMock);
+}
+
+// Tests that -handleApplication set startup parameters as expected with options
+// as nil.
+TEST_F(URLOpenerTest, VerifyLaunchOptionsNil) {
+  // Creates a mock with no stub. This test will pass only if we don't use these
+  // objects.
+  id startupInformationMock =
+      [OCMockObject mockForProtocol:@protocol(StartupInformation)];
+  id appStateMock = [OCMockObject mockForClass:[AppState class]];
+
+  // Action.
+  [URLOpener handleLaunchOptions:nil
+               applicationActive:YES
+                       tabOpener:nil
+              startupInformation:startupInformationMock
+                        appState:appStateMock];
+}
+
+// Tests that -handleApplication set startup parameters as expected with no
+// source application.
+TEST_F(URLOpenerTest, VerifyLaunchOptionsWithNoSourceApplication) {
+  // Setup.
+  NSURL* url = [NSURL URLWithString:@"chromium://www.google.com"];
+  NSDictionary* launchOptions = @{
+    UIApplicationLaunchOptionsURLKey : url,
+  };
+
+  // Creates a mock with no stub. This test will pass only if we don't use these
+  // objects.
+  id startupInformationMock =
+      [OCMockObject mockForProtocol:@protocol(StartupInformation)];
+  id appStateMock = [OCMockObject mockForClass:[AppState class]];
+
+  // Action.
+  [URLOpener handleLaunchOptions:launchOptions
+               applicationActive:YES
+                       tabOpener:nil
+              startupInformation:startupInformationMock
+                        appState:appStateMock];
+}
+
+// Tests that -handleApplication set startup parameters as expected with no url.
+TEST_F(URLOpenerTest, VerifyLaunchOptionsWithNoURL) {
+  // Setup.
+  NSDictionary* launchOptions = @{
+    UIApplicationLaunchOptionsSourceApplicationKey : @"com.apple.mobilesafari"
+  };
+
+  // Creates a mock with no stub. This test will pass only if we don't use these
+  // objects.
+  id startupInformationMock =
+      [OCMockObject mockForProtocol:@protocol(StartupInformation)];
+  id appStateMock = [OCMockObject mockForClass:[AppState class]];
+
+  // Action.
+  [URLOpener handleLaunchOptions:launchOptions
+               applicationActive:YES
+                       tabOpener:nil
+              startupInformation:startupInformationMock
+                        appState:appStateMock];
+}
+
+// Tests that -handleApplication set startup parameters as expected with a bad
+// url.
+TEST_F(URLOpenerTest, VerifyLaunchOptionsWithBadURL) {
+  // Setup.
+  NSURL* url = [NSURL URLWithString:@"chromium.www.google.com"];
+  NSDictionary* launchOptions = @{
+    UIApplicationLaunchOptionsURLKey : url,
+    UIApplicationLaunchOptionsSourceApplicationKey : @"com.apple.mobilesafari"
+  };
+
+  id tabOpenerMock = [OCMockObject mockForProtocol:@protocol(TabOpening)];
+
+  id startupInformationMock =
+      [OCMockObject mockForProtocol:@protocol(StartupInformation)];
+
+  id appStateMock = [OCMockObject mockForClass:[AppState class]];
+  [[appStateMock expect] launchFromURLHandled:YES];
+
+  __block BOOL hasBeenCalled = NO;
+
+  id implementation_block = ^BOOL(
+      id self, NSURL* urlArg, BOOL applicationActive, NSDictionary* options,
+      id<TabOpening> tabOpener, id<StartupInformation> startupInformation) {
+    hasBeenCalled = YES;
+    EXPECT_EQ([url absoluteString], [urlArg absoluteString]);
+    EXPECT_EQ(@"com.apple.mobilesafari",
+              options[UIApplicationOpenURLOptionsSourceApplicationKey]);
+    EXPECT_EQ(startupInformationMock, startupInformation);
+    EXPECT_EQ(tabOpenerMock, tabOpener);
+    return YES;
+  };
+  ScopedBlockSwizzler URL_opening_openURL_swizzler([URLOpener class],
+                                                   @selector(openURL:
+                                                        applicationActive:
+                                                                  options:
+                                                                tabOpener:
+                                                       startupInformation:),
+                                                   implementation_block);
+
+  // Action.
+  [URLOpener handleLaunchOptions:launchOptions
+               applicationActive:NO
+                       tabOpener:tabOpenerMock
+              startupInformation:startupInformationMock
+                        appState:appStateMock];
+
+  // Test.
+  EXPECT_TRUE(hasBeenCalled);
+  EXPECT_OCMOCK_VERIFY(startupInformationMock);
+}
diff --git a/ios/chrome/app/application_delegate/user_activity_handler.h b/ios/chrome/app/application_delegate/user_activity_handler.h
new file mode 100644
index 0000000..4cd75d38
--- /dev/null
+++ b/ios/chrome/app/application_delegate/user_activity_handler.h
@@ -0,0 +1,50 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_APPLICATION_DELEGATE_USER_ACTIVITY_HANDLER_H_
+#define IOS_CHROME_APP_APPLICATION_DELEGATE_USER_ACTIVITY_HANDLER_H_
+
+#import <UIKit/UIKit.h>
+
+@protocol BrowserViewInformation;
+@protocol StartupInformation;
+@protocol TabOpening;
+
+// TODO(crbug.com/619598): When the refactoring is over, check if it can be
+// merged with StartupInformation.
+// Handles all events based on user activity, as defined in
+// UIApplicationDelegate.
+@interface UserActivityHandler : NSObject
+
+// If the userActivity is a Handoff or an opening from Spotlight, opens a new
+// tab or setup startupParameters to open it later.
+// Returns wether it could continue userActivity.
++ (BOOL)continueUserActivity:(NSUserActivity*)userActivity
+         applicationIsActive:(BOOL)applicationIsActive
+                   tabOpener:(id<TabOpening>)tabOpener
+          startupInformation:(id<StartupInformation>)startupInformation;
+
+// Handles the 3D touch application static items. If the First Run UI is active,
+// |completionHandler| will be called with NO.
++ (void)performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
+                   completionHandler:(void (^)(BOOL succeeded))completionHandler
+                           tabOpener:(id<TabOpening>)tabOpener
+                  startupInformation:(id<StartupInformation>)startupInformation
+              browserViewInformation:
+                  (id<BrowserViewInformation>)browserViewInformation;
+
+// Returns YES if Chrome is passing a Handoff to itself or if it is an opening
+// from Spotlight.
++ (BOOL)willContinueUserActivityWithType:(NSString*)userActivityType;
+
+// Opens a new Tab or routes to correct Tab.
++ (void)handleStartupParametersWithTabOpener:(id<TabOpening>)tabOpener
+                          startupInformation:
+                              (id<StartupInformation>)startupInformation
+                      browserViewInformation:
+                          (id<BrowserViewInformation>)browserViewInformation;
+
+@end
+
+#endif  // IOS_CHROME_APP_APPLICATION_DELEGATE_USER_ACTIVITY_HANDLER_H_
diff --git a/ios/chrome/app/application_delegate/user_activity_handler.mm b/ios/chrome/app/application_delegate/user_activity_handler.mm
new file mode 100644
index 0000000..6b3b9e8c
--- /dev/null
+++ b/ios/chrome/app/application_delegate/user_activity_handler.mm
@@ -0,0 +1,320 @@
+// Copyright 2016 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/application_delegate/user_activity_handler.h"
+
+#import <CoreSpotlight/CoreSpotlight.h>
+#import <UIKit/UIKit.h>
+
+#include "base/ios/block_types.h"
+#include "base/ios/ios_util.h"
+#include "base/ios/weak_nsobject.h"
+#include "base/mac/foundation_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/user_metrics_action.h"
+#include "base/strings/sys_string_conversions.h"
+#include "components/handoff/handoff_utility.h"
+#import "ios/chrome/app/application_delegate/startup_information.h"
+#import "ios/chrome/app/application_delegate/tab_opening.h"
+#include "ios/chrome/app/application_mode.h"
+#import "ios/chrome/app/spotlight/actions_spotlight_manager.h"
+#import "ios/chrome/app/spotlight/spotlight_util.h"
+#include "ios/chrome/browser/app_startup_parameters.h"
+#include "ios/chrome/browser/chrome_url_constants.h"
+#include "ios/chrome/browser/experimental_flags.h"
+#include "ios/chrome/browser/metrics/first_user_action_recorder.h"
+#import "ios/chrome/browser/tabs/tab.h"
+#import "ios/chrome/browser/tabs/tab_model.h"
+#import "ios/chrome/browser/u2f/u2f_controller.h"
+#import "ios/chrome/browser/ui/main/browser_view_information.h"
+#import "net/base/mac/url_conversions.h"
+#include "ui/base/page_transition_types.h"
+#include "url/gurl.h"
+
+using base::UserMetricsAction;
+
+namespace {
+// Constants for 3D touch application static shortcuts.
+NSString* const kShortcutNewTab = @"OpenNewTab";
+NSString* const kShortcutNewIncognitoTab = @"OpenIncognitoTab";
+NSString* const kShortcutVoiceSearch = @"OpenVoiceSearch";
+NSString* const kShortcutQRScanner = @"OpenQRScanner";
+}  // namespace
+
+@interface UserActivityHandler ()
+// Handles the 3D touch application static items. Does nothing if in first run.
++ (BOOL)handleShortcutItem:(UIApplicationShortcutItem*)shortcutItem
+        startupInformation:(id<StartupInformation>)startupInformation;
+// Routes Universal 2nd Factor (U2F) callback to the correct Tab.
++ (void)routeU2FURL:(const GURL&)URL
+    browserViewInformation:(id<BrowserViewInformation>)browserViewInformation;
+@end
+
+@implementation UserActivityHandler
+
+#pragma mark - Public methods.
+
++ (BOOL)continueUserActivity:(NSUserActivity*)userActivity
+         applicationIsActive:(BOOL)applicationIsActive
+                   tabOpener:(id<TabOpening>)tabOpener
+          startupInformation:(id<StartupInformation>)startupInformation {
+  NSURL* webpageURL = userActivity.webpageURL;
+
+  if ([userActivity.activityType
+          isEqualToString:handoff::kChromeHandoffActivityType]) {
+    // App was launched by iOS as a result of Handoff.
+    NSString* originString = base::mac::ObjCCast<NSString>(
+        userActivity.userInfo[handoff::kOriginKey]);
+    handoff::Origin origin = handoff::OriginFromString(originString);
+    UMA_HISTOGRAM_ENUMERATION("IOS.Handoff.Origin", origin,
+                              handoff::ORIGIN_COUNT);
+  } else if ([userActivity.activityType
+                 isEqualToString:NSUserActivityTypeBrowsingWeb]) {
+    // App was launched as the result of a Universal Link navigation. The value
+    // of userActivity.webpageURL is not used. The only supported action
+    // at this time is opening a New Tab Page.
+    GURL newTabURL(kChromeUINewTabURL);
+    webpageURL = net::NSURLWithGURL(newTabURL);
+    base::scoped_nsobject<AppStartupParameters> startupParams(
+        [[AppStartupParameters alloc] initWithExternalURL:newTabURL]);
+    [startupInformation setStartupParameters:startupParams];
+    base::RecordAction(base::UserMetricsAction("IOSLaunchedByUniversalLink"));
+  } else if (spotlight::IsSpotlightAvailable() &&
+             [userActivity.activityType
+                 isEqualToString:CSSearchableItemActionType]) {
+    // App was launched by iOS as the result of a tap on a Spotlight Search
+    // result.
+    NSString* itemID =
+        [userActivity.userInfo objectForKey:CSSearchableItemActivityIdentifier];
+    spotlight::Domain domain = spotlight::SpotlightDomainFromString(itemID);
+    if (domain == spotlight::DOMAIN_ACTIONS &&
+        !experimental_flags::IsSpotlightActionsEnabled()) {
+      return NO;
+    }
+    UMA_HISTOGRAM_ENUMERATION("IOS.Spotlight.Origin", domain,
+                              spotlight::DOMAIN_COUNT);
+
+    if (!itemID) {
+      return NO;
+    }
+    if (domain == spotlight::DOMAIN_ACTIONS) {
+      webpageURL =
+          [NSURL URLWithString:base::SysUTF8ToNSString(kChromeUINewTabURL)];
+      base::scoped_nsobject<AppStartupParameters> startupParams(
+          [[AppStartupParameters alloc]
+              initWithExternalURL:GURL(kChromeUINewTabURL)]);
+      BOOL startupParamsSet = spotlight::SetStartupParametersForSpotlightAction(
+          itemID, startupParams);
+      if (!startupParamsSet) {
+        return NO;
+      }
+      [startupInformation setStartupParameters:startupParams];
+    } else if (!webpageURL && base::ios::IsRunningOnIOS10OrLater()) {
+      // spotlight::GetURLForSpotlightItemID uses CSSearchQuery, which is only
+      // supported from iOS 10.
+      spotlight::GetURLForSpotlightItemID(itemID, ^(NSURL* contentURL) {
+        if (!contentURL) {
+          return;
+        }
+        dispatch_async(dispatch_get_main_queue(), ^{
+          // Update the isActive flag as it may have changed during the async
+          // calls.
+          BOOL isActive = [[UIApplication sharedApplication]
+                              applicationState] == UIApplicationStateActive;
+          [self continueUserActivityURL:contentURL
+                    applicationIsActive:isActive
+                              tabOpener:tabOpener
+                     startupInformation:startupInformation];
+        });
+      });
+      return YES;
+    }
+  } else {
+    // Do nothing for unknown activity type.
+    return NO;
+  }
+
+  return [self continueUserActivityURL:webpageURL
+                   applicationIsActive:applicationIsActive
+                             tabOpener:tabOpener
+                    startupInformation:startupInformation];
+}
+
++ (BOOL)continueUserActivityURL:(NSURL*)webpageURL
+            applicationIsActive:(BOOL)applicationIsActive
+                      tabOpener:(id<TabOpening>)tabOpener
+             startupInformation:(id<StartupInformation>)startupInformation {
+  if (!webpageURL)
+    return NO;
+
+  GURL webpageGURL(net::GURLWithNSURL(webpageURL));
+  if (!webpageGURL.is_valid())
+    return NO;
+
+  if (applicationIsActive && ![startupInformation isPresentingFirstRunUI]) {
+    // The app is already active so the applicationDidBecomeActive: method will
+    // never be called. Open the requested URL immediately.
+    ApplicationMode targetMode =
+        [[startupInformation startupParameters] launchInIncognito]
+            ? ApplicationMode::INCOGNITO
+            : ApplicationMode::NORMAL;
+    [tabOpener dismissModalsAndOpenSelectedTabInMode:targetMode
+                                             withURL:webpageGURL
+                                          transition:ui::PAGE_TRANSITION_LINK
+                                          completion:^{
+                                            [startupInformation
+                                                setStartupParameters:nil];
+                                          }];
+    return YES;
+  }
+
+  // Don't record the first action as a user action, since it will not be
+  // initiated by the user.
+  [startupInformation resetFirstUserActionRecorder];
+
+  if (![startupInformation startupParameters]) {
+    base::scoped_nsobject<AppStartupParameters> startupParams(
+        [[AppStartupParameters alloc] initWithExternalURL:webpageGURL]);
+    [startupInformation setStartupParameters:startupParams];
+  }
+  return YES;
+}
+
++ (void)performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
+                   completionHandler:(void (^)(BOOL succeeded))completionHandler
+                           tabOpener:(id<TabOpening>)tabOpener
+                  startupInformation:(id<StartupInformation>)startupInformation
+              browserViewInformation:
+                  (id<BrowserViewInformation>)browserViewInformation {
+  BOOL handledShortcutItem =
+      [UserActivityHandler handleShortcutItem:shortcutItem
+                           startupInformation:startupInformation];
+  if (handledShortcutItem) {
+    [UserActivityHandler
+        handleStartupParametersWithTabOpener:tabOpener
+                          startupInformation:startupInformation
+                      browserViewInformation:browserViewInformation];
+  }
+  completionHandler(handledShortcutItem);
+}
+
++ (BOOL)willContinueUserActivityWithType:(NSString*)userActivityType {
+  return
+      [userActivityType isEqualToString:handoff::kChromeHandoffActivityType] ||
+      (spotlight::IsSpotlightAvailable() &&
+       [userActivityType isEqualToString:CSSearchableItemActionType]);
+}
+
++ (void)handleStartupParametersWithTabOpener:(id<TabOpening>)tabOpener
+                          startupInformation:
+                              (id<StartupInformation>)startupInformation
+                      browserViewInformation:
+                          (id<BrowserViewInformation>)browserViewInformation {
+  DCHECK([startupInformation startupParameters]);
+  // Do not load the external URL if the user has not accepted the terms of
+  // service. This corresponds to the case when the user installed Chrome,
+  // has never launched it and attempts to open an external URL in Chrome.
+  if ([startupInformation isPresentingFirstRunUI])
+    return;
+
+  // Check if it's an U2F call. If so, route it to correct tab.
+  // If not, open or reuse tab in main BVC.
+  if ([U2FController
+          isU2FURL:[[startupInformation startupParameters] externalURL]]) {
+    [UserActivityHandler routeU2FURL:[[startupInformation startupParameters]
+                                         externalURL]
+              browserViewInformation:browserViewInformation];
+    // It's OK to clear startup parameters here because routeU2FURL works
+    // synchronously.
+    [startupInformation setStartupParameters:nil];
+  } else {
+    // The app is already active so the applicationDidBecomeActive: method
+    // will never be called. Open the requested URL after all modal UIs have
+    // been dismissed. |_startupParameters| must be retained until all deferred
+    // modal UIs are dismissed and tab opened with requested URL.
+    ApplicationMode targetMode =
+        [[startupInformation startupParameters] launchInIncognito]
+            ? ApplicationMode::INCOGNITO
+            : ApplicationMode::NORMAL;
+    [tabOpener dismissModalsAndOpenSelectedTabInMode:targetMode
+                                             withURL:[[startupInformation
+                                                         startupParameters]
+                                                         externalURL]
+                                          transition:ui::PAGE_TRANSITION_LINK
+                                          completion:^{
+                                            [startupInformation
+                                                setStartupParameters:nil];
+                                          }];
+  }
+}
+
+#pragma mark - Internal methods.
+
++ (BOOL)handleShortcutItem:(UIApplicationShortcutItem*)shortcutItem
+        startupInformation:(id<StartupInformation>)startupInformation {
+  if ([startupInformation isPresentingFirstRunUI])
+    return NO;
+
+  base::scoped_nsobject<AppStartupParameters> startupParams(
+      [[AppStartupParameters alloc]
+          initWithExternalURL:GURL(kChromeUINewTabURL)]);
+
+  if ([shortcutItem.type isEqualToString:kShortcutNewTab]) {
+    base::RecordAction(UserMetricsAction("ApplicationShortcut.NewTabPressed"));
+    [startupInformation setStartupParameters:startupParams];
+    return YES;
+
+  } else if ([shortcutItem.type isEqualToString:kShortcutNewIncognitoTab]) {
+    base::RecordAction(
+        UserMetricsAction("ApplicationShortcut.NewIncognitoTabPressed"));
+    [startupParams setLaunchInIncognito:YES];
+    [startupInformation setStartupParameters:startupParams];
+    return YES;
+
+  } else if ([shortcutItem.type isEqualToString:kShortcutVoiceSearch]) {
+    base::RecordAction(
+        UserMetricsAction("ApplicationShortcut.VoiceSearchPressed"));
+    [startupParams setLaunchVoiceSearch:YES];
+    [startupInformation setStartupParameters:startupParams];
+    return YES;
+
+  } else if ([shortcutItem.type isEqualToString:kShortcutQRScanner]) {
+    if (experimental_flags::IsQRCodeReaderEnabled()) {
+      base::RecordAction(
+          UserMetricsAction("ApplicationShortcut.ScanQRCodePressed"));
+      [startupParams setLaunchQRScanner:YES];
+    }
+    [startupInformation setStartupParameters:startupParams];
+    return YES;
+  }
+
+  NOTREACHED();
+  return NO;
+}
+
++ (void)routeU2FURL:(const GURL&)URL
+    browserViewInformation:(id<BrowserViewInformation>)browserViewInformation {
+  // Retrieve the designated TabID from U2F URL.
+  NSString* tabID = [U2FController tabIDFromResponseURL:URL];
+  if (!tabID) {
+    return;
+  }
+
+  // TODO(crbug.com/619598): move this code to BrowserViewInformation to hide
+  // implementation details of TabModel.
+  // Iterate through mainTabModel and OTRTabModel to find the corresponding tab.
+  NSArray* tabModels = @[
+    [browserViewInformation mainTabModel], [browserViewInformation otrTabModel]
+  ];
+  for (TabModel* tabModel in tabModels) {
+    for (Tab* tab in tabModel) {
+      if ([tab.tabId isEqualToString:tabID]) {
+        [tab evaluateU2FResultFromURL:URL];
+        return;
+      }
+    }
+  }
+}
+
+@end
diff --git a/ios/chrome/app/application_delegate/user_activity_handler_unittest.mm b/ios/chrome/app/application_delegate/user_activity_handler_unittest.mm
new file mode 100644
index 0000000..da1fc34
--- /dev/null
+++ b/ios/chrome/app/application_delegate/user_activity_handler_unittest.mm
@@ -0,0 +1,625 @@
+// Copyright 2016 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.
+
+#include "ios/chrome/app/application_delegate/user_activity_handler.h"
+
+#include <memory>
+
+#import <CoreSpotlight/CoreSpotlight.h>
+
+#include "base/ios/ios_util.h"
+#include "base/mac/scoped_block.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/test/scoped_command_line.h"
+#include "components/handoff/handoff_utility.h"
+#include "ios/chrome/app/application_delegate/fake_startup_information.h"
+#include "ios/chrome/app/application_delegate/mock_tab_opener.h"
+#include "ios/chrome/app/application_delegate/startup_information.h"
+#include "ios/chrome/app/application_delegate/tab_opening.h"
+#include "ios/chrome/app/application_mode.h"
+#include "ios/chrome/app/main_controller.h"
+#include "ios/chrome/app/spotlight/actions_spotlight_manager.h"
+#import "ios/chrome/app/spotlight/spotlight_util.h"
+#include "ios/chrome/browser/app_startup_parameters.h"
+#include "ios/chrome/browser/chrome_switches.h"
+#include "ios/chrome/browser/chrome_url_constants.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/u2f/u2f_controller.h"
+#import "ios/chrome/test/base/scoped_block_swizzler.h"
+#import "net/base/mac/url_conversions.h"
+#include "net/test/gtest_util.h"
+#include "testing/platform_test.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
+#include "third_party/ocmock/gtest_support.h"
+#include "ui/base/page_transition_types.h"
+#include "url/gurl.h"
+
+#pragma mark - Tab Mock
+
+// Tab mock for using in UserActivity tests.
+@interface UserActivityHandlerTabMock : NSObject
+
+@property(nonatomic, readonly) GURL url;
+@property(nonatomic, readonly) NSString* tabId;
+
+@end
+
+@implementation UserActivityHandlerTabMock
+@synthesize url = _url;
+@synthesize tabId = _tabId;
+
+- (void)evaluateU2FResultFromURL:(const GURL&)url {
+  _url = url;
+}
+
+@end
+
+#pragma mark - TabModel Mock
+
+// TabModel mock for using in UserActivity tests.
+@interface UserActivityHandlerTabModelMock : NSObject<NSFastEnumeration> {
+ @private
+  base::scoped_nsobject<NSMutableArray> _tabs;
+}
+
+- (void)addTab:(Tab*)tab;
+- (void)addObserver:(id<TabModelObserver>)observer;
+- (void)removeObserver:(id<TabModelObserver>)observer;
+
+@end
+
+@implementation UserActivityHandlerTabModelMock
+
+- (instancetype)init {
+  if ((self = [super init])) {
+    _tabs.reset([[NSMutableArray alloc] init]);
+  }
+  return self;
+}
+
+- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState*)state
+                                  objects:(id*)stackbuf
+                                    count:(NSUInteger)len {
+  return [_tabs countByEnumeratingWithState:state objects:stackbuf count:len];
+}
+
+- (void)addTab:(Tab*)tab {
+  [_tabs addObject:tab];
+}
+
+- (void)addObserver:(id<TabModelObserver>)observer {
+  // Empty.
+}
+
+- (void)removeObserver:(id<TabModelObserver>)observer {
+  // Empty.
+}
+
+@end
+
+#pragma mark - Test class.
+
+// A block that takes as arguments the caller and the arguments from
+// UserActivityHandler +handleStartupParameters and returns nothing.
+typedef void (^startupParameterBlock)(id,
+                                      id<TabOpening>,
+                                      id<StartupInformation>,
+                                      id<BrowserViewInformation>);
+
+// A block that takes a BOOL argument and returns nothing.
+typedef void (^conditionBlock)(BOOL);
+
+class UserActivityHandlerTest : public PlatformTest {
+ protected:
+  void swizzleHandleStartupParameters() {
+    handle_startup_parameters_has_been_called_ = NO;
+    swizzle_block_.reset([^(id self) {
+      handle_startup_parameters_has_been_called_ = YES;
+    } copy]);
+    user_activity_handler_swizzler_.reset(new ScopedBlockSwizzler(
+        [UserActivityHandler class],
+        @selector(handleStartupParametersWithTabOpener:
+                                    startupInformation:
+                                browserViewInformation:),
+        swizzle_block_));
+  }
+
+  BOOL getHandleStartupParametersHasBeenCalled() {
+    return handle_startup_parameters_has_been_called_;
+  }
+
+  void resetHandleStartupParametersHasBeenCalled() {
+    handle_startup_parameters_has_been_called_ = NO;
+  }
+
+  conditionBlock getCompletionHandler() {
+    if (!completion_block_) {
+      block_executed_ = NO;
+      completion_block_.reset([^(BOOL arg) {
+        block_executed_ = YES;
+        block_argument_ = arg;
+      } copy]);
+    }
+    return completion_block_;
+  }
+
+  BOOL completionHandlerExecuted() { return block_executed_; }
+
+  BOOL completionHandlerArgument() { return block_argument_; }
+
+ private:
+  __block BOOL block_executed_;
+  __block BOOL block_argument_;
+  std::unique_ptr<ScopedBlockSwizzler> user_activity_handler_swizzler_;
+  base::mac::ScopedBlock<startupParameterBlock> swizzle_block_;
+  base::mac::ScopedBlock<conditionBlock> completion_block_;
+  __block BOOL handle_startup_parameters_has_been_called_;
+};
+
+#pragma mark - Tests.
+
+// Tests that Chrome notifies the user if we are passing a correct
+// userActivityType.
+TEST(UserActivityHandlerNoFixtureTest,
+     willContinueUserActivityCorrectActivity) {
+  EXPECT_TRUE([UserActivityHandler
+      willContinueUserActivityWithType:handoff::kChromeHandoffActivityType]);
+
+  if (spotlight::IsSpotlightAvailable()) {
+    EXPECT_TRUE([UserActivityHandler
+        willContinueUserActivityWithType:CSSearchableItemActionType]);
+  }
+}
+
+// Tests that Chrome does not notifies the user if we are passing an incorrect
+// userActivityType.
+TEST(UserActivityHandlerNoFixtureTest,
+     willContinueUserActivityIncorrectActivity) {
+  EXPECT_FALSE([UserActivityHandler
+      willContinueUserActivityWithType:[handoff::kChromeHandoffActivityType
+                                           stringByAppendingString:@"test"]]);
+
+  EXPECT_FALSE([UserActivityHandler
+      willContinueUserActivityWithType:@"it.does.not.work"]);
+
+  EXPECT_FALSE([UserActivityHandler willContinueUserActivityWithType:@""]);
+
+  EXPECT_FALSE([UserActivityHandler willContinueUserActivityWithType:nil]);
+}
+
+// Tests that Chrome does not continue the activity is the activity type is
+// random.
+TEST(UserActivityHandlerNoFixtureTest, continueUserActivityFromGarbage) {
+  // Setup.
+  NSString* handoffWithSuffix =
+      [handoff::kChromeHandoffActivityType stringByAppendingString:@"test"];
+  NSString* handoffWithPrefix =
+      [@"test" stringByAppendingString:handoff::kChromeHandoffActivityType];
+  NSArray* userActivityTypes = @[
+    @"thisIsGarbage", @"it.does.not.work", handoffWithSuffix, handoffWithPrefix
+  ];
+  for (NSString* userActivityType in userActivityTypes) {
+    base::scoped_nsobject<NSUserActivity> userActivity(
+        [[NSUserActivity alloc] initWithActivityType:userActivityType]);
+    [userActivity setWebpageURL:[NSURL URLWithString:@"http://www.google.com"]];
+
+    // The test will fail is a method of those objects is called.
+    id tabOpenerMock = [OCMockObject mockForProtocol:@protocol(TabOpening)];
+    id startupInformationMock =
+        [OCMockObject mockForProtocol:@protocol(StartupInformation)];
+
+    // Action.
+    BOOL result =
+        [UserActivityHandler continueUserActivity:userActivity
+                              applicationIsActive:NO
+                                        tabOpener:tabOpenerMock
+                               startupInformation:startupInformationMock];
+
+    // Tests.
+    EXPECT_FALSE(result);
+  }
+}
+
+// Tests that Chrome does not continue the activity if the webpage url is not
+// set.
+TEST(UserActivityHandlerNoFixtureTest, continueUserActivityNoWebpage) {
+  // Setup.
+  base::scoped_nsobject<NSUserActivity> userActivity([[NSUserActivity alloc]
+      initWithActivityType:handoff::kChromeHandoffActivityType]);
+
+  // The test will fail is a method of those objects is called.
+  id tabOpenerMock = [OCMockObject mockForProtocol:@protocol(TabOpening)];
+  id startupInformationMock =
+      [OCMockObject mockForProtocol:@protocol(StartupInformation)];
+
+  // Action.
+  BOOL result =
+      [UserActivityHandler continueUserActivity:userActivity
+                            applicationIsActive:NO
+                                      tabOpener:tabOpenerMock
+                             startupInformation:startupInformationMock];
+
+  // Tests.
+  EXPECT_FALSE(result);
+}
+
+// Tests that Chrome does not continue the activity if the activity is a
+// Spotlight action of an unknown type.
+TEST(UserActivityHandlerNoFixtureTest,
+     continueUserActivitySpotlightActionFromGarbage) {
+  // Only test Spotlight if it is enabled and available on the device.
+  if (!spotlight::IsSpotlightAvailable()) {
+    return;
+  }
+  // Setup.
+  base::scoped_nsobject<NSUserActivity> userActivity(
+      [[NSUserActivity alloc] initWithActivityType:CSSearchableItemActionType]);
+  NSString* invalidAction =
+      [NSString stringWithFormat:@"%@.invalidAction",
+                                 spotlight::StringFromSpotlightDomain(
+                                     spotlight::DOMAIN_ACTIONS)];
+  NSDictionary* userInfo =
+      @{CSSearchableItemActivityIdentifier : invalidAction};
+  [userActivity addUserInfoEntriesFromDictionary:userInfo];
+
+  // Enable the SpotlightActions experiment.
+  base::test::ScopedCommandLine scoped_command_line;
+  scoped_command_line.GetProcessCommandLine()->AppendSwitch(
+      switches::kEnableSpotlightActions);
+
+  id tabOpenerMock = [OCMockObject mockForProtocol:@protocol(TabOpening)];
+  id startupInformationMock =
+      [OCMockObject mockForProtocol:@protocol(StartupInformation)];
+
+  // Action.
+  BOOL result =
+      [UserActivityHandler continueUserActivity:userActivity
+                            applicationIsActive:NO
+                                      tabOpener:tabOpenerMock
+                             startupInformation:startupInformationMock];
+
+  // Tests.
+  EXPECT_FALSE(result);
+}
+
+// Tests that Chrome continues the activity if the application is in background
+// by saving the url to startupParameters.
+TEST(UserActivityHandlerNoFixtureTest, continueUserActivityBackground) {
+  // Setup.
+  base::scoped_nsobject<NSUserActivity> userActivity([[NSUserActivity alloc]
+      initWithActivityType:handoff::kChromeHandoffActivityType]);
+  NSURL* nsurl = [NSURL URLWithString:@"http://www.google.com"];
+  [userActivity setWebpageURL:nsurl];
+
+  id startupInformationMock =
+      [OCMockObject niceMockForProtocol:@protocol(StartupInformation)];
+  [[startupInformationMock expect]
+      setStartupParameters:[OCMArg checkWithBlock:^BOOL(id value) {
+        EXPECT_TRUE([value isKindOfClass:[AppStartupParameters class]]);
+
+        AppStartupParameters* startupParameters = (AppStartupParameters*)value;
+        const GURL calledURL = startupParameters.externalURL;
+        return calledURL == net::GURLWithNSURL(nsurl);
+      }]];
+
+  // The test will fail is a method of this object is called.
+  id tabOpenerMock = [OCMockObject mockForProtocol:@protocol(TabOpening)];
+
+  // Action.
+  BOOL result =
+      [UserActivityHandler continueUserActivity:userActivity
+                            applicationIsActive:NO
+                                      tabOpener:tabOpenerMock
+                             startupInformation:startupInformationMock];
+
+  // Test.
+  EXPECT_OCMOCK_VERIFY(startupInformationMock);
+  EXPECT_TRUE(result);
+}
+
+// Tests that Chrome continues the activity if the application is in foreground
+// by opening a new tab.
+TEST(UserActivityHandlerNoFixtureTest, continueUserActivityForeground) {
+  // Setup.
+  base::scoped_nsobject<NSUserActivity> userActivity([[NSUserActivity alloc]
+      initWithActivityType:handoff::kChromeHandoffActivityType]);
+  NSURL* nsurl = [NSURL URLWithString:@"http://www.google.com"];
+  [userActivity setWebpageURL:nsurl];
+
+  base::scoped_nsobject<MockTabOpener> tabOpener([[MockTabOpener alloc] init]);
+
+  id startupInformationMock =
+      [OCMockObject mockForProtocol:@protocol(StartupInformation)];
+  [[[startupInformationMock stub] andReturnValue:@NO] isPresentingFirstRunUI];
+
+  base::scoped_nsobject<AppStartupParameters> startupParams(
+      [[AppStartupParameters alloc]
+          initWithExternalURL:(GURL("http://www.google.com"))]);
+  [[[startupInformationMock stub] andReturn:startupParams] startupParameters];
+
+  // Action.
+  BOOL result =
+      [UserActivityHandler continueUserActivity:userActivity
+                            applicationIsActive:YES
+                                      tabOpener:tabOpener
+                             startupInformation:startupInformationMock];
+
+  // Test.
+  EXPECT_EQ(net::GURLWithNSURL(nsurl), [tabOpener url]);
+  EXPECT_TRUE(result);
+}
+
+// Tests that a new tab is created when application is started via Universal
+// Link.
+TEST_F(UserActivityHandlerTest, continueUserActivityBrowsingWeb) {
+  base::scoped_nsobject<NSUserActivity> userActivity([[NSUserActivity alloc]
+      initWithActivityType:NSUserActivityTypeBrowsingWeb]);
+  // This URL is passed to application by iOS but is not used in this part
+  // of application logic.
+  NSURL* nsurl = [NSURL URLWithString:@"http://goo.gl/foo/bar"];
+  [userActivity setWebpageURL:nsurl];
+
+  base::scoped_nsobject<MockTabOpener> tabOpener([[MockTabOpener alloc] init]);
+
+  // Use an object to capture the startup paramters set by UserActivityHandler.
+  base::scoped_nsobject<FakeStartupInformation> fakeStartupInformation(
+      [[FakeStartupInformation alloc] init]);
+  [fakeStartupInformation setIsPresentingFirstRunUI:NO];
+
+  BOOL result =
+      [UserActivityHandler continueUserActivity:userActivity
+                            applicationIsActive:YES
+                                      tabOpener:tabOpener
+                             startupInformation:fakeStartupInformation];
+
+  GURL newTabURL(kChromeUINewTabURL);
+  EXPECT_EQ(newTabURL, [tabOpener url]);
+  // AppStartupParameters default to opening pages in non-Incognito mode.
+  EXPECT_EQ(ApplicationMode::NORMAL, [tabOpener applicationMode]);
+  EXPECT_TRUE(result);
+  // Verifies that a new tab is being requested.
+  EXPECT_EQ(newTabURL,
+            [[fakeStartupInformation startupParameters] externalURL]);
+}
+
+// Tests that continueUserActivity sets startupParameters accordingly to the
+// Spotlight action used.
+TEST_F(UserActivityHandlerTest, continueUserActivityShortcutActions) {
+  // Only test Spotlight if it is enabled and available on the device.
+  if (!spotlight::IsSpotlightAvailable()) {
+    return;
+  }
+  // Setup.
+  GURL gurlNewTab(kChromeUINewTabURL);
+  base::scoped_nsobject<FakeStartupInformation> fakeStartupInformation(
+      [[FakeStartupInformation alloc] init]);
+
+  NSArray* parametersToTest = @[
+    @[
+      base::SysUTF8ToNSString(spotlight::kSpotlightActionNewTab), @NO, @NO, @NO
+    ],
+    @[
+      base::SysUTF8ToNSString(spotlight::kSpotlightActionNewIncognitoTab), @YES,
+      @NO, @NO
+    ],
+    @[
+      base::SysUTF8ToNSString(spotlight::kSpotlightActionVoiceSearch), @NO,
+      @YES, @NO
+    ],
+    @[
+      base::SysUTF8ToNSString(spotlight::kSpotlightActionQRScanner), @NO, @NO,
+      @YES
+    ]
+  ];
+
+  // Enable the QR Scanner and Spotlight Actions experiments.
+  base::test::ScopedCommandLine scoped_command_line;
+  scoped_command_line.GetProcessCommandLine()->AppendSwitch(
+      switches::kEnableQRScanner);
+  scoped_command_line.GetProcessCommandLine()->AppendSwitch(
+      switches::kEnableSpotlightActions);
+
+  for (id parameters in parametersToTest) {
+    base::scoped_nsobject<NSUserActivity> userActivity([[NSUserActivity alloc]
+        initWithActivityType:CSSearchableItemActionType]);
+    NSString* action = [NSString
+        stringWithFormat:@"%@.%@", spotlight::StringFromSpotlightDomain(
+                                       spotlight::DOMAIN_ACTIONS),
+                         parameters[0]];
+    NSDictionary* userInfo = @{CSSearchableItemActivityIdentifier : action};
+    [userActivity addUserInfoEntriesFromDictionary:userInfo];
+
+    id tabOpenerMock = [OCMockObject mockForProtocol:@protocol(TabOpening)];
+
+    // Action.
+    BOOL result =
+        [UserActivityHandler continueUserActivity:userActivity
+                              applicationIsActive:NO
+                                        tabOpener:tabOpenerMock
+                               startupInformation:fakeStartupInformation];
+
+    // Tests.
+    EXPECT_TRUE(result);
+    EXPECT_EQ(gurlNewTab,
+              [fakeStartupInformation startupParameters].externalURL);
+    EXPECT_EQ([parameters[1] boolValue],
+              [fakeStartupInformation startupParameters].launchInIncognito);
+    EXPECT_EQ([parameters[2] boolValue],
+              [fakeStartupInformation startupParameters].launchVoiceSearch);
+    EXPECT_EQ([parameters[3] boolValue],
+              [fakeStartupInformation startupParameters].launchQRScanner);
+  }
+}
+
+// Tests that handleStartupParameters with a non-U2F url opens a new tab.
+TEST(UserActivityHandlerNoFixtureTest, handleStartupParamsNonU2F) {
+  // Setup.
+  GURL gurl("http://www.google.com");
+
+  base::scoped_nsobject<AppStartupParameters> startupParams(
+      [[AppStartupParameters alloc] initWithExternalURL:gurl]);
+  [startupParams setLaunchInIncognito:YES];
+
+  id startupInformationMock =
+      [OCMockObject mockForProtocol:@protocol(StartupInformation)];
+  [[[startupInformationMock stub] andReturnValue:@NO] isPresentingFirstRunUI];
+  [[[startupInformationMock stub] andReturn:startupParams] startupParameters];
+  [[startupInformationMock expect] setStartupParameters:nil];
+
+  base::scoped_nsobject<MockTabOpener> tabOpener([[MockTabOpener alloc] init]);
+
+  // The test will fail is a method of this object is called.
+  id browserViewMock =
+      [OCMockObject mockForProtocol:@protocol(BrowserViewInformation)];
+
+  // Action.
+  [UserActivityHandler
+      handleStartupParametersWithTabOpener:tabOpener
+                        startupInformation:startupInformationMock
+                    browserViewInformation:browserViewMock];
+  [tabOpener completionBlock]();
+
+  // Tests.
+  EXPECT_OCMOCK_VERIFY(startupInformationMock);
+  EXPECT_EQ(gurl, [tabOpener url]);
+  EXPECT_EQ(ApplicationMode::INCOGNITO, [tabOpener applicationMode]);
+}
+
+// Tests that handleStartupParameters with a U2F url opens in the correct tab.
+TEST(UserActivityHandlerNoFixtureTest, handleStartupParamsU2F) {
+  // Setup.
+  GURL gurl("chromium://u2f-callback?isU2F=1&tabID=B05B1860");
+  NSString* tabID = [U2FController tabIDFromResponseURL:gurl];
+
+  base::scoped_nsobject<AppStartupParameters> startupParams(
+      [[AppStartupParameters alloc] initWithExternalURL:gurl]);
+  [startupParams setLaunchInIncognito:YES];
+
+  base::scoped_nsobject<UserActivityHandlerTabMock> tabMock(
+      [[UserActivityHandlerTabMock alloc] init]);
+  id tabOCMock = [OCMockObject partialMockForObject:tabMock];
+  [[[tabOCMock stub] andReturn:tabID] tabId];
+
+  base::scoped_nsobject<UserActivityHandlerTabModelMock> tabModel(
+      [[UserActivityHandlerTabModelMock alloc] init]);
+  [tabModel addTab:(Tab*)tabMock.get()];
+
+  id startupInformationMock =
+      [OCMockObject mockForProtocol:@protocol(StartupInformation)];
+  [[[startupInformationMock stub] andReturnValue:@NO] isPresentingFirstRunUI];
+  [[[startupInformationMock stub] andReturn:startupParams] startupParameters];
+  [[startupInformationMock expect] setStartupParameters:nil];
+
+  id browserViewInformationMock =
+      [OCMockObject mockForProtocol:@protocol(BrowserViewInformation)];
+  [[[browserViewInformationMock stub] andReturn:(TabModel*)tabModel.get()]
+      mainTabModel];
+  [[[browserViewInformationMock stub] andReturn:(TabModel*)tabModel.get()]
+      otrTabModel];
+
+  base::scoped_nsobject<MockTabOpener> tabOpener([[MockTabOpener alloc] init]);
+
+  // Action.
+  [UserActivityHandler
+      handleStartupParametersWithTabOpener:tabOpener
+                        startupInformation:startupInformationMock
+                    browserViewInformation:browserViewInformationMock];
+
+  // Tests.
+  EXPECT_OCMOCK_VERIFY(startupInformationMock);
+  EXPECT_EQ(gurl, [tabMock url]);
+}
+
+// Tests that performActionForShortcutItem set startupParameters accordingly to
+// the shortcut used
+TEST_F(UserActivityHandlerTest, performActionForShortcutItemWithRealShortcut) {
+  // Setup.
+  GURL gurlNewTab("chrome://newtab/");
+
+  base::scoped_nsobject<FakeStartupInformation> fakeStartupInformation(
+      [[FakeStartupInformation alloc] init]);
+  [fakeStartupInformation setIsPresentingFirstRunUI:NO];
+
+  NSArray* parametersToTest = @[
+    @[ @"OpenNewTab", @NO, @NO, @NO ], @[ @"OpenIncognitoTab", @YES, @NO, @NO ],
+    @[ @"OpenVoiceSearch", @NO, @YES, @NO ],
+    @[ @"OpenQRScanner", @NO, @NO, @YES ]
+  ];
+
+  // Enable the QR Scanner experiment.
+  base::test::ScopedCommandLine scoped_command_line;
+  scoped_command_line.GetProcessCommandLine()->AppendSwitch(
+      switches::kEnableQRScanner);
+
+  swizzleHandleStartupParameters();
+
+  for (id parameters in parametersToTest) {
+    base::scoped_nsobject<UIApplicationShortcutItem> shortcut(
+        [[UIApplicationShortcutItem alloc] initWithType:parameters[0]
+                                         localizedTitle:parameters[0]]);
+
+    resetHandleStartupParametersHasBeenCalled();
+
+    // The test will fail is a method of those objects is called.
+    id tabOpenerMock = [OCMockObject mockForProtocol:@protocol(TabOpening)];
+    id browserViewInformationMock =
+        [OCMockObject mockForProtocol:@protocol(BrowserViewInformation)];
+
+    // Action.
+    [UserActivityHandler
+        performActionForShortcutItem:shortcut
+                   completionHandler:getCompletionHandler()
+                           tabOpener:tabOpenerMock
+                  startupInformation:fakeStartupInformation
+              browserViewInformation:browserViewInformationMock];
+
+    // Tests.
+    EXPECT_EQ(gurlNewTab,
+              [fakeStartupInformation startupParameters].externalURL);
+    EXPECT_EQ([[parameters objectAtIndex:1] boolValue],
+              [fakeStartupInformation startupParameters].launchInIncognito);
+    EXPECT_EQ([[parameters objectAtIndex:2] boolValue],
+              [fakeStartupInformation startupParameters].launchVoiceSearch);
+    EXPECT_EQ([[parameters objectAtIndex:3] boolValue],
+              [fakeStartupInformation startupParameters].launchQRScanner);
+    EXPECT_TRUE(completionHandlerExecuted());
+    EXPECT_TRUE(completionHandlerArgument());
+    EXPECT_TRUE(getHandleStartupParametersHasBeenCalled());
+  }
+}
+
+// Tests that performActionForShortcutItem just executes the completionHandler
+// with NO if the firstRunUI is present.
+TEST_F(UserActivityHandlerTest, performActionForShortcutItemWithFirstRunUI) {
+  // Setup.
+  id startupInformationMock =
+      [OCMockObject mockForProtocol:@protocol(StartupInformation)];
+  [[[startupInformationMock stub] andReturnValue:@YES] isPresentingFirstRunUI];
+
+  base::scoped_nsobject<UIApplicationShortcutItem> shortcut(
+      [[UIApplicationShortcutItem alloc] initWithType:@"OpenNewTab"
+                                       localizedTitle:@""]);
+
+  swizzleHandleStartupParameters();
+
+  // The test will fail is a method of those objects is called.
+  id tabOpenerMock = [OCMockObject mockForProtocol:@protocol(TabOpening)];
+  id browserViewInformationMock =
+      [OCMockObject mockForProtocol:@protocol(BrowserViewInformation)];
+
+  // Action.
+  [UserActivityHandler performActionForShortcutItem:shortcut
+                                  completionHandler:getCompletionHandler()
+                                          tabOpener:tabOpenerMock
+                                 startupInformation:startupInformationMock
+                             browserViewInformation:browserViewInformationMock];
+
+  // Tests.
+  EXPECT_TRUE(completionHandlerExecuted());
+  EXPECT_FALSE(completionHandlerArgument());
+  EXPECT_FALSE(getHandleStartupParametersHasBeenCalled());
+}
diff --git a/ios/chrome/app/application_mode.h b/ios/chrome/app/application_mode.h
new file mode 100644
index 0000000..848fd7e7
--- /dev/null
+++ b/ios/chrome/app/application_mode.h
@@ -0,0 +1,11 @@
+// Copyright 2015 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.
+
+#ifndef IOS_CHROME_APP_APPLICATION_MODE_H_
+#define IOS_CHROME_APP_APPLICATION_MODE_H_
+
+// Global mode of the application.
+enum class ApplicationMode { NORMAL, INCOGNITO };
+
+#endif  // IOS_CHROME_APP_APPLICATION_MODE_H_
diff --git a/ios/chrome/app/application_phase.h b/ios/chrome/app/application_phase.h
new file mode 100644
index 0000000..83e5b9bd
--- /dev/null
+++ b/ios/chrome/app/application_phase.h
@@ -0,0 +1,32 @@
+// Copyright 2016 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.
+
+// ======                        New Architecture                         =====
+// =         This code is only used in the new iOS Chrome architecture.       =
+// ============================================================================
+
+#ifndef IOS_CHROME_APP_APPLICATION_PHASE_H_
+#define IOS_CHROME_APP_APPLICATION_PHASE_H_
+
+// The phases that the application moves through as it transitions between
+// states.
+typedef NS_ENUM(NSUInteger, ApplicationPhase) {
+  // The initial state of the application performing a cold launch.
+  APPLICATION_COLD,
+  // The minimal initialization that must be completed before any further
+  // startup can happen. |applicationDidFinishLaunching:withOptions:| must
+  // bring the appication to at least this phase before returning.
+  APPLICATION_BASIC,
+  // The phase required for any background handling.
+  APPLICATION_BACKGROUNDED,
+  // The phase where the app is foregrounded but Safe Mode has been started.
+  APPLICATION_SAFE_MODE,
+  // The phase where the app is fully foregrounded and the regular UI has
+  // started.
+  APPLICATION_FOREGROUNDED,
+  // The phase where the application has started to terminate.
+  APPLICATION_TERMINATING
+};
+
+#endif  // IOS_CHROME_APP_APPLICATION_PHASE_H_
diff --git a/ios/chrome/app/application_state.h b/ios/chrome/app/application_state.h
new file mode 100644
index 0000000..a5331a7
--- /dev/null
+++ b/ios/chrome/app/application_state.h
@@ -0,0 +1,113 @@
+// Copyright 2016 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.
+
+// ======                        New Architecture                         =====
+// =         This code is only used in the new iOS Chrome architecture.       =
+// ============================================================================
+
+#ifndef IOS_CHROME_APP_APPLICATION_STATE_H_
+#define IOS_CHROME_APP_APPLICATION_STATE_H_
+
+#import <UIKit/UIKit.h>
+
+#import "ios/chrome/app/application_phase.h"
+
+namespace base {
+class SupportsUserData;
+}
+
+namespace ios {
+class ChromeBrowserState;
+}
+
+@protocol ApplicationStep;
+@protocol URLOpening;
+
+typedef NSMutableArray<id<ApplicationStep>> ApplicationStepArray;
+
+// An ApplicationState object stores information about Chrome's status in the
+// iOS application lifecycle and owns root-level global objects needed
+// throughout the app (specifically, |browserState| and |window|).
+// Additionally, an ApplicationState object keeps arrays of steps to perform
+// in response to various changes in the overall application states.
+// These steps are grouped into launch (used when the app is cold starting),
+// termination (used when the app is shutting down), background (used when the
+// app is backgrounding but staying resident), and foreground (used when the app
+// is warm starting). Each group of steps is an ordered array of objects
+// implementing the ApplicationStep protocol.
+// Some methods (indicated below) will cause the ApplicationState to run one of
+// these lists of steps; this consists of:
+//   (1) Calling -canRunInState: on the first item in the list, passing in the
+//       running ApplicationState object. If this returns NO, stop. Otherwise:
+//   (2) Removing the first item from the list and then calling -runInState:,
+//       again passing in the current state object. State objects can assume
+//       that they are no longer in the step array once -runInState: is called.
+//   (3) When runInState: completes, going back to (1). Since the list of steps
+//       had ownership of the application step that just ran, that step will
+//       then no longer be strongly held, and will thus be deallocated unless
+//       it is being strongly held elsewhere.
+// Steps cannot themselves store state, since they don't persist after running
+// by default. Steps can write to the ApplicationState's |state| object to
+// store state.
+@interface ApplicationState : NSObject
+
+// The UIApplication instance this object is storing state for.
+@property(nonatomic, weak) UIApplication* application;
+
+// The launch options dictionary for this application, set via
+// -launchWithOptions:
+@property(nonatomic, readonly) NSDictionary* launchOptions;
+
+// The browser state this object stores; many launch steps are expected to
+// make use of this. At least one launch step must assign to this property at
+// some point in the launch sequence.
+@property(nonatomic, assign) ios::ChromeBrowserState* browserState;
+
+// The current phase the application is in. Changing this doesn't trigger
+// any launch steps; one of the phase change methods must be called to do that.
+// Steps invoked during the phase change methods must update this property.
+@property(nonatomic, assign) ApplicationPhase phase;
+
+// Persistant storage for application steps that need to have objects live
+// beyond the execution of the step. This should be used sparingly for objects
+// that actually need to persist for the lifetime of the app.
+@property(nonatomic, readonly) base::SupportsUserData* persistentState;
+
+// The window for this application. Launch steps must create this, make it key
+// and make it visible, although those three actions may be spread across
+// more than one step.
+@property(nonatomic, strong) UIWindow* window;
+
+// An object that can open URLs when the application is asked to do so by the
+// operating system.
+@property(nonatomic, weak) id<URLOpening> URLOpener;
+
+// Steps for each phase. Steps may add more steps to these arrays, and steps
+// added in this way become strongly held by this object.
+@property(nonatomic, readonly) ApplicationStepArray* launchSteps;
+@property(nonatomic, readonly) ApplicationStepArray* terminationSteps;
+@property(nonatomic, readonly) ApplicationStepArray* backgroundSteps;
+@property(nonatomic, readonly) ApplicationStepArray* foregroundSteps;
+
+// Phase change methods.
+
+// Sets the launchOptions property to |launchOptions| and then runs the launch
+// steps.
+- (void)launchWithOptions:(NSDictionary*)launchOptions;
+
+// Runs the launch steps.
+- (void)continueLaunch;
+
+// Runs the termination steps.
+- (void)terminate;
+
+// Runs the background steps.
+- (void)background;
+
+// Runs the foreground steps.
+- (void)foreground;
+
+@end
+
+#endif  // IOS_CHROME_APP_APPLICATION_STATE_H_
diff --git a/ios/chrome/app/application_state.mm b/ios/chrome/app/application_state.mm
new file mode 100644
index 0000000..f6a8fb8
--- /dev/null
+++ b/ios/chrome/app/application_state.mm
@@ -0,0 +1,110 @@
+// Copyright 2016 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.
+
+// ======                        New Architecture                         =====
+// =         This code is only used in the new iOS Chrome architecture.       =
+// ============================================================================
+
+#import "ios/chrome/app/application_state.h"
+
+#include <memory>
+
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/supports_user_data.h"
+#import "ios/chrome/app/application_step.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+
+// Specialization of base::SupportsUserData for use in this class.
+class PersistentApplicationState : public base::SupportsUserData {
+ public:
+  ~PersistentApplicationState() override {}
+};
+
+}  // namespace
+
+@interface ApplicationState ()
+@property(nonatomic, readwrite, copy) NSDictionary* launchOptions;
+@end
+
+@implementation ApplicationState {
+  std::unique_ptr<PersistentApplicationState> _persistentState;
+}
+
+@synthesize application = _application;
+@synthesize browserState = _browserState;
+@synthesize URLOpener = _URLOpener;
+@synthesize phase = _phase;
+@synthesize window = _window;
+@synthesize launchSteps = _launchSteps;
+@synthesize terminationSteps = _terminationSteps;
+@synthesize backgroundSteps = _backgroundSteps;
+@synthesize foregroundSteps = _foregroundSteps;
+@synthesize launchOptions = _launchOptions;
+
+#pragma mark - Object lifecycle
+
+- (instancetype)init {
+  if ((self = [super init])) {
+    _phase = APPLICATION_COLD;
+    _launchSteps = [ApplicationStepArray array];
+    _terminationSteps = [ApplicationStepArray array];
+    _backgroundSteps = [ApplicationStepArray array];
+    _foregroundSteps = [ApplicationStepArray array];
+    _persistentState = base::MakeUnique<PersistentApplicationState>();
+  }
+  return self;
+}
+
+#pragma mark - Public API
+
+- (base::SupportsUserData*)persistentState {
+  return _persistentState.get();
+}
+
+- (void)launchWithOptions:(NSDictionary*)launchOptions {
+  self.launchOptions = launchOptions;
+  [self continueLaunch];
+}
+
+- (void)continueLaunch {
+  [self runSteps:self.launchSteps];
+}
+
+- (void)terminate {
+  CHECK(self.phase != APPLICATION_TERMINATING);
+  self.phase = APPLICATION_TERMINATING;
+  [self runSteps:self.terminationSteps];
+  CHECK(self.terminationSteps.count == 0);
+}
+
+- (void)background {
+  [self runSteps:self.backgroundSteps];
+}
+
+- (void)foreground {
+  [self runSteps:self.foregroundSteps];
+}
+
+#pragma mark - Running steps
+
+// While the first step in |steps| can run in |self|, pop it, run it, and
+// release ownership of it.
+- (void)runSteps:(ApplicationStepArray*)steps {
+  while ([steps.firstObject canRunInState:self]) {
+    id<ApplicationStep> nextStep = steps.firstObject;
+    [steps removeObject:nextStep];
+    // |nextStep| should not be in |steps| when -runInState is called.
+    // (Some steps may re-insert themselves into |steps|, for example).
+    DCHECK(![steps containsObject:nextStep]);
+    [nextStep runInState:self];
+  }
+}
+
+@end
diff --git a/ios/chrome/app/application_step.h b/ios/chrome/app/application_step.h
new file mode 100644
index 0000000..f392b96
--- /dev/null
+++ b/ios/chrome/app/application_step.h
@@ -0,0 +1,30 @@
+// Copyright 2016 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.
+
+// ======                        New Architecture                         =====
+// =         This code is only used in the new iOS Chrome architecture.       =
+// ============================================================================
+
+#ifndef IOS_CHROME_APP_APPLICATION_STEP_H_
+#define IOS_CHROME_APP_APPLICATION_STEP_H_
+
+#import <Foundation/Foundation.h>
+
+@class ApplicationState;
+
+// Objects that perform application state change steps must conform to this
+// protocol.
+@protocol ApplicationStep<NSObject>
+// Implementors should not modify |state| in -canRunInState.
+- (BOOL)canRunInState:(ApplicationState*)state;
+
+// Implementors should expect to be deallocated after -runInState is called.
+// If an implementor creates objects that need to persist longer than that, they
+// should be stored in |state.state|.
+// Implementors can assume that they are not in any of |state|'s step arrays
+// when this method is called.
+- (void)runInState:(ApplicationState*)state;
+@end
+
+#endif  // IOS_CHROME_APP_APPLICATION_STEP_H_
diff --git a/ios/chrome/app/chrome_app_startup_parameters.h b/ios/chrome/app/chrome_app_startup_parameters.h
new file mode 100644
index 0000000..1e875a1
--- /dev/null
+++ b/ios/chrome/app/chrome_app_startup_parameters.h
@@ -0,0 +1,110 @@
+// Copyright 2015 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.
+
+#ifndef IOS_CHROME_APP_CHROME_APP_STARTUP_PARAMETERS_H_
+#define IOS_CHROME_APP_CHROME_APP_STARTUP_PARAMETERS_H_
+
+#import <UIKit/UIKit.h>
+
+#import "ios/chrome/browser/app_startup_parameters.h"
+#import "ios/chrome/browser/first_run/first_run_metrics.h"
+
+// Values of the UMA Startup.MobileSessionCallerApp histogram.
+enum MobileSessionCallerApp {
+  CALLER_APP_GOOGLE_SEARCH = 0,
+  CALLER_APP_GOOGLE_GMAIL,
+  CALLER_APP_GOOGLE_PLUS,
+  CALLER_APP_GOOGLE_DRIVE,
+  CALLER_APP_GOOGLE_EARTH,
+  CALLER_APP_GOOGLE_OTHER,
+  CALLER_APP_OTHER,
+  CALLER_APP_APPLE_MOBILESAFARI,
+  CALLER_APP_APPLE_OTHER,
+  CALLER_APP_GOOGLE_YOUTUBE,
+  CALLER_APP_GOOGLE_MAPS,
+  CALLER_APP_NOT_AVAILABLE,  // Includes being launched from Smart App Banner.
+  CALLER_APP_GOOGLE_CHROME_TODAY_EXTENSION,
+  MOBILE_SESSION_CALLER_APP_COUNT,
+};
+
+@interface ChromeAppStartupParameters : AppStartupParameters
+
+- (instancetype)initWithExternalURL:(const GURL&)externalURL
+                xCallbackParameters:(XCallbackParameters*)xCallbackParameters
+    NS_UNAVAILABLE;
+
+- (instancetype)initWithExternalURL:(const GURL&)externalURL
+                xCallbackParameters:(XCallbackParameters*)xCallbackParameters
+                  declaredSourceApp:(NSString*)declaredSourceApp
+                    secureSourceApp:(NSString*)secureSourceApp
+                        completeURL:(NSURL*)completeURL
+    NS_DESIGNATED_INITIALIZER;
+
+// Returns a ChromeAppStartupParameters instance containing the URL to
+// open (|externalURL|). In case the URL is conforming to the x-callback-url
+// specification, additional information are stored in the returned value.
+//
+// The forms of the URLs we expect are:
+//
+// - protocol0://url/goes/here
+//   Here protocol0s opens the app. The string for the
+//   parsed URL is "url/goes/here" with protocol
+//   "http", that is, the string for the parsed URL is
+//   "http://url/goes/here"
+//
+// - protocol0s://url/goes/here
+//   Here protocol0s opens the app. The string for the
+//   parsed URL is "url/goes/here" with protocol
+//   "https", that is, the string for the parsed URL is
+//   "https://url/goes/here"
+//
+// - url/goes/here
+//   No protocol is given. The string for the parsed URL is
+//   "url/goes/here", with protocol defaulting to "http",
+//   that is, the string for the parsed URL is
+//   "http://url/goes/here"
+//
+// - file://url/goes/here
+//   Here the received URL is a file. This is used in cases where the app
+//   receives a file from another app. The string for the parser URL is
+//   "chrome://external-file/url/goes/here"
+//
+// - x-<protocol>://x-callback-url/<action>?url=<url/goes/here>
+//   This forms is compliant with x-callback-url (x-callback-url.com).
+//   Currently the only action supported for external application is "open" and
+//   the only required parameter is |url| containing the url to open inclusive
+//   of protocol.
+//   For application members of the Chrome Application Group,
+//   "app-group-command" command can be used. In that case, the paramaters are
+//   sent via the shared NSUserDefault dictionary.
+//
+// Note the protocol isn't hardcoded so we accept anything. Moreover, in iOS 6
+// SmartAppBanners can send any URL to the app without even needing the app
+// to be registered for that protocol.
+// If the string for the parsed URL is malformed (according to RFC 2396),
+// returns nil.
++ (instancetype)newChromeAppStartupParametersWithURL:(NSURL*)url
+                               fromSourceApplication:(NSString*)appId;
+
+// Returns the MobileSessionCallerApp for the given bundle ID.
+- (MobileSessionCallerApp)callerApp;
+
+// Checks the parsed url and heuristically determine if it implies that the
+// current openURL: delegate call is the result of a user click on Smart App
+// Banner.
+- (first_run::ExternalLaunch)launchSource;
+
+@end
+
+@interface ChromeAppStartupParameters (Testing)
+
++ (instancetype)newAppStartupParametersForCommand:(NSString*)command
+                                    withParameter:(id)parameter
+                                          withURL:(NSURL*)url
+                            fromSourceApplication:(NSString*)appId
+                      fromSecureSourceApplication:(NSString*)secureSourceApp;
+
+@end
+
+#endif  // IOS_CHROME_APP_CHROME_APP_STARTUP_PARAMETERS_H_
diff --git a/ios/chrome/app/chrome_app_startup_parameters.mm b/ios/chrome/app/chrome_app_startup_parameters.mm
new file mode 100644
index 0000000..b00dc5c
--- /dev/null
+++ b/ios/chrome/app/chrome_app_startup_parameters.mm
@@ -0,0 +1,339 @@
+// Copyright 2015 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/chrome_app_startup_parameters.h"
+
+#include "base/logging.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/user_metrics_action.h"
+#include "base/strings/sys_string_conversions.h"
+#include "ios/chrome/browser/chrome_url_constants.h"
+#include "ios/chrome/browser/xcallback_parameters.h"
+#include "ios/chrome/common/app_group/app_group_constants.h"
+#include "ios/chrome/common/x_callback_url.h"
+#import "net/base/mac/url_conversions.h"
+#include "url/gurl.h"
+
+namespace {
+
+// Key of the UMA Startup.MobileSessionStartAction histogram.
+const char kUMAMobileSessionStartActionHistogram[] =
+    "Startup.MobileSessionStartAction";
+
+const char kApplicationGroupCommandDelay[] =
+    "Startup.ApplicationGroupCommandDelay";
+
+// URL Query String parameter to indicate that this openURL: request arrived
+// here due to a Smart App Banner presentation on a Google.com page.
+NSString* const kSmartAppBannerKey = @"safarisab";
+
+const CGFloat kAppGroupTriggersVoiceSearchTimeout = 15.0;
+
+// Values of the UMA Startup.MobileSessionStartAction histogram.
+enum MobileSessionStartAction {
+  START_ACTION_OPEN_HTTP = 0,
+  START_ACTION_OPEN_HTTPS,
+  START_ACTION_OPEN_FILE,
+  START_ACTION_XCALLBACK_OPEN,
+  START_ACTION_XCALLBACK_OTHER,
+  START_ACTION_OTHER,
+  START_ACTION_XCALLBACK_APPGROUP_COMMAND,
+  MOBILE_SESSION_START_ACTION_COUNT,
+};
+
+}  // namespace
+
+@implementation ChromeAppStartupParameters {
+  base::scoped_nsobject<NSString> _secureSourceApp;
+  base::scoped_nsobject<NSString> _declaredSourceApp;
+  base::scoped_nsobject<NSURL> _completeURL;
+}
+
+- (instancetype)initWithExternalURL:(const GURL&)externalURL
+                xCallbackParameters:(XCallbackParameters*)xCallbackParameters {
+  NOTREACHED();
+  return nil;
+}
+
+- (instancetype)initWithExternalURL:(const GURL&)externalURL
+                xCallbackParameters:(XCallbackParameters*)xCallbackParameters
+                  declaredSourceApp:(NSString*)declaredSourceApp
+                    secureSourceApp:(NSString*)secureSourceApp
+                        completeURL:(NSURL*)completeURL {
+  self = [super initWithExternalURL:externalURL
+                xCallbackParameters:xCallbackParameters];
+  if (self) {
+    _declaredSourceApp.reset([declaredSourceApp copy]);
+    _secureSourceApp.reset([secureSourceApp copy]);
+    _completeURL.reset([completeURL retain]);
+  }
+  return self;
+}
+
++ (instancetype)newChromeAppStartupParametersWithURL:(NSURL*)completeURL
+                               fromSourceApplication:(NSString*)appId {
+  GURL gurl = net::GURLWithNSURL(completeURL);
+
+  if (!gurl.is_valid() || gurl.scheme().length() == 0)
+    return nil;
+
+  // TODO(ios): Temporary fix for b/7174478
+  if (IsXCallbackURL(gurl)) {
+    NSString* action = [completeURL path];
+    // Currently only "open" and "extension-command" are supported.
+    // Other actions are being considered (see b/6914153).
+    if ([action
+            isEqualToString:
+                [NSString
+                    stringWithFormat:
+                        @"/%s", app_group::kChromeAppGroupXCallbackCommand]]) {
+      UMA_HISTOGRAM_ENUMERATION(kUMAMobileSessionStartActionHistogram,
+                                START_ACTION_XCALLBACK_APPGROUP_COMMAND,
+                                MOBILE_SESSION_START_ACTION_COUNT);
+      return [ChromeAppStartupParameters
+          newExtensionCommandAppStartupParametersFromWithURL:completeURL
+                                       fromSourceApplication:appId];
+    }
+
+    if (![action isEqualToString:@"/open"]) {
+      UMA_HISTOGRAM_ENUMERATION(kUMAMobileSessionStartActionHistogram,
+                                START_ACTION_XCALLBACK_OTHER,
+                                MOBILE_SESSION_START_ACTION_COUNT);
+      return nil;
+    }
+
+    UMA_HISTOGRAM_ENUMERATION(kUMAMobileSessionStartActionHistogram,
+                              START_ACTION_XCALLBACK_OPEN,
+                              MOBILE_SESSION_START_ACTION_COUNT);
+
+    std::map<std::string, std::string> parameters =
+        ExtractQueryParametersFromXCallbackURL(gurl);
+    GURL url = GURL(parameters["url"]);
+    if (!url.is_valid() ||
+        (!url.SchemeIs(url::kHttpScheme) && !url.SchemeIs(url::kHttpsScheme))) {
+      return nil;
+    }
+
+    base::scoped_nsobject<XCallbackParameters> xcallbackParameters(
+        [[XCallbackParameters alloc] initWithSourceAppId:appId]);
+
+    return [[ChromeAppStartupParameters alloc]
+        initWithExternalURL:url
+        xCallbackParameters:xcallbackParameters
+          declaredSourceApp:appId
+            secureSourceApp:nil
+                completeURL:completeURL];
+
+  } else if (gurl.SchemeIsFile()) {
+    UMA_HISTOGRAM_ENUMERATION(kUMAMobileSessionStartActionHistogram,
+                              START_ACTION_OPEN_FILE,
+                              MOBILE_SESSION_START_ACTION_COUNT);
+    // |url| is the path to a file received from another application.
+    GURL::Replacements replacements;
+    const std::string host(kChromeUIExternalFileHost);
+    std::string filename = gurl.ExtractFileName();
+    replacements.SetPathStr(filename);
+    replacements.SetSchemeStr(kChromeUIScheme);
+    replacements.SetHostStr(host);
+    GURL externalURL = gurl.ReplaceComponents(replacements);
+    if (!externalURL.is_valid())
+      return nil;
+    return [[ChromeAppStartupParameters alloc] initWithExternalURL:externalURL
+                                               xCallbackParameters:nil
+                                                 declaredSourceApp:appId
+                                                   secureSourceApp:nil
+                                                       completeURL:completeURL];
+  } else {
+    // Replace the scheme with https or http depending on whether the input
+    // |url| scheme ends with an 's'.
+    BOOL useHttps = gurl.scheme()[gurl.scheme().length() - 1] == 's';
+    MobileSessionStartAction action =
+        useHttps ? START_ACTION_OPEN_HTTPS : START_ACTION_OPEN_HTTP;
+    UMA_HISTOGRAM_ENUMERATION(kUMAMobileSessionStartActionHistogram, action,
+                              MOBILE_SESSION_START_ACTION_COUNT);
+    GURL::Replacements replace_scheme;
+    if (useHttps)
+      replace_scheme.SetSchemeStr(url::kHttpsScheme);
+    else
+      replace_scheme.SetSchemeStr(url::kHttpScheme);
+    GURL externalURL = gurl.ReplaceComponents(replace_scheme);
+    if (!externalURL.is_valid())
+      return nil;
+    return [[ChromeAppStartupParameters alloc] initWithExternalURL:externalURL
+                                               xCallbackParameters:nil
+                                                 declaredSourceApp:appId
+                                                   secureSourceApp:nil
+                                                       completeURL:completeURL];
+  }
+}
+
++ (instancetype)newExtensionCommandAppStartupParametersFromWithURL:(NSURL*)url
+                                             fromSourceApplication:
+                                                 (NSString*)appId {
+  NSString* appGroup = app_group::ApplicationGroup();
+  base::scoped_nsobject<NSUserDefaults> sharedDefaults(
+      [[NSUserDefaults alloc] initWithSuiteName:appGroup]);
+
+  NSString* commandDictionaryPreference =
+      base::SysUTF8ToNSString(app_group::kChromeAppGroupCommandPreference);
+  NSDictionary* commandDictionary = base::mac::ObjCCast<NSDictionary>(
+      [sharedDefaults objectForKey:commandDictionaryPreference]);
+
+  [sharedDefaults removeObjectForKey:commandDictionaryPreference];
+
+  // |sharedDefaults| is used for communication between apps. Synchronize to
+  // avoid synchronization issues (like removing the next order).
+  [sharedDefaults synchronize];
+
+  if (!commandDictionary) {
+    return nil;
+  }
+
+  NSString* commandCallerPreference =
+      base::SysUTF8ToNSString(app_group::kChromeAppGroupCommandAppPreference);
+  NSString* commandCaller = base::mac::ObjCCast<NSString>(
+      [commandDictionary objectForKey:commandCallerPreference]);
+
+  NSString* commandPreference = base::SysUTF8ToNSString(
+      app_group::kChromeAppGroupCommandCommandPreference);
+  NSString* command = base::mac::ObjCCast<NSString>(
+      [commandDictionary objectForKey:commandPreference]);
+
+  NSString* commandTimePreference =
+      base::SysUTF8ToNSString(app_group::kChromeAppGroupCommandTimePreference);
+  id commandTime = base::mac::ObjCCast<NSDate>(
+      [commandDictionary objectForKey:commandTimePreference]);
+
+  NSString* commandParameterPreference = base::SysUTF8ToNSString(
+      app_group::kChromeAppGroupCommandParameterPreference);
+  NSString* commandParameter = base::mac::ObjCCast<NSString>(
+      [commandDictionary objectForKey:commandParameterPreference]);
+
+  if (!commandCaller || !command || !commandTimePreference) {
+    return nil;
+  }
+
+  // Check the time of the last request to avoid app from intercepting old
+  // open url request and replay it later.
+  NSTimeInterval delay = [[NSDate date] timeIntervalSinceDate:commandTime];
+  UMA_HISTOGRAM_COUNTS_100(kApplicationGroupCommandDelay, delay);
+  if (delay > kAppGroupTriggersVoiceSearchTimeout)
+    return nil;
+  return [ChromeAppStartupParameters
+      newAppStartupParametersForCommand:command
+                          withParameter:commandParameter
+                                withURL:url
+                  fromSourceApplication:appId
+            fromSecureSourceApplication:commandCaller];
+}
+
++ (instancetype)newAppStartupParametersForCommand:(NSString*)command
+                                    withParameter:(id)parameter
+                                          withURL:(NSURL*)url
+                            fromSourceApplication:(NSString*)appId
+                      fromSecureSourceApplication:(NSString*)secureSourceApp {
+  if ([command
+          isEqualToString:base::SysUTF8ToNSString(
+                              app_group::kChromeAppGroupVoiceSearchCommand)]) {
+    ChromeAppStartupParameters* params = [[ChromeAppStartupParameters alloc]
+        initWithExternalURL:GURL(kChromeUINewTabURL)
+        xCallbackParameters:nil
+          declaredSourceApp:appId
+            secureSourceApp:secureSourceApp
+                completeURL:url];
+    [params setLaunchVoiceSearch:YES];
+    return params;
+  }
+
+  if ([command isEqualToString:base::SysUTF8ToNSString(
+                                   app_group::kChromeAppGroupNewTabCommand)]) {
+    return [[ChromeAppStartupParameters alloc]
+        initWithExternalURL:GURL(kChromeUINewTabURL)
+        xCallbackParameters:nil
+          declaredSourceApp:appId
+            secureSourceApp:secureSourceApp
+                completeURL:url];
+  }
+  if ([command isEqualToString:base::SysUTF8ToNSString(
+                                   app_group::kChromeAppGroupOpenURLCommand)]) {
+    if (!parameter || ![parameter isKindOfClass:[NSString class]])
+      return nil;
+    GURL externalURL(base::SysNSStringToUTF8(parameter));
+    if (!externalURL.is_valid() || !externalURL.SchemeIsHTTPOrHTTPS())
+      return nil;
+    return
+        [[ChromeAppStartupParameters alloc] initWithExternalURL:externalURL
+                                            xCallbackParameters:nil
+                                              declaredSourceApp:appId
+                                                secureSourceApp:secureSourceApp
+                                                    completeURL:url];
+  }
+
+  return nil;
+}
+
+- (MobileSessionCallerApp)callerApp {
+  if ([_secureSourceApp isEqualToString:@"TodayExtension"])
+    return CALLER_APP_GOOGLE_CHROME_TODAY_EXTENSION;
+
+  if (![_declaredSourceApp length])
+    return CALLER_APP_NOT_AVAILABLE;
+  if ([_declaredSourceApp isEqualToString:@"com.google.GoogleMobile"])
+    return CALLER_APP_GOOGLE_SEARCH;
+  if ([_declaredSourceApp isEqualToString:@"com.google.Gmail"])
+    return CALLER_APP_GOOGLE_GMAIL;
+  if ([_declaredSourceApp isEqualToString:@"com.google.GooglePlus"])
+    return CALLER_APP_GOOGLE_PLUS;
+  if ([_declaredSourceApp isEqualToString:@"com.google.Drive"])
+    return CALLER_APP_GOOGLE_DRIVE;
+  if ([_declaredSourceApp isEqualToString:@"com.google.b612"])
+    return CALLER_APP_GOOGLE_EARTH;
+  if ([_declaredSourceApp isEqualToString:@"com.google.ios.youtube"])
+    return CALLER_APP_GOOGLE_YOUTUBE;
+  if ([_declaredSourceApp isEqualToString:@"com.google.Maps"])
+    return CALLER_APP_GOOGLE_MAPS;
+  if ([_declaredSourceApp hasPrefix:@"com.google."])
+    return CALLER_APP_GOOGLE_OTHER;
+  if ([_declaredSourceApp isEqualToString:@"com.apple.mobilesafari"])
+    return CALLER_APP_APPLE_MOBILESAFARI;
+  if ([_declaredSourceApp hasPrefix:@"com.apple."])
+    return CALLER_APP_APPLE_OTHER;
+
+  return CALLER_APP_OTHER;
+}
+
+- (first_run::ExternalLaunch)launchSource {
+  if ([self callerApp] != CALLER_APP_APPLE_MOBILESAFARI) {
+    return first_run::LAUNCH_BY_OTHERS;
+  }
+
+  NSString* query = [_completeURL query];
+  // Takes care of degenerated case of no QUERY_STRING.
+  if (![query length])
+    return first_run::LAUNCH_BY_MOBILESAFARI;
+  // Look for |kSmartAppBannerKey| anywhere within the query string.
+  NSRange found = [query rangeOfString:kSmartAppBannerKey];
+  if (found.location == NSNotFound)
+    return first_run::LAUNCH_BY_MOBILESAFARI;
+  // |kSmartAppBannerKey| can be at the beginning or end of the query
+  // string and may also be optionally followed by a equal sign and a value.
+  // For now, just look for the presence of the key and ignore the value.
+  if (found.location + found.length < [query length]) {
+    // There are characters following the found location.
+    unichar charAfter =
+        [query characterAtIndex:(found.location + found.length)];
+    if (charAfter != '&' && charAfter != '=')
+      return first_run::LAUNCH_BY_MOBILESAFARI;
+  }
+  if (found.location > 0) {
+    unichar charBefore = [query characterAtIndex:(found.location - 1)];
+    if (charBefore != '&')
+      return first_run::LAUNCH_BY_MOBILESAFARI;
+  }
+  return first_run::LAUNCH_BY_SMARTAPPBANNER;
+}
+
+@end
diff --git a/ios/chrome/app/chrome_app_startup_parameters_unittest.mm b/ios/chrome/app/chrome_app_startup_parameters_unittest.mm
new file mode 100644
index 0000000..58b4405
--- /dev/null
+++ b/ios/chrome/app/chrome_app_startup_parameters_unittest.mm
@@ -0,0 +1,244 @@
+// Copyright 2015 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/chrome_app_startup_parameters.h"
+
+#include <Foundation/Foundation.h>
+
+#include "base/mac/scoped_nsobject.h"
+#include "base/strings/stringprintf.h"
+#include "ios/chrome/browser/app_startup_parameters.h"
+#include "ios/chrome/browser/chrome_url_constants.h"
+#import "ios/chrome/browser/xcallback_parameters.h"
+#include "ios/chrome/common/app_group/app_group_constants.h"
+#include "testing/gtest_mac.h"
+#include "testing/platform_test.h"
+#include "url/gurl.h"
+
+namespace {
+void CheckLaunchSourceForURL(first_run::ExternalLaunch expectedSource,
+                             NSString* urlString) {
+  NSURL* url = [NSURL URLWithString:urlString];
+  base::scoped_nsobject<ChromeAppStartupParameters> params(
+      [ChromeAppStartupParameters
+          newChromeAppStartupParametersWithURL:url
+                         fromSourceApplication:@"com.apple.mobilesafari"]);
+  EXPECT_EQ(expectedSource, [params launchSource]);
+}
+
+typedef PlatformTest AppStartupParametersTest;
+TEST_F(PlatformTest, ParseURLWithEmptyURL) {
+  NSURL* url = [NSURL URLWithString:@""];
+  base::scoped_nsobject<ChromeAppStartupParameters> params(
+      [ChromeAppStartupParameters newChromeAppStartupParametersWithURL:url
+                                                 fromSourceApplication:nil]);
+
+  EXPECT_FALSE(params);
+}
+
+TEST_F(AppStartupParametersTest, ParseURLWithOneProtocol) {
+  NSURL* url = [NSURL URLWithString:@"protocol://www.google.com"];
+  base::scoped_nsobject<ChromeAppStartupParameters> params(
+      [ChromeAppStartupParameters newChromeAppStartupParametersWithURL:url
+                                                 fromSourceApplication:nil]);
+  // Here "protocol" opens the app and no protocol is given for the parsed URL,
+  // which defaults to be "http".
+  EXPECT_EQ("http://www.google.com/", [params externalURL].spec());
+}
+
+TEST_F(AppStartupParametersTest, ParseURLWithEmptyParsedURL) {
+  // Test chromium://
+  NSURL* url = [NSURL URLWithString:@"chromium://"];
+  base::scoped_nsobject<ChromeAppStartupParameters> params(
+      [ChromeAppStartupParameters newChromeAppStartupParametersWithURL:url
+                                                 fromSourceApplication:nil]);
+
+  EXPECT_FALSE(params);
+}
+
+TEST_F(AppStartupParametersTest, ParseURLWithParsedURLDefaultToHttp) {
+  NSURL* url = [NSURL URLWithString:@"chromium://www.google.com"];
+  base::scoped_nsobject<ChromeAppStartupParameters> params(
+      [ChromeAppStartupParameters newChromeAppStartupParametersWithURL:url
+                                                 fromSourceApplication:nil]);
+
+  EXPECT_EQ("http://www.google.com/", [params externalURL].spec());
+}
+
+TEST_F(AppStartupParametersTest, ParseURLWithInvalidParsedURL) {
+  NSURL* url = [NSURL URLWithString:@"http:google.com:foo"];
+  base::scoped_nsobject<ChromeAppStartupParameters> params(
+      [ChromeAppStartupParameters newChromeAppStartupParametersWithURL:url
+                                                 fromSourceApplication:nil]);
+
+  EXPECT_FALSE(params);
+}
+
+TEST_F(AppStartupParametersTest, ParseURLWithHttpsParsedURL) {
+  NSURL* url = [NSURL URLWithString:@"chromiums://www.google.com"];
+  base::scoped_nsobject<ChromeAppStartupParameters> params(
+      [ChromeAppStartupParameters newChromeAppStartupParametersWithURL:url
+                                                 fromSourceApplication:nil]);
+
+  EXPECT_EQ("https://www.google.com/", [params externalURL].spec());
+}
+
+TEST_F(AppStartupParametersTest, ParseURLWithXCallbackURL) {
+  NSURL* url =
+      [NSURL URLWithString:@"chromium-x-callback://x-callback-url/open?"
+                            "url=https://www.google.com"];
+  base::scoped_nsobject<ChromeAppStartupParameters> params(
+      [ChromeAppStartupParameters newChromeAppStartupParametersWithURL:url
+                                                 fromSourceApplication:nil]);
+  EXPECT_EQ("https://www.google.com/", [params externalURL].spec());
+}
+
+TEST_F(AppStartupParametersTest, ParseURLWithXCallbackURLAndExtraParams) {
+  NSURL* url =
+      [NSURL URLWithString:@"chromium-x-callback://x-callback-url/open?"
+                            "url=https://www.google.com&"
+                            "x-success=http://success"];
+  base::scoped_nsobject<ChromeAppStartupParameters> params(
+      [ChromeAppStartupParameters newChromeAppStartupParametersWithURL:url
+                                                 fromSourceApplication:nil]);
+  EXPECT_EQ("https://www.google.com/", [params externalURL].spec());
+}
+
+TEST_F(AppStartupParametersTest, ParseURLWithMalformedXCallbackURL) {
+  NSURL* url = [NSURL
+      URLWithString:@"chromium-x-callback://x-callback-url/open?url=foobar&"
+                     "x-source=myapp&x-success=http://success"];
+  base::scoped_nsobject<ChromeAppStartupParameters> params(
+      [ChromeAppStartupParameters
+          newChromeAppStartupParametersWithURL:url
+                         fromSourceApplication:@"com.myapp"]);
+  EXPECT_FALSE(params);
+}
+
+TEST_F(AppStartupParametersTest, ParseURLWithJavascriptURLInXCallbackURL) {
+  NSURL* url = [NSURL
+      URLWithString:
+          @"chromium-x-callback://x-callback-url/open?url="
+           "javascript:window.open()&x-source=myapp&x-success=http://success"];
+  base::scoped_nsobject<ChromeAppStartupParameters> params(
+      [ChromeAppStartupParameters
+          newChromeAppStartupParametersWithURL:url
+                         fromSourceApplication:@"com.myapp"]);
+  EXPECT_FALSE(params);
+}
+
+TEST_F(AppStartupParametersTest, ParseURLWithChromeURLInXCallbackURL) {
+  NSURL* url =
+      [NSURL URLWithString:@"chromium-x-callback://x-callback-url/open?url="
+                            "chrome:passwords"];
+  base::scoped_nsobject<ChromeAppStartupParameters> params(
+      [ChromeAppStartupParameters
+          newChromeAppStartupParametersWithURL:url
+                         fromSourceApplication:@"com.myapp"]);
+  EXPECT_FALSE(params);
+}
+
+TEST_F(AppStartupParametersTest, ParseURLWithFileParsedURL) {
+  NSURL* url = [NSURL URLWithString:@"file://localhost/path/to/file.pdf"];
+  base::scoped_nsobject<ChromeAppStartupParameters> params(
+      [ChromeAppStartupParameters newChromeAppStartupParametersWithURL:url
+                                                 fromSourceApplication:nil]);
+
+  std::string expectedUrlString = base::StringPrintf(
+      "%s://%s/file.pdf", kChromeUIScheme, kChromeUIExternalFileHost);
+
+  EXPECT_EQ(expectedUrlString, [params externalURL].spec());
+}
+
+TEST_F(AppStartupParametersTest, ParseURLWithAppGroupVoiceSearch) {
+  base::scoped_nsobject<ChromeAppStartupParameters> params(
+      [ChromeAppStartupParameters
+          newAppStartupParametersForCommand:@"voicesearch"
+                              withParameter:nil
+                                    withURL:nil
+                      fromSourceApplication:nil
+                fromSecureSourceApplication:nil]);
+
+  std::string expectedUrlString =
+      base::StringPrintf("%s://%s/", kChromeUIScheme, kChromeUINewTabHost);
+
+  EXPECT_EQ(expectedUrlString, [params externalURL].spec());
+  EXPECT_TRUE([params launchVoiceSearch]);
+}
+
+TEST_F(AppStartupParametersTest, ParseURLWithAppGroupNewTab) {
+  base::scoped_nsobject<ChromeAppStartupParameters> params(
+      [ChromeAppStartupParameters newAppStartupParametersForCommand:@"newtab"
+                                                      withParameter:nil
+                                                            withURL:nil
+                                              fromSourceApplication:nil
+                                        fromSecureSourceApplication:nil]);
+  std::string expectedUrlString =
+      base::StringPrintf("%s://%s/", kChromeUIScheme, kChromeUINewTabHost);
+
+  EXPECT_EQ(expectedUrlString, [params externalURL].spec());
+  EXPECT_FALSE([params launchVoiceSearch]);
+}
+
+TEST_F(AppStartupParametersTest, ParseURLWithAppGroupOpenURL) {
+  base::scoped_nsobject<ChromeAppStartupParameters> params(
+      [ChromeAppStartupParameters
+          newAppStartupParametersForCommand:@"openurl"
+                              withParameter:@"http://foo/bar"
+                                    withURL:nil
+                      fromSourceApplication:nil
+                fromSecureSourceApplication:nil]);
+
+  EXPECT_EQ("http://foo/bar", [params externalURL].spec());
+}
+
+TEST_F(AppStartupParametersTest, ParseURLWithAppGroupGarbage) {
+  base::scoped_nsobject<ChromeAppStartupParameters> params(
+      [ChromeAppStartupParameters newAppStartupParametersForCommand:@"garbage"
+                                                      withParameter:nil
+                                                            withURL:nil
+                                              fromSourceApplication:nil
+                                        fromSecureSourceApplication:nil]);
+  EXPECT_FALSE(params);
+}
+
+TEST_F(AppStartupParametersTest, FirstRunExternalLaunchSource) {
+  // Key at the beginning of query string.
+  CheckLaunchSourceForURL(
+      first_run::LAUNCH_BY_SMARTAPPBANNER,
+      @"http://www.google.com/search?safarisab=1&query=pony");
+  // Key at the end of query string.
+  CheckLaunchSourceForURL(
+      first_run::LAUNCH_BY_SMARTAPPBANNER,
+      @"http://www.google.com/search?query=pony&safarisab=1");
+  // Key in the middle of query string.
+  CheckLaunchSourceForURL(
+      first_run::LAUNCH_BY_SMARTAPPBANNER,
+      @"http://www.google.com/search?query=pony&safarisab=1&hl=en");
+  // Key without '=' sign at the beginning, end, and middle of query string.
+  CheckLaunchSourceForURL(first_run::LAUNCH_BY_SMARTAPPBANNER,
+                          @"http://www.google.com/search?safarisab&query=pony");
+  CheckLaunchSourceForURL(first_run::LAUNCH_BY_SMARTAPPBANNER,
+                          @"http://www.google.com/search?query=pony&safarisab");
+  CheckLaunchSourceForURL(
+      first_run::LAUNCH_BY_SMARTAPPBANNER,
+      @"http://www.google.com/search?query=pony&safarisab&hl=en");
+  // No query string in URL.
+  CheckLaunchSourceForURL(first_run::LAUNCH_BY_MOBILESAFARI,
+                          @"http://www.google.com/");
+  CheckLaunchSourceForURL(first_run::LAUNCH_BY_MOBILESAFARI,
+                          @"http://www.google.com/safarisab/foo/bar");
+  // Key not present in query string.
+  CheckLaunchSourceForURL(first_run::LAUNCH_BY_MOBILESAFARI,
+                          @"http://www.google.com/search?query=pony");
+  // Key is a substring of some other string.
+  CheckLaunchSourceForURL(
+      first_run::LAUNCH_BY_MOBILESAFARI,
+      @"http://www.google.com/search?query=pony&safarisabcdefg=1");
+  CheckLaunchSourceForURL(
+      first_run::LAUNCH_BY_MOBILESAFARI,
+      @"http://www.google.com/search?query=pony&notsafarisab=1&abc=def");
+}
+
+}  // namespace
diff --git a/ios/chrome/app/chrome_exe_main.mm b/ios/chrome/app/chrome_exe_main.mm
new file mode 100644
index 0000000..c447445
--- /dev/null
+++ b/ios/chrome/app/chrome_exe_main.mm
@@ -0,0 +1,69 @@
+// 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 <UIKit/UIKit.h>
+
+#include "base/at_exit.h"
+#include "base/debug/crash_logging.h"
+#include "components/crash/core/common/crash_keys.h"
+#include "ios/chrome/app/startup/ios_chrome_main.h"
+#include "ios/chrome/browser/crash_report/breakpad_helper.h"
+#include "ios/chrome/browser/crash_report/crash_keys.h"
+#include "ios/chrome/common/channel_info.h"
+
+namespace {
+
+NSString* const kUIApplicationDelegateInfoKey = @"UIApplicationDelegate";
+
+void StartCrashController() {
+  std::string channel_string = GetChannelString();
+
+  RegisterChromeIOSCrashKeys();
+  base::debug::SetCrashKeyValue(crash_keys::kChannel, channel_string);
+  breakpad_helper::Start(channel_string);
+}
+
+}  // namespace
+
+int main(int argc, char* argv[]) {
+  IOSChromeMain::InitStartTime();
+  NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+
+  NSUserDefaults* standardDefaults = [NSUserDefaults standardUserDefaults];
+
+  // Set NSUserDefaults keys to force pseudo-RTL if needed.
+  if ([standardDefaults boolForKey:@"EnablePseudoRTL"]) {
+    NSDictionary* pseudoDict = [NSDictionary
+        dictionaryWithObjectsAndKeys:@"YES", @"AppleTextDirection", @"YES",
+                                     @"NSForceRightToLeftWritingDirection",
+                                     nil];
+    [standardDefaults registerDefaults:pseudoDict];
+  }
+
+  // Create this here since it's needed to start the crash handler.
+  base::AtExitManager at_exit;
+
+  // The Crash Controller is started here even if the user opted out since we
+  // don't have yet preferences. Later on it is stopped if the user opted out.
+  // In any case reports are not sent if the user opted out.
+  StartCrashController();
+
+  // Always ignore SIGPIPE.  We check the return value of write().
+  CHECK_NE(SIG_ERR, signal(SIGPIPE, SIG_IGN));
+
+  // Purging the pool to prevent autorelease objects created by the previous
+  // calls to live forever.
+  [pool release];
+  pool = [[NSAutoreleasePool alloc] init];
+
+  // Part of code that requires us to specify which UIApplication delegate class
+  // to use by adding "UIApplicationDelegate" key to Info.plist file.
+  NSString* delegateClassName = [[NSBundle mainBundle]
+      objectForInfoDictionaryKey:kUIApplicationDelegateInfoKey];
+  CHECK(delegateClassName);
+
+  int retVal = UIApplicationMain(argc, argv, nil, delegateClassName);
+  [pool release];
+  return retVal;
+}
diff --git a/ios/chrome/app/chrome_overlay_window.h b/ios/chrome/app/chrome_overlay_window.h
new file mode 100644
index 0000000..4ba7c79
--- /dev/null
+++ b/ios/chrome/app/chrome_overlay_window.h
@@ -0,0 +1,15 @@
+// Copyright 2015 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.
+
+#ifndef IOS_CHROME_APP_CHROME_OVERLAY_WINDOW_H_
+#define IOS_CHROME_APP_CHROME_OVERLAY_WINDOW_H_
+
+#import "ios/third_party/material_components_ios/src/components/OverlayWindow/src/MaterialOverlayWindow.h"
+
+// Tracks size classes changes and kTabModelTabDidFinishLoadingNotification
+// then reports to SizeClassRecorder and Breakpad.
+@interface ChromeOverlayWindow : MDCOverlayWindow
+@end
+
+#endif  // IOS_CHROME_APP_CHROME_OVERLAY_WINDOW_H_
diff --git a/ios/chrome/app/chrome_overlay_window.mm b/ios/chrome/app/chrome_overlay_window.mm
new file mode 100644
index 0000000..7962b20
--- /dev/null
+++ b/ios/chrome/app/chrome_overlay_window.mm
@@ -0,0 +1,96 @@
+// Copyright 2015 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/chrome_overlay_window.h"
+
+#include "base/logging.h"
+#import "base/mac/scoped_nsobject.h"
+#import "ios/chrome/browser/crash_report/breakpad_helper.h"
+#import "ios/chrome/browser/metrics/size_class_recorder.h"
+#import "ios/chrome/browser/tabs/tab_model.h"
+#import "ios/chrome/browser/ui/ui_util.h"
+
+@interface ChromeOverlayWindow () {
+  base::scoped_nsobject<SizeClassRecorder> _recorder;
+}
+
+// Initializes the size class recorder. On iPad iOS 9+, it starts tracking
+// horizontal size class changes. Otherwise, it is a no-op.
+- (void)initializeRecorderIfNeeded;
+
+// Updates the Breakpad report with the current size class on iOS 8+. Otherwise,
+// it's a no-op since size class doesn't exist.
+- (void)updateBreakpad;
+
+@end
+
+@implementation ChromeOverlayWindow
+
+- (instancetype)initWithFrame:(CGRect)frame {
+  self = [super initWithFrame:frame];
+  if (self) {
+    // When not created via a nib, create the recorder immediately.
+    [self initializeRecorderIfNeeded];
+    [self updateBreakpad];
+  }
+  return self;
+}
+
+- (void)awakeFromNib {
+  [super awakeFromNib];
+  // When creating via a nib, wait to be awoken, as the size class is not
+  // reliable before.
+  [self initializeRecorderIfNeeded];
+  [self updateBreakpad];
+}
+
+- (void)initializeRecorderIfNeeded {
+  DCHECK(!_recorder);
+  if (IsIPadIdiom()) {
+    _recorder.reset([[SizeClassRecorder alloc]
+        initWithHorizontalSizeClass:self.traitCollection.horizontalSizeClass]);
+    [[NSNotificationCenter defaultCenter]
+        addObserver:self
+           selector:@selector(pageLoaded:)
+               name:kTabModelTabDidFinishLoadingNotification
+             object:nil];
+  }
+}
+
+- (void)updateBreakpad {
+  breakpad_helper::SetCurrentHorizontalSizeClass(
+      self.traitCollection.horizontalSizeClass);
+}
+
+- (void)dealloc {
+  [[NSNotificationCenter defaultCenter] removeObserver:self];
+  [super dealloc];
+}
+
+#pragma mark - UITraitEnvironment
+
+- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
+  [super traitCollectionDidChange:previousTraitCollection];
+  if (previousTraitCollection.horizontalSizeClass !=
+      self.traitCollection.horizontalSizeClass) {
+    [_recorder
+        horizontalSizeClassDidChange:self.traitCollection.horizontalSizeClass];
+    [self updateBreakpad];
+  }
+}
+
+#pragma mark - Notification handler
+
+- (void)pageLoaded:(NSNotification*)notification {
+  [_recorder pageLoadedWithHorizontalSizeClass:self.traitCollection
+                                                   .horizontalSizeClass];
+}
+
+#pragma mark - Testing methods
+
+- (void)unsetSizeClassRecorder {
+  _recorder.reset();
+}
+
+@end
diff --git a/ios/chrome/app/chrome_overlay_window_testing.h b/ios/chrome/app/chrome_overlay_window_testing.h
new file mode 100644
index 0000000..662d1b2
--- /dev/null
+++ b/ios/chrome/app/chrome_overlay_window_testing.h
@@ -0,0 +1,16 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_CHROME_OVERLAY_WINDOW_TESTING_H_
+#define IOS_CHROME_APP_CHROME_OVERLAY_WINDOW_TESTING_H_
+
+#import "ios/chrome/app/chrome_overlay_window.h"
+
+@interface ChromeOverlayWindow (ForTesting)
+
+// Cleans up the size class recorder of main window for testing.
+- (void)unsetSizeClassRecorder;
+
+@end
+#endif  // IOS_CHROME_APP_CHROME_OVERLAY_WINDOW_TESTING_H_
diff --git a/ios/chrome/app/main.mm b/ios/chrome/app/main.mm
new file mode 100644
index 0000000..4657ab70
--- /dev/null
+++ b/ios/chrome/app/main.mm
@@ -0,0 +1,72 @@
+// Copyright 2016 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.
+
+// ======                        New Architecture                         =====
+// =         This code is only used in the new iOS Chrome architecture.       =
+// ============================================================================
+
+#import <UIKit/UIKit.h>
+
+#include "base/at_exit.h"
+#include "base/debug/crash_logging.h"
+#include "components/crash/core/common/crash_keys.h"
+#include "ios/chrome/app/app_delegate.h"
+#include "ios/chrome/app/startup/ios_chrome_main.h"
+#include "ios/chrome/browser/crash_report/breakpad_helper.h"
+#include "ios/chrome/browser/crash_report/crash_keys.h"
+#include "ios/chrome/common/channel_info.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+
+void StartCrashController() {
+  std::string channel_string = GetChannelString();
+
+  RegisterChromeIOSCrashKeys();
+  base::debug::SetCrashKeyValue(crash_keys::kChannel, channel_string);
+  breakpad_helper::Start(channel_string);
+}
+
+}  // namespace
+
+int main(int argc, char* argv[]) {
+  // Create the AtExitManager for the application before the crash controller
+  // is started. This needs to be stack allocated and live for the lifetime of
+  // the app.
+  base::AtExitManager at_exit;
+
+  IOSChromeMain::InitStartTime();
+
+  // Pre-launch actions are in their own autorelease pool so they get cleaned
+  // up before the main app starts.
+  @autoreleasepool {
+    NSUserDefaults* standardDefaults = [NSUserDefaults standardUserDefaults];
+
+    // Set NSUserDefaults keys to force pseudo-RTL if needed.
+    if ([standardDefaults boolForKey:@"EnablePseudoRTL"]) {
+      NSDictionary* pseudoDict = @{ @"YES" : @"AppleTextDirection" };
+      [standardDefaults registerDefaults:pseudoDict];
+    }
+
+    // The crash controller is started here, even though the user may have opted
+    // out; it needs to start as soon as possible to catch crashes during app
+    // launch. Since user preferences aren't loaded yet, it isn't known if the
+    // user has opted out. Once the preferences are loaded, the crash
+    // controller is stopped for opted-out users, and any crash reports that
+    // are collected before then are not sent.
+    StartCrashController();
+
+    // Always ignore SIGPIPE.  Chrome should always check the return value of
+    // write() instead of relying on this signal.
+    CHECK_NE(SIG_ERR, signal(SIGPIPE, SIG_IGN));
+  }
+
+  @autoreleasepool {
+    return UIApplicationMain(argc, argv, nil,
+                             NSStringFromClass([AppDelegate class]));
+  }
+}
diff --git a/ios/chrome/app/main_application_delegate.h b/ios/chrome/app/main_application_delegate.h
new file mode 100644
index 0000000..a02bdd8
--- /dev/null
+++ b/ios/chrome/app/main_application_delegate.h
@@ -0,0 +1,14 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_MAIN_APPLICATION_DELEGATE_H_
+#define IOS_CHROME_APP_MAIN_APPLICATION_DELEGATE_H_
+
+#import <UIKit/UIKit.h>
+
+// The main delegate of the application.
+@interface MainApplicationDelegate : NSObject<UIApplicationDelegate>
+@end
+
+#endif  // IOS_CHROME_APP_MAIN_APPLICATION_DELEGATE_H_
diff --git a/ios/chrome/app/main_application_delegate.mm b/ios/chrome/app/main_application_delegate.mm
new file mode 100644
index 0000000..9d3c911
--- /dev/null
+++ b/ios/chrome/app/main_application_delegate.mm
@@ -0,0 +1,274 @@
+// Copyright 2016 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_application_delegate.h"
+
+#include "base/mac/foundation_util.h"
+#import "base/mac/scoped_nsobject.h"
+#import "ios/chrome/app/application_delegate/app_navigation.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/browser_launcher.h"
+#import "ios/chrome/app/application_delegate/memory_warning_helper.h"
+#import "ios/chrome/app/application_delegate/metrics_mediator.h"
+#import "ios/chrome/app/application_delegate/startup_information.h"
+#import "ios/chrome/app/application_delegate/tab_opening.h"
+#import "ios/chrome/app/application_delegate/tab_switching.h"
+#import "ios/chrome/app/application_delegate/url_opener.h"
+#import "ios/chrome/app/application_delegate/user_activity_handler.h"
+#import "ios/chrome/app/chrome_overlay_window.h"
+#import "ios/chrome/app/main_application_delegate_testing.h"
+#import "ios/chrome/app/main_controller.h"
+#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
+#include "ios/public/provider/chrome/browser/signin/chrome_identity_service.h"
+
+@interface MainApplicationDelegate () {
+  base::scoped_nsobject<MainController> _mainController;
+  // Memory helper used to log the number of memory warnings received.
+  base::scoped_nsobject<MemoryWarningHelper> _memoryHelper;
+  // Metrics mediator used to check and update the metrics accordingly to
+  // to the user preferences.
+  base::scoped_nsobject<MetricsMediator> _metricsMediator;
+  // Browser launcher to have a global launcher.
+  base::scoped_nsprotocol<id<BrowserLauncher>> _browserLauncher;
+  // Container for startup information.
+  base::scoped_nsprotocol<id<StartupInformation>> _startupInformation;
+  // Helper to open new tabs.
+  base::scoped_nsprotocol<id<TabOpening>> _tabOpener;
+  // Handles the application stage changes.
+  base::scoped_nsobject<AppState> _appState;
+  // Handles tab switcher.
+  base::scoped_nsprotocol<id<AppNavigation>> _appNavigation;
+  // Handles tab switcher.
+  base::scoped_nsprotocol<id<TabSwitching>> _tabSwitcherProtocol;
+}
+
+@end
+
+@implementation MainApplicationDelegate
+
+- (instancetype)init {
+  if (self = [super init]) {
+    _memoryHelper.reset([[MemoryWarningHelper alloc] init]);
+    _mainController.reset([[MainController alloc] init]);
+    _metricsMediator.reset([[MetricsMediator alloc] init]);
+    [_mainController setMetricsMediator:_metricsMediator];
+    _browserLauncher.reset([_mainController retain]);
+    _startupInformation.reset([_mainController retain]);
+    _tabOpener.reset([_mainController retain]);
+    _appState.reset([[AppState alloc]
+        initWithBrowserLauncher:_browserLauncher
+             startupInformation:_startupInformation
+            applicationDelegate:self]);
+    _tabSwitcherProtocol.reset([_mainController retain]);
+    _appNavigation.reset([_mainController retain]);
+    [_mainController setAppState:_appState];
+  }
+  return self;
+}
+
+- (UIWindow*)window {
+  return [_mainController window];
+}
+
+- (void)setWindow:(UIWindow*)newWindow {
+  DCHECK(newWindow);
+  [_mainController setWindow:newWindow];
+  // self.window has been set by this time. _appState window can now be set.
+  [_appState setWindow:newWindow];
+}
+
+#pragma mark - UIApplicationDelegate methods -
+
+#pragma mark Responding to App State Changes and System Events
+
+// Called by the OS to create the UI for display.  The UI will not be displayed,
+// even if it is ready, until this function returns.
+// The absolute minimum work should be done here, to ensure that the application
+// startup is fast, and the UI appears as soon as possible.
+- (BOOL)application:(UIApplication*)application
+    didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
+  // Main window must be ChromeOverlayWindow or a subclass of it.
+  self.window = [[[ChromeOverlayWindow alloc]
+      initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
+
+  BOOL inBackground =
+      [application applicationState] == UIApplicationStateBackground;
+  return [_appState requiresHandlingAfterLaunchWithOptions:launchOptions
+                                           stateBackground:inBackground];
+}
+
+- (void)applicationDidBecomeActive:(UIApplication*)application {
+  if ([_appState isInSafeMode])
+    return;
+
+  [_appState resumeSessionWithTabOpener:_tabOpener
+                            tabSwitcher:_tabSwitcherProtocol];
+}
+
+- (void)applicationWillResignActive:(UIApplication*)application {
+  if ([_appState isInSafeMode])
+    return;
+
+  [_appState willResignActiveTabModel];
+}
+
+// Called when going into the background. iOS already broadcasts, so
+// stakeholders can register for it directly.
+- (void)applicationDidEnterBackground:(UIApplication*)application {
+  [_appState
+      applicationDidEnterBackground:application
+                       memoryHelper:_memoryHelper
+                tabSwitcherIsActive:[_mainController isTabSwitcherActive]];
+}
+
+// Called when returning to the foreground.
+- (void)applicationWillEnterForeground:(UIApplication*)application {
+  [_appState applicationWillEnterForeground:application
+                            metricsMediator:_metricsMediator
+                               memoryHelper:_memoryHelper
+                                  tabOpener:_tabOpener
+                              appNavigation:_appNavigation];
+}
+
+- (void)applicationWillTerminate:(UIApplication*)application {
+  if ([_appState isInSafeMode])
+    return;
+
+  // Instead of adding code here, consider if it could be handled by listening
+  // for  UIApplicationWillterminate.
+  [_appState applicationWillTerminate:application
+                applicationNavigation:_appNavigation];
+}
+
+- (void)applicationDidReceiveMemoryWarning:(UIApplication*)application {
+  if ([_appState isInSafeMode])
+    return;
+
+  [_memoryHelper handleMemoryPressure];
+}
+
+#pragma mark Downloading Data in the Background
+
+- (void)application:(UIApplication*)application
+    performFetchWithCompletionHandler:
+        (void (^)(UIBackgroundFetchResult))completionHandler {
+  if ([_appState isInSafeMode])
+    return;
+
+  if ([application applicationState] != UIApplicationStateBackground) {
+    // If this handler is called in foreground, it means it has to be activated.
+    // Returning |UIBackgroundFetchResultNewData| means that the handler will be
+    // called again in case of a crash.
+    completionHandler(UIBackgroundFetchResultNewData);
+    return;
+  }
+
+  [BackgroundActivity application:application
+      performFetchWithCompletionHandler:completionHandler
+                        metricsMediator:_metricsMediator
+                        browserLauncher:_browserLauncher];
+}
+
+- (void)application:(UIApplication*)application
+    handleEventsForBackgroundURLSession:(NSString*)identifier
+                      completionHandler:(void (^)(void))completionHandler {
+  if ([_appState isInSafeMode])
+    return;
+
+  [BackgroundActivity handleEventsForBackgroundURLSession:identifier
+                                        completionHandler:completionHandler
+                                          browserLauncher:_browserLauncher];
+}
+
+#pragma mark Continuing User Activity and Handling Quick Actions
+
+- (BOOL)application:(UIApplication*)application
+    willContinueUserActivityWithType:(NSString*)userActivityType {
+  if ([_appState isInSafeMode])
+    return NO;
+
+  return
+      [UserActivityHandler willContinueUserActivityWithType:userActivityType];
+}
+
+- (BOOL)application:(UIApplication*)application
+    continueUserActivity:(NSUserActivity*)userActivity
+      restorationHandler:(void (^)(NSArray*))restorationHandler {
+  if ([_appState isInSafeMode])
+    return NO;
+
+  BOOL applicationIsActive =
+      [application applicationState] == UIApplicationStateActive;
+
+  return [UserActivityHandler continueUserActivity:userActivity
+                               applicationIsActive:applicationIsActive
+                                         tabOpener:_tabOpener
+                                startupInformation:_startupInformation];
+}
+
+- (void)application:(UIApplication*)application
+    performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
+               completionHandler:(void (^)(BOOL succeeded))completionHandler {
+  if ([_appState isInSafeMode])
+    return;
+
+  [UserActivityHandler
+      performActionForShortcutItem:shortcutItem
+                 completionHandler:completionHandler
+                         tabOpener:_tabOpener
+                startupInformation:_startupInformation
+            browserViewInformation:[_mainController browserViewInformation]];
+}
+
+#pragma mark Opening a URL-Specified Resource
+
+// Handles open URL. The registered URL Schemes are defined in project
+// variables ${CHROMIUM_URL_SCHEME_x}.
+// The url can either be empty, in which case the app is simply opened or
+// can contain an URL that will be opened in a new tab.
+- (BOOL)application:(UIApplication*)application
+            openURL:(NSURL*)url
+            options:(NSDictionary<NSString*, id>*)options {
+  if ([_appState isInSafeMode])
+    return NO;
+
+  if (ios::GetChromeBrowserProvider()
+          ->GetChromeIdentityService()
+          ->HandleApplicationOpenURL(application, url, options)) {
+    return YES;
+  }
+
+  BOOL applicationActive =
+      [application applicationState] == UIApplicationStateActive;
+  return [URLOpener openURL:url
+          applicationActive:applicationActive
+                    options:options
+                  tabOpener:_tabOpener
+         startupInformation:_startupInformation];
+}
+
+#pragma mark - chromeExecuteCommand
+
+- (void)chromeExecuteCommand:(id)sender {
+  [_mainController chromeExecuteCommand:sender];
+}
+
+#pragma mark - Testing methods
+
+- (MainController*)mainController {
+  return _mainController;
+}
+
+- (AppState*)appState {
+  return _appState;
+}
+
++ (MainController*)sharedMainController {
+  return base::mac::ObjCCast<MainApplicationDelegate>(
+             [[UIApplication sharedApplication] delegate])
+      .mainController;
+}
+
+@end
diff --git a/ios/chrome/app/main_application_delegate_testing.h b/ios/chrome/app/main_application_delegate_testing.h
new file mode 100644
index 0000000..0da219e
--- /dev/null
+++ b/ios/chrome/app/main_application_delegate_testing.h
@@ -0,0 +1,20 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_MAIN_APPLICATION_DELEGATE_TESTING_H_
+#define IOS_CHROME_APP_MAIN_APPLICATION_DELEGATE_TESTING_H_
+
+#import "ios/chrome/app/main_application_delegate.h"
+
+@class MainController;
+@class AppState;
+
+@interface MainApplicationDelegate ()
+@property(nonatomic, readonly) MainController* mainController;
+@property(nonatomic, readonly) AppState* appState;
+
++ (MainController*)sharedMainController;
+
+@end
+#endif  // IOS_CHROME_APP_MAIN_APPLICATION_DELEGATE_TESTING_H_
diff --git a/ios/chrome/app/main_application_delegate_unittest.mm b/ios/chrome/app/main_application_delegate_unittest.mm
new file mode 100644
index 0000000..ba947d29
--- /dev/null
+++ b/ios/chrome/app/main_application_delegate_unittest.mm
@@ -0,0 +1,46 @@
+// 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.
+
+#include "ios/chrome/app/main_application_delegate.h"
+
+#import <Foundation/Foundation.h>
+
+#import "base/mac/foundation_util.h"
+#include "base/mac/scoped_nsobject.h"
+#import "ios/chrome/app/chrome_overlay_window_testing.h"
+#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
+#include "testing/platform_test.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
+
+// Tests that the application does not crash if |applicationDidEnterBackground|
+// is called when the application is launched in background.
+// http://crbug.com/437307
+TEST(MainApplicationDelegateTest, CrashIfNotInitialized) {
+  // Save both ChromeBrowserProvider as MainController register new instance.
+  ios::ChromeBrowserProvider* stashed_chrome_browser_provider =
+      ios::GetChromeBrowserProvider();
+
+  id application = [OCMockObject niceMockForClass:[UIApplication class]];
+  UIApplicationState backgroundState = UIApplicationStateBackground;
+  [[[application stub] andReturnValue:OCMOCK_VALUE(backgroundState)]
+      applicationState];
+
+  MainApplicationDelegate* delegate =
+      [[[MainApplicationDelegate alloc] init] autorelease];
+  [delegate application:application didFinishLaunchingWithOptions:nil];
+  [delegate applicationDidEnterBackground:application];
+
+  // Clean up the size class recorder, which is created by the main window via
+  // a previous call to |application:didFinishLaunchingWithOptions:|, to prevent
+  // it from interfering with subsequent tests.
+  ChromeOverlayWindow* mainWindow =
+      base::mac::ObjCCastStrict<ChromeOverlayWindow>([delegate window]);
+  [mainWindow unsetSizeClassRecorder];
+
+  // Restore both ChromeBrowserProvider to its original value and destroy
+  // instances created by MainController.
+  DCHECK_NE(ios::GetChromeBrowserProvider(), stashed_chrome_browser_provider);
+  delete ios::GetChromeBrowserProvider();
+  ios::SetChromeBrowserProvider(stashed_chrome_browser_provider);
+}
diff --git a/ios/chrome/app/main_controller.h b/ios/chrome/app/main_controller.h
new file mode 100644
index 0000000..9181a6c
--- /dev/null
+++ b/ios/chrome/app/main_controller.h
@@ -0,0 +1,57 @@
+// 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.
+
+#ifndef IOS_CHROME_APP_MAIN_CONTROLLER_H_
+#define IOS_CHROME_APP_MAIN_CONTROLLER_H_
+
+#import <UIKit/UIKit.h>
+
+#import "ios/chrome/app/application_delegate/app_navigation.h"
+#import "ios/chrome/app/application_delegate/browser_launcher.h"
+#import "ios/chrome/app/application_delegate/startup_information.h"
+#import "ios/chrome/app/application_delegate/tab_opening.h"
+#import "ios/chrome/app/application_delegate/tab_switching.h"
+#import "ios/chrome/browser/ui/main/browser_view_information.h"
+
+@class AppState;
+@class MetricsMediator;
+
+// The main controller of the application, owned by the MainWindow nib. Also
+// serves as the delegate for the app. Owns all the various top-level
+// UI controllers.
+//
+// By design, it has no public API of its own. Anything interacting with
+// MainController should be doing so through a specific protocol.
+@interface MainController : NSObject<AppNavigation,
+                                     BrowserLauncher,
+                                     StartupInformation,
+                                     TabOpening,
+                                     TabSwitching>
+
+// A BrowserViewInformation object to perform BrowserViewController operations.
+@property(nonatomic, readonly) id<BrowserViewInformation>
+    browserViewInformation;
+
+// The application window.
+@property(nonatomic, retain) UIWindow* window;
+
+// Contains information about the application state, for example whether the
+// safe mode is activated.
+@property(nonatomic, assign) AppState* appState;
+
+// This metrics mediator is used to check and update the metrics accordingly to
+// to the user preferences.
+@property(nonatomic, assign) MetricsMediator* metricsMediator;
+
+// UIResponder addition to execute a Chrome command.  Overridden in UIWindow to
+// forward the call to the application delegate. The application delegate
+// forwards the call to this class.
+- (void)chromeExecuteCommand:(id)sender;
+
+// Returns whether the tab switcher is active.
+- (BOOL)isTabSwitcherActive;
+
+@end
+
+#endif  // IOS_CHROME_APP_MAIN_CONTROLLER_H_
diff --git a/ios/chrome/app/main_controller.mm b/ios/chrome/app/main_controller.mm
new file mode 100644
index 0000000..fa6b767
--- /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
diff --git a/ios/chrome/app/main_controller_private.h b/ios/chrome/app/main_controller_private.h
new file mode 100644
index 0000000..a97856c
--- /dev/null
+++ b/ios/chrome/app/main_controller_private.h
@@ -0,0 +1,80 @@
+// Copyright 2015 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.
+
+#ifndef IOS_CHROME_APP_MAIN_CONTROLLER_PRIVATE_H_
+#define IOS_CHROME_APP_MAIN_CONTROLLER_PRIVATE_H_
+
+#import "base/ios/block_types.h"
+#include "components/browsing_data/core/browsing_data_utils.h"
+#import "ios/chrome/app/application_delegate/browser_launcher.h"
+#import "ios/chrome/app/main_controller.h"
+
+@class BrowserViewController;
+@class DeviceSharingManager;
+class GURL;
+@class SettingsNavigationController;
+@class SigninInteractionController;
+@class TabModel;
+@protocol TabSwitcher;
+
+namespace ios {
+class ChromeBrowserState;
+}
+
+// Private methods and protocols that are made visible here for tests.
+@interface MainController ()
+
+// YES if the last time the app was launched was with a previous version.
+@property(nonatomic, readonly) BOOL isFirstLaunchAfterUpgrade;
+
+// Presents a promo's navigation controller.
+- (void)showPromo:(UIViewController*)promo;
+
+// Removes browsing data from |browserState| for datatypes in |mask|.
+// |browserState| cannot be null and must not be off the record.
+// |completionHandler| is called when this operation finishes.
+- (void)removeBrowsingDataFromBrowserState:
+            (ios::ChromeBrowserState*)browserState
+                                      mask:(int)mask
+                                timePeriod:(browsing_data::TimePeriod)timePeriod
+                         completionHandler:(ProceduralBlock)completionHandler;
+
+// Dismisses all modal dialogs then call |completion|.
+- (void)dismissModalDialogsWithCompletion:(ProceduralBlock)completion;
+
+@end
+
+// Methods that only exist for tests.
+@interface MainController (TestingOnly)
+
+@property(nonatomic, readonly) DeviceSharingManager* deviceSharingManager;
+@property(nonatomic, retain)
+    UIViewController<TabSwitcher>* tabSwitcherController;
+@property(nonatomic, readonly)
+    SettingsNavigationController* settingsNavigationController;
+@property(nonatomic, readonly)
+    SigninInteractionController* signinInteractionController;
+
+// The top presented view controller that is not currently being dismissed.
+@property(nonatomic, readonly) UIViewController* topPresentedViewController;
+
+// Tab switcher state.
+@property(nonatomic, getter=isTabSwitcherActive) BOOL tabSwitcherActive;
+@property(nonatomic, readonly) BOOL dismissingTabSwitcher;
+
+// Sets up MainController for testing; clears history, closes all tabs and
+// switches to the main BVC. |completionHandler| is called when MainController
+// is completely set up for testing.
+- (void)setUpForTestingWithCompletionHandler:(ProceduralBlock)completionHandler;
+
+// Sets the internal startup state to indicate that the launch was triggered
+// by an external app opening the given URL.
+- (void)setStartupParametersWithURL:(const GURL&)launchURL;
+
+// Sets the internal state to indicate that the app has been foregrounded.
+- (void)setUpAsForegrounded;
+
+@end
+
+#endif  // IOS_CHROME_APP_MAIN_CONTROLLER_PRIVATE_H_
diff --git a/ios/chrome/app/main_controller_unittest.mm b/ios/chrome/app/main_controller_unittest.mm
new file mode 100644
index 0000000..87dec94
--- /dev/null
+++ b/ios/chrome/app/main_controller_unittest.mm
@@ -0,0 +1,143 @@
+// 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 <Foundation/Foundation.h>
+
+#import "base/mac/bind_objc_block.h"
+#include "base/threading/thread.h"
+#import "ios/chrome/app/application_delegate/app_state.h"
+#import "ios/chrome/app/application_delegate/url_opener.h"
+#include "ios/chrome/app/main_controller_private.h"
+#import "ios/chrome/browser/tabs/tab_model.h"
+#import "ios/chrome/test/base/scoped_block_swizzler.h"
+#include "testing/platform_test.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
+
+#pragma mark - MainController Testing Additions
+
+@interface MainController (TestingAdditions)
+- (id)initForTesting;
+@end
+
+@implementation MainController (TestingAdditions)
+- (id)initForTesting {
+  self = [self init];
+  if (self) {
+    [self setUpAsForegrounded];
+    id mainTabModel = [OCMockObject mockForClass:[TabModel class]];
+    [[mainTabModel stub] resetSessionMetrics];
+    [[mainTabModel stub] browserStateDestroyed];
+    [[mainTabModel stub] addObserver:[OCMArg any]];
+    [[mainTabModel stub] removeObserver:[OCMArg any]];
+    [[self browserViewInformation] setMainTabModel:mainTabModel];
+  }
+  return self;
+}
+
+@end
+
+#pragma mark - MainController Test
+
+namespace {
+
+// A block that takes the arguments of
+// +handleLaunchOptions:applicationActive:tabOpener:startupInformation: and
+// returns nothing.
+typedef void (^HandleLaunchOptions)(id self,
+                                    NSDictionary* options,
+                                    BOOL applicationActive,
+                                    id<TabOpening> tabOpener,
+                                    id<StartupInformation> startupInformation,
+                                    AppState* appState);
+
+class TabOpenerTest : public PlatformTest {
+ protected:
+  BOOL swizzleHasBeenCalled() { return swizzle_block_executed_; }
+
+  void swizzleHandleLaunchOptions(
+      NSDictionary* expectedLaunchOptions,
+      id<StartupInformation> expectedStartupInformation,
+      AppState* expectedAppState) {
+    swizzle_block_executed_ = NO;
+    swizzle_block_.reset(
+        [^(id self, NSDictionary* options, BOOL applicationActive,
+           id<TabOpening> tabOpener, id<StartupInformation> startupInformation,
+           AppState* appState) {
+          swizzle_block_executed_ = YES;
+          EXPECT_EQ(expectedLaunchOptions, options);
+          EXPECT_EQ(expectedStartupInformation, startupInformation);
+          EXPECT_EQ(main_controller_.get(), tabOpener);
+          EXPECT_EQ(expectedAppState, appState);
+        } copy]);
+    URL_opening_handle_launch_swizzler_.reset(new ScopedBlockSwizzler(
+        [URLOpener class], @selector(handleLaunchOptions:
+                                       applicationActive:
+                                               tabOpener:
+                                      startupInformation:
+                                                appState:),
+        swizzle_block_));
+  }
+
+  MainController* GetMainController() {
+    if (!main_controller_.get()) {
+      main_controller_.reset([[MainController alloc] initForTesting]);
+    }
+    return main_controller_.get();
+  }
+
+ private:
+  base::scoped_nsobject<MainController> main_controller_;
+  __block BOOL swizzle_block_executed_;
+  base::mac::ScopedBlock<HandleLaunchOptions> swizzle_block_;
+  std::unique_ptr<ScopedBlockSwizzler> URL_opening_handle_launch_swizzler_;
+};
+
+#pragma mark - Tests.
+
+// Tests that -newTabFromLaunchOptions calls +handleLaunchOption and reset
+// options.
+TEST_F(TabOpenerTest, openTabFromLaunchOptionsWithOptions) {
+  // Setup.
+  NSString* sourceApplication = @"com.apple.mobilesafari";
+  NSDictionary* launchOptions =
+      @{UIApplicationLaunchOptionsSourceApplicationKey : sourceApplication};
+
+  id startupInformationMock =
+      [OCMockObject mockForProtocol:@protocol(StartupInformation)];
+  id appStateMock = [OCMockObject mockForClass:[AppState class]];
+
+  swizzleHandleLaunchOptions(launchOptions, startupInformationMock,
+                             appStateMock);
+
+  id<TabOpening> tabOpener = GetMainController();
+
+  // Action.
+  [tabOpener openTabFromLaunchOptions:launchOptions
+                   startupInformation:startupInformationMock
+                             appState:appStateMock];
+
+  // Test.
+  EXPECT_TRUE(swizzleHasBeenCalled());
+}
+
+// Tests that -newTabFromLaunchOptions do nothing if launchOptions is nil.
+TEST_F(TabOpenerTest, openTabFromLaunchOptionsWithNil) {
+  // Setup.
+  id startupInformationMock =
+      [OCMockObject mockForProtocol:@protocol(StartupInformation)];
+  id appStateMock = [OCMockObject mockForClass:[AppState class]];
+
+  swizzleHandleLaunchOptions(nil, startupInformationMock, appStateMock);
+
+  id<TabOpening> tabOpener = GetMainController();
+
+  // Action.
+  [tabOpener openTabFromLaunchOptions:nil
+                   startupInformation:startupInformationMock
+                             appState:appStateMock];
+
+  // Test.
+  EXPECT_FALSE(swizzleHasBeenCalled());
+}
+}  // namespace
diff --git a/ios/chrome/app/memory_monitor.h b/ios/chrome/app/memory_monitor.h
new file mode 100644
index 0000000..eb2642f
--- /dev/null
+++ b/ios/chrome/app/memory_monitor.h
@@ -0,0 +1,19 @@
+// Copyright 2014 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.
+
+#ifndef IOS_CHROME_APP_MEMORY_MONITOR_H_
+#define IOS_CHROME_APP_MEMORY_MONITOR_H_
+
+namespace ios_internal {
+
+// Timer to launch [UpdateBreakpadMemoryValues] every 5 seconds.
+void AsynchronousFreeMemoryMonitor();
+
+// Checks the values of free RAM and free disk space and updates breakpad with
+// these values.
+void UpdateBreakpadMemoryValues();
+
+}  // namespace ios_internal
+
+#endif  // IOS_CHROME_APP_MEMORY_MONITOR_H_
diff --git a/ios/chrome/app/memory_monitor.mm b/ios/chrome/app/memory_monitor.mm
new file mode 100644
index 0000000..162b156
--- /dev/null
+++ b/ios/chrome/app/memory_monitor.mm
@@ -0,0 +1,42 @@
+// Copyright 2014 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/memory_monitor.h"
+
+#include <dispatch/dispatch.h>
+#import <Foundation/NSPathUtilities.h>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/location.h"
+#include "base/logging.h"
+#import "base/mac/foundation_util.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/sys_info.h"
+#import "ios/chrome/browser/crash_report/breakpad_helper.h"
+#include "ios/web/public/web_thread.h"
+
+namespace ios_internal {
+
+void AsynchronousFreeMemoryMonitor() {
+  UpdateBreakpadMemoryValues();
+  web::WebThread::PostDelayedTask(
+      web::WebThread::FILE, FROM_HERE,
+      base::Bind(&ios_internal::AsynchronousFreeMemoryMonitor),
+      base::TimeDelta::FromSeconds(30));
+}
+
+void UpdateBreakpadMemoryValues() {
+  int freeMemory =
+      static_cast<int>(base::SysInfo::AmountOfAvailablePhysicalMemory() / 1024);
+  breakpad_helper::SetCurrentFreeMemoryInKB(freeMemory);
+  NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
+                                                       NSUserDomainMask, YES);
+  NSString* value = base::mac::ObjCCastStrict<NSString>([paths lastObject]);
+  base::FilePath filePath = base::FilePath(base::SysNSStringToUTF8(value));
+  int freeDiskSpace =
+      static_cast<int>(base::SysInfo::AmountOfFreeDiskSpace(filePath) / 1024);
+  breakpad_helper::SetCurrentFreeDiskInKB(freeDiskSpace);
+}
+}
diff --git a/ios/chrome/app/multitasking_test_application_delegate.h b/ios/chrome/app/multitasking_test_application_delegate.h
new file mode 100644
index 0000000..09a235dc
--- /dev/null
+++ b/ios/chrome/app/multitasking_test_application_delegate.h
@@ -0,0 +1,19 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_MULTITASKING_TEST_APPLICATION_DELEGATE_H_
+#define IOS_CHROME_APP_MULTITASKING_TEST_APPLICATION_DELEGATE_H_
+
+#import <UIKit/UIKit.h>
+
+#import "ios/chrome/app/main_application_delegate_testing.h"
+
+// Subclass that allows setting up environments for multitasking tests.
+@interface MultitaskingTestApplicationDelegate : MainApplicationDelegate
+
+- (BOOL)application:(UIApplication*)application
+    didFinishLaunchingWithOptions:(NSDictionary*)launchOptions;
+@end
+
+#endif  // IOS_CHROME_APP_MULTITASKING_TEST_APPLICATION_DELEGATE_H_
diff --git a/ios/chrome/app/multitasking_test_application_delegate.mm b/ios/chrome/app/multitasking_test_application_delegate.mm
new file mode 100644
index 0000000..d9030a03
--- /dev/null
+++ b/ios/chrome/app/multitasking_test_application_delegate.mm
@@ -0,0 +1,94 @@
+// Copyright 2016 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.
+
+#include "base/logging.h"
+#import "ios/chrome/app/application_delegate/app_state.h"
+#import "ios/chrome/app/chrome_overlay_window.h"
+#import "ios/chrome/app/multitasking_test_application_delegate.h"
+
+namespace {
+
+// These command line switches enable slide over or split view test mode for
+// multitasking tests. Only one of them should be enabled at any time.
+NSString* const kEnableSlideOverTestMode = @"--enable-slide-over-test-mode";
+NSString* const kEnableSplitViewTestMode = @"--enable-split-view-test-mode";
+
+// Screen size of various iPad models in terms of logical points. All models
+// have regular size except for 12.9 inch iPad Pro, which is slightly larger.
+const CGSize kRegularIPadPortraitSize = CGSizeMake(768.0, 1024.0);
+const CGSize kLargeIPadPortraitSize = CGSizeMake(1024.0, 1366.0);
+
+// Width of the application window size while in portrait slide over mode or
+// landscape half-screen split view mode. These values are obtained by running
+// application in actual portrait slide over mode and landscape half-screen
+// split view mode.
+const CGFloat kWidthPortraitSlideOverOnRegularIPad = 320.0;
+const CGFloat kWidthPortraitSlideOverOnLargeIPad = 375.0;
+const CGFloat kWidthLandscapeSplitViewOnRegularIPad = 507.0;
+const CGFloat kWidthLandscapeSplitViewOnLargeIPad = 678.0;
+
+}  // namespace
+
+@implementation MultitaskingTestApplicationDelegate
+
+- (BOOL)application:(UIApplication*)application
+    didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
+  // Configure application window size for multitasking tests.
+  CGSize newWindowSize = [self windowSize];
+  self.window = [[[ChromeOverlayWindow alloc]
+      initWithFrame:CGRectMake(0, 0, newWindowSize.width, newWindowSize.height)]
+      autorelease];
+
+  BOOL inBackground =
+      [application applicationState] == UIApplicationStateBackground;
+  return [[self appState] requiresHandlingAfterLaunchWithOptions:launchOptions
+                                                 stateBackground:inBackground];
+}
+
+// Returns true if test is running on 12.9 inch iPad Pro. Otherwise, it's
+// running on regular iPad.
+- (BOOL)isRunningOnLargeIPadPro {
+  CGSize size = [[UIScreen mainScreen] bounds].size;
+  return MAX(size.height, size.width) ==
+         MAX(kLargeIPadPortraitSize.width, kLargeIPadPortraitSize.height);
+}
+
+- (BOOL)IsRunningInSlideOverTestMode {
+  return [[[NSProcessInfo processInfo] arguments]
+      containsObject:kEnableSlideOverTestMode];
+}
+
+- (BOOL)IsRunningInSplitViewTestMode {
+  return [[[NSProcessInfo processInfo] arguments]
+      containsObject:kEnableSplitViewTestMode];
+}
+
+// Returns the size that will be used to configure the application window for
+// multitasking tests. Both width and height are determined by the target name
+// and on which iPad model it is running.
+- (CGSize)windowSize {
+  CGSize size;
+  if ([self IsRunningInSlideOverTestMode]) {
+    if ([self isRunningOnLargeIPadPro]) {
+      size.width = kWidthPortraitSlideOverOnLargeIPad;
+      size.height = kLargeIPadPortraitSize.height;
+    } else {
+      size.width = kWidthPortraitSlideOverOnRegularIPad;
+      size.height = kRegularIPadPortraitSize.height;
+    }
+  } else if ([self IsRunningInSplitViewTestMode]) {
+    if ([self isRunningOnLargeIPadPro]) {
+      size.width = kWidthLandscapeSplitViewOnLargeIPad;
+      size.height = kLargeIPadPortraitSize.width;
+    } else {
+      size.width = kWidthLandscapeSplitViewOnRegularIPad;
+      size.height = kRegularIPadPortraitSize.width;
+    }
+  } else {
+    LOG(ERROR) << "Unsupported multitasking test mode.";
+  }
+  return size;
+}
+
+@end
diff --git a/ios/chrome/app/safe_mode/safe_mode_coordinator.h b/ios/chrome/app/safe_mode/safe_mode_coordinator.h
new file mode 100644
index 0000000..bf57173
--- /dev/null
+++ b/ios/chrome/app/safe_mode/safe_mode_coordinator.h
@@ -0,0 +1,31 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_SAFE_MODE_SAFE_MODE_COORDINATOR_H_
+#define IOS_CHROME_APP_SAFE_MODE_SAFE_MODE_COORDINATOR_H_
+
+#import "ios/chrome/browser/root_coordinator.h"
+
+#import <UIKit/UIKit.h>
+
+@class SafeModeCoordinator;
+
+@protocol SafeModeCoordinatorDelegate<NSObject>
+- (void)coordinatorDidExitSafeMode:(nonnull SafeModeCoordinator*)coordinator;
+@end
+
+// Coordinator to manage the Safe Mode UI. This should be self-contained.
+// While this is a ChromeCoordinator, it doesn't support (and will DCHECK) using
+// child coordinators.
+@interface SafeModeCoordinator : RootCoordinator
+
+// Delegate for this coordinator.
+@property(nonatomic, nullable, assign) id<SafeModeCoordinatorDelegate> delegate;
+
+// If YES, there's a reason to show this coordinator.
++ (BOOL)shouldStart;
+
+@end
+
+#endif  // IOS_CHROME_APP_SAFE_MODE_SAFE_MODE_COORDINATOR_H_
diff --git a/ios/chrome/app/safe_mode/safe_mode_coordinator.mm b/ios/chrome/app/safe_mode/safe_mode_coordinator.mm
new file mode 100644
index 0000000..a919785
--- /dev/null
+++ b/ios/chrome/app/safe_mode/safe_mode_coordinator.mm
@@ -0,0 +1,79 @@
+// Copyright 2016 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/safe_mode/safe_mode_coordinator.h"
+
+#include "base/mac/scoped_nsobject.h"
+#import "ios/chrome/app/safe_mode/safe_mode_view_controller.h"
+#include "ios/chrome/browser/crash_loop_detection_util.h"
+
+namespace {
+const int kStartupCrashLoopThreshold = 2;
+}
+
+@interface SafeModeCoordinator ()<SafeModeViewControllerDelegate> {
+  // Weak pointer to window passed on init.
+  base::WeakNSObject<UIWindow> _window;
+  // Weak pointer backing property of the same name.
+  base::WeakNSProtocol<id<SafeModeCoordinatorDelegate>> _delegate;
+}
+
+@end
+
+@implementation SafeModeCoordinator
+
+#pragma mark - property implementation.
+
+- (id<SafeModeCoordinatorDelegate>)delegate {
+  return _delegate;
+}
+
+- (void)setDelegate:(id<SafeModeCoordinatorDelegate>)delegate {
+  _delegate.reset(delegate);
+}
+
+#pragma mark - Public class methods
+
++ (BOOL)shouldStart {
+  // Check whether there appears to be a startup crash loop. If not, don't look
+  // at anything else.
+  if (crash_util::GetFailedStartupAttemptCount() < kStartupCrashLoopThreshold)
+    return NO;
+
+  return [SafeModeViewController hasSuggestions];
+}
+
+#pragma mark - ChromeCoordinator implementation
+
+- (void)start {
+  // Create the SafeModeViewController and make it the root view controller for
+  // the window. The window has ownership of it and will dispose of it when
+  // another view controller is made root.
+  //
+  // General note: Safe mode should be safe; it should not depend on other
+  // objects being created. Be extremely conservative when adding code to this
+  // method.
+  base::scoped_nsobject<SafeModeViewController> viewController(
+      [[SafeModeViewController alloc] initWithDelegate:self]);
+  [self.window setRootViewController:viewController];
+
+  // Reset the crash count; the user may change something based on the recovery
+  // UI that will fix the crash, and having the next launch start in recovery
+  // mode would be strange.
+  crash_util::ResetFailedStartupAttemptCount();
+}
+
+// Override of ChildCoordinators method, which is not supported in this class.
+- (MutableCoordinatorArray*)childCoordinators {
+  NOTREACHED() << "Do not add child coordinators to SafeModeCoordinator.";
+  return nil;
+}
+
+#pragma mark - SafeModeViewControllerDelegate implementation
+
+- (void)startBrowserFromSafeMode {
+  [self.delegate coordinatorDidExitSafeMode:self];
+}
+
+@end
diff --git a/ios/chrome/app/safe_mode/safe_mode_coordinator_unittest.mm b/ios/chrome/app/safe_mode/safe_mode_coordinator_unittest.mm
new file mode 100644
index 0000000..246c977
--- /dev/null
+++ b/ios/chrome/app/safe_mode/safe_mode_coordinator_unittest.mm
@@ -0,0 +1,22 @@
+// Copyright 2016 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/safe_mode/safe_mode_coordinator.h"
+
+#include <UIKit/UIKit.h>
+
+#include "base/mac/scoped_nsobject.h"
+#include "testing/gtest_mac.h"
+
+TEST(SafeModeCoordinatorTest, RootVC) {
+  // Expect that starting a safe mode coordinator will populate the root view
+  // controller.
+  base::scoped_nsobject<UIWindow> window(
+      [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]);
+  EXPECT_TRUE([window rootViewController] == nil);
+  base::scoped_nsobject<SafeModeCoordinator> safe_mode_coordinator(
+      [[SafeModeCoordinator alloc] initWithWindow:window]);
+  [safe_mode_coordinator start];
+  EXPECT_FALSE([window rootViewController] == nil);
+}
diff --git a/ios/chrome/app/safe_mode/safe_mode_egtest.mm b/ios/chrome/app/safe_mode/safe_mode_egtest.mm
new file mode 100644
index 0000000..88925df
--- /dev/null
+++ b/ios/chrome/app/safe_mode/safe_mode_egtest.mm
@@ -0,0 +1,160 @@
+// Copyright 2016 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 <EarlGrey/EarlGrey.h>
+#import <XCTest/XCTest.h>
+
+#include "base/logging.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_nsobject.h"
+#import "ios/chrome/app/chrome_overlay_window.h"
+#import "ios/chrome/app/safe_mode/safe_mode_view_controller.h"
+#import "ios/chrome/browser/ui/main/main_view_controller.h"
+#include "ios/chrome/grit/ios_chromium_strings.h"
+#import "ios/chrome/test/base/scoped_block_swizzler.h"
+#import "ios/chrome/test/earl_grey/chrome_test_case.h"
+
+namespace {
+
+// Returns the top view controller for rendering the Safe Mode Controller.
+UIViewController* GetActiveViewController() {
+  UIWindow* mainWindow = [[UIApplication sharedApplication] keyWindow];
+  DCHECK([mainWindow isKindOfClass:[ChromeOverlayWindow class]]);
+  MainViewController* main_view_controller =
+      base::mac::ObjCCast<MainViewController>([mainWindow rootViewController]);
+  return main_view_controller.activeViewController;
+}
+
+// Verifies that |message| is displayed.
+void AssertMessageOnPage(NSString* message) {
+  id<GREYMatcher> messageMatcher = [GREYMatchers matcherForText:message];
+  [[EarlGrey selectElementWithMatcher:messageMatcher]
+      assertWithMatcher:grey_notNil()];
+}
+
+// Verifies that |message| is not displayed.
+void AssertMessageNotOnPage(NSString* message) {
+  id<GREYMatcher> messageMatcher = [GREYMatchers matcherForText:message];
+  [[EarlGrey selectElementWithMatcher:messageMatcher]
+      assertWithMatcher:grey_nil()];
+}
+
+// Verifies that the button to reload chrome is displayed.
+void AssertTryAgainButtonOnPage() {
+  NSString* tryAgain =
+      NSLocalizedString(@"IDS_IOS_SAFE_MODE_RELOAD_CHROME", @"");
+  // This is uppercased to match MDC button label convention.
+  NSString* tryAgainPrimaryAction =
+      [tryAgain uppercaseStringWithLocale:[NSLocale currentLocale]];
+  id<GREYMatcher> tryAgainMatcher =
+      [GREYMatchers matcherForButtonTitle:tryAgainPrimaryAction];
+  [[EarlGrey selectElementWithMatcher:tryAgainMatcher]
+      assertWithMatcher:grey_notNil()];
+}
+
+}  // namespace
+
+// Tests the display of Safe Mode Controller under different error states of
+// jailbroken-ness and whether a crash dump was saved.
+@interface SafeModeTestCase : ChromeTestCase
+@end
+
+@implementation SafeModeTestCase
+
+// Tests that Safe Mode crash upload screen is displayed when there are crash
+// reports to upload.
+- (void)testSafeModeSendingCrashReport {
+  // Mocks the +hasReportToUpload method by swizzling to return positively that
+  // there are crash reports to upload.
+  ScopedBlockSwizzler hasReport([SafeModeViewController class],
+                                @selector(hasReportToUpload), ^{
+                                  return YES;
+                                });
+
+  // Instantiates a Safe Mode controller and displays it.
+  base::scoped_nsobject<SafeModeViewController> safeModeController(
+      [[SafeModeViewController alloc] initWithDelegate:nil]);
+  [GetActiveViewController() presentViewController:safeModeController
+                                          animated:NO
+                                        completion:nil];
+  // Verifies screen content that shows that crash report is being uploaded.
+  AssertMessageOnPage(NSLocalizedString(@"IDS_IOS_SAFE_MODE_AW_SNAP", @""));
+  AssertMessageOnPage(
+      NSLocalizedString(@"IDS_IOS_SAFE_MODE_UNKNOWN_CAUSE", @""));
+  AssertTryAgainButtonOnPage();
+  AssertMessageOnPage(
+      NSLocalizedString(@"IDS_IOS_SAFE_MODE_SENDING_CRASH_REPORT", @""));
+}
+
+// Tests that Safe Mode screen is displayed with a message that there are
+// jailbroken mods that caused a crash. Crash reports are not sent.
+- (void)testSafeModeDetectedThirdPartyMods {
+  // Mocks the +detectedThirdPartyMods method by swizzling to return positively
+  // that device appears to be jailbroken and contains third party mods.
+  ScopedBlockSwizzler thirdParty([SafeModeViewController class],
+                                 @selector(detectedThirdPartyMods), ^{
+                                   return YES;
+                                 });
+  // Returns an empty list to simulate no known mods detected.
+  ScopedBlockSwizzler badModules([SafeModeViewController class],
+                                 @selector(startupCrashModules), ^{
+                                   return @[];
+                                 });
+
+  // Instantiates a Safe Mode controller and displays it.
+  base::scoped_nsobject<SafeModeViewController> safeModeController(
+      [[SafeModeViewController alloc] initWithDelegate:nil]);
+  [GetActiveViewController() presentViewController:safeModeController
+                                          animated:NO
+                                        completion:nil];
+  // Verifies screen content that does not show crash report being uploaded.
+  // When devices are jailbroken, the crash reports are not very useful.
+  AssertMessageOnPage(NSLocalizedString(@"IDS_IOS_SAFE_MODE_AW_SNAP", @""));
+  AssertMessageOnPage(
+      NSLocalizedString(@"IDS_IOS_SAFE_MODE_TWEAKS_FOUND", @""));
+  AssertTryAgainButtonOnPage();
+  AssertMessageNotOnPage(
+      NSLocalizedString(@"IDS_IOS_SAFE_MODE_SENDING_CRASH_REPORT", @""));
+}
+
+// Tests that Safe Mode screen is displayed with a message that there are
+// jailbroken mods listing the names of the known to be bad mods that caused a
+// crash. Crash reports are not sent.
+- (void)testSafeModeBothThirdPartyModsAndHasReport {
+  // Mocks the +detectedThirdPartyMods method by swizzling to return positively
+  // that device appears to be jailbroken and contains third party mods.
+  ScopedBlockSwizzler thirdParty([SafeModeViewController class],
+                                 @selector(detectedThirdPartyMods), ^{
+                                   return YES;
+                                 });
+  // Mocked list of bad jailbroken mods. These will be checked later.
+  NSArray* badModulesList = @[ @"iAmBad", @"MJackson" ];
+  ScopedBlockSwizzler badModules([SafeModeViewController class],
+                                 @selector(startupCrashModules), ^{
+                                   return badModulesList;
+                                 });
+  ScopedBlockSwizzler hasReport([SafeModeViewController class],
+                                @selector(hasReportToUpload), ^{
+                                  return YES;
+                                });
+  // Instantiates a Safe Mode controller and displays it.
+  base::scoped_nsobject<SafeModeViewController> safeModeController(
+      [[SafeModeViewController alloc] initWithDelegate:nil]);
+  [GetActiveViewController() presentViewController:safeModeController
+                                          animated:NO
+                                        completion:nil];
+  // Verifies screen content that does not show crash report being uploaded.
+  // When devices are jailbroken, the crash reports are not very useful.
+  AssertMessageOnPage(NSLocalizedString(@"IDS_IOS_SAFE_MODE_AW_SNAP", @""));
+  // Constructs the list of bad mods based on |badModulesList| above.
+  NSString* message =
+      [NSLocalizedString(@"IDS_IOS_SAFE_MODE_NAMED_TWEAKS_FOUND", @"")
+          stringByAppendingString:@"\n\n    iAmBad\n    MJackson"];
+  AssertMessageOnPage(message);
+  AssertTryAgainButtonOnPage();
+  AssertMessageNotOnPage(
+      NSLocalizedString(@"IDS_IOS_SAFE_MODE_SENDING_CRASH_REPORT", @""));
+}
+
+@end
diff --git a/ios/chrome/app/safe_mode/safe_mode_view_controller.h b/ios/chrome/app/safe_mode/safe_mode_view_controller.h
new file mode 100644
index 0000000..38078aa
--- /dev/null
+++ b/ios/chrome/app/safe_mode/safe_mode_view_controller.h
@@ -0,0 +1,40 @@
+// Copyright 2013 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.
+
+#ifndef IOS_CHROME_APP_SAFE_MODE_SAFE_MODE_VIEW_CONTROLLER_H_
+#define IOS_CHROME_APP_SAFE_MODE_SAFE_MODE_VIEW_CONTROLLER_H_
+
+#import <UIKit/UIKit.h>
+
+#import "base/ios/weak_nsobject.h"
+#include "base/mac/scoped_nsobject.h"
+
+@class PrimaryActionButton;
+
+// A protocol required by delegates of the SafeModeViewController.
+@protocol SafeModeViewControllerDelegate
+@required
+// Tell delegate to attempt to start the browser.
+- (void)startBrowserFromSafeMode;
+@end
+
+@interface SafeModeViewController : UIViewController {
+ @private
+  base::WeakNSProtocol<id<SafeModeViewControllerDelegate>> delegate_;
+  base::scoped_nsobject<UIView> innerView_;
+  base::scoped_nsobject<PrimaryActionButton> startButton_;
+  base::scoped_nsobject<UILabel> uploadDescription_;
+  base::scoped_nsobject<UIProgressView> uploadProgress_;
+  base::scoped_nsobject<NSDate> uploadStartTime_;
+  base::scoped_nsobject<NSTimer> uploadTimer_;
+}
+
+- (id)initWithDelegate:(id<SafeModeViewControllerDelegate>)delegate;
+
+// Returns |YES| when the safe mode UI has information to show.
++ (BOOL)hasSuggestions;
+
+@end
+
+#endif  // IOS_CHROME_APP_SAFE_MODE_SAFE_MODE_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/app/safe_mode/safe_mode_view_controller.mm b/ios/chrome/app/safe_mode/safe_mode_view_controller.mm
new file mode 100644
index 0000000..fbd921c
--- /dev/null
+++ b/ios/chrome/app/safe_mode/safe_mode_view_controller.mm
@@ -0,0 +1,305 @@
+// Copyright 2013 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/safe_mode/safe_mode_view_controller.h"
+
+#import <QuartzCore/QuartzCore.h>
+
+#include "base/strings/sys_string_conversions.h"
+#import "ios/chrome/app/safe_mode_crashing_modules_config.h"
+#import "ios/chrome/app/safe_mode_util.h"
+#include "ios/chrome/browser/crash_report/breakpad_helper.h"
+#import "ios/chrome/browser/ui/fancy_ui/primary_action_button.h"
+#include "ios/chrome/browser/ui/ui_util.h"
+#import "ios/chrome/browser/ui/uikit_ui_util.h"
+#include "ios/chrome/grit/ios_chromium_strings.h"
+#import "ui/gfx/ios/NSString+CrStringDrawing.h"
+
+namespace {
+const CGFloat kVerticalSpacing = 20;
+const CGFloat kUploadProgressSpacing = 5;
+const NSTimeInterval kUploadPumpInterval = 0.1;
+const NSTimeInterval kUploadTotalTime = 5;
+}  // anonymous namespace
+
+@interface SafeModeViewController ()
+// Returns |YES| if any third-party modifications are detected.
++ (BOOL)detectedThirdPartyMods;
+// Returns |YES| if there are crash reports to upload.
++ (BOOL)hasReportToUpload;
+// Returns a message explaining which, if any, 3rd party modules were detected
+// that may cause Chrome to crash.
+- (NSString*)startupCrashModuleText;
+// Starts timer to update progress bar for crash report upload.
+- (void)startUploadProgress;
+// Updates progress bar for crash report upload.
+- (void)pumpUploadProgress;
+// Called when user taps on "Resume Chrome" button. Restores the default
+// Breakpad configuration and notifies the delegate to attempt to start the
+// browser.
+- (void)startBrowserFromSafeMode;
+@end
+
+@implementation SafeModeViewController
+
+- (id)initWithDelegate:(id<SafeModeViewControllerDelegate>)delegate {
+  self = [super init];
+  if (self) {
+    delegate_.reset(delegate);
+  }
+  return self;
+}
+
++ (BOOL)hasSuggestions {
+  if ([SafeModeViewController detectedThirdPartyMods])
+    return YES;
+  return [SafeModeViewController hasReportToUpload];
+}
+
++ (BOOL)detectedThirdPartyMods {
+  std::vector<std::string> thirdPartyMods = safe_mode_util::GetLoadedImages(
+      "/Library/MobileSubstrate/DynamicLibraries/");
+  return (thirdPartyMods.size() > 0);
+}
+
++ (BOOL)hasReportToUpload {
+  // If uploading is enabled and more than one report has stacked up, then we
+  // assume that the app may be in a state that is preventing crash report
+  // uploads before crashing again.
+  return breakpad_helper::IsUploadingEnabled() &&
+         breakpad_helper::GetCrashReportCount() > 1;
+}
+
+// Return any jailbroken library that appears in SafeModeCrashingModulesConfig.
+- (NSArray*)startupCrashModules {
+  std::vector<std::string> modules = safe_mode_util::GetLoadedImages(
+      "/Library/MobileSubstrate/DynamicLibraries/");
+  NSMutableArray* array = [NSMutableArray arrayWithCapacity:modules.size()];
+  SafeModeCrashingModulesConfig* config =
+      [SafeModeCrashingModulesConfig sharedInstance];
+  for (size_t i = 0; i < modules.size(); i++) {
+    NSString* path = base::SysUTF8ToNSString(modules[i]);
+    NSString* friendlyName = [config startupCrashModuleFriendlyName:path];
+    if (friendlyName != nil)
+      [array addObject:friendlyName];
+  }
+  return array;
+}
+
+// Since we are still supporting iOS5, this is a helper for basic flow layout.
+- (void)centerView:(UIView*)view afterView:(UIView*)afterView {
+  CGPoint center = [view center];
+  center.x = [innerView_ frame].size.width / 2;
+  [view setCenter:center];
+
+  if (afterView) {
+    CGRect frame = view.frame;
+    frame.origin.y = CGRectGetMaxY(afterView.frame) + kVerticalSpacing;
+    view.frame = frame;
+  }
+}
+
+- (NSString*)startupCrashModuleText {
+  NSArray* knownModules = [self startupCrashModules];
+  if ([knownModules count]) {
+    NSString* wrongText =
+        NSLocalizedString(@"IDS_IOS_SAFE_MODE_NAMED_TWEAKS_FOUND", @"");
+    NSMutableString* text = [NSMutableString stringWithString:wrongText];
+    [text appendString:@"\n"];
+    for (NSString* module in knownModules) {
+      [text appendFormat:@"\n    %@", module];
+    }
+    return text;
+  } else if ([SafeModeViewController detectedThirdPartyMods]) {
+    return NSLocalizedString(@"IDS_IOS_SAFE_MODE_TWEAKS_FOUND", @"");
+  } else {
+    return NSLocalizedString(@"IDS_IOS_SAFE_MODE_UNKNOWN_CAUSE", @"");
+  }
+}
+
+- (void)viewDidLoad {
+  [super viewDidLoad];
+
+  // Width of the inner view on iPhone.
+  const CGFloat kIPhoneWidth = 250;
+  // Width of the inner view on iPad.
+  const CGFloat kIPadWidth = 350;
+  // Horizontal buffer.
+  const CGFloat kHorizontalSpacing = 20;
+
+  self.view.autoresizesSubviews = YES;
+  CGRect mainBounds = [[UIScreen mainScreen] bounds];
+  // SafeModeViewController only supports portrait orientation (see
+  // implementation of supportedInterfaceOrientations: below) but if the app is
+  // launched from landscape mode (e.g. iPad or iPhone 6+) then the mainScreen's
+  // bounds will still be landscape at this point. Swap the height and width
+  // here so that the dimensions will be correct once the app rotates to
+  // portrait.
+  if (IsLandscape()) {
+    mainBounds.size = CGSizeMake(mainBounds.size.height, mainBounds.size.width);
+  }
+  base::scoped_nsobject<UIScrollView> scrollView(
+      [[UIScrollView alloc] initWithFrame:mainBounds]);
+  self.view = scrollView;
+  [self.view setBackgroundColor:[UIColor colorWithWhite:0.902 alpha:1.0]];
+  const CGFloat kIPadInset =
+      (mainBounds.size.width - kIPadWidth - kHorizontalSpacing) / 2;
+  const CGFloat widthInset = IsIPadIdiom() ? kIPadInset : kHorizontalSpacing;
+  innerView_.reset([[UIView alloc]
+      initWithFrame:CGRectInset(mainBounds, widthInset, kVerticalSpacing * 2)]);
+  [innerView_ setBackgroundColor:[UIColor whiteColor]];
+  [innerView_ layer].cornerRadius = 3;
+  [innerView_ layer].borderWidth = 1;
+  [innerView_ layer].borderColor =
+      [UIColor colorWithWhite:0.851 alpha:1.0].CGColor;
+  [innerView_ layer].masksToBounds = YES;
+  [scrollView addSubview:innerView_];
+
+  UIImage* fatalImage = [UIImage imageNamed:@"fatal_error.png"];
+  base::scoped_nsobject<UIImageView> imageView(
+      [[UIImageView alloc] initWithImage:fatalImage]);
+  // Shift the image down a bit.
+  CGRect imageFrame = [imageView frame];
+  imageFrame.origin.y = kVerticalSpacing;
+  [imageView setFrame:imageFrame];
+  [self centerView:imageView afterView:nil];
+  [innerView_ addSubview:imageView];
+
+  base::scoped_nsobject<UILabel> awSnap([[UILabel alloc] init]);
+  [awSnap setText:NSLocalizedString(@"IDS_IOS_SAFE_MODE_AW_SNAP", @"")];
+  [awSnap setBackgroundColor:[UIColor clearColor]];
+  [awSnap setTextColor:[UIColor blackColor]];
+  [awSnap setFont:[UIFont boldSystemFontOfSize:21]];
+  [awSnap sizeToFit];
+  [self centerView:awSnap afterView:imageView];
+  [innerView_ addSubview:awSnap];
+
+  base::scoped_nsobject<UILabel> description([[UILabel alloc] init]);
+  [description setText:[self startupCrashModuleText]];
+  [description setBackgroundColor:[UIColor clearColor]];
+  [description setTextColor:[UIColor colorWithWhite:0.31 alpha:1.0]];
+  [description setTextAlignment:NSTextAlignmentCenter];
+  [description setNumberOfLines:0];
+  [description setLineBreakMode:NSLineBreakByWordWrapping];
+  CGRect frame = [description frame];
+  frame.size.width = IsIPadIdiom() ? kIPadWidth : kIPhoneWidth;
+  CGSize maxSize = CGSizeMake(frame.size.width, 999999.0f);
+  frame.size.height =
+      [[description text] cr_boundingSizeWithSize:maxSize
+                                             font:[description font]]
+          .height;
+  [description setFrame:frame];
+  [self centerView:description afterView:awSnap];
+  [innerView_ addSubview:description];
+
+  startButton_.reset([[PrimaryActionButton alloc] init]);
+  NSString* startText =
+      NSLocalizedString(@"IDS_IOS_SAFE_MODE_RELOAD_CHROME", @"");
+  [startButton_ setTitle:startText forState:UIControlStateNormal];
+  [startButton_ titleLabel].textAlignment = NSTextAlignmentCenter;
+  [startButton_ titleLabel].lineBreakMode = NSLineBreakByWordWrapping;
+  frame = [startButton_ frame];
+  frame.size.width = IsIPadIdiom() ? kIPadWidth : kIPhoneWidth;
+  maxSize = CGSizeMake(frame.size.width, 999999.0f);
+  const CGFloat kButtonBuffer = kVerticalSpacing / 2;
+  CGSize startTextBoundingSize =
+      [startText cr_boundingSizeWithSize:maxSize
+                                    font:[startButton_ titleLabel].font];
+  frame.size.height = startTextBoundingSize.height + kButtonBuffer;
+  [startButton_ setFrame:frame];
+  [startButton_ addTarget:self
+                   action:@selector(startBrowserFromSafeMode)
+         forControlEvents:UIControlEventTouchUpInside];
+  [self centerView:startButton_ afterView:description];
+  [innerView_ addSubview:startButton_];
+
+  UIView* lastView = startButton_;
+  if ([SafeModeViewController hasReportToUpload]) {
+    breakpad_helper::StartUploadingReportsInRecoveryMode();
+
+    // If there are no jailbreak modifications, then present the "Sending crash
+    // report..." UI.
+    if (![SafeModeViewController detectedThirdPartyMods]) {
+      [startButton_ setEnabled:NO];
+
+      uploadDescription_.reset([[UILabel alloc] init]);
+      [uploadDescription_
+          setText:NSLocalizedString(@"IDS_IOS_SAFE_MODE_SENDING_CRASH_REPORT",
+                                    @"")];
+      [uploadDescription_ setBackgroundColor:[UIColor clearColor]];
+      [uploadDescription_ setFont:[UIFont systemFontOfSize:13]];
+      [uploadDescription_ setTextColor:[UIColor colorWithWhite:0.31 alpha:1.0]];
+      [uploadDescription_ sizeToFit];
+      [self centerView:uploadDescription_ afterView:startButton_];
+      [innerView_ addSubview:uploadDescription_];
+
+      uploadProgress_.reset([[UIProgressView alloc]
+          initWithProgressViewStyle:UIProgressViewStyleDefault]);
+      [self centerView:uploadProgress_ afterView:nil];
+      frame = [uploadProgress_ frame];
+      frame.origin.y =
+          CGRectGetMaxY([uploadDescription_ frame]) + kUploadProgressSpacing;
+      [uploadProgress_ setFrame:frame];
+      [innerView_ addSubview:uploadProgress_];
+
+      lastView = uploadProgress_;
+      [self startUploadProgress];
+    }
+  }
+
+  CGSize scrollSize =
+      CGSizeMake(mainBounds.size.width,
+                 CGRectGetMaxY([lastView frame]) + kVerticalSpacing);
+  frame = [innerView_ frame];
+  frame.size.height = scrollSize.height;
+  [innerView_ setFrame:frame];
+  scrollSize.height += frame.origin.y;
+  [scrollView setContentSize:scrollSize];
+}
+
+- (NSUInteger)supportedInterfaceOrientations {
+  return UIInterfaceOrientationMaskPortrait;
+}
+
+#pragma mark - Private
+
+- (void)startUploadProgress {
+  uploadStartTime_.reset([[NSDate date] retain]);
+  uploadTimer_.reset(
+      [[NSTimer scheduledTimerWithTimeInterval:kUploadPumpInterval
+                                        target:self
+                                      selector:@selector(pumpUploadProgress)
+                                      userInfo:nil
+                                       repeats:YES] retain]);
+}
+
+- (void)pumpUploadProgress {
+  NSTimeInterval elapsed =
+      [[NSDate date] timeIntervalSinceDate:uploadStartTime_];
+  // Theoretically we could stop early when the value returned by
+  // ios_internal::breakpad::GetCrashReportCount() changes, but this is
+  // simpler. If we decide to look for a change in crash report count, then we
+  // also probably want to replace the UIProgressView with a
+  // UIActivityIndicatorView.
+  if (elapsed <= kUploadTotalTime) {
+    [uploadProgress_ setProgress:elapsed / kUploadTotalTime animated:YES];
+  } else {
+    [uploadTimer_ invalidate];
+
+    [startButton_ setEnabled:YES];
+    [uploadDescription_
+        setText:NSLocalizedString(@"IDS_IOS_SAFE_MODE_CRASH_REPORT_SENT",
+                                  @"")];
+    [uploadDescription_ sizeToFit];
+    [self centerView:uploadDescription_ afterView:startButton_];
+    [uploadProgress_ setHidden:YES];
+  }
+}
+
+- (void)startBrowserFromSafeMode {
+  breakpad_helper::RestoreDefaultConfiguration();
+  [delegate_ startBrowserFromSafeMode];
+}
+
+@end
diff --git a/ios/chrome/app/safe_mode/safe_mode_view_controller_unittest.mm b/ios/chrome/app/safe_mode/safe_mode_view_controller_unittest.mm
new file mode 100644
index 0000000..1c083292
--- /dev/null
+++ b/ios/chrome/app/safe_mode/safe_mode_view_controller_unittest.mm
@@ -0,0 +1,93 @@
+// Copyright 2013 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.
+
+#include "base/mac/scoped_nsobject.h"
+#import "breakpad/src/client/ios/BreakpadController.h"
+#import "ios/chrome/app/safe_mode/safe_mode_view_controller.h"
+#import "ios/chrome/browser/crash_report/breakpad_helper.h"
+#import "ios/chrome/test/base/scoped_block_swizzler.h"
+#import "ios/chrome/test/ocmock/OCMockObject+BreakpadControllerTesting.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
+#include "third_party/ocmock/gtest_support.h"
+
+namespace {
+
+const int kCrashReportCount = 2;
+
+class SafeModeViewControllerTest : public PlatformTest {
+ public:
+  void SetUp() override {
+    PlatformTest::SetUp();
+
+    mock_breakpad_controller_.reset(
+        [[OCMockObject mockForClass:[BreakpadController class]] retain]);
+
+    // Swizzle +[BreakpadController sharedInstance] to return
+    // |mock_breakpad_controller_| instead of the normal singleton instance.
+    id implementation_block = ^BreakpadController*(id self) {
+      return mock_breakpad_controller_.get();
+    };
+    breakpad_controller_shared_instance_swizzler_.reset(new ScopedBlockSwizzler(
+        [BreakpadController class], @selector(sharedInstance),
+        implementation_block));
+  }
+
+  void TearDown() override {
+    [[mock_breakpad_controller_ stub] stop];
+    breakpad_helper::SetEnabled(false);
+
+    PlatformTest::TearDown();
+  }
+
+ protected:
+  base::scoped_nsobject<id> mock_breakpad_controller_;
+  std::unique_ptr<ScopedBlockSwizzler>
+      breakpad_controller_shared_instance_swizzler_;
+};
+
+// Verify that +[SafeModeViewController hasSuggestions] returns YES if and only
+// if crash reporter and crash report uploading are enabled and there are
+// multiple crash reports to upload.
+TEST_F(SafeModeViewControllerTest, HasSuggestions) {
+  // Test when crash reporter is disabled.
+  breakpad_helper::SetEnabled(false);
+  EXPECT_OCMOCK_VERIFY(mock_breakpad_controller_.get());
+  EXPECT_FALSE([SafeModeViewController hasSuggestions]);
+
+  breakpad_helper::SetUploadingEnabled(false);
+  EXPECT_OCMOCK_VERIFY(mock_breakpad_controller_.get());
+  EXPECT_FALSE([SafeModeViewController hasSuggestions]);
+
+  breakpad_helper::SetUploadingEnabled(true);
+  EXPECT_OCMOCK_VERIFY(mock_breakpad_controller_.get());
+  EXPECT_FALSE([SafeModeViewController hasSuggestions]);
+
+  // Test when crash reporter is enabled.
+  [[mock_breakpad_controller_ expect] start:NO];
+  breakpad_helper::SetEnabled(true);
+  EXPECT_OCMOCK_VERIFY(mock_breakpad_controller_.get());
+  EXPECT_FALSE([SafeModeViewController hasSuggestions]);
+
+  [[mock_breakpad_controller_ expect] setUploadingEnabled:NO];
+  breakpad_helper::SetUploadingEnabled(false);
+  EXPECT_OCMOCK_VERIFY(mock_breakpad_controller_.get());
+  EXPECT_FALSE([SafeModeViewController hasSuggestions]);
+
+  // Test when crash reporter and crash report uploading are enabled.
+  [[mock_breakpad_controller_ expect] setUploadingEnabled:YES];
+  breakpad_helper::SetUploadingEnabled(true);
+  EXPECT_OCMOCK_VERIFY(mock_breakpad_controller_.get());
+
+  [mock_breakpad_controller_ cr_expectGetCrashReportCount:0];
+  EXPECT_FALSE([SafeModeViewController hasSuggestions]);
+  EXPECT_OCMOCK_VERIFY(mock_breakpad_controller_.get());
+
+  [mock_breakpad_controller_ cr_expectGetCrashReportCount:kCrashReportCount];
+  EXPECT_TRUE([SafeModeViewController hasSuggestions]);
+  EXPECT_OCMOCK_VERIFY(mock_breakpad_controller_.get());
+}
+
+}  // namespace
diff --git a/ios/chrome/app/spotlight/actions_spotlight_manager.h b/ios/chrome/app/spotlight/actions_spotlight_manager.h
new file mode 100644
index 0000000..67f0acb
--- /dev/null
+++ b/ios/chrome/app/spotlight/actions_spotlight_manager.h
@@ -0,0 +1,42 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_SPOTLIGHT_ACTIONS_SPOTLIGHT_MANAGER_H_
+#define IOS_CHROME_APP_SPOTLIGHT_ACTIONS_SPOTLIGHT_MANAGER_H_
+
+#import "ios/chrome/app/spotlight/base_spotlight_manager.h"
+
+@class AppStartupParameters;
+
+namespace spotlight {
+
+// Keys for Spotlight actions.
+extern const char kSpotlightActionNewTab[];
+extern const char kSpotlightActionNewIncognitoTab[];
+extern const char kSpotlightActionVoiceSearch[];
+extern const char kSpotlightActionQRScanner[];
+
+// Sets the correct properties for startup parameters according to the action
+// specified by the |query|. Returns YES if the properties were successfully
+// set. The query must represent an action and |startupParams| must not be nil.
+BOOL SetStartupParametersForSpotlightAction(
+    NSString* query,
+    AppStartupParameters* startupParams);
+
+}  // namespace spotlight
+
+// Allows Chrome to add links to actions to the systemwide Spotlight search
+// index.
+@interface ActionsSpotlightManager : BaseSpotlightManager
+
+// Creates an ActionsSpotlightManager.
++ (ActionsSpotlightManager*)actionsSpotlightManager;
+
+// Updates the index with the Spotlight actions if the EnableSpotlightActions
+// experimental flag is set. Otherwise the index is only cleared.
+- (void)indexActions;
+
+@end
+
+#endif  // IOS_CHROME_APP_SPOTLIGHT_ACTIONS_SPOTLIGHT_MANAGER_H_
diff --git a/ios/chrome/app/spotlight/actions_spotlight_manager.mm b/ios/chrome/app/spotlight/actions_spotlight_manager.mm
new file mode 100644
index 0000000..6b010f2
--- /dev/null
+++ b/ios/chrome/app/spotlight/actions_spotlight_manager.mm
@@ -0,0 +1,205 @@
+// Copyright 2016 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/spotlight/actions_spotlight_manager.h"
+
+#import <CoreSpotlight/CoreSpotlight.h>
+
+#include "base/ios/weak_nsobject.h"
+#include "base/mac/foundation_util.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/sys_string_conversions.h"
+#include "ios/chrome/browser/app_startup_parameters.h"
+#include "ios/chrome/browser/experimental_flags.h"
+#include "ios/chrome/common/app_group/app_group_constants.h"
+#include "ios/chrome/grit/ios_strings.h"
+#include "net/base/mac/url_conversions.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "url/gurl.h"
+
+namespace {
+
+NSString* SpotlightActionFromString(NSString* query) {
+  NSString* domain =
+      [NSString stringWithFormat:@"%@.", spotlight::StringFromSpotlightDomain(
+                                             spotlight::DOMAIN_ACTIONS)];
+  DCHECK([query hasPrefix:domain]);
+  return
+      [query substringWithRange:NSMakeRange([domain length],
+                                            [query length] - [domain length])];
+}
+
+}  // namespace
+
+namespace spotlight {
+
+// Constants for Spotlight action links.
+const char kSpotlightActionNewTab[] = "OpenNewTab";
+const char kSpotlightActionNewIncognitoTab[] = "OpenIncognitoTab";
+const char kSpotlightActionVoiceSearch[] = "OpenVoiceSearch";
+const char kSpotlightActionQRScanner[] = "OpenQRScanner";
+
+// Enum is used to record the actions performed by the user.
+enum {
+  // Recorded when a user pressed the New Tab spotlight action.
+  SPOTLIGHT_ACTION_NEW_TAB_PRESSED,
+  // Recorded when a user pressed the New Incognito Tab spotlight action.
+  SPOTLIGHT_ACTION_NEW_INCOGNITO_TAB_PRESSED,
+  // Recorded when a user pressed the Voice Search spotlight action.
+  SPOTLIGHT_ACTION_VOICE_SEARCH_PRESSED,
+  // Recorded when a user pressed the QR scanner spotlight action.
+  SPOTLIGHT_ACTION_QR_CODE_SCANNER_PRESSED,
+  // NOTE: Add new spotlight actions in sources only immediately above this
+  // line. Also, make sure the enum list for histogram |SpotlightActions| in
+  // histograms.xml is updated with any change in here.
+  SPOTLIGHT_ACTION_COUNT
+};
+
+// The histogram used to record user actions performed on the spotlight actions.
+const char kSpotlightActionsHistogram[] = "IOS.Spotlight.Action";
+
+BOOL SetStartupParametersForSpotlightAction(
+    NSString* query,
+    AppStartupParameters* startupParams) {
+  DCHECK(startupParams);
+  NSString* action = SpotlightActionFromString(query);
+  if ([action isEqualToString:base::SysUTF8ToNSString(
+                                  kSpotlightActionNewIncognitoTab)]) {
+    UMA_HISTOGRAM_ENUMERATION(kSpotlightActionsHistogram,
+                              SPOTLIGHT_ACTION_NEW_INCOGNITO_TAB_PRESSED,
+                              SPOTLIGHT_ACTION_COUNT);
+    [startupParams setLaunchInIncognito:YES];
+  } else if ([action isEqualToString:base::SysUTF8ToNSString(
+                                         kSpotlightActionVoiceSearch)]) {
+    UMA_HISTOGRAM_ENUMERATION(kSpotlightActionsHistogram,
+                              SPOTLIGHT_ACTION_VOICE_SEARCH_PRESSED,
+                              SPOTLIGHT_ACTION_COUNT);
+    [startupParams setLaunchVoiceSearch:YES];
+  } else if ([action isEqualToString:base::SysUTF8ToNSString(
+                                         kSpotlightActionQRScanner)]) {
+    UMA_HISTOGRAM_ENUMERATION(kSpotlightActionsHistogram,
+                              SPOTLIGHT_ACTION_QR_CODE_SCANNER_PRESSED,
+                              SPOTLIGHT_ACTION_COUNT);
+    [startupParams setLaunchQRScanner:YES];
+  } else if ([action isEqualToString:base::SysUTF8ToNSString(
+                                         kSpotlightActionNewTab)]) {
+    UMA_HISTOGRAM_ENUMERATION(kSpotlightActionsHistogram,
+                              SPOTLIGHT_ACTION_NEW_TAB_PRESSED,
+                              SPOTLIGHT_ACTION_COUNT);
+  } else {
+    return NO;
+  }
+  return YES;
+}
+
+}  // namespace spotlight
+
+@interface ActionsSpotlightManager ()
+
+// Creates a new Spotlight entry with title |title| for the given |action|.
+- (CSSearchableItem*)getItemForAction:(NSString*)action title:(NSString*)title;
+
+// Clears and re-inserts all Spotlight actions.
+- (void)clearAndAddSpotlightActions;
+
+@end
+
+@implementation ActionsSpotlightManager
+
++ (ActionsSpotlightManager*)actionsSpotlightManager {
+  return [[[ActionsSpotlightManager alloc]
+      initWithLargeIconService:nil
+                        domain:spotlight::DOMAIN_ACTIONS] autorelease];
+}
+
+#pragma mark public methods
+
+- (void)indexActions {
+  base::WeakNSObject<ActionsSpotlightManager> weakSelf(self);
+  dispatch_after(
+      dispatch_time(DISPATCH_TIME_NOW, static_cast<int64_t>(1 * NSEC_PER_SEC)),
+      dispatch_get_main_queue(), ^{
+        base::scoped_nsobject<ActionsSpotlightManager> strongSelf(
+            [weakSelf retain]);
+        [strongSelf clearAndAddSpotlightActions];
+      });
+}
+
+#pragma mark private methods
+
+- (void)clearAndAddSpotlightActions {
+  [self clearAllSpotlightItems:^(NSError* error) {
+    if (!experimental_flags::IsSpotlightActionsEnabled()) {
+      return;
+    }
+    base::WeakNSObject<ActionsSpotlightManager> weakSelf(self);
+    dispatch_after(
+        dispatch_time(DISPATCH_TIME_NOW,
+                      static_cast<int64_t>(1 * NSEC_PER_SEC)),
+        dispatch_get_main_queue(), ^{
+          base::scoped_nsobject<ActionsSpotlightManager> strongSelf(
+              [weakSelf retain]);
+
+          if (!strongSelf) {
+            return;
+          }
+
+          NSString* voiceSearchTitle = l10n_util::GetNSString(
+              IDS_IOS_APPLICATION_SHORTCUT_VOICE_SEARCH_TITLE);
+          NSString* voiceSearchAction =
+              base::SysUTF8ToNSString(spotlight::kSpotlightActionVoiceSearch);
+
+          NSString* newTabTitle =
+              l10n_util::GetNSString(IDS_IOS_APPLICATION_SHORTCUT_NEWTAB_TITLE);
+          NSString* newTabAction =
+              base::SysUTF8ToNSString(spotlight::kSpotlightActionNewTab);
+
+          NSString* incognitoTitle = l10n_util::GetNSString(
+              IDS_IOS_APPLICATION_SHORTCUT_NEWINCOGNITOTAB_TITLE);
+          NSString* incognitoAction = base::SysUTF8ToNSString(
+              spotlight::kSpotlightActionNewIncognitoTab);
+
+          NSMutableArray* spotlightItems = [NSMutableArray
+              arrayWithObjects:[strongSelf getItemForAction:voiceSearchAction
+                                                      title:voiceSearchTitle],
+                               [strongSelf getItemForAction:newTabAction
+                                                      title:newTabTitle],
+                               [strongSelf getItemForAction:incognitoAction
+                                                      title:incognitoTitle],
+                               nil];
+
+          if (experimental_flags::IsQRCodeReaderEnabled()) {
+            NSString* qrScannerTitle = l10n_util::GetNSString(
+                IDS_IOS_APPLICATION_SHORTCUT_QR_SCANNER_TITLE);
+            NSString* qrScannerAction =
+                base::SysUTF8ToNSString(spotlight::kSpotlightActionQRScanner);
+
+            [spotlightItems
+                addObject:[strongSelf getItemForAction:qrScannerAction
+                                                 title:qrScannerTitle]];
+          }
+
+          [[CSSearchableIndex defaultSearchableIndex]
+              indexSearchableItems:spotlightItems
+                 completionHandler:nil];
+        });
+  }];
+}
+
+- (CSSearchableItem*)getItemForAction:(NSString*)action title:(NSString*)title {
+  base::scoped_nsobject<CSSearchableItemAttributeSet> attributeSet(
+      [[CSSearchableItemAttributeSet alloc]
+          initWithItemContentType:spotlight::StringFromSpotlightDomain(
+                                      spotlight::DOMAIN_ACTIONS)]);
+  [attributeSet setTitle:title];
+  [attributeSet setDisplayName:title];
+
+  NSString* domainID =
+      spotlight::StringFromSpotlightDomain(spotlight::DOMAIN_ACTIONS);
+  NSString* itemID = [NSString stringWithFormat:@"%@.%@", domainID, action];
+
+  return [self spotlightItemWithItemID:itemID attributeSet:attributeSet];
+}
+
+@end
diff --git a/ios/chrome/app/spotlight/base_spotlight_manager.h b/ios/chrome/app/spotlight/base_spotlight_manager.h
new file mode 100644
index 0000000..bdf5ffa
--- /dev/null
+++ b/ios/chrome/app/spotlight/base_spotlight_manager.h
@@ -0,0 +1,62 @@
+// Copyright 2015 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.
+
+#ifndef IOS_CHROME_APP_SPOTLIGHT_BASE_SPOTLIGHT_MANAGER_H_
+#define IOS_CHROME_APP_SPOTLIGHT_BASE_SPOTLIGHT_MANAGER_H_
+
+#import <CoreSpotlight/CoreSpotlight.h>
+#import <UIKit/UIKit.h>
+
+#include "base/mac/scoped_nsobject.h"
+#include "ios/chrome/app/spotlight/spotlight_util.h"
+
+class GURL;
+
+namespace favicon {
+class LargeIconService;
+}
+
+@interface BaseSpotlightManager : NSObject
+
+- (instancetype)initWithLargeIconService:
+                    (favicon::LargeIconService*)largeIconService
+                                  domain:(spotlight::Domain)domain
+    NS_DESIGNATED_INITIALIZER;
+
+- (instancetype)init NS_UNAVAILABLE;
+
+// Refreshes all items that point to |URLToRefresh|, using title |title|, by
+// calling spotlightItemsWithURL on given URL. The values of |title| and |URL|
+// will be passed to spotlightItemsWithURL.
+- (void)refreshItemsWithURL:(const GURL&)URLToRefresh title:(NSString*)title;
+
+// Creates a spotlight item with |itemID|, using the |attributeSet|.
+- (CSSearchableItem*)spotlightItemWithItemID:(NSString*)itemID
+                                attributeSet:
+                                    (CSSearchableItemAttributeSet*)attributeSet;
+
+// Creates spotlight items in the class's domain for |URL|,
+// using |favicon| and |defaultTitle|
+// Base implementation creates a single item directly using provided arguments
+// and expects a non-nil title.
+- (NSArray*)spotlightItemsWithURL:(const GURL&)URL
+                          favicon:(UIImage*)favicon
+                     defaultTitle:(NSString*)defaultTitle;
+
+// Removes all items in the current manager's domain from the Spotlight
+// index, then calls |callback| on completion
+- (void)clearAllSpotlightItems:(BlockWithError)callback;
+
+// Cancel all large icon pending tasks.
+- (void)cancelAllLargeIconPendingTasks;
+
+// Returns the spotlight ID for an item indexing |URL| and |title|.
+- (NSString*)spotlightIDForURL:(const GURL&)URL title:(NSString*)title;
+
+// Returns the number of pending large icon query tasks
+- (NSUInteger)pendingLargeIconTasksCount;
+
+@end
+
+#endif  // IOS_CHROME_APP_SPOTLIGHT_BASE_SPOTLIGHT_MANAGER_H_
diff --git a/ios/chrome/app/spotlight/base_spotlight_manager.mm b/ios/chrome/app/spotlight/base_spotlight_manager.mm
new file mode 100644
index 0000000..4d497c1
--- /dev/null
+++ b/ios/chrome/app/spotlight/base_spotlight_manager.mm
@@ -0,0 +1,272 @@
+// Copyright 2015 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/spotlight/base_spotlight_manager.h"
+
+#import <CommonCrypto/CommonCrypto.h>
+#import <MobileCoreServices/MobileCoreServices.h>
+
+#include "base/ios/weak_nsobject.h"
+#include "base/mac/bind_objc_block.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/task/cancelable_task_tracker.h"
+#include "components/favicon/core/fallback_url_util.h"
+#include "components/favicon/core/large_icon_service.h"
+#include "components/favicon_base/fallback_icon_style.h"
+#include "components/favicon_base/favicon_types.h"
+#include "ios/chrome/grit/ios_strings.h"
+#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
+#include "ios/public/provider/chrome/browser/spotlight/spotlight_provider.h"
+#import "ios/third_party/material_components_ios/src/components/Typography/src/MaterialTypography.h"
+#import "net/base/mac/url_conversions.h"
+#include "skia/ext/skia_utils_ios.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace {
+// Minimum size of the icon to be used in Spotlight.
+const NSInteger kMinIconSize = 32;
+
+// Preferred size of the icon to be used in Spotlight.
+const NSInteger kIconSize = 64;
+
+// Size of the fallback icon.
+const CGFloat kFallbackIconSize = 180;
+
+// Radius of the rounded corner of the fallback icon.
+const CGFloat kFallbackRoundedCorner = 8;
+}
+
+@interface BaseSpotlightManager () {
+  // Domain of the spotlight manager.
+  spotlight::Domain _spotlightDomain;
+
+  // Service to retrieve large favicon or colors for a fallback icon.
+  favicon::LargeIconService* _largeIconService;  // weak
+
+  // Queue to query large icons.
+  base::CancelableTaskTracker _largeIconTaskTracker;
+
+  // Dictionary to track the tasks querying the large icons.
+  base::scoped_nsobject<NSMutableDictionary> _pendingTasks;
+}
+
+// Compute a hash consisting of the first 8 bytes of the MD5 hash of a string
+// containing |URL| and |title|.
+- (int64_t)getHashForURL:(const GURL&)URL title:(NSString*)title;
+
+// Create an image with a rounded square with color |backgroundColor| and
+// |string| centered in color |textColor|.
+UIImage* GetFallbackImageWithStringAndColor(NSString* string,
+                                            UIColor* backgroundColor,
+                                            UIColor* textColor);
+
+// Returns an array of Keywords for Spotlight search.
+- (NSArray*)keywordsForSpotlightItems;
+
+@end
+
+@implementation BaseSpotlightManager
+
+- (instancetype)initWithLargeIconService:
+                    (favicon::LargeIconService*)largeIconService
+                                  domain:(spotlight::Domain)domain {
+  self = [super init];
+  if (self) {
+    _spotlightDomain = domain;
+    _largeIconService = largeIconService;
+    _pendingTasks.reset([[NSMutableDictionary alloc] init]);
+  }
+  return self;
+}
+
+- (instancetype)init {
+  NOTREACHED();
+  return nil;
+}
+
+- (int64_t)getHashForURL:(const GURL&)URL title:(NSString*)title {
+  NSString* key = [NSString
+      stringWithFormat:@"%@ %@", base::SysUTF8ToNSString(URL.spec()), title];
+  unsigned char hash[CC_MD5_DIGEST_LENGTH];
+  const std::string clipboard = base::SysNSStringToUTF8(key);
+  const char* c_string = clipboard.c_str();
+  CC_MD5(c_string, strlen(c_string), hash);
+  uint64_t md5 = *(reinterpret_cast<uint64_t*>(hash));
+  return md5;
+}
+
+- (NSString*)spotlightIDForURL:(const GURL&)URL title:(NSString*)title {
+  NSString* spotlightID = [NSString
+      stringWithFormat:@"%@.%016llx",
+                       spotlight::StringFromSpotlightDomain(_spotlightDomain),
+                       [self getHashForURL:URL title:title]];
+  return spotlightID;
+}
+
+- (void)cancelAllLargeIconPendingTasks {
+  _largeIconTaskTracker.TryCancelAll();
+  [_pendingTasks removeAllObjects];
+}
+
+- (void)clearAllSpotlightItems:(BlockWithError)callback {
+  [self cancelAllLargeIconPendingTasks];
+  spotlight::DeleteSearchableDomainItems(_spotlightDomain, callback);
+}
+
+- (CSSearchableItem*)spotlightItemWithItemID:(NSString*)itemID
+                                attributeSet:(CSSearchableItemAttributeSet*)
+                                                 attributeSet {
+  CSCustomAttributeKey* key = [[[CSCustomAttributeKey alloc]
+          initWithKeyName:spotlight::GetSpotlightCustomAttributeItemID()
+               searchable:YES
+      searchableByDefault:YES
+                   unique:YES
+              multiValued:NO] autorelease];
+  [attributeSet setValue:itemID forCustomKey:key];
+  attributeSet.keywords = [self keywordsForSpotlightItems];
+
+  NSString* domainID = spotlight::StringFromSpotlightDomain(_spotlightDomain);
+
+  return [[[CSSearchableItem alloc] initWithUniqueIdentifier:itemID
+                                            domainIdentifier:domainID
+                                                attributeSet:attributeSet]
+      autorelease];
+}
+
+- (NSArray*)spotlightItemsWithURL:(const GURL&)indexedURL
+                          favicon:(UIImage*)favicon
+                     defaultTitle:(NSString*)defaultTitle {
+  DCHECK(defaultTitle);
+  NSURL* nsURL = net::NSURLWithGURL(indexedURL);
+  std::string description = indexedURL.SchemeIsCryptographic()
+                                ? indexedURL.GetOrigin().spec()
+                                : indexedURL.spec();
+
+  base::scoped_nsobject<CSSearchableItemAttributeSet> attributeSet(
+      [[CSSearchableItemAttributeSet alloc]
+          initWithItemContentType:(NSString*)kUTTypeURL]);
+  [attributeSet setTitle:defaultTitle];
+  [attributeSet setDisplayName:defaultTitle];
+  [attributeSet setURL:nsURL];
+  [attributeSet setContentURL:nsURL];
+  [attributeSet setContentDescription:base::SysUTF8ToNSString(description)];
+  [attributeSet setThumbnailData:UIImagePNGRepresentation(favicon)];
+
+  NSString* itemID = [self spotlightIDForURL:indexedURL title:defaultTitle];
+  return [NSArray arrayWithObject:[self spotlightItemWithItemID:itemID
+                                                   attributeSet:attributeSet]];
+}
+
+UIImage* GetFallbackImageWithStringAndColor(NSString* string,
+                                            UIColor* backgroundColor,
+                                            UIColor* textColor) {
+  CGRect rect = CGRectMake(0, 0, kFallbackIconSize, kFallbackIconSize);
+  UIGraphicsBeginImageContext(rect.size);
+  CGContextRef context = UIGraphicsGetCurrentContext();
+  CGContextSetFillColorWithColor(context, [backgroundColor CGColor]);
+  CGContextSetStrokeColorWithColor(context, [textColor CGColor]);
+  UIBezierPath* rounded =
+      [UIBezierPath bezierPathWithRoundedRect:rect
+                                 cornerRadius:kFallbackRoundedCorner];
+  [rounded fill];
+  UIFont* font = [MDCTypography headlineFont];
+  font = [font fontWithSize:(kFallbackIconSize / 2)];
+  CGRect textRect = CGRectMake(0, (kFallbackIconSize - [font lineHeight]) / 2,
+                               kFallbackIconSize, [font lineHeight]);
+  base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
+      [[NSMutableParagraphStyle alloc] init]);
+  [paragraphStyle setAlignment:NSTextAlignmentCenter];
+  base::scoped_nsobject<NSMutableDictionary> attributes(
+      [[NSMutableDictionary alloc] init]);
+  [attributes setValue:font forKey:NSFontAttributeName];
+  [attributes setValue:textColor forKey:NSForegroundColorAttributeName];
+  [attributes setValue:paragraphStyle forKey:NSParagraphStyleAttributeName];
+
+  [string drawInRect:textRect withAttributes:attributes];
+  UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
+  UIGraphicsEndImageContext();
+  return image;
+}
+
+- (void)refreshItemsWithURL:(const GURL&)URLToRefresh title:(NSString*)title {
+  NSURL* NSURL = net::NSURLWithGURL(URLToRefresh);
+
+  if (!NSURL || [_pendingTasks objectForKey:NSURL]) {
+    return;
+  }
+
+  base::WeakNSObject<BaseSpotlightManager> weakSelf(self);
+  GURL URL = URLToRefresh;
+  void (^faviconBlock)(const favicon_base::LargeIconResult&) = ^(
+      const favicon_base::LargeIconResult& result) {
+    base::scoped_nsobject<BaseSpotlightManager> strongSelf([weakSelf retain]);
+    if (!strongSelf) {
+      return;
+    }
+    [strongSelf.get()->_pendingTasks removeObjectForKey:NSURL];
+    UIImage* favicon;
+    if (result.bitmap.is_valid()) {
+      scoped_refptr<base::RefCountedMemory> data =
+          result.bitmap.bitmap_data.get();
+      favicon = [UIImage
+          imageWithData:[NSData dataWithBytes:data->front() length:data->size()]
+                  scale:[UIScreen mainScreen].scale];
+    } else {
+      NSString* iconText =
+          base::SysUTF16ToNSString(favicon::GetFallbackIconText(URL));
+      UIColor* backgroundColor = skia::UIColorFromSkColor(
+          result.fallback_icon_style->background_color);
+      UIColor* textColor =
+          skia::UIColorFromSkColor(result.fallback_icon_style->text_color);
+      favicon = GetFallbackImageWithStringAndColor(iconText, backgroundColor,
+                                                   textColor);
+    }
+    NSArray* spotlightItems = [strongSelf spotlightItemsWithURL:URL
+                                                        favicon:favicon
+                                                   defaultTitle:title];
+
+    if ([spotlightItems count]) {
+      [[CSSearchableIndex defaultSearchableIndex]
+          indexSearchableItems:spotlightItems
+             completionHandler:nil];
+    }
+  };
+
+  base::CancelableTaskTracker::TaskId taskID =
+      _largeIconService->GetLargeIconOrFallbackStyle(
+          URL, kMinIconSize * [UIScreen mainScreen].scale,
+          kIconSize * [UIScreen mainScreen].scale,
+          base::BindBlock(faviconBlock), &_largeIconTaskTracker);
+  [_pendingTasks setObject:[NSNumber numberWithLongLong:taskID] forKey:NSURL];
+}
+
+- (NSUInteger)pendingLargeIconTasksCount {
+  return [_pendingTasks count];
+}
+
+#pragma mark private methods
+
+- (NSArray*)keywordsForSpotlightItems {
+  NSMutableArray* keywordsArray = [NSMutableArray arrayWithArray:@[
+    l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_ONE),
+    l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_TWO),
+    l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_THREE),
+    l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_FOUR),
+    l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_FIVE),
+    l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_SIX),
+    l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_SEVEN),
+    l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_EIGHT),
+    l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_NINE),
+    l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_TEN)
+  ]];
+  NSArray* additionalArray = ios::GetChromeBrowserProvider()
+                                 ->GetSpotlightProvider()
+                                 ->GetAdditionalKeywords();
+  if (additionalArray) {
+    [keywordsArray addObjectsFromArray:additionalArray];
+  }
+  return keywordsArray;
+}
+
+@end
diff --git a/ios/chrome/app/spotlight/bookmarks_spotlight_manager.h b/ios/chrome/app/spotlight/bookmarks_spotlight_manager.h
new file mode 100644
index 0000000..31534ed
--- /dev/null
+++ b/ios/chrome/app/spotlight/bookmarks_spotlight_manager.h
@@ -0,0 +1,57 @@
+// Copyright 2015 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.
+
+#ifndef IOS_CHROME_APP_SPOTLIGHT_BOOKMARKS_SPOTLIGHT_MANAGER_H_
+#define IOS_CHROME_APP_SPOTLIGHT_BOOKMARKS_SPOTLIGHT_MANAGER_H_
+
+#import "ios/chrome/app/spotlight/base_spotlight_manager.h"
+
+namespace bookmarks {
+class BookmarkNode;
+class BookmarkModel;
+}
+
+namespace ios {
+class ChromeBrowserState;
+}  // namespace ios
+
+@class CSSearchableItem;
+@class TopSitesSpotlightManager;
+
+@protocol BookmarkUpdatedDelegate
+
+// Called when a bookmark is updated.
+- (void)bookmarkUpdated;
+
+@end
+
+@interface BookmarksSpotlightManager : BaseSpotlightManager
+
+// The delegate notified when a bookmark is updated.
+@property(nonatomic, assign) id<BookmarkUpdatedDelegate> delegate;
+
++ (BookmarksSpotlightManager*)bookmarksSpotlightManagerWithBrowserState:
+    (ios::ChromeBrowserState*)browserState;
+
+// Checks the date of the latest global indexation and reindex all bookmarks if
+// needed.
+- (void)reindexBookmarksIfNeeded;
+
+// Methods below here are for testing use only.
+
+- (instancetype)
+initWithLargeIconService:(favicon::LargeIconService*)largeIconService
+           bookmarkModel:(bookmarks::BookmarkModel*)bookmarkModel;
+
+// Recursively adds node ancestors titles to keywords. Permanent nodes are
+// ignored.
+- (void)getParentKeywordsForNode:(const bookmarks::BookmarkNode*)node
+                         inArray:(NSMutableArray*)keywords;
+
+// Adds keywords to |item|.
+- (void)addKeywords:(NSArray*)keywords toSearchableItem:(CSSearchableItem*)item;
+
+@end
+
+#endif  // IOS_CHROME_APP_SPOTLIGHT_BOOKMARKS_SPOTLIGHT_MANAGER_H_
diff --git a/ios/chrome/app/spotlight/bookmarks_spotlight_manager.mm b/ios/chrome/app/spotlight/bookmarks_spotlight_manager.mm
new file mode 100644
index 0000000..edbfd6a
--- /dev/null
+++ b/ios/chrome/app/spotlight/bookmarks_spotlight_manager.mm
@@ -0,0 +1,348 @@
+// Copyright 2015 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/spotlight/bookmarks_spotlight_manager.h"
+
+#include <memory>
+
+#import <CoreSpotlight/CoreSpotlight.h>
+
+#include "base/ios/weak_nsobject.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/version.h"
+#include "components/bookmarks/browser/base_bookmark_model_observer.h"
+#include "components/bookmarks/browser/bookmark_model.h"
+#include "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
+#include "ios/chrome/browser/favicon/ios_chrome_large_icon_service_factory.h"
+
+namespace {
+// Limit the size of the initial indexing. This will not limit the size of the
+// index as new bookmarks can be added afterwards.
+const int kMaxInitialIndexSize = 1000;
+
+// Minimum delay between two global indexing of bookmarks.
+const int kDelayBetweenTwoIndexingInSeconds = 7 * 86400;  // One week.
+
+}  // namespace
+
+class SpotlightBookmarkModelBridge;
+
+// Called from the BrowserBookmarkModelBridge from C++ -> ObjC.
+@interface BookmarksSpotlightManager () {
+  base::WeakNSProtocol<id<BookmarkUpdatedDelegate>> _delegate;
+
+  // Bridge to register for bookmark changes.
+  std::unique_ptr<SpotlightBookmarkModelBridge> _bookmarkModelBridge;
+
+  // Keep a reference to detach before deallocing. Life cycle of _bookmarkModel
+  // is longer than life cycle of a SpotlightManager as
+  // |BookmarkModelBeingDeleted| will cause deletion of SpotlightManager.
+  bookmarks::BookmarkModel* _bookmarkModel;  // weak
+
+  // Number of nodes indexed in initial scan.
+  NSUInteger _nodesIndexed;
+
+  // Tracks whether initial indexing has been done.
+  BOOL _initialIndexDone;
+}
+
+// Detaches the |SpotlightBookmarkModelBridge| from the bookmark model. The
+// manager must not be used after calling this method.
+- (void)detachBookmarkModel;
+
+// Removes the node from the Spotlight index.
+- (void)removeNodeFromIndex:(const bookmarks::BookmarkNode*)node;
+
+// Clears all the bookmarks in the Spotlight index then index the bookmarks in
+// the model.
+- (void)clearAndReindexModel;
+
+// Refreshes all nodes in the subtree of node.
+// If |initial| is YES, limit the number of nodes to kMaxInitialIndexSize.
+- (void)refreshNodeInIndex:(const bookmarks::BookmarkNode*)node
+                   initial:(BOOL)initial;
+
+// Returns true is the current index is too old or from an incompatible version.
+- (BOOL)shouldReindex;
+
+@end
+
+// Handles notification that bookmarks has been removed changed so we can update
+// the Spotlight index.
+class SpotlightBookmarkModelBridge : public bookmarks::BookmarkModelObserver {
+ public:
+  explicit SpotlightBookmarkModelBridge(BookmarksSpotlightManager* owner)
+      : owner_(owner){};
+
+  ~SpotlightBookmarkModelBridge() override{};
+
+  void BookmarkNodeRemoved(bookmarks::BookmarkModel* model,
+                           const bookmarks::BookmarkNode* parent,
+                           int old_index,
+                           const bookmarks::BookmarkNode* node,
+                           const std::set<GURL>& removed_urls) override {}
+
+  void OnWillRemoveBookmarks(bookmarks::BookmarkModel* model,
+                             const bookmarks::BookmarkNode* parent,
+                             int old_index,
+                             const bookmarks::BookmarkNode* node) override {
+    [owner_ removeNodeFromIndex:node];
+  }
+
+  void BookmarkModelBeingDeleted(bookmarks::BookmarkModel* model) override {
+    [owner_ detachBookmarkModel];
+  };
+
+  void BookmarkModelLoaded(bookmarks::BookmarkModel* model,
+                           bool ids_reassigned) override {
+    [owner_ reindexBookmarksIfNeeded];
+  }
+
+  void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
+                         const bookmarks::BookmarkNode* parent,
+                         int index) override {
+    [owner_ refreshNodeInIndex:parent->GetChild(index) initial:NO];
+  }
+
+  void OnWillChangeBookmarkNode(bookmarks::BookmarkModel* model,
+                                const bookmarks::BookmarkNode* node) override {
+    [owner_ removeNodeFromIndex:node];
+  }
+
+  void BookmarkNodeChanged(bookmarks::BookmarkModel* model,
+                           const bookmarks::BookmarkNode* node) override {
+    [owner_ refreshNodeInIndex:node initial:NO];
+  }
+
+  void BookmarkNodeFaviconChanged(
+      bookmarks::BookmarkModel* model,
+      const bookmarks::BookmarkNode* node) override {
+    [owner_ refreshNodeInIndex:node initial:NO];
+  }
+
+  void BookmarkAllUserNodesRemoved(
+      bookmarks::BookmarkModel* model,
+      const std::set<GURL>& removed_urls) override {
+    [owner_ clearAllSpotlightItems:nil];
+  }
+
+  void BookmarkNodeChildrenReordered(
+      bookmarks::BookmarkModel* model,
+      const bookmarks::BookmarkNode* node) override{};
+
+  void BookmarkNodeMoved(bookmarks::BookmarkModel* model,
+                         const bookmarks::BookmarkNode* old_parent,
+                         int old_index,
+                         const bookmarks::BookmarkNode* new_parent,
+                         int new_index) override {
+    [owner_ refreshNodeInIndex:new_parent->GetChild(new_index) initial:NO];
+  };
+
+ private:
+  __unsafe_unretained BookmarksSpotlightManager* owner_;  // Weak.
+};
+
+@implementation BookmarksSpotlightManager
+
++ (BookmarksSpotlightManager*)bookmarksSpotlightManagerWithBrowserState:
+    (ios::ChromeBrowserState*)browserState {
+  return [[[BookmarksSpotlightManager alloc]
+      initWithLargeIconService:IOSChromeLargeIconServiceFactory::
+                                   GetForBrowserState(browserState)
+                 bookmarkModel:ios::BookmarkModelFactory::GetForBrowserState(
+                                   browserState)] autorelease];
+}
+
+- (instancetype)
+initWithLargeIconService:(favicon::LargeIconService*)largeIconService
+           bookmarkModel:(bookmarks::BookmarkModel*)bookmarkModel {
+  self = [super initWithLargeIconService:largeIconService
+                                  domain:spotlight::DOMAIN_BOOKMARKS];
+  if (self) {
+    _bookmarkModelBridge.reset(new SpotlightBookmarkModelBridge(self));
+    _bookmarkModel = bookmarkModel;
+    bookmarkModel->AddObserver(_bookmarkModelBridge.get());
+  }
+  return self;
+}
+
+- (void)dealloc {
+  [self detachBookmarkModel];
+  [super dealloc];
+}
+
+- (void)detachBookmarkModel {
+  [self cancelAllLargeIconPendingTasks];
+  if (_bookmarkModelBridge.get()) {
+    _bookmarkModel->RemoveObserver(_bookmarkModelBridge.get());
+    _bookmarkModelBridge.reset();
+  }
+}
+
+- (id<BookmarkUpdatedDelegate>)delegate {
+  return _delegate;
+}
+
+- (void)setDelegate:(id<BookmarkUpdatedDelegate>)delegate {
+  _delegate.reset(delegate);
+}
+
+- (void)getParentKeywordsForNode:(const bookmarks::BookmarkNode*)node
+                         inArray:(NSMutableArray*)keywords {
+  if (!node) {
+    return;
+  }
+  if (node->is_folder() && !_bookmarkModel->is_permanent_node(node)) {
+    [keywords addObject:base::SysUTF16ToNSString(node->GetTitle())];
+  }
+  [self getParentKeywordsForNode:node->parent() inArray:keywords];
+}
+
+- (void)removeNodeFromIndex:(const bookmarks::BookmarkNode*)node {
+  if (node->is_url()) {
+    GURL url(node->url());
+    NSString* title = base::SysUTF16ToNSString(node->GetTitle());
+    NSString* spotlightID = [self spotlightIDForURL:url title:title];
+    base::WeakNSObject<BookmarksSpotlightManager> weakself(self);
+    BlockWithError completion = ^(NSError* error) {
+      dispatch_async(dispatch_get_main_queue(), ^{
+        [weakself refreshItemsWithURL:url title:nil];
+        [_delegate bookmarkUpdated];
+      });
+    };
+    spotlight::DeleteItemsWithIdentifiers(@[ spotlightID ], completion);
+    return;
+  }
+  int childCount = node->child_count();
+  for (int child = 0; child < childCount; child++) {
+    [self removeNodeFromIndex:node->GetChild(child)];
+  }
+}
+
+- (BOOL)shouldReindex {
+  NSDate* date = [[NSUserDefaults standardUserDefaults]
+      objectForKey:@(spotlight::kSpotlightLastIndexingDateKey)];
+  if (!date) {
+    return YES;
+  }
+  NSDate* expirationDate =
+      [date dateByAddingTimeInterval:kDelayBetweenTwoIndexingInSeconds];
+  if ([expirationDate compare:[NSDate date]] == NSOrderedAscending) {
+    return YES;
+  }
+  NSNumber* lastIndexedVersionString = [[NSUserDefaults standardUserDefaults]
+      objectForKey:@(spotlight::kSpotlightLastIndexingVersionKey)];
+  if (!lastIndexedVersionString) {
+    return YES;
+  }
+
+  if ([lastIndexedVersionString integerValue] <
+      spotlight::kCurrentSpotlightIndexVersion) {
+    return YES;
+  }
+  return NO;
+}
+
+- (void)reindexBookmarksIfNeeded {
+  if (!_bookmarkModel->loaded() || _initialIndexDone) {
+    return;
+  }
+  _initialIndexDone = YES;
+  if ([self shouldReindex]) {
+    [self clearAndReindexModel];
+  }
+}
+
+- (void)addKeywords:(NSArray*)keywords
+    toSearchableItem:(CSSearchableItem*)item {
+  NSSet* itemKeywords = [NSSet setWithArray:[[item attributeSet] keywords]];
+  itemKeywords = [itemKeywords setByAddingObjectsFromArray:keywords];
+  [[item attributeSet] setKeywords:[itemKeywords allObjects]];
+}
+
+- (void)refreshNodeInIndex:(const bookmarks::BookmarkNode*)node
+                   initial:(BOOL)initial {
+  if (initial && _nodesIndexed > kMaxInitialIndexSize) {
+    return;
+  }
+  if (node->is_url()) {
+    _nodesIndexed++;
+    [self refreshItemsWithURL:node->url() title:nil];
+    if (!initial) {
+      [_delegate bookmarkUpdated];
+    }
+    return;
+  }
+  int childCount = node->child_count();
+  for (int child = 0; child < childCount; child++) {
+    [self refreshNodeInIndex:node->GetChild(child) initial:initial];
+  }
+}
+
+- (NSArray*)spotlightItemsWithURL:(const GURL&)URL
+                          favicon:(UIImage*)favicon
+                     defaultTitle:(NSString*)defaultTitle {
+  base::scoped_nsobject<NSMutableDictionary> spotlightItems(
+      [[NSMutableDictionary alloc] init]);
+  std::vector<const bookmarks::BookmarkNode*> nodes;
+  _bookmarkModel->GetNodesByURL(URL, &nodes);
+  for (auto node : nodes) {
+    NSString* nodeTitle = base::SysUTF16ToNSString(node->GetTitle());
+    NSString* spotlightID = [self spotlightIDForURL:URL title:nodeTitle];
+    CSSearchableItem* item = [spotlightItems objectForKey:spotlightID];
+    if (!item) {
+      item = [[super spotlightItemsWithURL:URL
+                                   favicon:favicon
+                              defaultTitle:nodeTitle] objectAtIndex:0];
+    }
+    base::scoped_nsobject<NSMutableArray> nodeKeywords(
+        [[NSMutableArray alloc] init]);
+    [self getParentKeywordsForNode:node inArray:nodeKeywords.get()];
+    [self addKeywords:nodeKeywords toSearchableItem:item];
+    [spotlightItems setObject:item forKey:spotlightID];
+  }
+  return [spotlightItems allValues];
+}
+
+- (void)clearAndReindexModel {
+  [self cancelAllLargeIconPendingTasks];
+  base::WeakNSObject<BookmarksSpotlightManager> weakself(self);
+  BlockWithError completion = ^(NSError* error) {
+    if (!error) {
+      dispatch_async(dispatch_get_main_queue(), ^{
+        base::scoped_nsobject<BookmarksSpotlightManager> strongSelf(
+            [weakself retain]);
+        if (!strongSelf)
+          return;
+
+        NSDate* startOfReindexing = [NSDate date];
+        strongSelf.get()->_nodesIndexed = 0;
+        [strongSelf
+            refreshNodeInIndex:strongSelf.get()->_bookmarkModel->root_node()
+                       initial:YES];
+        NSDate* endOfReindexing = [NSDate date];
+        NSTimeInterval indexingDuration =
+            [endOfReindexing timeIntervalSinceDate:startOfReindexing];
+        UMA_HISTOGRAM_TIMES(
+            "IOS.Spotlight.BookmarksIndexingDuration",
+            base::TimeDelta::FromMillisecondsD(1000 * indexingDuration));
+        UMA_HISTOGRAM_COUNTS_1000("IOS.Spotlight.BookmarksInitialIndexSize",
+                                  [strongSelf pendingLargeIconTasksCount]);
+        [[NSUserDefaults standardUserDefaults]
+            setObject:endOfReindexing
+               forKey:@(spotlight::kSpotlightLastIndexingDateKey)];
+
+        [[NSUserDefaults standardUserDefaults]
+            setObject:[NSNumber numberWithInteger:
+                                    spotlight::kCurrentSpotlightIndexVersion]
+               forKey:@(spotlight::kSpotlightLastIndexingVersionKey)];
+        [_delegate bookmarkUpdated];
+      });
+    }
+  };
+  [self clearAllSpotlightItems:completion];
+}
+
+@end
diff --git a/ios/chrome/app/spotlight/spotlight_manager.h b/ios/chrome/app/spotlight/spotlight_manager.h
new file mode 100644
index 0000000..527ff25
--- /dev/null
+++ b/ios/chrome/app/spotlight/spotlight_manager.h
@@ -0,0 +1,36 @@
+// Copyright 2015 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.
+
+#ifndef IOS_CHROME_APP_SPOTLIGHT_SPOTLIGHT_MANAGER_H_
+#define IOS_CHROME_APP_SPOTLIGHT_SPOTLIGHT_MANAGER_H_
+
+#import <Foundation/Foundation.h>
+
+namespace ios {
+class ChromeBrowserState;
+}
+
+// Allows Chrome to add the bookmarks of |bookmarkModel| in the systemwide
+// Spotlight search index.
+// Bookmarks are added, removed or updated in Spotlight based on
+// BookmarkModelObserver notification.
+// As there is no possibility to check the state of the index, a global
+// reindexing is triggered on cold start every 7 days to enforce
+// bookmarks/spotlight synchronization. A global reindexing will clear the index
+// and reindex the first 1000 bookmarks.
+@interface SpotlightManager : NSObject
+
+// Creates a SpotlightManager tracking and indexing various browser state
+// elements such as most actives and bookmarks.
+// |browserState| must not be nil.
+// There should be only one SpotlightManager observing |browserState|.
++ (SpotlightManager*)spotlightManagerWithBrowserState:
+    (ios::ChromeBrowserState*)browserState;
+
+// Resyncs the index if necessary
+- (void)resyncIndex;
+
+@end
+
+#endif  // IOS_CHROME_APP_SPOTLIGHT_SPOTLIGHT_MANAGER_H_
diff --git a/ios/chrome/app/spotlight/spotlight_manager.mm b/ios/chrome/app/spotlight/spotlight_manager.mm
new file mode 100644
index 0000000..22c83bd
--- /dev/null
+++ b/ios/chrome/app/spotlight/spotlight_manager.mm
@@ -0,0 +1,68 @@
+// Copyright 2015 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/spotlight/spotlight_manager.h"
+
+#include "base/logging.h"
+#include "base/mac/scoped_nsobject.h"
+#include "ios/chrome/app/spotlight/actions_spotlight_manager.h"
+#include "ios/chrome/app/spotlight/bookmarks_spotlight_manager.h"
+#include "ios/chrome/app/spotlight/topsites_spotlight_manager.h"
+#include "ios/chrome/browser/experimental_flags.h"
+
+// Called from the BrowserBookmarkModelBridge from C++ -> ObjC.
+@interface SpotlightManager ()<BookmarkUpdatedDelegate> {
+  base::scoped_nsobject<BookmarksSpotlightManager> _bookmarkManager;
+  base::scoped_nsobject<TopSitesSpotlightManager> _topSitesManager;
+  base::scoped_nsobject<ActionsSpotlightManager> _actionsManager;
+}
+
+- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
+    NS_DESIGNATED_INITIALIZER;
+
+- (instancetype)init NS_UNAVAILABLE;
+@end
+
+@implementation SpotlightManager
+
++ (SpotlightManager*)spotlightManagerWithBrowserState:
+    (ios::ChromeBrowserState*)browserState {
+  DCHECK(spotlight::IsSpotlightAvailable());
+  return [[[SpotlightManager alloc] initWithBrowserState:browserState]
+      autorelease];
+}
+
+- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState {
+  self = [super init];
+  if (self) {
+    _topSitesManager.reset([[TopSitesSpotlightManager
+        topSitesSpotlightManagerWithBrowserState:browserState] retain]);
+    _bookmarkManager.reset([[BookmarksSpotlightManager
+        bookmarksSpotlightManagerWithBrowserState:browserState] retain]);
+    [_bookmarkManager setDelegate:self];
+    _actionsManager.reset(
+        [[ActionsSpotlightManager actionsSpotlightManager] retain]);
+  }
+  return self;
+}
+
+- (instancetype)init {
+  NOTREACHED();
+  return nil;
+}
+
+- (void)dealloc {
+  [super dealloc];
+}
+
+- (void)resyncIndex {
+  [_bookmarkManager reindexBookmarksIfNeeded];
+  [_actionsManager indexActions];
+}
+
+- (void)bookmarkUpdated {
+  [_topSitesManager reindexTopSites];
+}
+
+@end
diff --git a/ios/chrome/app/spotlight/spotlight_manager_unittest.mm b/ios/chrome/app/spotlight/spotlight_manager_unittest.mm
new file mode 100644
index 0000000..52e8569
--- /dev/null
+++ b/ios/chrome/app/spotlight/spotlight_manager_unittest.mm
@@ -0,0 +1,204 @@
+// Copyright 2015 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.
+
+#include <memory>
+
+#import <CoreSpotlight/CoreSpotlight.h>
+#import <Foundation/Foundation.h>
+
+#include "base/location.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/message_loop/message_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/bookmarks/test/bookmark_test_helpers.h"
+#include "components/bookmarks/test/test_bookmark_client.h"
+#include "components/favicon/core/favicon_client.h"
+#include "components/favicon/core/favicon_service.h"
+#include "components/favicon/core/large_icon_service.h"
+#include "components/favicon_base/fallback_icon_style.h"
+#import "ios/chrome/app/spotlight/bookmarks_spotlight_manager.h"
+#import "ios/chrome/app/spotlight/spotlight_manager.h"
+#import "ios/chrome/app/spotlight/spotlight_util.h"
+#include "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
+#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
+#include "ios/public/provider/chrome/browser/spotlight/spotlight_provider.h"
+#import "net/base/mac/url_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/gtest_mac.h"
+
+const char kDummyIconUrl[] = "http://www.example.com/touch_icon.png";
+
+favicon_base::FaviconRawBitmapResult CreateTestBitmap(int w, int h) {
+  favicon_base::FaviconRawBitmapResult result;
+  result.expired = false;
+
+  CGRect rect = CGRectMake(0, 0, w, h);
+  UIGraphicsBeginImageContext(rect.size);
+  CGContextRef context = UIGraphicsGetCurrentContext();
+  CGContextFillRect(context, rect);
+  UIImage* favicon = UIGraphicsGetImageFromCurrentImageContext();
+  UIGraphicsEndImageContext();
+
+  NSData* png = UIImagePNGRepresentation(favicon);
+  scoped_refptr<base::RefCountedBytes> data(new base::RefCountedBytes(
+      static_cast<const unsigned char*>([png bytes]), [png length]));
+
+  result.bitmap_data = data;
+  result.pixel_size = gfx::Size(w, h);
+  result.icon_url = GURL(kDummyIconUrl);
+  result.icon_type = favicon_base::TOUCH_ICON;
+  CHECK(result.is_valid());
+  return result;
+}
+
+// A mock FaviconService that emits pre-programmed response.
+class MockFaviconService : public favicon::FaviconService {
+ public:
+  MockFaviconService() : FaviconService(nullptr, nullptr) {}
+
+  ~MockFaviconService() override {}
+
+  base::CancelableTaskTracker::TaskId GetLargestRawFaviconForPageURL(
+      const GURL& page_url,
+      const std::vector<int>& icon_types,
+      int minimum_size_in_pixels,
+      const favicon_base::FaviconRawBitmapCallback& callback,
+      base::CancelableTaskTracker* tracker) override {
+    favicon_base::FaviconRawBitmapResult mock_result = CreateTestBitmap(24, 24);
+    return tracker->PostTask(base::ThreadTaskRunnerHandle::Get().get(),
+                             FROM_HERE, base::Bind(callback, mock_result));
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockFaviconService);
+};
+
+// This class provides access to LargeIconService internals, using the current
+// thread's task runner for testing.
+class TestLargeIconService : public favicon::LargeIconService {
+ public:
+  explicit TestLargeIconService(MockFaviconService* mock_favicon_service)
+      : LargeIconService(mock_favicon_service,
+                         base::ThreadTaskRunnerHandle::Get()) {}
+  ~TestLargeIconService() override {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TestLargeIconService);
+};
+
+class SpotlightManagerTest : public testing::Test {
+ protected:
+  SpotlightManagerTest() {
+    mock_favicon_service_.reset(new MockFaviconService());
+    large_icon_service_.reset(
+        new TestLargeIconService(mock_favicon_service_.get()));
+    model_ = bookmarks::TestBookmarkClient::CreateModel();
+    mock_favicon_service_.reset(new MockFaviconService());
+    large_icon_service_.reset(
+        new TestLargeIconService(mock_favicon_service_.get()));
+    bookmarksSpotlightManager_.reset([[BookmarksSpotlightManager alloc]
+        initWithLargeIconService:large_icon_service_.get()
+                   bookmarkModel:model_.get()]);
+  }
+
+  base::MessageLoop loop_;
+  std::unique_ptr<MockFaviconService> mock_favicon_service_;
+  std::unique_ptr<TestLargeIconService> large_icon_service_;
+  base::CancelableTaskTracker cancelable_task_tracker_;
+  std::unique_ptr<bookmarks::BookmarkModel> model_;
+  base::scoped_nsobject<BookmarksSpotlightManager> bookmarksSpotlightManager_;
+};
+
+TEST_F(SpotlightManagerTest, testSpotlightID) {
+  // Creating CSSearchableItem requires Spotlight to be available on the device.
+  if (!spotlight::IsSpotlightAvailable())
+    return;
+  GURL url("http://url.com");
+  NSString* spotlightId =
+      [bookmarksSpotlightManager_ spotlightIDForURL:url title:@"title"];
+  NSString* expectedSpotlightId =
+      [NSString stringWithFormat:@"%@.9c6b643df2a0c990",
+                                 spotlight::StringFromSpotlightDomain(
+                                     spotlight::DOMAIN_BOOKMARKS)];
+  EXPECT_NSEQ(spotlightId, expectedSpotlightId);
+}
+
+TEST_F(SpotlightManagerTest, testParentKeywordsForNode) {
+  const bookmarks::BookmarkNode* root = model_->bookmark_bar_node();
+  static const std::string model_string("a 1:[ b c ] d 2:[ 21:[ e ] f g ] h");
+  bookmarks::test::AddNodesFromModelString(model_.get(), root, model_string);
+  const bookmarks::BookmarkNode* eNode =
+      root->GetChild(3)->GetChild(0)->GetChild(0);
+  base::scoped_nsobject<NSMutableArray> keywords([[NSMutableArray alloc] init]);
+  [bookmarksSpotlightManager_ getParentKeywordsForNode:eNode inArray:keywords];
+  EXPECT_EQ([keywords count], 2u);
+  EXPECT_TRUE([[keywords objectAtIndex:0] isEqualToString:@"21"]);
+  EXPECT_TRUE([[keywords objectAtIndex:1] isEqualToString:@"2"]);
+}
+
+TEST_F(SpotlightManagerTest, testBookmarksCreateSpotlightItemsWithUrl) {
+  // Creating CSSearchableItem requires Spotlight to be available on the device.
+  if (!spotlight::IsSpotlightAvailable())
+    return;
+
+  const bookmarks::BookmarkNode* root = model_->bookmark_bar_node();
+  static const std::string model_string("a 1:[ b c ] d 2:[ 21:[ e ] f g ] h");
+  bookmarks::test::AddNodesFromModelString(model_.get(), root, model_string);
+  const bookmarks::BookmarkNode* eNode =
+      root->GetChild(3)->GetChild(0)->GetChild(0);
+
+  NSString* spotlightID = [bookmarksSpotlightManager_
+      spotlightIDForURL:eNode->url()
+                  title:base::SysUTF16ToNSString(eNode->GetTitle())];
+  base::scoped_nsobject<NSMutableArray> keywords([[NSMutableArray alloc] init]);
+  [bookmarksSpotlightManager_ getParentKeywordsForNode:eNode inArray:keywords];
+  NSArray* items = [bookmarksSpotlightManager_
+      spotlightItemsWithURL:eNode->url()
+                    favicon:nil
+               defaultTitle:base::SysUTF16ToNSString(eNode->GetTitle())];
+  EXPECT_TRUE([items count] == 1);
+  CSSearchableItem* item = [items objectAtIndex:0];
+  EXPECT_NSEQ([item uniqueIdentifier], spotlightID);
+  EXPECT_NSEQ([[item attributeSet] title], @"e");
+  EXPECT_NSEQ([[[item attributeSet] URL] absoluteString], @"http://e.com/");
+  [bookmarksSpotlightManager_ addKeywords:keywords.get() toSearchableItem:item];
+  // We use the set intersection to verify that the item from the Spotlight
+  // manager
+  // contains all the newly added Keywords.
+  NSMutableSet* spotlightManagerKeywords =
+      [NSMutableSet setWithArray:[[item attributeSet] keywords]];
+  NSSet* testModelKeywords = [NSSet setWithArray:keywords];
+  [spotlightManagerKeywords intersectSet:testModelKeywords];
+  EXPECT_NSEQ(spotlightManagerKeywords, testModelKeywords);
+}
+
+TEST_F(SpotlightManagerTest, testDefaultKeywordsExist) {
+  // Creating CSSearchableItem requires Spotlight to be available on the device.
+  if (!spotlight::IsSpotlightAvailable())
+    return;
+
+  const bookmarks::BookmarkNode* root = model_->bookmark_bar_node();
+  static const std::string model_string("a 1:[ b c ] d 2:[ 21:[ e ] f g ] h");
+  bookmarks::test::AddNodesFromModelString(model_.get(), root, model_string);
+  const bookmarks::BookmarkNode* aNode = root->GetChild(0);
+  NSArray* items = [bookmarksSpotlightManager_
+      spotlightItemsWithURL:aNode->url()
+                    favicon:nil
+               defaultTitle:base::SysUTF16ToNSString(aNode->GetTitle())];
+  EXPECT_TRUE([items count] == 1);
+  CSSearchableItem* item = [items objectAtIndex:0];
+  NSSet* spotlightManagerKeywords =
+      [NSSet setWithArray:[[item attributeSet] keywords]];
+  EXPECT_TRUE([spotlightManagerKeywords count] > 0);
+  // Check static/hardcoded keywords exist
+  NSSet* hardCodedKeywordsSet =
+      [NSSet setWithArray:ios::GetChromeBrowserProvider()
+                              ->GetSpotlightProvider()
+                              ->GetAdditionalKeywords()];
+  EXPECT_TRUE([hardCodedKeywordsSet isSubsetOfSet:spotlightManagerKeywords]);
+}
diff --git a/ios/chrome/app/spotlight/spotlight_util.h b/ios/chrome/app/spotlight/spotlight_util.h
new file mode 100644
index 0000000..c35592e
--- /dev/null
+++ b/ios/chrome/app/spotlight/spotlight_util.h
@@ -0,0 +1,75 @@
+// Copyright 2015 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.
+
+#ifndef IOS_CHROME_APP_SPOTLIGHT_SPOTLIGHT_UTIL_H_
+#define IOS_CHROME_APP_SPOTLIGHT_SPOTLIGHT_UTIL_H_
+
+#import <UIKit/UIKit.h>
+
+typedef void (^BlockWithError)(NSError*);
+typedef void (^BlockWithNSURL)(NSURL*);
+
+namespace spotlight {
+
+// This enum is used for Histogram. Domains should not be removed or reordered
+// and this enum should be kept synced with histograms.xml.
+// DOMAIN_UNKNOWN may be reported if Spotlight is not synced with chrome and
+// a domain has been removed since last indexation (should not happen in stable
+// channel).
+enum Domain {
+  DOMAIN_UNKNOWN = 0,
+  DOMAIN_BOOKMARKS = 1,
+  DOMAIN_TOPSITES = 2,
+  DOMAIN_ACTIONS = 3,
+  DOMAIN_COUNT
+};
+
+// The key of a custom attribute containing the item ID so the item is
+// searchable using CSSearchQuery.
+NSString* GetSpotlightCustomAttributeItemID();
+
+// NSUserDefaults key of entry containing date of the latest bookmarks indexing.
+extern const char kSpotlightLastIndexingDateKey[];
+
+// The current version of the Spotlight index format.
+// Change this value if there are change int the information indexed in
+// Spotlight. This will force reindexation on next startup.
+// Value is stored in |kSpotlightLastIndexingVersionKey|.
+extern const int kCurrentSpotlightIndexVersion;
+
+// NSUserDefault key of entry containing Chrome version of the latest bookmarks
+// indexing.
+extern const char kSpotlightLastIndexingVersionKey[];
+
+// Utility methods deleting nodes in Spotlight index. Will be retried in case of
+// failure as required by Apple documentation.
+void DeleteSearchableDomainItems(Domain domain, BlockWithError callback);
+void DeleteItemsWithIdentifiers(NSArray* items, BlockWithError callback);
+void ClearAllSpotlightEntries(BlockWithError callback);
+
+// Converts the spotlight::Domain enum to Spotlight domain string
+NSString* StringFromSpotlightDomain(Domain domain);
+
+// Converts the Spotlight domain string to spotlight::Domain enum.
+Domain SpotlightDomainFromString(NSString* domain);
+
+// Returns whether Spotlight is available on the device. Must be tested before
+// calling other methods of this class.
+bool IsSpotlightAvailable();
+
+// Clears the current Spotlight index of the device. Method is static to allow
+// clearing the index without instantiating SpotlightManager.
+// This method must not be called if |isSpotlightAvailable| returns NO.
+// This method is asynchronous and can fail. Completion is called with a
+// parameter indicating if the deletion was a success.
+void ClearSpotlightIndexWithCompletion(BlockWithError completion);
+
+// Finds the Spoglight itemID and calls |completion| with the corresponding URL.
+// Calls |completion| with nil if none was found.
+// |completion| is called on the Spotlight Thread.
+void GetURLForSpotlightItemID(NSString* itemID, BlockWithNSURL completion);
+
+}  // namespace spotlight
+
+#endif  // IOS_CHROME_APP_SPOTLIGHT_SPOTLIGHT_UTIL_H_
diff --git a/ios/chrome/app/spotlight/spotlight_util.mm b/ios/chrome/app/spotlight/spotlight_util.mm
new file mode 100644
index 0000000..93b24168d
--- /dev/null
+++ b/ios/chrome/app/spotlight/spotlight_util.mm
@@ -0,0 +1,216 @@
+// Copyright 2015 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/spotlight/spotlight_util.h"
+
+#import <CoreSpotlight/CoreSpotlight.h>
+
+#include "base/metrics/histogram.h"
+#include "base/strings/sys_string_conversions.h"
+#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
+#include "ios/public/provider/chrome/browser/spotlight/spotlight_provider.h"
+#include "url/gurl.h"
+
+namespace {
+// This enum is used for Histogram. Items should not be removed or reordered and
+// this enum should be kept synced with histograms.xml.
+// The three states correspond to:
+// - SPOTLIGHT_UNSUPPORTED: Framework CoreSpotlight is not found and
+// [CSSearchableIndex class] returns nil.
+// - SPOTLIGHT_UNAVAILABLE: Framework is loaded but [CSSearchableIndex
+// isIndexingAvailable] return NO. Note: It is unclear if this state is
+// reachable (I could not find configuration where CoreSpotlight was loaded but
+// [CSSearchableIndex isIndexingAvailable] returned NO.
+// - SPOTLIGHT_AVAILABLE: Framework is loaded and [CSSearchableIndex
+// isIndexingAvailable] returns YES. Note: This does not mean the actual
+// indexing will happen. If the user disables Spotlight in the system settings,
+// [CSSearchableIndex isIndexingAvailable] still returns YES.
+enum Availability {
+  SPOTLIGHT_UNSUPPORTED = 0,
+  SPOTLIGHT_UNAVAILABLE,
+  SPOTLIGHT_AVAILABLE,
+  SPOTLIGHT_AVAILABILITY_COUNT
+};
+
+// Documentation says that failed deletion should be retried. Set a maximum
+// value to avoid infinite loop.
+const int kMaxDeletionAttempts = 5;
+
+// Execute blockName block with up to retryCount retries on error. Execute
+// callback when done.
+void DoWithRetry(BlockWithError callback,
+                 NSUInteger retryCount,
+                 void (^blockName)(BlockWithError error)) {
+  BlockWithError retryCallback = ^(NSError* error) {
+    if (error && retryCount > 0) {
+      DoWithRetry(callback, retryCount - 1, blockName);
+    } else {
+      if (callback) {
+        callback(error);
+      }
+    }
+  };
+  blockName(retryCallback);
+}
+
+// Execute blockName block with up to kMaxDeletionAttempts retries on error.
+// Execute callback when done.
+void DoWithRetry(BlockWithError completion,
+                 void (^blockName)(BlockWithError error)) {
+  DoWithRetry(completion, kMaxDeletionAttempts, blockName);
+}
+
+}  // namespace
+
+namespace spotlight {
+
+// NSUserDefaults key of entry containing date of the latest bookmarks indexing.
+const char kSpotlightLastIndexingDateKey[] = "SpotlightLastIndexingDate";
+
+// NSUserDefault key of entry containing Chrome version of the latest bookmarks
+// indexing.
+const char kSpotlightLastIndexingVersionKey[] = "SpotlightLastIndexingVersion";
+
+// The current version of the Spotlight index format.
+// Change this value if there are change int the information indexed in
+// Spotlight. This will force reindexation on next startup.
+// Value is stored in |kSpotlightLastIndexingVersionKey|.
+const int kCurrentSpotlightIndexVersion = 2;
+
+Domain SpotlightDomainFromString(NSString* domain) {
+  SpotlightProvider* provider =
+      ios::GetChromeBrowserProvider()->GetSpotlightProvider();
+  if ([domain hasPrefix:[provider->GetBookmarkDomain()
+                            stringByAppendingString:@"."]]) {
+    return DOMAIN_BOOKMARKS;
+  } else if ([domain hasPrefix:[provider->GetTopSitesDomain()
+                                   stringByAppendingString:@"."]]) {
+    return DOMAIN_TOPSITES;
+  } else if ([domain hasPrefix:[provider->GetActionsDomain()
+                                   stringByAppendingString:@"."]]) {
+    return DOMAIN_ACTIONS;
+  }
+  // On normal flow, it is not possible to reach this point. When testing the
+  // app, it may be possible though if the app is downgraded.
+  NOTREACHED();
+  return DOMAIN_UNKNOWN;
+}
+
+NSString* StringFromSpotlightDomain(Domain domain) {
+  SpotlightProvider* provider =
+      ios::GetChromeBrowserProvider()->GetSpotlightProvider();
+  switch (domain) {
+    case DOMAIN_BOOKMARKS:
+      return provider->GetBookmarkDomain();
+    case DOMAIN_TOPSITES:
+      return provider->GetTopSitesDomain();
+    case DOMAIN_ACTIONS:
+      return provider->GetActionsDomain();
+    default:
+      // On normal flow, it is not possible to reach this point. When testing
+      // the app, it may be possible though if the app is downgraded.
+      NOTREACHED();
+      return nil;
+  }
+}
+
+void DeleteItemsWithIdentifiers(NSArray* items, BlockWithError callback) {
+  void (^deleteItems)(BlockWithError) = ^(BlockWithError errorBlock) {
+    [[CSSearchableIndex defaultSearchableIndex]
+        deleteSearchableItemsWithIdentifiers:items
+                           completionHandler:errorBlock];
+  };
+
+  DoWithRetry(callback, deleteItems);
+}
+
+void DeleteSearchableDomainItems(Domain domain, BlockWithError callback) {
+  void (^deleteItems)(BlockWithError) = ^(BlockWithError errorBlock) {
+    [[CSSearchableIndex defaultSearchableIndex]
+        deleteSearchableItemsWithDomainIdentifiers:@[ StringFromSpotlightDomain(
+                                                       domain) ]
+                                 completionHandler:errorBlock];
+  };
+
+  DoWithRetry(callback, deleteItems);
+}
+
+void ClearAllSpotlightEntries(BlockWithError callback) {
+  BlockWithError augmentedCallback = ^(NSError* error) {
+    [[NSUserDefaults standardUserDefaults]
+        removeObjectForKey:@(kSpotlightLastIndexingDateKey)];
+    if (callback) {
+      callback(error);
+    }
+  };
+
+  void (^deleteItems)(BlockWithError) = ^(BlockWithError errorBlock) {
+    [[CSSearchableIndex defaultSearchableIndex]
+        deleteAllSearchableItemsWithCompletionHandler:errorBlock];
+  };
+
+  DoWithRetry(augmentedCallback, deleteItems);
+}
+
+bool IsSpotlightAvailable() {
+  bool provided = ios::GetChromeBrowserProvider()
+                      ->GetSpotlightProvider()
+                      ->IsSpotlightEnabled();
+  if (!provided) {
+    // The product does not support Spotlight, do not go further.
+    return false;
+  }
+  bool loaded = !![CSSearchableIndex class];
+  bool available = loaded && [CSSearchableIndex isIndexingAvailable];
+  static dispatch_once_t once;
+  dispatch_once(&once, ^{
+    Availability availability = SPOTLIGHT_UNSUPPORTED;
+    if (loaded) {
+      availability = SPOTLIGHT_UNAVAILABLE;
+    }
+    if (available) {
+      availability = SPOTLIGHT_AVAILABLE;
+    }
+    UMA_HISTOGRAM_ENUMERATION("IOS.Spotlight.Availability", availability,
+                              SPOTLIGHT_AVAILABILITY_COUNT);
+  });
+  return loaded && available;
+}
+
+void ClearSpotlightIndexWithCompletion(BlockWithError completion) {
+  DCHECK(IsSpotlightAvailable());
+  ClearAllSpotlightEntries(completion);
+}
+
+NSString* GetSpotlightCustomAttributeItemID() {
+  return ios::GetChromeBrowserProvider()
+      ->GetSpotlightProvider()
+      ->GetCustomAttributeItemID();
+}
+
+void GetURLForSpotlightItemID(NSString* itemID, BlockWithNSURL completion) {
+  NSString* queryString =
+      [NSString stringWithFormat:@"%@ == \"%@\"",
+                                 GetSpotlightCustomAttributeItemID(), itemID];
+
+  CSSearchQuery* query =
+      [[CSSearchQuery alloc] initWithQueryString:queryString
+                                      attributes:@[ @"contentURL" ]];
+
+  [query setFoundItemsHandler:^(NSArray<CSSearchableItem*>* items) {
+    if ([items count] == 1) {
+      CSSearchableItem* searchableItem = [items objectAtIndex:0];
+      if (searchableItem) {
+        completion([[searchableItem attributeSet] contentURL]);
+        return;
+      }
+    }
+    completion(nil);
+
+  }];
+
+  [query start];
+}
+
+}  // namespace spotlight
diff --git a/ios/chrome/app/spotlight/topsites_spotlight_manager.h b/ios/chrome/app/spotlight/topsites_spotlight_manager.h
new file mode 100644
index 0000000..02f010e0
--- /dev/null
+++ b/ios/chrome/app/spotlight/topsites_spotlight_manager.h
@@ -0,0 +1,30 @@
+// Copyright 2015 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.
+
+#ifndef IOS_CHROME_APP_SPOTLIGHT_TOPSITES_SPOTLIGHT_MANAGER_H_
+#define IOS_CHROME_APP_SPOTLIGHT_TOPSITES_SPOTLIGHT_MANAGER_H_
+
+#import "ios/chrome/app/spotlight/base_spotlight_manager.h"
+
+namespace ios {
+class ChromeBrowserState;
+}  // namespace ios
+
+// This spotlight manager handles indexing of sites shown on the NTP. Because of
+// privacy concerns, only sites shown on the NTP are indexed; therefore, this
+// manager mirrors the functionality seen in google_landing_controller. It uses
+// suggestions (most likely) as a data source if the user is logged in and top
+// sites otherwise.
+
+@interface TopSitesSpotlightManager : BaseSpotlightManager
+
++ (TopSitesSpotlightManager*)topSitesSpotlightManagerWithBrowserState:
+    (ios::ChromeBrowserState*)browserState;
+
+// Reindexes all top sites, batching reindexes by 1 second.
+- (void)reindexTopSites;
+
+@end
+
+#endif  // IOS_CHROME_APP_SPOTLIGHT_TOPSITES_SPOTLIGHT_MANAGER_H_
diff --git a/ios/chrome/app/spotlight/topsites_spotlight_manager.mm b/ios/chrome/app/spotlight/topsites_spotlight_manager.mm
new file mode 100644
index 0000000..33bb2315
--- /dev/null
+++ b/ios/chrome/app/spotlight/topsites_spotlight_manager.mm
@@ -0,0 +1,288 @@
+// Copyright 2015 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/spotlight/topsites_spotlight_manager.h"
+
+#include <memory>
+
+#include "base/ios/weak_nsobject.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/sys_string_conversions.h"
+#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/browser_sync/profile_sync_service.h"
+#include "components/history/core/browser/history_types.h"
+#include "components/history/core/browser/top_sites.h"
+#include "components/history/core/browser/top_sites_observer.h"
+#include "components/suggestions/suggestions_service.h"
+#include "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
+#include "ios/chrome/browser/favicon/ios_chrome_large_icon_service_factory.h"
+#include "ios/chrome/browser/history/top_sites_factory.h"
+#include "ios/chrome/browser/suggestions/suggestions_service_factory.h"
+#include "ios/chrome/browser/sync/ios_chrome_profile_sync_service_factory.h"
+#include "ios/chrome/browser/sync/sync_observer_bridge.h"
+#include "ios/chrome/browser/ui/ntp/google_landing_controller.h"
+
+class SpotlightTopSitesBridge;
+class SpotlightTopSitesCallbackBridge;
+class SpotlightSuggestionsBridge;
+
+@interface TopSitesSpotlightManager ()<SyncObserverModelBridge> {
+  // Bridge to register for top sites changes. It's important that this instance
+  // variable is released before the _topSite one.
+  std::unique_ptr<SpotlightTopSitesBridge> _topSitesBridge;
+
+  // Bridge to register for top sites callbacks.
+  std::unique_ptr<SpotlightTopSitesCallbackBridge> _topSitesCallbackBridge;
+
+  // Bridge to register for sync changes.
+  std::unique_ptr<SyncObserverBridge> sync_observer_bridge_;
+
+  // Bridge to register for suggestion changes.
+  std::unique_ptr<SpotlightSuggestionsBridge> _suggestionsBridge;
+
+  bookmarks::BookmarkModel* _bookmarkModel;             // weak
+  suggestions::SuggestionsService* _suggestionService;  // weak
+  browser_sync::ProfileSyncService* _syncService;       // weak
+
+  scoped_refptr<history::TopSites> _topSites;
+  std::unique_ptr<
+      suggestions::SuggestionsService::ResponseCallbackList::Subscription>
+      _suggestionsServiceResponseSubscription;
+
+  // Indicates if a reindex is pending. Reindexes made by calling the external
+  // reindexTopSites method are executed at most every second.
+  BOOL _isReindexPending;
+}
+@property(nonatomic, readonly) scoped_refptr<history::TopSites> topSites;
+
+- (instancetype)
+initWithLargeIconService:(favicon::LargeIconService*)largeIconService
+                topSites:(scoped_refptr<history::TopSites>)topSites
+           bookmarkModel:(bookmarks::BookmarkModel*)bookmarkModel
+      profileSyncService:(browser_sync::ProfileSyncService*)syncService
+      suggestionsService:(suggestions::SuggestionsService*)suggestionsService;
+
+// Updates all indexed top sites from appropriate source, within limit of number
+// of sites shown on NTP.
+- (void)updateAllTopSitesSpotlightItems;
+// Adds all top sites from appropriate source, within limit of number of sites
+// shown on NTP.
+- (void)addAllTopSitesSpotlightItems;
+// Adds all top sites from TopSites source (most visited sites on device),
+// within limit of number of sites shown on NTP.
+- (void)addAllLocalTopSitesItems;
+// Adds all top sites from Suggestions source (server-based), within limit of
+// number of sites shown on NTP.
+- (void)addAllSuggestionsTopSitesItems;
+// Callback for topsites mostvisited, adds sites to spotlight.
+- (void)onMostVisitedURLsAvailable:
+    (const history::MostVisitedURLList&)top_sites;
+// Callback for suggestions, adds sites to spotlight.
+- (void)onSuggestionsProfileAvailable:
+    (const suggestions::SuggestionsProfile&)suggestions_profile;
+
+@end
+
+class SpotlightTopSitesCallbackBridge
+    : public base::SupportsWeakPtr<SpotlightTopSitesCallbackBridge> {
+ public:
+  explicit SpotlightTopSitesCallbackBridge(TopSitesSpotlightManager* owner)
+      : owner_(owner) {}
+
+  SpotlightTopSitesCallbackBridge() {}
+
+  void OnMostVisitedURLsAvailable(const history::MostVisitedURLList& data) {
+    [owner_ onMostVisitedURLsAvailable:data];
+  }
+
+ private:
+  __unsafe_unretained TopSitesSpotlightManager* owner_;  // weak, owns us
+};
+
+class SpotlightTopSitesBridge : public history::TopSitesObserver {
+ public:
+  explicit SpotlightTopSitesBridge(TopSitesSpotlightManager* owner)
+      : owner_(owner) {
+    owner.topSites->AddObserver(this);
+  };
+
+  ~SpotlightTopSitesBridge() override {
+    owner_.topSites->RemoveObserver(this);
+  };
+
+  void TopSitesLoaded(history::TopSites* top_sites) override {}
+
+  void TopSitesChanged(history::TopSites* top_sites,
+                       ChangeReason change_reason) override {
+    [owner_ updateAllTopSitesSpotlightItems];
+  }
+
+ private:
+  __unsafe_unretained TopSitesSpotlightManager* owner_;  // weak
+};
+
+class SpotlightSuggestionsBridge
+    : public base::SupportsWeakPtr<SpotlightSuggestionsBridge> {
+ public:
+  explicit SpotlightSuggestionsBridge(TopSitesSpotlightManager* owner)
+      : owner_(owner) {}
+
+  SpotlightSuggestionsBridge() {}
+
+  void OnSuggestionsProfileAvailable(
+      const suggestions::SuggestionsProfile& suggestions_profile) {
+    [owner_ onSuggestionsProfileAvailable:suggestions_profile];
+  }
+
+ private:
+  __unsafe_unretained TopSitesSpotlightManager* owner_;  // weak, owns us
+};
+
+@implementation TopSitesSpotlightManager
+@synthesize topSites = _topSites;
+
++ (TopSitesSpotlightManager*)topSitesSpotlightManagerWithBrowserState:
+    (ios::ChromeBrowserState*)browserState {
+  return [[[TopSitesSpotlightManager alloc]
+      initWithLargeIconService:IOSChromeLargeIconServiceFactory::
+                                   GetForBrowserState(browserState)
+                      topSites:ios::TopSitesFactory::GetForBrowserState(
+                                   browserState)
+                 bookmarkModel:ios::BookmarkModelFactory::GetForBrowserState(
+                                   browserState)
+            profileSyncService:IOSChromeProfileSyncServiceFactory::
+                                   GetForBrowserState(browserState)
+            suggestionsService:suggestions::SuggestionsServiceFactory::
+                                   GetForBrowserState(browserState)]
+      autorelease];
+}
+
+- (instancetype)
+initWithLargeIconService:(favicon::LargeIconService*)largeIconService
+                topSites:(scoped_refptr<history::TopSites>)topSites
+           bookmarkModel:(bookmarks::BookmarkModel*)bookmarkModel
+      profileSyncService:(browser_sync::ProfileSyncService*)syncService
+      suggestionsService:(suggestions::SuggestionsService*)suggestionsService {
+  self = [super initWithLargeIconService:largeIconService
+                                  domain:spotlight::DOMAIN_TOPSITES];
+  if (self) {
+    _topSites = topSites;
+    _topSitesBridge.reset(new SpotlightTopSitesBridge(self));
+    _topSitesCallbackBridge.reset(new SpotlightTopSitesCallbackBridge(self));
+    _bookmarkModel = bookmarkModel;
+    _isReindexPending = false;
+    if (syncService && suggestionsService) {
+      _suggestionsBridge.reset(new SpotlightSuggestionsBridge(self));
+      _syncService = syncService;
+      _suggestionService = suggestionsService;
+      _suggestionsServiceResponseSubscription = _suggestionService->AddCallback(
+          base::Bind(&SpotlightSuggestionsBridge::OnSuggestionsProfileAvailable,
+                     _suggestionsBridge->AsWeakPtr()));
+      sync_observer_bridge_.reset(new SyncObserverBridge(self, syncService));
+    }
+  }
+  return self;
+}
+
+- (void)updateAllTopSitesSpotlightItems {
+  base::WeakNSObject<TopSitesSpotlightManager> weakSelf(self);
+  [self clearAllSpotlightItems:^(NSError* error) {
+    dispatch_async(dispatch_get_main_queue(), ^{
+      [weakSelf addAllTopSitesSpotlightItems];
+    });
+  }];
+}
+
+- (void)addAllTopSitesSpotlightItems {
+  if (_suggestionService) {
+    [self addAllSuggestionsTopSitesItems];
+  } else {
+    [self addAllLocalTopSitesItems];
+  }
+}
+
+- (void)addAllLocalTopSitesItems {
+  _topSites->GetMostVisitedURLs(
+      base::Bind(&SpotlightTopSitesCallbackBridge::OnMostVisitedURLsAvailable,
+                 _topSitesCallbackBridge->AsWeakPtr()),
+      true);
+}
+
+- (void)addAllSuggestionsTopSitesItems {
+  if (!_suggestionService->FetchSuggestionsData()) {
+    [self addAllLocalTopSitesItems];
+  }
+}
+
+- (BOOL)isURLBookmarked:(const GURL&)URL {
+  if (!_bookmarkModel->loaded())
+    return NO;
+
+  std::vector<const bookmarks::BookmarkNode*> nodes;
+  _bookmarkModel->GetNodesByURL(URL, &nodes);
+  return nodes.size() > 0;
+}
+
+- (void)onMostVisitedURLsAvailable:
+    (const history::MostVisitedURLList&)top_sites {
+  NSUInteger sitesToIndex =
+      MIN(top_sites.size(), [GoogleLandingController maxSitesShown]);
+  for (size_t i = 0; i < sitesToIndex; i++) {
+    const GURL& URL = top_sites[i].url;
+
+    // Check if the item is bookmarked, in which case it is already indexed.
+    if ([self isURLBookmarked:URL]) {
+      continue;
+    }
+
+    [self refreshItemsWithURL:URL
+                        title:base::SysUTF16ToNSString(top_sites[i].title)];
+  }
+}
+
+- (void)onSuggestionsProfileAvailable:
+    (const suggestions::SuggestionsProfile&)suggestionsProfile {
+  size_t size = suggestionsProfile.suggestions_size();
+  if (size) {
+    NSUInteger sitesToIndex =
+        MIN(size, [GoogleLandingController maxSitesShown]);
+    for (size_t i = 0; i < sitesToIndex; i++) {
+      const suggestions::ChromeSuggestion& suggestion =
+          suggestionsProfile.suggestions(i);
+      GURL URL = GURL(suggestion.url());
+      // Check if the item is bookmarked, in which case it is already indexed.
+      if ([self isURLBookmarked:URL]) {
+        continue;
+      }
+
+      std::string title = suggestion.title();
+      [self refreshItemsWithURL:URL title:base::SysUTF8ToNSString(title)];
+    }
+  } else {
+    [self addAllLocalTopSitesItems];
+  }
+}
+
+- (void)reindexTopSites {
+  if (_isReindexPending) {
+    return;
+  }
+  _isReindexPending = true;
+  base::WeakNSObject<TopSitesSpotlightManager> weakSelf(self);
+  dispatch_after(
+      dispatch_time(DISPATCH_TIME_NOW, static_cast<int64_t>(1 * NSEC_PER_SEC)),
+      dispatch_get_main_queue(), ^{
+        [weakSelf updateAllTopSitesSpotlightItems];
+        weakSelf.get()->_isReindexPending = false;
+      });
+}
+
+#pragma mark -
+#pragma mark SyncObserverModelBridge
+
+- (void)onSyncStateChanged {
+  [self updateAllTopSitesSpotlightItems];
+}
+
+@end
diff --git a/ios/chrome/app/startup/chrome_main_starter.h b/ios/chrome/app/startup/chrome_main_starter.h
new file mode 100644
index 0000000..7ad09bd
--- /dev/null
+++ b/ios/chrome/app/startup/chrome_main_starter.h
@@ -0,0 +1,22 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_STARTUP_CHROME_MAIN_STARTER_H_
+#define IOS_CHROME_APP_STARTUP_CHROME_MAIN_STARTER_H_
+
+#include <memory>
+
+#import <UIKit/UIKit.h>
+
+class IOSChromeMain;
+
+@interface ChromeMainStarter : NSObject
+
+// Setup and initialization that is needed by common Chrome code. This should be
+// called only once during app startup (or shortly after launch). Returns the
+// object that drives startup/shutdown logic.
++ (std::unique_ptr<IOSChromeMain>)startChromeMain;
+
+@end
+#endif  // IOS_CHROME_APP_STARTUP_CHROME_MAIN_STARTER_H_
diff --git a/ios/chrome/app/startup/chrome_main_starter.mm b/ios/chrome/app/startup/chrome_main_starter.mm
new file mode 100644
index 0000000..6a074aa
--- /dev/null
+++ b/ios/chrome/app/startup/chrome_main_starter.mm
@@ -0,0 +1,16 @@
+// Copyright 2016 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.
+
+#include "ios/chrome/app/startup/chrome_main_starter.h"
+
+#include "base/memory/ptr_util.h"
+#include "ios/chrome/app/startup/ios_chrome_main.h"
+
+@implementation ChromeMainStarter
+
++ (std::unique_ptr<IOSChromeMain>)startChromeMain {
+  return base::MakeUnique<IOSChromeMain>();
+}
+
+@end
diff --git a/ios/chrome/app/startup/client_registration.h b/ios/chrome/app/startup/client_registration.h
new file mode 100644
index 0000000..fc3bc5b
--- /dev/null
+++ b/ios/chrome/app/startup/client_registration.h
@@ -0,0 +1,16 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_STARTUP_CLIENT_REGISTRATION_H_
+#define IOS_CHROME_APP_STARTUP_CLIENT_REGISTRATION_H_
+
+#import <UIKit/UIKit.h>
+
+@interface ClientRegistration : NSObject
+
+// Registers all clients.  Must be called before any web code is called.
++ (void)registerClients;
+
+@end
+#endif  // IOS_CHROME_APP_STARTUP_CLIENT_REGISTRATION_H_
diff --git a/ios/chrome/app/startup/client_registration.mm b/ios/chrome/app/startup/client_registration.mm
new file mode 100644
index 0000000..9cdcb7f
--- /dev/null
+++ b/ios/chrome/app/startup/client_registration.mm
@@ -0,0 +1,17 @@
+// Copyright 2016 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.
+
+#include "ios/chrome/app/startup/client_registration.h"
+
+#import "base/mac/scoped_nsobject.h"
+#import "ios/chrome/browser/web/chrome_web_client.h"
+#import "ios/web/public/web_client.h"
+
+@implementation ClientRegistration
+
++ (void)registerClients {
+  web::SetWebClient(new ChromeWebClient());
+}
+
+@end
diff --git a/ios/chrome/app/startup/ios_chrome_main.h b/ios/chrome/app/startup/ios_chrome_main.h
new file mode 100644
index 0000000..27c0618e
--- /dev/null
+++ b/ios/chrome/app/startup/ios_chrome_main.h
@@ -0,0 +1,41 @@
+// 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.
+
+#ifndef IOS_CHROME_APP_STARTUP_IOS_CHROME_MAIN_H_
+#define IOS_CHROME_APP_STARTUP_IOS_CHROME_MAIN_H_
+
+#include <memory>
+
+#include "ios/chrome/app/startup/ios_chrome_main_delegate.h"
+
+namespace base {
+class Time;
+}
+
+namespace web {
+class WebMainRunner;
+}
+
+// Encapsulates any setup and initialization that is needed by common
+// Chrome code.  A single instance of this object should be created during app
+// startup (or shortly after launch), and clients must ensure that this object
+// is not destroyed while Chrome code is still on the stack.
+class IOSChromeMain {
+ public:
+  IOSChromeMain();
+  ~IOSChromeMain();
+
+  // The time main() starts.  Only call from main().
+  static void InitStartTime();
+
+  // Returns the time that main() started.  Used for performance tests.
+  // InitStartTime() must has been called before.
+  static const base::Time& StartTime();
+
+ private:
+  IOSChromeMainDelegate main_delegate_;
+  std::unique_ptr<web::WebMainRunner> web_main_runner_;
+};
+
+#endif  // IOS_CHROME_APP_STARTUP_IOS_CHROME_MAIN_H_
diff --git a/ios/chrome/app/startup/ios_chrome_main.mm b/ios/chrome/app/startup/ios_chrome_main.mm
new file mode 100644
index 0000000..bbd9cbba
--- /dev/null
+++ b/ios/chrome/app/startup/ios_chrome_main.mm
@@ -0,0 +1,66 @@
+// 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.
+
+#include "ios/chrome/app/startup/ios_chrome_main.h"
+
+#import <UIKit/UIKit.h>
+
+#include <vector>
+
+#include "base/logging.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/time/time.h"
+#include "ios/web/public/app/web_main_runner.h"
+
+namespace {
+base::Time* g_start_time;
+}  // namespace
+
+IOSChromeMain::IOSChromeMain() {
+  web_main_runner_.reset(web::WebMainRunner::Create());
+
+  web::WebMainParams main_params = web::WebMainParams(&main_delegate_);
+// Copy NSProcessInfo arguments into WebMainParams in debug only, since
+// command line should be meaningless outside of developer builds.
+#if !defined(NDEBUG)
+  NSArray* arguments = [[NSProcessInfo processInfo] arguments];
+  main_params.argc = [arguments count];
+  const char* argv[main_params.argc];
+  std::vector<std::string> argv_store;
+
+  // Avoid using std::vector::push_back (or any other method that could cause
+  // the vector to grow) as this will cause the std::string to be copied or
+  // moved (depends on the C++ implementation) which may invalidates the pointer
+  // returned by std::string::c_str(). Even if the strings are moved, this may
+  // cause garbage if std::string uses optimisation for small strings (by
+  // returning pointer to the object internals in that case).
+  argv_store.resize([arguments count]);
+  for (NSUInteger i = 0; i < [arguments count]; i++) {
+    argv_store[i] = base::SysNSStringToUTF8([arguments objectAtIndex:i]);
+    argv[i] = argv_store[i].c_str();
+  }
+  main_params.argv = argv;
+#endif
+
+  // Chrome registers an AtExitManager in main in order to initialize breakpad
+  // early, so prevent a second registration by WebMainRunner.
+  main_params.register_exit_manager = false;
+  web_main_runner_->Initialize(main_params);
+}
+
+IOSChromeMain::~IOSChromeMain() {
+  web_main_runner_->ShutDown();
+}
+
+// static
+void IOSChromeMain::InitStartTime() {
+  DCHECK(!g_start_time);
+  g_start_time = new base::Time(base::Time::Now());
+}
+
+// static
+const base::Time& IOSChromeMain::StartTime() {
+  CHECK(g_start_time);
+  return *g_start_time;
+}
diff --git a/ios/chrome/app/startup/ios_chrome_main_delegate.h b/ios/chrome/app/startup/ios_chrome_main_delegate.h
new file mode 100644
index 0000000..6455c3c
--- /dev/null
+++ b/ios/chrome/app/startup/ios_chrome_main_delegate.h
@@ -0,0 +1,24 @@
+// Copyright 2015 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.
+
+#ifndef IOS_CHROME_APP_STARTUP_IOS_CHROME_MAIN_DELEGATE_H_
+#define IOS_CHROME_APP_STARTUP_IOS_CHROME_MAIN_DELEGATE_H_
+
+#include "base/macros.h"
+#include "ios/web/public/app/web_main_delegate.h"
+
+// Implementation of WebMainDelegate for Chrome on iOS.
+class IOSChromeMainDelegate : public web::WebMainDelegate {
+ public:
+  IOSChromeMainDelegate();
+  ~IOSChromeMainDelegate() override;
+
+ protected:
+  // web::WebMainDelegate implementation:
+  void BasicStartupComplete() override;
+
+  DISALLOW_COPY_AND_ASSIGN(IOSChromeMainDelegate);
+};
+
+#endif  // IOS_CHROME_APP_STARTUP_IOS_CHROME_MAIN_DELEGATE_H_
diff --git a/ios/chrome/app/startup/ios_chrome_main_delegate.mm b/ios/chrome/app/startup/ios_chrome_main_delegate.mm
new file mode 100644
index 0000000..2ee0e0b
--- /dev/null
+++ b/ios/chrome/app/startup/ios_chrome_main_delegate.mm
@@ -0,0 +1,30 @@
+// Copyright (c) 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.
+
+#include "ios/chrome/app/startup/ios_chrome_main_delegate.h"
+
+#include "base/logging.h"
+#include "ios/chrome/browser/chrome_paths.h"
+#include "third_party/skia/include/core/SkGraphics.h"
+
+IOSChromeMainDelegate::IOSChromeMainDelegate() {}
+
+IOSChromeMainDelegate::~IOSChromeMainDelegate() {}
+
+void IOSChromeMainDelegate::BasicStartupComplete() {
+  // Initialize Skia. On desktop this is made by content::BrowserMainRunnerImpl,
+  // however web does not have a dependency on skia, so it is done as part of
+  // Chrome initialisation on iOS.
+  SkGraphics::Init();
+
+  // Initialize the Chrome path provider.
+  ios::RegisterPathProvider();
+
+  // Upstream wires up log file handling here based on flags; for now that's
+  // not supported, and this is called just to handle vlog levels and patterns.
+  // If redirecting to a file is ever needed, add it here (see
+  // logging_chrome.cc for example code).
+  logging::LoggingSettings log_settings;
+  logging::InitLogging(log_settings);
+}
diff --git a/ios/chrome/app/startup/network_stack_setup.h b/ios/chrome/app/startup/network_stack_setup.h
new file mode 100644
index 0000000..8bdfbe0c7
--- /dev/null
+++ b/ios/chrome/app/startup/network_stack_setup.h
@@ -0,0 +1,30 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_STARTUP_NETWORK_STACK_SETUP_H_
+#define IOS_CHROME_APP_STARTUP_NETWORK_STACK_SETUP_H_
+
+#import <UIKit/UIKit.h>
+
+#import <memory>
+
+namespace web {
+class RequestTrackerFactoryImpl;
+class WebHTTPProtocolHandlerDelegate;
+}  // namespace web
+
+@interface NetworkStackSetup : NSObject
+
+// Sets up the network stack: protocol handler and cache.
+// TODO(crbug.com/585700): Remove the first parameter.
++ (void)setUpChromeNetworkStack:
+            (std::unique_ptr<web::RequestTrackerFactoryImpl>*)
+                requestTrackerFactory
+    httpProtocolHandlerDelegate:
+        (std::unique_ptr<web::WebHTTPProtocolHandlerDelegate>*)
+            httpProtocolHandlerDelegate;
+
+@end
+
+#endif  // IOS_CHROME_APP_STARTUP_NETWORK_STACK_SETUP_H_
diff --git a/ios/chrome/app/startup/network_stack_setup.mm b/ios/chrome/app/startup/network_stack_setup.mm
new file mode 100644
index 0000000..533edee
--- /dev/null
+++ b/ios/chrome/app/startup/network_stack_setup.mm
@@ -0,0 +1,45 @@
+// Copyright 2016 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.
+
+#include "ios/chrome/app/startup/network_stack_setup.h"
+
+#include "base/memory/ptr_util.h"
+#include "ios/chrome/browser/application_context.h"
+#include "ios/chrome/browser/chrome_url_constants.h"
+#import "ios/net/empty_nsurlcache.h"
+#include "ios/web/net/request_tracker_factory_impl.h"
+#include "ios/web/net/web_http_protocol_handler_delegate.h"
+
+@implementation NetworkStackSetup
+
++ (void)setUpChromeNetworkStack:
+            (std::unique_ptr<web::RequestTrackerFactoryImpl>*)
+                requestTrackerFactory
+    httpProtocolHandlerDelegate:
+        (std::unique_ptr<web::WebHTTPProtocolHandlerDelegate>*)
+            httpProtocolHandlerDelegate {
+  // Disable the default cache.
+  [NSURLCache setSharedURLCache:[EmptyNSURLCache emptyNSURLCache]];
+
+  // Configuration for the HTTP protocol handler.
+  //  TODO(crbug.com/585700): Remove this code.
+  *httpProtocolHandlerDelegate =
+      base::MakeUnique<web::WebHTTPProtocolHandlerDelegate>(
+          GetApplicationContext()->GetSystemURLRequestContext());
+  net::HTTPProtocolHandlerDelegate::SetInstance(
+      httpProtocolHandlerDelegate->get());
+
+  // Register the chrome http protocol handler to replace the default one.
+  // TODO(crbug.com/665036): Move the network stack initialization to the web
+  // layer.
+  BOOL success = [NSURLProtocol registerClass:[CRNHTTPProtocolHandler class]];
+  DCHECK(success);
+  *requestTrackerFactory =
+      base::MakeUnique<web::RequestTrackerFactoryImpl>(kChromeUIScheme);
+  net::RequestTracker::SetRequestTrackerFactory(requestTrackerFactory->get());
+
+  DCHECK(success);
+}
+
+@end
diff --git a/ios/chrome/app/startup/provider_registration.h b/ios/chrome/app/startup/provider_registration.h
new file mode 100644
index 0000000..6fff846
--- /dev/null
+++ b/ios/chrome/app/startup/provider_registration.h
@@ -0,0 +1,15 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_STARTUP_PROVIDER_REGISTRATION_H_
+#define IOS_CHROME_APP_STARTUP_PROVIDER_REGISTRATION_H_
+
+#import <UIKit/UIKit.h>
+
+@interface ProviderRegistration : NSObject
+
+// Registers all providers. Must be called before any Chromium code is called.
++ (void)registerProviders;
+@end
+#endif  // IOS_CHROME_APP_STARTUP_PROVIDER_REGISTRATION_H_
diff --git a/ios/chrome/app/startup/provider_registration.mm b/ios/chrome/app/startup/provider_registration.mm
new file mode 100644
index 0000000..c34b8b45
--- /dev/null
+++ b/ios/chrome/app/startup/provider_registration.mm
@@ -0,0 +1,21 @@
+// Copyright 2016 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.
+
+#include "ios/chrome/app/startup/provider_registration.h"
+
+#include "ios/chrome/browser/web/web_controller_provider_factory_impl.h"
+#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
+
+@implementation ProviderRegistration
+
++ (void)registerProviders {
+  std::unique_ptr<ios::ChromeBrowserProvider> provider =
+      ios::CreateChromeBrowserProvider();
+
+  // Leak the providers.
+  ios::SetChromeBrowserProvider(provider.release());
+  ios::SetWebControllerProviderFactory(new WebControllerProviderFactoryImpl());
+}
+
+@end
diff --git a/ios/chrome/app/startup/register_experimental_settings.h b/ios/chrome/app/startup/register_experimental_settings.h
new file mode 100644
index 0000000..725023a
--- /dev/null
+++ b/ios/chrome/app/startup/register_experimental_settings.h
@@ -0,0 +1,20 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_STARTUP_REGISTER_EXPERIMENTAL_SETTINGS_H_
+#define IOS_CHROME_APP_STARTUP_REGISTER_EXPERIMENTAL_SETTINGS_H_
+
+#import <UIKit/UIKit.h>
+
+@interface RegisterExperimentalSettings : NSObject
+
+// Registers default values for experimental settings (Application Preferences).
+// Experimental keys removed from the Settings.bundle are automatically removed
+// from the UserDefaults.
++ (void)registerExperimentalSettingsWithUserDefaults:
+            (NSUserDefaults*)userDefaults
+                                              bundle:(NSBundle*)bundle;
+
+@end
+#endif  // IOS_CHROME_APP_STARTUP_REGISTER_EXPERIMENTAL_SETTINGS_H_
diff --git a/ios/chrome/app/startup/register_experimental_settings.mm b/ios/chrome/app/startup/register_experimental_settings.mm
new file mode 100644
index 0000000..be14221
--- /dev/null
+++ b/ios/chrome/app/startup/register_experimental_settings.mm
@@ -0,0 +1,128 @@
+// Copyright 2016 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.
+
+#include "ios/chrome/app/startup/register_experimental_settings.h"
+
+#include "base/logging.h"
+#include "base/mac/bundle_locations.h"
+#import "base/mac/scoped_nsobject.h"
+#include "base/strings/sys_string_conversions.h"
+
+namespace {
+// Key in the UserDefaults for the Experimental Keys.
+NSString* kExperimentalKeysKey = @"ExperimentalKeys";
+
+// Returns YES if a setting value is equivalent to not having the setting at
+// all. This must always be true for default values, otherwise the experimental
+// settings will have different default behaviors in stable channel (where the
+// bundle isn't present).
+BOOL IsDefaultSettingValueValid(id value) {
+  if (!value)
+    return YES;
+  if ([value isKindOfClass:[NSNumber class]])
+    return [value intValue] == 0;
+  if ([value isKindOfClass:[NSString class]])
+    return [value length] == 0;
+  // Add support for other types as necessary.
+  NOTREACHED() << "Unhandled value type "
+               << base::SysNSStringToUTF8(NSStringFromClass([value class]));
+  return NO;
+}
+}  // namespace
+
+@interface RegisterExperimentalSettings ()
+// Registers all the default values for a single settings file and returns
+// all the registered keys.
++ (NSArray*)registerExperimentalSettingsForFile:(NSString*)filepath
+                                   userDefaults:(NSUserDefaults*)userDefaults;
+@end
+
+@implementation RegisterExperimentalSettings
+
++ (void)registerExperimentalSettingsWithUserDefaults:
+            (NSUserDefaults*)userDefaults
+                                              bundle:(NSBundle*)bundle {
+  // Save the current app version in user defaults.
+  NSDictionary* infoDictionary = [bundle infoDictionary];
+  NSString* version = [infoDictionary objectForKey:@"CFBundleVersion"];
+  [userDefaults setObject:version forKey:@"Version"];
+
+  NSString* bundlePath = [bundle bundlePath];
+  NSString* settingsFilepath =
+      [bundlePath stringByAppendingPathComponent:@"Settings.bundle"];
+  NSArray* settingsContent =
+      [[NSFileManager defaultManager] contentsOfDirectoryAtPath:settingsFilepath
+                                                          error:NULL];
+  base::scoped_nsobject<NSMutableArray> currentExpKeys(
+      [[NSMutableArray alloc] init]);
+
+  for (NSString* filename in settingsContent) {
+    // Only plist files are preferences definition.
+    if ([[filename pathExtension] isEqualToString:@"plist"]) {
+      NSString* filepath =
+          [settingsFilepath stringByAppendingPathComponent:filename];
+      NSArray* registeredKeys =
+          [self registerExperimentalSettingsForFile:filepath
+                                       userDefaults:userDefaults];
+      [currentExpKeys addObjectsFromArray:registeredKeys];
+    }
+  }
+
+  // Remove all keys that are no longer used.
+  NSArray* expKeys = [userDefaults arrayForKey:kExperimentalKeysKey];
+  NSMutableSet* expKeysSet = [NSMutableSet setWithArray:expKeys];
+  NSSet* currentExpKeysSet = [NSSet setWithArray:currentExpKeys];
+  [expKeysSet minusSet:currentExpKeysSet];
+  for (NSString* key in expKeysSet) {
+    [userDefaults removeObjectForKey:key];
+  }
+
+  if ([currentExpKeys count] > 0) {
+    [userDefaults setObject:currentExpKeys forKey:kExperimentalKeysKey];
+  } else {
+    [userDefaults removeObjectForKey:kExperimentalKeysKey];
+  }
+}
+
++ (NSArray*)registerExperimentalSettingsForFile:(NSString*)filepath
+                                   userDefaults:(NSUserDefaults*)userDefaults {
+  NSMutableArray* registeredKeys = [NSMutableArray array];
+
+  NSDictionary* rootDictionary =
+      [NSDictionary dictionaryWithContentsOfFile:filepath];
+  // Array with all the preference specifiers. The plist is composed of many
+  // Preference specifiers; one for each preference row in the settings
+  // panel.
+  NSArray* preferencesArray =
+      [rootDictionary objectForKey:@"PreferenceSpecifiers"];
+
+  // Scan thru all the preferences in the plist file.
+  for (NSDictionary* preferenceSpecifier in preferencesArray) {
+    NSString* keyValue = [preferenceSpecifier objectForKey:@"Key"];
+    if (!keyValue)
+      continue;
+
+    id defaultValue = [preferenceSpecifier objectForKey:@"DefaultValue"];
+    // Within the app, the default for all experimental prefs is nil (matching
+    // the behavior of Stable channel, where there is no settings bundle). To
+    // make mistakes obvious, fail if someone tries to set any actual value as
+    // the default.
+    DCHECK(IsDefaultSettingValueValid(defaultValue))
+        << "'" << base::SysNSStringToUTF8([defaultValue description])
+        << "' is not a valid default value for "
+        << base::SysNSStringToUTF8(keyValue);
+
+    [registeredKeys addObject:keyValue];
+
+    // If a default value is set, normalize it to nil.
+    id currentValue = [userDefaults objectForKey:keyValue];
+    if (currentValue &&
+        (!defaultValue || [currentValue isEqual:defaultValue])) {
+      [userDefaults removeObjectForKey:keyValue];
+    }
+  }
+  return registeredKeys;
+}
+
+@end
diff --git a/ios/chrome/app/startup/setup_debugging.h b/ios/chrome/app/startup/setup_debugging.h
new file mode 100644
index 0000000..9bb770f
--- /dev/null
+++ b/ios/chrome/app/startup/setup_debugging.h
@@ -0,0 +1,16 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_STARTUP_SETUP_DEBUGGING_H_
+#define IOS_CHROME_APP_STARTUP_SETUP_DEBUGGING_H_
+
+#import <UIKit/UIKit.h>
+
+@interface SetupDebugging : NSObject
+
+// Set up any debug-only or simulator-only settings.
++ (void)setUpDebuggingOptions;
+
+@end
+#endif  // IOS_CHROME_APP_STARTUP_SETUP_DEBUGGING_H_
diff --git a/ios/chrome/app/startup/setup_debugging.mm b/ios/chrome/app/startup/setup_debugging.mm
new file mode 100644
index 0000000..b54a123d
--- /dev/null
+++ b/ios/chrome/app/startup/setup_debugging.mm
@@ -0,0 +1,20 @@
+// Copyright 2016 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.
+
+#include "ios/chrome/app/startup/setup_debugging.h"
+
+#include "base/logging.h"
+#include "components/crash/core/common/objc_zombie.h"
+
+@implementation SetupDebugging
+
++ (void)setUpDebuggingOptions {
+// Enable the zombie treadmill on simulator builds.
+// TODO(crbug.com/663390): Consider enabling this on device builds too.
+#if TARGET_IPHONE_SIMULATOR
+  DCHECK(ObjcEvilDoers::ZombieEnable(true, 10000));
+#endif
+}
+
+@end
diff --git a/ios/chrome/app/startup_tasks.h b/ios/chrome/app/startup_tasks.h
new file mode 100644
index 0000000..d9cc177
--- /dev/null
+++ b/ios/chrome/app/startup_tasks.h
@@ -0,0 +1,28 @@
+// Copyright 2016 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.
+
+#ifndef IOS_CHROME_APP_STARTUP_TASKS_H_
+#define IOS_CHROME_APP_STARTUP_TASKS_H_
+
+#import <Foundation/Foundation.h>
+
+namespace ios {
+class ChromeBrowserState;
+}  // namespace ios.
+
+// Class handling all startup tasks.
+@interface StartupTasks : NSObject
+
+// Asynchronously finishes the browser state initialization by scheduling
+// |performDeferredInitializationForBrowserState:|.
+- (void)scheduleDeferredBrowserStateInitialization:
+    (ios::ChromeBrowserState*)browserState;
+// Starts Omaha and, if first run, sets install time.  For official builds only.
+- (void)initializeOmaha;
+// Registers to receive UIApplicationWillResignActiveNotification.
+- (void)registerForApplicationWillResignActiveNotification;
+
+@end
+
+#endif  // IOS_CHROME_APP_STARTUP_TASKS_H_
diff --git a/ios/chrome/app/startup_tasks.mm b/ios/chrome/app/startup_tasks.mm
new file mode 100644
index 0000000..bbf847c
--- /dev/null
+++ b/ios/chrome/app/startup_tasks.mm
@@ -0,0 +1,107 @@
+// Copyright 2016 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/startup_tasks.h"
+
+#import <MediaPlayer/MediaPlayer.h>
+
+#import "base/ios/weak_nsobject.h"
+#import "base/mac/bind_objc_block.h"
+#include "components/bookmarks/browser/startup_task_runner_service.h"
+#include "components/reading_list/core/reading_list_switches.h"
+#import "ios/chrome/app/deferred_initialization_runner.h"
+#include "ios/chrome/app/tests_hook.h"
+#include "ios/chrome/browser/application_context.h"
+#include "ios/chrome/browser/bookmarks/startup_task_runner_service_factory.h"
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#include "ios/chrome/browser/ios_chrome_io_thread.h"
+#import "ios/chrome/browser/omaha/omaha_service.h"
+#include "ios/chrome/browser/reading_list/reading_list_download_service.h"
+#include "ios/chrome/browser/reading_list/reading_list_download_service_factory.h"
+#import "ios/chrome/browser/ui/main/browser_view_information.h"
+#import "ios/chrome/browser/upgrade/upgrade_center.h"
+
+namespace {
+// Constants for deferred initilization of the profile start-up task runners.
+NSString* const kStartProfileStartupTaskRunners =
+    @"StartProfileStartupTaskRunners";
+}  // namespace
+
+@interface StartupTasks ()
+
+// Performs browser state initialization tasks that don't need to happen
+// synchronously at startup.
+- (void)performDeferredInitializationForBrowserState:
+    (ios::ChromeBrowserState*)browserState;
+// Called when UIApplicationWillResignActiveNotification is received.
+- (void)applicationWillResignActiveNotification:(NSNotification*)notification;
+
+@end
+
+@implementation StartupTasks
+
+#pragma mark - Public methods.
+
+- (void)scheduleDeferredBrowserStateInitialization:
+    (ios::ChromeBrowserState*)browserState {
+  DCHECK(browserState);
+  // Schedule the start of the profile deferred task runners.
+  [[DeferredInitializationRunner sharedInstance]
+      enqueueBlockNamed:kStartProfileStartupTaskRunners
+                  block:^{
+                    [self performDeferredInitializationForBrowserState:
+                              browserState];
+                  }];
+}
+
+- (void)initializeOmaha {
+#if defined(GOOGLE_CHROME_BUILD)
+  if (tests_hook::DisableUpdateService())
+    return;
+  // Start omaha service. We only do this on official builds.
+  OmahaService::Start(
+      GetApplicationContext()
+          ->GetIOSChromeIOThread()
+          ->system_url_request_context_getter(),
+      base::BindBlock(^(const UpgradeRecommendedDetails& details) {
+        [[UpgradeCenter sharedInstance] upgradeNotificationDidOccur:details];
+      }));
+#endif  // defined(GOOGLE_CHROME_BUILD)
+}
+
+- (void)registerForApplicationWillResignActiveNotification {
+  [[NSNotificationCenter defaultCenter]
+      addObserver:self
+         selector:@selector(applicationWillResignActiveNotification:)
+             name:UIApplicationWillResignActiveNotification
+           object:nil];
+}
+
+#pragma mark - Private methods.
+
+- (void)performDeferredInitializationForBrowserState:
+    (ios::ChromeBrowserState*)browserState {
+  ios::StartupTaskRunnerServiceFactory::GetForBrowserState(browserState)
+      ->StartDeferredTaskRunners();
+  if (reading_list::switches::IsReadingListEnabled()) {
+    ReadingListDownloadServiceFactory::GetForBrowserState(browserState)
+        ->Initialize();
+  }
+}
+
+- (void)applicationWillResignActiveNotification:(NSNotification*)notification {
+  // If the control center is displaying now-playing information from Chrome,
+  // attempt to clear it so that the URL is no longer shown
+  // (crbug.com/475820). The now-playing information will not be cleared if
+  // it's from a different app.
+  NSDictionary* nowPlayingInfo =
+      [[MPNowPlayingInfoCenter defaultCenter] nowPlayingInfo];
+  if (nowPlayingInfo[MPMediaItemPropertyTitle]) {
+    // No need to clear playing info if media is being played but there is no
+    // way to check if video or audio is playing in web view.
+    [[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:nil];
+  }
+}
+
+@end
diff --git a/ios/chrome/app/steps/README.md b/ios/chrome/app/steps/README.md
new file mode 100644
index 0000000..80dffe1
--- /dev/null
+++ b/ios/chrome/app/steps/README.md
@@ -0,0 +1,6 @@
+# Application Steps
+
+-----
+**The files in this directory are only used in the new architecture for iOS
+Chrome**
+-----
diff --git a/ios/chrome/app/steps/launch_to_background.h b/ios/chrome/app/steps/launch_to_background.h
new file mode 100644
index 0000000..8b63e2b
--- /dev/null
+++ b/ios/chrome/app/steps/launch_to_background.h
@@ -0,0 +1,38 @@
+// Copyright 2016 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.
+
+// ======                        New Architecture                         =====
+// =         This code is only used in the new iOS Chrome architecture.       =
+// ============================================================================
+
+#ifndef IOS_CHROME_APP_STEPS_LAUNCH_TO_BACKGROUND_H_
+#define IOS_CHROME_APP_STEPS_LAUNCH_TO_BACKGROUND_H_
+
+#import <Foundation/Foundation.h>
+
+#include "ios/chrome/app/application_step.h"
+
+// This file includes ApplicationStep objects that perform the steps that bring
+// the application up to the background phase of launching.
+
+// Sets up the user defaults and application bundle.
+//  Pre:  Application phase is APPLICATION_BASIC.
+//  Post: Application phase is (still) APPLICATION_BASIC.
+@interface SetupBundleAndUserDefaults : NSObject<ApplicationStep>
+@end
+
+// Starts the ChromeMain object and sets the global WebClient object.
+//  Pre:  Application phase is APPLICATION_BASIC.
+//  Post: Application phase is APPLICATION_BACKGROUNDED.
+@interface StartChromeMain : NSObject<ApplicationStep>
+@end
+
+// Sets the browserState property in the ApplicationState.
+//  Pre:  Application phase is APPLICATION_BACKGROUNDED and a browser state
+//        manager is available.
+//  Post: Application phase is (still) APPLICATION_BACKGROUNDED.
+@interface SetBrowserState : NSObject<ApplicationStep>
+@end
+
+#endif  // IOS_CHROME_APP_STEPS_LAUNCH_TO_BACKGROUND_H_
diff --git a/ios/chrome/app/steps/launch_to_background.mm b/ios/chrome/app/steps/launch_to_background.mm
new file mode 100644
index 0000000..9a1fed6
--- /dev/null
+++ b/ios/chrome/app/steps/launch_to_background.mm
@@ -0,0 +1,131 @@
+// Copyright 2016 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.
+
+// ======                        New Architecture                         =====
+// =         This code is only used in the new iOS Chrome architecture.       =
+// ============================================================================
+
+#import "ios/chrome/app/steps/launch_to_background.h"
+
+#include <memory>
+
+#include "base/mac/bundle_locations.h"
+#include "base/mac/foundation_util.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/supports_user_data.h"
+#include "ios/chrome/app/application_state.h"
+#include "ios/chrome/app/startup/ios_chrome_main.h"
+#include "ios/chrome/app/startup/register_experimental_settings.h"
+#include "ios/chrome/browser/application_context.h"
+#include "ios/chrome/browser/browser_state/chrome_browser_state_manager.h"
+#include "ios/chrome/browser/web/chrome_web_client.h"
+#include "ios/web/public/web_client.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+// Step that removes a previously-stored IOSChromeMain instance.
+@interface StopChromeMain : NSObject<ApplicationStep>
+@end
+
+namespace {
+
+// A SupportsUserData::Data struct to store an IOSChromeMain object and preserve
+// its lifetime (by storing it in an ApplicationState's |state| property) beyond
+// the running time of the -StartChromeMain step.
+class ChromeMainContainer : public base::SupportsUserData::Data {
+ public:
+  explicit ChromeMainContainer(std::unique_ptr<IOSChromeMain> chrome_main)
+      : main_(std::move(chrome_main)) {}
+
+ private:
+  std::unique_ptr<IOSChromeMain> main_;
+};
+
+// Key for storing the ChromeMainContainer.
+const char kChromeMainKey[] = "chrome_main";
+
+}  // namespace
+
+@implementation SetupBundleAndUserDefaults
+
+- (BOOL)canRunInState:(ApplicationState*)state {
+  return state.phase == APPLICATION_BASIC;
+}
+
+- (void)runInState:(ApplicationState*)state {
+  NSBundle* baseBundle = base::mac::OuterBundle();
+  base::mac::SetBaseBundleID(
+      base::SysNSStringToUTF8([baseBundle bundleIdentifier]).c_str());
+
+  [RegisterExperimentalSettings
+      registerExperimentalSettingsWithUserDefaults:[NSUserDefaults
+                                                       standardUserDefaults]
+                                            bundle:base::mac::
+                                                       FrameworkBundle()];
+}
+
+@end
+
+@implementation StartChromeMain
+
+- (BOOL)canRunInState:(ApplicationState*)state {
+  return state.phase == APPLICATION_BASIC;
+}
+
+- (void)runInState:(ApplicationState*)state {
+  web::SetWebClient(new ChromeWebClient());
+
+  // Create and persist an IOSChromeMain instance.
+  state.persistentState->SetUserData(
+      kChromeMainKey,
+      new ChromeMainContainer(base::MakeUnique<IOSChromeMain>()));
+
+  // Add a step to the termination steps of |state| that will stop and remove
+  // the IOSChromeMain instance.
+  [[state terminationSteps] addObject:[[StopChromeMain alloc] init]];
+
+  state.phase = APPLICATION_BACKGROUNDED;
+}
+
+@end
+
+@implementation StopChromeMain
+
+- (BOOL)canRunInState:(ApplicationState*)state {
+  return state.phase == APPLICATION_TERMINATING;
+}
+
+- (void)runInState:(ApplicationState*)state {
+  // This should be the last step executed while the app is terminating.
+  if (state.terminationSteps.count) {
+    // There are other steps waiting to run, so add this to the end and return.
+    [state.terminationSteps addObject:self];
+    return;
+  }
+  state.persistentState->RemoveUserData(kChromeMainKey);
+}
+
+@end
+
+@implementation SetBrowserState
+
+- (BOOL)canRunInState:(ApplicationState*)state {
+  ApplicationContext* applicationContext = GetApplicationContext();
+  if (!applicationContext)
+    return NO;
+
+  return applicationContext->GetChromeBrowserStateManager() &&
+         state.phase == APPLICATION_BACKGROUNDED;
+}
+
+- (void)runInState:(ApplicationState*)state {
+  state.browserState = GetApplicationContext()
+                           ->GetChromeBrowserStateManager()
+                           ->GetLastUsedBrowserState();
+}
+
+@end
diff --git a/ios/chrome/app/steps/launch_to_basic.h b/ios/chrome/app/steps/launch_to_basic.h
new file mode 100644
index 0000000..af0ee775
--- /dev/null
+++ b/ios/chrome/app/steps/launch_to_basic.h
@@ -0,0 +1,25 @@
+// Copyright 2016 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.
+
+// ======                        New Architecture                         =====
+// =         This code is only used in the new iOS Chrome architecture.       =
+// ============================================================================
+
+#ifndef IOS_CHROME_APP_STEPS_LAUNCH_TO_BASIC_H_
+#define IOS_CHROME_APP_STEPS_LAUNCH_TO_BASIC_H_
+
+#import <Foundation/Foundation.h>
+
+#include "ios/chrome/app/application_step.h"
+
+// This file includes ApplicationStep objects that perform the very first steps
+// of application launch.
+
+// Initializes the application providers.
+//  Pre:  Application phase is APPLICATION_COLD.
+//  Post: Application phase is APPLICATION_BASIC.
+@interface ProviderInitializer : NSObject<ApplicationStep>
+@end
+
+#endif  // IOS_CHROME_APP_STEPS_LAUNCH_TO_BASIC_H_
diff --git a/ios/chrome/app/steps/launch_to_basic.mm b/ios/chrome/app/steps/launch_to_basic.mm
new file mode 100644
index 0000000..e69d65c
--- /dev/null
+++ b/ios/chrome/app/steps/launch_to_basic.mm
@@ -0,0 +1,30 @@
+// Copyright 2016 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.
+
+// ======                        New Architecture                         =====
+// =         This code is only used in the new iOS Chrome architecture.       =
+// ============================================================================
+
+#import "ios/chrome/app/steps/launch_to_basic.h"
+
+#include "ios/chrome/app/application_state.h"
+#include "ios/chrome/app/startup/provider_registration.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@implementation ProviderInitializer
+
+- (BOOL)canRunInState:(ApplicationState*)state {
+  return state.phase == APPLICATION_COLD;
+}
+
+- (void)runInState:(ApplicationState*)state {
+  [ProviderRegistration registerProviders];
+
+  state.phase = APPLICATION_BASIC;
+}
+
+@end
diff --git a/ios/chrome/app/steps/launch_to_foreground.h b/ios/chrome/app/steps/launch_to_foreground.h
new file mode 100644
index 0000000..4738131
--- /dev/null
+++ b/ios/chrome/app/steps/launch_to_foreground.h
@@ -0,0 +1,46 @@
+// Copyright 2016 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.
+
+// ======                        New Architecture                         =====
+// =         This code is only used in the new iOS Chrome architecture.       =
+// ============================================================================
+
+#ifndef IOS_CHROME_APP_STEPS_LAUNCH_TO_FOREGROUND_H_
+#define IOS_CHROME_APP_STEPS_LAUNCH_TO_FOREGROUND_H_
+
+#import <Foundation/Foundation.h>
+
+#include "ios/chrome/app/application_step.h"
+
+// This file includes ApplicationStep objects that perform the very first steps
+// of application launch.
+
+// Signals to the application context that the app is entering the foreground.
+//  Pre:  Application phase is APPLICATION_BACKGROUNDED.
+//  Post: Application phase is (still) APPLICATION_BACKGROUNDED.
+@interface BeginForegrounding : NSObject<ApplicationStep>
+@end
+
+// Initializes the browser state held by the application state, setting its
+// initial cookie policies and any other pre-foreground inialization.
+//  Pre:  Application phase is APPLICATION_BACKGROUNDED and the application
+//        state has a non-null browser state.
+//  Post: Application phase is (still) APPLICATION_BACKGROUNDED.
+@interface BrowserStateInitializer : NSObject<ApplicationStep>
+@end
+
+// Creates the main window and makes it key, but doesn't make it visible yet.
+//  Pre:  Application phase is APPLICATION_BACKGROUNDED.
+//  Post: Application phase is (still) APPLICATION_BACKGROUNDED.
+@interface PrepareForUI : NSObject<ApplicationStep>
+@end
+
+// Performs final foregrounding tasks.
+// Creates the main window and makes it key, but doesn't make it visible yet.
+//  Pre:  Application phase is APPLICATION_BACKGROUNDED and the main window is
+//        key.
+//  Post: Application phase is APPLICATION_FOREGROUNDED.
+@interface CompleteForegrounding : NSObject<ApplicationStep>
+@end
+#endif  // IOS_CHROME_APP_STEPS_LAUNCH_TO_FOREGROUND_H_
diff --git a/ios/chrome/app/steps/launch_to_foreground.mm b/ios/chrome/app/steps/launch_to_foreground.mm
new file mode 100644
index 0000000..9b9b357e
--- /dev/null
+++ b/ios/chrome/app/steps/launch_to_foreground.mm
@@ -0,0 +1,130 @@
+// Copyright 2016 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.
+
+// ======                        New Architecture                         =====
+// =         This code is only used in the new iOS Chrome architecture.       =
+// ============================================================================
+
+#import "ios/chrome/app/steps/launch_to_foreground.h"
+
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "ios/chrome/app/application_state.h"
+#include "ios/chrome/browser/application_context.h"
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#include "ios/chrome/browser/content_settings/host_content_settings_map_factory.h"
+#include "ios/net/cookies/cookie_store_ios.h"
+#include "ios/web/public/web_capabilities.h"
+#include "ios/web/public/web_thread.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+// Temporary class to trigger URL-opening events from a shake gesture.
+@interface ShakeCatchingWindow : UIWindow
+@end
+
+@implementation BeginForegrounding
+
+- (BOOL)canRunInState:(ApplicationState*)state {
+  return state.phase == APPLICATION_BACKGROUNDED;
+}
+
+- (void)runInState:(ApplicationState*)state {
+  GetApplicationContext()->OnAppEnterForeground();
+}
+
+@end
+
+@implementation BrowserStateInitializer
+
+- (BOOL)canRunInState:(ApplicationState*)state {
+  return state.browserState && state.phase == APPLICATION_BACKGROUNDED;
+}
+
+- (void)runInState:(ApplicationState*)state {
+  DCHECK(!state.browserState->IsOffTheRecord());
+  [self setInitialCookiesPolicy:state.browserState];
+}
+
+// Copied verbatim from MainController.
+- (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));
+}
+
+@end
+
+@implementation PrepareForUI
+
+- (BOOL)canRunInState:(ApplicationState*)state {
+  return state.phase == APPLICATION_BACKGROUNDED;
+}
+
+- (void)runInState:(ApplicationState*)state {
+  state.window =
+      [[ShakeCatchingWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
+  [state.window makeKeyWindow];
+}
+
+@end
+
+@implementation CompleteForegrounding
+- (BOOL)canRunInState:(ApplicationState*)state {
+  return state.window.keyWindow && state.phase == APPLICATION_BACKGROUNDED;
+}
+
+- (void)runInState:(ApplicationState*)state {
+  state.phase = APPLICATION_FOREGROUNDED;
+}
+
+@end
+
+@implementation ShakeCatchingWindow
+
+#pragma mark - UIResponder
+
+- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent*)event {
+  if (motion == UIEventSubtypeMotionShake) {
+    UIApplication* app = [UIApplication sharedApplication];
+    [app.delegate application:app
+                      openURL:[NSURL URLWithString:@"https://www.google.com"]
+                      options:@{}];
+  }
+  [super motionEnded:motion withEvent:event];
+}
+
+@end
diff --git a/ios/chrome/app/steps/tab_grid_coordinator+application_step.h b/ios/chrome/app/steps/tab_grid_coordinator+application_step.h
new file mode 100644
index 0000000..e7fdf90
--- /dev/null
+++ b/ios/chrome/app/steps/tab_grid_coordinator+application_step.h
@@ -0,0 +1,27 @@
+// Copyright 2016 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.
+
+// ======                        New Architecture                         =====
+// =         This code is only used in the new iOS Chrome architecture.       =
+// ============================================================================
+
+#ifndef IOS_CHROME_APP_STEPS_TAB_GRID_COORDINATOR_APPLICATION_STEP_H_
+#define IOS_CHROME_APP_STEPS_TAB_GRID_COORDINATOR_APPLICATION_STEP_H_
+
+#import <UIKit/UIKit.h>
+
+#include "ios/chrome/app/application_step.h"
+#import "ios/chrome/browser/ui/tab_grid/tab_grid_coordinator.h"
+
+// Category on TabGridCoordinator to allow it to act as an application step
+// and control the root UI for the application when is starts.
+// Creates the main window and makes it key, but doesn't make it visible yet.
+//  Pre:  Application phase is APPLICATION_FOREGROUNDED and the main window is
+//        key.
+//  Post: Application phase is (still) APPLICATION_FOREGROUNDED, the main window
+//        is visible and has a root view controller set.
+@interface TabGridCoordinator (ApplicationStep)<ApplicationStep>
+@end
+
+#endif  // IOS_CHROME_APP_STEPS_TAB_GRID_COORDINATOR_APPLICATION_STEP_H_
diff --git a/ios/chrome/app/steps/tab_grid_coordinator+application_step.mm b/ios/chrome/app/steps/tab_grid_coordinator+application_step.mm
new file mode 100644
index 0000000..03ff9da
--- /dev/null
+++ b/ios/chrome/app/steps/tab_grid_coordinator+application_step.mm
@@ -0,0 +1,80 @@
+// Copyright 2016 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.
+
+// ======                        New Architecture                         =====
+// =         This code is only used in the new iOS Chrome architecture.       =
+// ============================================================================
+
+#import "ios/chrome/app/steps/tab_grid_coordinator+application_step.h"
+
+#import "base/supports_user_data.h"
+#import "ios/chrome/app/application_state.h"
+#import "ios/chrome/browser/browser_coordinator+internal.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+// SupportsUserData storage container for a BrowserCoordinator object, which
+// keeps a strong reference to the object it is initialized with.
+class RootCoordinatorContainer : public base::SupportsUserData::Data {
+ public:
+  explicit RootCoordinatorContainer(BrowserCoordinator* coordinator)
+      : coordinator_(coordinator) {}
+
+ private:
+  // Mark this variable unused to avoid a compiler warning; the compiler
+  // doesn't have visibility into the ARC-injected retain/release.
+  BrowserCoordinator* __attribute__((unused)) coordinator_;
+};
+
+const char kRootCoordinatorContainerKey[] = "root_coordinator";
+}  // namespace
+
+@interface StopTabGridCoordinator : NSObject<ApplicationStep>
+@end
+
+@implementation TabGridCoordinator (ApplicationStep)
+
+- (BOOL)canRunInState:(ApplicationState*)state {
+  return [state.window isKeyWindow] && state.phase == APPLICATION_FOREGROUNDED;
+}
+
+- (void)runInState:(ApplicationState*)state {
+  self.browserState = state.browserState;
+  [self start];
+
+  state.window.rootViewController = self.viewController;
+
+  // Size the main view controller to fit the whole screen.
+  [self.viewController.view setFrame:state.window.bounds];
+
+  // Tell the application state to use this object to open URLs.
+  state.URLOpener = self;
+
+  // Show the window.
+  state.window.hidden = NO;
+
+  // Make sure this object stays alive.
+  state.persistentState->SetUserData(kRootCoordinatorContainerKey,
+                                     new RootCoordinatorContainer(self));
+
+  // Add a termination step to remove this object.
+  [[state terminationSteps] addObject:[[StopTabGridCoordinator alloc] init]];
+}
+
+@end
+
+@implementation StopTabGridCoordinator
+
+- (BOOL)canRunInState:(ApplicationState*)state {
+  return state.phase = APPLICATION_TERMINATING;
+}
+
+- (void)runInState:(ApplicationState*)state {
+  state.persistentState->RemoveUserData(kRootCoordinatorContainerKey);
+}
+
+@end
diff --git a/ios/chrome/browser/README.md b/ios/chrome/browser/README.md
new file mode 100644
index 0000000..ff049a4
--- /dev/null
+++ b/ios/chrome/browser/README.md
@@ -0,0 +1,10 @@
+# Browser
+
+-----
+**Some of the files in this directory are only used in the new iOS Chrome
+architecture:**
+
+* `browser_coordinator.h`, `.mm`, and `+internal.h`
+* `url_opening.h`
+
+-----
diff --git a/ios/chrome/browser/autofill/autofill_agent.h b/ios/chrome/browser/autofill/autofill_agent.h
new file mode 100644
index 0000000..797418ed
--- /dev/null
+++ b/ios/chrome/browser/autofill/autofill_agent.h
@@ -0,0 +1,64 @@
+// Copyright 2013 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.
+#ifndef IOS_CHROME_BROWSER_AUTOFILL_AUTOFILL_AGENT_H_
+#define IOS_CHROME_BROWSER_AUTOFILL_AUTOFILL_AGENT_H_
+
+#import <Foundation/Foundation.h>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "components/autofill/core/browser/autofill_metrics.h"
+#import "ios/chrome/browser/autofill/form_suggestion_provider.h"
+#import "ios/web/public/web_state/web_state_observer_bridge.h"
+
+namespace autofill {
+class AutofillPopupDelegate;
+struct FormData;
+class FormStructure;
+}
+
+namespace ios {
+class ChromeBrowserState;
+}
+
+namespace web {
+class WebState;
+}
+
+// Handles autofill form suggestions. Reads forms from the page, sends them to
+// AutofillManager for metrics and to retrieve suggestions, and fills forms in
+// response to user interaction with suggestions. This is the iOS counterpart
+// to the upstream class autofill::AutofillAgent.
+@interface AutofillAgent : NSObject<FormSuggestionProvider>
+
+@property(nonatomic, readonly) ios::ChromeBrowserState* browserState;
+
+// Designated initializer. Arguments |browserState| and |webState| should not be
+// null.
+- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
+                            webState:(web::WebState*)webState
+    NS_DESIGNATED_INITIALIZER;
+
+- (instancetype)init NS_UNAVAILABLE;
+
+// Callback by AutofillController when suggestions are ready.
+- (void)onSuggestionsReady:(NSArray*)suggestions
+             popupDelegate:
+                 (const base::WeakPtr<autofill::AutofillPopupDelegate>&)
+                     delegate;
+
+// The supplied data should be filled into the form.
+- (void)onFormDataFilled:(const autofill::FormData&)result;
+
+// Detatches from the web state.
+- (void)detachFromWebState;
+
+// Renders the field type predictions specified in |forms|. This method is a
+// no-op if the relevant experiment is not enabled.
+- (void)renderAutofillTypePredictions:
+    (const std::vector<autofill::FormStructure*>&)forms;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_AUTOFILL_AUTOFILL_AGENT_H_
diff --git a/ios/chrome/browser/autofill/autofill_agent.mm b/ios/chrome/browser/autofill/autofill_agent.mm
new file mode 100644
index 0000000..1b95bf5a
--- /dev/null
+++ b/ios/chrome/browser/autofill/autofill_agent.mm
@@ -0,0 +1,870 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/autofill/autofill_agent.h"
+
+#include <memory>
+#include <string>
+
+#include "base/format_macros.h"
+#include "base/guid.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_block.h"
+#include "base/metrics/field_trial.h"
+#include "base/strings/string16.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "components/autofill/core/browser/autofill_manager.h"
+#include "components/autofill/core/browser/autofill_metrics.h"
+#include "components/autofill/core/browser/autofill_profile.h"
+#include "components/autofill/core/browser/credit_card.h"
+#include "components/autofill/core/browser/keyboard_accessory_metrics_logger.h"
+#include "components/autofill/core/browser/popup_item_ids.h"
+#include "components/autofill/core/common/autofill_constants.h"
+#include "components/autofill/core/common/autofill_pref_names.h"
+#include "components/autofill/core/common/autofill_util.h"
+#include "components/autofill/core/common/form_data.h"
+#include "components/autofill/core/common/form_field_data.h"
+#include "components/autofill/ios/browser/autofill_driver_ios.h"
+#import "components/autofill/ios/browser/form_suggestion.h"
+#import "components/autofill/ios/browser/js_autofill_manager.h"
+#include "components/prefs/pref_service.h"
+#include "ios/chrome/browser/application_context.h"
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#include "ios/chrome/browser/pref_names.h"
+#include "ios/web/public/url_scheme_util.h"
+#import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
+#include "ios/web/public/web_state/url_verification_constants.h"
+#import "ios/web/public/web_state/web_state.h"
+#include "ui/gfx/geometry/rect.h"
+#include "url/gurl.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+
+using FormDataVector = std::vector<autofill::FormData>;
+
+// The type of the completion handler block for
+// |fetchFormsWithName:minimumRequiredFieldsCount:pageURL:completionHandler|
+typedef void (^FetchFormsCompletionHandler)(BOOL, const FormDataVector&);
+
+// Gets the first form and field specified by |fieldName| from |forms|,
+// modifying the returned field so that input elements are also handled.
+void GetFormAndField(autofill::FormData* form,
+                     autofill::FormFieldData* field,
+                     const FormDataVector& forms,
+                     const std::string& fieldName,
+                     const std::string& type) {
+  DCHECK_GE(forms.size(), 1U);
+  *form = forms[0];
+  const base::string16 fieldName16 = base::UTF8ToUTF16(fieldName);
+  for (const auto& currentField : form->fields) {
+    if (currentField.name == fieldName16) {
+      *field = currentField;
+      break;
+    }
+  }
+  if (field->SameFieldAs(autofill::FormFieldData()))
+    return;
+
+  // Hack to get suggestions from select input elements.
+  if (field->form_control_type == "select-one") {
+    // Any value set will cause the AutofillManager to filter suggestions (only
+    // show suggestions that begin the same as the current value) with the
+    // effect that one only suggestion would be returned; the value itself.
+    field->value = base::string16();
+  }
+}
+
+}  // namespace
+
+@interface AutofillAgent ()<CRWWebStateObserver>
+
+// Notifies the autofill manager when forms are detected on a page.
+- (void)notifyAutofillManager:(autofill::AutofillManager*)autofillManager
+                  ofFormsSeen:(const FormDataVector&)forms;
+
+// Notifies the autofill manager when forms are submitted.
+- (void)notifyAutofillManager:(autofill::AutofillManager*)autofillManager
+             ofFormsSubmitted:(const FormDataVector&)forms
+                userInitiated:(BOOL)userInitiated;
+
+// Invokes the form extraction script and loads the output into the format
+// expected by the AutofillManager.
+// If |formName| is non-empty, only a form of that name is extracted.
+// Only forms with at least |requiredFieldsCount| fields are extracted.
+// Calls |completionHandler| with a success BOOL of YES and the form data that
+// was extracted.
+// Calls |completionHandler| with NO if the forms could not be extracted.
+// |completionHandler| cannot be nil.
+- (void)fetchFormsWithName:(const base::string16&)formName
+    minimumRequiredFieldsCount:(NSUInteger)requiredFieldsCount
+                       pageURL:(const GURL&)pageURL
+             completionHandler:(FetchFormsCompletionHandler)completionHandler;
+
+// Processes the JSON form data extracted from the page into the format expected
+// by AutofillManager and fills it in |formsData|.
+// |formsData| cannot be nil.
+// Returns a BOOL indicating the success value and the vector of form data.
+- (BOOL)getExtractedFormsData:(FormDataVector*)formsData
+                     fromJSON:(NSString*)formJSON
+                     formName:(const base::string16&)formName
+                      pageURL:(const GURL&)pageURL;
+
+// Processes the JSON form data extracted from the page when form activity is
+// detected and informs the AutofillManager.
+- (void)processFormActivityExtractedData:(const FormDataVector&)forms
+                               fieldName:(const std::string&)fieldName
+                                    type:(const std::string&)type
+                                webState:(web::WebState*)webState;
+
+// Sends a request to AutofillManager to retrieve suggestions for the specified
+// form and field.
+- (void)queryAutofillWithForms:(const FormDataVector&)forms
+                         field:(NSString*)fieldName
+                          type:(NSString*)type
+                    typedValue:(NSString*)typedValue
+                      webState:(web::WebState*)webState
+             completionHandler:(SuggestionsAvailableCompletion)completion;
+
+// Rearranges and filters the suggestions to move profile or credit card
+// suggestions to the front if the user has selected one recently and remove
+// key/value suggestions if the user hasn't started typing.
+- (NSArray*)processSuggestions:(NSArray*)suggestions;
+
+@end
+
+@implementation AutofillAgent {
+  // Timestamp of the first time forms are seen.
+  base::TimeTicks formsSeenTimestamp_;
+
+  // Bridge to observe the web state from Objective-C.
+  std::unique_ptr<web::WebStateObserverBridge> _webStateObserverBridge;
+
+  // The browser state for which this agent was created.
+  ios::ChromeBrowserState* browserState_;
+
+  // Manager for Autofill JavaScripts.
+  JsAutofillManager* jsAutofillManager_;
+
+  // The name of the most recent autocomplete field; tracks the currently-
+  // focused form element in order to force filling of the currently selected
+  // form element, even if it's non-empty.
+  base::string16 pendingAutocompleteField_;
+  // The identifier of the most recent suggestion accepted by the user. Only
+  // used to reorder future suggestion lists, placing matching suggestions first
+  // in the list.
+  NSInteger mostRecentSelectedIdentifier_;
+
+  // Suggestions state:
+  // The most recent form suggestions.
+  NSArray* mostRecentSuggestions_;
+  // The completion to inform FormSuggestionController that a user selecti