diff --git a/DEPS b/DEPS
index bd5a815c..d9c1ae7 100644
--- a/DEPS
+++ b/DEPS
@@ -40,11 +40,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'ba7196c0c9844bc885692cf3abee7e62a2e5ec7b',
+  'skia_revision': '6d3af6faa23a931be1404cd3fcba02a3c271151c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '643d02d9ce587dba7d0d944c01a953e99397158c',
+  'v8_revision': '547d6a869d38e6c08a5d038abd1477530d01f9cc',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
diff --git a/chrome/browser/captive_portal/captive_portal_tab_helper_unittest.cc b/chrome/browser/captive_portal/captive_portal_tab_helper_unittest.cc
index 6c67e59..f536a0c 100644
--- a/chrome/browser/captive_portal/captive_portal_tab_helper_unittest.cc
+++ b/chrome/browser/captive_portal/captive_portal_tab_helper_unittest.cc
@@ -284,8 +284,9 @@
   if (content::IsBrowserSideNavigationEnabled()) {
     // This chain of event is not possible with PlzNavigate. For the same-site
     // navigation not to be cancelled by the second navigation, it needs to get
-    // to ReadyToCommit stage. This is not currently possible to simulate with
-    // the content/ test API.
+    // to ReadyToCommit stage. Navigation failure after the ReadyToCommit stage
+    // is something we want to suppress, so it is not implemented in the
+    // NavigationSimulator.
     return;
   }
   GURL same_site_url = GURL(kHttpUrl);
@@ -322,13 +323,6 @@
 // Similar to the above test, except the original RenderViewHost manages to
 // commit before its navigation is aborted.
 TEST_F(CaptivePortalTabHelperTest, UnexpectedCommit) {
-  if (content::IsBrowserSideNavigationEnabled()) {
-    // This chain of event is not possible with PlzNavigate. For the same-site
-    // navigation not to be cancelled by the second navigation, it needs to get
-    // to ReadyToCommit stage. This is not currently possible to simulate with
-    // the content/ test API.
-    return;
-  }
   GURL same_site_url = GURL(kHttpUrl);
   GURL cross_process_url = GURL(kHttpsUrl2);
 
@@ -337,10 +331,10 @@
               OnLoadStart(same_site_url.SchemeIsCryptographic())).Times(1);
   std::unique_ptr<NavigationSimulator> same_site_navigation =
       NavigationSimulator::CreateRendererInitiated(same_site_url, main_rfh());
-  same_site_navigation->Start();
+  same_site_navigation->ReadyToCommit();
 
   // It's unexpectedly interrupted by a cross-process navigation, which starts
-  // navigating before the old navigation cancels.
+  // navigating before the old navigation commits.
   EXPECT_CALL(mock_reloader(), OnAbort()).Times(1);
   EXPECT_CALL(mock_reloader(),
               OnLoadStart(cross_process_url.SchemeIsCryptographic())).Times(1);
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 89ddfc0..b31a4a0 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -782,6 +782,7 @@
     "lock_screen_apps/app_manager.h",
     "lock_screen_apps/app_manager_impl.cc",
     "lock_screen_apps/app_manager_impl.h",
+    "lock_screen_apps/focus_cycler_delegate.h",
     "lock_screen_apps/state_controller.cc",
     "lock_screen_apps/state_controller.h",
     "lock_screen_apps/state_observer.h",
diff --git a/chrome/browser/chromeos/lock_screen_apps/focus_cycler_delegate.h b/chrome/browser/chromeos/lock_screen_apps/focus_cycler_delegate.h
new file mode 100644
index 0000000..3ffff39
--- /dev/null
+++ b/chrome/browser/chromeos/lock_screen_apps/focus_cycler_delegate.h
@@ -0,0 +1,37 @@
+// Copyright 2017 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 CHROME_BROWSER_CHROMEOS_LOCK_SCREEN_APPS_FOCUS_CYCLER_DELEGATE_H_
+#define CHROME_BROWSER_CHROMEOS_LOCK_SCREEN_APPS_FOCUS_CYCLER_DELEGATE_H_
+
+#include "base/callback_forward.h"
+
+namespace lock_screen_apps {
+
+// Used by the StateController to inject a lock screen app window in the tab
+// order cycle with the lock screen UI and system tray, i.e. to move the focus
+// away from the app window when the app window is tabbed through, and to
+// register a handler for receiving focus from the lock screen UI.
+class FocusCyclerDelegate {
+ public:
+  virtual ~FocusCyclerDelegate() = default;
+
+  // Registers a callback that should be called when the focus should be moved
+  // to the app window.
+  using LockScreenAppFocusCallback = base::Callback<void(bool reverse)>;
+  virtual void RegisterLockScreenAppFocusHandler(
+      const LockScreenAppFocusCallback& focus_handler) = 0;
+
+  // Unregister the callback that should be called to move the focus to the
+  // app window, if one was registered.
+  virtual void UnregisterLockScreenAppFocusHandler() = 0;
+
+  // Called when the focus leaves the lock screen app window. The delegate
+  // should move the focus to the next appropriate UI element.
+  virtual void HandleLockScreenAppFocusOut(bool reverse) = 0;
+};
+
+}  // namespace lock_screen_apps
+
+#endif  // CHROME_BROWSER_CHROMEOS_LOCK_SCREEN_APPS_FOCUS_CYCLER_DELEGATE_H_
diff --git a/chrome/browser/chromeos/lock_screen_apps/state_controller.cc b/chrome/browser/chromeos/lock_screen_apps/state_controller.cc
index 03463b0..843225e6 100644
--- a/chrome/browser/chromeos/lock_screen_apps/state_controller.cc
+++ b/chrome/browser/chromeos/lock_screen_apps/state_controller.cc
@@ -15,6 +15,7 @@
 #include "base/strings/string16.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/lock_screen_apps/app_manager_impl.h"
+#include "chrome/browser/chromeos/lock_screen_apps/focus_cycler_delegate.h"
 #include "chrome/browser/chromeos/note_taking_helper.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/profiles/profile_manager.h"
@@ -25,6 +26,7 @@
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 #include "components/session_manager/core/session_manager.h"
+#include "content/public/browser/web_contents.h"
 #include "content/public/common/service_manager_connection.h"
 #include "crypto/symmetric_key.h"
 #include "extensions/browser/api/lock_screen_data/lock_screen_item_storage.h"
@@ -145,6 +147,7 @@
     ResetNoteTakingWindowAndMoveToNextState(true /*close_window*/);
     app_manager_.reset();
   }
+  focus_cycler_delegate_ = nullptr;
   power_manager_client_observer_.RemoveAll();
   input_devices_observer_.RemoveAll();
   binding_.Close();
@@ -240,6 +243,20 @@
   observers_.RemoveObserver(observer);
 }
 
+void StateController::SetFocusCyclerDelegate(FocusCyclerDelegate* delegate) {
+  DCHECK(!focus_cycler_delegate_ || !delegate);
+
+  if (focus_cycler_delegate_ && note_app_window_)
+    focus_cycler_delegate_->UnregisterLockScreenAppFocusHandler();
+
+  focus_cycler_delegate_ = delegate;
+
+  if (focus_cycler_delegate_ && note_app_window_) {
+    focus_cycler_delegate_->RegisterLockScreenAppFocusHandler(base::Bind(
+        &StateController::FocusAppWindow, weak_ptr_factory_.GetWeakPtr()));
+  }
+}
+
 TrayActionState StateController::GetLockScreenNoteState() const {
   return lock_screen_note_state_;
 }
@@ -323,9 +340,26 @@
   app_window_observer_.Add(
       extensions::AppWindowRegistry::Get(lock_screen_profile_));
   UpdateLockScreenNoteState(TrayActionState::kActive);
+  if (focus_cycler_delegate_) {
+    focus_cycler_delegate_->RegisterLockScreenAppFocusHandler(base::Bind(
+        &StateController::FocusAppWindow, weak_ptr_factory_.GetWeakPtr()));
+  }
   return note_app_window_;
 }
 
+bool StateController::HandleTakeFocus(content::WebContents* web_contents,
+                                      bool reverse) {
+  if (!focus_cycler_delegate_ ||
+      (GetLockScreenNoteState() != TrayActionState::kActive &&
+       GetLockScreenNoteState() != TrayActionState::kBackground) ||
+      note_app_window_->web_contents() != web_contents) {
+    return false;
+  }
+
+  focus_cycler_delegate_->HandleLockScreenAppFocusOut(reverse);
+  return true;
+}
+
 void StateController::MoveToBackground() {
   if (GetLockScreenNoteState() == TrayActionState::kLaunching) {
     UpdateLockScreenNoteState(TrayActionState::kAvailable);
@@ -352,11 +386,34 @@
     UpdateLockScreenNoteState(TrayActionState::kAvailable);
 }
 
+void StateController::FocusAppWindow(bool reverse) {
+  // If the app window is in background, move it to foreground (moving the
+  // window to foreground should also active it).
+  if (GetLockScreenNoteState() == TrayActionState::kBackground) {
+    note_app_window_->web_contents()->FocusThroughTabTraversal(reverse);
+    MoveToForeground();
+    return;
+  }
+
+  // If the app window is not active, pass the focus on to the delegate..
+  if (GetLockScreenNoteState() != TrayActionState::kActive) {
+    focus_cycler_delegate_->HandleLockScreenAppFocusOut(reverse);
+    return;
+  }
+
+  note_app_window_->web_contents()->FocusThroughTabTraversal(reverse);
+  note_app_window_->GetBaseWindow()->Activate();
+  note_app_window_->web_contents()->Focus();
+}
+
 void StateController::ResetNoteTakingWindowAndMoveToNextState(
     bool close_window) {
   app_window_observer_.RemoveAll();
 
   if (note_app_window_) {
+    if (focus_cycler_delegate_)
+      focus_cycler_delegate_->UnregisterLockScreenAppFocusHandler();
+
     if (close_window && note_app_window_->GetBaseWindow())
       note_app_window_->GetBaseWindow()->Close();
     note_app_window_ = nullptr;
diff --git a/chrome/browser/chromeos/lock_screen_apps/state_controller.h b/chrome/browser/chromeos/lock_screen_apps/state_controller.h
index 8893e6fa..a1ee49c 100644
--- a/chrome/browser/chromeos/lock_screen_apps/state_controller.h
+++ b/chrome/browser/chromeos/lock_screen_apps/state_controller.h
@@ -49,6 +49,7 @@
 
 namespace lock_screen_apps {
 
+class FocusCyclerDelegate;
 class StateObserver;
 
 // Manages state of lock screen action handler apps, and notifies
@@ -107,6 +108,10 @@
   void AddObserver(StateObserver* observer);
   void RemoveObserver(StateObserver* observer);
 
+  // Sets the focus cycler delegate the state controller should use to pass on
+  // from and give focus to the active lock screen app window.
+  void SetFocusCyclerDelegate(FocusCyclerDelegate* delegate);
+
   // Gets current state assiciated with the lock screen note action.
   ash::mojom::TrayActionState GetLockScreenNoteState() const;
 
@@ -138,6 +143,12 @@
       extensions::api::app_runtime::ActionType action,
       std::unique_ptr<extensions::AppDelegate> app_delegate);
 
+  // Should be called when the active app window is tabbed through. If needed,
+  // the method will take focus from the app window and pass it on using
+  // |focus_cycler_delegate_|.
+  // Returns whether the focus has been taken from the app window.
+  bool HandleTakeFocus(content::WebContents* web_contents, bool reverse);
+
   // If there are any active lock screen action handlers, moved their windows
   // to background, to ensure lock screen UI is visible.
   void MoveToBackground();
@@ -183,6 +194,11 @@
   // Notifies observers that the lock screen note action state changed.
   void NotifyLockScreenNoteStateChanged();
 
+  // Passed as a focus handler to |focus_cycler_delegate_| when the assiciated
+  // app window is visible (active or in background).
+  // It focuses the app window.
+  void FocusAppWindow(bool reverse);
+
   // Lock screen note action state.
   ash::mojom::TrayActionState lock_screen_note_state_ =
       ash::mojom::TrayActionState::kNotAvailable;
@@ -199,6 +215,8 @@
 
   std::unique_ptr<AppManager> app_manager_;
 
+  FocusCyclerDelegate* focus_cycler_delegate_ = nullptr;
+
   extensions::AppWindow* note_app_window_ = nullptr;
 
   ScopedObserver<extensions::AppWindowRegistry,
diff --git a/chrome/browser/chromeos/lock_screen_apps/state_controller_unittest.cc b/chrome/browser/chromeos/lock_screen_apps/state_controller_unittest.cc
index c995973..1ad6828 100644
--- a/chrome/browser/chromeos/lock_screen_apps/state_controller_unittest.cc
+++ b/chrome/browser/chromeos/lock_screen_apps/state_controller_unittest.cc
@@ -16,6 +16,7 @@
 #include "base/test/scoped_command_line.h"
 #include "chrome/browser/chromeos/arc/arc_session_manager.h"
 #include "chrome/browser/chromeos/lock_screen_apps/app_manager.h"
+#include "chrome/browser/chromeos/lock_screen_apps/focus_cycler_delegate.h"
 #include "chrome/browser/chromeos/lock_screen_apps/state_observer.h"
 #include "chrome/browser/chromeos/note_taking_helper.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
@@ -45,6 +46,7 @@
 #include "extensions/common/extension_builder.h"
 #include "extensions/common/value_builder.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/aura/window.h"
 #include "ui/events/devices/input_device_manager.h"
 #include "ui/events/devices/stylus_state.h"
 #include "ui/events/test/device_data_manager_test_api.h"
@@ -94,6 +96,44 @@
       .Build();
 }
 
+class TestFocusCyclerDelegate : public lock_screen_apps::FocusCyclerDelegate {
+ public:
+  TestFocusCyclerDelegate() = default;
+  ~TestFocusCyclerDelegate() override = default;
+
+  void RegisterLockScreenAppFocusHandler(
+      const LockScreenAppFocusCallback& handler) override {
+    focus_handler_ = handler;
+    lock_screen_app_focused_ = true;
+  }
+
+  void UnregisterLockScreenAppFocusHandler() override {
+    ASSERT_FALSE(focus_handler_.is_null());
+    focus_handler_.Reset();
+  }
+
+  void HandleLockScreenAppFocusOut(bool reverse) override {
+    ASSERT_FALSE(focus_handler_.is_null());
+    lock_screen_app_focused_ = false;
+  }
+
+  void RequestAppFocus(bool reverse) {
+    ASSERT_FALSE(focus_handler_.is_null());
+    lock_screen_app_focused_ = true;
+    focus_handler_.Run(reverse);
+  }
+
+  bool HasHandler() const { return !focus_handler_.is_null(); }
+
+  bool lock_screen_app_focused() const { return lock_screen_app_focused_; }
+
+ private:
+  bool lock_screen_app_focused_ = false;
+  LockScreenAppFocusCallback focus_handler_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestFocusCyclerDelegate);
+};
+
 class TestAppManager : public lock_screen_apps::AppManager {
  public:
   enum class State {
@@ -380,6 +420,8 @@
             profile(), lock_screen_profile()->GetOriginalProfile());
     app_manager_ = app_manager.get();
 
+    focus_cycler_delegate_ = base::MakeUnique<TestFocusCyclerDelegate>();
+
     state_controller_ = base::MakeUnique<lock_screen_apps::StateController>();
     state_controller_->SetTrayActionPtrForTesting(
         tray_action_.CreateInterfacePtrAndBind());
@@ -387,6 +429,7 @@
     state_controller_->SetReadyCallbackForTesting(ready_waiter_.QuitClosure());
     state_controller_->Initialize();
     state_controller_->FlushTrayActionForTesting();
+    state_controller_->SetFocusCyclerDelegate(focus_cycler_delegate_.get());
 
     state_controller_->AddObserver(&observer_);
   }
@@ -404,6 +447,7 @@
     app_window_.reset();
     BrowserWithTestWindowTest::TearDown();
     DestroyProfile(lock_screen_profile());
+    focus_cycler_delegate_.reset();
   }
 
   TestingProfile* CreateProfile() override {
@@ -548,6 +592,10 @@
   TestAppWindow* app_window() { return app_window_.get(); }
   const extensions::Extension* app() { return app_.get(); }
 
+  TestFocusCyclerDelegate* focus_cycler_delegate() {
+    return focus_cycler_delegate_.get();
+  }
+
  private:
   std::unique_ptr<base::test::ScopedCommandLine> command_line_;
   TestingProfileManager profile_manager_;
@@ -575,6 +623,8 @@
 
   std::unique_ptr<lock_screen_apps::StateController> state_controller_;
 
+  std::unique_ptr<TestFocusCyclerDelegate> focus_cycler_delegate_;
+
   TestStateObserver observer_;
   TestTrayAction tray_action_;
   TestAppManager* app_manager_ = nullptr;
@@ -1208,3 +1258,101 @@
   base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(secondary_app_window->closed());
 }
+
+// Goes through different states with no focus cycler set; mainly to check
+// there are no crashes.
+TEST_F(LockScreenAppStateTest, NoFocusCyclerDelegate) {
+  lock_screen_apps::StateController::Get()->SetFocusCyclerDelegate(nullptr);
+
+  ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kActive,
+                                      true /* enable_app_launch */));
+
+  state_controller()->MoveToBackground();
+  state_controller()->FlushTrayActionForTesting();
+
+  EXPECT_EQ(TrayActionState::kBackground,
+            state_controller()->GetLockScreenNoteState());
+
+  state_controller()->MoveToForeground();
+  state_controller()->FlushTrayActionForTesting();
+
+  EXPECT_EQ(TrayActionState::kActive,
+            state_controller()->GetLockScreenNoteState());
+
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(app_window()->closed());
+}
+
+TEST_F(LockScreenAppStateTest, ResetFocusCyclerDelegateWhileActive) {
+  ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kActive,
+                                      true /* enable_app_launch */));
+
+  lock_screen_apps::StateController::Get()->SetFocusCyclerDelegate(nullptr);
+  ASSERT_FALSE(focus_cycler_delegate()->HasHandler());
+
+  lock_screen_apps::StateController::Get()->SetFocusCyclerDelegate(
+      focus_cycler_delegate());
+  EXPECT_TRUE(focus_cycler_delegate()->HasHandler());
+}
+
+TEST_F(LockScreenAppStateTest, FocusCyclerDelegateGetsSetOnAppWindowCreation) {
+  ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kAvailable,
+                                      true /* enable_app_launch */));
+
+  tray_action()->SendNewNoteRequest();
+  state_controller()->FlushTrayActionForTesting();
+
+  EXPECT_FALSE(focus_cycler_delegate()->HasHandler());
+
+  std::unique_ptr<TestAppWindow> app_window =
+      CreateNoteTakingWindow(lock_screen_profile(), app());
+  app_window->Initialize(true /* shown */);
+
+  EXPECT_TRUE(focus_cycler_delegate()->HasHandler());
+
+  state_controller()->MoveToBackground();
+
+  EXPECT_TRUE(focus_cycler_delegate()->HasHandler());
+
+  app_window->Close();
+  EXPECT_FALSE(focus_cycler_delegate()->HasHandler());
+}
+
+TEST_F(LockScreenAppStateTest, TakeFocus) {
+  ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kActive,
+                                      true /* enable_app_launch */));
+
+  auto regular_app_window = base::MakeUnique<TestAppWindow>(
+      profile(),
+      new extensions::AppWindow(profile(), new ChromeAppDelegate(true), app()));
+  EXPECT_FALSE(state_controller()->HandleTakeFocus(
+      regular_app_window->window()->web_contents(), true));
+  EXPECT_TRUE(focus_cycler_delegate()->lock_screen_app_focused());
+
+  ASSERT_TRUE(state_controller()->HandleTakeFocus(
+      app_window()->window()->web_contents(), true));
+  EXPECT_FALSE(focus_cycler_delegate()->lock_screen_app_focused());
+
+  focus_cycler_delegate()->RequestAppFocus(true);
+  EXPECT_TRUE(focus_cycler_delegate()->lock_screen_app_focused());
+}
+
+TEST_F(LockScreenAppStateTest, RequestFocusFromBackgroundMovesAppToForeground) {
+  ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kActive,
+                                      true /* enable_app_launch */));
+
+  ASSERT_TRUE(state_controller()->HandleTakeFocus(
+      app_window()->window()->web_contents(), true));
+  EXPECT_FALSE(focus_cycler_delegate()->lock_screen_app_focused());
+
+  state_controller()->MoveToBackground();
+
+  EXPECT_EQ(TrayActionState::kBackground,
+            state_controller()->GetLockScreenNoteState());
+
+  focus_cycler_delegate()->RequestAppFocus(true);
+  EXPECT_TRUE(focus_cycler_delegate()->lock_screen_app_focused());
+
+  EXPECT_EQ(TrayActionState::kActive,
+            state_controller()->GetLockScreenNoteState());
+}
diff --git a/chrome/browser/chromeos/login/lock/webui_screen_locker.cc b/chrome/browser/chromeos/login/lock/webui_screen_locker.cc
index 5f8ec9b..7b4b7d6 100644
--- a/chrome/browser/chromeos/login/lock/webui_screen_locker.cc
+++ b/chrome/browser/chromeos/login/lock/webui_screen_locker.cc
@@ -143,6 +143,8 @@
   if (login_display_.get() && GetOobeUI())
     GetOobeUI()->ResetSigninScreenHandlerDelegate();
 
+  ClearLockScreenAppFocusCyclerDelegate();
+
   ResetKeyboardOverscrollOverride();
 
   RequestPreload();
@@ -175,6 +177,8 @@
   GetOobeUI()->ShowSigninScreen(
       LoginScreenContext(), login_display_.get(), login_display_.get());
 
+  SetLockScreenAppFocusCyclerDelegate();
+
   DisableKeyboardOverscroll();
 }
 
diff --git a/chrome/browser/chromeos/login/ui/webui_login_view.cc b/chrome/browser/chromeos/login/ui/webui_login_view.cc
index dee953d..d24bb8b 100644
--- a/chrome/browser/chromeos/login/ui/webui_login_view.cc
+++ b/chrome/browser/chromeos/login/ui/webui_login_view.cc
@@ -20,6 +20,7 @@
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/chromeos/accessibility/accessibility_util.h"
 #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
+#include "chrome/browser/chromeos/lock_screen_apps/state_controller.h"
 #include "chrome/browser/chromeos/login/enrollment/auto_enrollment_controller.h"
 #include "chrome/browser/chromeos/login/ui/login_display_host_impl.h"
 #include "chrome/browser/chromeos/login/ui/preloaded_web_view.h"
@@ -440,6 +441,20 @@
   return webui_login_.get();
 }
 
+void WebUILoginView::SetLockScreenAppFocusCyclerDelegate() {
+  if (lock_screen_apps::StateController::IsEnabled()) {
+    delegates_lock_screen_app_focus_cycle_ = true;
+    lock_screen_apps::StateController::Get()->SetFocusCyclerDelegate(this);
+  }
+}
+
+void WebUILoginView::ClearLockScreenAppFocusCyclerDelegate() {
+  if (!delegates_lock_screen_app_focus_cycle_)
+    return;
+  lock_screen_apps::StateController::Get()->SetFocusCyclerDelegate(nullptr);
+  delegates_lock_screen_app_focus_cycle_ = false;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // ash::ShellObserver:
 
@@ -519,21 +534,28 @@
   if (!forward_keyboard_event_)
     return false;
 
-  // Focus is accepted, but the Ash system tray is not available in Mash, so
-  // exit early.
-  if (ash_util::IsRunningInMash())
+  // For default tab order, after login UI, try focusing the system tray.
+  if (!reverse && MoveFocusToSystemTray(reverse))
     return true;
 
-  ash::SystemTray* tray = ash::Shell::Get()->GetPrimarySystemTray();
-  if (tray && tray->GetWidget()->IsVisible() && tray->visible()) {
-    ash::StatusAreaWidgetDelegate::GetPrimaryInstance()
-        ->set_default_last_focusable_child(reverse);
-    ash::Shell::Get()->focus_cycler()->RotateFocus(
-        reverse ? ash::FocusCycler::BACKWARD : ash::FocusCycler::FORWARD);
-  } else {
-    AboutToRequestFocusFromTabTraversal(reverse);
+  // Either if tab order is reversed (in which case system tray focus was not
+  // attempted), or system tray focus failed (in which case focusing an app
+  // window is preferrable to  focus returning to login UI), try moving focus
+  // to the app window.
+  if (!lock_screen_app_focus_handler_.is_null()) {
+    lock_screen_app_focus_handler_.Run(reverse);
+    return true;
   }
 
+  // If initial MoveFocusToSystemTray was skipped due to lock screen app being
+  // a preferred option (due to traversal direction), try focusing system tray
+  // again.
+  if (reverse && MoveFocusToSystemTray(reverse))
+    return true;
+
+  // Since neither system tray nor a lock screen app window was focusable, the
+  // focus should stay in the login UI.
+  AboutToRequestFocusFromTabTraversal(reverse);
   return true;
 }
 
@@ -562,10 +584,48 @@
   return blink::WebInputEvent::IsPinchGestureEventType(event.GetType());
 }
 
-void WebUILoginView::OnFocusOut(bool reverse) {
+void WebUILoginView::RegisterLockScreenAppFocusHandler(
+    const LockScreenAppFocusCallback& focus_handler) {
+  lock_screen_app_focus_handler_ = focus_handler;
+}
+
+void WebUILoginView::UnregisterLockScreenAppFocusHandler() {
+  lock_screen_app_focus_handler_.Reset();
+}
+
+void WebUILoginView::HandleLockScreenAppFocusOut(bool reverse) {
+  if (reverse && MoveFocusToSystemTray(reverse))
+    return;
+
   AboutToRequestFocusFromTabTraversal(reverse);
 }
 
+void WebUILoginView::OnFocusOut(bool reverse) {
+  if (!reverse && !lock_screen_app_focus_handler_.is_null()) {
+    lock_screen_app_focus_handler_.Run(reverse);
+    return;
+  }
+
+  AboutToRequestFocusFromTabTraversal(reverse);
+}
+
+bool WebUILoginView::MoveFocusToSystemTray(bool reverse) {
+  // Focus is accepted, but the Ash system tray is not available in Mash, so
+  // exit early.
+  if (ash_util::IsRunningInMash())
+    return true;
+
+  ash::SystemTray* tray = ash::Shell::Get()->GetPrimarySystemTray();
+  if (!tray || !tray->GetWidget()->IsVisible() || !tray->visible())
+    return false;
+
+  ash::StatusAreaWidgetDelegate::GetPrimaryInstance()
+      ->set_default_last_focusable_child(reverse);
+  ash::Shell::Get()->focus_cycler()->RotateFocus(
+      reverse ? ash::FocusCycler::BACKWARD : ash::FocusCycler::FORWARD);
+  return true;
+}
+
 void WebUILoginView::OnLoginPromptVisible() {
   // If we're hidden than will generate this signal once we're shown.
   if (is_hidden_ || webui_visible_) {
diff --git a/chrome/browser/chromeos/login/ui/webui_login_view.h b/chrome/browser/chromeos/login/ui/webui_login_view.h
index 0e3964b..932219d17 100644
--- a/chrome/browser/chromeos/login/ui/webui_login_view.h
+++ b/chrome/browser/chromeos/login/ui/webui_login_view.h
@@ -13,6 +13,7 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/observer_list.h"
+#include "chrome/browser/chromeos/lock_screen_apps/focus_cycler_delegate.h"
 #include "chrome/browser/ui/chrome_web_modal_dialog_manager_delegate.h"
 #include "components/web_modal/web_contents_modal_dialog_host.h"
 #include "content/public/browser/notification_observer.h"
@@ -47,6 +48,7 @@
                        public content::NotificationObserver,
                        public ChromeWebModalDialogManagerDelegate,
                        public web_modal::WebContentsModalDialogHost,
+                       public lock_screen_apps::FocusCyclerDelegate,
                        public ash::StatusAreaFocusObserver {
  public:
   struct WebViewSettings {
@@ -136,6 +138,14 @@
 
   views::WebView* web_view();
 
+  // Sets |this| as lock_screen_apps::StateController's
+  // lock_screen_apps::FocusCyclerDelegate.
+  void SetLockScreenAppFocusCyclerDelegate();
+
+  // Resets the lock_screen_apps::StateController's FocusCyclerDelegate,
+  // provided that |this|  was set as the delegate.
+  void ClearLockScreenAppFocusCyclerDelegate();
+
  private:
   // Map type for the accelerator-to-identifier map.
   typedef std::map<ui::Accelerator, std::string> AccelMap;
@@ -165,9 +175,19 @@
   bool PreHandleGestureEvent(content::WebContents* source,
                              const blink::WebGestureEvent& event) override;
 
+  // lock_screen_apps::FocusCyclerDelegate:
+  void RegisterLockScreenAppFocusHandler(
+      const LockScreenAppFocusCallback& focus_handler) override;
+  void UnregisterLockScreenAppFocusHandler() override;
+  void HandleLockScreenAppFocusOut(bool reverse) override;
+
   // Overridden from ash::StatusAreaFocusObserver.
   void OnFocusOut(bool reverse) override;
 
+  // Attempts to move focus to system tray. Returns whether the attempt was
+  // successful (it might fail if the system tray is not visible).
+  bool MoveFocusToSystemTray(bool reverse);
+
   // Performs series of actions when login prompt is considered
   // to be ready and visible.
   // 1. Emits LoginPromptVisible signal if needed
@@ -204,6 +224,15 @@
   // True to forward keyboard event.
   bool forward_keyboard_event_ = true;
 
+  // If set, the callback that should be called when focus should be moved to
+  // a lock screen app window.
+  // It gets registered using |RegisterLockScreenAppFocusHandler|.
+  LockScreenAppFocusCallback lock_screen_app_focus_handler_;
+
+  // Whether this was set as lock_screen_apps::StateController's
+  // FocusCyclerDelegate.
+  bool delegates_lock_screen_app_focus_cycle_ = false;
+
   base::ObserverList<web_modal::ModalDialogHostObserver> observer_list_;
 
   DISALLOW_COPY_AND_ASSIGN(WebUILoginView);
diff --git a/chrome/browser/resources/chromeos/login/md_top_header_bar.js b/chrome/browser/resources/chromeos/login/md_top_header_bar.js
index b46b5b7a..7108c2e 100644
--- a/chrome/browser/resources/chromeos/login/md_top_header_bar.js
+++ b/chrome/browser/resources/chromeos/login/md_top_header_bar.js
@@ -391,13 +391,12 @@
           this.lockScreenAppsState_ != LOCK_SCREEN_APPS_STATE.AVAILABLE &&
           this.lockScreenAppsState_ != LOCK_SCREEN_APPS_STATE.FOREGROUND;
 
+      var newNoteActionEnabled =
+          this.lockScreenAppsState_ == LOCK_SCREEN_APPS_STATE.AVAILABLE;
+      $('new-note-action').classList.toggle('disabled', !newNoteActionEnabled);
       $('new-note-action')
-          .classList.toggle(
-              'disabled',
-              this.lockScreenAppsState_ != LOCK_SCREEN_APPS_STATE.AVAILABLE);
-
-      $('new-note-action-icon').hidden =
-          this.lockScreenAppsState_ != LOCK_SCREEN_APPS_STATE.AVAILABLE;
+          .setAttribute('tabIndex', newNoteActionEnabled ? '0' : '-1');
+      $('new-note-action-icon').hidden = !newNoteActionEnabled;
 
       // This might get set when the action is activated - reset it when the
       // lock screen action is updated.
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller_impl_browsertest_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller_impl_browsertest_win.cc
index 3286e75d..9aab983b 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller_impl_browsertest_win.cc
+++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller_impl_browsertest_win.cc
@@ -80,7 +80,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(ChromeCleanerPromptUserTest,
-                       OnInfectedBrowserNotAvailable) {
+                       DISABLED_OnInfectedBrowserNotAvailable) {
   browser()->window()->Minimize();
   base::RunLoop().RunUntilIdle();
   dialog_controller_->OnInfected(std::set<base::FilePath>());
diff --git a/chrome/browser/search/one_google_bar/one_google_bar_service.cc b/chrome/browser/search/one_google_bar/one_google_bar_service.cc
index d812105..5e0e278 100644
--- a/chrome/browser/search/one_google_bar/one_google_bar_service.cc
+++ b/chrome/browser/search/one_google_bar/one_google_bar_service.cc
@@ -71,8 +71,11 @@
 }
 
 void OneGoogleBarService::SigninStatusChanged() {
-  one_google_bar_data_ = base::nullopt;
-  NotifyObservers();
+  // If we have cached data, clear it and notify observers.
+  if (one_google_bar_data_.has_value()) {
+    one_google_bar_data_ = base::nullopt;
+    NotifyObservers();
+  }
 }
 
 void OneGoogleBarService::OneGoogleBarDataFetched(
diff --git a/chrome/browser/search/one_google_bar/one_google_bar_service_unittest.cc b/chrome/browser/search/one_google_bar/one_google_bar_service_unittest.cc
index c91b8d62..da86d60 100644
--- a/chrome/browser/search/one_google_bar/one_google_bar_service_unittest.cc
+++ b/chrome/browser/search/one_google_bar/one_google_bar_service_unittest.cc
@@ -225,9 +225,15 @@
   fetcher()->RespondToAllCallbacks(OneGoogleBarFetcher::Status::OK, data);
   ASSERT_THAT(service()->one_google_bar_data(), Eq(data));
 
-  // Sign in. This should clear the cached data.
+  StrictMock<MockOneGoogleBarServiceObserver> observer;
+  service()->AddObserver(&observer);
+
+  // Sign in. This should clear the cached data and notify the observer.
+  EXPECT_CALL(observer, OnOneGoogleBarDataUpdated());
   SignIn();
   EXPECT_THAT(service()->one_google_bar_data(), Eq(base::nullopt));
+
+  service()->RemoveObserver(&observer);
 }
 
 TEST_F(OneGoogleBarServiceTest, ResetsOnSignOut) {
@@ -240,7 +246,27 @@
   fetcher()->RespondToAllCallbacks(OneGoogleBarFetcher::Status::OK, data);
   ASSERT_THAT(service()->one_google_bar_data(), Eq(data));
 
-  // Sign out. This should clear the cached data.
+  StrictMock<MockOneGoogleBarServiceObserver> observer;
+  service()->AddObserver(&observer);
+
+  // Sign in. This should clear the cached data and notify the observer.
+  EXPECT_CALL(observer, OnOneGoogleBarDataUpdated());
   SignOut();
   EXPECT_THAT(service()->one_google_bar_data(), Eq(base::nullopt));
+
+  service()->RemoveObserver(&observer);
+}
+
+TEST_F(OneGoogleBarServiceTest, DoesNotNotifyObserverOnSignInIfNoCachedData) {
+  ASSERT_THAT(service()->one_google_bar_data(), Eq(base::nullopt));
+
+  StrictMock<MockOneGoogleBarServiceObserver> observer;
+  service()->AddObserver(&observer);
+
+  // Sign in. This should *not* notify the observer, since there was no cached
+  // data before.
+  SignIn();
+  EXPECT_THAT(service()->one_google_bar_data(), Eq(base::nullopt));
+
+  service()->RemoveObserver(&observer);
 }
diff --git a/chrome/browser/ui/apps/chrome_app_delegate.cc b/chrome/browser/ui/apps/chrome_app_delegate.cc
index 619d53c..f18af9b3 100644
--- a/chrome/browser/ui/apps/chrome_app_delegate.cc
+++ b/chrome/browser/ui/apps/chrome_app_delegate.cc
@@ -47,6 +47,10 @@
 #include "ash/shelf/shelf_constants.h"  // nogncheck
 #endif
 
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/lock_screen_apps/state_controller.h"
+#endif
+
 #if BUILDFLAG(ENABLE_PRINTING)
 #if BUILDFLAG(ENABLE_PRINT_PREVIEW)
 #include "chrome/browser/printing/print_preview_message_handler.h"
@@ -164,6 +168,7 @@
 ChromeAppDelegate::ChromeAppDelegate(bool keep_alive)
     : has_been_shown_(false),
       is_hidden_(true),
+      for_lock_screen_app_(false),
       new_window_contents_delegate_(new NewWindowContentsDelegate()),
       weak_factory_(this) {
   if (keep_alive) {
@@ -348,6 +353,18 @@
                                         KeepAliveRestartOption::DISABLED));
 }
 
+bool ChromeAppDelegate::TakeFocus(content::WebContents* web_contents,
+                                  bool reverse) {
+  if (!for_lock_screen_app_)
+    return false;
+#if defined(OS_CHROMEOS)
+  return lock_screen_apps::StateController::Get()->HandleTakeFocus(web_contents,
+                                                                   reverse);
+#else
+  return false;
+#endif
+}
+
 void ChromeAppDelegate::Observe(int type,
                                 const content::NotificationSource& source,
                                 const content::NotificationDetails& details) {
diff --git a/chrome/browser/ui/apps/chrome_app_delegate.h b/chrome/browser/ui/apps/chrome_app_delegate.h
index 31ec3de..a98b0f8 100644
--- a/chrome/browser/ui/apps/chrome_app_delegate.h
+++ b/chrome/browser/ui/apps/chrome_app_delegate.h
@@ -28,6 +28,10 @@
 
   static void DisableExternalOpenForTesting();
 
+  void set_for_lock_screen_app(bool for_lock_screen_app) {
+    for_lock_screen_app_ = for_lock_screen_app;
+  }
+
  private:
   static void RelinquishKeepAliveAfterTimeout(
       const base::WeakPtr<ChromeAppDelegate>& chrome_app_delegate);
@@ -70,6 +74,7 @@
   void SetTerminatingCallback(const base::Closure& callback) override;
   void OnHide() override;
   void OnShow() override;
+  bool TakeFocus(content::WebContents* web_contents, bool reverse) override;
 
   // content::NotificationObserver:
   void Observe(int type,
@@ -78,6 +83,7 @@
 
   bool has_been_shown_;
   bool is_hidden_;
+  bool for_lock_screen_app_;
   std::unique_ptr<ScopedKeepAlive> keep_alive_;
   std::unique_ptr<NewWindowContentsDelegate> new_window_contents_delegate_;
   base::Closure terminating_callback_;
diff --git a/chrome/browser/ui/apps/chrome_app_window_client.cc b/chrome/browser/ui/apps/chrome_app_window_client.cc
index b13496b..99ca6d2 100644
--- a/chrome/browser/ui/apps/chrome_app_window_client.cc
+++ b/chrome/browser/ui/apps/chrome_app_window_client.cc
@@ -59,10 +59,12 @@
   if (!lock_screen_apps::StateController::IsEnabled())
     return nullptr;
 
+  auto app_delegate = base::MakeUnique<ChromeAppDelegate>(true /*keep_alive*/);
+  app_delegate->set_for_lock_screen_app(true);
+
   return lock_screen_apps::StateController::Get()
-      ->CreateAppWindowForLockScreenAction(
-          context, extension, action,
-          base::MakeUnique<ChromeAppDelegate>(true /* keep_alive */));
+      ->CreateAppWindowForLockScreenAction(context, extension, action,
+                                           std::move(app_delegate));
 #else
   return nullptr;
 #endif
diff --git a/components/ntp_snippets/contextual_suggestions_source_unittest.cc b/components/ntp_snippets/contextual_suggestions_source_unittest.cc
index c632242..c713fe9 100644
--- a/components/ntp_snippets/contextual_suggestions_source_unittest.cc
+++ b/components/ntp_snippets/contextual_suggestions_source_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
+#include "base/test/mock_callback.h"
 #include "components/image_fetcher/core/image_fetcher_impl.h"
 #include "components/ntp_snippets/category_info.h"
 #include "components/ntp_snippets/content_suggestion.h"
@@ -22,12 +23,17 @@
 #include "components/ntp_snippets/remote/remote_suggestion.h"
 #include "components/ntp_snippets/remote/remote_suggestion_builder.h"
 #include "components/ntp_snippets/remote/remote_suggestions_database.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_unittest_util.h"
 
 using testing::_;
 using testing::AllOf;
 using testing::ElementsAre;
+using testing::IsEmpty;
 using testing::Mock;
 using testing::Pointee;
 using testing::Property;
@@ -36,21 +42,26 @@
 
 namespace {
 
-const char kFromURL[] = "http://localhost";
-const char kSuggestionURL[] = "http://url.test";
+ACTION_TEMPLATE(MoveArg,
+                HAS_1_TEMPLATE_PARAMS(int, k),
+                AND_1_VALUE_PARAMS(out)) {
+  *out = std::move(*::testing::get<k>(args));
+};
 
-// Always fetches one valid RemoteSuggestion.
+// Always fetches the result that was set by SetFakeResponse.
 class FakeContextualSuggestionsFetcher : public ContextualSuggestionsFetcher {
+ public:
   void FetchContextualSuggestions(
       const GURL& url,
       SuggestionsAvailableCallback callback) override {
-    OptionalSuggestions suggestions = RemoteSuggestion::PtrVector();
-    suggestions->push_back(test::RemoteSuggestionBuilder()
-                               .AddId(kSuggestionURL)
-                               .SetUrl(kSuggestionURL)
-                               .SetAmpUrl(kSuggestionURL)
-                               .Build());
-    std::move(callback).Run(Status::Success(), std::move(suggestions));
+    std::move(callback).Run(fake_status_, std::move(fake_suggestions_));
+    fake_suggestions_ = base::nullopt;
+  }
+
+  void SetFakeResponse(Status fake_status,
+                       OptionalSuggestions fake_suggestions) {
+    fake_status_ = fake_status;
+    fake_suggestions_ = std::move(fake_suggestions);
   }
 
   const std::string& GetLastStatusForTesting() const override { return empty_; }
@@ -60,6 +71,27 @@
  private:
   std::string empty_;
   GURL empty_url_;
+  Status fake_status_ = Status::Success();
+  OptionalSuggestions fake_suggestions_;
+};
+
+// Always fetches a fake image if the given URL is valid.
+class FakeCachedImageFetcher : public CachedImageFetcher {
+ public:
+  FakeCachedImageFetcher(PrefService* pref_service)
+      : CachedImageFetcher(std::unique_ptr<image_fetcher::ImageFetcher>(),
+                           pref_service,
+                           nullptr){};
+
+  void FetchSuggestionImage(const ContentSuggestion::ID&,
+                            const GURL& image_url,
+                            ImageFetchedCallback callback) override {
+    gfx::Image image;
+    if (image_url.is_valid()) {
+      image = gfx::test::CreateImage();
+    }
+    std::move(callback).Run(image);
+  }
 };
 
 // GMock does not support movable-only types (ContentSuggestion).
@@ -90,16 +122,23 @@
 class ContextualSuggestionsSourceTest : public testing::Test {
  public:
   ContextualSuggestionsSourceTest() {
+    RequestThrottler::RegisterProfilePrefs(pref_service_.registry());
+    std::unique_ptr<FakeContextualSuggestionsFetcher> fetcher =
+        base::MakeUnique<FakeContextualSuggestionsFetcher>();
+    fetcher_ = fetcher.get();
     source_ = base::MakeUnique<ContextualSuggestionsSource>(
-        base::MakeUnique<FakeContextualSuggestionsFetcher>(),
-        std::unique_ptr<CachedImageFetcher>(),
+        std::move(fetcher),
+        base::MakeUnique<FakeCachedImageFetcher>(&pref_service_),
         std::unique_ptr<RemoteSuggestionsDatabase>());
   }
 
+  FakeContextualSuggestionsFetcher* fetcher() { return fetcher_; }
   ContextualSuggestionsSource* source() { return source_.get(); }
 
  private:
+  FakeContextualSuggestionsFetcher* fetcher_;
   base::MessageLoop message_loop_;
+  TestingPrefServiceSimple pref_service_;
   std::unique_ptr<ContextualSuggestionsSource> source_;
 
   DISALLOW_COPY_AND_ASSIGN(ContextualSuggestionsSourceTest);
@@ -107,17 +146,95 @@
 
 TEST_F(ContextualSuggestionsSourceTest, ShouldFetchContextualSuggestion) {
   MockFetchContextualSuggestionsCallback mock_suggestions_callback;
-  EXPECT_CALL(
-      mock_suggestions_callback,
-      Run(Property(&Status::IsSuccess, true), GURL(kFromURL),
-          Pointee(ElementsAre(AllOf(
-              Property(&ContentSuggestion::id,
-                       Property(&ContentSuggestion::ID::category,
-                                Category::FromKnownCategory(
-                                    KnownCategories::CONTEXTUAL))),
-              Property(&ContentSuggestion::url, GURL(kSuggestionURL)))))));
+  const std::string kValidFromUrl = "http://some.url";
+  const std::string kToUrl = "http://another.url";
+  ContextualSuggestionsFetcher::OptionalSuggestions remote_suggestions =
+      RemoteSuggestion::PtrVector();
+  remote_suggestions->push_back(test::RemoteSuggestionBuilder()
+                                    .AddId(kToUrl)
+                                    .SetUrl(kToUrl)
+                                    .SetAmpUrl(kToUrl)
+                                    .Build());
+  fetcher()->SetFakeResponse(Status::Success(), std::move(remote_suggestions));
+  EXPECT_CALL(mock_suggestions_callback,
+              Run(Property(&Status::IsSuccess, true), GURL(kValidFromUrl),
+                  Pointee(ElementsAre(AllOf(
+                      Property(&ContentSuggestion::id,
+                               Property(&ContentSuggestion::ID::category,
+                                        Category::FromKnownCategory(
+                                            KnownCategories::CONTEXTUAL))),
+                      Property(&ContentSuggestion::url, GURL(kToUrl)))))));
   source()->FetchContextualSuggestions(
-      GURL(kFromURL), mock_suggestions_callback.ToOnceCallback());
+      GURL(kValidFromUrl), mock_suggestions_callback.ToOnceCallback());
+  base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(ContextualSuggestionsSourceTest, ShouldRunCallbackOnEmptyResults) {
+  MockFetchContextualSuggestionsCallback mock_suggestions_callback;
+  const std::string kEmpty;
+  fetcher()->SetFakeResponse(Status::Success(), RemoteSuggestion::PtrVector());
+  EXPECT_CALL(mock_suggestions_callback, Run(Property(&Status::IsSuccess, true),
+                                             GURL(kEmpty), Pointee(IsEmpty())));
+  source()->FetchContextualSuggestions(
+      GURL(kEmpty), mock_suggestions_callback.ToOnceCallback());
+  base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(ContextualSuggestionsSourceTest, ShouldRunCallbackOnError) {
+  MockFetchContextualSuggestionsCallback mock_suggestions_callback;
+  const std::string kEmpty;
+  fetcher()->SetFakeResponse(Status(StatusCode::TEMPORARY_ERROR, ""),
+                             RemoteSuggestion::PtrVector());
+  EXPECT_CALL(mock_suggestions_callback,
+              Run(Property(&Status::IsSuccess, false), GURL(kEmpty),
+                  Pointee(IsEmpty())));
+  source()->FetchContextualSuggestions(
+      GURL(kEmpty), mock_suggestions_callback.ToOnceCallback());
+  base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(ContextualSuggestionsSourceTest, ShouldFetchEmptyImageIfNotFound) {
+  base::MockCallback<ImageFetchedCallback> mock_image_fetched_callback;
+  const std::string kEmpty;
+  ContentSuggestion::ID id(
+      Category::FromKnownCategory(KnownCategories::CONTEXTUAL), kEmpty);
+  EXPECT_CALL(mock_image_fetched_callback,
+              Run(Property(&gfx::Image::IsEmpty, true)));
+  source()->FetchContextualSuggestionImage(id,
+                                           mock_image_fetched_callback.Get());
+  // TODO(gaschler): Verify with a mock that the image fetcher is not called if
+  // the id is unknown.
+  base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(ContextualSuggestionsSourceTest,
+       ShouldFetchImageForPreviouslyFetchedSuggestion) {
+  const std::string kValidFromUrl = "http://some.url";
+  const std::string kToUrl = "http://another.url";
+  const std::string kValidImageUrl = "http://some.url/image.png";
+  ContextualSuggestionsFetcher::OptionalSuggestions remote_suggestions =
+      RemoteSuggestion::PtrVector();
+  remote_suggestions->push_back(test::RemoteSuggestionBuilder()
+                                    .AddId(kToUrl)
+                                    .SetUrl(kToUrl)
+                                    .SetAmpUrl(kToUrl)
+                                    .SetImageUrl(kValidImageUrl)
+                                    .Build());
+  fetcher()->SetFakeResponse(Status::Success(), std::move(remote_suggestions));
+  MockFetchContextualSuggestionsCallback mock_suggestions_callback;
+  std::vector<ContentSuggestion> suggestions;
+  EXPECT_CALL(mock_suggestions_callback, Run(_, _, _))
+      .WillOnce(MoveArg<2>(&suggestions));
+  source()->FetchContextualSuggestions(
+      GURL(kValidFromUrl), mock_suggestions_callback.ToOnceCallback());
+  base::RunLoop().RunUntilIdle();
+
+  ASSERT_THAT(suggestions, Not(IsEmpty()));
+  base::MockCallback<ImageFetchedCallback> mock_image_fetched_callback;
+  EXPECT_CALL(mock_image_fetched_callback,
+              Run(Property(&gfx::Image::IsEmpty, false)));
+  source()->FetchContextualSuggestionImage(suggestions[0].id(),
+                                           mock_image_fetched_callback.Get());
   base::RunLoop().RunUntilIdle();
 }
 
diff --git a/components/ntp_snippets/remote/cached_image_fetcher.h b/components/ntp_snippets/remote/cached_image_fetcher.h
index bf8ce56..51eef498 100644
--- a/components/ntp_snippets/remote/cached_image_fetcher.h
+++ b/components/ntp_snippets/remote/cached_image_fetcher.h
@@ -46,9 +46,9 @@
 
   // Fetches the image for a suggestion. The fetcher will first issue a lookup
   // to the underlying cache with a fallback to the network.
-  void FetchSuggestionImage(const ContentSuggestion::ID& suggestion_id,
-                            const GURL& image_url,
-                            ImageFetchedCallback callback);
+  virtual void FetchSuggestionImage(const ContentSuggestion::ID& suggestion_id,
+                                    const GURL& image_url,
+                                    ImageFetchedCallback callback);
 
  private:
   // image_fetcher::ImageFetcherDelegate implementation.
diff --git a/content/public/test/navigation_simulator.cc b/content/public/test/navigation_simulator.cc
index 339a2f7..8569b6a94 100644
--- a/content/public/test/navigation_simulator.cc
+++ b/content/public/test/navigation_simulator.cc
@@ -174,13 +174,13 @@
 }
 
 void NavigationSimulator::Redirect(const GURL& new_url) {
-  CHECK(state_ <= STARTED) << "NavigationSimulator::Redirect should be "
-                              "called before Fail or Commit";
+  CHECK_LE(state_, STARTED) << "NavigationSimulator::Redirect should be "
+                               "called before Fail or Commit";
   CHECK_EQ(0, num_did_finish_navigation_called_)
       << "NavigationSimulator::Redirect cannot be called after the "
          "navigation has finished";
 
-  if (state_ == INITIALIZATION) {
+  if (state_ < STARTED) {
     Start();
     if (state_ == FAILED)
       return;
@@ -232,15 +232,15 @@
   }
 }
 
-void NavigationSimulator::Commit() {
-  CHECK_LE(state_, STARTED) << "NavigationSimulator::Commit can only be "
-                               "called once, and cannot be called after "
+void NavigationSimulator::ReadyToCommit() {
+  CHECK_LE(state_, STARTED) << "NavigationSimulator::ReadyToCommit can only "
+                               "be called once, and cannot be called after "
                                "NavigationSimulator::Fail";
   CHECK_EQ(0, num_did_finish_navigation_called_)
-      << "NavigationSimulator::Commit cannot be called after the "
+      << "NavigationSimulator::ReadyToCommit cannot be called after the "
          "navigation has finished";
 
-  if (state_ == INITIALIZATION) {
+  if (state_ < STARTED) {
     Start();
     if (state_ == FAILED)
       return;
@@ -308,6 +308,22 @@
     CHECK(!handle_->is_transferring());
   }
   render_frame_host_ = new_render_frame_host;
+  state_ = READY_TO_COMMIT;
+}
+
+void NavigationSimulator::Commit() {
+  CHECK_LE(state_, READY_TO_COMMIT) << "NavigationSimulator::Commit can only "
+                                       "be called once, and cannot be called "
+                                       "after NavigationSimulator::Fail";
+  CHECK_EQ(0, num_did_finish_navigation_called_)
+      << "NavigationSimulator::Commit cannot be called after the navigation "
+         "has finished";
+
+  if (state_ < READY_TO_COMMIT) {
+    ReadyToCommit();
+    if (state_ == FAILED)
+      return;
+  }
 
   // Keep a pointer to the current RenderFrameHost that may be pending deletion
   // after commit.
@@ -648,7 +664,7 @@
 }
 
 RenderFrameHost* NavigationSimulator::GetFinalRenderFrameHost() {
-  CHECK_EQ(state_, FINISHED);
+  CHECK_GE(state_, READY_TO_COMMIT);
   return render_frame_host_;
 }
 
diff --git a/content/public/test/navigation_simulator.h b/content/public/test/navigation_simulator.h
index 29799b88..7b11024 100644
--- a/content/public/test/navigation_simulator.h
+++ b/content/public/test/navigation_simulator.h
@@ -115,6 +115,10 @@
   // Simulates a redirect to |new_url| for the navigation.
   virtual void Redirect(const GURL& new_url);
 
+  // Simulates receiving the navigation response and choosing a final
+  // RenderFrameHost to commit it.
+  virtual void ReadyToCommit();
+
   // Simulates the commit of the navigation in the RenderFrameHost.
   virtual void Commit();
 
@@ -215,6 +219,7 @@
   enum State {
     INITIALIZATION,
     STARTED,
+    READY_TO_COMMIT,
     FAILED,
     FINISHED,
   };
diff --git a/extensions/browser/app_window/app_delegate.h b/extensions/browser/app_window/app_delegate.h
index 1cdd50f2..ec5b8a3 100644
--- a/extensions/browser/app_window/app_delegate.h
+++ b/extensions/browser/app_window/app_delegate.h
@@ -83,6 +83,11 @@
   // Called when the app is hidden or shown.
   virtual void OnHide() = 0;
   virtual void OnShow() = 0;
+
+  // Called when app web contents finishes focus traversal - gives the delegate
+  // a chance to handle the focus change.
+  // Return whether focus has been handled.
+  virtual bool TakeFocus(content::WebContents* web_contents, bool reverse) = 0;
 };
 
 }  // namespace extensions
diff --git a/extensions/browser/app_window/app_window.cc b/extensions/browser/app_window/app_window.cc
index 80cc0ae..ed4ba26 100644
--- a/extensions/browser/app_window/app_window.cc
+++ b/extensions/browser/app_window/app_window.cc
@@ -440,6 +440,10 @@
                                                                 event_handler);
 }
 
+bool AppWindow::TakeFocus(WebContents* source, bool reverse) {
+  return app_delegate_->TakeFocus(source, reverse);
+}
+
 void AppWindow::RenderViewCreated(content::RenderViewHost* render_view_host) {
   app_delegate_->RenderViewCreated(render_view_host);
 }
diff --git a/extensions/browser/app_window/app_window.h b/extensions/browser/app_window/app_window.h
index 6022852..7d07ae89 100644
--- a/extensions/browser/app_window/app_window.h
+++ b/extensions/browser/app_window/app_window.h
@@ -437,6 +437,7 @@
   std::unique_ptr<content::BluetoothChooser> RunBluetoothChooser(
       content::RenderFrameHost* frame,
       const content::BluetoothChooser::EventHandler& event_handler) override;
+  bool TakeFocus(content::WebContents* source, bool reverse) override;
 
   // content::WebContentsObserver implementation.
   void RenderViewCreated(content::RenderViewHost* render_view_host) override;
diff --git a/extensions/shell/browser/shell_app_delegate.cc b/extensions/shell/browser/shell_app_delegate.cc
index 245cc2f..7509db5 100644
--- a/extensions/shell/browser/shell_app_delegate.cc
+++ b/extensions/shell/browser/shell_app_delegate.cc
@@ -101,4 +101,9 @@
   // manually or should it use a browser termination callback like Chrome?
 }
 
+bool ShellAppDelegate::TakeFocus(content::WebContents* web_contents,
+                                 bool reverse) {
+  return false;
+}
+
 }  // namespace extensions
diff --git a/extensions/shell/browser/shell_app_delegate.h b/extensions/shell/browser/shell_app_delegate.h
index ff124c1c..95ae1103 100644
--- a/extensions/shell/browser/shell_app_delegate.h
+++ b/extensions/shell/browser/shell_app_delegate.h
@@ -53,6 +53,7 @@
   void SetTerminatingCallback(const base::Closure& callback) override;
   void OnHide() override {}
   void OnShow() override {}
+  bool TakeFocus(content::WebContents* web_contents, bool reverse) override;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ShellAppDelegate);
diff --git a/ios/chrome/app/main_controller.mm b/ios/chrome/app/main_controller.mm
index ba738e58..9b64ed2 100644
--- a/ios/chrome/app/main_controller.mm
+++ b/ios/chrome/app/main_controller.mm
@@ -2467,6 +2467,8 @@
 }
 
 - (UIViewController*)topPresentedViewController {
+  // TODO(crbug.com/754642): Implement TopPresentedViewControllerFrom()
+  // privately.
   return top_view_controller::TopPresentedViewControllerFrom(
       self.mainViewController);
 }
diff --git a/ios/chrome/browser/ui/authentication/signin_interaction_controller.mm b/ios/chrome/browser/ui/authentication/signin_interaction_controller.mm
index 87e1799..84d7504e 100644
--- a/ios/chrome/browser/ui/authentication/signin_interaction_controller.mm
+++ b/ios/chrome/browser/ui/authentication/signin_interaction_controller.mm
@@ -221,6 +221,7 @@
       [weakSelf runCompletionCallbackWithSuccess:NO showAccountsSettings:NO];
     };
 
+    // TODO(crbug.com/754642): Stop using TopPresentedViewControllerFrom().
     alertCoordinator_ = ios_internal::ErrorCoordinator(
         error, dismissAction,
         top_view_controller::TopPresentedViewControllerFrom(viewController));
diff --git a/ios/chrome/browser/ui/payments/payment_request_can_make_payment_egtest.mm b/ios/chrome/browser/ui/payments/payment_request_can_make_payment_egtest.mm
index 53ef0bd7..dd86a6b 100644
--- a/ios/chrome/browser/ui/payments/payment_request_can_make_payment_egtest.mm
+++ b/ios/chrome/browser/ui/payments/payment_request_can_make_payment_egtest.mm
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/ios/ios_util.h"
 #include "components/autofill/core/browser/autofill_test_utils.h"
 #include "components/autofill/core/browser/credit_card.h"
 #import "ios/chrome/browser/ui/payments/payment_request_egtest_base.h"
@@ -111,6 +112,12 @@
 // Tests canMakePayment() exceeds query quota when different payment methods are
 // queried one after another.
 - (void)testCanMakePaymentExceedsQueryQuota {
+  if (!base::ios::IsRunningOnOrLater(10, 3, 0)) {
+    EARL_GREY_TEST_DISABLED(
+        @"Disabled on iOS versions below 10.3 because DOMException is not "
+        @"available.");
+  }
+
   [ChromeEarlGrey
       loadURL:web::test::HttpServer::MakeUrl(kCanMakePaymentCreditCardPage)];
 
@@ -161,6 +168,12 @@
 // Tests canMakePayment() exceeds query quota when different payment methods are
 // queried one after another.
 - (void)testCanMakePaymentExceedsQueryQuotaBasicaCard {
+  if (!base::ios::IsRunningOnOrLater(10, 3, 0)) {
+    EARL_GREY_TEST_DISABLED(
+        @"Disabled on iOS versions below 10.3 because DOMException is not "
+        @"available.");
+  }
+
   [ChromeEarlGrey loadURL:web::test::HttpServer::MakeUrl(
                               kCanMakePaymentMethodIdentifierPage)];
 
diff --git a/ios/chrome/browser/ui/payments/payment_request_cancel_pay_abort_egtest.mm b/ios/chrome/browser/ui/payments/payment_request_cancel_pay_abort_egtest.mm
index d075615..1d386452 100644
--- a/ios/chrome/browser/ui/payments/payment_request_cancel_pay_abort_egtest.mm
+++ b/ios/chrome/browser/ui/payments/payment_request_cancel_pay_abort_egtest.mm
@@ -4,6 +4,7 @@
 
 #include <vector>
 
+#include "base/ios/ios_util.h"
 #include "components/autofill/core/browser/autofill_profile.h"
 #include "components/autofill/core/browser/autofill_test_utils.h"
 #include "components/autofill/core/browser/credit_card.h"
@@ -107,6 +108,12 @@
 // Tests that tapping the cancel button closes the Payment Request UI and
 // rejects the Promise returned by request.show() with the appropriate error.
 - (void)testOpenAndCancel {
+  if (!base::ios::IsRunningOnOrLater(10, 3, 0)) {
+    EARL_GREY_TEST_DISABLED(
+        @"Disabled on iOS versions below 10.3 because DOMException is not "
+        @"available.");
+  }
+
   [ChromeEarlGrey loadURL:web::test::HttpServer::MakeUrl(kAbortPage)];
 
   [ChromeEarlGrey tapWebViewElementWithID:@"buy"];
@@ -133,6 +140,12 @@
 // rejects the Promise returned by request.show() with the appropriate error,
 // and displays the Autofill Settings UI.
 - (void)testOpenAndNavigateToSettings {
+  if (!base::ios::IsRunningOnOrLater(10, 3, 0)) {
+    EARL_GREY_TEST_DISABLED(
+        @"Disabled on iOS versions below 10.3 because DOMException is not "
+        @"available.");
+  }
+
   [ChromeEarlGrey loadURL:web::test::HttpServer::MakeUrl(kAbortPage)];
 
   [ChromeEarlGrey tapWebViewElementWithID:@"buy"];
diff --git a/ios/chrome/browser/ui/payments/payment_request_data_url_egtest.mm b/ios/chrome/browser/ui/payments/payment_request_data_url_egtest.mm
index 284c482..14dcb39 100644
--- a/ios/chrome/browser/ui/payments/payment_request_data_url_egtest.mm
+++ b/ios/chrome/browser/ui/payments/payment_request_data_url_egtest.mm
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/ios/ios_util.h"
 #import "ios/chrome/browser/ui/payments/payment_request_egtest_base.h"
 #import "ios/chrome/test/app/chrome_test_util.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
@@ -44,6 +45,12 @@
 // Tests that the Promise returned by show() gets rejected with a
 // NotSupportedError if the JS isContextSecure variable is not set in time.
 - (void)testShowDataURL {
+  if (!base::ios::IsRunningOnOrLater(10, 3, 0)) {
+    EARL_GREY_TEST_DISABLED(
+        @"Disabled on iOS versions below 10.3 because DOMException is not "
+        @"available.");
+  }
+
   [ChromeEarlGrey
       loadURL:GURL("data:text/html,<html><head><script>(new "
                    "PaymentRequest([{supportedMethods: ['basic-card']}], "
diff --git a/ios/chrome/browser/ui/qr_scanner/BUILD.gn b/ios/chrome/browser/ui/qr_scanner/BUILD.gn
index eac9145..7e74d40 100644
--- a/ios/chrome/browser/ui/qr_scanner/BUILD.gn
+++ b/ios/chrome/browser/ui/qr_scanner/BUILD.gn
@@ -52,6 +52,7 @@
     "//ios/chrome/app:app_internal",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser",
+    "//ios/chrome/browser/ui:ui",
     "//ios/chrome/browser/ui:ui_internal",
     "//ios/chrome/browser/ui/commands",
     "//ios/chrome/browser/ui/icons",
diff --git a/ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller_egtest.mm b/ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller_egtest.mm
index 962b623..9644ef01b 100644
--- a/ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller_egtest.mm
+++ b/ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller_egtest.mm
@@ -6,6 +6,7 @@
 #import <EarlGrey/EarlGrey.h>
 #import <UIKit/UIKit.h>
 
+#include "base/ios/ios_util.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "components/strings/grit/components_strings.h"
@@ -19,6 +20,7 @@
 #include "ios/chrome/browser/ui/qr_scanner/qr_scanner_view.h"
 #include "ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller.h"
 #include "ios/chrome/browser/ui/toolbar/web_toolbar_controller.h"
+#include "ios/chrome/browser/ui/ui_util.h"
 #include "ios/chrome/grit/ios_chromium_strings.h"
 #include "ios/chrome/grit/ios_strings.h"
 #import "ios/chrome/test/app/chrome_test_util.h"
@@ -808,6 +810,12 @@
 // Test that the correct page is loaded if the scanner result is a URL which is
 // then manually edited.
 - (void)testReceivingQRScannerURLResultAndEditingTheURL {
+  // TODO(crbug.com/753098): Re-enable this test on iOS 11 iPad once
+  // grey_typeText works on iOS 11.
+  if (base::ios::IsRunningOnIOS11OrLater() && IsIPadIdiom()) {
+    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 11.");
+  }
+
   [self doTestReceivingResult:_testURL.GetContent()
                      response:kTestURLEditedResponse
                          edit:@"\b\bedited/"];
@@ -822,6 +830,12 @@
 // Test that the correct page is loaded if the scanner result is a search query
 // which is then manually edited.
 - (void)testReceivingQRScannerSearchQueryResultAndEditingTheQuery {
+  // TODO(crbug.com/753098): Re-enable this test on iOS 11 iPad once
+  // grey_typeText works on iOS 11.
+  if (base::ios::IsRunningOnIOS11OrLater() && IsIPadIdiom()) {
+    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 11.");
+  }
+
   [self swizzleWebToolbarControllerLoadGURLFromLocationBar:_testQueryEdited];
   [self doTestReceivingResult:kTestQuery
                      response:kTestQueryEditedResponse
diff --git a/ios/chrome/browser/ui/settings/passwords_settings_egtest.mm b/ios/chrome/browser/ui/settings/passwords_settings_egtest.mm
index e413c9e..ea10af5 100644
--- a/ios/chrome/browser/ui/settings/passwords_settings_egtest.mm
+++ b/ios/chrome/browser/ui/settings/passwords_settings_egtest.mm
@@ -376,6 +376,7 @@
 MockReauthenticationModule* SetUpAndReturnMockReauthenticationModule() {
   MockReauthenticationModule* mock_reauthentication_module =
       [[MockReauthenticationModule alloc] init];
+  // TODO(crbug.com/754642): Stop using TopPresentedViewController();
   SettingsNavigationController* settings_navigation_controller =
       base::mac::ObjCCastStrict<SettingsNavigationController>(
           top_view_controller::TopPresentedViewController());
diff --git a/ios/chrome/browser/ui/util/top_view_controller.h b/ios/chrome/browser/ui/util/top_view_controller.h
index c0d3a343..0e3b2af3 100644
--- a/ios/chrome/browser/ui/util/top_view_controller.h
+++ b/ios/chrome/browser/ui/util/top_view_controller.h
@@ -9,9 +9,12 @@
 
 namespace top_view_controller {
 
+// DEPRECATED -- do not add further usage of these functions.
+// TODO(crbug.com/754642): Remove TopPresentedViewControllerFrom().
 UIViewController* TopPresentedViewControllerFrom(
     UIViewController* base_view_controller);
 
+// TODO(crbug.com/754642): Remove TopPresentedViewController().
 UIViewController* TopPresentedViewController();
 
 }  // namespace top_view_controller
diff --git a/ios/chrome/browser/web/repost_form_tab_helper.mm b/ios/chrome/browser/web/repost_form_tab_helper.mm
index 51fdb8bf..ea4e198e 100644
--- a/ios/chrome/browser/web/repost_form_tab_helper.mm
+++ b/ios/chrome/browser/web/repost_form_tab_helper.mm
@@ -17,6 +17,7 @@
 void RepostFormTabHelper::PresentDialog(
     CGPoint location,
     const base::Callback<void(bool)>& callback) {
+  // TODO(crbug.com/754642): Stop using TopPresentedViewControllerFrom().
   UIViewController* top_controller =
       top_view_controller::TopPresentedViewControllerFrom(
           [UIApplication sharedApplication].keyWindow.rootViewController);
diff --git a/ios/clean/chrome/app/BUILD.gn b/ios/clean/chrome/app/BUILD.gn
index 115ce97..e261dfe 100644
--- a/ios/clean/chrome/app/BUILD.gn
+++ b/ios/clean/chrome/app/BUILD.gn
@@ -98,13 +98,14 @@
     "application_phase.h",
     "application_state.h",
     "application_state.mm",
-    "application_step.h",
   ]
 
   configs += [ "//build/config/compiler:enable_arc" ]
 
   deps = [
     "//base",
+    "//ios/clean/chrome/app/steps:step_runner",
+    "//ios/clean/chrome/app/steps:steps",
     "//ios/shared/chrome/browser/ui/coordinators",
     "//ios/testing/perf:startup",
   ]
@@ -120,7 +121,10 @@
 
   deps = [
     ":application_state",
-    "//ios/clean/chrome/app/steps",
+    "//base",
+    "//ios/clean/chrome/app/steps:step_runner",
+    "//ios/clean/chrome/app/steps:steps",
+    "//ios/clean/chrome/browser",
     "//ios/testing/perf:startup",
   ]
 }
diff --git a/ios/clean/chrome/app/app_delegate.mm b/ios/clean/chrome/app/app_delegate.mm
index 1325e27..bc319283 100644
--- a/ios/clean/chrome/app/app_delegate.mm
+++ b/ios/clean/chrome/app/app_delegate.mm
@@ -4,11 +4,10 @@
 
 #import "ios/clean/chrome/app/app_delegate.h"
 
+#include "base/logging.h"
+#include "base/strings/sys_string_conversions.h"
 #import "ios/clean/chrome/app/application_state.h"
-#import "ios/clean/chrome/app/steps/launch_to_background.h"
-#import "ios/clean/chrome/app/steps/launch_to_basic.h"
-#import "ios/clean/chrome/app/steps/launch_to_foreground.h"
-#import "ios/clean/chrome/app/steps/root_coordinator+application_step.h"
+#import "ios/clean/chrome/browser/url_opening.h"
 #import "ios/testing/perf/startupLoggers.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -29,10 +28,24 @@
     didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
   startup_loggers::RegisterAppDidFinishLaunchingTime();
   self.applicationState = [[ApplicationState alloc] init];
-  self.applicationState.application = application;
-  [self configureApplicationState];
+  [self.applicationState configure];
+  self.applicationState.launchOptions = launchOptions;
 
-  [self.applicationState launchWithOptions:launchOptions];
+  switch (application.applicationState) {
+    case UIApplicationStateBackground:
+      // The app is launching in the background.
+      self.applicationState.phase = APPLICATION_BACKGROUNDED;
+      break;
+    case UIApplicationStateInactive:
+      // The app is launching in the foreground but hasn't become active yet.
+      self.applicationState.phase = APPLICATION_FOREGROUNDED;
+      break;
+    case UIApplicationStateActive:
+      // This should never happen.
+      NOTREACHED() << "Application unexpectedly active in "
+                   << base::SysNSStringToUTF8(NSStringFromSelector(_cmd));
+  }
+
   return YES;
 }
 
@@ -50,6 +63,7 @@
 }
 
 - (void)applicationWillTerminate:(UIApplication*)application {
+  self.applicationState.phase = APPLICATION_TERMINATING;
 }
 
 - (void)applicationDidReceiveMemoryWarning:(UIApplication*)application {
@@ -94,27 +108,4 @@
   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],
-    [[PrepareForUI alloc] init],
-    [[CompleteForegrounding alloc] init],
-    [[RootCoordinator alloc] init],
-    [[DebuggingInformationOverlay alloc] init],
-  ]];
-}
-
 @end
diff --git a/ios/clean/chrome/app/application_phase.h b/ios/clean/chrome/app/application_phase.h
index c7d91eaf..c7b82790 100644
--- a/ios/clean/chrome/app/application_phase.h
+++ b/ios/clean/chrome/app/application_phase.h
@@ -9,7 +9,7 @@
 // states.
 typedef NS_ENUM(NSUInteger, ApplicationPhase) {
   // The initial state of the application performing a cold launch.
-  APPLICATION_COLD,
+  APPLICATION_COLD = 0,
   // 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.
diff --git a/ios/clean/chrome/app/application_state.h b/ios/clean/chrome/app/application_state.h
index 00863261..38d07ca 100644
--- a/ios/clean/chrome/app/application_state.h
+++ b/ios/clean/chrome/app/application_state.h
@@ -8,101 +8,53 @@
 #import <UIKit/UIKit.h>
 
 #import "ios/clean/chrome/app/application_phase.h"
+#import "ios/clean/chrome/app/steps/phased_step_runner.h"
 
-namespace base {
-class SupportsUserData;
-}
+// This class manages the application's transitions from one state to another,
+// and keeps pointers to some global objects.
+// This class is a specialization of PhasedStepRunner (and thus it conforms to
+// StepContext). It uses the ApplicationPhase enum as the type of its |phase|
+// property, and all of its phase transitions should be expressed in terms of
+// those phases.
+//
+// See PhasedStepRunner's header documentation for information on how steps
+// are ordered and executed, and the distinctions between "features", "steps",
+// and "jobs" in this context.
+//
+// This class's -configure method will create all of the neccessary application
+// steps, and define the steps to execute in response to each phase change.
+//
+// The general process to add new steps that will be handled by this object is:
+//  (1) Implement the step as class in chrome/app/steps/, either subclassing
+//      SimpleApplicationStep or just conforming to ApplicationStep.
+//  (2) Add a feature string for the feature the step enables to step_features.h
+//  (3) Be sure your implementation sets this feature as its |providedFeature|.
+//  (4) Be sure your implementation sets the features it requires as its
+//      |requiredFeatures|.
+//  (5) If other features directly require your feature, be sure to add your
+//      feature to their |requiredFeatures| arrays.
+//  (6) Update [StepCollections +allApplicationSteps] to include an instance of
+//      your step.
+//  (7) If your step must run in one or more phases, and isn't dependent on
+//      a feature already (transitively) required for that phase, add the
+//      necessare -requireFeature:forPhase: calls into this class's -configure
+//      method.
 
-namespace ios {
-class ChromeBrowserState;
-}
+@interface ApplicationState : PhasedStepRunner
 
-@protocol ApplicationStep;
-@protocol URLOpening;
+// StepContext property redeclared readwrite.
+@property(nonatomic, readwrite, copy) NSDictionary* launchOptions;
 
-typedef NSMutableArray<id<ApplicationStep>> ApplicationStepArray;
+// StepContext property redeclared with enum type.
+@property(nonatomic) ApplicationPhase phase;
 
-// 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
+// Optional StepContext properties explicitly declared here so
+// direct users of this class don't need to check for support.
+@property(nonatomic) ios::ChromeBrowserState* browserState;
 
-// 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;
-
-// Persistent 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;
+- (void)configure;
 
 @end
 
diff --git a/ios/clean/chrome/app/application_state.mm b/ios/clean/chrome/app/application_state.mm
index e41f397b..1716e831 100644
--- a/ios/clean/chrome/app/application_state.mm
+++ b/ios/clean/chrome/app/application_state.mm
@@ -6,101 +6,31 @@
 
 #include <memory>
 
-#include "base/logging.h"
-#include "base/memory/ptr_util.h"
-#include "base/supports_user_data.h"
-#import "ios/clean/chrome/app/application_step.h"
+#import "ios/clean/chrome/app/application_state.h"
+#import "ios/clean/chrome/app/steps/step_collections.h"
+#import "ios/clean/chrome/app/steps/step_features.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
-namespace {
+@implementation ApplicationState
+@dynamic phase;
 
-// 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;
+- (void)configure {
+  [self addSteps:[StepCollections allApplicationSteps]];
+  [self requireFeature:step_features::kBrowserState
+              forPhase:APPLICATION_BACKGROUNDED];
+  [self requireFeature:step_features::kRootCoordinatorStarted
+              forPhase:APPLICATION_FOREGROUNDED];
+  [self requireFeature:step_features::kChromeMainStopped
+              forPhase:APPLICATION_TERMINATING];
 }
 
-#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/clean/chrome/app/application_step.h b/ios/clean/chrome/app/application_step.h
deleted file mode 100644
index be05b08..0000000
--- a/ios/clean/chrome/app/application_step.h
+++ /dev/null
@@ -1,26 +0,0 @@
-// 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_CLEAN_CHROME_APP_APPLICATION_STEP_H_
-#define IOS_CLEAN_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_CLEAN_CHROME_APP_APPLICATION_STEP_H_
diff --git a/ios/clean/chrome/app/steps/BUILD.gn b/ios/clean/chrome/app/steps/BUILD.gn
index f78ecdc..a9a6b5e 100644
--- a/ios/clean/chrome/app/steps/BUILD.gn
+++ b/ios/clean/chrome/app/steps/BUILD.gn
@@ -2,22 +2,50 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-source_set("steps") {
+source_set("step_runner") {
   sources = [
-    "launch_to_background.h",
-    "launch_to_background.mm",
-    "launch_to_basic.h",
-    "launch_to_basic.mm",
-    "launch_to_foreground.h",
-    "launch_to_foreground.mm",
-    "root_coordinator+application_step.h",
-    "root_coordinator+application_step.mm",
+    "application_step.h",
+    "phased_step_runner.h",
+    "phased_step_runner.mm",
+    "step_context.h",
   ]
 
   configs += [ "//build/config/compiler:enable_arc" ]
 
   deps = [
     "//base",
+  ]
+}
+
+source_set("steps") {
+  sources = [
+    "browser_state_setter.h",
+    "browser_state_setter.mm",
+    "bundle_and_defaults_configurator.h",
+    "bundle_and_defaults_configurator.mm",
+    "chrome_main.h",
+    "chrome_main.mm",
+    "foregrounder.h",
+    "foregrounder.mm",
+    "provider_initializer.h",
+    "provider_initializer.mm",
+    "root_coordinator_initializer.h",
+    "root_coordinator_initializer.mm",
+    "simple_application_step.h",
+    "simple_application_step.mm",
+    "step_collections.h",
+    "step_collections.mm",
+    "step_features.h",
+    "step_features.mm",
+    "ui_initializer.h",
+    "ui_initializer.mm",
+  ]
+
+  configs += [ "//build/config/compiler:enable_arc" ]
+
+  deps = [
+    ":step_runner",
+    "//base",
     "//components/content_settings/core/browser",
     "//ios/chrome/app:app_internal",
     "//ios/chrome/app/startup",
@@ -29,7 +57,6 @@
     "//ios/chrome/browser/content_settings",
     "//ios/chrome/browser/web:web_internal",
     "//ios/chrome/browser/web_state_list",
-    "//ios/clean/chrome/app:application_state",
     "//ios/clean/chrome/browser/ui/root",
     "//ios/net",
     "//ios/shared/chrome/browser/ui/browser_list",
@@ -42,14 +69,16 @@
   testonly = true
 
   sources = [
-    "root_coordinator+application_step_unittest.mm",
+    "phased_step_runner_unittest.mm",
   ]
 
   configs += [ "//build/config/compiler:enable_arc" ]
 
   deps = [
+    ":step_runner",
     ":steps",
     "//base",
+    "//ios/chrome/test/base",
     "//ios/clean/chrome/app:application_state",
     "//testing/gtest",
     "//third_party/ocmock",
diff --git a/ios/clean/chrome/app/steps/application_step.h b/ios/clean/chrome/app/steps/application_step.h
new file mode 100644
index 0000000..07beeb2b
--- /dev/null
+++ b/ios/clean/chrome/app/steps/application_step.h
@@ -0,0 +1,52 @@
+// Copyright 2017 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_CLEAN_CHROME_APP_STEPS_APPLICATION_STEP_H_
+#define IOS_CLEAN_CHROME_APP_STEPS_APPLICATION_STEP_H_
+
+#import <Foundation/Foundation.h>
+
+@protocol StepContext;
+
+// Objects that perform application state change steps must conform to this
+// protocol.
+@protocol ApplicationStep<NSObject>
+
+// The feature(s) that running this step provides. Other steps that require
+// one of these features will cause this step to be run before they run.
+// Steps that do not return at least one value for this property will
+// never be run.
+// Steps should always return the same values for this property each time it
+// is accessed.
+@property(nonatomic, readonly) NSArray<NSString*>* providedFeatures;
+
+// The features that this step requires in order to run. This array can be
+// empty (or nil), indicating that this step has no dependencies.
+// Steps should always return the same values for this property each time it
+// is accessed.
+@property(nonatomic, readonly) NSArray<NSString*>* requiredFeatures;
+
+// YES if this step must run synchronously with other steps on the main thread.
+// NO if this step can run on another thread. Steps will wait for their
+// dependencies to complete regardless of which threads they are running on.
+// When in doubt, return YES.
+// Steps should always return the same values for this property each time it
+// is accessed.
+@property(nonatomic, readonly) BOOL synchronous;
+
+// Runs the step.
+// This method will be called when the step is executing. A given step
+// may have this method called multiple times in its lifetime, depending on
+// how the step runner is configured. It is up to the step's implementation
+// to manage being run multiple times.
+// A step will only be called once for each phase change, however.
+// |feature| will be the feature this step was run to satisfy a requirement
+// for. If a feature has only one |prividedFeature|, |feature| will always
+// be that value.
+// |context| is the StepContext object the step can operate on.
+- (void)runFeature:(NSString*)feature withContext:(id<StepContext>)context;
+
+@end
+
+#endif  // IOS_CLEAN_CHROME_APP_STEPS_APPLICATION_STEP_H_
diff --git a/ios/clean/chrome/app/steps/browser_state_setter.h b/ios/clean/chrome/app/steps/browser_state_setter.h
new file mode 100644
index 0000000..14180c1
--- /dev/null
+++ b/ios/clean/chrome/app/steps/browser_state_setter.h
@@ -0,0 +1,16 @@
+// Copyright 2017 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_CLEAN_CHROME_APP_STEPS_BROWSER_STATE_SETTER_H_
+#define IOS_CLEAN_CHROME_APP_STEPS_BROWSER_STATE_SETTER_H_
+
+#import <Foundation/Foundation.h>
+
+#import "ios/clean/chrome/app/steps/application_step.h"
+#import "ios/clean/chrome/app/steps/simple_application_step.h"
+
+@interface BrowserStateSetter : SimpleApplicationStep<ApplicationStep>
+@end
+
+#endif  // IOS_CLEAN_CHROME_APP_STEPS_BROWSER_STATE_SETTER_H_
diff --git a/ios/clean/chrome/app/steps/browser_state_setter.mm b/ios/clean/chrome/app/steps/browser_state_setter.mm
new file mode 100644
index 0000000..52f406c
--- /dev/null
+++ b/ios/clean/chrome/app/steps/browser_state_setter.mm
@@ -0,0 +1,36 @@
+// Copyright 2017 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/clean/chrome/app/steps/browser_state_setter.h"
+
+#include "base/logging.h"
+#include "ios/chrome/browser/application_context.h"
+#include "ios/chrome/browser/browser_state/chrome_browser_state_manager.h"
+#import "ios/clean/chrome/app/steps/step_context.h"
+#import "ios/clean/chrome/app/steps/step_features.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@implementation BrowserStateSetter
+
+- (instancetype)init {
+  if ((self = [super init])) {
+    self.providedFeature = step_features::kBrowserState;
+    self.requiredFeatures = @[
+      step_features::kChromeMainStarted, step_features::kBundleAndDefaults
+    ];
+  }
+  return self;
+}
+
+- (void)runFeature:(NSString*)feature withContext:(id<StepContext>)context {
+  DCHECK([context respondsToSelector:@selector(setBrowserState:)]);
+  context.browserState = GetApplicationContext()
+                             ->GetChromeBrowserStateManager()
+                             ->GetLastUsedBrowserState();
+}
+
+@end
diff --git a/ios/clean/chrome/app/steps/bundle_and_defaults_configurator.h b/ios/clean/chrome/app/steps/bundle_and_defaults_configurator.h
new file mode 100644
index 0000000..a7849f66
--- /dev/null
+++ b/ios/clean/chrome/app/steps/bundle_and_defaults_configurator.h
@@ -0,0 +1,17 @@
+// Copyright 2017 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_CLEAN_CHROME_APP_STEPS_BUNDLE_AND_DEFAULTS_CONFIGURATOR_H_
+#define IOS_CLEAN_CHROME_APP_STEPS_BUNDLE_AND_DEFAULTS_CONFIGURATOR_H_
+
+#import <Foundation/Foundation.h>
+
+#import "ios/clean/chrome/app/steps/application_step.h"
+#import "ios/clean/chrome/app/steps/simple_application_step.h"
+
+@interface BundleAndDefaultsConfigurator
+    : SimpleApplicationStep<ApplicationStep>
+@end
+
+#endif  // IOS_CLEAN_CHROME_APP_STEPS_BUNDLE_AND_DEFAULTS_CONFIGURATOR_H_
diff --git a/ios/clean/chrome/app/steps/bundle_and_defaults_configurator.mm b/ios/clean/chrome/app/steps/bundle_and_defaults_configurator.mm
new file mode 100644
index 0000000..5c7e928
--- /dev/null
+++ b/ios/clean/chrome/app/steps/bundle_and_defaults_configurator.mm
@@ -0,0 +1,40 @@
+// Copyright 2017 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/clean/chrome/app/steps/bundle_and_defaults_configurator.h"
+
+#include "base/mac/bundle_locations.h"
+#include "base/mac/foundation_util.h"
+#include "base/strings/sys_string_conversions.h"
+#import "ios/chrome/app/startup/register_experimental_settings.h"
+#import "ios/clean/chrome/app/steps/step_features.h"
+
+@protocol StepContext;
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@implementation BundleAndDefaultsConfigurator
+
+- (instancetype)init {
+  if ((self = [super init])) {
+    self.providedFeature = step_features::kBundleAndDefaults;
+  }
+  return self;
+}
+
+- (void)runFeature:(NSString*)feature withContext:(id<StepContext>)context {
+  NSBundle* baseBundle = base::mac::OuterBundle();
+  base::mac::SetBaseBundleID(
+      base::SysNSStringToUTF8([baseBundle bundleIdentifier]).c_str());
+
+  [RegisterExperimentalSettings
+      registerExperimentalSettingsWithUserDefaults:[NSUserDefaults
+                                                       standardUserDefaults]
+                                            bundle:base::mac::
+                                                       FrameworkBundle()];
+}
+
+@end
diff --git a/ios/clean/chrome/app/steps/chrome_main.h b/ios/clean/chrome/app/steps/chrome_main.h
new file mode 100644
index 0000000..8f0429e
--- /dev/null
+++ b/ios/clean/chrome/app/steps/chrome_main.h
@@ -0,0 +1,16 @@
+// Copyright 2017 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_CLEAN_CHROME_APP_STEPS_CHROME_MAIN_H_
+#define IOS_CLEAN_CHROME_APP_STEPS_CHROME_MAIN_H_
+
+#import <Foundation/Foundation.h>
+
+#import "ios/clean/chrome/app/steps/application_step.h"
+#import "ios/clean/chrome/app/steps/simple_application_step.h"
+
+@interface ChromeMain : SimpleApplicationStep<ApplicationStep>
+@end
+
+#endif  // IOS_CLEAN_CHROME_APP_STEPS_CHROME_MAIN_H_
diff --git a/ios/clean/chrome/app/steps/chrome_main.mm b/ios/clean/chrome/app/steps/chrome_main.mm
new file mode 100644
index 0000000..3a2bd0c
--- /dev/null
+++ b/ios/clean/chrome/app/steps/chrome_main.mm
@@ -0,0 +1,43 @@
+// Copyright 2017 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/clean/chrome/app/steps/chrome_main.h"
+
+#include "base/memory/ptr_util.h"
+#include "ios/chrome/app/startup/ios_chrome_main.h"
+#import "ios/chrome/browser/web/chrome_web_client.h"
+#import "ios/clean/chrome/app/steps/step_features.h"
+#include "ios/web/public/web_client.h"
+
+@protocol StepContext;
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@implementation ChromeMain {
+  std::unique_ptr<IOSChromeMain> _chromeMain;
+}
+
+- (instancetype)init {
+  if ((self = [super init])) {
+    self.providedFeatures = @[
+      step_features::kChromeMainStarted, step_features::kChromeMainStopped
+    ];
+    self.requiredFeatures = @[ step_features::kProviders ];
+  }
+  return self;
+}
+
+- (void)runFeature:(NSString*)feature withContext:(id<StepContext>)context {
+  if ([feature isEqualToString:step_features::kChromeMainStarted]) {
+    web::SetWebClient(new ChromeWebClient());
+    _chromeMain = base::MakeUnique<IOSChromeMain>();
+  } else {
+    DCHECK([feature isEqualToString:step_features::kChromeMainStopped]);
+    _chromeMain.reset();
+  }
+}
+
+@end
diff --git a/ios/clean/chrome/app/steps/foregrounder.h b/ios/clean/chrome/app/steps/foregrounder.h
new file mode 100644
index 0000000..a8db6d8
--- /dev/null
+++ b/ios/clean/chrome/app/steps/foregrounder.h
@@ -0,0 +1,16 @@
+// Copyright 2017 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_CLEAN_CHROME_APP_STEPS_FOREGROUNDER_H_
+#define IOS_CLEAN_CHROME_APP_STEPS_FOREGROUNDER_H_
+
+#import <Foundation/Foundation.h>
+
+#import "ios/clean/chrome/app/steps/application_step.h"
+#import "ios/clean/chrome/app/steps/simple_application_step.h"
+
+@interface Foregrounder : SimpleApplicationStep<ApplicationStep>
+@end
+
+#endif  // IOS_CLEAN_CHROME_APP_STEPS_FOREGROUNDER_H_
diff --git a/ios/clean/chrome/app/steps/foregrounder.mm b/ios/clean/chrome/app/steps/foregrounder.mm
new file mode 100644
index 0000000..6febc46
--- /dev/null
+++ b/ios/clean/chrome/app/steps/foregrounder.mm
@@ -0,0 +1,30 @@
+// Copyright 2017 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/clean/chrome/app/steps/foregrounder.h"
+
+#include "ios/chrome/browser/application_context.h"
+#import "ios/clean/chrome/app/steps/step_features.h"
+
+@protocol StepContext;
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+@implementation Foregrounder
+- (instancetype)init {
+  if ((self = [super init])) {
+    self.providedFeature = step_features::kForeground;
+    self.requiredFeatures = @[
+      step_features::kBundleAndDefaults, step_features::kChromeMainStarted,
+      step_features::kBrowserState, step_features::kChromeMainStarted
+    ];
+  }
+  return self;
+}
+
+- (void)runFeature:(NSString*)feature withContext:(id<StepContext>)context {
+  GetApplicationContext()->OnAppEnterForeground();
+}
+
+@end
diff --git a/ios/clean/chrome/app/steps/launch_to_background.h b/ios/clean/chrome/app/steps/launch_to_background.h
deleted file mode 100644
index 36914a4..0000000
--- a/ios/clean/chrome/app/steps/launch_to_background.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// 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_CLEAN_CHROME_APP_STEPS_LAUNCH_TO_BACKGROUND_H_
-#define IOS_CLEAN_CHROME_APP_STEPS_LAUNCH_TO_BACKGROUND_H_
-
-#import <Foundation/Foundation.h>
-
-#import "ios/clean/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_CLEAN_CHROME_APP_STEPS_LAUNCH_TO_BACKGROUND_H_
diff --git a/ios/clean/chrome/app/steps/launch_to_background.mm b/ios/clean/chrome/app/steps/launch_to_background.mm
deleted file mode 100644
index 9858c1d..0000000
--- a/ios/clean/chrome/app/steps/launch_to_background.mm
+++ /dev/null
@@ -1,127 +0,0 @@
-// 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/clean/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/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/clean/chrome/app/application_state.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,
-      base::MakeUnique<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/clean/chrome/app/steps/launch_to_basic.h b/ios/clean/chrome/app/steps/launch_to_basic.h
deleted file mode 100644
index eac1b603..0000000
--- a/ios/clean/chrome/app/steps/launch_to_basic.h
+++ /dev/null
@@ -1,21 +0,0 @@
-// 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_CLEAN_CHROME_APP_STEPS_LAUNCH_TO_BASIC_H_
-#define IOS_CLEAN_CHROME_APP_STEPS_LAUNCH_TO_BASIC_H_
-
-#import <Foundation/Foundation.h>
-
-#import "ios/clean/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_CLEAN_CHROME_APP_STEPS_LAUNCH_TO_BASIC_H_
diff --git a/ios/clean/chrome/app/steps/launch_to_basic.mm b/ios/clean/chrome/app/steps/launch_to_basic.mm
deleted file mode 100644
index 72d7943..0000000
--- a/ios/clean/chrome/app/steps/launch_to_basic.mm
+++ /dev/null
@@ -1,26 +0,0 @@
-// 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/clean/chrome/app/steps/launch_to_basic.h"
-
-#include "ios/chrome/app/startup/provider_registration.h"
-#include "ios/clean/chrome/app/application_state.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/clean/chrome/app/steps/launch_to_foreground.h b/ios/clean/chrome/app/steps/launch_to_foreground.h
deleted file mode 100644
index c8a1a75..0000000
--- a/ios/clean/chrome/app/steps/launch_to_foreground.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// 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_CLEAN_CHROME_APP_STEPS_LAUNCH_TO_FOREGROUND_H_
-#define IOS_CLEAN_CHROME_APP_STEPS_LAUNCH_TO_FOREGROUND_H_
-
-#import <Foundation/Foundation.h>
-
-#import "ios/clean/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
-
-// 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
-
-// Performs preparation steps for UIDebuggingInformationOverlay.
-//  Pre:  Application phase is APPLICATION_FOREGROUNDED.
-//  Post: Application phase is (still) APPLICATION_FOREGROUNDED.
-@interface DebuggingInformationOverlay : NSObject<ApplicationStep>
-@end
-
-#endif  // IOS_CLEAN_CHROME_APP_STEPS_LAUNCH_TO_FOREGROUND_H_
diff --git a/ios/clean/chrome/app/steps/launch_to_foreground.mm b/ios/clean/chrome/app/steps/launch_to_foreground.mm
deleted file mode 100644
index 4647934..0000000
--- a/ios/clean/chrome/app/steps/launch_to_foreground.mm
+++ /dev/null
@@ -1,92 +0,0 @@
-// 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/clean/chrome/app/steps/launch_to_foreground.h"
-
-#include "components/content_settings/core/browser/host_content_settings_map.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/clean/chrome/app/application_state.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 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:@"chrome://newtab"]
-                      options:@{}];
-  }
-  [super motionEnded:motion withEvent:event];
-}
-
-@end
-
-@implementation DebuggingInformationOverlay
-
-- (BOOL)canRunInState:(ApplicationState*)state {
-  return state.phase == APPLICATION_FOREGROUNDED;
-}
-
-- (void)runInState:(ApplicationState*)state {
-#ifndef NDEBUG
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
-  [NSClassFromString(@"UIDebuggingInformationOverlay")
-      performSelector:NSSelectorFromString(@"prepareDebuggingOverlay")];
-#pragma clang diagnostic pop
-#endif  // NDEBUG
-}
-
-@end
diff --git a/ios/clean/chrome/app/steps/phased_step_runner.h b/ios/clean/chrome/app/steps/phased_step_runner.h
new file mode 100644
index 0000000..591b8f7
--- /dev/null
+++ b/ios/clean/chrome/app/steps/phased_step_runner.h
@@ -0,0 +1,71 @@
+// Copyright 2017 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_CLEAN_CHROME_APP_STEPS_PHASED_STEP_RUNNER_H_
+#define IOS_CLEAN_CHROME_APP_STEPS_PHASED_STEP_RUNNER_H_
+
+#import <Foundation/Foundation.h>
+
+#import "ios/clean/chrome/app/steps/application_step.h"
+#import "ios/clean/chrome/app/steps/step_context.h"
+
+// This class implements a generic state machine that will execute one or more
+// "steps" in response to changes in its |phase| property. (The numeric |phase|
+// property is defined as part of the StepContext protocol.)
+//
+// Steps, their dependencies, and the mechanisms by which they are executed
+// are defined in terms of "features", "steps" and "jobs".
+//
+// A "feature" in this context is a NSString that refers to some action that a
+// step performs, or some global structure that it initializes. Features are
+// intentionally indirect labels for steps; they allow for dependencies to
+// be expressed between the steps while keeping the step implementations
+// encapsulated.
+//
+// A "step" is an object that conforms to ApplicationStep. A step defines one
+// or more features it provides, and may define any number of features it
+// requires as well. The step runner will use the dependencies expressed by
+// steps to determine the order that steps need to execute in.
+//
+// Once a step is added to the step runner, the step object will persist for
+// the lifetime of the step runner. If a step is run in multiple phases, it
+// is the same step object that is run each time. This allows a step to maintain
+// internal state.
+//
+// A "job" is the internal object that is used to encapsulate the steps that
+// need to run each time the step runner's phase changes. Jobs are created,
+// run at most once, and then disposed of.
+//
+// "Running a step" consists of calling the step's -runFeature:withContext:
+// method, passing in the feature that the step was run for, and the
+// current StepContext (the task runner itself). StepContext exposes the
+// step runner's phase as readwrite, which means that during the execution of
+// the steps triggered by a phase change, the phase might be changed to a new
+// value.
+//
+// If this happens, as soon as the phase is set to another value, all queued
+// jobs are cancelled, and new jobs are triggered for the new phase change.
+//
+// By defualt, steps are executed on the main thread. Steps that advertise
+// a |synchronous| property value of |NO| will execute on another thread.
+// The -setPhase: method of the phasedStepRunner will wait for all jobs to
+// complete before returning, regardless of the threads they run on.
+@interface PhasedStepRunner : NSObject<StepContext>
+
+// Adds |step| as one of the application steps that this runner knows about.
+// (The step will only be executed if it is required to do so in the
+// dependency graph for a phase change).
+// It is an error if |step| provides a feature that has already been provided
+// by a previously-added step.
+- (void)addStep:(id<ApplicationStep>)step;
+
+// Adds all of the steps in |steps|.
+- (void)addSteps:(NSArray<id<ApplicationStep>>*)steps;
+
+// Adds |feature| as a requirement when the receiver's phase changes to
+// |phase|.
+- (void)requireFeature:(NSString*)feature forPhase:(NSUInteger)phase;
+@end
+
+#endif  // IOS_CLEAN_CHROME_APP_STEPS_PHASED_STEP_RUNNER_H_
diff --git a/ios/clean/chrome/app/steps/phased_step_runner.mm b/ios/clean/chrome/app/steps/phased_step_runner.mm
new file mode 100644
index 0000000..6983b62
--- /dev/null
+++ b/ios/clean/chrome/app/steps/phased_step_runner.mm
@@ -0,0 +1,265 @@
+// Copyright 2017 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/clean/chrome/app/steps/phased_step_runner.h"
+
+#import "base/logging.h"
+#import "base/mac/foundation_util.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+// An ApplicationStep used to anchor the dependency graph for a phase change.
+// PhasedStepRunner will create a PhaseChangeStep for each phase that has
+// required features. All other steps for that phase change will be dependencies
+// of the PhaseChangeStep. Running the PhaseChangeStep will set the
+// PhasedStepRunner's |running| property to NO.
+@interface PhaseChangeStep : NSObject<ApplicationStep>
+// |runner| is the PhasedStepRunner whose |running| property should be unset
+// when this step runs.
+// |phase| is the phase that the PhasedStepRunner is expected to be in when
+// this step is run.
+- (instancetype)initWithRunner:(PhasedStepRunner*)runner
+                         phase:(NSUInteger)phase NS_DESIGNATED_INITIALIZER;
+- (instancetype)init NS_UNAVAILABLE;
+// Phase set in -initWithRunner:phase:
+@property(nonatomic, readonly) NSUInteger phase;
+// Runner set in -initWithRunner:phase:
+@property(nonatomic, weak) PhasedStepRunner* runner;
+// Features required for this phase.
+@property(nonatomic, readonly) NSMutableArray<NSString*>* requiredFeatures;
+@end
+
+// NSOperation subclass used for jobs.
+@interface StepOperation : NSOperation
+- (instancetype)initWithStep:(id<ApplicationStep>)step
+                     feature:(NSString*)feature
+                     context:(id<StepContext>)context NS_DESIGNATED_INITIALIZER;
+- (instancetype)init NS_UNAVAILABLE;
+@property(nonatomic, copy) NSString* feature;
+@property(nonatomic, readonly, weak) id<ApplicationStep> step;
+@property(nonatomic, readonly, weak) id<StepContext> context;
+@end
+
+@interface PhasedStepRunner ()
++ (NSString*)featureForChangeToPhase:(NSUInteger)phase;
+@property(nonatomic)
+    NSMutableDictionary<NSString*, id<ApplicationStep>>* featureProviders;
+@property(nonatomic) NSOperationQueue* queue;
+@property(nonatomic) NSMutableSet<StepOperation*>* jobs;
+@property(nonatomic, readonly) NSSet<StepOperation*>* readyJobs;
+@property(nonatomic) BOOL running;
+@end
+
+@implementation PhaseChangeStep
+@synthesize phase = _phase;
+@synthesize runner = _runner;
+@synthesize providedFeatures = _providedFeatures;
+@synthesize requiredFeatures = _requiredFeatures;
+@synthesize synchronous = _synchronous;
+
+- (instancetype)initWithRunner:(PhasedStepRunner*)runner
+                         phase:(NSUInteger)phase {
+  if ((self = [super init])) {
+    _runner = runner;
+    _phase = phase;
+    _providedFeatures = @[ [PhasedStepRunner featureForChangeToPhase:_phase] ];
+    _requiredFeatures = [[NSMutableArray alloc] init];
+    // Always synchronous.
+    _synchronous = YES;
+  }
+  return self;
+}
+
+- (void)runFeature:(NSString*)feature withContext:(id<StepContext>)context {
+  self.runner.running = NO;
+}
+
+@end
+
+@implementation StepOperation
+@synthesize step = _step;
+@synthesize feature = _feature;
+@synthesize context = _context;
+
+- (instancetype)initWithStep:(id<ApplicationStep>)step
+                     feature:(NSString*)feature
+                     context:(id<StepContext>)context {
+  if ((self = [super init])) {
+    _step = step;
+    _feature = feature;
+    _context = context;
+    self.name = feature;
+  }
+  return self;
+}
+
+- (void)main {
+  [self.step runFeature:self.feature withContext:self.context];
+}
+
+@end
+
+@implementation PhasedStepRunner
+
+@synthesize phase = _phase;
+@synthesize featureProviders = _featureProviders;
+@synthesize jobs = _jobs;
+@synthesize queue = _queue;
+@synthesize running = _running;
+@synthesize URLOpener = _URLOpener;
+
+- (instancetype)init {
+  if ((self = [super init])) {
+    _featureProviders = [[NSMutableDictionary alloc] init];
+    _jobs = [[NSMutableSet alloc] init];
+  }
+  return self;
+}
+
++ (NSString*)featureForChangeToPhase:(NSUInteger)phase {
+  return [NSString stringWithFormat:@"__PhaseChangeCompleteForPhase_%ld",
+                                    static_cast<unsigned long>(phase)];
+}
+
+- (void)addStep:(id<ApplicationStep>)step {
+  for (NSString* feature in step.providedFeatures) {
+    DCHECK(!self.featureProviders[feature])
+        << "Step provides duplicated feature.";
+    self.featureProviders[feature] = step;
+  }
+}
+
+- (void)addSteps:(NSArray<id<ApplicationStep>>*)steps {
+  for (id<ApplicationStep> step in steps) {
+    [self addStep:step];
+  }
+}
+
+- (void)requireFeature:(NSString*)feature forPhase:(NSUInteger)phase {
+  NSString* phaseFeature = [[self class] featureForChangeToPhase:phase];
+  PhaseChangeStep* changeStep =
+      base::mac::ObjCCast<PhaseChangeStep>(self.featureProviders[phaseFeature]);
+  if (!changeStep) {
+    changeStep = [[PhaseChangeStep alloc] initWithRunner:self phase:phase];
+    [self addStep:changeStep];
+  }
+  [changeStep.requiredFeatures addObject:feature];
+}
+
+- (void)setPhase:(NSUInteger)phase {
+  if (phase == _phase)
+    return;
+  _phase = phase;
+  if (self.running) {
+    [self stop];
+  } else {
+    [self run];
+  }
+}
+
+- (NSSet<NSOperation*>*)readyJobs {
+  return [self.jobs objectsPassingTest:^BOOL(NSOperation* job, BOOL* stop) {
+    return job.ready && !job.executing;
+  }];
+}
+
+- (NSArray<StepOperation*>*)jobsForCurrentPhase {
+  // Build the jobs.
+  LOG(ERROR) << "Building jobs for phase " << self.phase;
+  NSMutableDictionary<NSString*, StepOperation*>* featureJobs =
+      [[NSMutableDictionary alloc] init];
+  NSMutableSet<NSString*>* requiredFeatures = [NSMutableSet<NSString*>
+      setWithObject:[[self class] featureForChangeToPhase:self.phase]];
+
+  while (requiredFeatures.count) {
+    NSString* feature = [requiredFeatures anyObject];
+    id<ApplicationStep> step = self.featureProviders[feature];
+    DCHECK(step) << "No provider for feature " << feature.UTF8String;
+    if (!featureJobs[feature]) {
+      featureJobs[feature] = [[StepOperation alloc] initWithStep:step
+                                                         feature:feature
+                                                         context:self];
+    }
+    [requiredFeatures removeObject:feature];
+    for (NSString* requirement in step.requiredFeatures) {
+      [requiredFeatures addObject:requirement];
+    }
+  }
+
+  // Set up dependencies.
+  for (NSString* feature in featureJobs.allKeys) {
+    for (NSString* requirement in self.featureProviders[feature]
+             .requiredFeatures) {
+      [featureJobs[feature] addDependency:featureJobs[requirement]];
+    }
+  }
+
+  LOG(ERROR) << "Created " << featureJobs.allValues.count << " jobs";
+  return featureJobs.allValues;
+}
+
+- (void)stop {
+  LOG(ERROR) << "Stopping; " << self.jobs.count << " sync jobs, "
+             << self.queue.operationCount << " async";
+  [self.jobs removeAllObjects];
+  [self.queue cancelAllOperations];
+  [self.queue waitUntilAllOperationsAreFinished];
+  self.running = NO;
+}
+
+// Run all of the steps for the current phase.
+- (void)run {
+  [self.jobs removeAllObjects];
+  self.queue = [[NSOperationQueue alloc] init];
+  self.running = YES;
+  for (StepOperation* job in [self jobsForCurrentPhase]) {
+    if (job.step.synchronous)
+      [self.jobs addObject:job];
+    else
+      [self.queue addOperation:job];
+  }
+
+  // Watch all of the jobs running synchronously on this thread.
+  for (NSOperation* job in self.jobs) {
+    [job addObserver:self
+          forKeyPath:@"isFinished"
+             options:NSKeyValueObservingOptionNew
+             context:nullptr];
+  }
+
+  // Run the synchronous jobs.
+  LOG(ERROR) << "Running " << self.jobs.count << " sync jobs for phase "
+             << self.phase;
+  NSUInteger initialPhase = self.phase;
+  while (self.jobs.count) {
+    [[self.readyJobs anyObject] start];
+  }
+  DCHECK_EQ(self.queue.operationCount, 0UL);
+  DCHECK(!self.running);
+  self.queue = nil;
+
+  // If the phase was changed while running the jobs, then start over again.
+  if (initialPhase != self.phase) {
+    [self run];
+  }
+}
+
+- (void)observeValueForKeyPath:(NSString*)keyPath
+                      ofObject:(id)object
+                        change:(NSDictionary<NSKeyValueChangeKey, id>*)change
+                       context:(void*)context {
+  StepOperation* job = base::mac::ObjCCast<StepOperation>(object);
+  if (!job)
+    return;
+  if (job.finished) {
+    // When a job finishes, remove it.
+    [self.jobs removeObject:job];
+    // Also stop observing it.
+    [job removeObserver:self forKeyPath:@"isFinished"];
+  }
+}
+
+@end
diff --git a/ios/clean/chrome/app/steps/phased_step_runner_unittest.mm b/ios/clean/chrome/app/steps/phased_step_runner_unittest.mm
new file mode 100644
index 0000000..57a1b97af
--- /dev/null
+++ b/ios/clean/chrome/app/steps/phased_step_runner_unittest.mm
@@ -0,0 +1,396 @@
+// Copyright 2017 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/clean/chrome/app/steps/phased_step_runner.h"
+#import "ios/clean/chrome/app/steps/application_step.h"
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/gtest_mac.h"
+#include "testing/platform_test.h"
+
+// Step that provides a single feature.
+@interface TestStep : NSObject<ApplicationStep>
+@property(nonatomic) BOOL hasRun;
+@property(nonatomic) NSString* providedFeature;
+@property(nonatomic) NSMutableSet<TestStep*>* dependencies;
+@end
+
+@interface AsyncTestStep : TestStep
+@end
+
+@interface PhaseChangeTestStep : TestStep
+@property(nonatomic) NSUInteger newPhase;
+@end
+
+@implementation TestStep
+
+@synthesize providedFeature = _providedFeature;
+@synthesize hasRun = _hasRun;
+@synthesize dependencies = _dependencies;
+
+- (instancetype)init {
+  if ((self = [super init])) {
+    _dependencies = [[NSMutableSet alloc] init];
+  }
+  return self;
+}
+
+- (NSArray<NSString*>*)providedFeatures {
+  return @[ self.providedFeature ];
+}
+
+- (BOOL)synchronous {
+  return YES;
+}
+
+- (NSArray<NSString*>*)requiredFeatures {
+  NSMutableArray<NSString*>* deps = [[NSMutableArray alloc] init];
+  for (TestStep* dependency in self.dependencies) {
+    [deps addObject:dependency.providedFeature];
+  }
+  return deps;
+}
+
+- (void)checkDeps {
+  std::string mt = [NSThread isMainThread] ? "(mt) " : "(ot) ";
+  LOG(ERROR) << "Running " << mt << self.providedFeature.UTF8String;
+  EXPECT_FALSE(self.hasRun);
+  for (TestStep* dependency in self.dependencies) {
+    EXPECT_TRUE(dependency.hasRun);
+  }
+}
+
+- (void)runFeature:(NSString*)feature withContext:(id<StepContext>)context {
+  EXPECT_NSEQ(feature, self.providedFeature);
+  [self checkDeps];
+  self.hasRun = YES;
+}
+
+@end
+
+@implementation AsyncTestStep
+
+- (BOOL)synchronous {
+  return NO;
+}
+
+@end
+
+@implementation PhaseChangeTestStep
+@synthesize newPhase = _newPhase;
+
+- (void)runFeature:(NSString*)feature withContext:(id<StepContext>)context {
+  EXPECT_NSEQ(feature, self.providedFeature);
+  [self checkDeps];
+  context.phase = self.newPhase;
+  self.hasRun = YES;
+}
+
+@end
+
+TEST(PhasedStepRunnerTest, TestUnexecutedSteps) {
+  TestStep* step1 = [[TestStep alloc] init];
+  step1.providedFeature = @"feature_a";
+
+  TestStep* task2 = [[TestStep alloc] init];
+  task2.providedFeature = @"feature_b";
+
+  TestStep* task3 = [[TestStep alloc] init];
+  task3.providedFeature = @"feature_c";
+
+  NSUInteger testPhase = 1;
+  NSUInteger untestedPhase = 2;
+
+  PhasedStepRunner* runner = [[PhasedStepRunner alloc] init];
+  [runner addSteps:@[ step1, task2, task3 ]];
+  EXPECT_NE(testPhase, runner.phase);
+  [runner requireFeature:@"feature_a" forPhase:testPhase];
+  [runner requireFeature:@"feature_b" forPhase:untestedPhase];
+  EXPECT_FALSE(step1.hasRun);
+  EXPECT_FALSE(task2.hasRun);
+  EXPECT_FALSE(task3.hasRun);
+
+  runner.phase = testPhase;
+
+  EXPECT_EQ(runner.phase, testPhase);
+  EXPECT_TRUE(step1.hasRun);
+  EXPECT_FALSE(task2.hasRun);
+  EXPECT_FALSE(task3.hasRun);
+}
+
+// Simple dependency chain A-> B-> C
+TEST(PhasedStepRunnerTest, TestSimpleDependencies) {
+  TestStep* step1 = [[TestStep alloc] init];
+  step1.providedFeature = @"feature_a";
+
+  TestStep* task2 = [[TestStep alloc] init];
+  task2.providedFeature = @"feature_b";
+
+  TestStep* task3 = [[TestStep alloc] init];
+  task3.providedFeature = @"feature_c";
+
+  [step1.dependencies addObject:task2];
+  [task2.dependencies addObject:task3];
+
+  NSUInteger testPhase = 1;
+
+  PhasedStepRunner* runner = [[PhasedStepRunner alloc] init];
+  [runner addSteps:@[ step1, task2, task3 ]];
+  EXPECT_NE(testPhase, runner.phase);
+  [runner requireFeature:@"feature_a" forPhase:testPhase];
+  EXPECT_FALSE(step1.hasRun);
+  EXPECT_FALSE(task2.hasRun);
+  EXPECT_FALSE(task3.hasRun);
+
+  runner.phase = testPhase;
+
+  EXPECT_EQ(runner.phase, testPhase);
+  EXPECT_TRUE(step1.hasRun);
+  EXPECT_TRUE(task2.hasRun);
+  EXPECT_TRUE(task3.hasRun);
+}
+
+// Dependency graph: A->B; B->C; B->D, C->D.
+TEST(PhasedStepRunnerTest, TestDependencyGraph) {
+  TestStep* step1 = [[TestStep alloc] init];
+  step1.providedFeature = @"feature_a";
+
+  TestStep* task2 = [[TestStep alloc] init];
+  task2.providedFeature = @"feature_b";
+
+  TestStep* task3 = [[TestStep alloc] init];
+  task3.providedFeature = @"feature_c";
+
+  TestStep* task4 = [[TestStep alloc] init];
+  task4.providedFeature = @"feature_d";
+
+  [step1.dependencies addObject:task2];
+  [step1.dependencies addObject:task3];
+  [task2.dependencies addObject:task4];
+  [task3.dependencies addObject:task4];
+
+  NSUInteger testPhase = 1;
+
+  PhasedStepRunner* runner = [[PhasedStepRunner alloc] init];
+  [runner addSteps:@[ step1, task2, task3, task4 ]];
+  EXPECT_NE(testPhase, runner.phase);
+  [runner requireFeature:@"feature_a" forPhase:testPhase];
+  EXPECT_FALSE(step1.hasRun);
+  EXPECT_FALSE(task2.hasRun);
+  EXPECT_FALSE(task3.hasRun);
+  EXPECT_FALSE(task4.hasRun);
+  runner.phase = testPhase;
+
+  EXPECT_EQ(runner.phase, testPhase);
+  EXPECT_TRUE(step1.hasRun);
+  EXPECT_TRUE(task2.hasRun);
+  EXPECT_TRUE(task3.hasRun);
+  EXPECT_TRUE(task4.hasRun);
+}
+
+TEST(PhasedStepRunnerTest, TestOneAsyncAction) {
+  AsyncTestStep* step1 = [[AsyncTestStep alloc] init];
+  step1.providedFeature = @"feature_a";
+  NSUInteger testPhase = 1;
+
+  PhasedStepRunner* runner = [[PhasedStepRunner alloc] init];
+  [runner addStep:step1];
+  EXPECT_NE(testPhase, runner.phase);
+  [runner requireFeature:@"feature_a" forPhase:testPhase];
+  EXPECT_FALSE(step1.hasRun);
+  runner.phase = testPhase;
+  EXPECT_EQ(runner.phase, testPhase);
+  EXPECT_TRUE(step1.hasRun);
+}
+
+TEST(PhasedStepRunnerTest, TestAsyncDependencies) {
+  AsyncTestStep* step1 = [[AsyncTestStep alloc] init];
+  step1.providedFeature = @"feature_a";
+
+  TestStep* task2 = [[TestStep alloc] init];
+  task2.providedFeature = @"feature_b";
+
+  AsyncTestStep* task3 = [[AsyncTestStep alloc] init];
+  task3.providedFeature = @"feature_c";
+
+  [step1.dependencies addObject:task2];
+  [task2.dependencies addObject:task3];
+
+  NSUInteger testPhase = 1;
+
+  PhasedStepRunner* runner = [[PhasedStepRunner alloc] init];
+  [runner addSteps:@[ step1, task2, task3 ]];
+  EXPECT_NE(testPhase, runner.phase);
+  [runner requireFeature:@"feature_a" forPhase:testPhase];
+  EXPECT_FALSE(step1.hasRun);
+  EXPECT_FALSE(task2.hasRun);
+  EXPECT_FALSE(task3.hasRun);
+
+  runner.phase = testPhase;
+
+  EXPECT_EQ(runner.phase, testPhase);
+  EXPECT_TRUE(step1.hasRun);
+  EXPECT_TRUE(task2.hasRun);
+  EXPECT_TRUE(task3.hasRun);
+}
+
+TEST(PhasedStepRunnerTest, TestManyDependencies) {
+  NSMutableArray<TestStep*>* tasks = [[NSMutableArray alloc] init];
+  AsyncTestStep* root_task = [[AsyncTestStep alloc] init];
+  root_task.providedFeature = @"feature_a";
+  [tasks addObject:root_task];
+
+  for (int i = 0; i < 5; i++) {
+    AsyncTestStep* task = [[AsyncTestStep alloc] init];
+    task.providedFeature = [NSString stringWithFormat:@"feature_%d_", i];
+    [root_task.dependencies addObject:task];
+    for (int j = 0; j < 5; j++) {
+      TestStep* task2 = [[TestStep alloc] init];
+      task2.providedFeature =
+          [NSString stringWithFormat:@"feature_%d_%d", i, j];
+      [task.dependencies addObject:task2];
+      for (int k = 0; k < 5; k++) {
+        AsyncTestStep* task3 = [[AsyncTestStep alloc] init];
+        task3.providedFeature =
+            [NSString stringWithFormat:@"feature_%d_%d_%d", i, j, k];
+        [task2.dependencies addObject:task3];
+        [tasks addObject:task3];
+      }
+      [tasks addObject:task2];
+    }
+    [tasks addObject:task];
+  }
+
+  for (int i = 0; i < 20; i++) {
+    NSUInteger index1 = arc4random() % [tasks count];
+    NSUInteger index2 = arc4random() % [tasks count];
+    AsyncTestStep* atask = [[AsyncTestStep alloc] init];
+    atask.providedFeature = [NSString stringWithFormat:@"feature_x_%d", i];
+    [[tasks objectAtIndex:index1].dependencies addObject:atask];
+    [[tasks objectAtIndex:index2].dependencies addObject:atask];
+    [tasks addObject:atask];
+
+    index1 = arc4random() % [tasks count];
+    index2 = arc4random() % [tasks count];
+    TestStep* stask = [[TestStep alloc] init];
+    stask.providedFeature = [NSString stringWithFormat:@"feature_y_%d", i];
+    [[tasks objectAtIndex:index1].dependencies addObject:stask];
+    [[tasks objectAtIndex:index2].dependencies addObject:stask];
+    [tasks addObject:stask];
+  }
+
+  NSUInteger testPhase = 1;
+
+  PhasedStepRunner* runner = [[PhasedStepRunner alloc] init];
+  [runner addSteps:tasks];
+  EXPECT_NE(testPhase, runner.phase);
+  [runner requireFeature:@"feature_a" forPhase:testPhase];
+  for (TestStep* task in tasks) {
+    EXPECT_FALSE(task.hasRun);
+  }
+  runner.phase = testPhase;
+
+  EXPECT_EQ(runner.phase, testPhase);
+  for (TestStep* task in tasks) {
+    EXPECT_TRUE(task.hasRun)
+        << "Expected task " << task.providedFeature.UTF8String
+        << " to have run";
+  }
+}
+
+// Simple phase change
+TEST(PhasedStepRunnerTest, TestPhaseChangeInStep) {
+  TestStep* step1 = [[TestStep alloc] init];
+  step1.providedFeature = @"feature_a";
+
+  PhaseChangeTestStep* task2 = [[PhaseChangeTestStep alloc] init];
+  task2.providedFeature = @"feature_b";
+
+  TestStep* task3 = [[TestStep alloc] init];
+  task3.providedFeature = @"feature_c";
+
+  TestStep* task4 = [[TestStep alloc] init];
+  task4.providedFeature = @"feature_d";
+
+  [step1.dependencies addObject:task2];
+  [task2.dependencies addObject:task3];
+
+  NSUInteger testPhase = 1;
+  NSUInteger otherPhase = 2;
+  task2.newPhase = otherPhase;
+
+  PhasedStepRunner* runner = [[PhasedStepRunner alloc] init];
+  [runner addSteps:@[ step1, task2, task3, task4 ]];
+  EXPECT_NE(testPhase, runner.phase);
+  [runner requireFeature:@"feature_a" forPhase:testPhase];
+  [runner requireFeature:@"feature_d" forPhase:otherPhase];
+
+  EXPECT_FALSE(step1.hasRun);
+  EXPECT_FALSE(task2.hasRun);
+  EXPECT_FALSE(task3.hasRun);
+  EXPECT_FALSE(task4.hasRun);
+
+  runner.phase = testPhase;
+
+  EXPECT_EQ(runner.phase, otherPhase);
+  EXPECT_FALSE(step1.hasRun);
+  EXPECT_TRUE(task2.hasRun);
+  EXPECT_TRUE(task3.hasRun);
+  EXPECT_TRUE(task4.hasRun);
+}
+
+// Sync phase change with async tasks
+TEST(PhasedStepRunnerTest, TestPhaseChangeInStepWithAsync) {
+  TestStep* step1 = [[AsyncTestStep alloc] init];
+  step1.providedFeature = @"feature_a";
+
+  TestStep* task2 = [[AsyncTestStep alloc] init];
+  task2.providedFeature = @"feature_b";
+
+  PhaseChangeTestStep* task3 = [[PhaseChangeTestStep alloc] init];
+  task3.providedFeature = @"feature_c";
+
+  TestStep* task4 = [[AsyncTestStep alloc] init];
+  task4.providedFeature = @"feature_d";
+
+  TestStep* task5 = [[AsyncTestStep alloc] init];
+  task5.providedFeature = @"feature_e";
+
+  TestStep* task6 = [[TestStep alloc] init];
+  task6.providedFeature = @"feature_f";
+
+  [task3.dependencies addObject:step1];
+  [task3.dependencies addObject:task2];
+  [task4.dependencies addObject:task3];
+  [task5.dependencies addObject:task3];
+
+  NSUInteger testPhase = 1;
+  NSUInteger otherPhase = 2;
+  task3.newPhase = otherPhase;
+
+  PhasedStepRunner* runner = [[PhasedStepRunner alloc] init];
+  [runner addSteps:@[ step1, task2, task3, task4, task5, task6 ]];
+  EXPECT_NE(testPhase, runner.phase);
+  [runner requireFeature:@"feature_d" forPhase:testPhase];
+  [runner requireFeature:@"feature_e" forPhase:testPhase];
+  [runner requireFeature:@"feature_f" forPhase:otherPhase];
+
+  EXPECT_FALSE(step1.hasRun);
+  EXPECT_FALSE(task2.hasRun);
+  EXPECT_FALSE(task3.hasRun);
+  EXPECT_FALSE(task4.hasRun);
+  EXPECT_FALSE(task5.hasRun);
+  EXPECT_FALSE(task6.hasRun);
+
+  runner.phase = testPhase;
+
+  EXPECT_EQ(runner.phase, otherPhase);
+  EXPECT_TRUE(step1.hasRun);
+  EXPECT_TRUE(task2.hasRun);
+  EXPECT_TRUE(task3.hasRun);
+  EXPECT_FALSE(task4.hasRun);
+  EXPECT_FALSE(task5.hasRun);
+  EXPECT_TRUE(task6.hasRun);
+}
diff --git a/ios/clean/chrome/app/steps/provider_initializer.h b/ios/clean/chrome/app/steps/provider_initializer.h
new file mode 100644
index 0000000..259f1355
--- /dev/null
+++ b/ios/clean/chrome/app/steps/provider_initializer.h
@@ -0,0 +1,16 @@
+// Copyright 2017 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_CLEAN_CHROME_APP_STEPS_PROVIDER_INITIALIZER_H_
+#define IOS_CLEAN_CHROME_APP_STEPS_PROVIDER_INITIALIZER_H_
+
+#import <Foundation/Foundation.h>
+
+#import "ios/clean/chrome/app/steps/application_step.h"
+#import "ios/clean/chrome/app/steps/simple_application_step.h"
+
+@interface ProviderInitializer : SimpleApplicationStep<ApplicationStep>
+@end
+
+#endif  // IOS_CLEAN_CHROME_APP_STEPS_PROVIDER_INITIALIZER_H_
diff --git a/ios/clean/chrome/app/steps/provider_initializer.mm b/ios/clean/chrome/app/steps/provider_initializer.mm
new file mode 100644
index 0000000..724d280
--- /dev/null
+++ b/ios/clean/chrome/app/steps/provider_initializer.mm
@@ -0,0 +1,27 @@
+// Copyright 2017 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/clean/chrome/app/steps/provider_initializer.h"
+
+#import "ios/chrome/app/startup/provider_registration.h"
+#include "ios/chrome/browser/application_context.h"
+#import "ios/clean/chrome/app/steps/step_features.h"
+
+@protocol StepContext;
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+@implementation ProviderInitializer
+- (instancetype)init {
+  if ((self = [super init])) {
+    self.providedFeature = step_features::kProviders;
+  }
+  return self;
+}
+
+- (void)runFeature:(NSString*)feature withContext:(id<StepContext>)context {
+  [ProviderRegistration registerProviders];
+}
+
+@end
diff --git a/ios/clean/chrome/app/steps/root_coordinator+application_step.h b/ios/clean/chrome/app/steps/root_coordinator+application_step.h
deleted file mode 100644
index 4558831..0000000
--- a/ios/clean/chrome/app/steps/root_coordinator+application_step.h
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2017 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_CLEAN_CHROME_APP_STEPS_ROOT_COORDINATOR_APPLICATION_STEP_H_
-#define IOS_CLEAN_CHROME_APP_STEPS_ROOT_COORDINATOR_APPLICATION_STEP_H_
-
-#import "ios/clean/chrome/app/application_step.h"
-#import "ios/clean/chrome/browser/ui/root/root_coordinator.h"
-
-// Category on RootCoordinator 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 RootCoordinator (ApplicationStep)<ApplicationStep>
-@end
-
-#endif  // IOS_CLEAN_CHROME_APP_STEPS_ROOT_COORDINATOR_APPLICATION_STEP_H_
diff --git a/ios/clean/chrome/app/steps/root_coordinator+application_step_unittest.mm b/ios/clean/chrome/app/steps/root_coordinator+application_step_unittest.mm
deleted file mode 100644
index 00a84ca..0000000
--- a/ios/clean/chrome/app/steps/root_coordinator+application_step_unittest.mm
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2017 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/clean/chrome/app/steps/root_coordinator+application_step.h"
-
-#include "base/macros.h"
-#import "ios/clean/chrome/app/application_state.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "testing/platform_test.h"
-#import "third_party/ocmock/OCMock/OCMock.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-namespace {
-
-class RootCoordinatorApplicationStepTest : public PlatformTest {
- public:
-  RootCoordinatorApplicationStepTest() {
-    coordinator_ = [[RootCoordinator alloc] init];
-    state_ = [[ApplicationState alloc] init];
-    window_ = OCMClassMock([UIWindow class]);
-  }
-
- protected:
-  RootCoordinator* coordinator_;
-  ApplicationState* state_;
-  id window_;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(RootCoordinatorApplicationStepTest);
-};
-
-// Tests that the coordinator can run if the window is key and application is
-// foregrounded.
-TEST_F(RootCoordinatorApplicationStepTest, TestRunsInKeyWindow) {
-  state_.phase = APPLICATION_FOREGROUNDED;
-  state_.window = window_;
-  OCMStub([window_ isKeyWindow]).andReturn(YES);
-  EXPECT_TRUE([coordinator_ canRunInState:state_]);
-}
-
-// Tests that the coordinator cannot run if the window is not key.
-TEST_F(RootCoordinatorApplicationStepTest, TestCannotRunWithoutKeyWindow) {
-  state_.phase = APPLICATION_FOREGROUNDED;
-  state_.window = window_;
-  OCMStub([window_ isKeyWindow]).andReturn(NO);
-  EXPECT_FALSE([coordinator_ canRunInState:state_]);
-}
-
-// Tests that the coordinator cannot run if application is not foregrounded.
-TEST_F(RootCoordinatorApplicationStepTest, TestCannotRunIfNotForegrounded) {
-  state_.phase = APPLICATION_COLD;
-  state_.window = window_;
-  OCMStub([window_ isKeyWindow]).andReturn(YES);
-  EXPECT_FALSE([coordinator_ canRunInState:state_]);
-}
-
-}  // namespace
diff --git a/ios/clean/chrome/app/steps/root_coordinator_initializer.h b/ios/clean/chrome/app/steps/root_coordinator_initializer.h
new file mode 100644
index 0000000..6a3c7bf6
--- /dev/null
+++ b/ios/clean/chrome/app/steps/root_coordinator_initializer.h
@@ -0,0 +1,16 @@
+// Copyright 2017 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_CLEAN_CHROME_APP_STEPS_ROOT_COORDINATOR_INITIALIZER_H_
+#define IOS_CLEAN_CHROME_APP_STEPS_ROOT_COORDINATOR_INITIALIZER_H_
+
+#import <Foundation/Foundation.h>
+
+#import "ios/clean/chrome/app/steps/application_step.h"
+#import "ios/clean/chrome/app/steps/simple_application_step.h"
+
+@interface RootCoordinatorInitializer : SimpleApplicationStep<ApplicationStep>
+@end
+
+#endif  // IOS_CLEAN_CHROME_APP_STEPS_ROOT_COORDINATOR_INITIALIZER_H_
diff --git a/ios/clean/chrome/app/steps/root_coordinator_initializer.mm b/ios/clean/chrome/app/steps/root_coordinator_initializer.mm
new file mode 100644
index 0000000..d098a546
--- /dev/null
+++ b/ios/clean/chrome/app/steps/root_coordinator_initializer.mm
@@ -0,0 +1,67 @@
+// Copyright 2017 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/clean/chrome/app/steps/root_coordinator_initializer.h"
+
+#import "ios/chrome/app/startup/provider_registration.h"
+#include "ios/chrome/browser/application_context.h"
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#import "ios/chrome/browser/web_state_list/web_state_list.h"
+#import "ios/chrome/browser/web_state_list/web_state_opener.h"
+#import "ios/clean/chrome/app/steps/step_context.h"
+#import "ios/clean/chrome/app/steps/step_features.h"
+#import "ios/clean/chrome/browser/ui/root/root_coordinator.h"
+#import "ios/shared/chrome/browser/ui/browser_list/browser_list.h"
+#import "ios/shared/chrome/browser/ui/browser_list/browser_list_session_service.h"
+#import "ios/shared/chrome/browser/ui/browser_list/browser_list_session_service_factory.h"
+#import "ios/shared/chrome/browser/ui/coordinators/browser_coordinator+internal.h"
+#include "ios/web/public/web_state/web_state.h"
+
+@protocol StepContext;
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@implementation RootCoordinatorInitializer {
+  RootCoordinator* _rootCoordinator;
+}
+- (instancetype)init {
+  if ((self = [super init])) {
+    self.providedFeature = step_features::kRootCoordinatorStarted;
+    self.requiredFeatures = @[ step_features::kMainWindow ];
+  }
+  return self;
+}
+
+- (void)runFeature:(NSString*)feature withContext:(id<StepContext>)context {
+  _rootCoordinator = [[RootCoordinator alloc] init];
+  [_rootCoordinator
+      setBrowser:BrowserList::FromBrowserState(context.browserState)
+                     ->CreateNewBrowser()];
+
+  BrowserListSessionService* service =
+      BrowserListSessionServiceFactory::GetForBrowserState(
+          context.browserState);
+
+  if (!service || !service->RestoreSession()) {
+    WebStateList& webStateList = _rootCoordinator.browser->web_state_list();
+    web::WebState::CreateParams webStateCreateParams(
+        _rootCoordinator.browser->browser_state());
+    webStateList.InsertWebState(0, web::WebState::Create(webStateCreateParams),
+                                WebStateList::INSERT_NO_FLAGS,
+                                WebStateOpener());
+    webStateList.ActivateWebStateAt(0);
+  }
+
+  [_rootCoordinator start];
+  context.URLOpener = _rootCoordinator;
+  context.window.rootViewController = _rootCoordinator.viewController;
+
+  // Size the main view controller to fit the whole screen.
+  [_rootCoordinator.viewController.view setFrame:context.window.bounds];
+  context.window.hidden = NO;
+}
+
+@end
diff --git a/ios/clean/chrome/app/steps/simple_application_step.h b/ios/clean/chrome/app/steps/simple_application_step.h
new file mode 100644
index 0000000..6e37da2
--- /dev/null
+++ b/ios/clean/chrome/app/steps/simple_application_step.h
@@ -0,0 +1,21 @@
+// Copyright 2017 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_CLEAN_CHROME_APP_STEPS_SIMPLE_APPLICATION_STEP_H_
+#define IOS_CLEAN_CHROME_APP_STEPS_SIMPLE_APPLICATION_STEP_H_
+
+#import <Foundation/Foundation.h>
+
+// Note that SimpleApplicationStep doesn't conform to ApplicationStep; it's
+// an abstract base class that many steps will derive from, but they will
+// need to (at least) implement -runFeature:withContext.
+@interface SimpleApplicationStep : NSObject
+@property(nonatomic, copy) NSString* providedFeature;
+// ApplicationStep properties.
+@property(nonatomic) NSArray<NSString*>* providedFeatures;
+@property(nonatomic) NSArray<NSString*>* requiredFeatures;
+@property(nonatomic) BOOL synchronous;
+@end
+
+#endif  // IOS_CLEAN_CHROME_APP_STEPS_SIMPLE_APPLICATION_STEP_H_
diff --git a/ios/clean/chrome/app/steps/simple_application_step.mm b/ios/clean/chrome/app/steps/simple_application_step.mm
new file mode 100644
index 0000000..ca716a4
--- /dev/null
+++ b/ios/clean/chrome/app/steps/simple_application_step.mm
@@ -0,0 +1,28 @@
+// Copyright 2017 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/clean/chrome/app/steps/simple_application_step.h"
+
+@implementation SimpleApplicationStep
+
+@synthesize providedFeature = _providedFeature;
+@synthesize providedFeatures = _providedFeatures;
+@synthesize requiredFeatures = _requiredFeatures;
+@synthesize synchronous = _synchronous;
+
+- (instancetype)init {
+  if ((self = [super init])) {
+    _synchronous = YES;
+    _providedFeatures = @[];
+    _requiredFeatures = @[];
+  }
+  return self;
+}
+
+- (void)setProvidedFeature:(NSString*)providedFeature {
+  _providedFeature = providedFeature;
+  self.providedFeatures = @[ self.providedFeature ];
+}
+
+@end
diff --git a/ios/clean/chrome/app/steps/step_collections.h b/ios/clean/chrome/app/steps/step_collections.h
new file mode 100644
index 0000000..0e355e82
--- /dev/null
+++ b/ios/clean/chrome/app/steps/step_collections.h
@@ -0,0 +1,21 @@
+// Copyright 2017 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_CLEAN_CHROME_APP_STEPS_STEP_COLLECTIONS_H_
+#define IOS_CLEAN_CHROME_APP_STEPS_STEP_COLLECTIONS_H_
+
+#import <Foundation/Foundation.h>
+
+@protocol ApplicationStep;
+
+typedef NSArray<id<ApplicationStep>> ApplicationSteps;
+
+@interface StepCollections : NSObject
+
+// Returns an array containing an instance of every application step.
++ (ApplicationSteps*)allApplicationSteps;
+
+@end
+
+#endif  // IOS_CLEAN_CHROME_APP_STEPS_STEP_COLLECTIONS_H_
diff --git a/ios/clean/chrome/app/steps/step_collections.mm b/ios/clean/chrome/app/steps/step_collections.mm
new file mode 100644
index 0000000..3eddb73
--- /dev/null
+++ b/ios/clean/chrome/app/steps/step_collections.mm
@@ -0,0 +1,26 @@
+// Copyright 2017 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/clean/chrome/app/steps/step_collections.h"
+
+#import "ios/clean/chrome/app/steps/browser_state_setter.h"
+#import "ios/clean/chrome/app/steps/bundle_and_defaults_configurator.h"
+#import "ios/clean/chrome/app/steps/chrome_main.h"
+#import "ios/clean/chrome/app/steps/foregrounder.h"
+#import "ios/clean/chrome/app/steps/provider_initializer.h"
+#import "ios/clean/chrome/app/steps/root_coordinator_initializer.h"
+#import "ios/clean/chrome/app/steps/ui_initializer.h"
+
+@implementation StepCollections
+
++ (ApplicationSteps*)allApplicationSteps {
+  return @[
+    [[BrowserStateSetter alloc] init],
+    [[BundleAndDefaultsConfigurator alloc] init], [[ChromeMain alloc] init],
+    [[Foregrounder alloc] init], [[ProviderInitializer alloc] init],
+    [[UIInitializer alloc] init], [[RootCoordinatorInitializer alloc] init]
+  ];
+}
+
+@end
diff --git a/ios/clean/chrome/app/steps/step_context.h b/ios/clean/chrome/app/steps/step_context.h
new file mode 100644
index 0000000..6b7cf70b
--- /dev/null
+++ b/ios/clean/chrome/app/steps/step_context.h
@@ -0,0 +1,45 @@
+// Copyright 2017 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_CLEAN_CHROME_APP_STEPS_STEP_CONTEXT_H_
+#define IOS_CLEAN_CHROME_APP_STEPS_STEP_CONTEXT_H_
+
+#import <UIKit/UIKit.h>
+
+namespace ios {
+class ChromeBrowserState;
+}
+
+@class BrowserCoordinator;
+
+@protocol URLOpening;
+
+// Objects that provide a context for ApplicationSteps to run in should conform
+// to this protocol.
+@protocol StepContext<NSObject>
+
+// The current phase that the object running the step is in. Generally
+// speaking, the phase value determines which steps will be run.
+// This property is writable, so steps can change the current phase.
+@property(nonatomic) NSUInteger phase;
+
+@property(nonatomic, weak) id<URLOpening> URLOpener;
+
+@optional
+// Properties with contextual information for the execution of application
+// steps.
+
+// The main browserState object for the application
+@property(nonatomic) ios::ChromeBrowserState* browserState;
+
+// A dictionary with keys and values as provided by the UIApplicationDelegate
+// -application:(did|will)FinishLaunchingWithOptions: methods.
+@property(nonatomic, readonly) NSDictionary* launchOptions;
+
+// The main UIWindow for the application.
+@property(nonatomic, strong) UIWindow* window;
+
+@end
+
+#endif  // IOS_CLEAN_CHROME_APP_STEPS_STEP_CONTEXT_H_
diff --git a/ios/clean/chrome/app/steps/step_features.h b/ios/clean/chrome/app/steps/step_features.h
new file mode 100644
index 0000000..b61fd6e7a
--- /dev/null
+++ b/ios/clean/chrome/app/steps/step_features.h
@@ -0,0 +1,24 @@
+// Copyright 2017 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_CLEAN_CHROME_APP_STEPS_STEP_FEATURES_H_
+#define IOS_CLEAN_CHROME_APP_STEPS_STEP_FEATURES_H_
+
+#import <Foundation/Foundation.h>
+
+namespace step_features {
+
+extern NSString* const kProviders;
+extern NSString* const kBundleAndDefaults;
+extern NSString* const kChromeMainStarted;
+extern NSString* const kChromeMainStopped;
+extern NSString* const kForeground;
+extern NSString* const kBrowserState;
+extern NSString* const kBrowserStateInitialized;
+extern NSString* const kMainWindow;
+extern NSString* const kRootCoordinatorStarted;
+
+}  // namespace step_features
+
+#endif  // IOS_CLEAN_CHROME_APP_STEPS_STEP_FEATURES_H_
diff --git a/ios/clean/chrome/app/steps/step_features.mm b/ios/clean/chrome/app/steps/step_features.mm
new file mode 100644
index 0000000..2f8dbc4
--- /dev/null
+++ b/ios/clean/chrome/app/steps/step_features.mm
@@ -0,0 +1,19 @@
+// Copyright 2017 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/clean/chrome/app/steps/step_features.h"
+
+namespace step_features {
+
+NSString* const kProviders = @"providers";
+NSString* const kBundleAndDefaults = @"bundleAndDefaults";
+NSString* const kChromeMainStarted = @"chromeMainStarted";
+NSString* const kChromeMainStopped = @"chromeMainStopped";
+NSString* const kForeground = @"foreground";
+NSString* const kBrowserState = @"browserState";
+NSString* const kBrowserStateInitialized = @"browserStateInit";
+NSString* const kMainWindow = @"mainWindow";
+NSString* const kRootCoordinatorStarted = @"rootCoordinatorStarted";
+
+}  // namespace step_features
diff --git a/ios/clean/chrome/app/steps/ui_initializer.h b/ios/clean/chrome/app/steps/ui_initializer.h
new file mode 100644
index 0000000..fa68f01
--- /dev/null
+++ b/ios/clean/chrome/app/steps/ui_initializer.h
@@ -0,0 +1,16 @@
+// Copyright 2017 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_CLEAN_CHROME_APP_STEPS_UI_INITIALIZER_H_
+#define IOS_CLEAN_CHROME_APP_STEPS_UI_INITIALIZER_H_
+
+#import <Foundation/Foundation.h>
+
+#import "ios/clean/chrome/app/steps/application_step.h"
+#import "ios/clean/chrome/app/steps/simple_application_step.h"
+
+@interface UIInitializer : SimpleApplicationStep<ApplicationStep>
+@end
+
+#endif  // IOS_CLEAN_CHROME_APP_STEPS_UI_INITIALIZER_H_
diff --git a/ios/clean/chrome/app/steps/ui_initializer.mm b/ios/clean/chrome/app/steps/ui_initializer.mm
new file mode 100644
index 0000000..0affe06d
--- /dev/null
+++ b/ios/clean/chrome/app/steps/ui_initializer.mm
@@ -0,0 +1,33 @@
+// Copyright 2017 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/clean/chrome/app/steps/ui_initializer.h"
+
+#import "base/logging.h"
+#import "ios/clean/chrome/app/steps/step_context.h"
+#import "ios/clean/chrome/app/steps/step_features.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@implementation UIInitializer
+
+- (instancetype)init {
+  if ((self = [super init])) {
+    self.providedFeature = step_features::kMainWindow;
+    self.requiredFeatures = @[ step_features::kForeground ];
+  }
+  return self;
+}
+
+- (void)runFeature:(NSString*)feature withContext:(id<StepContext>)context {
+  DCHECK([context respondsToSelector:@selector(setWindow:)]);
+  DCHECK([context respondsToSelector:@selector(window)]);
+  context.window =
+      [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
+  [context.window makeKeyWindow];
+}
+
+@end
diff --git a/ios/clean/chrome/browser/ui/ntp/ntp_home_header_coordinator.mm b/ios/clean/chrome/browser/ui/ntp/ntp_home_header_coordinator.mm
index 7b92bc3..91a7b2c 100644
--- a/ios/clean/chrome/browser/ui/ntp/ntp_home_header_coordinator.mm
+++ b/ios/clean/chrome/browser/ui/ntp/ntp_home_header_coordinator.mm
@@ -11,7 +11,7 @@
 #error "This file requires ARC support."
 #endif
 
-@interface NTPHomeHeaderCoordinator ()
+@interface NTPHomeHeaderCoordinator ()<NTPHomeHeaderMediatorAlerter>
 
 @property(nonatomic, strong) NTPHomeHeaderMediator* mediator;
 @property(nonatomic, strong) NTPHomeHeaderViewController* viewController;
@@ -54,6 +54,7 @@
   self.mediator.commandHandler = self.commandHandler;
   self.mediator.collectionSynchronizer = self.collectionSynchronizer;
   self.mediator.headerViewController = self.viewController;
+  self.mediator.alerter = self;
 
   [super start];
 }
@@ -64,4 +65,21 @@
   self.viewController = nil;
 }
 
+#pragma mark - NTPHomeHeaderMediatorAlerter
+
+- (void)showAlert:(NSString*)title {
+  UIAlertController* alertController =
+      [UIAlertController alertControllerWithTitle:title
+                                          message:nil
+                                   preferredStyle:UIAlertControllerStyleAlert];
+  UIAlertAction* action =
+      [UIAlertAction actionWithTitle:@"Done"
+                               style:UIAlertActionStyleCancel
+                             handler:nil];
+  [alertController addAction:action];
+  [self.viewController presentViewController:alertController
+                                    animated:YES
+                                  completion:nil];
+}
+
 @end
diff --git a/ios/clean/chrome/browser/ui/ntp/ntp_home_header_mediator.h b/ios/clean/chrome/browser/ui/ntp/ntp_home_header_mediator.h
index 9403b4ba..1788310 100644
--- a/ios/clean/chrome/browser/ui/ntp/ntp_home_header_mediator.h
+++ b/ios/clean/chrome/browser/ui/ntp/ntp_home_header_mediator.h
@@ -17,6 +17,11 @@
 @protocol ContentSuggestionsHeaderViewControllerDelegate;
 @class NTPHomeHeaderViewController;
 
+// TODO(crbug.com/740793): Remove this protocol once no item is using it.
+@protocol NTPHomeHeaderMediatorAlerter
+- (void)showAlert:(NSString*)title;
+@end
+
 @interface NTPHomeHeaderMediator
     : NSObject<ContentSuggestionsHeaderControlling,
                ContentSuggestionsHeaderProvider,
@@ -30,6 +35,7 @@
         commandHandler;
 @property(nonatomic, weak) id<ContentSuggestionsCollectionSynchronizing>
     collectionSynchronizer;
+@property(nonatomic, weak) id<NTPHomeHeaderMediatorAlerter> alerter;
 
 @property(nonatomic, weak) NTPHomeHeaderViewController* headerViewController;
 
diff --git a/ios/clean/chrome/browser/ui/ntp/ntp_home_header_mediator.mm b/ios/clean/chrome/browser/ui/ntp/ntp_home_header_mediator.mm
index e829da4a..148692a 100644
--- a/ios/clean/chrome/browser/ui/ntp/ntp_home_header_mediator.mm
+++ b/ios/clean/chrome/browser/ui/ntp/ntp_home_header_mediator.mm
@@ -31,6 +31,7 @@
 @synthesize commandHandler = _commandHandler;
 @synthesize collectionSynchronizer = _collectionSynchronizer;
 @synthesize headerViewController = _headerViewController;
+@synthesize alerter = _alerter;
 
 @synthesize isShowing = _isShowing;
 @synthesize omniboxFocused = _omniboxFocused;
@@ -55,7 +56,7 @@
 - (void)unfocusOmnibox {
   if (self.omniboxFocused) {
     // TODO(crbug.com/740793): Remove alert once VoiceSearch is implemented.
-    [self showAlert:@"Cancel omnibox edit"];
+    [self.alerter showAlert:@"Cancel omnibox edit"];
   } else {
     [self locationBarResignsFirstResponder];
   }
@@ -159,7 +160,7 @@
   if (!IsIPadIdiom()) {
     [self.headerViewController collectionWillShiftDown];
     // TODO(crbug.com/740793): Remove alert once VoiceSearch is implemented.
-    [self showAlert:@"Omnibox unfocused"];
+    [self.alerter showAlert:@"Omnibox unfocused"];
   }
 
   [self.collectionSynchronizer shiftTilesDown];
@@ -171,28 +172,11 @@
   void (^completionBlock)() = ^{
     if (!IsIPadIdiom()) {
       // TODO(crbug.com/740793): Remove alert once VoiceSearch is implemented.
-      [self showAlert:@"Omnibox animation completed"];
+      [self.alerter showAlert:@"Omnibox animation completed"];
       [self.headerViewController collectionDidShiftUp];
     }
   };
   [self.collectionSynchronizer shiftTilesUpWithCompletionBlock:completionBlock];
 }
 
-// TODO(crbug.com/740793): Remove this method once no item is using it.
-- (void)showAlert:(NSString*)title {
-  UIAlertController* alertController =
-      [UIAlertController alertControllerWithTitle:title
-                                          message:nil
-                                   preferredStyle:UIAlertControllerStyleAlert];
-  UIAlertAction* action =
-      [UIAlertAction actionWithTitle:@"Done"
-                               style:UIAlertActionStyleCancel
-                             handler:nil];
-  [alertController addAction:action];
-  [self.headerViewController.parentViewController
-      presentViewController:alertController
-                   animated:YES
-                 completion:nil];
-}
-
 @end
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index 38a269d..44fd6622 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -1740,7 +1740,7 @@
 crbug.com/626703 virtual/threaded/transitions/transition-end-event-multiple-03.html [ Pass Failure ]
 
 # ====== New tests from wpt-importer added here ======
-crbug.com/626703 external/wpt/beacon/headers/header-content-type.html [ Pass Failure Timeout ]
+crbug.com/626703 external/wpt/beacon/headers/header-content-type.html [ Pass Timeout ]
 crbug.com/626703 [ Win ] external/wpt/css/css-ui-3/outline-004.html [ Failure ]
 crbug.com/626703 [ Win ] external/wpt/css/css-ui-3/text-overflow-001.html [ Pass Failure ]
 crbug.com/626703 [ Win ] external/wpt/css/css-ui-3/text-overflow-002.html [ Pass Failure ]
diff --git a/third_party/WebKit/LayoutTests/external/wpt/beacon/headers/header-content-type-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/beacon/headers/header-content-type-expected.txt
index 3282c62..9fe202e 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/beacon/headers/header-content-type-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/beacon/headers/header-content-type-expected.txt
@@ -1,6 +1,9 @@
 This is a testharness.js-based test.
 PASS Test content-type header for a body string 
-FAIL Test content-type header for a body ArrayBuffer assert_equals: Correct Content-Type header result expected "" but got "application/octet-stream"
+FAIL Test content-type header for a body ArrayBufferView assert_equals: Correct Content-Type header result expected "" but got "application/octet-stream"
+FAIL Test content-type header for a body ArrayBuffer assert_equals: Correct Content-Type header result expected "" but got "text/plain;charset=UTF-8"
+PASS Test content-type header for a body Blob 
 PASS Test content-type header for a body FormData 
+FAIL Test content-type header for a body URLSearchParams assert_equals: Correct Content-Type header result expected "application/x-www-form-urlencoded;charset=UTF-8" but got "text/plain;charset=UTF-8"
 Harness: the test ran to completion.
 
diff --git a/third_party/WebKit/Source/core/loader/DocumentThreadableLoader.cpp b/third_party/WebKit/Source/core/loader/DocumentThreadableLoader.cpp
index db0c4da..2482d91 100644
--- a/third_party/WebKit/Source/core/loader/DocumentThreadableLoader.cpp
+++ b/third_party/WebKit/Source/core/loader/DocumentThreadableLoader.cpp
@@ -51,7 +51,6 @@
 #include "core/probe/CoreProbes.h"
 #include "platform/SharedBuffer.h"
 #include "platform/exported/WrappedResourceRequest.h"
-#include "platform/exported/WrappedResourceResponse.h"
 #include "platform/loader/fetch/FetchParameters.h"
 #include "platform/loader/fetch/FetchUtils.h"
 #include "platform/loader/fetch/Resource.h"
@@ -648,10 +647,11 @@
   if (cors_flag_) {
     // The redirect response must pass the access control check if the CORS
     // flag is set.
-    WebCORS::AccessStatus cors_status =
-        WebCORS::CheckAccess(WrappedResourceResponse(redirect_response),
-                             new_request.GetFetchCredentialsMode(),
-                             WebSecurityOrigin(GetSecurityOrigin()));
+    WebCORS::AccessStatus cors_status = WebCORS::CheckAccess(
+        redirect_response.Url(), redirect_response.HttpStatusCode(),
+        redirect_response.HttpHeaderFields(),
+        new_request.GetFetchCredentialsMode(),
+        WebSecurityOrigin(GetSecurityOrigin()));
     if (cors_status != WebCORS::AccessStatus::kAccessAllowed) {
       StringBuilder builder;
       builder.Append("Redirect from '");
@@ -660,7 +660,8 @@
       builder.Append(new_url.GetString());
       builder.Append("' has been blocked by CORS policy: ");
       builder.Append(WebCORS::AccessControlErrorString(
-          cors_status, WrappedResourceResponse(redirect_response),
+          cors_status, redirect_response.HttpStatusCode(),
+          redirect_response.HttpHeaderFields(),
           WebSecurityOrigin(GetSecurityOrigin()), request_context_));
       DispatchDidFailAccessControlCheck(
           ResourceError::CancelledDueToAccessCheckError(
@@ -780,41 +781,41 @@
     const ResourceResponse& response) {
   String access_control_error_description;
 
-  WebCORS::AccessStatus cors_status =
-      WebCORS::CheckAccess(WrappedResourceResponse(response),
-                           actual_request_.GetFetchCredentialsMode(),
-                           WebSecurityOrigin(GetSecurityOrigin()));
+  WebCORS::AccessStatus cors_status = WebCORS::CheckAccess(
+      response.Url(), response.HttpStatusCode(), response.HttpHeaderFields(),
+      actual_request_.GetFetchCredentialsMode(),
+      WebSecurityOrigin(GetSecurityOrigin()));
   if (cors_status != WebCORS::AccessStatus::kAccessAllowed) {
     StringBuilder builder;
     builder.Append(
         "Response to preflight request doesn't pass access "
         "control check: ");
     builder.Append(WebCORS::AccessControlErrorString(
-        cors_status, WrappedResourceResponse(response),
+        cors_status, response.HttpStatusCode(), response.HttpHeaderFields(),
         WebSecurityOrigin(GetSecurityOrigin()), request_context_));
     HandlePreflightFailure(response.Url(), builder.ToString());
     return;
   }
 
   WebCORS::PreflightStatus preflight_status =
-      WebCORS::CheckPreflight(WrappedResourceResponse(response));
+      WebCORS::CheckPreflight(response.HttpStatusCode());
   if (preflight_status != WebCORS::PreflightStatus::kPreflightSuccess) {
-    HandlePreflightFailure(
-        response.Url(),
-        WebCORS::PreflightErrorString(preflight_status,
-                                      WrappedResourceResponse(response)));
+    HandlePreflightFailure(response.Url(),
+                           WebCORS::PreflightErrorString(
+                               preflight_status, response.HttpHeaderFields(),
+                               response.HttpStatusCode()));
     return;
   }
 
   if (actual_request_.IsExternalRequest()) {
     WebCORS::PreflightStatus external_preflight_status =
-        WebCORS::CheckExternalPreflight(WrappedResourceResponse(response));
+        WebCORS::CheckExternalPreflight(response.HttpHeaderFields());
     if (external_preflight_status !=
         WebCORS::PreflightStatus::kPreflightSuccess) {
-      HandlePreflightFailure(
-          response.Url(),
-          WebCORS::PreflightErrorString(external_preflight_status,
-                                        WrappedResourceResponse(response)));
+      HandlePreflightFailure(response.Url(), WebCORS::PreflightErrorString(
+                                                 external_preflight_status,
+                                                 response.HttpHeaderFields(),
+                                                 response.HttpStatusCode()));
       return;
     }
   }
@@ -893,9 +894,9 @@
             network::mojom::FetchResponseType::kOpaque) {
       StringBuilder builder;
       builder.Append(WebCORS::AccessControlErrorString(
-          WebCORS::AccessStatus::kInvalidResponse,
-          WrappedResourceResponse(response),
-          WebSecurityOrigin(GetSecurityOrigin()), request_context_));
+          WebCORS::AccessStatus::kInvalidResponse, response.HttpStatusCode(),
+          response.HttpHeaderFields(), WebSecurityOrigin(GetSecurityOrigin()),
+          request_context_));
       DispatchDidFailAccessControlCheck(
           ResourceError::CancelledDueToAccessCheckError(
               response.Url(), ResourceRequestBlockedReason::kOther,
@@ -924,15 +925,16 @@
 
   if (IsCORSEnabledRequestMode(request_mode) && cors_flag_) {
     WebCORS::AccessStatus cors_status = WebCORS::CheckAccess(
-        WrappedResourceResponse(response), credentials_mode,
-        WebSecurityOrigin(GetSecurityOrigin()));
+        response.Url(), response.HttpStatusCode(), response.HttpHeaderFields(),
+        credentials_mode, WebSecurityOrigin(GetSecurityOrigin()));
     if (cors_status != WebCORS::AccessStatus::kAccessAllowed) {
       ReportResponseReceived(identifier, response);
       DispatchDidFailAccessControlCheck(
           ResourceError::CancelledDueToAccessCheckError(
               response.Url(), ResourceRequestBlockedReason::kOther,
               WebCORS::AccessControlErrorString(
-                  cors_status, WrappedResourceResponse(response),
+                  cors_status, response.HttpStatusCode(),
+                  response.HttpHeaderFields(),
                   WebSecurityOrigin(GetSecurityOrigin()), request_context_)));
       return;
     }
diff --git a/third_party/WebKit/Source/platform/exported/WebCORS.cpp b/third_party/WebKit/Source/platform/exported/WebCORS.cpp
index d7c0cf1a..d2213487 100644
--- a/third_party/WebKit/Source/platform/exported/WebCORS.cpp
+++ b/third_party/WebKit/Source/platform/exported/WebCORS.cpp
@@ -40,6 +40,7 @@
 #include "public/platform/WebSecurityOrigin.h"
 #include "public/platform/WebURLResponse.h"
 #include "url/gurl.h"
+#include "url/url_util.h"
 
 namespace blink {
 
@@ -171,22 +172,23 @@
 }  // namespace
 
 AccessStatus CheckAccess(
-    const WebURLResponse& response,
+    const WebURL response_url,
+    const int response_status_code,
+    const HTTPHeaderMap& response_header,
     const WebURLRequest::FetchCredentialsMode credentials_mode,
     const WebSecurityOrigin& security_origin) {
-  int status_code = response.HttpStatusCode();
-  if (!status_code)
+  if (!response_status_code)
     return AccessStatus::kInvalidResponse;
 
   const WebString& allow_origin_header_value =
-      response.HttpHeaderField(HTTPNames::Access_Control_Allow_Origin);
+      response_header.Get(HTTPNames::Access_Control_Allow_Origin);
 
   // Check Suborigins, unless the Access-Control-Allow-Origin is '*', which
   // implies that all Suborigins are okay as well.
   if (!security_origin.Suborigin().IsEmpty() &&
       allow_origin_header_value != WebString(g_star_atom)) {
     const WebString& allow_suborigin_header_value =
-        response.HttpHeaderField(HTTPNames::Access_Control_Allow_Suborigin);
+        response_header.Get(HTTPNames::Access_Control_Allow_Suborigin);
     if (allow_suborigin_header_value != WebString(g_star_atom) &&
         allow_suborigin_header_value != security_origin.Suborigin()) {
       return AccessStatus::kSubOriginMismatch;
@@ -200,7 +202,7 @@
       return AccessStatus::kAccessAllowed;
     // TODO(hintzed): Is the following a sound substitute for
     // blink::ResourceResponse::IsHTTP()?
-    if (GURL(response.Url().GetString().Utf16()).SchemeIsHTTPOrHTTPS()) {
+    if (GURL(response_url.GetString().Utf16()).SchemeIsHTTPOrHTTPS()) {
       return AccessStatus::kWildcardOriginNotAllowed;
     }
   } else if (allow_origin_header_value != security_origin.ToString()) {
@@ -219,7 +221,7 @@
 
   if (FetchUtils::ShouldTreatCredentialsModeAsInclude(credentials_mode)) {
     const WebString& allow_credentials_header_value =
-        response.HttpHeaderField(HTTPNames::Access_Control_Allow_Credentials);
+        response_header.Get(HTTPNames::Access_Control_Allow_Credentials);
     if (allow_credentials_header_value != "true") {
       return AccessStatus::kDisallowCredentialsNotSetToTrue;
     }
@@ -229,11 +231,13 @@
 
 bool HandleRedirect(WebSecurityOrigin& current_security_origin,
                     WebURLRequest& new_request,
-                    const WebURLResponse& redirect_response,
+                    const WebURL redirect_response_url,
+                    const int redirect_response_status_code,
+                    const HTTPHeaderMap& redirect_response_header,
                     WebURLRequest::FetchCredentialsMode credentials_mode,
                     ResourceLoaderOptions& options,
                     WebString& error_message) {
-  const KURL& last_url = redirect_response.Url();
+  const KURL& last_url = redirect_response_url;
   const KURL& new_url = new_request.Url();
 
   WebSecurityOrigin& new_security_origin = current_security_origin;
@@ -252,15 +256,16 @@
       return false;
     }
 
-    AccessStatus cors_status = CheckAccess(redirect_response, credentials_mode,
-                                           current_security_origin);
+    AccessStatus cors_status = CheckAccess(
+        redirect_response_url, redirect_response_status_code,
+        redirect_response_header, credentials_mode, current_security_origin);
     if (cors_status != AccessStatus::kAccessAllowed) {
       StringBuilder builder;
       builder.Append("Redirect from '");
       builder.Append(last_url.GetString());
       builder.Append("' has been blocked by CORS policy: ");
       builder.Append(AccessControlErrorString(
-          cors_status, redirect_response,
+          cors_status, redirect_response_status_code, redirect_response_header,
           WebSecurityOrigin(current_security_origin.Get()),
           new_request.GetRequestContext()));
       error_message = builder.ToString();
@@ -314,25 +319,23 @@
   return RedirectStatus::kRedirectSuccess;
 }
 
-PreflightStatus CheckPreflight(const WebURLResponse& response) {
+PreflightStatus CheckPreflight(const int preflight_response_status_code) {
   // CORS preflight with 3XX is considered network error in
   // Fetch API Spec: https://fetch.spec.whatwg.org/#cors-preflight-fetch
   // CORS Spec: http://www.w3.org/TR/cors/#cross-origin-request-with-preflight-0
   // https://crbug.com/452394
-  int status_code = response.HttpStatusCode();
-  if (!FetchUtils::IsOkStatus(status_code))
+  if (!FetchUtils::IsOkStatus(preflight_response_status_code))
     return PreflightStatus::kPreflightInvalidStatus;
 
   return PreflightStatus::kPreflightSuccess;
 }
 
-PreflightStatus CheckExternalPreflight(const WebURLResponse& response) {
+PreflightStatus CheckExternalPreflight(const HTTPHeaderMap& response_header) {
   WebString result =
-      response.HttpHeaderField(HTTPNames::Access_Control_Allow_External);
+      response_header.Get(HTTPNames::Access_Control_Allow_External);
   if (result.IsNull())
     return PreflightStatus::kPreflightMissingAllowExternal;
-  // TODO(hintzed) replace with EqualIgnoringASCIICase()
-  if (!DeprecatedEqualIgnoringCase(result, "true"))
+  if (!EqualIgnoringASCIICase(result, "true"))
     return PreflightStatus::kPreflightInvalidAllowExternal;
   return PreflightStatus::kPreflightSuccess;
 }
@@ -372,7 +375,8 @@
 
 WebString AccessControlErrorString(
     const AccessStatus status,
-    const WebURLResponse& response,
+    const int response_status_code,
+    const HTTPHeaderMap& response_header,
     const WebSecurityOrigin& origin,
     const WebURLRequest::RequestContext context) {
   String origin_denied =
@@ -395,7 +399,7 @@
       return String::Format(
           "The 'Access-Control-Allow-Suborigin' header has a value '%s' that "
           "is not equal to the supplied suborigin. %s",
-          response.HttpHeaderField(HTTPNames::Access_Control_Allow_Suborigin)
+          response_header.Get(HTTPNames::Access_Control_Allow_Suborigin)
               .Utf8()
               .data(),
           origin_denied.Utf8().data());
@@ -413,9 +417,9 @@
     }
     case AccessStatus::kMissingAllowOriginHeader: {
       String status_code_msg =
-          IsInterestingStatusCode(response.HttpStatusCode())
+          IsInterestingStatusCode(response_status_code)
               ? String::Format(" The response had HTTP status code %d.",
-                               response.HttpStatusCode())
+                               response_status_code)
               : "";
 
       return String::Format(
@@ -431,7 +435,7 @@
       return String::Format(
           "The 'Access-Control-Allow-Origin' header contains multiple values "
           "'%s', but only one is allowed. %s%s",
-          response.HttpHeaderField(HTTPNames::Access_Control_Allow_Origin)
+          response_header.Get(HTTPNames::Access_Control_Allow_Origin)
               .Utf8()
               .data(),
           origin_denied.Utf8().data(), no_cors_information.Utf8().data());
@@ -440,7 +444,7 @@
       return String::Format(
           "The 'Access-Control-Allow-Origin' header contains the invalid value "
           "'%s'. %s%s",
-          response.HttpHeaderField(HTTPNames::Access_Control_Allow_Origin)
+          response_header.Get(HTTPNames::Access_Control_Allow_Origin)
               .Utf8()
               .data(),
           origin_denied.Utf8().data(), no_cors_information.Utf8().data());
@@ -449,7 +453,7 @@
       return String::Format(
           "The 'Access-Control-Allow-Origin' header has a value '%s' that is "
           "not equal to the supplied origin. %s%s",
-          response.HttpHeaderField(HTTPNames::Access_Control_Allow_Origin)
+          response_header.Get(HTTPNames::Access_Control_Allow_Origin)
               .Utf8()
               .data(),
           origin_denied.Utf8().data(), no_cors_information.Utf8().data());
@@ -459,7 +463,7 @@
           "The value of the 'Access-Control-Allow-Credentials' header in "
           "the response is '%s' which must be 'true' when the request's "
           "credentials mode is 'include'. %s%s",
-          response.HttpHeaderField(HTTPNames::Access_Control_Allow_Credentials)
+          response_header.Get(HTTPNames::Access_Control_Allow_Credentials)
               .Utf8()
               .data(),
           origin_denied.Utf8().data(),
@@ -475,13 +479,14 @@
   }
 }
 
-WebString PreflightErrorString(PreflightStatus status,
-                               const WebURLResponse& response) {
+WebString PreflightErrorString(const PreflightStatus status,
+                               const HTTPHeaderMap& response_header,
+                               const int preflight_response_status_code) {
   switch (status) {
     case PreflightStatus::kPreflightInvalidStatus: {
       return String::Format(
           "Response for preflight has invalid HTTP status code %d",
-          response.HttpStatusCode());
+          preflight_response_status_code);
     }
     case PreflightStatus::kPreflightMissingAllowExternal: {
       return String::Format(
@@ -496,7 +501,7 @@
           "response for this external request had a value of '%s',  not 'true' "
           "(This is an experimental header which is defined in "
           "'https://wicg.github.io/cors-rfc1918/').",
-          response.HttpHeaderField("access-control-allow-external")
+          response_header.Get(HTTPNames::Access_Control_Allow_External)
               .Utf8()
               .data());
     }
diff --git a/third_party/WebKit/Source/platform/loader/fetch/ResourceLoader.cpp b/third_party/WebKit/Source/platform/loader/fetch/ResourceLoader.cpp
index 3448e240..c5e7658a 100644
--- a/third_party/WebKit/Source/platform/loader/fetch/ResourceLoader.cpp
+++ b/third_party/WebKit/Source/platform/loader/fetch/ResourceLoader.cpp
@@ -280,11 +280,11 @@
       WebSecurityOrigin source_web_origin(source_origin.Get());
       WrappedResourceRequest new_request_wrapper(new_request);
       WebString cors_error_msg;
-      if (!WebCORS::HandleRedirect(source_web_origin, new_request_wrapper,
-                                   WrappedResourceResponse(redirect_response),
-                                   fetch_credentials_mode,
-                                   resource_->MutableOptions(),
-                                   cors_error_msg)) {
+      if (!WebCORS::HandleRedirect(
+              source_web_origin, new_request_wrapper, redirect_response.Url(),
+              redirect_response.HttpStatusCode(),
+              redirect_response.HttpHeaderFields(), fetch_credentials_mode,
+              resource_->MutableOptions(), cors_error_msg)) {
         resource_->SetCORSStatus(CORSStatus::kFailed);
 
         if (!unused_preload) {
@@ -432,7 +432,9 @@
           : response;
 
   WebCORS::AccessStatus cors_status =
-      WebCORS::CheckAccess(WrappedResourceResponse(response_for_access_control),
+      WebCORS::CheckAccess(response_for_access_control.Url(),
+                           response_for_access_control.HttpStatusCode(),
+                           response_for_access_control.HttpHeaderFields(),
                            initial_request.GetFetchCredentialsMode(),
                            WebSecurityOrigin(source_origin));
 
@@ -449,7 +451,8 @@
   error_msg.Append(source_origin->ToString());
   error_msg.Append("' has been blocked by CORS policy: ");
   error_msg.Append(WebCORS::AccessControlErrorString(
-      cors_status, WrappedResourceResponse(response_for_access_control),
+      cors_status, response_for_access_control.HttpStatusCode(),
+      response_for_access_control.HttpHeaderFields(),
       WebSecurityOrigin(source_origin), initial_request.GetRequestContext()));
 
   return CORSStatus::kFailed;
diff --git a/third_party/WebKit/public/platform/WebCORS.h b/third_party/WebKit/public/platform/WebCORS.h
index 5c528329..20ba7ac 100644
--- a/third_party/WebKit/public/platform/WebCORS.h
+++ b/third_party/WebKit/public/platform/WebCORS.h
@@ -28,6 +28,7 @@
 #define WebCORS_h
 
 #include "platform/loader/fetch/ResourceLoaderOptions.h"
+#include "platform/network/HTTPHeaderMap.h"
 #include "platform/wtf/HashSet.h"
 #include "platform/wtf/text/StringHash.h"
 #include "public/platform/WebString.h"
@@ -86,11 +87,14 @@
   kRedirectContainsCredentials,
 };
 
-// Perform a CORS access check on the response. Returns |kAccessAllowed| if
-// access is allowed. Use |AccessControlErrorString()| to construct a
-// user-friendly error message for any of the other (error) conditions.
+// Perform a CORS access check on the response parameters. Returns
+// |kAccessAllowed| if access is allowed. Use |AccessControlErrorString()| to
+// construct a user-friendly error message for any of the other (error)
+// conditions.
 BLINK_PLATFORM_EXPORT AccessStatus
-CheckAccess(const WebURLResponse&,
+CheckAccess(const WebURL,
+            const int response_status_code,
+            const HTTPHeaderMap&,
             WebURLRequest::FetchCredentialsMode,
             const WebSecurityOrigin&);
 
@@ -108,13 +112,14 @@
 // Returns |kPreflightSuccess| if preflight response was successful.
 // Use |PreflightErrorString()| to construct a user-friendly error message
 // for any of the other (error) conditions.
-BLINK_PLATFORM_EXPORT PreflightStatus CheckPreflight(const WebURLResponse&);
+BLINK_PLATFORM_EXPORT PreflightStatus
+CheckPreflight(const int preflight_response_status_code);
 
 // Error checking for the currently experimental
 // "Access-Control-Allow-External:" header. Shares error conditions with
 // standard preflight checking.
 BLINK_PLATFORM_EXPORT PreflightStatus
-CheckExternalPreflight(const WebURLResponse&);
+CheckExternalPreflight(const HTTPHeaderMap&);
 
 BLINK_PLATFORM_EXPORT WebURLRequest
 CreateAccessControlPreflightRequest(const WebURLRequest&);
@@ -122,22 +127,28 @@
 // TODO(tyoshino): Using platform/loader/fetch/ResourceLoaderOptions violates
 // the DEPS rule. This will be fixed soon by making HandleRedirect() not
 // depending on ResourceLoaderOptions.
-BLINK_PLATFORM_EXPORT bool HandleRedirect(WebSecurityOrigin&,
-                                          WebURLRequest&,
-                                          const WebURLResponse&,
-                                          WebURLRequest::FetchCredentialsMode,
-                                          ResourceLoaderOptions&,
-                                          WebString&);
+BLINK_PLATFORM_EXPORT bool HandleRedirect(
+    WebSecurityOrigin&,
+    WebURLRequest&,
+    const WebURL,
+    const int redirect_response_status_code,
+    const HTTPHeaderMap&,
+    WebURLRequest::FetchCredentialsMode,
+    ResourceLoaderOptions&,
+    WebString&);
 
 // Stringify errors from CORS access checks, preflight or redirect checks.
 BLINK_PLATFORM_EXPORT WebString
 AccessControlErrorString(const AccessStatus,
-                         const WebURLResponse&,
+                         const int response_status_code,
+                         const HTTPHeaderMap&,
                          const WebSecurityOrigin&,
                          const WebURLRequest::RequestContext);
 
-BLINK_PLATFORM_EXPORT WebString PreflightErrorString(const PreflightStatus,
-                                                     const WebURLResponse&);
+BLINK_PLATFORM_EXPORT WebString
+PreflightErrorString(const PreflightStatus,
+                     const HTTPHeaderMap&,
+                     const int preflight_response_status_code);
 
 BLINK_PLATFORM_EXPORT WebString RedirectErrorString(const RedirectStatus,
                                                     const WebURL&);
diff --git a/ui/login/account_picker/md_screen_account_picker.js b/ui/login/account_picker/md_screen_account_picker.js
index a65e465..d392cc3 100644
--- a/ui/login/account_picker/md_screen_account_picker.js
+++ b/ui/login/account_picker/md_screen_account_picker.js
@@ -427,10 +427,15 @@
       // pods. Main goal is to clear any credentials the user might have input.
       if (state === LOCK_SCREEN_APPS_STATE.FOREGROUND) {
         $('pod-row').clearFocusedPod();
-      } else if (wasForeground) {
-        // If the app window was moved to background, ensure the active pod is
-        // focused.
-        $('pod-row').refocusCurrentPod();
+        $('pod-row').disabled = true;
+      } else {
+        $('pod-row').disabled = false;
+        if (wasForeground) {
+          // If the app window was moved to background, ensure the active pod is
+          // focused.
+          $('pod-row').maybePreselectPod();
+          $('pod-row').refocusCurrentPod();
+        }
       }
     },
 
diff --git a/ui/login/account_picker/md_user_pod_row.js b/ui/login/account_picker/md_user_pod_row.js
index f0f0ab9..dae3ddd1c 100644
--- a/ui/login/account_picker/md_user_pod_row.js
+++ b/ui/login/account_picker/md_user_pod_row.js
@@ -895,6 +895,10 @@
         element.disabled = value
       });
 
+      this.tabIndex = value ? -1 : UserPodTabOrder.POD_INPUT;
+      this.actionBoxAreaElement.tabIndex =
+          value ? -1 : UserPodTabOrder.POD_INPUT;
+
       // Special handling for submit button - the submit button should be
       // enabled only if there is the password value set.
       var submitButton = this.submitButton;