diff --git a/.gitignore b/.gitignore
index 2d2ec98..f01599cf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -61,7 +61,7 @@
 /.settings/
 /.vs/
 # Visual Studio Code
-/.vscode/
+.vscode/
 /_out
 /android_emulator_sdk
 /ash/ash_unittests_run.xml
diff --git a/DEPS b/DEPS
index a617575..fdb998db 100644
--- a/DEPS
+++ b/DEPS
@@ -129,7 +129,7 @@
   # 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': '69da7ad49b55a9085ff4e442b5d67ea110ac5566',
+  'skia_revision': 'c7755d9470846024ad8108bc7b9b868e808258d6',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -145,7 +145,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': 'bd3af855ae74d9767eb68adcdd1a66c29dc1d81e',
+  'swiftshader_revision': '484e08e0fae627f107df3458242e158741a42e96',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -196,7 +196,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '9057b413d4b19b24786542b710e03cfa62bbb6af',
+  'catapult_revision': '27980609ac44bfbce0fd4d752f7c1efd5f32d58a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -805,7 +805,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'c5403d69e17c0cf99abb9eb10e827515afca499f',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'c6dfce981f13e2699c827618d3f92d8cb0656aca',
       'condition': 'checkout_linux',
   },
 
@@ -959,7 +959,7 @@
   },
 
   'src/third_party/arcore-android-sdk/src': {
-      'url': Var('chromium_git') + '/external/github.com/google-ar/arcore-android-sdk.git' + '@' + '6fcebcbf021b53ceb254e58cce6d5d4a4214819d',
+      'url': Var('chromium_git') + '/external/github.com/google-ar/arcore-android-sdk.git' + '@' + '5b67d88f0b33edbfbe4902141ca44e750df9b6c2',
       'condition': 'checkout_android',
   },
 
@@ -1172,7 +1172,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' +  '3c836b671503ce665ee4a9e4fe79e8758866acfc',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' +  'cc57bf714a300c5e0b6bc04f76ea058e50c38399',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + 'ac0d98b5cee6c024b0cffeb4f8f45b6fc5ccdb78',
@@ -1999,7 +1999,7 @@
       'packages': [
           {
               'package': 'chromium/third_party/android_deps/libs/com_google_ar_core',
-              'version': 'version:1.6.0-cr0',
+              'version': 'version:1.8.0-cr0',
           },
       ],
       'condition': 'checkout_android',
diff --git a/ash/display/display_output_protection.cc b/ash/display/display_output_protection.cc
index cd734f83..e676944 100644
--- a/ash/display/display_output_protection.cc
+++ b/ash/display/display_output_protection.cc
@@ -24,11 +24,13 @@
     display_configurator_->UnregisterContentProtectionClient(client_id_);
   }
 
-  uint64_t client_id() const { return client_id_; }
+  DisplayConfigurator::ContentProtectionClientId client_id() const {
+    return client_id_;
+  }
 
  private:
-  display::DisplayConfigurator* const display_configurator_;
-  const uint64_t client_id_;
+  DisplayConfigurator* const display_configurator_;
+  const DisplayConfigurator::ContentProtectionClientId client_id_;
 
   DISALLOW_COPY_AND_ASSIGN(BindingContext);
 };
@@ -49,7 +51,7 @@
 void DisplayOutputProtection::QueryContentProtectionStatus(
     int64_t display_id,
     QueryContentProtectionStatusCallback callback) {
-  display_configurator_->QueryContentProtectionStatus(
+  display_configurator_->QueryContentProtection(
       bindings_.dispatch_context()->client_id(), display_id,
       std::move(callback));
 }
@@ -58,7 +60,7 @@
     int64_t display_id,
     uint32_t desired_method_mask,
     SetContentProtectionCallback callback) {
-  display_configurator_->SetContentProtection(
+  display_configurator_->ApplyContentProtection(
       bindings_.dispatch_context()->client_id(), display_id,
       desired_method_mask, std::move(callback));
 }
diff --git a/ash/kiosk_next/kiosk_next_shell_controller.cc b/ash/kiosk_next/kiosk_next_shell_controller.cc
index 5ca69ca33..279be8a 100644
--- a/ash/kiosk_next/kiosk_next_shell_controller.cc
+++ b/ash/kiosk_next/kiosk_next_shell_controller.cc
@@ -26,9 +26,16 @@
 
 // static
 void KioskNextShellController::RegisterProfilePrefs(
-    PrefRegistrySimple* registry) {
-  registry->RegisterBooleanPref(prefs::kKioskNextShellEnabled, false,
-                                PrefRegistry::PUBLIC);
+    PrefRegistrySimple* registry,
+    bool for_test) {
+  if (for_test) {
+    registry->RegisterBooleanPref(prefs::kKioskNextShellEnabled, false,
+                                  PrefRegistry::PUBLIC);
+    return;
+  }
+  // The registration has been moved to
+  // chromeos::Preferences::RegisterProfilePrefs to avoid race conditions.
+  registry->RegisterForeignPref(prefs::kKioskNextShellEnabled);
 }
 
 void KioskNextShellController::BindRequest(
diff --git a/ash/kiosk_next/kiosk_next_shell_controller.h b/ash/kiosk_next/kiosk_next_shell_controller.h
index 3d0c072..7d3fd45 100644
--- a/ash/kiosk_next/kiosk_next_shell_controller.h
+++ b/ash/kiosk_next/kiosk_next_shell_controller.h
@@ -30,7 +30,7 @@
   ~KioskNextShellController() override;
 
   // Register prefs related to the Kiosk Next Shell.
-  static void RegisterProfilePrefs(PrefRegistrySimple* registry);
+  static void RegisterProfilePrefs(PrefRegistrySimple* registry, bool for_test);
 
   // Binds the mojom::KioskNextShellController interface to this object.
   void BindRequest(mojom::KioskNextShellControllerRequest request);
diff --git a/ash/kiosk_next/kiosk_next_shell_controller_unittest.cc b/ash/kiosk_next/kiosk_next_shell_controller_unittest.cc
index 85a3e6d..99e0446 100644
--- a/ash/kiosk_next/kiosk_next_shell_controller_unittest.cc
+++ b/ash/kiosk_next/kiosk_next_shell_controller_unittest.cc
@@ -58,29 +58,26 @@
   }
 
  protected:
-  void LoginKioskNextUser() {
+  void LoginKioskNextUser() { LoginUser(true); }
+
+  void LoginUser(bool enable_kiosk_next_shell) {
     TestSessionControllerClient* session_controller_client =
         GetSessionControllerClient();
 
     // Create session for KioskNext user.
     session_controller_client->AddUserSession(
         kTestUserEmail, user_manager::USER_TYPE_REGULAR,
-        true /* enable_settings */, false /* provide_pref_service */);
+        true /* enable_settings */, true /* provide_pref_service */);
 
-    // Create a KioskNext User and register its preferences.
-    auto pref_service = std::make_unique<TestingPrefServiceSimple>();
+    ash::SessionController* controller = Shell::Get()->session_controller();
 
-    // Initialize the default preferences.
-    Shell::RegisterUserProfilePrefs(pref_service->registry(),
-                                    true /* for_test */);
+    PrefService* pref_service = controller->GetUserPrefServiceForUser(
+        AccountId::FromUserEmail(kTestUserEmail));
 
-    // Set the user's KioskNextShell preference.
-    pref_service->SetUserPref(prefs::kKioskNextShellEnabled,
-                              std::make_unique<base::Value>(true));
-
-    // Provide PrefService for test user.
-    Shell::Get()->session_controller()->ProvideUserPrefServiceForTest(
-        AccountId::FromUserEmail(kTestUserEmail), std::move(pref_service));
+    if (enable_kiosk_next_shell) {
+      // Set the user's KioskNextShell preference.
+      pref_service->Set(prefs::kKioskNextShellEnabled, base::Value(true));
+    }
 
     session_controller_client->SwitchActiveUser(
         AccountId::FromUserEmail(kTestUserEmail));
@@ -101,7 +98,7 @@
   EXPECT_FALSE(GetKioskNextShellController()->IsEnabled());
 
   // Login a regular user whose KioskNext pref has not been enabled.
-  SimulateNewUserFirstLogin("primary_user@test.com");
+  LoginUser(false /* enable_kiosk_next_shell */);
 
   // KioskNextShell is not enabled for regular users by default.
   EXPECT_FALSE(GetKioskNextShellController()->IsEnabled());
diff --git a/ash/media/media_notification_item.cc b/ash/media/media_notification_item.cc
index 4d17ccc7..67c796f 100644
--- a/ash/media/media_notification_item.cc
+++ b/ash/media/media_notification_item.cc
@@ -8,6 +8,7 @@
 #include "ash/media/media_notification_view.h"
 #include "ash/public/cpp/notification_utils.h"
 #include "base/bind.h"
+#include "base/metrics/histogram_macros.h"
 #include "base/strings/string16.h"
 #include "base/time/time.h"
 #include "services/media_session/public/mojom/constants.mojom.h"
@@ -32,6 +33,10 @@
 
 }  // namespace
 
+// static
+const char MediaNotificationItem::kUserActionHistogramName[] =
+    "Media.Notification.UserAction";
+
 MediaNotificationItem::MediaNotificationItem(
     const std::string& id,
     media_session::mojom::MediaControllerPtr controller,
@@ -186,7 +191,11 @@
   if (!button_id)
     return;
 
-  switch (static_cast<MediaSessionAction>(*button_id)) {
+  const MediaSessionAction action = static_cast<MediaSessionAction>(*button_id);
+
+  UMA_HISTOGRAM_ENUMERATION(kUserActionHistogramName, action);
+
+  switch (action) {
     case MediaSessionAction::kPreviousTrack:
       media_controller_ptr_->PreviousTrack();
       break;
diff --git a/ash/media/media_notification_item.h b/ash/media/media_notification_item.h
index 468cccb..944e2d5 100644
--- a/ash/media/media_notification_item.h
+++ b/ash/media/media_notification_item.h
@@ -26,6 +26,9 @@
     : public media_session::mojom::MediaControllerObserver,
       public media_session::mojom::MediaControllerImageObserver {
  public:
+  // The name of the histogram used when recording user actions.
+  static const char kUserActionHistogramName[];
+
   MediaNotificationItem(const std::string& id,
                         media_session::mojom::MediaControllerPtr controller,
                         media_session::mojom::MediaSessionInfoPtr session_info);
diff --git a/ash/media/media_notification_view_unittest.cc b/ash/media/media_notification_view_unittest.cc
index 88a7c0e..0ec769bc 100644
--- a/ash/media/media_notification_view_unittest.cc
+++ b/ash/media/media_notification_view_unittest.cc
@@ -20,6 +20,7 @@
 #include "base/bind.h"
 #include "base/macros.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/unguessable_token.h"
 #include "services/media_session/public/cpp/test/test_media_controller.h"
@@ -230,6 +231,12 @@
                                ui::EventTimeForNow(), 0, 0));
   }
 
+  void ExpectHistogramActionRecorded(MediaSessionAction action) {
+    histogram_tester_.ExpectUniqueSample(
+        MediaNotificationItem::kUserActionHistogramName,
+        static_cast<base::HistogramBase::Sample>(action), 1);
+  }
+
  private:
   std::unique_ptr<message_center::MessageView> CreateAndCaptureCustomView(
       const message_center::Notification& notification) {
@@ -247,6 +254,8 @@
 
   base::test::ScopedFeatureList scoped_feature_list_;
 
+  base::HistogramTester histogram_tester_;
+
   std::set<MediaSessionAction> actions_;
 
   std::unique_ptr<TestMediaController> media_controller_;
@@ -316,6 +325,7 @@
   GetItem()->FlushForTesting();
 
   EXPECT_EQ(1, media_controller()->next_track_count());
+  ExpectHistogramActionRecorded(MediaSessionAction::kNextTrack);
 }
 
 TEST_F(MediaNotificationViewTest, PlayButtonClick) {
@@ -327,6 +337,7 @@
   GetItem()->FlushForTesting();
 
   EXPECT_EQ(1, media_controller()->resume_count());
+  ExpectHistogramActionRecorded(MediaSessionAction::kPlay);
 }
 
 TEST_F(MediaNotificationViewTest, PauseButtonClick) {
@@ -345,6 +356,7 @@
   GetItem()->FlushForTesting();
 
   EXPECT_EQ(1, media_controller()->suspend_count());
+  ExpectHistogramActionRecorded(MediaSessionAction::kPause);
 }
 
 TEST_F(MediaNotificationViewTest, PreviousTrackButtonClick) {
@@ -356,6 +368,7 @@
   GetItem()->FlushForTesting();
 
   EXPECT_EQ(1, media_controller()->previous_track_count());
+  ExpectHistogramActionRecorded(MediaSessionAction::kPreviousTrack);
 }
 
 TEST_F(MediaNotificationViewTest, SeekBackwardButtonClick) {
@@ -367,6 +380,7 @@
   GetItem()->FlushForTesting();
 
   EXPECT_EQ(1, media_controller()->seek_backward_count());
+  ExpectHistogramActionRecorded(MediaSessionAction::kSeekBackward);
 }
 
 TEST_F(MediaNotificationViewTest, SeekForwardButtonClick) {
@@ -378,6 +392,7 @@
   GetItem()->FlushForTesting();
 
   EXPECT_EQ(1, media_controller()->seek_forward_count());
+  ExpectHistogramActionRecorded(MediaSessionAction::kSeekForward);
 }
 
 TEST_F(MediaNotificationViewTest, ClickNotification) {
diff --git a/ash/shell.cc b/ash/shell.cc
index 5a28ef66..0fd5af9 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -256,7 +256,7 @@
   BluetoothPowerController::RegisterProfilePrefs(registry);
   CapsLockNotificationController::RegisterProfilePrefs(registry, for_test);
   DockedMagnifierController::RegisterProfilePrefs(registry, for_test);
-  KioskNextShellController::RegisterProfilePrefs(registry);
+  KioskNextShellController::RegisterProfilePrefs(registry, for_test);
   LoginScreenController::RegisterProfilePrefs(registry, for_test);
   LogoutButtonTray::RegisterProfilePrefs(registry);
   MessageCenterController::RegisterProfilePrefs(registry);
diff --git a/ash/wm/non_client_frame_controller.cc b/ash/wm/non_client_frame_controller.cc
index 7d7bb5c..7ede688 100644
--- a/ash/wm/non_client_frame_controller.cc
+++ b/ash/wm/non_client_frame_controller.cc
@@ -255,7 +255,6 @@
       new WmNativeWidgetAura(widget_, DoesClientProvideFrame(properties));
   window_ = native_widget->GetNativeView();
   window_->SetProperty(kNonClientFrameControllerKey, this);
-  window_->SetProperty(kWidgetCreationTypeKey, WidgetCreationType::FOR_CLIENT);
   window_->AddObserver(this);
   params.native_widget = native_widget;
   aura::SetWindowType(window_, ws::mojom::WindowType::WINDOW);
diff --git a/ash/wm/top_level_window_factory_unittest.cc b/ash/wm/top_level_window_factory_unittest.cc
index ce2cdfe..cdd9ebf 100644
--- a/ash/wm/top_level_window_factory_unittest.cc
+++ b/ash/wm/top_level_window_factory_unittest.cc
@@ -108,8 +108,6 @@
   ASSERT_TRUE(window->parent());
   EXPECT_EQ(kShellWindowId_DefaultContainer, window->parent()->id());
   EXPECT_EQ(bounds, window->bounds());
-  EXPECT_EQ(WidgetCreationType::FOR_CLIENT,
-            window->GetProperty(kWidgetCreationTypeKey));
   EXPECT_FALSE(window->IsVisible());
 }
 
diff --git a/ash/wm/widget_finder.cc b/ash/wm/widget_finder.cc
index 5989f32..8701c51 100644
--- a/ash/wm/widget_finder.cc
+++ b/ash/wm/widget_finder.cc
@@ -4,17 +4,16 @@
 
 #include "ash/wm/widget_finder.h"
 
-#include "ash/wm/window_properties.h"
+#include "services/ws/window_service.h"
 #include "ui/aura/window.h"
 #include "ui/views/widget/widget.h"
 
 namespace ash {
 
 views::Widget* GetInternalWidgetForWindow(aura::Window* window) {
-  return window->GetProperty(kWidgetCreationTypeKey) ==
-                 WidgetCreationType::INTERNAL
-             ? views::Widget::GetWidgetForNativeView(window)
-             : nullptr;
+  return ws::WindowService::IsProxyWindow(window)
+             ? nullptr
+             : views::Widget::GetWidgetForNativeView(window);
 }
 
 }  // namespace ash
diff --git a/ash/wm/window_properties.cc b/ash/wm/window_properties.cc
index 5be1ad3..99d5c66e 100644
--- a/ash/wm/window_properties.cc
+++ b/ash/wm/window_properties.cc
@@ -8,16 +8,11 @@
 #include "ui/gfx/geometry/rect.h"
 
 DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(ASH_EXPORT, ash::wm::WindowState*)
-DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(ASH_EXPORT, ash::WidgetCreationType)
 
 namespace ash {
 
 DEFINE_UI_CLASS_PROPERTY_KEY(bool, kLockedToRootKey, false)
 
-DEFINE_UI_CLASS_PROPERTY_KEY(WidgetCreationType,
-                             kWidgetCreationTypeKey,
-                             WidgetCreationType::INTERNAL)
-
 DEFINE_UI_CLASS_PROPERTY_KEY(bool, kWindowIsJanky, false)
 
 DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(ash::wm::WindowState, kWindowStateKey, NULL)
diff --git a/ash/wm/window_properties.h b/ash/wm/window_properties.h
index c243e56..3d7f88b 100644
--- a/ash/wm/window_properties.h
+++ b/ash/wm/window_properties.h
@@ -19,26 +19,6 @@
 class WindowState;
 }  // namespace wm
 
-// Used with kWidgetCreationType to indicate source of the widget creation.
-//
-// TODO: investigate removing this. If it's still needed, we can likely ask
-// the window service if it has a remote client for the window rather than using
-// a property. https://crbug.com/865616
-enum class WidgetCreationType {
-  // The widget was created internally, and not at the request of a client.
-  // For example, overview mode creates a number of widgets. These widgets are
-  // created with a type of INTERNAL. This is the default.
-  INTERNAL,
-
-  // The widget was created for a client. In other words there is a client
-  // embedded in the aura::Window. For example, when Chrome creates a new
-  // browser window the window manager is asked to create the aura::Window.
-  // The window manager creates an aura::Window and a views::Widget to show
-  // the non-client frame decorations. In this case the creation type is
-  // FOR_CLIENT.
-  FOR_CLIENT,
-};
-
 // Shell-specific window property keys; some keys are exported for use in tests.
 
 // Alphabetical sort.
@@ -47,9 +27,6 @@
 // bounds outside of its root window is set.
 ASH_EXPORT extern const aura::WindowProperty<bool>* const kLockedToRootKey;
 
-ASH_EXPORT extern const aura::WindowProperty<WidgetCreationType>* const
-    kWidgetCreationTypeKey;
-
 // Set to true if the window server tells us the window is janky (see
 // WindowManagerDelegate::OnWmClientJankinessChanged()).
 ASH_EXPORT extern const aura::WindowProperty<bool>* const kWindowIsJanky;
diff --git a/ash/wm/window_state.cc b/ash/wm/window_state.cc
index 9f19663..493252e9 100644
--- a/ash/wm/window_state.cc
+++ b/ash/wm/window_state.cc
@@ -194,7 +194,8 @@
 }
 
 void WindowState::SetDelegate(std::unique_ptr<WindowStateDelegate> delegate) {
-  DCHECK(!delegate_.get());
+  DCHECK((!delegate_.get() && !!delegate.get()) ||
+         (!!delegate_.get() && !delegate.get()));
   delegate_ = std::move(delegate);
 }
 
diff --git a/base/profiler/stack_sampler.h b/base/profiler/stack_sampler.h
index 262e38d..a6f13d00 100644
--- a/base/profiler/stack_sampler.h
+++ b/base/profiler/stack_sampler.h
@@ -13,6 +13,7 @@
 
 namespace base {
 
+class Unwinder;
 class ModuleCache;
 class ProfileBuilder;
 class StackSamplerTestDelegate;
@@ -61,6 +62,10 @@
   // The following functions are all called on the SamplingThread (not the
   // thread being sampled).
 
+  // Adds an auxiliary unwinder to handle additional, non-native-code unwind
+  // scenarios.
+  virtual void AddAuxUnwinder(Unwinder* unwinder) = 0;
+
   // Records a set of frames and returns them.
   virtual void RecordStackFrames(StackBuffer* stackbuffer,
                                  ProfileBuilder* profile_builder) = 0;
diff --git a/base/profiler/stack_sampler_impl.cc b/base/profiler/stack_sampler_impl.cc
index a1153c3..fffb59a1 100644
--- a/base/profiler/stack_sampler_impl.cc
+++ b/base/profiler/stack_sampler_impl.cc
@@ -85,6 +85,10 @@
 
 StackSamplerImpl::~StackSamplerImpl() = default;
 
+void StackSamplerImpl::AddAuxUnwinder(Unwinder* unwinder) {
+  aux_unwinder_ = unwinder;
+}
+
 void StackSamplerImpl::RecordStackFrames(StackBuffer* stack_buffer,
                                          ProfileBuilder* profile_builder) {
   DCHECK(stack_buffer);
diff --git a/base/profiler/stack_sampler_impl.h b/base/profiler/stack_sampler_impl.h
index 926c4a3..d7aaac1 100644
--- a/base/profiler/stack_sampler_impl.h
+++ b/base/profiler/stack_sampler_impl.h
@@ -30,7 +30,8 @@
   StackSamplerImpl(const StackSamplerImpl&) = delete;
   StackSamplerImpl& operator=(const StackSamplerImpl&) = delete;
 
-  // Records a set of frames and returns them.
+  // StackSampler:
+  void AddAuxUnwinder(Unwinder* unwinder) override;
   void RecordStackFrames(StackBuffer* stack_buffer,
                          ProfileBuilder* profile_builder) override;
 
@@ -45,6 +46,7 @@
 
   const std::unique_ptr<ThreadDelegate> thread_delegate_;
   const std::unique_ptr<Unwinder> native_unwinder_;
+  Unwinder* aux_unwinder_ = nullptr;
   ModuleCache* const module_cache_;
   StackSamplerTestDelegate* const test_delegate_;
 };
diff --git a/base/profiler/stack_sampling_profiler.cc b/base/profiler/stack_sampling_profiler.cc
index f5687e7..0c7c1f4 100644
--- a/base/profiler/stack_sampling_profiler.cc
+++ b/base/profiler/stack_sampling_profiler.cc
@@ -125,6 +125,10 @@
   // stop the sampling.
   int Add(std::unique_ptr<CollectionContext> collection);
 
+  // Adds an auxiliary unwinder to be used for the collection, to handle
+  // additional, non-native-code unwind scenarios.
+  void AddAuxUnwinder(int collection_id, Unwinder* unwinder);
+
   // Removes an active collection based on its collection id, forcing it to run
   // its callback if any data has been collected. This can be called externally
   // from any thread.
@@ -174,6 +178,7 @@
 
   // These methods are tasks that get posted to the internal message queue.
   void AddCollectionTask(std::unique_ptr<CollectionContext> collection);
+  void AddAuxUnwinderTask(int collection_id, Unwinder* unwinder);
   void RemoveCollectionTask(int collection_id);
   void RecordSampleTask(int collection_id);
   void ShutdownTask(int add_events);
@@ -324,6 +329,18 @@
   return collection_id;
 }
 
+void StackSamplingProfiler::SamplingThread::AddAuxUnwinder(int collection_id,
+                                                           Unwinder* unwinder) {
+  ThreadExecutionState state;
+  scoped_refptr<SingleThreadTaskRunner> task_runner = GetTaskRunner(&state);
+  if (state != RUNNING)
+    return;
+  DCHECK(task_runner);
+  task_runner->PostTask(
+      FROM_HERE, BindOnce(&SamplingThread::AddAuxUnwinderTask, Unretained(this),
+                          collection_id, unwinder));
+}
+
 void StackSamplingProfiler::SamplingThread::Remove(int collection_id) {
   // This is not to be run on the sampling thread.
 
@@ -458,6 +475,18 @@
       TimeDelta::FromSeconds(60));
 }
 
+void StackSamplingProfiler::SamplingThread::AddAuxUnwinderTask(
+    int collection_id,
+    Unwinder* unwinder) {
+  DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
+
+  auto loc = active_collections_.find(collection_id);
+  if (loc == active_collections_.end())
+    return;
+
+  loc->second->native_sampler->AddAuxUnwinder(unwinder);
+}
+
 void StackSamplingProfiler::SamplingThread::AddCollectionTask(
     std::unique_ptr<CollectionContext> collection) {
   DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
@@ -715,4 +744,9 @@
   profiler_id_ = kNullProfilerId;
 }
 
+void StackSamplingProfiler::AddAuxUnwinder(Unwinder* unwinder) {
+  SamplingThread::GetInstance()->AddAuxUnwinder(profiler_id_,
+                                                std::move(unwinder));
+}
+
 }  // namespace base
diff --git a/base/profiler/stack_sampling_profiler.h b/base/profiler/stack_sampling_profiler.h
index 25779f3..71f8d0c5 100644
--- a/base/profiler/stack_sampling_profiler.h
+++ b/base/profiler/stack_sampling_profiler.h
@@ -17,6 +17,7 @@
 
 namespace base {
 
+class Unwinder;
 class StackSampler;
 class StackSamplerTestDelegate;
 
@@ -110,6 +111,10 @@
   // are completed or the profiler object is destroyed, whichever occurs first.
   void Stop();
 
+  // Adds an auxiliary unwinder to handle additional, non-native-code unwind
+  // scenarios.
+  void AddAuxUnwinder(Unwinder* unwinder);
+
   // Test peer class. These functions are purely for internal testing of
   // StackSamplingProfiler; DO NOT USE within tests outside of this directory.
   // The functions are static because they interact with the sampling thread, a
diff --git a/cc/paint/image_provider.cc b/cc/paint/image_provider.cc
index fa224ad5..e30c8bb 100644
--- a/cc/paint/image_provider.cc
+++ b/cc/paint/image_provider.cc
@@ -17,27 +17,24 @@
                                           DestructionCallback callback)
     : image_(std::move(image)), destruction_callback_(std::move(callback)) {}
 
-ImageProvider::ScopedResult::ScopedResult(const PaintRecord* record,
+ImageProvider::ScopedResult::ScopedResult(sk_sp<PaintRecord> record,
                                           DestructionCallback callback)
-    : record_(record), destruction_callback_(std::move(callback)) {
+    : record_(std::move(record)), destruction_callback_(std::move(callback)) {
   DCHECK(!destruction_callback_.is_null());
 }
 
 ImageProvider::ScopedResult::ScopedResult(ScopedResult&& other)
     : image_(std::move(other.image_)),
-      record_(other.record_),
-      destruction_callback_(std::move(other.destruction_callback_)) {
-  other.record_ = nullptr;
-}
+      record_(std::move(other.record_)),
+      destruction_callback_(std::move(other.destruction_callback_)) {}
 
 ImageProvider::ScopedResult& ImageProvider::ScopedResult::operator=(
     ScopedResult&& other) {
   DestroyDecode();
 
   image_ = std::move(other.image_);
-  record_ = other.record_;
+  record_ = std::move(other.record_);
   destruction_callback_ = std::move(other.destruction_callback_);
-  other.record_ = nullptr;
   return *this;
 }
 
diff --git a/cc/paint/image_provider.h b/cc/paint/image_provider.h
index 6ffd8978..2b5e5f707 100644
--- a/cc/paint/image_provider.h
+++ b/cc/paint/image_provider.h
@@ -10,6 +10,7 @@
 #include "cc/paint/decoded_draw_image.h"
 #include "cc/paint/draw_image.h"
 #include "cc/paint/paint_export.h"
+#include "cc/paint/paint_op_buffer.h"
 
 #include <vector>
 
@@ -27,7 +28,7 @@
     ScopedResult();
     explicit ScopedResult(DecodedDrawImage image);
     ScopedResult(DecodedDrawImage image, DestructionCallback callback);
-    ScopedResult(const PaintRecord* record, DestructionCallback callback);
+    ScopedResult(sk_sp<PaintRecord> record, DestructionCallback callback);
     ScopedResult(const ScopedResult&) = delete;
     ScopedResult(ScopedResult&& other);
     ~ScopedResult();
@@ -40,14 +41,14 @@
     bool needs_unlock() const { return !destruction_callback_.is_null(); }
     const PaintRecord* paint_record() {
       DCHECK(record_);
-      return record_;
+      return record_.get();
     }
 
    private:
     void DestroyDecode();
 
     DecodedDrawImage image_;
-    const PaintRecord* record_ = nullptr;
+    sk_sp<PaintRecord> record_;
     DestructionCallback destruction_callback_;
   };
 
diff --git a/cc/paint/paint_op_buffer_unittest.cc b/cc/paint/paint_op_buffer_unittest.cc
index 5d82c6e..39ffaff 100644
--- a/cc/paint/paint_op_buffer_unittest.cc
+++ b/cc/paint/paint_op_buffer_unittest.cc
@@ -2742,7 +2742,7 @@
                                          quality_[i], true));
   }
 
-  void SetRecord(PaintRecord* record) { record_ = record; }
+  void SetRecord(sk_sp<PaintRecord> record) { record_ = std::move(record); }
 
  private:
   std::vector<SkSize> src_rect_offset_;
@@ -2750,7 +2750,7 @@
   std::vector<SkFilterQuality> quality_;
   size_t index_ = 0;
   bool fail_all_decodes_ = false;
-  PaintRecord* record_;
+  sk_sp<PaintRecord> record_;
 };
 
 TEST(PaintOpBufferTest, SkipsOpsOutsideClip) {
@@ -2845,18 +2845,18 @@
 }
 
 TEST(PaintOpBufferTest, RasterPaintWorkletImage1) {
-  PaintOpBuffer paint_worklet_buffer;
+  sk_sp<PaintOpBuffer> paint_worklet_buffer = sk_make_sp<PaintOpBuffer>();
   PaintFlags noop_flags;
   SkRect savelayer_rect = SkRect::MakeXYWH(0, 0, 100, 100);
-  paint_worklet_buffer.push<TranslateOp>(8.0f, 8.0f);
-  paint_worklet_buffer.push<SaveLayerOp>(&savelayer_rect, &noop_flags);
+  paint_worklet_buffer->push<TranslateOp>(8.0f, 8.0f);
+  paint_worklet_buffer->push<SaveLayerOp>(&savelayer_rect, &noop_flags);
   PaintFlags draw_flags;
   draw_flags.setColor(0u);
   SkRect rect = SkRect::MakeXYWH(0, 0, 100, 100);
-  paint_worklet_buffer.push<DrawRectOp>(rect, draw_flags);
+  paint_worklet_buffer->push<DrawRectOp>(rect, draw_flags);
 
   MockImageProvider provider;
-  provider.SetRecord(&paint_worklet_buffer);
+  provider.SetRecord(paint_worklet_buffer);
 
   PaintOpBuffer blink_buffer;
   scoped_refptr<TestPaintWorkletInput> input =
@@ -2878,20 +2878,20 @@
 }
 
 TEST(PaintOpBufferTest, RasterPaintWorkletImage2) {
-  PaintOpBuffer paint_worklet_buffer;
+  sk_sp<PaintOpBuffer> paint_worklet_buffer = sk_make_sp<PaintOpBuffer>();
   PaintFlags noop_flags;
   SkRect savelayer_rect = SkRect::MakeXYWH(0, 0, 10, 10);
-  paint_worklet_buffer.push<SaveLayerOp>(&savelayer_rect, &noop_flags);
+  paint_worklet_buffer->push<SaveLayerOp>(&savelayer_rect, &noop_flags);
   PaintFlags draw_flags;
   draw_flags.setFilterQuality(kLow_SkFilterQuality);
   PaintImage paint_image = CreateDiscardablePaintImage(gfx::Size(10, 10));
-  paint_worklet_buffer.push<DrawImageOp>(paint_image, 0.0f, 0.0f, &draw_flags);
+  paint_worklet_buffer->push<DrawImageOp>(paint_image, 0.0f, 0.0f, &draw_flags);
 
   std::vector<SkSize> src_rect_offset = {SkSize::MakeEmpty()};
   std::vector<SkSize> scale_adjustment = {SkSize::Make(0.2f, 0.2f)};
   std::vector<SkFilterQuality> quality = {kHigh_SkFilterQuality};
   MockImageProvider provider(src_rect_offset, scale_adjustment, quality);
-  provider.SetRecord(&paint_worklet_buffer);
+  provider.SetRecord(paint_worklet_buffer);
 
   PaintOpBuffer blink_buffer;
   scoped_refptr<TestPaintWorkletInput> input =
diff --git a/cc/raster/paint_worklet_image_provider.cc b/cc/raster/paint_worklet_image_provider.cc
index f33424d..7312ba3 100644
--- a/cc/raster/paint_worklet_image_provider.cc
+++ b/cc/raster/paint_worklet_image_provider.cc
@@ -25,9 +25,9 @@
 
 ImageProvider::ScopedResult PaintWorkletImageProvider::GetPaintRecordResult(
     PaintWorkletInput* input) {
-  std::pair<PaintRecord*, base::OnceCallback<void()>> record_and_callback =
-      cache_->GetPaintRecordAndRef(input);
-  return ImageProvider::ScopedResult(record_and_callback.first,
+  std::pair<sk_sp<PaintRecord>, base::OnceCallback<void()>>
+      record_and_callback = cache_->GetPaintRecordAndRef(input);
+  return ImageProvider::ScopedResult(std::move(record_and_callback.first),
                                      std::move(record_and_callback.second));
 }
 
diff --git a/cc/tiles/paint_worklet_image_cache.cc b/cc/tiles/paint_worklet_image_cache.cc
index b955b08..7d6cc848 100644
--- a/cc/tiles/paint_worklet_image_cache.cc
+++ b/cc/tiles/paint_worklet_image_cache.cc
@@ -5,6 +5,7 @@
 #include "cc/tiles/paint_worklet_image_cache.h"
 
 #include "base/bind.h"
+#include "base/bind_helpers.h"
 #include "cc/paint/paint_worklet_layer_painter.h"
 
 namespace cc {
@@ -85,15 +86,13 @@
   }
 }
 
-std::pair<PaintRecord*, base::OnceCallback<void()>>
+std::pair<sk_sp<PaintRecord>, base::OnceCallback<void()>>
 PaintWorkletImageCache::GetPaintRecordAndRef(PaintWorkletInput* input) {
   base::AutoLock hold(records_lock_);
   // If the |painter_| is null, then GetTaskForPaintWorkletImage will return a
   // null TileTask, and hence there will be no cache entry for this input.
-  if (!painter_) {
-    return std::make_pair(sk_make_sp<PaintOpBuffer>().get(),
-                          base::OnceCallback<void()>());
-  }
+  if (!painter_)
+    return std::make_pair(sk_make_sp<PaintOpBuffer>(), base::DoNothing::Once());
   DCHECK(records_.find(input) != records_.end());
   records_[input].used_ref_count++;
   records_[input].num_of_frames_not_accessed = 0u;
@@ -103,7 +102,7 @@
   auto callback =
       base::BindOnce(&PaintWorkletImageCache::DecrementCacheRefCount,
                      base::Unretained(this), base::Unretained(input));
-  return std::make_pair(records_[input].record.get(), std::move(callback));
+  return std::make_pair(records_[input].record, std::move(callback));
 }
 
 void PaintWorkletImageCache::SetNumOfFramesToPurgeCacheEntryForTest(
diff --git a/cc/tiles/paint_worklet_image_cache.h b/cc/tiles/paint_worklet_image_cache.h
index e5ef233a..f9b976f3 100644
--- a/cc/tiles/paint_worklet_image_cache.h
+++ b/cc/tiles/paint_worklet_image_cache.h
@@ -51,8 +51,8 @@
   void NotifyDidPrepareTiles();
 
   // Returns a callback to decrement the ref count for the corresponding entry.
-  std::pair<PaintRecord*, base::OnceCallback<void()>> GetPaintRecordAndRef(
-      PaintWorkletInput* input);
+  std::pair<sk_sp<PaintRecord>, base::OnceCallback<void()>>
+  GetPaintRecordAndRef(PaintWorkletInput* input);
 
   const base::flat_map<PaintWorkletInput*, PaintWorkletImageCacheValue>&
   GetRecordsForTest() {
diff --git a/cc/tiles/paint_worklet_image_cache_unittest.cc b/cc/tiles/paint_worklet_image_cache_unittest.cc
index 4ee6b0d..4a26f40 100644
--- a/cc/tiles/paint_worklet_image_cache_unittest.cc
+++ b/cc/tiles/paint_worklet_image_cache_unittest.cc
@@ -198,7 +198,7 @@
       records[paint_image2.paint_worklet_input()].num_of_frames_not_accessed,
       1u);
 
-  std::pair<PaintRecord*, base::OnceCallback<void()>> pair =
+  std::pair<sk_sp<PaintRecord>, base::OnceCallback<void()>> pair =
       cache.GetPaintRecordAndRef(paint_image1.paint_worklet_input());
   // Run the callback to decrement the ref count.
   std::move(pair.second).Run();
@@ -257,5 +257,23 @@
   }
 }
 
+TEST(PaintWorkletImageCacheTest, TaskIsNullWhenPainterIsNull) {
+  TestPaintWorkletImageCache cache;
+  PaintImage paint_image = CreatePaintImage(100, 100);
+  scoped_refptr<TileTask> task =
+      GetTaskForPaintWorkletImage(paint_image, &cache);
+  EXPECT_EQ(task, nullptr);
+}
+
+TEST(PaintWorkletImageCacheTest, RecordAndCallbackAreEmptyWhenPainterIsNull) {
+  TestPaintWorkletImageCache cache;
+  PaintImage paint_image = CreatePaintImage(100, 100);
+  std::pair<sk_sp<PaintRecord>, base::OnceCallback<void()>> result =
+      cache.GetPaintRecordAndRef(paint_image.paint_worklet_input());
+  EXPECT_EQ(result.first->total_op_count(), 0u);
+  // This is an empty callback, running it should not crash.
+  std::move(result.second).Run();
+}
+
 }  // namespace
 }  // namespace cc
diff --git a/chrome/BUILD.gn b/chrome/BUILD.gn
index 048d0bd..0bec89c 100644
--- a/chrome/BUILD.gn
+++ b/chrome/BUILD.gn
@@ -1582,6 +1582,7 @@
 
   if (is_chromeos) {
     public_deps += [
+      "//chrome/browser/resources/chromeos:cellular_setup_resources",
       "//chrome/browser/resources/chromeos:multidevice_setup_resources",
       "//chrome/browser/resources/chromeos/chromevox",
       "//chrome/browser/resources/chromeos/select_to_speak:build",
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 0d054c7..0d9abf1 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -397,6 +397,8 @@
   "java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTapState.java",
   "java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTranslateController.java",
   "java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTranslateInterface.java",
+  "java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTranslation.java",
+  "java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTranslationImpl.java",
   "java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchUma.java",
   "java/src/org/chromium/chrome/browser/contextualsearch/CtrSuppression.java",
   "java/src/org/chromium/chrome/browser/contextualsearch/DisableablePromoTapCounter.java",
diff --git a/chrome/android/chrome_junit_test_java_sources.gni b/chrome/android/chrome_junit_test_java_sources.gni
index 1d68bf3..3650ab8 100644
--- a/chrome/android/chrome_junit_test_java_sources.gni
+++ b/chrome/android/chrome_junit_test_java_sources.gni
@@ -51,6 +51,7 @@
   "junit/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchEntityHeuristicTest.java",
   "junit/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateTest.java",
   "junit/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionControllerTest.java",
+  "junit/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTranslationImplTest.java",
   "junit/src/org/chromium/chrome/browser/contextualsearch/SelectionClientManagerTest.java",
   "junit/src/org/chromium/chrome/browser/cookies/CanonicalCookieTest.java",
   "junit/src/org/chromium/chrome/browser/crash/LogcatExtractionRunnableUnitTest.java",
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java
index 4549443..291cf89 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java
@@ -4,16 +4,20 @@
 
 package org.chromium.chrome.browser.tasks.tab_management;
 
+import static org.chromium.chrome.browser.tasks.tab_management.TabManagementModuleProvider.SYNTHETIC_TRIAL_POSTFIX;
+
 import android.content.Context;
 import android.view.ViewGroup;
 
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.ThemeColorProvider;
 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
 import org.chromium.chrome.browser.init.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.lifecycle.PauseResumeWithNativeObserver;
+import org.chromium.chrome.browser.metrics.UmaSessionStats;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabModelFilterProvider;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
@@ -59,6 +63,11 @@
     @Override
     public void initializeWithNative(ChromeActivity activity,
             BottomControlsCoordinator.BottomControlsVisibilityController visibilityController) {
+        if (ChromeFeatureList.isInitialized()) {
+            UmaSessionStats.registerSyntheticFieldTrial(
+                    ChromeFeatureList.TAB_GROUPS_ANDROID + SYNTHETIC_TRIAL_POSTFIX,
+                    "Downloaded_Enabled");
+        }
         assert activity instanceof ChromeTabbedActivity;
         mActivity = activity;
         TabModelSelector tabModelSelector = activity.getTabModelSelector();
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementModuleImpl.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementModuleImpl.java
index 73c61ae..3c94a78 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementModuleImpl.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementModuleImpl.java
@@ -4,11 +4,15 @@
 
 package org.chromium.chrome.browser.tasks.tab_management;
 
+import static org.chromium.chrome.browser.tasks.tab_management.TabManagementModuleProvider.SYNTHETIC_TRIAL_POSTFIX;
+
 import android.view.ViewGroup;
 
 import org.chromium.base.annotations.UsedByReflection;
 import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.ThemeColorProvider;
+import org.chromium.chrome.browser.metrics.UmaSessionStats;
 
 /**
  * Impl class that will resolve components for tab management.
@@ -17,6 +21,11 @@
 public class TabManagementModuleImpl implements TabManagementModule {
     @Override
     public GridTabSwitcher createGridTabSwitcher(ChromeActivity activity) {
+        if (ChromeFeatureList.isInitialized()) {
+            UmaSessionStats.registerSyntheticFieldTrial(
+                    ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID + SYNTHETIC_TRIAL_POSTFIX,
+                    "Downloaded_Enabled");
+        }
         return new GridTabSwitcherCoordinator(activity, activity.getLifecycleDispatcher(),
                 activity.getToolbarManager(), activity.getTabModelSelector(),
                 activity.getTabContentManager(), activity.getCompositorViewHolder(),
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementModuleProvider.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementModuleProvider.java
index 17fcb67..718b195b 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementModuleProvider.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementModuleProvider.java
@@ -6,12 +6,16 @@
 
 import android.support.annotation.Nullable;
 
+import org.chromium.chrome.browser.ChromeFeatureList;
+import org.chromium.chrome.browser.metrics.UmaSessionStats;
 import org.chromium.components.module_installer.ModuleInstaller;
 
 /**
  * Provider class for TabManagementModule.
  */
 public class TabManagementModuleProvider {
+    public static final String SYNTHETIC_TRIAL_POSTFIX = "SyntheticTrial";
+
     private static final String TAB_MANAGEMENT_MODULE_IMPL_CLASS_NAME =
             "org.chromium.chrome.browser.tasks.tab_management.TabManagementModuleImpl";
 
@@ -28,8 +32,28 @@
         } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
                 | IllegalArgumentException e) {
             ModuleInstaller.installDeferred("tab_ui");
+            if (ChromeFeatureList.isInitialized()) {
+                UmaSessionStats.registerSyntheticFieldTrial(
+                        ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID + SYNTHETIC_TRIAL_POSTFIX,
+                        "DownloadAttempted");
+                UmaSessionStats.registerSyntheticFieldTrial(
+                        ChromeFeatureList.TAB_GROUPS_ANDROID + SYNTHETIC_TRIAL_POSTFIX,
+                        "DownloadAttempted");
+            }
             return null;
         }
+        if (ChromeFeatureList.isInitialized()) {
+            if (!ChromeFeatureList.isEnabled(ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID)) {
+                UmaSessionStats.registerSyntheticFieldTrial(
+                        ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID + SYNTHETIC_TRIAL_POSTFIX,
+                        "Downloaded_Control");
+            }
+            if (!ChromeFeatureList.isEnabled(ChromeFeatureList.TAB_GROUPS_ANDROID)) {
+                UmaSessionStats.registerSyntheticFieldTrial(
+                        ChromeFeatureList.TAB_GROUPS_ANDROID + SYNTHETIC_TRIAL_POSTFIX,
+                        "Downloaded_Control");
+            }
+        }
         return tabManagementModule;
     }
 }
diff --git a/chrome/android/java/AndroidManifest.xml b/chrome/android/java/AndroidManifest.xml
index 640ec33c5..bf56f45 100644
--- a/chrome/android/java/AndroidManifest.xml
+++ b/chrome/android/java/AndroidManifest.xml
@@ -182,7 +182,7 @@
              the module installation will not automatically bring in DFM's
              manifest entries. -->
         <meta-data android:name="com.google.ar.core.min_apk_version"
-            android:value="181012000"/>
+            android:value="190310000"/>
         <activity
             android:name="com.google.ar.core.InstallActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
diff --git a/chrome/android/java/monochrome_public_apk.AndroidManifest.expected b/chrome/android/java/monochrome_public_apk.AndroidManifest.expected
index f78d443..e872549 100644
--- a/chrome/android/java/monochrome_public_apk.AndroidManifest.expected
+++ b/chrome/android/java/monochrome_public_apk.AndroidManifest.expected
@@ -1197,7 +1197,7 @@
         android:name="com.google.android.gms.version"
         android:value="@integer/google_play_services_version"/>
     <meta-data android:name="com.google.ar.core" android:value="optional"/>
-    <meta-data android:name="com.google.ar.core.min_apk_version" android:value="181012000"/>
+    <meta-data android:name="com.google.ar.core.min_apk_version" android:value="190310000"/>
     <meta-data android:name="com.samsung.android.sdk.multiwindow.enable" android:value="true"/>
     <meta-data
         android:name="com.samsung.android.sdk.multiwindow.penwindow.enable"
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
index c6c8935d..3485f71 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
@@ -209,6 +209,8 @@
     public static final String CONTEXTUAL_SEARCH_SECOND_TAP = "ContextualSearchSecondTap";
     public static final String CONTEXTUAL_SEARCH_TAP_DISABLE_OVERRIDE =
             "ContextualSearchTapDisableOverride";
+    public static final String CONTEXTUAL_SEARCH_TRANSLATION_MODEL =
+            "ContextualSearchTranslationModel";
     public static final String CONTEXTUAL_SEARCH_UNITY_INTEGRATION =
             "ContextualSearchUnityIntegration";
     public static final String CONTEXTUAL_SUGGESTIONS_BUTTON = "ContextualSuggestionsButton";
@@ -219,6 +221,8 @@
     public static final String DATA_SAVER_LITE_MODE_REBRANDING = "DataSaverLiteModeRebranding";
     public static final String DELEGATE_OVERSCROLL_SWIPES = "DelegateOverscrollSwipes";
     public static final String DONT_PREFETCH_LIBRARIES = "DontPrefetchLibraries";
+    public static final String DOWNLOAD_LOCATION_SHOW_IMAGE_IN_GALLERY =
+            "DownloadLocationShowImageInGallery";
     public static final String DOWNLOAD_HOME_SHOW_STORAGE_INFO = "DownloadHomeShowStorageInfo";
     public static final String DOWNLOAD_PROGRESS_INFOBAR = "DownloadProgressInfoBar";
     public static final String DOWNLOAD_HOME_V2 = "DownloadHomeV2";
@@ -228,6 +232,7 @@
     public static final String DOWNLOAD_OFFLINE_CONTENT_PROVIDER =
             "UseDownloadOfflineContentProvider";
     public static final String DOWNLOADS_LOCATION_CHANGE = "DownloadsLocationChange";
+    public static final String DOWNLOAD_TAB_MANAGEMENT_MODULE = "DownloadTabManagementModule";
     public static final String DRAW_VERTICALLY_EDGE_TO_EDGE = "DrawVerticallyEdgeToEdge";
     public static final String EPHEMERAL_TAB = "EphemeralTab";
     public static final String EXPERIMENTAL_APP_BANNERS = "ExperimentalAppBanners";
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
index ad9e5822..45c5182 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
@@ -136,7 +136,7 @@
     private ContextualSearchInternalStateController mInternalStateController;
 
     @VisibleForTesting
-    protected ContextualSearchTranslateController mTranslateController;
+    protected ContextualSearchTranslation mTranslateController;
 
     // The Overlay panel.
     private ContextualSearchPanel mSearchPanel;
@@ -238,7 +238,8 @@
         mSelectionController = new ContextualSearchSelectionController(activity, this);
         mNetworkCommunicator = this;
         mPolicy = new ContextualSearchPolicy(mSelectionController, mNetworkCommunicator);
-        mTranslateController = new ContextualSearchTranslateController(mPolicy, this);
+        mTranslateController =
+                ContextualSearchTranslateController.getContextualSearchTranslation(mPolicy, this);
         mInternalStateController = new ContextualSearchInternalStateController(
                 mPolicy, getContextualSearchInternalStateHandler());
         mInteractionRecorder = new ContextualSearchRankerLoggerImpl();
@@ -465,8 +466,7 @@
             mShouldLoadDelayedSearch = false;
         }
         if (isTap && mPolicy.shouldPreviousTapResolve()) {
-            // Cache the native translate data, so JNI calls won't be made when time-critical.
-            mTranslateController.cacheNativeTranslateData();
+            // For a resolving Tap we'll figure out translation need after the Resolve.
         } else if (!TextUtils.isEmpty(selection)) {
             boolean shouldPrefetch = mPolicy.shouldPrefetchSearchResult();
             mSearchRequest = new ContextualSearchRequest(selection, shouldPrefetch);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTranslateController.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTranslateController.java
index faba8f4..2f2427e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTranslateController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTranslateController.java
@@ -9,6 +9,7 @@
 import android.text.TextUtils;
 
 import org.chromium.base.ContextUtils;
+import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.ui.UiUtils;
 
 import java.util.ArrayList;
@@ -16,11 +17,10 @@
 import java.util.List;
 import java.util.Locale;
 
-
 /**
  * Controls how Translation One-box triggering is handled for the {@link ContextualSearchManager}.
  */
-public class ContextualSearchTranslateController  {
+public class ContextualSearchTranslateController implements ContextualSearchTranslation {
     private static final int LOCALE_MIN_LENGTH = 2;
 
     private final ContextualSearchPolicy mPolicy;
@@ -30,19 +30,33 @@
     private String mTranslateServiceTargetLanguage;
     private String mAcceptLanguages;
 
-    ContextualSearchTranslateController(
+    /**
+     * Constructs a translation implementation that determines when to trigger translations for
+     * Contextual Search requests.
+     * @param policy The {@link ContextualSearchPolicy} for determining the target language and
+     *        whether translation is disabled.
+     * @param hostInterface A {@link ContextualSearchTranslateInterface} back to the host which
+     *        provides native implementations of this interface.
+     */
+    static public ContextualSearchTranslation getContextualSearchTranslation(
+            ContextualSearchPolicy policy, ContextualSearchTranslateInterface hostInterface) {
+        if (useChromeLanguageModel()) {
+            return new ContextualSearchTranslationImpl(policy);
+        } else {
+            return new ContextualSearchTranslateController(policy, hostInterface);
+        }
+    }
+
+    /** Do not construct directly, call getContextualSearchTranslation static method. */
+    protected ContextualSearchTranslateController(
             ContextualSearchPolicy policy, ContextualSearchTranslateInterface hostInterface) {
         mPolicy = policy;
         mHost = hostInterface;
     }
 
-    /**
-     * Force translation from the given language for the current search request,
-     * unless disabled by a Chrome Variation.  Also log whenever conditions are right to translate.
-     * @param searchRequest The search request to force translation upon.
-     * @param sourceLanguage The language to translate from, or an empty string if not known.
-     */
-    void forceTranslateIfNeeded(ContextualSearchRequest searchRequest, String sourceLanguage) {
+    @Override
+    public void forceTranslateIfNeeded(
+            ContextualSearchRequest searchRequest, String sourceLanguage) {
         if (mPolicy.isTranslationDisabled()) return;
 
         // Force translation if not disabled and server controlled or client logic says required.
@@ -51,20 +65,12 @@
             searchRequest.forceTranslation(
                     sourceLanguage, mPolicy.bestTargetLanguage(getProficientLanguageList()));
         }
-        // Log that conditions were right for translation, even though it may be disabled
-        // for an experiment so we can compare with the counter factual data.
-        ContextualSearchUma.logTranslateOnebox(doForceTranslate);
-
         // Log whether or not translate conditions are met
         ContextualSearchUma.logTranslateCondition(doForceTranslate);
     }
 
-    /**
-     * Force auto-detect translation for the current search request unless disabled by experiment.
-     * Also log that conditions are right to translate.
-     * @param searchRequest The search request to force translation upon.
-     */
-    void forceAutoDetectTranslateUnlessDisabled(ContextualSearchRequest searchRequest) {
+    @Override
+    public void forceAutoDetectTranslateUnlessDisabled(ContextualSearchRequest searchRequest) {
         // Always trigger translation using auto-detect when we're not resolving,
         // unless disabled by policy.
         if (mPolicy.isTranslationDisabled()) return;
@@ -75,27 +81,19 @@
             searchRequest.forceAutoDetectTranslation(
                     mPolicy.bestTargetLanguage(getProficientLanguageList()));
         }
-        // Log that conditions were right for translation, even though it may be disabled
-        // for an experiment so we can compare with the counter factual data.
-        ContextualSearchUma.logTranslateOnebox(true);
     }
 
-    /**
-     * Caches all the native translate language info, so we can avoid repeated JNI calls.
-     */
-    void cacheNativeTranslateData() {
-        if (mPolicy.isTranslationDisabled()) return;
-
-        getNativeTranslateServiceTargetLanguage();
-        getNativeAcceptLanguages();
-    }
-
-    /** @return Whether the given {@code sourceLanguage} needs translation for the current user. */
-    boolean needsTranslation(@Nullable String sourceLanguage) {
+    @Override
+    public boolean needsTranslation(@Nullable String sourceLanguage) {
         return !mPolicy.isTranslationDisabled() && !TextUtils.isEmpty(sourceLanguage)
                 && mPolicy.needsTranslation(sourceLanguage, getReadableLanguages());
     }
 
+    @Override
+    public String getTranslateServiceTargetLanguage() {
+        return getNativeTranslateServiceTargetLanguage();
+    }
+
     /**
      * Gets the list of readable languages for the current user, with the first
      * item in the list being the user's primary language (according to the Translate Service).
@@ -182,6 +180,11 @@
         return new Locale(trimmedLocale).getLanguage();
     }
 
+    /** @return whether we should use the Chrome Language Model due to the feature being enabled. */
+    static private boolean useChromeLanguageModel() {
+        return ChromeFeatureList.isEnabled(ChromeFeatureList.CONTEXTUAL_SEARCH_TRANSLATION_MODEL);
+    }
+
     /**
      * @return The accept-languages string from the cache or from native code (when not cached).
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTranslation.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTranslation.java
new file mode 100644
index 0000000..2f74c35
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTranslation.java
@@ -0,0 +1,41 @@
+// Copyright 2019 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.
+
+package org.chromium.chrome.browser.contextualsearch;
+
+import android.support.annotation.Nullable;
+
+/**
+ * Provides an interface for forcing translation on a Contextual Search Request.
+ * When translation is forced, the request has additional parameters that force a one-box for the
+ * supplied source and destination languages.
+ * Methods support forcing translation on a request from a given source language, or forcing
+ * translation on a request using an auto-detection for whether the source and destination are
+ * different.
+ */
+public interface ContextualSearchTranslation {
+    /**
+     * Force translation from the given language for the given search request.
+     * Also log whenever conditions are right to translate.
+     * @param searchRequest The search request to force translation upon.
+     * @param sourceLanguage The language to translate from, or an empty string if not known.
+     */
+    void forceTranslateIfNeeded(ContextualSearchRequest searchRequest, String sourceLanguage);
+
+    /**
+     * Force auto-detect translation for the current search request.  The language to translate from
+     * will be auto-detected, and some overtriggering is likely but not harmful (because a translate
+     * onebox is suppressed when the from/to languages match.
+     * @param searchRequest The search request to force translation upon.
+     */
+    void forceAutoDetectTranslateUnlessDisabled(ContextualSearchRequest searchRequest);
+
+    /** @return Whether the given {@code sourceLanguage} needs translation for the current user. */
+    boolean needsTranslation(@Nullable String sourceLanguage);
+
+    /**
+     * @return The best target language based on what the Translate Service knows about the user.
+     */
+    String getTranslateServiceTargetLanguage();
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTranslationImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTranslationImpl.java
new file mode 100644
index 0000000..24d622f
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTranslationImpl.java
@@ -0,0 +1,98 @@
+// Copyright 2019 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.
+
+package org.chromium.chrome.browser.contextualsearch;
+
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+
+import org.chromium.chrome.browser.translate.TranslateBridge;
+
+import java.util.LinkedHashSet;
+
+/**
+ * Controls how Translation One-box triggering is handled for the {@link ContextualSearchManager}.
+ */
+public class ContextualSearchTranslationImpl implements ContextualSearchTranslation {
+    private final TranslateBridgeWrapper mTranslateBridgeWrapper;
+    private final ContextualSearchPolicy mPolicy;
+    /**
+     * Creates a {@link ContextualSearchTranslation} for updating {@link ContextualSearchRequest}s
+     * for translation.
+     */
+    ContextualSearchTranslationImpl(ContextualSearchPolicy policy) {
+        mPolicy = policy;
+        mTranslateBridgeWrapper = new TranslateBridgeWrapper();
+    }
+
+    /** Constructor useful for testing, uses the given {@link TranslateBridgeWrapper}. */
+    ContextualSearchTranslationImpl(
+            ContextualSearchPolicy policy, TranslateBridgeWrapper translateBridgeWrapper) {
+        mPolicy = policy;
+        mTranslateBridgeWrapper = translateBridgeWrapper;
+    }
+
+    @Override
+    public void forceTranslateIfNeeded(
+            ContextualSearchRequest searchRequest, String sourceLanguage) {
+        if (needsTranslation(sourceLanguage)) {
+            searchRequest.forceTranslation(sourceLanguage, getTranslateServiceTargetLanguage());
+        }
+    }
+
+    @Override
+    public void forceAutoDetectTranslateUnlessDisabled(ContextualSearchRequest searchRequest) {
+        searchRequest.forceAutoDetectTranslation(getTranslateServiceTargetLanguage());
+    }
+
+    @Override
+    public boolean needsTranslation(@Nullable String sourceLanguage) {
+        if (TextUtils.isEmpty(sourceLanguage) || isBlockedLanguage(sourceLanguage)
+                || mPolicy.isTranslationDisabled())
+            return false;
+
+        LinkedHashSet<String> languages = mTranslateBridgeWrapper.getModelLanguages();
+        for (String language : languages) {
+            if (language.equals(sourceLanguage)) return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String getTranslateServiceTargetLanguage() {
+        return mTranslateBridgeWrapper.getTargetLanguage();
+    }
+
+    /** @return whether the given {@code language} is blocked for translation. */
+    private boolean isBlockedLanguage(String language) {
+        return mTranslateBridgeWrapper.isBlockedLanguage(language);
+    }
+
+    /**
+     * Wraps our usage of the static methods in the {@link TranslateBridge} into a class that can be
+     * mocked for testing.
+     */
+    static class TranslateBridgeWrapper {
+        /** @return whether the given string is blocked for translation. */
+        public boolean isBlockedLanguage(String language) {
+            return TranslateBridge.isBlockedLanguage(language);
+        }
+
+        /**
+         * @return The best target language based on what the Translate Service knows about the
+         *         user.
+         */
+        public String getTargetLanguage() {
+            return TranslateBridge.getTargetLanguage();
+        }
+
+        /**
+         * @return The {@link LinkedHashSet} of language code strings that the Chrome Language Model
+         *         thinks the user knows, in order of most familiar to least familiar.
+         */
+        public LinkedHashSet<String> getModelLanguages() {
+            return TranslateBridge.getModelLanguages();
+        }
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchUma.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchUma.java
index 6ec6770..bad960bf 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchUma.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchUma.java
@@ -1348,17 +1348,6 @@
     }
 
     /**
-     * Logs that the conditions are right to force the translation one-box, and whether it
-     * was actually forced or not.
-     * @param didForceTranslate Whether the translation onebox was forced.
-     */
-    public static void logTranslateOnebox(boolean didForceTranslate) {
-        int code = didForceTranslate ? ForceTranslate.DID_FORCE : ForceTranslate.WOULD_FORCE;
-        RecordHistogram.recordEnumeratedHistogram(
-                "Search.ContextualSearchShouldTranslate", code, ForceTranslate.NUM_ENTRIES);
-    }
-
-    /**
      * Logs that whether or not the conditions are met to perform a translation.
      * @param isConditionMet Whether the translation conditions were met.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java
index 848210d..ea13439 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java
@@ -816,11 +816,21 @@
 
     @VisibleForTesting
     void onStop(int actionSource) {
+        // MediaSessionCompat calls this sometimes when `mMediaNotificationInfo`
+        // is no longer available. It's unclear if it is a Support Library issue
+        // or something that isn't properly cleaned up but given that the
+        // crashes are rare and the fix is simple, null check was enough.
+        if (mMediaNotificationInfo == null) return;
         mMediaNotificationInfo.listener.onStop(actionSource);
     }
 
     @VisibleForTesting
     void onMediaSessionAction(int action) {
+        // MediaSessionCompat calls this sometimes when `mMediaNotificationInfo`
+        // is no longer available. It's unclear if it is a Support Library issue
+        // or something that isn't properly cleaned up but given that the
+        // crashes are rare and the fix is simple, null check was enough.
+        if (mMediaNotificationInfo == null) return;
         mMediaNotificationInfo.listener.onMediaSessionAction(action);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ActionItem.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ActionItem.java
index b433f07..37da76b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ActionItem.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ActionItem.java
@@ -144,11 +144,6 @@
         uiDelegate.getEventReporter().onMoreButtonClicked(this);
 
         switch (mCategoryInfo.getAdditionalAction()) {
-            case ContentSuggestionsAdditionalAction.VIEW_ALL:
-                // The action does not reach the backend, so we record it here.
-                SuggestionsMetrics.recordActionViewAll();
-                mCategoryInfo.performViewAllAction(uiDelegate.getNavigationDelegate());
-                return;
             case ContentSuggestionsAdditionalAction.FETCH:
                 mParentSection.fetchSuggestions(onFailure, onNoNewSuggestions);
                 return;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsCategoryInfo.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsCategoryInfo.java
index 735ec94..7e24c073 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsCategoryInfo.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsCategoryInfo.java
@@ -6,14 +6,12 @@
 
 import android.support.annotation.Nullable;
 
-import org.chromium.base.Log;
 import org.chromium.chrome.browser.native_page.ContextMenuManager;
 import org.chromium.chrome.browser.native_page.ContextMenuManager.ContextMenuItemId;
 import org.chromium.chrome.browser.ntp.snippets.CategoryInt;
 import org.chromium.chrome.browser.ntp.snippets.ContentSuggestionsCardLayout;
 import org.chromium.chrome.browser.ntp.snippets.KnownCategories;
 import org.chromium.chrome.browser.suggestions.ContentSuggestionsAdditionalAction;
-import org.chromium.chrome.browser.suggestions.SuggestionsNavigationDelegate;
 
 /**
  * Contains meta information about a Category. Equivalent of the CategoryInfo class in
@@ -112,36 +110,10 @@
     public Boolean isContextMenuItemSupported(@ContextMenuItemId int menuItemId) {
         if (menuItemId == ContextMenuManager.ContextMenuItemId.REMOVE) return null;
 
-        if (mCategory == KnownCategories.DOWNLOADS) {
-            if (menuItemId == ContextMenuManager.ContextMenuItemId.OPEN_IN_INCOGNITO_TAB
-                    || menuItemId == ContextMenuManager.ContextMenuItemId.SAVE_FOR_OFFLINE) {
-                return false;
-            }
-        }
         return true;
     }
 
     /**
-     * Performs the View All action for the provided category, navigating navigating to the view
-     * showing all the content.
-     */
-    public void performViewAllAction(SuggestionsNavigationDelegate navigationDelegate) {
-        switch (mCategory) {
-            case KnownCategories.DOWNLOADS:
-                navigationDelegate.navigateToDownloadManager();
-                break;
-            case KnownCategories.BOOKMARKS_DEPRECATED:
-            case KnownCategories.FOREIGN_TABS_DEPRECATED:
-            case KnownCategories.PHYSICAL_WEB_PAGES_DEPRECATED:
-            case KnownCategories.RECENT_TABS_DEPRECATED:
-            case KnownCategories.ARTICLES:
-            default:
-                Log.wtf(TAG, "'Empty State' action called for unsupported category: %d", mCategory);
-                break;
-        }
-    }
-
-    /**
      * Whether the Category supports fetching more content. Only Articles supports this at this
      * time.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetArticle.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetArticle.java
index 8071496..7fc4b95 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetArticle.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetArticle.java
@@ -13,7 +13,6 @@
 import org.chromium.chrome.browser.suggestions.OfflinableSuggestion;
 import org.chromium.ui.modelutil.PropertyObservable;
 
-import java.io.File;
 import java.util.Collection;
 import java.util.Collections;
 
@@ -81,18 +80,6 @@
     /** The thumbnail dominant color. */
     private @ColorInt Integer mThumbnailDominantColor;
 
-    /** Whether the linked article represents an asset download. */
-    private boolean mIsAssetDownload;
-
-    /** The GUID of the asset download (only for asset download articles). */
-    private String mAssetDownloadGuid;
-
-    /** The path to the asset download (only for asset download articles). */
-    private File mAssetDownloadFile;
-
-    /** The mime type of the asset download (only for asset download articles). */
-    private String mAssetDownloadMimeType;
-
     /** The tab id of the corresponding tab (only for recent tab articles). */
     private int mRecentTabId;
 
@@ -202,71 +189,9 @@
         return mCategory == KnownCategories.CONTEXTUAL;
     }
 
-    /** @return whether a snippet is either offline page or asset download. */
-    public boolean isDownload() {
-        return mCategory == KnownCategories.DOWNLOADS;
-    }
-
-    /** @return whether a snippet is asset download. */
-    public boolean isAssetDownload() {
-        return mIsAssetDownload;
-    }
-
-    /**
-     * @return the GUID of the asset download. May only be called if {@link #mIsAssetDownload} is
-     * {@code true} (which implies that this snippet belongs to the DOWNLOADS category).
-     */
-    public String getAssetDownloadGuid() {
-        assert isDownload();
-        assert mIsAssetDownload;
-        return mAssetDownloadGuid;
-    }
-
-    /**
-     * @return the asset download path. May only be called if {@link #mIsAssetDownload} is
-     * {@code true} (which implies that this snippet belongs to the DOWNLOADS category).
-     */
-    public File getAssetDownloadFile() {
-        assert isDownload();
-        assert mIsAssetDownload;
-        return mAssetDownloadFile;
-    }
-
-    /**
-     * @return the mime type of the asset download. May only be called if {@link #mIsAssetDownload}
-     * is {@code true} (which implies that this snippet belongs to the DOWNLOADS category).
-     */
-    public String getAssetDownloadMimeType() {
-        assert isDownload();
-        assert mIsAssetDownload;
-        return mAssetDownloadMimeType;
-    }
-
-    /**
-     * Marks the article suggestion as an asset download with the given path and mime type. May only
-     * be called if this snippet belongs to DOWNLOADS category.
-     */
-    public void setAssetDownloadData(String downloadGuid, String filePath, String mimeType) {
-        assert isDownload();
-        mIsAssetDownload = true;
-        mAssetDownloadGuid = downloadGuid;
-        mAssetDownloadFile = new File(filePath);
-        mAssetDownloadMimeType = mimeType;
-    }
-
-    /**
-     * Marks the article suggestion as an offline page download with the given id. May only
-     * be called if this snippet belongs to DOWNLOADS category.
-     */
-    public void setOfflinePageDownloadData(long offlinePageId) {
-        assert isDownload();
-        mIsAssetDownload = false;
-        setOfflinePageOfflineId(offlinePageId);
-    }
-
     @Override
     public boolean requiresExactOfflinePage() {
-        return isDownload();
+        return false;
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetArticleViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetArticleViewHolder.java
index 8ce1dc3..c9bbb3a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetArticleViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetArticleViewHolder.java
@@ -195,7 +195,7 @@
 
     /** Updates the visibility of the card's offline badge by checking the bound article's info. */
     private void refreshOfflineBadgeVisibility() {
-        boolean visible = mArticle.getOfflinePageOfflineId() != null || mArticle.isAssetDownload();
+        boolean visible = mArticle.getOfflinePageOfflineId() != null;
         mSuggestionsBinder.updateOfflineBadgeVisibility(visible);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsBridge.java
index 97d3923..0f79667 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsBridge.java
@@ -207,18 +207,6 @@
     }
 
     @CalledByNative
-    private static void setAssetDownloadDataForSuggestion(
-            SnippetArticle suggestion, String downloadGuid, String filePath, String mimeType) {
-        suggestion.setAssetDownloadData(downloadGuid, filePath, mimeType);
-    }
-
-    @CalledByNative
-    private static void setOfflinePageDownloadDataForSuggestion(
-            SnippetArticle suggestion, long offlinePageId) {
-        suggestion.setOfflinePageDownloadData(offlinePageId);
-    }
-
-    @CalledByNative
     private static SuggestionsCategoryInfo createSuggestionsCategoryInfo(int category, String title,
             @ContentSuggestionsCardLayout int cardLayout,
             @ContentSuggestionsAdditionalAction int additionalAction, boolean showIfEmpty,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/ImageFetcher.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/ImageFetcher.java
index 768a5b51..a97b835 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/ImageFetcher.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/ImageFetcher.java
@@ -6,12 +6,9 @@
 
 import android.graphics.Bitmap;
 import android.os.SystemClock;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
 
 import org.chromium.base.Callback;
 import org.chromium.base.DiscardableReferencePool;
-import org.chromium.base.Promise;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.browser.favicon.LargeIconBridge;
 import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
@@ -29,7 +26,6 @@
  * To fetch an image, the caller should create a request which is done in the following way:
  *   - for favicons: {@link #makeFaviconRequest(SnippetArticle, int, Callback)}
  *   - for article thumbnails: {@link #makeArticleThumbnailRequest(SnippetArticle, Callback)}
- *   - for article downloads: {@link #makeDownloadThumbnailRequest(SnippetArticle, int)}
  *   - for large icons: {@link #makeLargeIconRequest(String, int,
  * LargeIconBridge.LargeIconCallback)}
  *
@@ -59,22 +55,6 @@
     }
 
     /**
-     * Creates a request for a thumbnail from a downloaded image.
-     *
-     * If there is an error while fetching the thumbnail, the callback will not be called.
-     *
-     * @param suggestion The suggestion for which we need a thumbnail.
-     * @param thumbnailSizePx The required size for the thumbnail.
-     * @return The request which will be used to fetch the thumbnail.
-     */
-    public DownloadThumbnailRequest makeDownloadThumbnailRequest(
-            SnippetArticle suggestion, int thumbnailSizePx) {
-        assert !mIsDestroyed;
-
-        return new DownloadThumbnailRequest(suggestion, thumbnailSizePx);
-    }
-
-    /**
      * Creates a request for an article thumbnail.
      *
      * If there is an error while fetching the thumbnail, the callback will not be called.
@@ -179,65 +159,4 @@
         }
         return mLargeIconBridge;
     }
-
-    /**
-     * Request for a download thumbnail.
-     *
-     * The request uses a {@link Promise<Bitmap>}, which will be fulfilled once the thumbnail is
-     * received.
-     *
-     * Cancellation of the request is available through {@link #cancel(), which will remove the
-     * request from the ThumbnailProvider queue}.
-     */
-    public class DownloadThumbnailRequest implements ThumbnailProvider.ThumbnailRequest {
-        private final Promise<Bitmap> mThumbnailReceivedPromise;
-        private final SnippetArticle mSuggestion;
-        private final int mSize;
-
-        /**
-         * @param suggestion The suggestion whose thumbnail will be fetched.
-         * @param size The required size for the thumbnail.
-         */
-        DownloadThumbnailRequest(SnippetArticle suggestion, int size) {
-            mThumbnailReceivedPromise = new Promise<>();
-            mSuggestion = suggestion;
-            mSize = size;
-
-            getThumbnailProvider().getThumbnail(this);
-        }
-
-        @Override
-        public @Nullable String getFilePath() {
-            return mSuggestion.getAssetDownloadFile().getAbsolutePath();
-        }
-
-        @Override
-        public @Nullable String getContentId() {
-            return mSuggestion.getAssetDownloadGuid();
-        }
-
-        @Override
-        public void onThumbnailRetrieved(@NonNull String contentId, @Nullable Bitmap thumbnail) {
-            mThumbnailReceivedPromise.fulfill(thumbnail);
-        }
-
-        @Override
-        public int getIconSize() {
-            return mSize;
-        }
-
-        public void cancel() {
-            if (mIsDestroyed) return;
-            getThumbnailProvider().cancelRetrieval(this);
-        }
-
-        public Promise<Bitmap> getPromise() {
-            return mThumbnailReceivedPromise;
-        }
-
-        @Override
-        public String getMimeType() {
-            return null;
-        }
-    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsBinder.java
index 22d3d0f..731d2c6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsBinder.java
@@ -26,7 +26,6 @@
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.Callback;
-import org.chromium.base.Promise;
 import org.chromium.base.SysUtils;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.compositor.animation.CompositorAnimationHandler;
@@ -71,9 +70,6 @@
     boolean mHasVideoBadge;
     boolean mHasOfflineBadge;
 
-    @Nullable
-    private ImageFetcher.DownloadThumbnailRequest mThumbnailRequest;
-
     private SnippetArticle mSuggestion;
 
     /**
@@ -209,9 +205,6 @@
     }
 
     private void setThumbnail() {
-        // If there's still a pending thumbnail fetch, cancel it.
-        cancelThumbnailFetch();
-
         // mThumbnailView's visibility is modified in updateFieldsVisibility().
         if (mThumbnailView.getVisibility() != View.VISIBLE) return;
 
@@ -221,11 +214,6 @@
             return;
         }
 
-        if (mSuggestion.isDownload()) {
-            setDownloadThumbnail();
-            return;
-        }
-
         // Temporarily set placeholder and then fetch the thumbnail from a provider.
         mThumbnailView.setBackground(null);
         if (mIsContextual) {
@@ -246,42 +234,6 @@
                 mSuggestion, new FetchThumbnailCallback(mSuggestion, mThumbnailSize));
     }
 
-    private void setDownloadThumbnail() {
-        assert mSuggestion.isDownload();
-        if (!mSuggestion.isAssetDownload()) {
-            setThumbnailFromFileType(DownloadFilter.Type.PAGE);
-            return;
-        }
-
-        @DownloadFilter.Type
-        int fileType = DownloadFilter.fromMimeType(mSuggestion.getAssetDownloadMimeType());
-        if (fileType == DownloadFilter.Type.IMAGE) {
-            // For image downloads, attempt to fetch a thumbnail.
-            ImageFetcher.DownloadThumbnailRequest thumbnailRequest =
-                    mImageFetcher.makeDownloadThumbnailRequest(mSuggestion, mThumbnailSize);
-
-            Promise<Bitmap> thumbnailReceivedPromise = thumbnailRequest.getPromise();
-
-            if (thumbnailReceivedPromise.isFulfilled()) {
-                // If the thumbnail was cached, then it will be retrieved synchronously, the promise
-                // will be fulfilled and we can set the thumbnail immediately.
-                verifyBitmap(thumbnailReceivedPromise.getResult());
-                setThumbnail(ThumbnailGradient.createDrawableWithGradientIfNeeded(
-                        thumbnailReceivedPromise.getResult(), mCardContainerView.getResources()));
-
-                return;
-            }
-
-            mThumbnailRequest = thumbnailRequest;
-
-            // Queue a callback to be called after the thumbnail is retrieved asynchronously.
-            thumbnailReceivedPromise.then(new FetchThumbnailCallback(mSuggestion, mThumbnailSize));
-        }
-
-        // Set a placeholder for the file type.
-        setThumbnailFromFileType(fileType);
-    }
-
     private void setThumbnail(Drawable thumbnail) {
         assert thumbnail != null;
 
@@ -318,13 +270,6 @@
         mPublisherTextView.setVisibility(View.VISIBLE);
     }
 
-    private void cancelThumbnailFetch() {
-        if (mThumbnailRequest != null) {
-            mThumbnailRequest.cancel();
-            mThumbnailRequest = null;
-        }
-    }
-
     private void fadeThumbnailIn(Drawable thumbnail) {
         assert mThumbnailView.getDrawable() != null;
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsEventReporterBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsEventReporterBridge.java
index 07f974c..c73cff5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsEventReporterBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsEventReporterBridge.java
@@ -4,10 +4,8 @@
 
 package org.chromium.chrome.browser.suggestions;
 
-import org.chromium.chrome.browser.ntp.NewTabPageUma;
 import org.chromium.chrome.browser.ntp.cards.ActionItem;
 import org.chromium.chrome.browser.ntp.snippets.CategoryInt;
-import org.chromium.chrome.browser.ntp.snippets.KnownCategories;
 import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
 
 /**
@@ -57,14 +55,6 @@
         @CategoryInt
         int category = actionItem.getCategory();
         nativeOnMoreButtonClicked(category, actionItem.getPerSectionRank());
-        switch (category) {
-            case KnownCategories.DOWNLOADS:
-                NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_DOWNLOADS_MANAGER);
-                break;
-            default:
-                // No action associated
-                break;
-        }
     }
 
     public static void onSuggestionTargetVisited(int category, long visitTimeMs) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsMetrics.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsMetrics.java
index a045366..b2b4f87 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsMetrics.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsMetrics.java
@@ -67,10 +67,6 @@
 
     // Effect/Purpose of the interactions. Most are recorded in |content_suggestions_metrics.h|
 
-    public static void recordActionViewAll() {
-        RecordUserAction.record("Suggestions.Category.ViewAll");
-    }
-
     /**
      * Records metrics for the visit to the provided content suggestion, such as the time spent on
      * the website, or if the user comes back to the starting point.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsNavigationDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsNavigationDelegate.java
index 5df40a0..619843d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsNavigationDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsNavigationDelegate.java
@@ -4,12 +4,8 @@
 
 package org.chromium.chrome.browser.suggestions;
 
-import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ChromeFeatureList;
-import org.chromium.chrome.browser.bookmarks.BookmarkUtils;
-import org.chromium.chrome.browser.download.DownloadMetrics;
-import org.chromium.chrome.browser.download.DownloadUtils;
 import org.chromium.chrome.browser.native_page.NativePageHost;
 import org.chromium.chrome.browser.native_page.NativePageNavigationDelegateImpl;
 import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
@@ -38,18 +34,6 @@
         super(activity, profile, host, tabModelSelector);
     }
 
-    /** Opens the bookmarks page in the current tab.  */
-    public void navigateToBookmarks() {
-        RecordUserAction.record("MobileNTPSwitchToBookmarks");
-        BookmarkUtils.showBookmarkManager(mActivity);
-    }
-
-    /** Opens the Download Manager UI in the current tab. */
-    public void navigateToDownloadManager() {
-        RecordUserAction.record("MobileNTPSwitchToDownloadManager");
-        DownloadUtils.showDownloadManager(mActivity, mHost.getActiveTab());
-    }
-
     @Override
     public void navigateToHelpPage() {
         NewTabPageUma.recordAction(NewTabPageUma.ACTION_CLICKED_LEARN_MORE);
@@ -80,23 +64,11 @@
             NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_SNIPPET);
         }
 
-        if (article.isAssetDownload()) {
-            assert windowOpenDisposition == WindowOpenDisposition.CURRENT_TAB
-                    || windowOpenDisposition == WindowOpenDisposition.NEW_WINDOW
-                    || windowOpenDisposition == WindowOpenDisposition.NEW_BACKGROUND_TAB;
-            DownloadUtils.openFile(article.getAssetDownloadFile().getPath(),
-                    article.getAssetDownloadMimeType(), article.getAssetDownloadGuid(), false, null,
-                    null, DownloadMetrics.DownloadOpenSource.NEW_TAP_PAGE);
-            return;
-        }
-
-        // We explicitly open an offline page only for offline page downloads or for prefetched
-        // offline pages when Data Reduction Proxy is enabled. For all other
-        // sections the URL is opened and it is up to Offline Pages whether to open its offline
-        // page (e.g. when offline).
-        if ((article.isDownload() && !article.isAssetDownload())
-                || (DataReductionProxySettings.getInstance().isDataReductionProxyEnabled()
-                           && article.isPrefetched())) {
+        // We explicitly open an offline page only for prefetched offline pages when Data Reduction
+        // Proxy is enabled. For all other sections the URL is opened and it is up to Offline Pages
+        // whether to open its offline page (e.g. when offline).
+        if (DataReductionProxySettings.getInstance().isDataReductionProxyEnabled()
+                && article.isPrefetched()) {
             assert article.getOfflinePageOfflineId() != null;
             assert windowOpenDisposition == WindowOpenDisposition.CURRENT_TAB
                     || windowOpenDisposition == WindowOpenDisposition.NEW_WINDOW
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/translate/TranslateBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/translate/TranslateBridge.java
index b3df252..1a11013 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/translate/TranslateBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/translate/TranslateBridge.java
@@ -4,9 +4,12 @@
 
 package org.chromium.chrome.browser.translate;
 
+import org.chromium.base.annotations.CalledByNative;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.content_public.browser.WebContents;
 
+import java.util.LinkedHashSet;
+
 /**
  * Bridge class that lets Android code access native code to execute translate on a tab.
  */
@@ -42,9 +45,42 @@
         nativeSetPredefinedTargetLanguage(tab.getWebContents(), targetLanguage);
     }
 
+    /**
+     * @return The best target language based on what the Translate Service knows about the user.
+     */
+    public static String getTargetLanguage() {
+        return nativeGetTargetLanguage();
+    }
+
+    /** @return whether the given string is blocked for translation. */
+    public static boolean isBlockedLanguage(String language) {
+        return nativeIsBlockedLanguage(language);
+    }
+
+    /**
+     * @return The ordered set of all languages that the user's knows, ordered by how well they know
+     *         them with the most familiar listed first.
+     */
+    public static LinkedHashSet<String> getModelLanguages() {
+        LinkedHashSet<String> set = new LinkedHashSet<String>();
+        // Calls back through addModelLanguageToSet repeatedly.
+        nativeGetModelLanguages(set);
+        return set;
+    }
+
+    /** Called by {@link #nativeGetModelLanguages} with the set to add to and the language to add.*/
+    @CalledByNative
+    private static void addModelLanguageToSet(
+            LinkedHashSet<String> languages, String languageCode) {
+        languages.add(languageCode);
+    }
+
     private static native void nativeManualTranslateWhenReady(WebContents webContents);
     private static native boolean nativeCanManuallyTranslate(WebContents webContents);
     private static native boolean nativeShouldShowManualTranslateIPH(WebContents webContents);
     private static native void nativeSetPredefinedTargetLanguage(
             WebContents webContents, String targetLanguage);
+    private static native String nativeGetTargetLanguage();
+    private static native boolean nativeIsBlockedLanguage(String language);
+    private static native void nativeGetModelLanguages(LinkedHashSet<String> set);
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java b/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java
index 4696142..b895a2cb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java
@@ -560,6 +560,11 @@
         ChromePreferenceManager.getInstance().writeBoolean(
                 ChromePreferenceManager.GRID_TAB_SWITCHER_ENABLED_KEY,
                 !DeviceClassManager.enableAccessibilityLayout()
+                        && (ChromeFeatureList.isEnabled(
+                                    ChromeFeatureList.DOWNLOAD_TAB_MANAGEMENT_MODULE)
+                                || ChromeFeatureList.isEnabled(
+                                        ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID))
+                        && TabManagementModuleProvider.getTabManagementModule() != null
                         && ChromeFeatureList.isEnabled(ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID));
     }
 
@@ -575,14 +580,18 @@
         }
         // TODO(yusufo): AccessibilityLayout check should not be here and the flow should support
         // changing that setting while Chrome is alive.
-        return sIsGridTabSwitcherEnabled
-                && TabManagementModuleProvider.getTabManagementModule() != null;
+        return sIsGridTabSwitcherEnabled;
     }
 
     private static void cacheTabGroupsAndroidEnabled() {
         ChromePreferenceManager.getInstance().writeBoolean(
                 ChromePreferenceManager.TAB_GROUPS_ANDROID_ENABLED_KEY,
                 !DeviceClassManager.enableAccessibilityLayout()
+                        && (ChromeFeatureList.isEnabled(
+                                    ChromeFeatureList.DOWNLOAD_TAB_MANAGEMENT_MODULE)
+                                || ChromeFeatureList.isEnabled(
+                                        ChromeFeatureList.TAB_GROUPS_ANDROID))
+                        && TabManagementModuleProvider.getTabManagementModule() != null
                         && ChromeFeatureList.isEnabled(ChromeFeatureList.TAB_GROUPS_ANDROID));
     }
 
@@ -599,8 +608,7 @@
                     ChromePreferenceManager.TAB_GROUPS_ANDROID_ENABLED_KEY, false);
         }
 
-        return sIsTabGroupsAndroidEnabled
-                && TabManagementModuleProvider.getTabManagementModule() != null;
+        return sIsTabGroupsAndroidEnabled;
     }
 
     private static boolean isHighEndPhone() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java
index 48276b6a..31f86ae 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java
@@ -1503,14 +1503,16 @@
 
     /**
      * Checks whether the sheet can be moved. It cannot be moved when the activity is in overview
-     * mode, when "find in page" is visible, or when the toolbar is hidden.
+     * mode, when "find in page" is visible, when the toolbar is in the animation to hide, or when
+     * the toolbar is hidden.
      */
     protected boolean canMoveSheet() {
         if (mFindInPageView == null) mFindInPageView = findViewById(R.id.find_toolbar);
         boolean isFindInPageVisible =
                 mFindInPageView != null && mFindInPageView.getVisibility() == View.VISIBLE;
 
-        return !isToolbarAndroidViewHidden() && !isFindInPageVisible;
+        return !isToolbarAndroidViewHidden() && !isFindInPageVisible
+                && mTargetState != SheetState.HIDDEN;
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/snippets/ArticleSnippetsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/snippets/ArticleSnippetsTest.java
index 833100e..bb2549c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/snippets/ArticleSnippetsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/snippets/ArticleSnippetsTest.java
@@ -239,46 +239,6 @@
     @Test
     @MediumTest
     @Feature({"ArticleSnippets", "RenderTest"})
-    public void testDownloadSuggestion() throws IOException {
-        String downloadFilePath =
-                UrlUtils.getIsolatedTestFilePath("chrome/test/data/android/capybara.jpg");
-        TestThreadUtils.runOnUiThreadBlocking(() -> {
-            SnippetArticle downloadSuggestion = new SnippetArticle(KnownCategories.DOWNLOADS, "id1",
-                    "test_image.jpg", "example.com", "http://example.com",
-                    mTimestamp, // Publish timestamp
-                    10f, // Score
-                    mTimestamp, // Fetch timestamp
-                    false, // Is video suggestion
-                    null); // Thumbnail dominant color
-            downloadSuggestion.setAssetDownloadData("asdf", downloadFilePath, "image/jpeg");
-            SuggestionsCategoryInfo downloadsCategory = new SuggestionsCategoryInfo(
-                    KnownCategories.DOWNLOADS, "Downloads", ContentSuggestionsCardLayout.FULL_CARD,
-                    ContentSuggestionsAdditionalAction.NONE,
-                    /* show_if_empty = */ true, "No suggestions");
-
-            mSuggestion.onBindViewHolder(downloadSuggestion, downloadsCategory);
-            mContentView.addView(mSuggestion.itemView);
-        });
-
-        mRenderTestRule.render(mSuggestion.itemView, "download_snippet_placeholder");
-
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        List<ThumbnailRequest> requests = mThumbnailProvider.getRequests();
-        Assert.assertEquals(1, requests.size());
-        ThumbnailRequest request = requests.get(0);
-        Assert.assertEquals(downloadFilePath, request.getFilePath());
-
-        Bitmap thumbnail = BitmapFactory.decodeFile(downloadFilePath);
-
-        TestThreadUtils.runOnUiThreadBlocking(
-                () -> { mThumbnailProvider.fulfillRequest(request, thumbnail); });
-
-        mRenderTestRule.render(mSuggestion.itemView, "download_snippet_thumbnail");
-    }
-
-    @Test
-    @MediumTest
-    @Feature({"ArticleSnippets", "RenderTest"})
     public void testVideoSuggestion() throws IOException {
         SuggestionsCategoryInfo categoryInfo = new SuggestionsCategoryInfo(FULL_CATEGORY,
                 "Section Title", ContentSuggestionsCardLayout.FULL_CARD,
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTranslationImplTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTranslationImplTest.java
new file mode 100644
index 0000000..be264d3
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTranslationImplTest.java
@@ -0,0 +1,135 @@
+// Copyright 2019 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.
+
+package org.chromium.chrome.browser.contextualsearch;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import static org.chromium.chrome.browser.contextualsearch.ContextualSearchTranslationImpl.TranslateBridgeWrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import org.chromium.base.test.util.Feature;
+
+import java.util.LinkedHashSet;
+
+/**
+ * Tests the {@link ContextualSearchTranslationImpl} class.
+ */
+@RunWith(BlockJUnit4ClassRunner.class)
+public class ContextualSearchTranslationImplTest {
+    private static final String ENGLISH = "en";
+    private static final String SPANISH = "es";
+    private static final String GERMAN = "de";
+    private static final LinkedHashSet<String> ENGLISH_AND_SPANISH;
+    static {
+        LinkedHashSet<String> langs = new LinkedHashSet<String>();
+        langs.add(ENGLISH);
+        langs.add(SPANISH);
+        ENGLISH_AND_SPANISH = langs;
+    }
+    private static final LinkedHashSet<String> ENGLISH_SET;
+    static {
+        LinkedHashSet<String> langs = new LinkedHashSet<String>();
+        langs.add(ENGLISH);
+        ENGLISH_SET = langs;
+    }
+
+    @Mock
+    private TranslateBridgeWrapper mTranslateBridgeWrapperMock;
+    @Mock
+    private ContextualSearchRequest mRequest;
+    @Mock
+    private ContextualSearchPolicy mPolicy;
+
+    private ContextualSearchTranslationImpl mImpl;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mImpl = new ContextualSearchTranslationImpl(mPolicy, mTranslateBridgeWrapperMock);
+        doReturn(false).when(mPolicy).isTranslationDisabled();
+    }
+
+    @Test
+    @Feature("TranslateUtilities")
+    public void testNeedsTranslationEmptyModelLanguages() {
+        doReturn(new LinkedHashSet<String>()).when(mTranslateBridgeWrapperMock).getModelLanguages();
+        assertThat(mImpl.needsTranslation(ENGLISH), is(true));
+    }
+
+    @Test
+    @Feature("TranslateUtilities")
+    public void testNeedsTranslationUserModelLanguages() {
+        doReturn(ENGLISH_SET).when(mTranslateBridgeWrapperMock).getModelLanguages();
+        assertThat(mImpl.needsTranslation(ENGLISH), is(false));
+    }
+
+    @Test
+    @Feature("TranslateUtilities")
+    public void testNeedsTranslationMultipleModelLanguages() {
+        doReturn(ENGLISH_AND_SPANISH).when(mTranslateBridgeWrapperMock).getModelLanguages();
+        assertThat(mImpl.needsTranslation(ENGLISH), is(false));
+    }
+
+    @Test
+    @Feature("TranslateUtilities")
+    public void testNeedsTranslationOtherModelLanguage() {
+        doReturn(ENGLISH_AND_SPANISH).when(mTranslateBridgeWrapperMock).getModelLanguages();
+        assertThat(mImpl.needsTranslation(GERMAN), is(true));
+    }
+
+    @Test
+    @Feature("TranslateUtilities")
+    public void testNeedsTranslationOtherBlocked() {
+        doReturn(ENGLISH_AND_SPANISH).when(mTranslateBridgeWrapperMock).getModelLanguages();
+        doReturn(true).when(mTranslateBridgeWrapperMock).isBlockedLanguage(GERMAN);
+        assertThat(mImpl.needsTranslation(GERMAN), is(false));
+    }
+
+    @Test
+    @Feature("TranslateUtilities")
+    public void testTargetLanguage() {
+        doReturn(GERMAN).when(mTranslateBridgeWrapperMock).getTargetLanguage();
+        assertThat(mImpl.getTranslateServiceTargetLanguage(), is(GERMAN));
+    }
+
+    @Test
+    @Feature("TranslateUtilities")
+    public void testForceTranslateIfNeededWhenNeeded() {
+        doReturn(ENGLISH_AND_SPANISH).when(mTranslateBridgeWrapperMock).getModelLanguages();
+        doNothing().when(mRequest).forceTranslation(any(), any());
+        when(mRequest.isTranslationForced()).thenReturn(true);
+
+        mImpl.forceTranslateIfNeeded(mRequest, GERMAN);
+
+        assertThat(mRequest.isTranslationForced(), is(true));
+        verify(mTranslateBridgeWrapperMock).getModelLanguages();
+        verify(mRequest).forceTranslation(GERMAN, null);
+    }
+
+    @Test
+    @Feature("TranslateUtilities")
+    public void testForceTranslateIfNeededWhenNotNeeded() {
+        doReturn(ENGLISH_AND_SPANISH).when(mTranslateBridgeWrapperMock).getModelLanguages();
+
+        mImpl.forceTranslateIfNeeded(mRequest, ENGLISH);
+
+        assertThat(mRequest.isTranslationForced(), is(false));
+        verify(mTranslateBridgeWrapperMock).getModelLanguages();
+        verify(mRequest, never()).forceTranslation(any(), any());
+    }
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsCategoryInfoTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsCategoryInfoTest.java
index 401302c..eab4c17 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsCategoryInfoTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsCategoryInfoTest.java
@@ -24,27 +24,6 @@
 @Config(manifest = Config.NONE)
 public class SuggestionsCategoryInfoTest {
     @Test
-    public void testDownloadContextMenu() {
-        SuggestionsCategoryInfo categoryInfo =
-                new CategoryInfoBuilder(KnownCategories.DOWNLOADS).build();
-        assertThat(categoryInfo.isContextMenuItemSupported(
-                           ContextMenuManager.ContextMenuItemId.OPEN_IN_NEW_WINDOW),
-                is(true));
-        assertThat(categoryInfo.isContextMenuItemSupported(
-                           ContextMenuManager.ContextMenuItemId.OPEN_IN_NEW_TAB),
-                is(true));
-        assertThat(categoryInfo.isContextMenuItemSupported(
-                           ContextMenuManager.ContextMenuItemId.OPEN_IN_INCOGNITO_TAB),
-                is(false));
-        assertThat(categoryInfo.isContextMenuItemSupported(
-                           ContextMenuManager.ContextMenuItemId.SAVE_FOR_OFFLINE),
-                is(false));
-        assertThat(categoryInfo.isContextMenuItemSupported(
-                           ContextMenuManager.ContextMenuItemId.REMOVE),
-                nullValue());
-    }
-
-    @Test
     public void testArticleContextMenu() {
         SuggestionsCategoryInfo categoryInfo =
                 new CategoryInfoBuilder(KnownCategories.ARTICLES).build();
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java
index 3527959..5e1d2b2 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java
@@ -527,23 +527,6 @@
 
     @Test
     @Feature({"Ntp"})
-    public void testViewAllAction() {
-        // When all the actions are enabled, ViewAll always has the priority and is shown.
-
-        // Spy so that VerifyAction can check methods being called.
-        SuggestionsCategoryInfo info =
-                spy(new CategoryInfoBuilder(TEST_CATEGORY_ID)
-                                .withAction(ContentSuggestionsAdditionalAction.VIEW_ALL)
-                                .showIfEmpty()
-                                .build());
-        SuggestionsSection section = createSection(info);
-
-        assertTrue(section.getActionItemForTesting().isVisible());
-        verifyAction(section, ContentSuggestionsAdditionalAction.VIEW_ALL);
-    }
-
-    @Test
-    @Feature({"Ntp"})
     public void testFetchAction() {
         // When only FetchMore is shown when enabled.
 
@@ -1092,10 +1075,6 @@
             section.getActionItemForTesting().performAction(mUiDelegate, null, null);
         }
 
-        verify(section.getCategoryInfo(),
-                (action == ContentSuggestionsAdditionalAction.VIEW_ALL ? times(1) : never()))
-                .performViewAllAction(mUiDelegate.getNavigationDelegate());
-
         // noinspection unchecked -- See https://crbug.com/740162 for rationale.
         verify(mUiDelegate.getSuggestionsSource(),
                 (action == ContentSuggestionsAdditionalAction.FETCH ? times(1) : never()))
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/SuggestionsImageFetcherTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/SuggestionsImageFetcherTest.java
index 3bb195b..2fe1d09 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/SuggestionsImageFetcherTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/SuggestionsImageFetcherTest.java
@@ -31,7 +31,6 @@
 import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
 import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource;
 import org.chromium.chrome.browser.profiles.Profile;
-import org.chromium.chrome.browser.suggestions.ImageFetcher.DownloadThumbnailRequest;
 import org.chromium.chrome.browser.widget.ThumbnailProvider;
 import org.chromium.chrome.test.support.DisableHistogramsRule;
 import org.chromium.chrome.test.util.browser.suggestions.SuggestionsDependenciesRule;
@@ -92,8 +91,7 @@
         }
 
         @KnownCategories
-        int[] categoriesThatDontFetch =
-                new int[] {KnownCategories.DOWNLOADS, KnownCategories.READING_LIST};
+        int[] categoriesThatDontFetch = new int[] {KnownCategories.READING_LIST};
         for (@KnownCategories int category : categoriesThatDontFetch) {
             SnippetArticle suggestion = createDummySuggestion(category);
             imageFetcher.makeFaviconRequest(suggestion, mockCallback);
@@ -104,21 +102,6 @@
         }
     }
 
-    @Test
-    public void testDownloadThumbnailFetch() {
-        ImageFetcher imageFetcher =
-                new ImageFetcher(mSuggestionsSource, mock(Profile.class), mReferencePool);
-
-        SnippetArticle suggestion = createDummySuggestion(KnownCategories.DOWNLOADS);
-
-        DownloadThumbnailRequest request =
-                imageFetcher.makeDownloadThumbnailRequest(suggestion, IMAGE_SIZE_PX);
-        verify(mThumbnailProvider).getThumbnail(eq(request));
-
-        request.cancel();
-        verify(mThumbnailProvider).cancelRetrieval(eq(request));
-    }
-
     @SuppressWarnings("unchecked")
     @Test
     public void testArticleThumbnailFetch() {
diff --git a/chrome/android/touchless/java/res/drawable/ic_apps_black_24dp.xml b/chrome/android/touchless/java/res/drawable/ic_apps_black_24dp.xml
index 51d4fbf..5c228efc 100644
--- a/chrome/android/touchless/java/res/drawable/ic_apps_black_24dp.xml
+++ b/chrome/android/touchless/java/res/drawable/ic_apps_black_24dp.xml
@@ -1,6 +1,3 @@
-<!-- Copyright 2019 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. -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:tools="http://schemas.android.com/tools"
         tools:targetApi="21"
@@ -9,6 +6,6 @@
         android:viewportWidth="24.0"
         android:viewportHeight="24.0">
     <path
-        android:fillColor="@color/default_icon_color_blue"
+        android:fillColor="#4267B2"
         android:pathData="M4,8h4L8,4L4,4v4zM10,20h4v-4h-4v4zM4,20h4v-4L4,16v4zM4,14h4v-4L4,10v4zM10,14h4v-4h-4v4zM16,4v4h4L20,4h-4zM10,8h4L14,4h-4v4zM16,14h4v-4h-4v4zM16,20h4v-4h-4v4z"/>
 </vector>
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsAdapter.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsAdapter.java
index f4029cf..f0ebc57 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsAdapter.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsAdapter.java
@@ -98,9 +98,8 @@
     private TextView mTitleView;
 
     /**
-     * @param model The main property model coming from {@link SiteSuggestionsCoordinator}. Contains
-     *         properties for a list of suggestions, number of items, and current focused index.
-     * @param iconGenerator An icon generator for creating icons.
+     * @param model the main property model coming from {@link SiteSuggestionsCoordinator}.
+     * @param iconGenerator an icon generator for creating icons.
      * @param navigationDelegate delegate for navigation controls
      * @param contextMenuManager handles context menu creation
      * @param layoutManager the layout manager controlling this recyclerview and adapter
@@ -130,7 +129,8 @@
 
     @Override
     public int getItemViewType(int position) {
-        if (isAllAppsPosition(position)) return ViewType.ALL_APPS_TYPE;
+        int itemCount = mModel.get(ITEM_COUNT_KEY);
+        if (itemCount == 1 || position % itemCount == 0) return ViewType.ALL_APPS_TYPE;
         return ViewType.SUGGESTION_TYPE;
     }
 
@@ -155,9 +155,9 @@
                                     WindowOpenDisposition.CURRENT_TAB, UrlConstants.EXPLORE_URL));
         } else if (holder.getItemViewType() == ViewType.SUGGESTION_TYPE) {
             // If site suggestion, attach context menu handler; clicks navigate to site url.
+            int itemCount = mModel.get(ITEM_COUNT_KEY);
             // Subtract 1 from position % MAX_TILES to account for "all apps" taking up one space.
-            PropertyModel item =
-                    mModel.get(SUGGESTIONS_KEY).get(getModelPositionFromAdapterPosition(position));
+            PropertyModel item = mModel.get(SUGGESTIONS_KEY).get((position % itemCount) - 1);
             // Only update the icon for icon updates.
             if (payload == SiteSuggestionModel.ICON_KEY) {
                 tile.updateIcon(item.get(SiteSuggestionModel.ICON_KEY),
@@ -185,12 +185,13 @@
         if (propertyKey == CURRENT_INDEX_KEY) {
             // When the current index changes, we want to scroll to position and update the title.
             int position = mModel.get(CURRENT_INDEX_KEY);
+            int itemCount = mModel.get(ITEM_COUNT_KEY);
             mLayoutManager.scrollToPosition(position);
-            if (isAllAppsPosition(position)) {
+            if (itemCount == 1 || position % itemCount == 0) {
                 mTitleView.setText(R.string.ntp_all_apps);
             } else {
                 mTitleView.setText(mModel.get(SUGGESTIONS_KEY)
-                                           .get(getModelPositionFromAdapterPosition(position))
+                                           .get(position % itemCount - 1)
                                            .get(SiteSuggestionModel.TITLE_KEY));
             }
         }
@@ -213,7 +214,7 @@
     public void notifyItemRangeRemoved(int index, int count) {
         if (mModel.get(SUGGESTIONS_KEY).size() == 0) {
             // When we removed the last item in the model, we would go from infinite scroll
-            // back to non-scrolling. Notify RecyclerView to remove everything.
+            // back to non-scrolling. Notify Recyclerview to remove everything.
             super.notifyItemRangeRemoved(1, Integer.MAX_VALUE - 1);
         } else {
             // Otherwise we are already infinite-scrolling, so just tell recyclerview that
@@ -224,21 +225,33 @@
 
     @Override
     public void notifyItemRangeChanged(int index, int count, @Nullable PropertyKey payload) {
-        // When something has changed, assume everything has changed.
-        super.notifyItemRangeChanged(0, Integer.MAX_VALUE, payload);
-    }
-
-    public void destroy() {
-        mModel.removeObserver(this);
-        mModel.get(SUGGESTIONS_KEY).removeObserver(this);
-    }
-
-    private int getModelPositionFromAdapterPosition(int adapterPosition) {
-        return adapterPosition % mModel.get(ITEM_COUNT_KEY) - 1;
-    }
-
-    private boolean isAllAppsPosition(int adapterPosition) {
-        return mModel.get(ITEM_COUNT_KEY) == 1
-                || getModelPositionFromAdapterPosition(adapterPosition) < 0;
+        if (count > 1) {
+            // If more than 1 item was changed, then assume everything was changed. This should
+            // only happen if we are infinite-scrolling.
+            super.notifyItemRangeChanged(0, Integer.MAX_VALUE, payload);
+        } else if (mModel.get(SUGGESTIONS_KEY).size() == 0) {
+            // If itemCount is 1, then notify super.
+            // This should only happen if "All apps" icon has changed in some way and we aren't
+            // infinite-scrolling.
+            super.notifyItemRangeChanged(index, count, payload);
+        } else {
+            // Otherwise, count = 1 and we have an infinite list. We will only notify that items
+            // near the currently visible area has changed.
+            // beginIndex at the layoutManager's firstVisibleItemPosition, with buffer.
+            int beginIndex =
+                    mLayoutManager.findFirstVisibleItemPosition() - mModel.get(ITEM_COUNT_KEY);
+            // endIndex at the lastVisibleItemPosition, with buffer.
+            int endIndex =
+                    mLayoutManager.findLastVisibleItemPosition() + mModel.get(ITEM_COUNT_KEY);
+            // Find elements between begin and end such that (i % itemCount) - 1 == index.
+            // Subtract 1 because itemRangeChanged is called from the listObserver which does not
+            // have "All apps". However, i is calculated from layoutManager, which includes "All
+            // apps"
+            for (int i = beginIndex; i < endIndex; i++) {
+                if (i % mModel.get(ITEM_COUNT_KEY) - 1 == index) {
+                    super.notifyItemRangeChanged(i, 1, payload);
+                }
+            }
+        }
     }
 }
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsCoordinator.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsCoordinator.java
index ece6b74b..5b6044e 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsCoordinator.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsCoordinator.java
@@ -35,7 +35,6 @@
             new PropertyModel.WritableIntPropertyKey();
 
     private SiteSuggestionsMediator mMediator;
-    private SiteSuggestionsAdapter mAdapterDelegate;
 
     SiteSuggestionsCoordinator(View parentView, Profile profile,
             SuggestionsNavigationDelegate navigationDelegate, ContextMenuManager contextMenuManager,
@@ -57,13 +56,13 @@
         LinearLayoutManager layoutManager = new SiteSuggestionsLayoutManager(context);
         RecyclerView recyclerView =
                 suggestionsView.findViewById(R.id.most_likely_launcher_recycler);
-        mAdapterDelegate = new SiteSuggestionsAdapter(model, iconGenerator, navigationDelegate,
-                contextMenuManager, layoutManager,
+        SiteSuggestionsAdapter adapterDelegate = new SiteSuggestionsAdapter(model, iconGenerator,
+                navigationDelegate, contextMenuManager, layoutManager,
                 suggestionsView.findViewById(R.id.most_likely_web_title_text));
 
         RecyclerViewAdapter<SiteSuggestionsViewHolderFactory.SiteSuggestionsViewHolder, PropertyKey>
                 adapter = new RecyclerViewAdapter<>(
-                        mAdapterDelegate, new SiteSuggestionsViewHolderFactory());
+                        adapterDelegate, new SiteSuggestionsViewHolderFactory());
 
         recyclerView.setLayoutManager(layoutManager);
         recyclerView.setAdapter(adapter);
@@ -73,6 +72,5 @@
 
     public void destroy() {
         mMediator.destroy();
-        mAdapterDelegate.destroy();
     }
 }
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsMediator.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsMediator.java
index 0b4d6a5b..9371076 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsMediator.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsMediator.java
@@ -71,7 +71,6 @@
                     protected Bitmap doInBackground() {
                         return BitmapFactory.decodeFile(suggestion.whitelistIconPath);
                     }
-
                     @Override
                     protected void onPostExecute(Bitmap icon) {
                         if (icon == null) makeIconRequest(siteSuggestion);
@@ -83,7 +82,7 @@
         // Total item count is 1 more than number of site suggestions to account for "all apps".
         mModel.set(SiteSuggestionsCoordinator.ITEM_COUNT_KEY, getItemCount() + 1);
 
-        // If we fetched site suggestions for the first time, set initial scrolled position.
+        // If we fetched site suggestions the first time, set initial scrolled position.
         // We don't want to set scrolled position if we've already set position before.
         if (siteSuggestions.size() > 0
                 && mModel.get(SiteSuggestionsCoordinator.CURRENT_INDEX_KEY) == 0) {
diff --git a/chrome/android/touchless/javatests/src/org/chromium/chrome/browser/touchless/NoTouchActivityTest.java b/chrome/android/touchless/javatests/src/org/chromium/chrome/browser/touchless/NoTouchActivityTest.java
index c2762491..7fab62a 100644
--- a/chrome/android/touchless/javatests/src/org/chromium/chrome/browser/touchless/NoTouchActivityTest.java
+++ b/chrome/android/touchless/javatests/src/org/chromium/chrome/browser/touchless/NoTouchActivityTest.java
@@ -80,7 +80,7 @@
         mActivityTestRule.startMainActivityFromIntent(i, null);
         mActivity = mActivityTestRule.getActivity();
         Assert.assertFalse(mActivity.getActivityTab().isNativePage());
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Assert.assertEquals("chrome://dino/",
                     mActivity.getActivityTab().getWebContents().getLastCommittedUrl());
         });
@@ -101,7 +101,7 @@
         mActivityTestRule.startMainActivityFromIntent(i, null);
         mActivity = mActivityTestRule.getActivity();
         Assert.assertFalse(mActivity.getActivityTab().isNativePage());
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Assert.assertEquals("chrome://dino/",
                     mActivity.getActivityTab().getWebContents().getLastCommittedUrl());
         });
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index 772fb81f..e7cd1c8 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -2723,6 +2723,15 @@
   <message name="IDS_NETWORK_UI_DEVICES" desc="Label for list of network devices, e.g. wifi or cellular">
     Devices:
   </message>
+  <message name="IDS_NETWORK_UI_NO_CELLULAR_ACTIVATION_LABEL" desc="Label for section dealing with activating a cellular network SIM card.">
+    Cellular Activation
+  </message>
+  <message name="IDS_NETWORK_UI_OPEN_CELLULAR_ACTIVATION_BUTTON_TEXT" desc="Text for button which, when pressed, opens the cellular activation UI.">
+    Open Cellular Activation UI
+  </message>
+  <message name="IDS_NETWORK_UI_NO_CELLULAR_ERROR_TEXT" desc="Text displayed when the cellular activation UI cannot be opened because no cellular network exists.">
+    No cellular network exists
+  </message>
 
   <message name="IDS_DEVICE_LOG_LINK_TEXT" desc="Message preceeding link to chrome://device-log">
     For network logs, see: <ph name="DEVICE_LOG_LINK">&lt;a href="chrome://device-log"&gt;chrome://device-log&lt;/a&gt;</ph>
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index d260a8b..69793ca 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -1179,6 +1179,11 @@
          chrome::android::kContextualSearchSimplifiedServer,
          kSimplifiedServerVariations,
          "ContextualSearchSimplifiedServer")},
+    {"contextual-search-translation-model",
+     flag_descriptions::kContextualSearchTranslationModelName,
+     flag_descriptions::kContextualSearchTranslationModelDescription,
+     kOsAndroid,
+     FEATURE_VALUE_TYPE(chrome::android::kContextualSearchTranslationModel)},
     {"contextual-search-unity-integration",
      flag_descriptions::kContextualSearchUnityIntegrationName,
      flag_descriptions::kContextualSearchUnityIntegrationDescription,
@@ -1597,18 +1602,6 @@
      ENABLE_DISABLE_VALUE_TYPE(switches::kEnableZeroCopy,
                                switches::kDisableZeroCopy)},
 #if defined(OS_MACOSX)
-    {"bookmark-apps", flag_descriptions::kNewBookmarkAppsName,
-     flag_descriptions::kNewBookmarkAppsDescription, kOsMac,
-     FEATURE_VALUE_TYPE(features::kBookmarkApps)},
-    {"disable-hosted-apps-in-windows",
-     flag_descriptions::kHostedAppsInWindowsName,
-     flag_descriptions::kHostedAppsInWindowsDescription, kOsMac,
-     ENABLE_DISABLE_VALUE_TYPE(switches::kEnableHostedAppsInWindows,
-                               switches::kDisableHostedAppsInWindows)},
-    {"create-app-windows-in-app",
-     flag_descriptions::kCreateAppWindowsInAppShimProcessName,
-     flag_descriptions::kCreateAppWindowsInAppShimProcessDescription, kOsMac,
-     FEATURE_VALUE_TYPE(features::kHostWindowsInAppShimProcess)},
     {"disable-hosted-app-shim-creation",
      flag_descriptions::kHostedAppShimCreationName,
      flag_descriptions::kHostedAppShimCreationDescription, kOsMac,
diff --git a/chrome/browser/android/chrome_feature_list.cc b/chrome/browser/android/chrome_feature_list.cc
index 6635f67..67701a2 100644
--- a/chrome/browser/android/chrome_feature_list.cc
+++ b/chrome/browser/android/chrome_feature_list.cc
@@ -122,14 +122,17 @@
     &kContextualSearchSecondTap,
     &kContextualSearchSimplifiedServer,
     &kContextualSearchTapDisableOverride,
+    &kContextualSearchTranslationModel,
     &kContextualSearchUnityIntegration,
     &kCustomContextMenu,
     &kDelegateOverscrollSwipes,
     &kDontPrefetchLibraries,
+    &kDownloadLocationShowImageInGallery,
     &kDownloadProgressInfoBar,
     &kDownloadHomeV2,
     &kDownloadHomeShowStorageInfo,
     &kDownloadRename,
+    &kDownloadTabManagementModule,
     &kDrawVerticallyEdgeToEdge,
     &kEphemeralTab,
     &kExploreSites,
@@ -340,6 +343,9 @@
 const base::Feature kContextualSearchTapDisableOverride{
     "ContextualSearchTapDisableOverride", base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kContextualSearchTranslationModel{
+    "ContextualSearchTranslationModel", base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kContextualSearchUnityIntegration{
     "ContextualSearchUnityIntegration", base::FEATURE_DISABLED_BY_DEFAULT};
 
@@ -358,6 +364,9 @@
 const base::Feature kDownloadAutoResumptionThrottling{
     "DownloadAutoResumptionThrottling", base::FEATURE_ENABLED_BY_DEFAULT};
 
+const base::Feature kDownloadLocationShowImageInGallery{
+    "DownloadLocationShowImageInGallery", base::FEATURE_ENABLED_BY_DEFAULT};
+
 const base::Feature kDownloadProgressInfoBar{"DownloadProgressInfoBar",
                                              base::FEATURE_ENABLED_BY_DEFAULT};
 
@@ -370,6 +379,9 @@
 const base::Feature kDownloadRename{"DownloadRename",
                                     base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kDownloadTabManagementModule{
+    "DownloadTabManagementModule", base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kEphemeralTab{"EphemeralTab",
                                   base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/chrome/browser/android/chrome_feature_list.h b/chrome/browser/android/chrome_feature_list.h
index 87ebb545..ec2067f1 100644
--- a/chrome/browser/android/chrome_feature_list.h
+++ b/chrome/browser/android/chrome_feature_list.h
@@ -51,15 +51,18 @@
 extern const base::Feature kContextualSearchSecondTap;
 extern const base::Feature kContextualSearchSimplifiedServer;
 extern const base::Feature kContextualSearchTapDisableOverride;
+extern const base::Feature kContextualSearchTranslationModel;
 extern const base::Feature kContextualSearchUnityIntegration;
 extern const base::Feature kCustomContextMenu;
 extern const base::Feature kDelegateOverscrollSwipes;
 extern const base::Feature kDontPrefetchLibraries;
 extern const base::Feature kDownloadAutoResumptionThrottling;
+extern const base::Feature kDownloadLocationShowImageInGallery;
 extern const base::Feature kDownloadProgressInfoBar;
 extern const base::Feature kDownloadHomeV2;
 extern const base::Feature kDownloadHomeShowStorageInfo;
 extern const base::Feature kDownloadRename;
+extern const base::Feature kDownloadTabManagementModule;
 extern const base::Feature kDrawVerticallyEdgeToEdge;
 extern const base::Feature kEphemeralTab;
 extern const base::Feature kExploreSites;
diff --git a/chrome/browser/android/contextualsearch/contextual_search_delegate.cc b/chrome/browser/android/contextualsearch/contextual_search_delegate.cc
index b9c9f22..46be73d 100644
--- a/chrome/browser/android/contextualsearch/contextual_search_delegate.cc
+++ b/chrome/browser/android/contextualsearch/contextual_search_delegate.cc
@@ -44,6 +44,7 @@
 #include "url/gurl.h"
 
 using content::RenderFrameHost;
+using language::LanguageModel;
 using unified_consent::UrlKeyedDataCollectionConsentHelper;
 
 namespace {
@@ -422,10 +423,11 @@
 // Gets the target language from the translate service using the user's profile.
 std::string ContextualSearchDelegate::GetTargetLanguage() {
   Profile* profile = ProfileManager::GetActiveUserProfile();
-  PrefService* pref_service = profile->GetPrefs();
-  language::LanguageModel* language_model =
+  LanguageModel* language_model =
       LanguageModelManagerFactory::GetForBrowserContext(profile)
           ->GetPrimaryModel();
+  DCHECK(language_model);
+  PrefService* pref_service = profile->GetPrefs();
   std::string result =
       TranslateService::GetTargetLanguage(pref_service, language_model);
   DCHECK(!result.empty());
diff --git a/chrome/browser/android/contextualsearch/contextual_search_delegate.h b/chrome/browser/android/contextualsearch/contextual_search_delegate.h
index 13d64af..386da81 100644
--- a/chrome/browser/android/contextualsearch/contextual_search_delegate.h
+++ b/chrome/browser/android/contextualsearch/contextual_search_delegate.h
@@ -68,6 +68,8 @@
       content::WebContents* web_contents);
 
   // Gets the target language for translation purposes for this user.
+  // TODO(donnd): remove these language accessors once the transition to the
+  // Chrome Language Model is complete.
   std::string GetTargetLanguage();
 
   // Returns the accept languages preference string.
diff --git a/chrome/browser/android/ntp/ntp_snippets_bridge.cc b/chrome/browser/android/ntp/ntp_snippets_bridge.cc
index b6700d47..158a903 100644
--- a/chrome/browser/android/ntp/ntp_snippets_bridge.cc
+++ b/chrome/browser/android/ntp/ntp_snippets_bridge.cc
@@ -77,24 +77,6 @@
             suggestion.fetch_date().ToJavaTime(),
             suggestion.is_video_suggestion(),
             suggestion.optional_image_dominant_color().value_or(0));
-    if (suggestion.id().category().IsKnownCategory(
-            KnownCategories::DOWNLOADS) &&
-        suggestion.download_suggestion_extra() != nullptr) {
-      if (suggestion.download_suggestion_extra()->is_download_asset) {
-        Java_SnippetsBridge_setAssetDownloadDataForSuggestion(
-            env, java_suggestion,
-            ConvertUTF8ToJavaString(
-                env, suggestion.download_suggestion_extra()->download_guid),
-            ConvertUTF8ToJavaString(env, suggestion.download_suggestion_extra()
-                                             ->target_file_path.value()),
-            ConvertUTF8ToJavaString(
-                env, suggestion.download_suggestion_extra()->mime_type));
-      } else {
-        Java_SnippetsBridge_setOfflinePageDownloadDataForSuggestion(
-            env, java_suggestion,
-            suggestion.download_suggestion_extra()->offline_page_id);
-      }
-    }
   }
 
   return result;
diff --git a/chrome/browser/android/vr/BUILD.gn b/chrome/browser/android/vr/BUILD.gn
index b033f0e1..d748cc4 100644
--- a/chrome/browser/android/vr/BUILD.gn
+++ b/chrome/browser/android/vr/BUILD.gn
@@ -87,11 +87,6 @@
       "arcore_device/arcore_shim.cc",
       "arcore_device/arcore_shim.h",
     ]
-
-    # TODO(https://crbug.com/936004): Remove this once arcore_c_api.h is
-    # -Wextra-semi clean.
-    configs -= [ "//build/config/compiler:chromium_code" ]
-    configs += [ "//build/config/compiler:no_chromium_code" ]
   }
 
   deps = [
diff --git a/chrome/browser/android/vr/arcore_device/arcore_impl.cc b/chrome/browser/android/vr/arcore_device/arcore_impl.cc
index bb6c9f5..cb7585c 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_impl.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_impl.cc
@@ -108,8 +108,11 @@
   size_t num_elements = uvs.size();
   DCHECK(num_elements % 2 == 0);
   std::vector<float> uvs_out(num_elements);
-  ArFrame_transformDisplayUvCoords(arcore_session_.get(), arcore_frame_.get(),
-                                   num_elements, &uvs[0], &uvs_out[0]);
+
+  ArFrame_transformCoordinates2d(
+      arcore_session_.get(), arcore_frame_.get(),
+      AR_COORDINATES_2D_VIEW_NORMALIZED, num_elements / 2, &uvs[0],
+      AR_COORDINATES_2D_TEXTURE_NORMALIZED, &uvs_out[0]);
   return uvs_out;
 }
 
diff --git a/chrome/browser/android/vr/arcore_device/arcore_shim.cc b/chrome/browser/android/vr/arcore_device/arcore_shim.cc
index 5ee88a84..eaf2fc5 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_shim.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_shim.cc
@@ -12,40 +12,40 @@
 namespace {
 
 // Run CALL macro for every function defined in the API.
-#define FOR_EACH_API_FN                  \
-  CALL(ArCamera_getDisplayOrientedPose)  \
-  CALL(ArCamera_getProjectionMatrix)     \
-  CALL(ArCamera_getTrackingState)        \
-  CALL(ArCamera_getViewMatrix)           \
-  CALL(ArConfig_create)                  \
-  CALL(ArConfig_destroy)                 \
-  CALL(ArFrame_acquireCamera)            \
-  CALL(ArFrame_create)                   \
-  CALL(ArFrame_destroy)                  \
-  CALL(ArFrame_hitTestRay)               \
-  CALL(ArFrame_transformDisplayUvCoords) \
-  CALL(ArHitResult_create)               \
-  CALL(ArHitResult_destroy)              \
-  CALL(ArHitResult_getHitPose)           \
-  CALL(ArHitResultList_create)           \
-  CALL(ArHitResultList_destroy)          \
-  CALL(ArHitResultList_getItem)          \
-  CALL(ArHitResultList_getSize)          \
-  CALL(ArPose_create)                    \
-  CALL(ArPose_destroy)                   \
-  CALL(ArPose_getMatrix)                 \
-  CALL(ArPose_getPoseRaw)                \
-  CALL(ArSession_configure)              \
-  CALL(ArSession_create)                 \
-  CALL(ArSession_destroy)                \
-  CALL(ArSession_pause)                  \
-  CALL(ArSession_resume)                 \
-  CALL(ArSession_setCameraTextureName)   \
-  CALL(ArSession_setDisplayGeometry)     \
-  CALL(ArHitResult_acquireTrackable)     \
-  CALL(ArTrackable_getType)              \
-  CALL(ArTrackable_release)              \
-  CALL(ArPlane_isPoseInPolygon)          \
+#define FOR_EACH_API_FN                 \
+  CALL(ArCamera_getDisplayOrientedPose) \
+  CALL(ArCamera_getProjectionMatrix)    \
+  CALL(ArCamera_getTrackingState)       \
+  CALL(ArCamera_getViewMatrix)          \
+  CALL(ArConfig_create)                 \
+  CALL(ArConfig_destroy)                \
+  CALL(ArFrame_acquireCamera)           \
+  CALL(ArFrame_create)                  \
+  CALL(ArFrame_destroy)                 \
+  CALL(ArFrame_hitTestRay)              \
+  CALL(ArFrame_transformCoordinates2d)  \
+  CALL(ArHitResult_create)              \
+  CALL(ArHitResult_destroy)             \
+  CALL(ArHitResult_getHitPose)          \
+  CALL(ArHitResultList_create)          \
+  CALL(ArHitResultList_destroy)         \
+  CALL(ArHitResultList_getItem)         \
+  CALL(ArHitResultList_getSize)         \
+  CALL(ArPose_create)                   \
+  CALL(ArPose_destroy)                  \
+  CALL(ArPose_getMatrix)                \
+  CALL(ArPose_getPoseRaw)               \
+  CALL(ArSession_configure)             \
+  CALL(ArSession_create)                \
+  CALL(ArSession_destroy)               \
+  CALL(ArSession_pause)                 \
+  CALL(ArSession_resume)                \
+  CALL(ArSession_setCameraTextureName)  \
+  CALL(ArSession_setDisplayGeometry)    \
+  CALL(ArHitResult_acquireTrackable)    \
+  CALL(ArTrackable_getType)             \
+  CALL(ArTrackable_release)             \
+  CALL(ArPlane_isPoseInPolygon)         \
   CALL(ArSession_update)
 
 #define CALL(fn) decltype(&fn) impl_##fn = nullptr;
@@ -160,13 +160,16 @@
                                       ray_direction_3, out_hit_results);
 }
 
-void ArFrame_transformDisplayUvCoords(const ArSession* session,
-                                      const ArFrame* frame,
-                                      int32_t num_elements,
-                                      const float* uvs_in,
-                                      float* uvs_out) {
-  arcore_api->impl_ArFrame_transformDisplayUvCoords(
-      session, frame, num_elements, uvs_in, uvs_out);
+void ArFrame_transformCoordinates2d(const ArSession* session,
+                                    const ArFrame* frame,
+                                    ArCoordinates2dType input_coordinates,
+                                    int32_t number_of_vertices,
+                                    const float* vertices_2d,
+                                    ArCoordinates2dType output_coordinates,
+                                    float* out_vertices_2d) {
+  arcore_api->impl_ArFrame_transformCoordinates2d(
+      session, frame, input_coordinates, number_of_vertices, vertices_2d,
+      output_coordinates, out_vertices_2d);
 }
 
 void ArHitResult_create(const ArSession* session,
diff --git a/chrome/browser/apps/app_shim/app_shim_interactive_uitest_mac.mm b/chrome/browser/apps/app_shim/app_shim_interactive_uitest_mac.mm
index 83d13d1..1b8d8a3 100644
--- a/chrome/browser/apps/app_shim/app_shim_interactive_uitest_mac.mm
+++ b/chrome/browser/apps/app_shim/app_shim_interactive_uitest_mac.mm
@@ -21,7 +21,6 @@
 #include "base/run_loop.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/task/post_task.h"
-#include "base/test/scoped_feature_list.h"
 #include "base/test/test_timeouts.h"
 #include "base/threading/thread_restrictions.h"
 #include "build/build_config.h"
@@ -58,12 +57,6 @@
   AppShimInteractiveTest()
       : auto_reset_(&g_app_shims_allow_update_and_launch_in_tests, true) {}
 
-  // testing::Test:
-  void SetUp() override {
-    scoped_feature_list_.InitAndEnableFeature(features::kBookmarkApps);
-    PlatformAppBrowserTest::SetUp();
-  }
-
   // Install a test app of |type| and reliably wait for its app shim to be
   // created on disk. Sets |shim_path_|.
   const extensions::Extension* InstallAppWithShim(AppType type,
@@ -75,7 +68,6 @@
  private:
   // Temporarily enable app shims.
   base::AutoReset<bool> auto_reset_;
-  base::test::ScopedFeatureList scoped_feature_list_;
 
   DISALLOW_COPY_AND_ASSIGN(AppShimInteractiveTest);
 };
diff --git a/chrome/browser/apps/guest_view/web_view_browsertest.cc b/chrome/browser/apps/guest_view/web_view_browsertest.cc
index d7ef52a..57996bb 100644
--- a/chrome/browser/apps/guest_view/web_view_browsertest.cc
+++ b/chrome/browser/apps/guest_view/web_view_browsertest.cc
@@ -2234,7 +2234,7 @@
 // then launches the app window again. The process is repeated 3 times.
 // TODO(crbug.com/949923): The test is flaky (crash) on ChromeOS debug and ASan/LSan
 #if defined(OS_CHROMEOS) && (!defined(NDEBUG) || defined(ADDRESS_SANITIZER))
-#define MAYBE_CloseOnLoadcommit DEFINE_CloseOnLoadcommit
+#define MAYBE_CloseOnLoadcommit DISABLED_CloseOnLoadcommit
 #else
 #define MAYBE_CloseOnLoadcommit CloseOnLoadcommit
 #endif
diff --git a/chrome/browser/apps/platform_apps/platform_app_launch.cc b/chrome/browser/apps/platform_apps/platform_app_launch.cc
index 2fc86ba..34ac465 100644
--- a/chrome/browser/apps/platform_apps/platform_app_launch.cc
+++ b/chrome/browser/apps/platform_apps/platform_app_launch.cc
@@ -4,7 +4,6 @@
 
 #include "chrome/browser/apps/platform_apps/platform_app_launch.h"
 
-#include "chrome/browser/extensions/extension_util.h"
 #include "chrome/browser/extensions/launch_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/extensions/app_launch_params.h"
@@ -44,12 +43,6 @@
   extensions::LaunchContainer launch_container = extensions::GetLaunchContainer(
       extensions::ExtensionPrefs::Get(profile), app);
 
-  if (!extensions::util::IsNewBookmarkAppsEnabled() &&
-      !extensions::HasPreferredLaunchContainer(
-          extensions::ExtensionPrefs::Get(profile), app)) {
-    launch_container = extensions::LAUNCH_CONTAINER_WINDOW;
-  }
-
   *out_app = app;
   *out_launch_container = launch_container;
   return true;
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index bd231c5..62f138f 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -2383,6 +2383,7 @@
     "input_method/input_method_engine_unittest.cc",
     "input_method/input_method_manager_impl_unittest.cc",
     "input_method/input_method_persistence_unittest.cc",
+    "kiosk_next_home/app_controller_service_unittest.cc",
     "locale_change_guard_unittest.cc",
     "lock_screen_apps/app_manager_impl_unittest.cc",
     "lock_screen_apps/lock_screen_profile_creator_impl_unittest.cc",
diff --git a/chrome/browser/chromeos/accessibility/select_to_speak_browsertest.cc b/chrome/browser/chromeos/accessibility/select_to_speak_browsertest.cc
index dcb02bc..223f31d 100644
--- a/chrome/browser/chromeos/accessibility/select_to_speak_browsertest.cc
+++ b/chrome/browser/chromeos/accessibility/select_to_speak_browsertest.cc
@@ -281,7 +281,8 @@
                                  "Second paragraph*"));
 }
 
-IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, FocusRingMovesWithMouse) {
+// Flaky test. https://crbug.com/950049
+IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, DISABLED_FocusRingMovesWithMouse) {
   // Create a callback for the focus ring observer.
   base::RepeatingCallback<void()> callback =
       base::BindRepeating(&SelectToSpeakTest::OnFocusRingChanged, GetWeakPtr());
diff --git a/chrome/browser/chromeos/arc/process/arc_process.cc b/chrome/browser/chromeos/arc/process/arc_process.cc
index 3b917c5..ab151f45 100644
--- a/chrome/browser/chromeos/arc/process/arc_process.cc
+++ b/chrome/browser/chromeos/arc/process/arc_process.cc
@@ -40,7 +40,7 @@
                                   ProcessState::FOREGROUND_SERVICE,
                                   ProcessState::BOUND_FOREGROUND_SERVICE,
                                   ProcessState::IMPORTANT_FOREGROUND,
-                                  ProcessState::IMPORTANT_FOREGROUND});
+                                  ProcessState::IMPORTANT_BACKGROUND});
   return *kProtectedBackgroundStates;
 }
 
@@ -89,6 +89,8 @@
 ArcProcess::ArcProcess(ArcProcess&& other) = default;
 ArcProcess& ArcProcess::operator=(ArcProcess&& other) = default;
 
+// TODO(wvk): Use a simple switch/case instead of std::unordered_set lookup,
+// it will likely be faster.
 bool ArcProcess::IsImportant() const {
   return ImportantStates().count(process_state()) == 1 || IsArcProtected();
 }
diff --git a/chrome/browser/chromeos/display/output_protection_controller_ash.cc b/chrome/browser/chromeos/display/output_protection_controller_ash.cc
index 13648a2..f55153ab 100644
--- a/chrome/browser/chromeos/display/output_protection_controller_ash.cc
+++ b/chrome/browser/chromeos/display/output_protection_controller_ash.cc
@@ -7,29 +7,29 @@
 
 #include "ash/shell.h"  // mash-ok
 
+namespace {
+
+display::DisplayConfigurator* configurator() {
+  return ash::Shell::Get()->display_configurator();
+}
+
+}  // namespace
+
 namespace chromeos {
 
 OutputProtectionControllerAsh::OutputProtectionControllerAsh()
-    : client_id_(ash::Shell::Get()
-                     ->display_configurator()
-                     ->RegisterContentProtectionClient()) {}
+    : client_id_(configurator()->RegisterContentProtectionClient()) {}
 
 OutputProtectionControllerAsh::~OutputProtectionControllerAsh() {
   DCHECK(thread_checker_.CalledOnValidThread());
-  if (client_id_ != display::DisplayConfigurator::INVALID_CLIENT_ID) {
-    display::DisplayConfigurator* configurator =
-        ash::Shell::Get()->display_configurator();
-    configurator->UnregisterContentProtectionClient(client_id_);
-  }
+  configurator()->UnregisterContentProtectionClient(client_id_);
 }
 
 void OutputProtectionControllerAsh::QueryStatus(
     int64_t display_id,
     const OutputProtectionDelegate::QueryStatusCallback& callback) {
   DCHECK(thread_checker_.CalledOnValidThread());
-  display::DisplayConfigurator* configurator =
-      ash::Shell::Get()->display_configurator();
-  configurator->QueryContentProtectionStatus(client_id_, display_id, callback);
+  configurator()->QueryContentProtection(client_id_, display_id, callback);
 }
 
 void OutputProtectionControllerAsh::SetProtection(
@@ -37,10 +37,8 @@
     uint32_t desired_method_mask,
     const OutputProtectionDelegate::SetProtectionCallback& callback) {
   DCHECK(thread_checker_.CalledOnValidThread());
-  display::DisplayConfigurator* configurator =
-      ash::Shell::Get()->display_configurator();
-  configurator->SetContentProtection(client_id_, display_id,
-                                     desired_method_mask, callback);
+  configurator()->ApplyContentProtection(client_id_, display_id,
+                                         desired_method_mask, callback);
 }
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/display/output_protection_controller_ash.h b/chrome/browser/chromeos/display/output_protection_controller_ash.h
index d69c0e1..1ad6e5fc 100644
--- a/chrome/browser/chromeos/display/output_protection_controller_ash.h
+++ b/chrome/browser/chromeos/display/output_protection_controller_ash.h
@@ -8,6 +8,7 @@
 #include "base/macros.h"
 #include "base/threading/thread_checker.h"
 #include "chrome/browser/chromeos/display/output_protection_delegate.h"
+#include "ui/display/manager/display_configurator.h"
 
 namespace chromeos {
 
@@ -28,7 +29,7 @@
       const OutputProtectionDelegate::SetProtectionCallback& callback) override;
 
  private:
-  const uint64_t client_id_;
+  const display::DisplayConfigurator::ContentProtectionClientId client_id_;
   base::ThreadChecker thread_checker_;
 
   DISALLOW_COPY_AND_ASSIGN(OutputProtectionControllerAsh);
diff --git a/chrome/browser/chromeos/kiosk_next_home/app_controller_service.cc b/chrome/browser/chromeos/kiosk_next_home/app_controller_service.cc
index c8540af..617a994 100644
--- a/chrome/browser/chromeos/kiosk_next_home/app_controller_service.cc
+++ b/chrome/browser/chromeos/kiosk_next_home/app_controller_service.cc
@@ -98,10 +98,11 @@
     mojom::AppController::GetArcAndroidIdCallback callback) {
   arc::GetAndroidId(base::BindOnce(
       [](mojom::AppController::GetArcAndroidIdCallback callback, bool success,
-         int64_t android_id) {
-        // We need the string version of the Android ID since the int64_t
-        // is too big for Javascript.
-        std::move(callback).Run(success, base::NumberToString(android_id));
+         int64_t raw_android_id) {
+        // The bridge expects the Android id as a hex string.
+        std::string android_id = base::NumberToString(raw_android_id);
+        std::move(callback).Run(
+            success, base::HexEncode(android_id.data(), android_id.size()));
       },
       std::move(callback)));
 }
diff --git a/chrome/browser/chromeos/kiosk_next_home/app_controller_service_unittest.cc b/chrome/browser/chromeos/kiosk_next_home/app_controller_service_unittest.cc
new file mode 100644
index 0000000..8a0d5c9
--- /dev/null
+++ b/chrome/browser/chromeos/kiosk_next_home/app_controller_service_unittest.cc
@@ -0,0 +1,330 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/kiosk_next_home/app_controller_service.h"
+
+#include <map>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/test/bind_test_util.h"
+#include "chrome/browser/apps/app_service/app_service_proxy.h"
+#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
+#include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
+#include "chrome/browser/ui/app_list/arc/arc_app_test.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/services/app_service/public/cpp/app_registry_cache.h"
+#include "chrome/services/app_service/public/cpp/app_update.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/arc/test/fake_app_instance.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+namespace kiosk_next_home {
+namespace {
+
+// Fake activity that we use when seeding data to ARC.
+constexpr char kFakeActivity[] = "test.kiosk_next_home.activity";
+
+using apps::mojom::AppType;
+using apps::mojom::OptionalBool;
+using apps::mojom::Readiness;
+
+typedef std::map<std::string, mojom::AppPtr> AppMap;
+
+}  // namespace
+
+class AppControllerServiceTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    profile_ = std::make_unique<TestingProfile>();
+
+    arc_test_.SetUp(profile());
+    proxy_ = apps::AppServiceProxy::Get(profile());
+
+    app_controller_service_ = AppControllerService::Get(profile());
+  }
+
+  void TearDown() override { arc_test_.TearDown(); }
+
+  Profile* profile() { return profile_.get(); }
+
+  std::string GetAppIdFromAndroidPackage(const std::string& package) {
+    return ArcAppListPrefs::GetAppId(package, kFakeActivity);
+  }
+
+  void AddAndroidPackageToArc(const std::string& package) {
+    arc::mojom::AppInfo app_info;
+    app_info.package_name = package;
+
+    // We are only interested in the package name that we already set above,
+    // but we need to send a full struct so ARC doesn't drop it.
+    app_info.name = "test_app_name";
+    app_info.activity = kFakeActivity;
+    app_info.sticky = false;
+    app_info.notifications_enabled = false;
+    arc_test_.app_instance()->SendAppAdded(app_info);
+  }
+
+  void AddAppDeltaToAppService(apps::mojom::AppPtr delta) {
+    std::vector<apps::mojom::AppPtr> deltas;
+    deltas.push_back(std::move(delta));
+    proxy_->AppRegistryCache().OnApps(std::move(deltas));
+  }
+
+  // Gets all apps from the AppControllerService instance being tested and
+  // returns them in a map keyed by their |app_id|.
+  AppMap GetAppsFromController() {
+    AppMap apps;
+    app_controller_service_->GetApps(base::BindLambdaForTesting(
+        [&apps](std::vector<mojom::AppPtr> app_list) {
+          for (const auto& app : app_list) {
+            apps[app->app_id] = app.Clone();
+          }
+        }));
+    return apps;
+  }
+
+  // Expects the given apps to be returned by a call to
+  // AppControllerService::GetApps(). This function doesn't take into account
+  // the order of the returned apps.
+  void ExpectApps(const std::vector<mojom::App>& expected_apps) {
+    AppMap returned_apps_map = GetAppsFromController();
+
+    EXPECT_EQ(expected_apps.size(), returned_apps_map.size())
+        << "AppServiceController::GetApps() returned wrong number of apps.";
+
+    for (const auto& expected_app : expected_apps) {
+      auto returned_app_it = returned_apps_map.find(expected_app.app_id);
+      ASSERT_NE(returned_app_it, returned_apps_map.end())
+          << "App with app_id " << expected_app.app_id
+          << " was not returned by the AppControllerService::GetApps() "
+             "call.";
+
+      mojom::AppPtr& returned_app = returned_app_it->second;
+
+      // Test equality of every single field to make tests failures more
+      // readable.
+      EXPECT_EQ(returned_app->app_id, expected_app.app_id);
+      EXPECT_EQ(returned_app->type, expected_app.type);
+      EXPECT_EQ(returned_app->display_name, expected_app.display_name);
+      EXPECT_EQ(returned_app->readiness, expected_app.readiness);
+      EXPECT_EQ(returned_app->android_package_name,
+                expected_app.android_package_name);
+
+      // Catch all clause of equality. This will only be necessary if we add
+      // more fields that are not expected above.
+      EXPECT_TRUE(expected_app.Equals(*returned_app));
+    }
+  }
+
+ private:
+  content::TestBrowserThreadBundle test_browser_thread_bundle_;
+  std::unique_ptr<TestingProfile> profile_;
+  ArcAppTest arc_test_;
+  apps::AppServiceProxy* proxy_;
+  AppControllerService* app_controller_service_;
+};
+
+TEST_F(AppControllerServiceTest, AppIsFetchedCorrectly) {
+  std::string app_id = "fake_app_id";
+  std::string display_name = "Fake app name";
+  AppType app_type = AppType::kExtension;
+  Readiness readiness = Readiness::kReady;
+
+  // Seeding data.
+  apps::mojom::App delta;
+  delta.app_id = app_id;
+  delta.name = display_name;
+  delta.app_type = app_type;
+  delta.readiness = readiness;
+  delta.show_in_launcher = OptionalBool::kTrue;
+  AddAppDeltaToAppService(delta.Clone());
+
+  mojom::App expected_app;
+  expected_app.android_package_name = "";
+  expected_app.app_id = app_id;
+  expected_app.display_name = display_name;
+  expected_app.type = app_type;
+  expected_app.readiness = readiness;
+
+  ExpectApps({expected_app});
+}
+
+TEST_F(AppControllerServiceTest, AndroidAppIsFetchedCorrectly) {
+  std::string android_package_name = "fake.app.package";
+  std::string app_id = GetAppIdFromAndroidPackage(android_package_name);
+  std::string display_name = "Fake app name";
+  AppType app_type = AppType::kArc;
+  Readiness readiness = Readiness::kReady;
+
+  // Seeding data.
+  AddAndroidPackageToArc(android_package_name);
+  apps::mojom::App delta;
+  delta.app_id = app_id;
+  delta.name = display_name;
+  delta.app_type = app_type;
+  delta.readiness = readiness;
+  delta.show_in_launcher = OptionalBool::kTrue;
+  AddAppDeltaToAppService(delta.Clone());
+
+  mojom::App expected_app;
+  expected_app.android_package_name = android_package_name;
+  expected_app.app_id = app_id;
+  expected_app.display_name = display_name;
+  expected_app.type = app_type;
+  expected_app.readiness = readiness;
+
+  ExpectApps({expected_app});
+}
+
+TEST_F(AppControllerServiceTest, AndroidAppWithMissingPackageFetchedCorrectly) {
+  std::string android_package_name = "fake.app.package";
+  std::string app_id = GetAppIdFromAndroidPackage(android_package_name);
+  std::string display_name = "Fake app name";
+  AppType app_type = AppType::kArc;
+  Readiness readiness = Readiness::kReady;
+
+  // Seeding data. This time we intentionally don't seed the package to ARC.
+  apps::mojom::App delta;
+  delta.app_id = app_id;
+  delta.name = display_name;
+  delta.app_type = app_type;
+  delta.readiness = readiness;
+  delta.show_in_launcher = OptionalBool::kTrue;
+  AddAppDeltaToAppService(delta.Clone());
+
+  mojom::App expected_app;
+  // Since we don't seed information to ARC, we don't expect to receive a
+  // package here. In this case, expect an empty string (and not a crash :)
+  expected_app.android_package_name = "";
+  expected_app.app_id = app_id;
+  expected_app.display_name = display_name;
+  expected_app.type = app_type;
+  expected_app.readiness = readiness;
+
+  ExpectApps({expected_app});
+}
+
+TEST_F(AppControllerServiceTest, AppReadinessIsUpdated) {
+  std::string app_id = "fake_app_id";
+  AppType app_type = AppType::kExtension;
+  Readiness readiness = Readiness::kUnknown;
+
+  apps::mojom::App delta;
+  delta.app_id = app_id;
+  delta.app_type = app_type;
+  delta.readiness = readiness;
+  delta.show_in_launcher = OptionalBool::kTrue;
+  AddAppDeltaToAppService(delta.Clone());
+
+  mojom::App expected_app;
+  expected_app.app_id = app_id;
+  expected_app.type = app_type;
+  expected_app.readiness = readiness;
+  ExpectApps({expected_app});
+
+  // Now we change the readiness.
+  delta.readiness = Readiness::kReady;
+  AddAppDeltaToAppService(delta.Clone());
+
+  expected_app.readiness = Readiness::kReady;
+  ExpectApps({expected_app});
+}
+
+TEST_F(AppControllerServiceTest, AppDisplayNameIsUpdated) {
+  std::string app_id = "fake_app_id";
+  AppType app_type = AppType::kExtension;
+  std::string display_name = "Initial App Name";
+
+  apps::mojom::App delta;
+  delta.app_id = app_id;
+  delta.app_type = app_type;
+  delta.name = display_name;
+  delta.show_in_launcher = OptionalBool::kTrue;
+  AddAppDeltaToAppService(delta.Clone());
+
+  mojom::App expected_app;
+  expected_app.app_id = app_id;
+  expected_app.type = app_type;
+  expected_app.display_name = display_name;
+  ExpectApps({expected_app});
+
+  // Now we change the name.
+  std::string new_display_name = "New App Name";
+  delta.name = new_display_name;
+  AddAppDeltaToAppService(delta.Clone());
+
+  expected_app.display_name = new_display_name;
+  ExpectApps({expected_app});
+}
+
+TEST_F(AppControllerServiceTest, MultipleAppsAreFetchedCorrectly) {
+  // Seed the first app.
+  apps::mojom::App first_delta;
+  first_delta.app_id = "first_app";
+  first_delta.app_type = AppType::kBuiltIn;
+  first_delta.show_in_launcher = OptionalBool::kTrue;
+  AddAppDeltaToAppService(first_delta.Clone());
+
+  mojom::App first_expected_app;
+  first_expected_app.app_id = "first_app";
+  first_expected_app.type = AppType::kBuiltIn;
+  // Expect only the first app.
+  ExpectApps({first_expected_app});
+
+  // Seed second app.
+  apps::mojom::App second_delta;
+  second_delta.app_id = "second_app";
+  second_delta.app_type = AppType::kBuiltIn;
+  second_delta.show_in_launcher = OptionalBool::kTrue;
+  AddAppDeltaToAppService(second_delta.Clone());
+
+  mojom::App second_expected_app;
+  second_expected_app.app_id = "second_app";
+  second_expected_app.type = AppType::kBuiltIn;
+  // Expect both apps.
+  ExpectApps({first_expected_app, second_expected_app});
+}
+
+TEST_F(AppControllerServiceTest, AppsThatAreNotRelevantAreFiltered) {
+  // Seed an app that's allowed to be returned by the AppControllerService.
+  apps::mojom::App allowed_app_delta;
+  allowed_app_delta.app_id = "allowed_app";
+  allowed_app_delta.app_type = AppType::kBuiltIn;
+  allowed_app_delta.show_in_launcher = OptionalBool::kTrue;
+  AddAppDeltaToAppService(allowed_app_delta.Clone());
+
+  apps::mojom::App first_blocked_app_delta;
+  first_blocked_app_delta.app_id = "first_blocked_app";
+  first_blocked_app_delta.app_type = AppType::kBuiltIn;
+  first_blocked_app_delta.show_in_launcher = OptionalBool::kUnknown;
+  AddAppDeltaToAppService(first_blocked_app_delta.Clone());
+
+  apps::mojom::App second_blocked_app_delta;
+  second_blocked_app_delta.app_id = "second_blocked_app";
+  second_blocked_app_delta.app_type = AppType::kBuiltIn;
+  second_blocked_app_delta.show_in_launcher = OptionalBool::kFalse;
+  AddAppDeltaToAppService(second_blocked_app_delta.Clone());
+
+  apps::mojom::App kiosk_next_app_delta;
+  kiosk_next_app_delta.app_id = extension_misc::kKioskNextHomeAppId;
+  kiosk_next_app_delta.app_type = AppType::kBuiltIn;
+  // Even though the Kiosk Next might be allowed in the launcher, we cannot
+  // return it.
+  kiosk_next_app_delta.show_in_launcher = OptionalBool::kTrue;
+  AddAppDeltaToAppService(kiosk_next_app_delta.Clone());
+
+  mojom::App allowed_app;
+  allowed_app.app_id = "allowed_app";
+  allowed_app.type = AppType::kBuiltIn;
+
+  // Expect only the allowed app, all the other ones were filtered.
+  ExpectApps({allowed_app});
+}
+
+}  // namespace kiosk_next_home
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/session/user_session_manager.cc b/chrome/browser/chromeos/login/session/user_session_manager.cc
index ca003a2a..0e02efe 100644
--- a/chrome/browser/chromeos/login/session/user_session_manager.cc
+++ b/chrome/browser/chromeos/login/session/user_session_manager.cc
@@ -2157,9 +2157,6 @@
     browser_creator.LaunchBrowser(
         *base::CommandLine::ForCurrentProcess(), profile, base::FilePath(),
         chrome::startup::IS_PROCESS_STARTUP, first_run);
-  } else {
-    LOG(WARNING) << "Browser hasn't been launched, should_launch_browser_"
-                 << " is false. This is normal in some tests.";
   }
 
   if (HatsNotificationController::ShouldShowSurveyToProfile(profile))
diff --git a/chrome/browser/chromeos/login/ui/login_display_host_mojo.cc b/chrome/browser/chromeos/login/ui/login_display_host_mojo.cc
index 0d6d9ff..eb4de186 100644
--- a/chrome/browser/chromeos/login/ui/login_display_host_mojo.cc
+++ b/chrome/browser/chromeos/login/ui/login_display_host_mojo.cc
@@ -9,6 +9,7 @@
 
 #include "base/bind.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/login/existing_user_controller.h"
 #include "chrome/browser/chromeos/login/mojo_system_info_dispatcher.h"
 #include "chrome/browser/chromeos/login/screens/chrome_user_selection_screen.h"
@@ -20,6 +21,7 @@
 #include "chrome/browser/ui/ash/login_screen_client.h"
 #include "chrome/browser/ui/ash/wallpaper_controller_client.h"
 #include "chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h"
+#include "chrome/common/pref_names.h"
 #include "chromeos/login/auth/user_context.h"
 #include "components/user_manager/user_names.h"
 
@@ -200,6 +202,35 @@
     return;
   }
 
+  // Check whether factory reset or debugging feature have been requested in
+  // prior session. Ideally this would be handled earlier in startup flow, but
+  // it's handled here, after mojo login display host is set up to avoid running
+  // the wizard with web UI based login display host, and possibly adding
+  // another way to land on web UI based sign-in screen.
+  // TODO(tbarzic): Reassess when https://crbug.com/943720 is fixed.
+  PrefService* local_state = g_browser_process->local_state();
+  if (local_state->GetBoolean(prefs::kFactoryResetRequested)) {
+    StartWizard(OobeScreen::SCREEN_OOBE_RESET);
+    start_delayed_for_oobe_dialog_ = true;
+    return;
+  }
+
+  if (local_state->GetBoolean(prefs::kDebuggingFeaturesRequested)) {
+    StartWizard(OobeScreen::SCREEN_OOBE_ENABLE_DEBUGGING);
+    start_delayed_for_oobe_dialog_ = true;
+    return;
+  }
+
+  // If initial signin screen was delayed to show a OOBE dialog, make sure the
+  // dialog is hidden.
+  if (start_delayed_for_oobe_dialog_) {
+    dialog_->Hide();
+    // Reset accelerator will not work properly if OOBE UI stays in reset
+    // dialog state, so make sure the curren dialog screen changes.
+    GetOobeUI()->GetGaiaScreenView()->ShowGaiaAsync(base::nullopt);
+    start_delayed_for_oobe_dialog_ = false;
+  }
+
   signin_screen_started_ = true;
 
   existing_user_controller_ = std::make_unique<ExistingUserController>();
diff --git a/chrome/browser/chromeos/login/ui/login_display_host_mojo.h b/chrome/browser/chromeos/login/ui/login_display_host_mojo.h
index 5c8b37a..6219862 100644
--- a/chrome/browser/chromeos/login/ui/login_display_host_mojo.h
+++ b/chrome/browser/chromeos/login/ui/login_display_host_mojo.h
@@ -164,6 +164,10 @@
   // first OnStartSigninScreen and remains true afterward.
   bool signin_screen_started_ = false;
 
+  // Set if the signin screen initialization was delayed to show a OOBE dialog,
+  // for example to run reset or enable debugging wizard.
+  bool start_delayed_for_oobe_dialog_ = false;
+
   base::WeakPtrFactory<LoginDisplayHostMojo> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(LoginDisplayHostMojo);
diff --git a/chrome/browser/chromeos/preferences.cc b/chrome/browser/chromeos/preferences.cc
index 0a449d9..71300c99b 100644
--- a/chrome/browser/chromeos/preferences.cc
+++ b/chrome/browser/chromeos/preferences.cc
@@ -199,6 +199,10 @@
     hardware_keyboard_id = "xkb:us::eng";  // only for testing.
   }
 
+  registry->RegisterBooleanPref(ash::prefs::kKioskNextShellEnabled,
+                                false /* default_value */,
+                                PrefRegistry::PUBLIC);
+
   registry->RegisterBooleanPref(prefs::kPerformanceTracingEnabled, false);
 
   // This pref is device specific and must not be synced.
@@ -591,6 +595,7 @@
   pref_change_registrar_.Add(prefs::kResolveTimezoneByGeolocationMethod,
                              callback);
   pref_change_registrar_.Add(prefs::kUse24HourClock, callback);
+  pref_change_registrar_.Add(prefs::kParentAccessCodeConfig, callback);
   for (auto* remap_pref : kLanguageRemapPrefs)
     pref_change_registrar_.Add(remap_pref, callback);
 }
@@ -946,6 +951,20 @@
                                              prefs::kUse24HourClock, value);
   }
 
+  if (pref_name == prefs::kParentAccessCodeConfig ||
+      reason != REASON_PREF_CHANGED) {
+    const base::Value* value =
+        prefs_->GetDictionary(prefs::kParentAccessCodeConfig);
+    if (value && prefs_->IsManagedPreference(prefs::kParentAccessCodeConfig)) {
+      user_manager::known_user::SetPref(user_->GetAccountId(),
+                                        prefs::kKnownUserParentAccessCodeConfig,
+                                        value->Clone());
+    } else {
+      user_manager::known_user::RemovePref(
+          user_->GetAccountId(), prefs::kKnownUserParentAccessCodeConfig);
+    }
+  }
+
   for (auto* remap_pref : kLanguageRemapPrefs) {
     if (pref_name == remap_pref || reason != REASON_ACTIVE_USER_CHANGED) {
       const int value = prefs_->GetInteger(remap_pref);
diff --git a/chrome/browser/complex_tasks/task_tab_helper_unittest.cc b/chrome/browser/complex_tasks/task_tab_helper_unittest.cc
index 47e3fdc5..5f56efc 100644
--- a/chrome/browser/complex_tasks/task_tab_helper_unittest.cc
+++ b/chrome/browser/complex_tasks/task_tab_helper_unittest.cc
@@ -37,7 +37,8 @@
 
 class TaskTabHelperUnitTest : public ChromeRenderViewHostTestHarness {
  protected:
-  const GURL URL = GURL("http://www.google.com");
+  const std::string kSearchDomain = "http://www.google.com/";
+  const GURL kSearchURL = GURL(kSearchDomain);
   const std::string from_default_search_engine_histogram =
       "Tabs.Tasks.HubAndSpokeNavigationUsage.FromDefaultSearchEngine";
   const std::string from_form_submit_histogram =
@@ -56,7 +57,7 @@
     ChromeRenderViewHostTestHarness::SetUp();
     MockTaskTabHelper::CreateForWebContents(web_contents());
     task_tab_helper_ = MockTaskTabHelper::FromWebContents(web_contents());
-    NavigateAndCommit(URL);
+    NavigateAndCommit(kSearchURL);
 
     ON_CALL(*task_tab_helper_, GetSpokeEntryHubType())
         .WillByDefault(testing::Return(DEFAULT_SEARCH_ENGINE_HUB_TYPE));
@@ -74,7 +75,12 @@
 
   void NavigateAndCommitNTimes(int times) {
     while (times--) {
-      NavigateAndCommit(URL);
+      static int unique_int = 0;
+      // Note: The URLs need to be different on each iteration. Otherwise,
+      // navigations will be treated as reloads and will not create a new
+      // NavigationEntry.
+      NavigateAndCommit(
+          GURL(kSearchDomain + base::NumberToString(++unique_int)));
     }
   }
 
@@ -83,7 +89,7 @@
 };
 
 // Testing the reset counter logic
-TEST_F(TaskTabHelperUnitTest, spokeCountShouldResetInNavigationEntryCommitted) {
+TEST_F(TaskTabHelperUnitTest, SpokeCountShouldResetInNavigationEntryCommitted) {
   NavigateAndCommitNTimes(2);
   GoBackNTimes(1);
   NavigateAndCommitNTimes(1);
@@ -95,7 +101,7 @@
 }
 
 TEST_F(TaskTabHelperUnitTest,
-       spokeCountShouldNotResetInNavigationEntryCommitted) {
+       SpokeCountShouldNotResetInNavigationEntryCommitted) {
   NavigateAndCommitNTimes(2);
   GoBackNTimes(1);
   NavigateAndCommitNTimes(1);
@@ -107,7 +113,7 @@
 
 // Testing the recording
 TEST_F(TaskTabHelperUnitTest,
-       simpleRecordHubAndSpokeUsageFromDefaultSearchEngine) {
+       SimpleRecordHubAndSpokeUsageFromDefaultSearchEngine) {
   EXPECT_CALL(*task_tab_helper_, GetSpokeEntryHubType())
       .WillOnce(testing::Return(DEFAULT_SEARCH_ENGINE_HUB_TYPE));
 
@@ -119,7 +125,7 @@
                                       1);
 }
 
-TEST_F(TaskTabHelperUnitTest, simpleRecordHubAndSpokeUsageFromFormSubmit) {
+TEST_F(TaskTabHelperUnitTest, SimpleRecordHubAndSpokeUsageFromFormSubmit) {
   EXPECT_CALL(*task_tab_helper_, GetSpokeEntryHubType())
       .WillOnce(testing::Return(FORM_SUBMIT_HUB_TYPE));
 
@@ -130,7 +136,7 @@
   histogram_tester_.ExpectBucketCount(from_form_submit_histogram, 2, 1);
 }
 
-TEST_F(TaskTabHelperUnitTest, simpleRecordHubAndSpokeUsageFromOther) {
+TEST_F(TaskTabHelperUnitTest, SimpleRecordHubAndSpokeUsageFromOther) {
   EXPECT_CALL(*task_tab_helper_, GetSpokeEntryHubType())
       .WillOnce(testing::Return(OTHER_HUB_TYPE));
 
@@ -141,7 +147,7 @@
   histogram_tester_.ExpectBucketCount(from_others_histogram, 2, 1);
 }
 
-TEST_F(TaskTabHelperUnitTest, complexRecordHubAndSpokeUsage) {
+TEST_F(TaskTabHelperUnitTest, ComplexRecordHubAndSpokeUsage) {
   {
     testing::InSequence s;
 
diff --git a/chrome/browser/data_reduction_proxy/data_reduction_proxy_browsertest.cc b/chrome/browser/data_reduction_proxy/data_reduction_proxy_browsertest.cc
index e95bf82..f520395 100644
--- a/chrome/browser/data_reduction_proxy/data_reduction_proxy_browsertest.cc
+++ b/chrome/browser/data_reduction_proxy/data_reduction_proxy_browsertest.cc
@@ -756,7 +756,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(DataReductionProxyFallbackBrowsertest,
-                       ProxyBlockedOnAuthError) {
+                       DISABLED_ProxyBlockedOnAuthError) {
   base::HistogramTester histogram_tester;
   net::EmbeddedTestServer test_server;
   test_server.RegisterRequestHandler(
@@ -774,7 +774,8 @@
 
 // Tests that if using data reduction proxy results in redirect loop, then
 // the proxy is bypassed, and the request is fetched directly.
-IN_PROC_BROWSER_TEST_F(DataReductionProxyFallbackBrowsertest, RedirectCycle) {
+IN_PROC_BROWSER_TEST_F(DataReductionProxyFallbackBrowsertest,
+                       DISABLED_RedirectCycle) {
   base::HistogramTester histogram_tester;
   net::EmbeddedTestServer test_server;
   test_server.RegisterRequestHandler(
diff --git a/chrome/browser/download/download_service_factory.cc b/chrome/browser/download/download_service_factory.cc
index cd3f5bb..dafae7e 100644
--- a/chrome/browser/download/download_service_factory.cc
+++ b/chrome/browser/download/download_service_factory.cc
@@ -22,6 +22,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/chrome_constants.h"
 #include "components/download/content/factory/download_service_factory_helper.h"
+#include "components/download/content/factory/navigation_monitor_factory.h"
 #include "components/download/public/background_service/clients.h"
 #include "components/download/public/background_service/download_service.h"
 #include "components/download/public/background_service/features.h"
@@ -59,6 +60,7 @@
     : BrowserContextKeyedServiceFactory(
           "download::DownloadService",
           BrowserContextDependencyManager::GetInstance()) {
+  DependsOn(download::NavigationMonitorFactory::GetInstance());
   DependsOn(leveldb_proto::ProtoDatabaseProviderFactory::GetInstance());
 }
 
diff --git a/chrome/browser/extensions/api/management/chrome_management_api_delegate.cc b/chrome/browser/extensions/api/management/chrome_management_api_delegate.cc
index 1dcbd430..b961735 100644
--- a/chrome/browser/extensions/api/management/chrome_management_api_delegate.cc
+++ b/chrome/browser/extensions/api/management/chrome_management_api_delegate.cc
@@ -12,7 +12,6 @@
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/extensions/chrome_extension_function_details.h"
 #include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/extensions/extension_util.h"
 #include "chrome/browser/extensions/launch_util.h"
 #include "chrome/browser/favicon/favicon_service_factory.h"
 #include "chrome/browser/installable/installable_metrics.h"
@@ -312,14 +311,6 @@
   return std::unique_ptr<extensions::AppForLinkDelegate>(delegate);
 }
 
-bool ChromeManagementAPIDelegate::CanHostedAppsOpenInWindows() const {
-  return extensions::util::CanHostedAppsOpenInWindows();
-}
-
-bool ChromeManagementAPIDelegate::IsNewBookmarkAppsEnabled() const {
-  return extensions::util::IsNewBookmarkAppsEnabled();
-}
-
 void ChromeManagementAPIDelegate::EnableExtension(
     content::BrowserContext* context,
     const std::string& extension_id) const {
diff --git a/chrome/browser/extensions/api/management/chrome_management_api_delegate.h b/chrome/browser/extensions/api/management/chrome_management_api_delegate.h
index eb53b3d..8146845 100644
--- a/chrome/browser/extensions/api/management/chrome_management_api_delegate.h
+++ b/chrome/browser/extensions/api/management/chrome_management_api_delegate.h
@@ -46,8 +46,6 @@
       content::BrowserContext* context,
       const std::string& title,
       const GURL& launch_url) const override;
-  bool CanHostedAppsOpenInWindows() const override;
-  bool IsNewBookmarkAppsEnabled() const override;
   void EnableExtension(content::BrowserContext* context,
                        const std::string& extension_id) const override;
   void DisableExtension(
diff --git a/chrome/browser/extensions/api/management/management_apitest.cc b/chrome/browser/extensions/api/management/management_apitest.cc
index cfea99a..425e774 100644
--- a/chrome/browser/extensions/api/management/management_apitest.cc
+++ b/chrome/browser/extensions/api/management/management_apitest.cc
@@ -7,7 +7,6 @@
 #include "build/build_config.h"
 #include "chrome/browser/extensions/extension_apitest.h"
 #include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/extensions/extension_util.h"
 #include "chrome/browser/extensions/launch_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
@@ -301,23 +300,11 @@
   // If the ID changed, then the pref will not apply to the app.
   ASSERT_EQ(app_id, app_id_new);
 
-  unsigned expected_browser_count = 2;
-#if defined(OS_MACOSX)
-  // Without the new Bookmark Apps, Mac has no way of making standalone browser
-  // windows for apps, so it will add to the tabstrip instead.
-  EXPECT_FALSE(extensions::util::IsNewBookmarkAppsEnabled());
-  EXPECT_FALSE(extensions::util::CanHostedAppsOpenInWindows());
-  expected_browser_count = 1;
-  ASSERT_EQ(2, browser()->tab_strip_model()->count());
-#endif
   // Find the app's browser.  Opening in a new window will create
   // a new browser.
-  ASSERT_EQ(expected_browser_count,
-            chrome::GetBrowserCount(browser()->profile()));
-  if (expected_browser_count == 2) {
-    Browser* app_browser = FindOtherBrowser(browser());
-    ASSERT_TRUE(app_browser->is_app());
-  }
+  ASSERT_EQ(2u, chrome::GetBrowserCount(browser()->profile()));
+  Browser* app_browser = FindOtherBrowser(browser());
+  ASSERT_TRUE(app_browser->is_app());
 }
 
 // Flaky on MacOS: crbug.com/915339
diff --git a/chrome/browser/extensions/api/vpn_provider/vpn_provider_apitest.cc b/chrome/browser/extensions/api/vpn_provider/vpn_provider_apitest.cc
index d4506f84..f7f0ce9 100644
--- a/chrome/browser/extensions/api/vpn_provider/vpn_provider_apitest.cc
+++ b/chrome/browser/extensions/api/vpn_provider/vpn_provider_apitest.cc
@@ -13,10 +13,10 @@
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/extensions/extension_apitest.h"
 #include "chrome/browser/extensions/extension_service.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
-#include "chromeos/dbus/shill/fake_shill_profile_client.h"
-#include "chromeos/dbus/shill/fake_shill_service_client.h"
 #include "chromeos/dbus/shill/fake_shill_third_party_vpn_driver_client.h"
+#include "chromeos/dbus/shill/shill_clients.h"
+#include "chromeos/dbus/shill/shill_profile_client.h"
+#include "chromeos/dbus/shill/shill_service_client.h"
 #include "chromeos/network/network_configuration_handler.h"
 #include "chromeos/network/network_profile_handler.h"
 #include "content/public/browser/browser_context.h"
@@ -122,17 +122,15 @@
 
   void SetUpInProcessBrowserTestFixture() override {
     extensions::ExtensionApiTest::SetUpInProcessBrowserTestFixture();
+    // Destroy the existing client and create a test specific fake client. It
+    // will be destroyed in ChromeBrowserMain.
     test_client_ = new TestShillThirdPartyVpnDriverClient();
-    DBusThreadManager::GetSetterForTesting()->SetShillThirdPartyVpnDriverClient(
-        base::WrapUnique(test_client_));
   }
 
   void AddNetworkProfileForUser() {
-    static_cast<FakeShillProfileClient*>(
-        DBusThreadManager::Get()->GetShillProfileClient())
-        ->AddProfile(
-            kNetworkProfilePath,
-            chromeos::ProfileHelper::GetUserIdHashFromProfile(profile()));
+    ShillProfileClient::Get()->GetTestInterface()->AddProfile(
+        kNetworkProfilePath,
+        chromeos::ProfileHelper::GetUserIdHashFromProfile(profile()));
     content::RunAllPendingInMessageLoop();
   }
 
@@ -191,7 +189,7 @@
   }
 
  protected:
-  TestShillThirdPartyVpnDriverClient* test_client_ = nullptr;
+  TestShillThirdPartyVpnDriverClient* test_client_ = nullptr;  // Unowned
   VpnService* service_ = nullptr;
   std::string extension_id_;
   std::string service_path_;
@@ -217,10 +215,8 @@
   const std::string service_path = GetSingleServicePath();
   std::string profile_path;
   base::DictionaryValue properties;
-  EXPECT_TRUE(DBusThreadManager::Get()
-                  ->GetShillProfileClient()
-                  ->GetTestInterface()
-                  ->GetService(service_path, &profile_path, &properties));
+  EXPECT_TRUE(ShillProfileClient::Get()->GetTestInterface()->GetService(
+      service_path, &profile_path, &properties));
 }
 
 IN_PROC_BROWSER_TEST_F(VpnProviderApiTest, DestroyConfig) {
@@ -231,17 +227,13 @@
   const std::string service_path = GetSingleServicePath();
   std::string profile_path;
   base::DictionaryValue properties;
-  EXPECT_TRUE(DBusThreadManager::Get()
-                  ->GetShillProfileClient()
-                  ->GetTestInterface()
-                  ->GetService(service_path, &profile_path, &properties));
+  EXPECT_TRUE(ShillProfileClient::Get()->GetTestInterface()->GetService(
+      service_path, &profile_path, &properties));
 
   EXPECT_TRUE(RunExtensionTest("destroyConfigSuccess"));
   EXPECT_FALSE(DoesConfigExist(kTestConfig));
-  EXPECT_FALSE(DBusThreadManager::Get()
-                   ->GetShillProfileClient()
-                   ->GetTestInterface()
-                   ->GetService(service_path, &profile_path, &properties));
+  EXPECT_FALSE(ShillProfileClient::Get()->GetTestInterface()->GetService(
+      service_path, &profile_path, &properties));
 }
 
 IN_PROC_BROWSER_TEST_F(VpnProviderApiTest, DestroyConnectedConfig) {
@@ -253,10 +245,8 @@
   const std::string service_path = GetSingleServicePath();
   std::string profile_path;
   base::DictionaryValue properties;
-  EXPECT_TRUE(DBusThreadManager::Get()
-                  ->GetShillProfileClient()
-                  ->GetTestInterface()
-                  ->GetService(service_path, &profile_path, &properties));
+  EXPECT_TRUE(ShillProfileClient::Get()->GetTestInterface()->GetService(
+      service_path, &profile_path, &properties));
   EXPECT_FALSE(IsConfigConnected());
 
   const std::string object_path = shill::kObjectPathBase + GetKey(kTestConfig);
@@ -268,10 +258,8 @@
 
   EXPECT_TRUE(DestroyConfigForTest(kTestConfig));
   EXPECT_FALSE(DoesConfigExist(kTestConfig));
-  EXPECT_FALSE(DBusThreadManager::Get()
-                   ->GetShillProfileClient()
-                   ->GetTestInterface()
-                   ->GetService(service_path, &profile_path, &properties));
+  EXPECT_FALSE(ShillProfileClient::Get()->GetTestInterface()->GetService(
+      service_path, &profile_path, &properties));
 
   extensions::ResultCatcher catcher;
   ASSERT_TRUE(catcher.GetNextResult());
@@ -285,10 +273,8 @@
   const std::string service_path = GetSingleServicePath();
   std::string profile_path;
   base::DictionaryValue properties;
-  EXPECT_TRUE(DBusThreadManager::Get()
-                  ->GetShillProfileClient()
-                  ->GetTestInterface()
-                  ->GetService(service_path, &profile_path, &properties));
+  EXPECT_TRUE(ShillProfileClient::Get()->GetTestInterface()->GetService(
+      service_path, &profile_path, &properties));
   EXPECT_FALSE(IsConfigConnected());
 
   const std::string object_path = shill::kObjectPathBase + GetKey(kTestConfig);
@@ -384,18 +370,14 @@
   const std::string service_path = GetSingleServicePath();
   std::string profile_path;
   base::DictionaryValue properties;
-  EXPECT_TRUE(DBusThreadManager::Get()
-                  ->GetShillProfileClient()
-                  ->GetTestInterface()
-                  ->GetService(service_path, &profile_path, &properties));
+  EXPECT_TRUE(ShillProfileClient::Get()->GetTestInterface()->GetService(
+      service_path, &profile_path, &properties));
 
   UninstallExtension(extension_id_);
   content::RunAllPendingInMessageLoop();
   EXPECT_FALSE(DoesConfigExist(kTestConfig));
-  EXPECT_FALSE(DBusThreadManager::Get()
-                   ->GetShillProfileClient()
-                   ->GetTestInterface()
-                   ->GetService(service_path, &profile_path, &properties));
+  EXPECT_FALSE(ShillProfileClient::Get()->GetTestInterface()->GetService(
+      service_path, &profile_path, &properties));
 }
 
 IN_PROC_BROWSER_TEST_F(VpnProviderApiTest, CreateDisable) {
@@ -407,10 +389,8 @@
   const std::string service_path = GetSingleServicePath();
   std::string profile_path;
   base::DictionaryValue properties;
-  EXPECT_TRUE(DBusThreadManager::Get()
-                  ->GetShillProfileClient()
-                  ->GetTestInterface()
-                  ->GetService(service_path, &profile_path, &properties));
+  EXPECT_TRUE(ShillProfileClient::Get()->GetTestInterface()->GetService(
+      service_path, &profile_path, &properties));
 
   extensions::ExtensionService* extension_service =
       extensions::ExtensionSystem::Get(profile())->extension_service();
@@ -418,10 +398,8 @@
       extension_id_, extensions::disable_reason::DISABLE_USER_ACTION);
   content::RunAllPendingInMessageLoop();
   EXPECT_FALSE(DoesConfigExist(kTestConfig));
-  EXPECT_FALSE(DBusThreadManager::Get()
-                   ->GetShillProfileClient()
-                   ->GetTestInterface()
-                   ->GetService(service_path, &profile_path, &properties));
+  EXPECT_FALSE(ShillProfileClient::Get()->GetTestInterface()->GetService(
+      service_path, &profile_path, &properties));
 }
 
 IN_PROC_BROWSER_TEST_F(VpnProviderApiTest, CreateBlacklist) {
@@ -433,20 +411,16 @@
   const std::string service_path = GetSingleServicePath();
   std::string profile_path;
   base::DictionaryValue properties;
-  EXPECT_TRUE(DBusThreadManager::Get()
-                  ->GetShillProfileClient()
-                  ->GetTestInterface()
-                  ->GetService(service_path, &profile_path, &properties));
+  EXPECT_TRUE(ShillProfileClient::Get()->GetTestInterface()->GetService(
+      service_path, &profile_path, &properties));
 
   extensions::ExtensionService* extension_service =
       extensions::ExtensionSystem::Get(profile())->extension_service();
   extension_service->BlacklistExtensionForTest(extension_id_);
   content::RunAllPendingInMessageLoop();
   EXPECT_FALSE(DoesConfigExist(kTestConfig));
-  EXPECT_FALSE(DBusThreadManager::Get()
-                   ->GetShillProfileClient()
-                   ->GetTestInterface()
-                   ->GetService(service_path, &profile_path, &properties));
+  EXPECT_FALSE(ShillProfileClient::Get()->GetTestInterface()->GetService(
+      service_path, &profile_path, &properties));
 }
 
 }  // namespace chromeos
diff --git a/chrome/browser/extensions/extension_util.cc b/chrome/browser/extensions/extension_util.cc
index 401d622..b36d1916 100644
--- a/chrome/browser/extensions/extension_util.cc
+++ b/chrome/browser/extensions/extension_util.cc
@@ -309,26 +309,6 @@
       IDR_EXTENSION_DEFAULT_ICON);
 }
 
-bool IsNewBookmarkAppsEnabled() {
-#if defined(OS_MACOSX)
-  return base::FeatureList::IsEnabled(features::kBookmarkApps) ||
-         base::FeatureList::IsEnabled(features::kAppBanners) ||
-         banners::AppBannerManager::IsExperimentalAppBannersEnabled();
-#else
-  return true;
-#endif
-}
-
-bool CanHostedAppsOpenInWindows() {
-#if defined(OS_MACOSX)
-  return base::CommandLine::ForCurrentProcess()->HasSwitch(
-             ::switches::kEnableHostedAppsInWindows) ||
-         base::FeatureList::IsEnabled(features::kDesktopPWAWindowing);
-#else
-  return true;
-#endif
-}
-
 bool IsExtensionSupervised(const Extension* extension, Profile* profile) {
   return WasInstalledByCustodian(extension->id(), profile) &&
          profile->IsSupervised();
diff --git a/chrome/browser/extensions/extension_util.h b/chrome/browser/extensions/extension_util.h
index c395032..ec062ab 100644
--- a/chrome/browser/extensions/extension_util.h
+++ b/chrome/browser/extensions/extension_util.h
@@ -102,16 +102,6 @@
 const gfx::ImageSkia& GetDefaultExtensionIcon();
 const gfx::ImageSkia& GetDefaultAppIcon();
 
-// Returns true if the bookmark apps feature is enabled.
-//
-// TODO(benwells): http://crbug.com/441128: Remove this entirely once the
-// feature is stable.
-bool IsNewBookmarkAppsEnabled();
-
-// TODO(dominickn): http://crbug.com/517682: Remove this entirely once
-// open in window is stable on Mac.
-bool CanHostedAppsOpenInWindows();
-
 // Returns true for custodian-installed extensions in a supervised profile.
 bool IsExtensionSupervised(const Extension* extension, Profile* profile);
 
diff --git a/chrome/browser/extensions/launch_util.cc b/chrome/browser/extensions/launch_util.cc
index c231fb56f..14d1653 100644
--- a/chrome/browser/extensions/launch_util.cc
+++ b/chrome/browser/extensions/launch_util.cc
@@ -9,7 +9,6 @@
 #include "base/values.h"
 #include "build/build_config.h"
 #include "chrome/browser/extensions/extension_sync_service.h"
-#include "chrome/browser/extensions/extension_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_applications/extensions/bookmark_app_util.h"
 #include "chrome/common/extensions/extension_constants.h"
@@ -51,26 +50,11 @@
   if (extension->is_hosted_app() &&
       !BookmarkAppIsLocallyInstalled(prefs, extension)) {
     result = LAUNCH_TYPE_REGULAR;
-  }
-
-#if defined(OS_MACOSX)
-  // Disable opening as window on Mac if:
-  //  1. the extension isn't a platform app, AND
-  //  2. the intended result is open as window, AND
-  //  3. CanHostedAppsOpenInWindows() is false
-  if (!extension->is_platform_app() && result == LAUNCH_TYPE_WINDOW &&
-      !extensions::util::CanHostedAppsOpenInWindows()) {
+  } else if (result == LAUNCH_TYPE_PINNED) {
     result = LAUNCH_TYPE_REGULAR;
+  } else if (result == LAUNCH_TYPE_FULLSCREEN) {
+    result = LAUNCH_TYPE_WINDOW;
   }
-#else
-  if (extensions::util::IsNewBookmarkAppsEnabled()) {
-    if (result == LAUNCH_TYPE_PINNED)
-      result = LAUNCH_TYPE_REGULAR;
-    if (result == LAUNCH_TYPE_FULLSCREEN)
-      result = LAUNCH_TYPE_WINDOW;
-  }
-#endif
-
   return result;
 }
 
diff --git a/chrome/browser/extensions/tab_helper.cc b/chrome/browser/extensions/tab_helper.cc
index 744e081..7eb0908a 100644
--- a/chrome/browser/extensions/tab_helper.cc
+++ b/chrome/browser/extensions/tab_helper.cc
@@ -225,18 +225,13 @@
   ExtensionRegistry* registry = ExtensionRegistry::Get(context);
   const ExtensionSet& enabled_extensions = registry->enabled_extensions();
 
-  if (util::IsNewBookmarkAppsEnabled()) {
-    Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
-    if (browser && browser->is_app()) {
-      const Extension* extension = registry->GetExtensionById(
-          web_app::GetAppIdFromApplicationName(browser->app_name()),
-          ExtensionRegistry::EVERYTHING);
-      if (extension && AppLaunchInfo::GetFullLaunchURL(extension).is_valid())
-        SetExtensionApp(extension);
-    } else {
-      UpdateExtensionAppIcon(
-          enabled_extensions.GetExtensionOrAppByURL(
-              navigation_handle->GetURL()));
+  Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
+  if (browser && browser->is_app()) {
+    const Extension* extension = registry->GetExtensionById(
+        web_app::GetAppIdFromApplicationName(browser->app_name()),
+        ExtensionRegistry::EVERYTHING);
+    if (extension && AppLaunchInfo::GetFullLaunchURL(extension).is_valid()) {
+      SetExtensionApp(extension);
     }
   } else {
     UpdateExtensionAppIcon(
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 8baab65..cbc127ce 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -417,6 +417,11 @@
     "expiry_milestone": 78
   },
   {
+    "name": "contextual-search-translation-model",
+    "owners": [ "//chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/OWNERS" ],
+    "expiry_milestone": 77
+  },
+  {
     "name": "contextual-search-unity-integration",
     "owners": [ "//chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/OWNERS" ],
     "expiry_milestone": 76
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 156682f..13ef88f 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -2300,6 +2300,12 @@
     "Enables triggering on a second tap gesture even when Ranker would "
     "normally suppress that tap.";
 
+const char kContextualSearchTranslationModelName[] =
+    "Contextual Search translation using the Chrome translate model.";
+const char kContextualSearchTranslationModelDescription[] =
+    "Enables triggering translation in Contextual Search according to the "
+    "Chrome translation model semantics.";
+
 const char kContextualSearchUnityIntegrationName[] =
     "Contextual Search integration with Unified Consent";
 const char kContextualSearchUnityIntegrationDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 38e8643..b33d9be 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1375,6 +1375,9 @@
 extern const char kContextualSearchSimplifiedServerName[];
 extern const char kContextualSearchSimplifiedServerDescription[];
 
+extern const char kContextualSearchTranslationModelName[];
+extern const char kContextualSearchTranslationModelDescription[];
+
 extern const char kContextualSearchUnityIntegrationName[];
 extern const char kContextualSearchUnityIntegrationDescription[];
 
diff --git a/chrome/browser/image_fetcher/image_fetcher_service_factory.cc b/chrome/browser/image_fetcher/image_fetcher_service_factory.cc
index a5615a1..7024306 100644
--- a/chrome/browser/image_fetcher/image_fetcher_service_factory.cc
+++ b/chrome/browser/image_fetcher/image_fetcher_service_factory.cc
@@ -22,6 +22,7 @@
 #include "components/image_fetcher/core/image_fetcher_service.h"
 #include "components/keyed_service/core/simple_dependency_manager.h"
 #include "components/keyed_service/core/simple_factory_key.h"
+#include "components/leveldb_proto/content/proto_database_provider_factory.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/storage_partition.h"
 
@@ -58,7 +59,9 @@
 
 ImageFetcherServiceFactory::ImageFetcherServiceFactory()
     : SimpleKeyedServiceFactory("ImageFetcherService",
-                                SimpleDependencyManager::GetInstance()) {}
+                                SimpleDependencyManager::GetInstance()) {
+  DependsOn(leveldb_proto::ProtoDatabaseProviderFactory::GetInstance());
+}
 
 ImageFetcherServiceFactory::~ImageFetcherServiceFactory() = default;
 
@@ -73,6 +76,7 @@
   base::DefaultClock* clock = base::DefaultClock::GetInstance();
 
   auto metadata_store = std::make_unique<ImageMetadataStoreLevelDB>(
+      leveldb_proto::ProtoDatabaseProviderFactory::GetForKey(key, prefs),
       cache_path, task_runner, clock);
   auto data_store =
       std::make_unique<ImageDataStoreDisk>(cache_path, task_runner);
diff --git a/chrome/browser/page_load_metrics/observers/data_saver_site_breakdown_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/data_saver_site_breakdown_metrics_observer.cc
index e7a055f..4b862f1 100644
--- a/chrome/browser/page_load_metrics/observers/data_saver_site_breakdown_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/data_saver_site_breakdown_metrics_observer.cc
@@ -28,8 +28,6 @@
     content::NavigationHandle* navigation_handle,
     ukm::SourceId source_id) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!data_reduction_proxy::params::IsDataSaverSiteBreakdownUsingPLMEnabled())
-    return STOP_OBSERVING;
 
   // This BrowserContext is valid for the lifetime of
   // DataReductionProxyMetricsObserver. BrowserContext is always valid and
diff --git a/chrome/browser/page_load_metrics/observers/data_saver_site_breakdown_metrics_observer_browsertest.cc b/chrome/browser/page_load_metrics/observers/data_saver_site_breakdown_metrics_observer_browsertest.cc
index f37e0d1..4e95d52 100644
--- a/chrome/browser/page_load_metrics/observers/data_saver_site_breakdown_metrics_observer_browsertest.cc
+++ b/chrome/browser/page_load_metrics/observers/data_saver_site_breakdown_metrics_observer_browsertest.cc
@@ -60,11 +60,7 @@
  protected:
   void SetUp() override {
     scoped_feature_list_.InitWithFeatures(
-        {data_reduction_proxy::features::
-             kDataSaverSiteBreakdownUsingPageLoadMetrics,
-         previews::features::kClientLoFi, features::kLazyImageLoading},
-        {});
-
+        {previews::features::kClientLoFi, features::kLazyImageLoading}, {});
     InProcessBrowserTest::SetUp();
   }
 
diff --git a/chrome/browser/resource_coordinator/lifecycle_unit.cc b/chrome/browser/resource_coordinator/lifecycle_unit.cc
index ea80def9..2de2ff1 100644
--- a/chrome/browser/resource_coordinator/lifecycle_unit.cc
+++ b/chrome/browser/resource_coordinator/lifecycle_unit.cc
@@ -17,12 +17,8 @@
 LifecycleUnit::SortKey::SortKey(const SortKey& other) = default;
 
 bool LifecycleUnit::SortKey::operator<(const SortKey& other) const {
-  if (score.has_value()) {
-    DCHECK(other.score.has_value());
-    if (score.value() != other.score.value())
-      return score.value() < other.score.value();
-  }
-
+  if (score != other.score)
+    return score < other.score;
   return last_focused_time < other.last_focused_time;
 }
 
diff --git a/chrome/browser/resource_coordinator/lifecycle_unit.h b/chrome/browser/resource_coordinator/lifecycle_unit.h
index 8ed3034..9fb0314 100644
--- a/chrome/browser/resource_coordinator/lifecycle_unit.h
+++ b/chrome/browser/resource_coordinator/lifecycle_unit.h
@@ -60,7 +60,7 @@
     // Abstract importance score calculated by the Tab Ranker where a higher
     // score suggests the tab is more likely to be reactivated.
     // kMaxScore if the LifecycleUnit is currently focused.
-    base::Optional<float> score;
+    float score = kMaxScore;
 
     // Last time at which the LifecycleUnit was focused. base::TimeTicks::Max()
     // if the LifecycleUnit is currently focused.
diff --git a/chrome/browser/resource_coordinator/tab_manager_delegate_chromeos.cc b/chrome/browser/resource_coordinator/tab_manager_delegate_chromeos.cc
index c7c4e15..3ef89aa 100644
--- a/chrome/browser/resource_coordinator/tab_manager_delegate_chromeos.cc
+++ b/chrome/browser/resource_coordinator/tab_manager_delegate_chromeos.cc
@@ -82,11 +82,6 @@
     LOG(WARNING) << "Set OOM score: " << output;
 }
 
-bool IsNewProcessTypesEnabled() {
-  return base::FeatureList::IsEnabled(features::kNewProcessTypes) &&
-         !base::FeatureList::IsEnabled(features::kTabRanker);
-}
-
 }  // namespace
 
 // static
@@ -98,14 +93,6 @@
       return os << "FOCUSED_TAB";
     case ProcessType::FOCUSED_APP:
       return os << "FOCUSED_APP";
-    case ProcessType::IMPORTANT_APP:
-      return os << "IMPORTANT_APP";
-    case ProcessType::BACKGROUND_APP:
-      return os << "BACKGROUND_APP";
-    case ProcessType::BACKGROUND_TAB:
-      return os << "BACKGROUND_TAB";
-    case ProcessType::PROTECTED_BACKGROUND_TAB:
-      return os << "PROTECTED_BACKGROUND_TAB";
     case ProcessType::UNKNOWN_TYPE:
       return os << "UNKNOWN_TYPE";
     case ProcessType::BACKGROUND:
@@ -138,39 +125,13 @@
     const TabManagerDelegate::Candidate& rhs) const {
   if (process_type() != rhs.process_type())
     return process_type() < rhs.process_type();
-  if (app() && rhs.app())
-    return *app() < *rhs.app();
-  if (lifecycle_unit() && rhs.lifecycle_unit())
-    return lifecycle_unit_sort_key_ > rhs.lifecycle_unit_sort_key_;
-  // When NewProcessTypes feature is turned off and using old ProcessType
-  // categories, tabs and apps are in separate categories so this is an
-  // impossible case. Otherwise, tabs and apps are compared using last active
-  // time.
-  if ((lifecycle_unit() && rhs.app()) || (app() && rhs.lifecycle_unit()))
-    return GetLastActiveTime() < rhs.GetLastActiveTime();
-
-  NOTREACHED() << "Undefined comparison between apps and tabs: process_type="
-               << process_type();
-  return app();
-}
-
-TimeTicks TabManagerDelegate::Candidate::GetLastActiveTime() const {
-  if (app())
-    return TimeTicks::FromUptimeMillis(app()->last_activity_time());
-  if (lifecycle_unit())
-    return lifecycle_unit()->GetLastFocusedTime();
-  return TimeTicks();
+  return lifecycle_unit_sort_key_ > rhs.lifecycle_unit_sort_key_;
 }
 
 ProcessType TabManagerDelegate::Candidate::GetProcessTypeInternal() const {
-  const bool use_new_proc_types = IsNewProcessTypesEnabled();
   if (app()) {
     if (app()->is_focused())
       return ProcessType::FOCUSED_APP;
-    if (!use_new_proc_types) {
-      return app()->IsImportant() ? ProcessType::IMPORTANT_APP
-                                  : ProcessType::BACKGROUND_APP;
-    }
     if (app()->IsBackgroundProtected())
       return ProcessType::PROTECTED_BACKGROUND;
     if (app()->IsCached())
@@ -184,11 +145,9 @@
     if (!lifecycle_unit()->CanDiscard(
             ::mojom::LifecycleUnitDiscardReason::PROACTIVE,
             &decision_details)) {
-      return use_new_proc_types ? ProcessType::PROTECTED_BACKGROUND
-                                : ProcessType::PROTECTED_BACKGROUND_TAB;
+      return ProcessType::PROTECTED_BACKGROUND;
     }
-    return use_new_proc_types ? ProcessType::BACKGROUND
-                              : ProcessType::BACKGROUND_TAB;
+    return ProcessType::BACKGROUND;
   }
   return ProcessType::UNKNOWN_TYPE;
 }
@@ -633,20 +592,14 @@
 
     const ProcessType process_type = it->process_type();
 
-    // Never kill selected tab, foreground app, and important apps regardless of
-    // whether they're in the active window. Since the user experience would be
-    // bad.
+    // Never kill selected tab and foreground app regardless of whether they're
+    // in the active window. Since the user experience would be bad.
     if (it->app()) {
       if (process_type == ProcessType::FOCUSED_APP) {
         MEMORY_LOG(ERROR) << "Skipped killing focused app "
                           << it->app()->process_name();
         continue;
       }
-      if (process_type == ProcessType::IMPORTANT_APP) {
-        MEMORY_LOG(ERROR) << "Skipped killing important app "
-                          << it->app()->process_name();
-        continue;
-      }
       if (IsRecentlyKilledArcProcess(it->app()->process_name(), now)) {
         MEMORY_LOG(ERROR) << "Avoided killing " << it->app()->process_name()
                           << " too often";
@@ -745,12 +698,11 @@
   int range_middle =
       (chrome::kLowestRendererOomScore + chrome::kHighestRendererOomScore) / 2;
 
-  // Find some pivot point. For now (roughly) apps are in the first half and
-  // tabs are in the second half.
+  // Find some pivot point. FOCUSED_TAB, FOCUSED_APP, and PROTECTED_BACKGROUND
+  // processes are in the first half and BACKGROUND and CACHED_APP processes
+  // are in the second half.
   auto lower_priority_part = candidates.end();
-  bool use_new_proc_types = IsNewProcessTypesEnabled();
-  ProcessType pivot_type = use_new_proc_types ? ProcessType::BACKGROUND
-                                              : ProcessType::BACKGROUND_TAB;
+  ProcessType pivot_type = ProcessType::BACKGROUND;
   for (auto it = candidates.begin(); it != candidates.end(); ++it) {
     if (it->process_type() >= pivot_type) {
       lower_priority_part = it;
diff --git a/chrome/browser/resource_coordinator/tab_manager_delegate_chromeos.h b/chrome/browser/resource_coordinator/tab_manager_delegate_chromeos.h
index 7715a81c..47a655f 100644
--- a/chrome/browser/resource_coordinator/tab_manager_delegate_chromeos.h
+++ b/chrome/browser/resource_coordinator/tab_manager_delegate_chromeos.h
@@ -42,27 +42,14 @@
   FOCUSED_TAB = 1,
   FOCUSED_APP = 2,
 
-  // Important apps are protected in two ways: 1) Chrome never kills them, and
-  // 2) the kernel is still allowed to kill them, but their OOM adjustment
-  // scores are better than BACKGROUND_TABs and BACKGROUND_APPs.
-  IMPORTANT_APP = 3,
-
-  BACKGROUND_APP = 4,
-  PROTECTED_BACKGROUND_TAB = 5,
-  BACKGROUND_TAB = 6,
-
-  // PROTECTED_BACKGROUND, BACKGROUND, and CACHED_APP are newer types
-  // that are used instead of the previous 4 types on systems where the
-  // TabRanker experiment is disabled. Processes previously in IMPORTANT_APP
-  // and PROTECTED_BACKGROUND_TAB are now in PROTECTED_BACKGROUND, processes
-  // previously in either BACKGROUND_APP or BACKGROUND_TAB are now BACKGROUND.
-  // CACHED_APP marks Android processes which are cached or empty.
-  // Currently these types are only used on systems where the NewProcessTypes
-  // feature is enabled.
-  PROTECTED_BACKGROUND = 7,
-  BACKGROUND = 8,
-  CACHED_APP = 9,
-  UNKNOWN_TYPE = 10,
+  // PROTECTED_BACKGROUND processes are those that are in the background but
+  // are more important than BACKGROUND processes because they may be disruptive
+  // to the user if killed. CACHED_APP marks Android processes which are cached
+  // or empty.
+  PROTECTED_BACKGROUND = 3,
+  BACKGROUND = 4,
+  CACHED_APP = 5,
+  UNKNOWN_TYPE = 6,
 };
 
 // The Chrome OS TabManagerDelegate is responsible for keeping the
@@ -123,7 +110,7 @@
   FRIEND_TEST_ALL_PREFIXES(TabManagerDelegateTest,
                            CandidatesSortedWithFocusedAppAndTab);
   FRIEND_TEST_ALL_PREFIXES(TabManagerDelegateTest,
-                           CandidatesSortedWithNewProcessTypes);
+                           CandidatesSortedWithTabRanker);
   FRIEND_TEST_ALL_PREFIXES(TabManagerDelegateTest,
                            DoNotKillRecentlyKilledArcProcesses);
   FRIEND_TEST_ALL_PREFIXES(TabManagerDelegateTest, IsRecentlyKilledArcProcess);
@@ -244,20 +231,44 @@
         lifecycle_unit_sort_key_(lifecycle_unit_->GetSortKey()) {
     DCHECK(lifecycle_unit_);
   }
-  explicit Candidate(const arc::ArcProcess* app) : app_(app) { DCHECK(app_); }
+
+  // Candidates are sorted by a pair of sort keys <TabRanker score,
+  // last_activity_time>. Apps are not scored by TabRanker, so their score
+  // is set to kMaxScore. When TabRanker is off, tabs also have a score of
+  // kMaxScore so this has the effect of sorting by last_activity_time only.
+  // But if TabRanker is on, kMaxScore guarantees all apps are sorted before
+  // tabs.
+  explicit Candidate(const arc::ArcProcess* app)
+      : lifecycle_unit_sort_key_(
+            LifecycleUnit::SortKey::kMaxScore,
+            base::TimeTicks::FromUptimeMillis(app->last_activity_time())),
+        app_(app) {
+    DCHECK(app_);
+  }
 
   // Move-only class.
   Candidate(Candidate&&) = default;
   Candidate& operator=(Candidate&& other);
 
-  // Higher priority first.
+  // Candidates are sorted higher priority first. They are first sorted into
+  // their respective ProcessTypes.
+  // LifecycleUnit::SortKey is used to compare processes within a ProcessType,
+  // using a combination of TabRanker reactivation score (for tabs only) and
+  // last focused time for all processes. When TabRanker is disabled, tabs are
+  // given a default score of kMaxScore. An ArcProcess (app) is always assigned
+  // kMaxScore. This means that when TabRanker is off, all processes have
+  // kMaxScore and are then compared by last focused time.
+  // When TabRanker is on, tabs have their own defined order so we can't compare
+  // apps to tabs, since we wouldn't have a comparator to satisfy transitivity.
+  // In this case, ARC processes are sorted before (higher priority than) tabs
+  // and compared by last focused time, while tabs are compared by their
+  // reactivation score.
   bool operator<(const Candidate& rhs) const;
 
   LifecycleUnit* lifecycle_unit() { return lifecycle_unit_; }
   const LifecycleUnit* lifecycle_unit() const { return lifecycle_unit_; }
   const arc::ArcProcess* app() const { return app_; }
   ProcessType process_type() const { return process_type_; }
-  base::TimeTicks GetLastActiveTime() const;
 
  private:
   // Derive process type for this candidate. Used to initialize |process_type_|.
diff --git a/chrome/browser/resource_coordinator/tab_manager_delegate_chromeos_unittest.cc b/chrome/browser/resource_coordinator/tab_manager_delegate_chromeos_unittest.cc
index 12baa93..290bda2 100644
--- a/chrome/browser/resource_coordinator/tab_manager_delegate_chromeos_unittest.cc
+++ b/chrome/browser/resource_coordinator/tab_manager_delegate_chromeos_unittest.cc
@@ -41,11 +41,11 @@
   arc_processes.emplace_back(1, 10, "focused", arc::mojom::ProcessState::TOP,
                              kIsFocused, 100);
   arc_processes.emplace_back(2, 20, "visible1", arc::mojom::ProcessState::TOP,
-                             kNotFocused, 200);
+                             kNotFocused, 6000);
   arc_processes.emplace_back(
-      3, 30, "service", arc::mojom::ProcessState::SERVICE, kNotFocused, 500);
+      3, 30, "service", arc::mojom::ProcessState::SERVICE, kNotFocused, 2001);
   arc_processes.emplace_back(4, 40, "visible2", arc::mojom::ProcessState::TOP,
-                             kNotFocused, 150);
+                             kNotFocused, 5001);
 
   TestLifecycleUnit focused_lifecycle_unit(base::TimeTicks::Max());
   TestLifecycleUnit protected_lifecycle_unit(
@@ -77,11 +77,11 @@
   // visible app 2, last_activity_time less than visible app 1.
   ASSERT_TRUE(candidates[3].app());
   EXPECT_EQ("visible2", candidates[3].app()->process_name());
+  EXPECT_EQ(candidates[4].lifecycle_unit(), &protected_lifecycle_unit);
   // background service.
-  ASSERT_TRUE(candidates[4].app());
-  EXPECT_EQ("service", candidates[4].app()->process_name());
+  ASSERT_TRUE(candidates[5].app());
+  EXPECT_EQ("service", candidates[5].app()->process_name());
   // protected LifecycleUnit
-  EXPECT_EQ(candidates[5].lifecycle_unit(), &protected_lifecycle_unit);
   // non-focused LifecycleUnits, sorted by last focused time.
   EXPECT_EQ(candidates[6].lifecycle_unit(), &other_non_focused_lifecycle_unit);
   EXPECT_EQ(candidates[7].lifecycle_unit(), &non_focused_lifecycle_unit);
@@ -109,28 +109,29 @@
   EXPECT_EQ("focused", candidates[1].app()->process_name());
 }
 
-TEST_F(TabManagerDelegateTest, CandidatesSortedWithNewProcessTypes) {
+// Test to make sure old process types are active when TabRanker experiment
+// is turned on.
+TEST_F(TabManagerDelegateTest, CandidatesSortedWithTabRanker) {
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatures({features::kNewProcessTypes},
-                                {features::kTabRanker});
+  feature_list.InitWithFeatures({features::kTabRanker}, {});
   std::vector<arc::ArcProcess> arc_processes;
   arc_processes.emplace_back(1, 10, "focused", arc::mojom::ProcessState::TOP,
-                             kIsFocused, 100);
+                             kIsFocused, 99);
   arc_processes.emplace_back(2, 20, "visible1", arc::mojom::ProcessState::TOP,
-                             kNotFocused, 200);
+                             kNotFocused, 89);
   arc_processes.emplace_back(
-      3, 30, "service", arc::mojom::ProcessState::SERVICE, kNotFocused, 500);
-  arc_processes.emplace_back(4, 40, "cached",
-                             arc::mojom::ProcessState::CACHED_EMPTY,
-                             kNotFocused, 150);
+      3, 30, "service", arc::mojom::ProcessState::SERVICE, kNotFocused, 79);
 
-  TestLifecycleUnit focused_tab(base::TimeTicks::Max());
-  TestLifecycleUnit protected_tab(
-      base::TimeTicks() + base::TimeDelta::FromSeconds(1), 0, false);
-  TestLifecycleUnit background_tab(base::TimeTicks() +
-                                   base::TimeDelta::FromSeconds(5));
-  LifecycleUnitVector lifecycle_units{&focused_tab, &protected_tab,
-                                      &background_tab};
+  TestLifecycleUnit tab1(
+      base::TimeTicks() + base::TimeDelta::FromMilliseconds(100), 4);
+  tab1.SetSortKey(LifecycleUnit::SortKey(10, tab1.GetLastFocusedTime()));
+  TestLifecycleUnit tab2(
+      base::TimeTicks() + base::TimeDelta::FromMilliseconds(90), 5);
+  tab2.SetSortKey(LifecycleUnit::SortKey(20, tab2.GetLastFocusedTime()));
+  TestLifecycleUnit tab3(
+      base::TimeTicks() + base::TimeDelta::FromMilliseconds(80), 6);
+  tab3.SetSortKey(LifecycleUnit::SortKey(30, tab3.GetLastFocusedTime()));
+  LifecycleUnitVector lifecycle_units{&tab1, &tab2, &tab3};
 
   TabManagerDelegate::OptionalArcProcessList opt_arc_processes(
       std::move(arc_processes));
@@ -138,25 +139,23 @@
   candidates = TabManagerDelegate::GetSortedCandidates(lifecycle_units,
                                                        opt_arc_processes);
 
-  ASSERT_EQ(7U, candidates.size());
-  EXPECT_EQ(&focused_tab, candidates[0].lifecycle_unit());
-  EXPECT_EQ(ProcessType::FOCUSED_TAB, candidates[0].process_type());
+  ASSERT_EQ(6U, candidates.size());
+  ASSERT_TRUE(candidates[0].app());
+  EXPECT_EQ("focused", candidates[0].app()->process_name());
+  EXPECT_EQ(ProcessType::FOCUSED_APP, candidates[0].process_type());
   ASSERT_TRUE(candidates[1].app());
-  EXPECT_EQ("focused", candidates[1].app()->process_name());
-  EXPECT_EQ(ProcessType::FOCUSED_APP, candidates[1].process_type());
+  EXPECT_EQ("visible1", candidates[1].app()->process_name());
+  EXPECT_EQ(ProcessType::PROTECTED_BACKGROUND, candidates[1].process_type());
   ASSERT_TRUE(candidates[2].app());
-  EXPECT_EQ("visible1", candidates[2].app()->process_name());
-  EXPECT_EQ(ProcessType::PROTECTED_BACKGROUND, candidates[2].process_type());
-  EXPECT_EQ(&protected_tab, candidates[3].lifecycle_unit());
-  EXPECT_EQ(ProcessType::PROTECTED_BACKGROUND, candidates[3].process_type());
-  ASSERT_TRUE(candidates[4].app());
-  EXPECT_EQ("service", candidates[4].app()->process_name());
+  EXPECT_EQ("service", candidates[2].app()->process_name());
+  EXPECT_EQ(ProcessType::BACKGROUND, candidates[2].process_type());
+
+  EXPECT_EQ(&tab3, candidates[3].lifecycle_unit());
+  EXPECT_EQ(ProcessType::BACKGROUND, candidates[3].process_type());
+  EXPECT_EQ(&tab2, candidates[4].lifecycle_unit());
   EXPECT_EQ(ProcessType::BACKGROUND, candidates[4].process_type());
-  EXPECT_EQ(&background_tab, candidates[5].lifecycle_unit());
+  EXPECT_EQ(&tab1, candidates[5].lifecycle_unit());
   EXPECT_EQ(ProcessType::BACKGROUND, candidates[5].process_type());
-  ASSERT_TRUE(candidates[6].app());
-  EXPECT_EQ("cached", candidates[6].app()->process_name());
-  EXPECT_EQ(ProcessType::CACHED_APP, candidates[6].process_type());
 }
 
 class MockTabManagerDelegate : public TabManagerDelegate {
@@ -288,16 +287,16 @@
 
   // Sorted order (by GetSortedCandidates):
   // app "focused"       pid: 10
-  // app "persistent"    pid: 50
-  // app "persistent_ui" pid: 60
   // app "visible1"      pid: 20
   // app "visible2"      pid: 40
-  // app "service"       pid: 30
   // tab3                pid: 12
   // tab4                pid: 12
   // tab1                pid: 11
   // tab5                pid: 12
   // tab2                pid: 11
+  // app "persistent"    pid: 50
+  // app "persistent_ui" pid: 60
+  // app "service"       pid: 30
   tab_manager_delegate.AdjustOomPrioritiesImpl(std::move(arc_processes));
   auto& oom_score_map = tab_manager_delegate.oom_score_map_;
 
@@ -312,13 +311,13 @@
 
   // Higher priority part.
   EXPECT_EQ(300, oom_score_map[10]);
-  EXPECT_EQ(388, oom_score_map[20]);
-  EXPECT_EQ(475, oom_score_map[40]);
-  EXPECT_EQ(563, oom_score_map[30]);
+  EXPECT_EQ(417, oom_score_map[20]);
+  EXPECT_EQ(533, oom_score_map[40]);
 
   // Lower priority part.
   EXPECT_EQ(650, oom_score_map[12]);
-  EXPECT_EQ(720, oom_score_map[11]);
+  EXPECT_EQ(708, oom_score_map[11]);
+  EXPECT_EQ(767, oom_score_map[30]);
 }
 
 TEST_F(TabManagerDelegateTest, IsRecentlyKilledArcProcess) {
@@ -435,26 +434,28 @@
 
   // Sorted order (by GetSortedCandidates):
   // app "focused"     pid: 10  nspid 1
-  // app "persistent"  pid: 60  nspid 6
+  // app "not-visible" pid: 50  nspid 5
   // app "visible1"    pid: 20  nspid 2
   // app "visible2"    pid: 40  nspid 4
-  // app "not-visible" pid: 50  nspid 5
-  // app "service"     pid: 30  nspid 3
   // tab3              pid: 12  id 3
   // tab4              pid: 12  id 4
   // tab1              pid: 11  id 1
   // tab5              pid: 12  id 5
   // tab2              pid: 11  id 2
+  // app "service"     pid: 30  nspid 3
+  // app "persistent"  pid: 60  nspid 6
   memory_stat->SetTargetMemoryToFreeKB(250000);
+
+  // TODO(wvk) For now the estimation of freed memory for tabs is 0, but we
+  // probably want to fix it later by implementing
+  // TestLifecycleUnit::GetEstimatedMemoryFreedOnDiscardKB.
   // Entities to be killed.
-  memory_stat->SetProcessPss(11, 50000);
-  memory_stat->SetProcessPss(12, 30000);
-  memory_stat->SetProcessPss(30, 10000);
-  memory_stat->SetProcessPss(50, 60000);
-  // Should not be used.
-  memory_stat->SetProcessPss(60, 500000);
-  memory_stat->SetProcessPss(40, 50000);
   memory_stat->SetProcessPss(20, 30000);
+  memory_stat->SetProcessPss(30, 10000);
+  memory_stat->SetProcessPss(40, 50000);
+  memory_stat->SetProcessPss(50, 60000);
+  // Should not be killed.
+  memory_stat->SetProcessPss(60, 500000);
   memory_stat->SetProcessPss(10, 100000);
 
   tab_manager_delegate.LowMemoryKillImpl(
@@ -465,10 +466,13 @@
   auto killed_arc_processes = tab_manager_delegate.GetKilledArcProcesses();
   auto killed_tabs = tab_manager_delegate.GetKilledTabs();
 
-  // Killed apps and their nspid.
-  ASSERT_EQ(2U, killed_arc_processes.size());
+  // Killed apps and their nspid. All of the apps (except the focused app
+  // and the app marked as persistent) should have been killed.
+  ASSERT_EQ(4U, killed_arc_processes.size());
   EXPECT_EQ(3, killed_arc_processes[0]);
-  EXPECT_EQ(5, killed_arc_processes[1]);
+  EXPECT_EQ(4, killed_arc_processes[1]);
+  EXPECT_EQ(2, killed_arc_processes[2]);
+  EXPECT_EQ(5, killed_arc_processes[3]);
   // Killed tabs and their content id.
   // Note that process with pid 11 is counted twice and pid 12 is counted 3
   // times. But so far I don't have a good way to estimate the memory freed
@@ -483,9 +487,11 @@
   // Check that killed apps are in the map.
   const TabManagerDelegate::KilledArcProcessesMap& processes_map =
       tab_manager_delegate.recently_killed_arc_processes_;
-  EXPECT_EQ(2U, processes_map.size());
+  EXPECT_EQ(4U, processes_map.size());
   EXPECT_EQ(1U, processes_map.count("service"));
   EXPECT_EQ(1U, processes_map.count("not-visible"));
+  EXPECT_EQ(1U, processes_map.count("visible1"));
+  EXPECT_EQ(1U, processes_map.count("visible2"));
 }
 
 }  // namespace resource_coordinator
diff --git a/chrome/browser/resource_coordinator/tab_manager_features.cc b/chrome/browser/resource_coordinator/tab_manager_features.cc
index 0e1dd21..4293249 100644
--- a/chrome/browser/resource_coordinator/tab_manager_features.cc
+++ b/chrome/browser/resource_coordinator/tab_manager_features.cc
@@ -54,13 +54,6 @@
 // on last focused time.
 const base::Feature kTabRanker{"TabRanker", base::FEATURE_DISABLED_BY_DEFAULT};
 
-#if defined(OS_CHROMEOS)
-// On ChromeOS, enables using new ProcessType enums that combine apps and tabs
-// in the same categories.
-const base::Feature kNewProcessTypes{
-  "NewProcessTypes", base::FEATURE_DISABLED_BY_DEFAULT};
-#endif // defined(OS_CHROMEOS)
-
 }  // namespace features
 
 namespace resource_coordinator {
diff --git a/chrome/browser/resource_coordinator/tab_manager_features.h b/chrome/browser/resource_coordinator/tab_manager_features.h
index 0599ef4..45748f06 100644
--- a/chrome/browser/resource_coordinator/tab_manager_features.h
+++ b/chrome/browser/resource_coordinator/tab_manager_features.h
@@ -20,9 +20,6 @@
 extern const base::Feature kStaggeredBackgroundTabOpening;
 extern const base::Feature kStaggeredBackgroundTabOpeningExperiment;
 extern const base::Feature kTabRanker;
-#if defined(OS_CHROMEOS)
-extern const base::Feature kNewProcessTypes;
-#endif // defined(OS_CHROMEOS)
 
 }  // namespace features
 
diff --git a/chrome/browser/resource_coordinator/tab_metrics_logger_unittest.cc b/chrome/browser/resource_coordinator/tab_metrics_logger_unittest.cc
index 595b74c..b4b2d0a 100644
--- a/chrome/browser/resource_coordinator/tab_metrics_logger_unittest.cc
+++ b/chrome/browser/resource_coordinator/tab_metrics_logger_unittest.cc
@@ -179,7 +179,7 @@
 // Tests navigation_entry_count.
 TEST_F(TabMetricsLoggerTest, GetNavigationEntryCount) {
   EXPECT_EQ(CurrentTabFeatures().navigation_entry_count, 1);
-  tab_activity_simulator_.Navigate(web_contents_, GURL(kChromiumUrl),
+  tab_activity_simulator_.Navigate(web_contents_, GURL(kExampleUrl),
                                    pg_metrics_.page_transition);
   EXPECT_EQ(CurrentTabFeatures().navigation_entry_count, 2);
   tab_activity_simulator_.Navigate(web_contents_, GURL(kChromiumUrl),
diff --git a/chrome/browser/resource_coordinator/test_lifecycle_unit.cc b/chrome/browser/resource_coordinator/test_lifecycle_unit.cc
index 22885b7..51cdb4d 100644
--- a/chrome/browser/resource_coordinator/test_lifecycle_unit.cc
+++ b/chrome/browser/resource_coordinator/test_lifecycle_unit.cc
@@ -15,6 +15,7 @@
     : LifecycleUnitBase(nullptr, content::Visibility::VISIBLE, nullptr),
       last_focused_time_(last_focused_time),
       process_handle_(process_handle),
+      sort_key_(last_focused_time),
       can_discard_(can_discard) {}
 
 TestLifecycleUnit::TestLifecycleUnit(content::Visibility visibility,
@@ -45,7 +46,7 @@
 }
 
 LifecycleUnit::SortKey TestLifecycleUnit::GetSortKey() const {
-  return SortKey(last_focused_time_);
+  return sort_key_;
 }
 
 content::Visibility TestLifecycleUnit::GetVisibility() const {
diff --git a/chrome/browser/resource_coordinator/test_lifecycle_unit.h b/chrome/browser/resource_coordinator/test_lifecycle_unit.h
index cde4b206..f9357a9 100644
--- a/chrome/browser/resource_coordinator/test_lifecycle_unit.h
+++ b/chrome/browser/resource_coordinator/test_lifecycle_unit.h
@@ -29,6 +29,8 @@
     last_focused_time_ = last_focused_time;
   }
 
+  void SetSortKey(LifecycleUnit::SortKey sort_key) { sort_key_ = sort_key; }
+
   void SetTitle(base::StringPiece16 title) { title_ = title.as_string(); }
 
   // LifecycleUnit:
@@ -53,6 +55,7 @@
   base::string16 title_;
   base::TimeTicks last_focused_time_;
   base::ProcessHandle process_handle_;
+  LifecycleUnit::SortKey sort_key_;
   bool can_discard_ = true;
 
   DISALLOW_COPY_AND_ASSIGN(TestLifecycleUnit);
diff --git a/chrome/browser/resources/chromeos/BUILD.gn b/chrome/browser/resources/chromeos/BUILD.gn
index 88dd640..941dfca 100644
--- a/chrome/browser/resources/chromeos/BUILD.gn
+++ b/chrome/browser/resources/chromeos/BUILD.gn
@@ -8,6 +8,19 @@
 
 assert(is_chromeos, "Only Chrome OS resources in //c/b/resources//chromeos.")
 
+grit("cellular_setup_resources") {
+  source = "cellular_setup/cellular_setup_resources.grd"
+
+  defines = chrome_grit_defines
+  outputs = [
+    "grit/cellular_setup_resources.h",
+    "grit/cellular_setup_resources_map.cc",
+    "grit/cellular_setup_resources_map.h",
+    "cellular_setup_resources.pak",
+  ]
+  output_dir = "$root_gen_dir/chrome"
+}
+
 grit("multidevice_setup_resources") {
   source = "multidevice_setup/multidevice_setup_resources.grd"
 
diff --git a/chrome/browser/resources/chromeos/cellular_setup/OWNERS b/chrome/browser/resources/chromeos/cellular_setup/OWNERS
new file mode 100644
index 0000000..75e2f312
--- /dev/null
+++ b/chrome/browser/resources/chromeos/cellular_setup/OWNERS
@@ -0,0 +1,2 @@
+azeemarshad@chromium.org
+khorimoto@chromium.org
diff --git a/chrome/browser/resources/chromeos/cellular_setup/cellular_setup_dialog.html b/chrome/browser/resources/chromeos/cellular_setup/cellular_setup_dialog.html
new file mode 100644
index 0000000..8ec69bc
--- /dev/null
+++ b/chrome/browser/resources/chromeos/cellular_setup/cellular_setup_dialog.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<html dir="$i18n{textdirection}" lang="$i18n{language}">
+<head>
+  <meta charset="utf8">
+  <base href="chrome://cellular-setup">
+  <style>
+    html,
+    body {
+      display: flex;
+      height: 100%;
+      justify-content: center;
+      margin: 0;
+      overflow: hidden;
+      width: 100%;
+    }
+  </style>
+</head>
+<body>
+  <link rel="import" href="i18n_setup.html">
+  <!-- TODO(khorimoto): Replace button with actual content.-->
+  <button>$i18n{cancel}</button>
+</body>
+</html>
diff --git a/chrome/browser/resources/chromeos/cellular_setup/cellular_setup_resources.grd b/chrome/browser/resources/chromeos/cellular_setup/cellular_setup_resources.grd
new file mode 100644
index 0000000..319dbda
--- /dev/null
+++ b/chrome/browser/resources/chromeos/cellular_setup/cellular_setup_resources.grd
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+  <outputs>
+    <output filename="grit/cellular_setup_resources.h" type="rc_header">
+      <emit emit_type='prepend'></emit>
+    </output>
+    <output filename="grit/cellular_setup_resources_map.cc"
+            type="resource_file_map_source" />
+    <output filename="grit/cellular_setup_resources_map.h"
+            type="resource_map_header" />
+    <output filename="cellular_setup_resources.pak" type="data_package" />
+  </outputs>
+  <release seq="1">
+    <structures>
+      <structure name="IDR_CELLULAR_SETUP_I18N_SETUP_HTML"
+                 file="i18n_setup.html"
+                 type="chrome_html" />
+      <structure name="IDR_CELLULAR_SETUP_CELLULAR_SETUP_DIALOG_HTML"
+                 file="cellular_setup_dialog.html"
+                 flattenhtml="true"
+                 allowexternalscript="true"
+                 type="chrome_html" />
+    </structures>
+  </release>
+</grit>
diff --git a/chrome/browser/resources/chromeos/cellular_setup/i18n_setup.html b/chrome/browser/resources/chromeos/cellular_setup/i18n_setup.html
new file mode 100644
index 0000000..b1bc3de
--- /dev/null
+++ b/chrome/browser/resources/chromeos/cellular_setup/i18n_setup.html
@@ -0,0 +1,3 @@
+<link rel="import" href="chrome://resources/html/load_time_data.html">
+
+<script src="strings.js"></script>
diff --git a/chrome/browser/resources/chromeos/network_ui/network_ui.html b/chrome/browser/resources/chromeos/network_ui/network_ui.html
index c35eb276..29b8e18 100644
--- a/chrome/browser/resources/chromeos/network_ui/network_ui.html
+++ b/chrome/browser/resources/chromeos/network_ui/network_ui.html
@@ -15,12 +15,26 @@
   <script src="chrome://resources/js/util.js"></script>
   <script src="chrome://network/strings.js"></script>
   <script src="chrome://network/network_ui.js"></script>
+
+  <style>
+    #cellular-error-text {
+      color: red;
+    }
+  </style>
 </head>
 
 <body>
   <div>$i18n{autoRefreshText}</div>
   <span>$i18nRaw{deviceLogLinkText}</span>
 
+  <div>
+    <h2>$i18n{cellularActivationLabel}</h2>
+    <paper-button raised class="colored" id="cellular-activation-button">
+      $i18n{cellularActivationButtonText}
+    </paper-button>
+    <div id="cellular-error-text" hidden>$i18n{noCellularErrorText}</div>
+  </div>
+
   <h2>$i18n{globalPolicyLabel}</h2>
   <div id="global-policy"></div>
 
diff --git a/chrome/browser/resources/chromeos/network_ui/network_ui.js b/chrome/browser/resources/chromeos/network_ui/network_ui.js
index 067fe5b..06eb54d 100644
--- a/chrome/browser/resources/chromeos/network_ui/network_ui.js
+++ b/chrome/browser/resources/chromeos/network_ui/network_ui.js
@@ -400,6 +400,23 @@
   };
 
   /**
+   * Callback invoked by Chrome after a openCellularActivationUi call.
+   * @param {boolean} didOpenActivationUi Whether the activation UI was actually
+   *     opened. If this value is false, it means that no cellular network was
+   *     available to be activated.
+   */
+  var openCellularActivationUiResult = function(didOpenActivationUi) {
+    $('cellular-error-text').hidden = didOpenActivationUi;
+  };
+
+  /**
+   * Requests that the cellular activation UI be displayed.
+   */
+  var openCellularActivationUi = function() {
+    chrome.send('openCellularActivationUi');
+  };
+
+  /**
    * Requests an update of all network info.
    */
   var requestNetworks = function() {
@@ -446,6 +463,7 @@
       {customItemName: 'Add WiFi', polymerIcon: 'cr:add', customData: 'WiFi'},
       {customItemName: 'Add VPN', polymerIcon: 'cr:add', customData: 'VPN'}
     ];
+    $('cellular-activation-button').onclick = openCellularActivationUi;
     $('refresh').onclick = requestNetworks;
     setRefresh();
     requestNetworks();
@@ -458,6 +476,7 @@
 
   return {
     getShillNetworkPropertiesResult: getShillNetworkPropertiesResult,
-    getShillDevicePropertiesResult: getShillDevicePropertiesResult
+    getShillDevicePropertiesResult: getShillDevicePropertiesResult,
+    openCellularActivationUiResult: openCellularActivationUiResult
   };
 })();
diff --git a/chrome/browser/resources/discards/generate_graph_tab.py b/chrome/browser/resources/discards/generate_graph_tab.py
index 528e1d8..4205e36 100644
--- a/chrome/browser/resources/discards/generate_graph_tab.py
+++ b/chrome/browser/resources/discards/generate_graph_tab.py
@@ -30,7 +30,8 @@
   html_doc = html_template.substitute({'javascript_file': js_file_contents});
 
   # Construct the data: URL that contains the combined doc.
-  data_url = "data:text/html;base64,%s" % base64.b64encode(html_doc);
+  data_url = "data:text/html;base64,%s" % base64.b64encode(
+      html_doc.encode()).decode()
 
   # And finally stamp the the data URL into the output template.
   output = output_template.substitute({'data_url': data_url})
diff --git a/chrome/browser/resources/ntp4/apps_page.js b/chrome/browser/resources/ntp4/apps_page.js
index 2336547..32bb497 100644
--- a/chrome/browser/resources/ntp4/apps_page.js
+++ b/chrome/browser/resources/ntp4/apps_page.js
@@ -53,9 +53,7 @@
       menu.appendChild(cr.ui.MenuItem.createSeparator());
       this.launchRegularTab_ = this.appendMenuItem_('applaunchtyperegular');
       this.launchPinnedTab_ = this.appendMenuItem_('applaunchtypepinned');
-      if (loadTimeData.getBoolean('canHostedAppsOpenInWindows')) {
-        this.launchNewWindow_ = this.appendMenuItem_('applaunchtypewindow');
-      }
+      this.launchNewWindow_ = this.appendMenuItem_('applaunchtypewindow');
       this.launchFullscreen_ = this.appendMenuItem_('applaunchtypefullscreen');
 
       const self = this;
@@ -152,16 +150,11 @@
       this.forAllLaunchTypes_(function(launchTypeButton, id) {
         launchTypeButton.disabled = false;
         launchTypeButton.checked = app.appData.launch_type == id;
-        // There are three cases when a launch type is hidden:
+        // There are two cases when a launch type is hidden:
         //  1. if the launch type can't be changed.
-        //  2. canHostedAppsOpenInWindows is false and type is launchTypeWindow
-        //  3. enableNewBookmarkApps is true and type is anything except
-        //     launchTypeWindow
+        //  2. type is anything except launchTypeWindow
         launchTypeButton.hidden = !app.appData.mayChangeLaunchType ||
-            (!loadTimeData.getBoolean('canHostedAppsOpenInWindows') &&
-             launchTypeButton == launchTypeWindow) ||
-            (loadTimeData.getBoolean('enableNewBookmarkApps') &&
-             launchTypeButton != launchTypeWindow);
+            launchTypeButton != launchTypeWindow;
         if (!launchTypeButton.hidden) {
           hasLaunchType = true;
         }
@@ -201,11 +194,9 @@
       let targetLaunchType = pressed;
       // When bookmark apps are enabled, hosted apps can only toggle between
       // open as window and open as tab.
-      if (loadTimeData.getBoolean('enableNewBookmarkApps')) {
-        targetLaunchType = this.launchNewWindow_.checked ?
-            this.launchRegularTab_ :
-            this.launchNewWindow_;
-      }
+      targetLaunchType = this.launchNewWindow_.checked ?
+          this.launchRegularTab_ :
+          this.launchNewWindow_;
       this.forAllLaunchTypes_(function(launchTypeButton, id) {
         if (launchTypeButton == targetLaunchType) {
           chrome.send('setLaunchType', [app.appId, id]);
diff --git a/chrome/browser/resources/settings/autofill_page/payments_section.html b/chrome/browser/resources/settings/autofill_page/payments_section.html
index 6113b3f..7e53d968 100644
--- a/chrome/browser/resources/settings/autofill_page/payments_section.html
+++ b/chrome/browser/resources/settings/autofill_page/payments_section.html
@@ -69,7 +69,7 @@
       </paper-button>
     </div>
     <div class="settings-box two-line" id="migrateCreditCards"
-        hidden$="[[!checkIfMigratable_(syncStatus, creditCards,
+        hidden$="[[!checkIfMigratable_(creditCards,
             prefs.autofill.credit_card_enabled.value)]]"
         on-click="onMigrateCreditCardsClick_" actionable>
       <div class="start">
diff --git a/chrome/browser/resources/settings/autofill_page/payments_section.js b/chrome/browser/resources/settings/autofill_page/payments_section.js
index 0eca2bcf..03d51d2 100644
--- a/chrome/browser/resources/settings/autofill_page/payments_section.js
+++ b/chrome/browser/resources/settings/autofill_page/payments_section.js
@@ -120,7 +120,10 @@
      * An array of all saved credit cards.
      * @type {!Array<!PaymentsManager.CreditCardEntry>}
      */
-    creditCards: Array,
+    creditCards: {
+      type: Array,
+      value: () => [],
+    },
 
     /**
      * The model for any credit card related action menus or dialogs.
@@ -135,12 +138,6 @@
     migratableCreditCardsInfo_: String,
 
     /**
-     * The current sync status, supplied by SyncBrowserProxy.
-     * @type {?settings.SyncStatus}
-     */
-    syncStatus: Object,
-
-    /**
      * Whether migration local card on settings page is enabled.
      * @private
      */
@@ -151,66 +148,6 @@
       },
       readOnly: true,
     },
-
-    /**
-     * Whether user has a Google Payments account.
-     * @private
-     */
-    hasGooglePaymentsAccount_: {
-      type: Boolean,
-      value: function() {
-        return loadTimeData.getBoolean('hasGooglePaymentsAccount');
-      },
-      readOnly: true,
-    },
-
-    /**
-     * Whether Autofill Upstream is enabled.
-     * @private
-     */
-    upstreamEnabled_: {
-      type: Boolean,
-      value: function() {
-        return loadTimeData.getBoolean('upstreamEnabled');
-      },
-      readOnly: true,
-    },
-
-    /**
-     * Whether the user has a secondary sync passphrase.
-     * @private
-     */
-    isUsingSecondaryPassphrase_: {
-      type: Boolean,
-      value: function() {
-        return loadTimeData.getBoolean('isUsingSecondaryPassphrase');
-      },
-      readOnly: true,
-    },
-
-    /**
-     * Whether the upload-to-google state is active.
-     * @private
-     */
-    uploadToGoogleActive_: {
-      type: Boolean,
-      value: function() {
-        return loadTimeData.getBoolean('uploadToGoogleActive');
-      },
-      readOnly: true,
-    },
-
-    /**
-     * Whether the domain of the user's email is allowed.
-     * @private
-     */
-    userEmailDomainAllowed_: {
-      type: Boolean,
-      value: function() {
-        return loadTimeData.getBoolean('userEmailDomainAllowed');
-      },
-      readOnly: true,
-    },
   },
 
   listeners: {
@@ -238,9 +175,6 @@
    */
   setCreditCardsListener_: null,
 
-  /** @private {?settings.SyncBrowserProxy} */
-  syncBrowserProxy_: null,
-
   /** @override */
   attached: function() {
     // Create listener function.
@@ -262,12 +196,6 @@
     this.paymentsManager_.addCreditCardListChangedListener(
         setCreditCardsListener);
 
-    this.syncBrowserProxy_ = settings.SyncBrowserProxyImpl.getInstance();
-    this.syncBrowserProxy_.getSyncStatus().then(
-        this.handleSyncStatus_.bind(this));
-    this.addWebUIListener(
-        'sync-status-changed', this.handleSyncStatus_.bind(this));
-
     // Record that the user opened the payments settings.
     chrome.metricsPrivate.recordUserAction('AutofillCreditCardsViewed');
   },
@@ -383,73 +311,22 @@
   },
 
   /**
-   * Handler for when the sync state is pushed from the browser.
-   * @param {?settings.SyncStatus} syncStatus
-   * @private
-   */
-  handleSyncStatus_: function(syncStatus) {
-    this.syncStatus = syncStatus;
-  },
-
-  /**
-   * @param {!settings.SyncStatus} syncStatus
    * @param {!Array<!PaymentsManager.CreditCardEntry>} creditCards
    * @param {boolean} creditCardEnabled
-   * @return {boolean} Whether to show the migration button. True iff at
-   *     least
-   * one valid local card, enable migration, signed-in & synced and credit
-   * card pref enabled.
+   * @return {boolean} Whether to show the migration button.
    * @private
    */
-  checkIfMigratable_: function(syncStatus, creditCards, creditCardEnabled) {
-    if (syncStatus == undefined) {
-      return false;
-    }
-
-    // If user not enable migration experimental flag, return false.
+  checkIfMigratable_: function(creditCards, creditCardEnabled) {
+    // If migration prerequisites are not met, return false.
     if (!this.migrationEnabled_) {
       return false;
     }
 
-    // If user does not have Google Payments Account, return false.
-    if (!this.hasGooglePaymentsAccount_) {
-      return false;
-    }
-
-    // If the Autofill Upstream feature is not enabled, return false.
-    if (!this.upstreamEnabled_) {
-      return false;
-    }
-
-    // Don't offer upload if user has a secondary passphrase. Users who have
-    // enabled a passphrase have chosen to not make their sync information
-    // accessible to Google. Since upload makes credit card data available
-    // to other Google systems, disable it for passphrase users.
-    if (this.isUsingSecondaryPassphrase_) {
-      return false;
-    }
-
-    // If upload-to-Google state is not active, card cannot be saved to
-    // Google Payments. Return false.
-    if (!this.uploadToGoogleActive_) {
-      return false;
-    }
-
-    // The domain of the user's email address is not allowed, return false.
-    if (!this.userEmailDomainAllowed_) {
-      return false;
-    }
-
     // If credit card enabled pref is false, return false.
     if (!creditCardEnabled) {
       return false;
     }
 
-    // If user not signed-in and synced, return false.
-    if (!syncStatus.signedIn || !syncStatus.syncSystemEnabled) {
-      return false;
-    }
-
     const numberOfMigratableCreditCard =
         creditCards.filter(card => card.metadata.isMigratable).length;
     // Check whether exist at least one local valid card for migration.
diff --git a/chrome/browser/resources/welcome/OWNERS b/chrome/browser/resources/welcome/OWNERS
index bfe00f2..08ee1a9 100644
--- a/chrome/browser/resources/welcome/OWNERS
+++ b/chrome/browser/resources/welcome/OWNERS
@@ -1,2 +1 @@
 hcarmona@chromium.org
-scottchen@chromium.org
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/email/nux_email.html b/chrome/browser/resources/welcome/onboarding_welcome/email/nux_email.html
index 1c8b099..70c4455 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/email/nux_email.html
+++ b/chrome/browser/resources/welcome/onboarding_welcome/email/nux_email.html
@@ -13,15 +13,17 @@
       }
 
       .email-logo {
-        content: -webkit-image-set(
-            url(../images/email_provider_1x.png) 1x,
-            url(../images/email_provider_2x.png) 2x);
+        content: url(../images/module_icons/email_light.svg);
         height: 38px;
         margin: auto;
         margin-bottom: 16px;
         width: 42px;
       }
 
+      :host-context([dark]) .email-logo {
+        content: url(../images/module_icons/email_dark.svg);
+      }
+
       h1 {
         color: var(--cr-primary-text-color);
         font-size: 1.5rem;
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/google_apps/nux_google_apps.html b/chrome/browser/resources/welcome/onboarding_welcome/google_apps/nux_google_apps.html
index 51fc1c9..2cae5b1 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/google_apps/nux_google_apps.html
+++ b/chrome/browser/resources/welcome/onboarding_welcome/google_apps/nux_google_apps.html
@@ -13,15 +13,17 @@
       }
 
       .chrome-logo {
-        content: -webkit-image-set(
-            url(chrome://welcome/images/google_apps_1x.png) 1x,
-            url(chrome://welcome/images/google_apps_2x.png) 2x);
+        content: url(../images/module_icons/google_light.svg);
         height: 38px;
         margin: auto;
         margin-bottom: 16px;
         width: 42px;
       }
 
+      :host-context([dark]) .chrome-logo {
+        content: url(../images/module_icons/google_dark.svg);
+      }
+
       h1 {
         color: var(--cr-primary-text-color);
         font-size: 1.5rem;
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/images/email_provider_1x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/email_provider_1x.png
deleted file mode 100644
index 4ef0c29..0000000
--- a/chrome/browser/resources/welcome/onboarding_welcome/images/email_provider_1x.png
+++ /dev/null
Binary files differ
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/images/email_provider_2x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/email_provider_2x.png
deleted file mode 100644
index 62b7614..0000000
--- a/chrome/browser/resources/welcome/onboarding_welcome/images/email_provider_2x.png
+++ /dev/null
Binary files differ
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/images/google_apps_1x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/google_apps_1x.png
deleted file mode 100644
index adaff6c..0000000
--- a/chrome/browser/resources/welcome/onboarding_welcome/images/google_apps_1x.png
+++ /dev/null
Binary files differ
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/images/google_apps_2x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/google_apps_2x.png
deleted file mode 100644
index eeccb7d..0000000
--- a/chrome/browser/resources/welcome/onboarding_welcome/images/google_apps_2x.png
+++ /dev/null
Binary files differ
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/images/module_icons/email_dark.svg b/chrome/browser/resources/welcome/onboarding_welcome/images/module_icons/email_dark.svg
new file mode 100644
index 0000000..f6eac4f
--- /dev/null
+++ b/chrome/browser/resources/welcome/onboarding_welcome/images/module_icons/email_dark.svg
@@ -0,0 +1 @@
+<svg width="46" height="39" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-2 -2)" fill="none" fill-rule="evenodd"><circle fill="#90CFA3" cx="9.764" cy="38.785" r="2"/><path d="M34.998 27.443l11.433-.565a1.157 1.157 0 0 1 1.156 1.16 1.142 1.142 0 0 1-.127.514L42.284 38.58a1.174 1.174 0 0 1-1.977.163L34.05 29.28a1.144 1.144 0 0 1 .247-1.611c.203-.148.45-.227.701-.226z" fill="#EE675C" style="mix-blend-mode:screen"/><path d="M37.43 13.118H16.097a2.663 2.663 0 0 0-2.653 2.667l-.014 16c0 1.466 1.2 2.666 2.667 2.666H37.43c1.467 0 2.667-1.2 2.667-2.666v-16c0-1.467-1.2-2.667-2.667-2.667zm0 5.333l-10.666 6.667-10.667-6.667v-2.666l10.667 6.666 10.666-6.666v2.666z" fill="#8AB4F8" fill-rule="nonzero"/><path style="mix-blend-mode:multiply" d="M10.764 7.785h32v32h-32z"/><path d="M7.356 5.426c4.903-4.184 12.016-3.946 16.689.284.464.42 1.274 1.412-.187 2.658L7.085 22.678c-1.324 1.13-1.914.198-2.203-.255C1.42 16.985 2.38 9.671 7.356 5.426z" fill="#FDD663" style="mix-blend-mode:screen"/></g></svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/images/module_icons/email_light.svg b/chrome/browser/resources/welcome/onboarding_welcome/images/module_icons/email_light.svg
new file mode 100644
index 0000000..bb7b52ab
--- /dev/null
+++ b/chrome/browser/resources/welcome/onboarding_welcome/images/module_icons/email_light.svg
@@ -0,0 +1 @@
+<svg width="46" height="39" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-2 -2)" fill="none" fill-rule="evenodd"><circle fill="#31A753" cx="9.764" cy="38.785" r="2"/><path d="M34.998 27.443l11.433-.565a1.157 1.157 0 0 1 1.156 1.16 1.142 1.142 0 0 1-.127.514L42.284 38.58a1.174 1.174 0 0 1-1.977.163L34.05 29.28a1.144 1.144 0 0 1 .247-1.611c.203-.148.45-.227.701-.226z" fill="#E74133" style="mix-blend-mode:multiply"/><path d="M37.43 13.118H16.097a2.663 2.663 0 0 0-2.653 2.667l-.014 16c0 1.466 1.2 2.666 2.667 2.666H37.43c1.467 0 2.667-1.2 2.667-2.666v-16c0-1.467-1.2-2.667-2.667-2.667zm0 5.333l-10.666 6.667-10.667-6.667v-2.666l10.667 6.666 10.666-6.666v2.666z" fill="#1A73E8" fill-rule="nonzero"/><path style="mix-blend-mode:multiply" d="M10.764 7.785h32v32h-32z"/><path d="M7.356 5.426c4.903-4.184 12.016-3.946 16.689.284.464.42 1.274 1.412-.187 2.658L7.085 22.678c-1.324 1.13-1.914.198-2.203-.255C1.42 16.985 2.38 9.671 7.356 5.426z" fill="#FACF4C" style="mix-blend-mode:multiply"/></g></svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/images/module_icons/google_dark.svg b/chrome/browser/resources/welcome/onboarding_welcome/images/module_icons/google_dark.svg
new file mode 100644
index 0000000..40991d56
--- /dev/null
+++ b/chrome/browser/resources/welcome/onboarding_welcome/images/module_icons/google_dark.svg
@@ -0,0 +1 @@
+<svg width="42" height="38" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-2 -2)" fill="none" fill-rule="evenodd"><path d="M31.235 26.057l11.432-.566a1.157 1.157 0 0 1 1.157 1.16 1.142 1.142 0 0 1-.128.515L38.52 37.192a1.174 1.174 0 0 1-1.977.163l-6.257-9.461a1.144 1.144 0 0 1 .247-1.612c.203-.148.45-.227.702-.225z" fill="#EE675C"/><path d="M23.239 25.28v-4.464h11.455c.171.756.306 1.464.306 2.46C35 30.128 30.313 35 23.25 35 16.484 35 11 29.624 11 23s5.483-12 12.239-12c3.304 0 6.07 1.188 8.187 3.132l-3.475 3.312c-.882-.816-2.411-1.788-4.712-1.788-4.051 0-7.356 3.3-7.356 7.344 0 4.044 3.305 7.344 7.356 7.344 4.687 0 6.413-3.18 6.73-5.064h-6.73z" fill="#8AB4F8" fill-rule="nonzero"/><path d="M7 7h32v32H7z"/><circle fill="#81C995" cx="10" cy="37.398" r="2"/><path d="M6.592 5.039c4.904-4.183 12.017-3.946 16.689.284.464.42 1.274 1.412-.187 2.658L6.321 22.291c-1.324 1.13-1.914.198-2.202-.254C.655 16.597 1.616 9.284 6.592 5.039z" fill="#FDD663" style="mix-blend-mode:screen"/></g></svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/images/module_icons/google_light.svg b/chrome/browser/resources/welcome/onboarding_welcome/images/module_icons/google_light.svg
new file mode 100644
index 0000000..a0bed3b
--- /dev/null
+++ b/chrome/browser/resources/welcome/onboarding_welcome/images/module_icons/google_light.svg
@@ -0,0 +1 @@
+<svg width="42" height="38" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-2 -2)" fill="none" fill-rule="evenodd"><path d="M31.235 26.057l11.432-.566a1.157 1.157 0 0 1 1.157 1.16 1.142 1.142 0 0 1-.128.515L38.52 37.192a1.174 1.174 0 0 1-1.977.163l-6.257-9.461a1.144 1.144 0 0 1 .247-1.612c.203-.148.45-.227.702-.225z" fill="#E74133"/><path d="M23.239 25.678v-4.464h11.455c.171.756.306 1.464.306 2.46 0 6.852-4.687 11.724-11.75 11.724-6.767 0-12.25-5.376-12.25-12s5.483-12 12.239-12c3.304 0 6.07 1.188 8.187 3.132l-3.475 3.312c-.882-.816-2.411-1.788-4.712-1.788-4.051 0-7.356 3.3-7.356 7.344 0 4.044 3.305 7.344 7.356 7.344 4.687 0 6.413-3.18 6.73-5.064h-6.73z" fill="#1A73E8" fill-rule="nonzero"/><path d="M7 7.398h32v32H7z"/><circle fill="#31A753" cx="10" cy="37.398" r="2"/><path d="M6.592 5.039c4.904-4.183 12.017-3.946 16.689.284.464.42 1.274 1.412-.187 2.658L6.321 22.291c-1.324 1.13-1.914.198-2.202-.254C.655 16.597 1.616 9.284 6.592 5.039z" fill="#FACF4C" style="mix-blend-mode:multiply"/></g></svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/images/module_icons/set_default_dark.svg b/chrome/browser/resources/welcome/onboarding_welcome/images/module_icons/set_default_dark.svg
new file mode 100644
index 0000000..414dff5
--- /dev/null
+++ b/chrome/browser/resources/welcome/onboarding_welcome/images/module_icons/set_default_dark.svg
@@ -0,0 +1 @@
+<svg width="44" height="39" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path d="M14 3C7.929 3 3 7.929 3 14s4.929 11 11 11 11-4.929 11-11S20.071 3 14 3zm0-3c7.728 0 14 6.272 14 14s-6.272 14-14 14S0 21.728 0 14 6.272 0 14 0z" id="a"/><path d="M14 .667C21.36.667 27.333 6.64 27.333 14S21.36 27.333 14 27.333.667 21.36.667 14 6.64.667 14 .667zM3.333 14H9.2c4.543.029 6.562 2.308 6.058 6.836H10.65v3.293c1.054.349 2.18.538 3.35.538 5.887 0 10.667-4.78 10.667-10.667 0-.217-.007-.433-.02-.647-.876 1.32-2.203 1.98-3.98 1.98-2.85 0-4.275-1.222-4.275-3.665h-4.998c-.365-3.638.91-5.457 3.827-5.457 0-1.3.436-2.13 1.082-2.628A10.69 10.69 0 0 0 14 3.333C8.113 3.333 3.333 8.113 3.333 14z" id="c"/></defs><g transform="translate(-2 -2)" fill="none" fill-rule="evenodd"><path d="M33.235 28.659l11.432-.566a1.157 1.157 0 0 1 1.157 1.16 1.142 1.142 0 0 1-.128.515L40.52 39.794a1.174 1.174 0 0 1-1.977.163l-6.257-9.461a1.144 1.144 0 0 1 .247-1.612c.203-.148.45-.227.702-.225z" fill="#EE675C"/><g transform="translate(11 9)"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><use fill="#000" fill-rule="nonzero" xlink:href="#a"/><g mask="url(#b)" fill="#1A73E8"><path d="M-2-2h32v32H-2z"/></g><use fill="#8AB4F8" fill-rule="nonzero" xlink:href="#c"/></g><circle fill="#81C995" cx="9" cy="38.398" r="2"/><path d="M6.592 5.039c4.904-4.183 12.017-3.946 16.689.284.464.42 1.274 1.412-.187 2.658L6.321 22.291c-1.324 1.13-1.914.198-2.202-.254C.655 16.597 1.616 9.284 6.592 5.039z" fill="#FDDF8B" style="mix-blend-mode:screen"/></g></svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/images/module_icons/set_default_light.svg b/chrome/browser/resources/welcome/onboarding_welcome/images/module_icons/set_default_light.svg
new file mode 100644
index 0000000..2a0c6eb
--- /dev/null
+++ b/chrome/browser/resources/welcome/onboarding_welcome/images/module_icons/set_default_light.svg
@@ -0,0 +1 @@
+<svg width="44" height="39" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path d="M16 5C9.929 5 5 9.929 5 16s4.929 11 11 11 11-4.929 11-11S22.071 5 16 5zm0-3c7.728 0 14 6.272 14 14s-6.272 14-14 14S2 23.728 2 16 8.272 2 16 2z" id="a"/><path d="M16 2.667c7.36 0 13.333 5.973 13.333 13.333S23.36 29.333 16 29.333 2.667 23.36 2.667 16 8.64 2.667 16 2.667zM5.333 16H11.2c4.543.029 6.562 2.308 6.058 6.836H12.65v3.293c1.054.349 2.18.538 3.35.538 5.887 0 10.667-4.78 10.667-10.667 0-.217-.007-.433-.02-.647-.876 1.32-2.203 1.98-3.98 1.98-2.85 0-4.275-1.222-4.275-3.665h-4.998c-.365-3.638.91-5.457 3.827-5.457 0-1.3.436-2.13 1.082-2.628A10.69 10.69 0 0 0 16 5.333c-5.887 0-10.667 4.78-10.667 10.667z" id="c"/></defs><g transform="translate(-2 -2)" fill="none" fill-rule="evenodd"><path d="M33.235 28.659l11.432-.566a1.157 1.157 0 0 1 1.157 1.16 1.142 1.142 0 0 1-.128.515L40.52 39.794a1.174 1.174 0 0 1-1.977.163l-6.257-9.461a1.144 1.144 0 0 1 .247-1.612c.203-.148.45-.227.702-.225z" fill="#E74133"/><g transform="translate(9 7.398)"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><use fill="#000" fill-rule="nonzero" xlink:href="#a"/><g mask="url(#b)" fill="#1A73E8"><path d="M0 0h32v32H0z"/></g><mask id="d" fill="#fff"><use xlink:href="#c"/></mask><use fill="#000" fill-rule="nonzero" xlink:href="#c"/><g mask="url(#d)" fill="#1A73E8"><path d="M0 0h32v32H0z"/></g></g><circle fill="#31A753" cx="9" cy="38.398" r="2"/><path d="M6.592 5.039c4.904-4.183 12.017-3.946 16.689.284.464.42 1.274 1.412-.187 2.658L6.321 22.291c-1.324 1.13-1.914.198-2.202-.254C.655 16.597 1.616 9.284 6.592 5.039z" fill="#FACF4C" style="mix-blend-mode:multiply"/></g></svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/images/module_icons/wallpaper_dark.svg b/chrome/browser/resources/welcome/onboarding_welcome/images/module_icons/wallpaper_dark.svg
new file mode 100644
index 0000000..45cfa17
--- /dev/null
+++ b/chrome/browser/resources/welcome/onboarding_welcome/images/module_icons/wallpaper_dark.svg
@@ -0,0 +1 @@
+<svg width="44" height="39" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-2 -2)" fill="none" fill-rule="evenodd"><path d="M33.235 28.659l11.432-.566a1.157 1.157 0 0 1 1.157 1.16 1.142 1.142 0 0 1-.128.515L40.52 39.794a1.174 1.174 0 0 1-1.977.163l-6.257-9.461a1.144 1.144 0 0 1 .247-1.612c.203-.148.45-.227.702-.225z" fill="#EE675C"/><circle fill="#31A753" cx="9" cy="38.398" r="2"/><path d="M9 7h32v32H9z"/><path d="M34.333 13.667v18.666H15.667V13.667h18.666zm0-2.667H15.667A2.675 2.675 0 0 0 13 13.667v18.666C13 33.8 14.2 35 15.667 35h18.666C35.8 35 37 33.8 37 32.333V13.667C37 12.2 35.8 11 34.333 11zm-6.48 11.813l-4 5.16L21 24.52l-4 5.147h16l-5.147-6.854z" fill="#8AB4F8" fill-rule="nonzero"/><path d="M6.592 5.039c4.904-4.183 12.017-3.946 16.689.284.464.42 1.274 1.412-.187 2.658L6.321 22.291c-1.324 1.13-1.914.198-2.202-.254C.655 16.597 1.616 9.284 6.592 5.039z" fill="#FDDB79" style="mix-blend-mode:screen"/></g></svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/images/module_icons/wallpaper_light.svg b/chrome/browser/resources/welcome/onboarding_welcome/images/module_icons/wallpaper_light.svg
new file mode 100644
index 0000000..300ae22
--- /dev/null
+++ b/chrome/browser/resources/welcome/onboarding_welcome/images/module_icons/wallpaper_light.svg
@@ -0,0 +1 @@
+<svg width="44" height="39" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-2 -2)" fill="none" fill-rule="evenodd"><path d="M33.235 28.659l11.432-.566a1.157 1.157 0 0 1 1.157 1.16 1.142 1.142 0 0 1-.128.515L40.52 39.794a1.174 1.174 0 0 1-1.977.163l-6.257-9.461a1.144 1.144 0 0 1 .247-1.612c.203-.148.45-.227.702-.225z" fill="#E74133"/><circle fill="#31A753" cx="9" cy="38.398" r="2"/><path d="M6.592 5.039c4.904-4.183 12.017-3.946 16.689.284.464.42 1.274 1.412-.187 2.658L6.321 22.291c-1.324 1.13-1.914.198-2.202-.254C.655 16.597 1.616 9.284 6.592 5.039z" fill="#FACF4C" style="mix-blend-mode:multiply"/><path d="M9 7h32v32H9z"/><path d="M34.333 13.667v18.666H15.667V13.667h18.666zm0-2.667H15.667A2.675 2.675 0 0 0 13 13.667v18.666C13 33.8 14.2 35 15.667 35h18.666C35.8 35 37 33.8 37 32.333V13.667C37 12.2 35.8 11 34.333 11zm-6.48 11.813l-4 5.16L21 24.52l-4 5.147h16l-5.147-6.854z" fill="#1A73E8" fill-rule="nonzero"/></g></svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/images/ntp_background_1x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/ntp_background_1x.png
deleted file mode 100644
index fdc3f4f..0000000
--- a/chrome/browser/resources/welcome/onboarding_welcome/images/ntp_background_1x.png
+++ /dev/null
Binary files differ
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/images/ntp_background_2x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/ntp_background_2x.png
deleted file mode 100644
index a6e3139..0000000
--- a/chrome/browser/resources/welcome/onboarding_welcome/images/ntp_background_2x.png
+++ /dev/null
Binary files differ
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/images/set_as_default_1x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/set_as_default_1x.png
deleted file mode 100644
index c6d2637..0000000
--- a/chrome/browser/resources/welcome/onboarding_welcome/images/set_as_default_1x.png
+++ /dev/null
Binary files differ
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/images/set_as_default_2x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/set_as_default_2x.png
deleted file mode 100644
index b82f6201..0000000
--- a/chrome/browser/resources/welcome/onboarding_welcome/images/set_as_default_2x.png
+++ /dev/null
Binary files differ
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.html b/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.html
index 132f6c5..d8b7649 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.html
+++ b/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.html
@@ -57,15 +57,17 @@
       }
 
       .ntp-background-logo {
-        content: -webkit-image-set(
-            url(chrome://welcome/images/ntp_background_1x.png) 1x,
-            url(chrome://welcome/images/ntp_background_2x.png) 2x);
+        content: url(../images/module_icons/wallpaper_light.svg);
         height: 39px;
         margin: auto;
         margin-bottom: 16px;
         width: 44px;
       }
 
+      :host-context([dark]) .ntp-background-logo {
+        content: url(../images/module_icons/wallpaper_dark.svg);
+      }
+
       h1 {
         color: var(--cr-primary-text-color);
         font-size: 1.5rem;
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/onboarding_welcome_resources.grd b/chrome/browser/resources/welcome/onboarding_welcome/onboarding_welcome_resources.grd
index 2f3f089..174bf93 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/onboarding_welcome_resources.grd
+++ b/chrome/browser/resources/welcome/onboarding_welcome/onboarding_welcome_resources.grd
@@ -15,103 +15,111 @@
   <release seq="1">
     <includes>
       <include name="IDR_NUX_EMAIL_AOL_1X"
-               file="images\aol_1x.png"
+               file="images/aol_1x.png"
                type="BINDATA"/>
       <include name="IDR_NUX_EMAIL_AOL_2X"
-               file="images\aol_2x.png"
+               file="images/aol_2x.png"
                type="BINDATA" />
       <include name="IDR_NUX_EMAIL_GMAIL_1X"
-               file="images\gmail_1x.png"
+               file="images/gmail_1x.png"
                type="BINDATA" />
       <include name="IDR_NUX_EMAIL_GMAIL_2X"
-               file="images\gmail_2x.png"
+               file="images/gmail_2x.png"
                type="BINDATA" />
       <include name="IDR_NUX_EMAIL_ICLOUD_1X"
-               file="images\icloud_1x.png"
+               file="images/icloud_1x.png"
                type="BINDATA" />
       <include name="IDR_NUX_EMAIL_ICLOUD_2X"
-               file="images\icloud_2x.png"
+               file="images/icloud_2x.png"
                type="BINDATA" />
       <include name="IDR_NUX_EMAIL_OUTLOOK_1X"
-               file="images\outlook_1x.png"
+               file="images/outlook_1x.png"
                type="BINDATA" />
       <include name="IDR_NUX_EMAIL_OUTLOOK_2X"
-               file="images\outlook_2x.png"
-               type="BINDATA" />
-      <include name="IDR_NUX_EMAIL_PROVIDER_LOGO_1X"
-               file="images\email_provider_1x.png"
-               type="BINDATA" />
-      <include name="IDR_NUX_EMAIL_PROVIDER_LOGO_2X"
-               file="images\email_provider_2x.png"
+               file="images/outlook_2x.png"
                type="BINDATA" />
       <include name="IDR_NUX_EMAIL_YAHOO_1X"
-               file="images\yahoo_1x.png"
+               file="images/yahoo_1x.png"
                type="BINDATA" />
       <include name="IDR_NUX_EMAIL_YAHOO_2X"
-               file="images\yahoo_2x.png"
+               file="images/yahoo_2x.png"
                type="BINDATA" />
       <include name="IDR_NUX_GOOGLE_APPS_CHROME_STORE_1X"
-               file="images\chrome_store_1x.png"
+               file="images/chrome_store_1x.png"
                type="BINDATA" />
       <include name="IDR_NUX_GOOGLE_APPS_CHROME_STORE_2X"
-               file="images\chrome_store_2x.png"
-               type="BINDATA" />
-      <include name="IDR_NUX_GOOGLE_APPS_LOGO_1X"
-               file="images\google_apps_1x.png"
-               type="BINDATA" />
-      <include name="IDR_NUX_GOOGLE_APPS_LOGO_2X"
-               file="images\google_apps_2x.png"
+               file="images/chrome_store_2x.png"
                type="BINDATA" />
       <include name="IDR_NUX_GOOGLE_APPS_MAPS_1X"
-               file="images\maps_1x.png"
+               file="images/maps_1x.png"
                type="BINDATA" />
       <include name="IDR_NUX_GOOGLE_APPS_MAPS_2X"
-               file="images\maps_2x.png"
+               file="images/maps_2x.png"
                type="BINDATA" />
       <include name="IDR_NUX_GOOGLE_APPS_NEWS_1X"
-               file="images\news_1x.png"
+               file="images/news_1x.png"
                type="BINDATA" />
       <include name="IDR_NUX_GOOGLE_APPS_NEWS_2X"
-               file="images\news_2x.png"
+               file="images/news_2x.png"
                type="BINDATA" />
       <include name="IDR_NUX_GOOGLE_APPS_TRANSLATE_1X"
-               file="images\translate_1x.png"
+               file="images/translate_1x.png"
                type="BINDATA" />
       <include name="IDR_NUX_GOOGLE_APPS_TRANSLATE_2X"
-               file="images\translate_2x.png"
+               file="images/translate_2x.png"
                type="BINDATA" />
       <include name="IDR_NUX_GOOGLE_APPS_YOUTUBE_1X"
-               file="images\youtube_1x.png"
+               file="images/youtube_1x.png"
                type="BINDATA" />
       <include name="IDR_NUX_GOOGLE_APPS_YOUTUBE_2X"
-               file="images\youtube_2x.png"
+               file="images/youtube_2x.png"
                type="BINDATA" />
-      <include name="IDR_NUX_NTP_BACKGROUND_LOGO_1X"
-               file="images\ntp_background_1x.png"
+      <include name="IDR_NUX_MODULE_ICONS_EMAIL_DARK"
+               file="images/module_icons/email_dark.svg"
+               compress="gzip"
                type="BINDATA" />
-      <include name="IDR_NUX_NTP_BACKGROUND_LOGO_2X"
-               file="images\ntp_background_2x.png"
+      <include name="IDR_NUX_MODULE_ICONS_EMAIL_LIGHT"
+               file="images/module_icons/email_light.svg"
+               compress="gzip"
+               type="BINDATA" />
+      <include name="IDR_NUX_MODULE_ICONS_GOOGLE_DARK"
+               file="images/module_icons/google_dark.svg"
+               compress="gzip"
+               type="BINDATA" />
+      <include name="IDR_NUX_MODULE_ICONS_GOOGLE_LIGHT"
+               file="images/module_icons/google_light.svg"
+               compress="gzip"
+               type="BINDATA" />
+      <include name="IDR_NUX_MODULE_ICONS_SET_DEFAULT_DARK"
+               file="images/module_icons/set_default_dark.svg"
+               compress="gzip"
+               type="BINDATA" />
+      <include name="IDR_NUX_MODULE_ICONS_SET_DEFAULT_LIGHT"
+               file="images/module_icons/set_default_light.svg"
+               compress="gzip"
+               type="BINDATA" />
+      <include name="IDR_NUX_MODULE_ICONS_WALLPAPER_DARK"
+               file="images/module_icons/wallpaper_dark.svg"
+               compress="gzip"
+               type="BINDATA" />
+      <include name="IDR_NUX_MODULE_ICONS_WALLPAPER_LIGHT"
+               file="images/module_icons/wallpaper_light.svg"
+               compress="gzip"
                type="BINDATA" />
       <include name="IDR_NUX_NTP_BACKGROUND_THUMBNAIL_ART"
-               file="images\ntp_thumbnails\art.jpg"
+               file="images/ntp_thumbnails/art.jpg"
                type="BINDATA" />
       <include name="IDR_NUX_NTP_BACKGROUND_THUMBNAIL_CITYSCAPE"
-               file="images\ntp_thumbnails\cityscape.jpg"
+               file="images/ntp_thumbnails/cityscape.jpg"
                type="BINDATA" />
       <include name="IDR_NUX_NTP_BACKGROUND_THUMBNAIL_EARTH"
-               file="images\ntp_thumbnails\earth.jpg"
+               file="images/ntp_thumbnails/earth.jpg"
                type="BINDATA" />
       <include name="IDR_NUX_NTP_BACKGROUND_THUMBNAIL_GEOMETRIC_SHAPES"
-               file="images\ntp_thumbnails\geometric_shapes.jpg"
+               file="images/ntp_thumbnails/geometric_shapes.jpg"
                type="BINDATA" />
       <include name="IDR_NUX_NTP_BACKGROUND_THUMBNAIL_LANDSCAPE"
-               file="images\ntp_thumbnails\landscape.jpg"
-               type="BINDATA" />
-      <include name="IDR_NUX_SET_AS_DEFAULT_LOGO_1X"
-               file="images\set_as_default_1x.png"
-               type="BINDATA" />
-      <include name="IDR_NUX_SET_AS_DEFAULT_LOGO_2X"
-               file="images\set_as_default_2x.png"
+               file="images/ntp_thumbnails/landscape.jpg"
                type="BINDATA" />
       <include name="IDR_NUX_SET_DEFAULT_DARK"
                file="images\set_default_dark.svg"
@@ -122,31 +130,31 @@
                compress="gzip"
                type="BINDATA" />
       <include name="IDR_WELCOME_ONBOARDING_WELCOME_IMAGES_BACKGROUND_SVGS_BLUE_CIRCLE_SVG"
-               file="images\background_svgs\blue_circle.svg"
+               file="images/background_svgs/blue_circle.svg"
                compress="gzip"
                type="BINDATA" />
       <include name="IDR_WELCOME_ONBOARDING_WELCOME_IMAGES_BACKGROUND_SVGS_GREEN_RECTANGLE_SVG"
-               file="images\background_svgs\green_rectangle.svg"
+               file="images/background_svgs/green_rectangle.svg"
                compress="gzip"
                type="BINDATA" />
       <include name="IDR_WELCOME_ONBOARDING_WELCOME_IMAGES_BACKGROUND_SVGS_GREY_OVAL_SVG"
-               file="images\background_svgs\grey_oval.svg"
+               file="images/background_svgs/grey_oval.svg"
                compress="gzip"
                type="BINDATA" />
       <include name="IDR_WELCOME_ONBOARDING_WELCOME_IMAGES_BACKGROUND_SVGS_GREY_ROUNDED_RECTANGLE_SVG"
-               file="images\background_svgs\grey_rounded_rectangle.svg"
+               file="images/background_svgs/grey_rounded_rectangle.svg"
                compress="gzip"
                type="BINDATA" />
       <include name="IDR_WELCOME_ONBOARDING_WELCOME_IMAGES_BACKGROUND_SVGS_RED_TRIANGLE_SVG"
-               file="images\background_svgs\red_triangle.svg"
+               file="images/background_svgs/red_triangle.svg"
                compress="gzip"
                type="BINDATA" />
       <include name="IDR_WELCOME_ONBOARDING_WELCOME_IMAGES_BACKGROUND_SVGS_YELLOW_DOTS_SVG"
-               file="images\background_svgs\yellow_dots.svg"
+               file="images/background_svgs/yellow_dots.svg"
                compress="gzip"
                type="BINDATA" />
       <include name="IDR_WELCOME_ONBOARDING_WELCOME_IMAGES_BACKGROUND_SVGS_YELLOW_SEMICIRCLE_SVG"
-               file="images\background_svgs\yellow_semicircle.svg"
+               file="images/background_svgs/yellow_semicircle.svg"
                compress="gzip"
                type="BINDATA" />
     </includes>
@@ -182,67 +190,67 @@
                  compress="gzip"
                  preprocess="true"/>
       <structure name="IDR_NUX_SHARED_APP_CHOOSER_HTML"
-                 file="shared\app_chooser.html"
+                 file="shared/app_chooser.html"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_NUX_SHARED_APP_CHOOSER_JS"
-                 file="shared\app_chooser.js"
+                 file="shared/app_chooser.js"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_WELCOME_ONBOARDING_WELCOME_SHARED_ACTION_LINK_STYLE_JS"
-                 file="shared\action_link_style.js"
+                 file="shared/action_link_style.js"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_WELCOME_ONBOARDING_WELCOME_SHARED_ACTION_LINK_STYLE_CSS_HTML"
-                 file="shared\action_link_style_css.html"
+                 file="shared/action_link_style_css.html"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_WELCOME_ONBOARDING_WELCOME_SHARED_BOOKMARK_PROXY_HTML"
-                 file="shared\bookmark_proxy.html"
+                 file="shared/bookmark_proxy.html"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_WELCOME_ONBOARDING_WELCOME_SHARED_BOOKMARK_PROXY_JS"
-                 file="shared\bookmark_proxy.js"
+                 file="shared/bookmark_proxy.js"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_WELCOME_ONBOARDING_WELCOME_SHARED_CHOOSER_SHARED_CSS"
-                 file="shared\chooser_shared_css.html"
+                 file="shared/chooser_shared_css.html"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_WELCOME_ONBOARDING_WELCOME_SHARED_I18N_SETUP_HTML"
-                 file="shared\i18n_setup.html"
+                 file="shared/i18n_setup.html"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_WELCOME_ONBOARDING_WELCOME_SHARED_MODULE_METRICS_PROXY_HTML"
-                 file="shared\module_metrics_proxy.html"
+                 file="shared/module_metrics_proxy.html"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_WELCOME_ONBOARDING_WELCOME_SHARED_MODULE_METRICS_PROXY_JS"
-                 file="shared\module_metrics_proxy.js"
+                 file="shared/module_metrics_proxy.js"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_WELCOME_ONBOARDING_WELCOME_SHARED_NAVI_COLORS_CSS"
-                 file="shared\navi_colors_css.html"
+                 file="shared/navi_colors_css.html"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_WELCOME_ONBOARDING_WELCOME_SHARED_ONBOARDING_BACKGROUND_HTML"
-                 file="shared\onboarding_background.html"
+                 file="shared/onboarding_background.html"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_WELCOME_ONBOARDING_WELCOME_SHARED_ONBOARDING_BACKGROUND_JS"
-                 file="shared\onboarding_background.js"
+                 file="shared/onboarding_background.js"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_WELCOME_ONBOARDING_WELCOME_SHARED_STEP_INDICATOR_HTML"
-                 file="shared\step_indicator.html"
+                 file="shared/step_indicator.html"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_WELCOME_ONBOARDING_WELCOME_SHARED_STEP_INDICATOR_JS"
-                 file="shared\step_indicator.js"
+                 file="shared/step_indicator.js"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_WELCOME_ONBOARDING_WELCOME_SHARED_SPLASH_PAGES_SHARED_CSS"
-                 file="shared\splash_pages_shared_css.html"
+                 file="shared/splash_pages_shared_css.html"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_WELCOME_ONBOARDING_WELCOME_SIGNIN_VIEW_HTML"
@@ -301,73 +309,73 @@
 
        <!-- NUX Email-->
       <structure name="IDR_EMAIL_APP_PROXY_HTML"
-                 file="email\email_app_proxy.html"
+                 file="email/email_app_proxy.html"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_EMAIL_APP_PROXY_JS"
-                 file="email\email_app_proxy.js"
+                 file="email/email_app_proxy.js"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_NUX_EMAIL_HTML"
-                 file="email\nux_email.html"
+                 file="email/nux_email.html"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_NUX_EMAIL_JS"
-                 file="email\nux_email.js"
+                 file="email/nux_email.js"
                  compress="gzip"
                  type="chrome_html" />
 
        <!-- NUX Google apps-->
       <structure name="IDR_NUX_GOOGLE_APPS_HTML"
-                 file="google_apps\nux_google_apps.html"
+                 file="google_apps/nux_google_apps.html"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_NUX_GOOGLE_APPS_JS"
-                 file="google_apps\nux_google_apps.js"
+                 file="google_apps/nux_google_apps.js"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_GOOGLE_APP_PROXY_HTML"
-                 file="google_apps\google_app_proxy.html"
+                 file="google_apps/google_app_proxy.html"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_GOOGLE_APP_PROXY_JS"
-                 file="google_apps\google_app_proxy.js"
+                 file="google_apps/google_app_proxy.js"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_NUX_SET_AS_DEFAULT_HTML"
-                 file="set_as_default\nux_set_as_default.html"
+                 file="set_as_default/nux_set_as_default.html"
                  type="chrome_html"
                  compress="gzip"
                  preprocess="true"/>
       <structure name="IDR_NUX_SET_AS_DEFAULT_JS"
-                 file="set_as_default\nux_set_as_default.js"
+                 file="set_as_default/nux_set_as_default.js"
                  type="chrome_html"
                  compress="gzip"
                  preprocess="true"/>
       <structure name="IDR_NUX_SET_AS_DEFAULT_PROXY_HTML"
-                 file="set_as_default\nux_set_as_default_proxy.html"
+                 file="set_as_default/nux_set_as_default_proxy.html"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_NUX_SET_AS_DEFAULT_PROXY_JS"
-                 file="set_as_default\nux_set_as_default_proxy.js"
+                 file="set_as_default/nux_set_as_default_proxy.js"
                  compress="gzip"
                  type="chrome_html" />
 
       <!-- NUX NTP background-->
       <structure name="IDR_NUX_NTP_BACKGROUND_HTML"
-                 file="ntp_background\nux_ntp_background.html"
+                 file="ntp_background/nux_ntp_background.html"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_NUX_NTP_BACKGROUND_JS"
-                 file="ntp_background\nux_ntp_background.js"
+                 file="ntp_background/nux_ntp_background.js"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_NUX_NTP_BACKGROUND_PROXY_HTML"
-                 file="ntp_background\ntp_background_proxy.html"
+                 file="ntp_background/ntp_background_proxy.html"
                  compress="gzip"
                  type="chrome_html" />
       <structure name="IDR_NUX_NTP_BACKGROUND_PROXY_JS"
-                 file="ntp_background\ntp_background_proxy.js"
+                 file="ntp_background/ntp_background_proxy.js"
                  compress="gzip"
                  type="chrome_html" />
     </structures>
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default.html b/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default.html
index 303d6e2..aca9698 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default.html
+++ b/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default.html
@@ -21,15 +21,17 @@
       }
 
       .logo {
-        content: -webkit-image-set(
-          url(../images/set_as_default_1x.png) 1x,
-          url(../images/set_as_default_2x.png) 2x);
+        content: url(../images/module_icons/set_default_light.svg);
         display: inline-block;
         height: 38px;
         margin-bottom: 16px;
         width: 42px;
       }
 
+      :host-context([dark]) .logo {
+        content: url(../images/module_icons/set_default_dark.svg);
+      }
+
       .illustration {
         content: url(../images/set_default_light.svg);
         margin: auto;
diff --git a/chrome/browser/safe_browsing/download_protection/download_protection_service_browsertest.cc b/chrome/browser/safe_browsing/download_protection/download_protection_service_browsertest.cc
new file mode 100644
index 0000000..9d399e2
--- /dev/null
+++ b/chrome/browser/safe_browsing/download_protection/download_protection_service_browsertest.cc
@@ -0,0 +1,121 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/safe_browsing/proto/csd.pb.h"
+#include "components/safe_browsing/web_ui/safe_browsing_ui.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/download_manager.h"
+#include "content/public/browser/site_instance.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/download_test_observer.h"
+#include "crypto/sha2.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/window_open_disposition.h"
+
+namespace safe_browsing {
+
+class DownloadProtectionServiceBrowserTest : public InProcessBrowserTest {
+ protected:
+  base::FilePath GetTestDataDirectory() {
+    base::FilePath test_file_directory;
+    base::PathService::Get(chrome::DIR_TEST_DATA, &test_file_directory);
+    return test_file_directory;
+  }
+
+  void DownloadAndWait(GURL url) {
+    content::DownloadManager* download_manager =
+        content::BrowserContext::GetDownloadManager(browser()->profile());
+    content::DownloadTestObserverTerminal observer(
+        download_manager, 1,
+        content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_IGNORE);
+
+    // This call will block until the navigation completes, but will not wait
+    // for the download to finish.
+    ui_test_utils::NavigateToURLWithDisposition(
+        browser(), url, WindowOpenDisposition::CURRENT_TAB,
+        ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
+
+    observer.WaitForFinished();
+  }
+
+  // The hash of 10 A's, followed by a newline. Was generated as follows:
+  //   echo "AAAAAAAAAA" > a.zip
+  //   sha256sum a.zip
+  static const uint8_t kAZipDigest[];
+
+  // The hash of 9 B's, followed by a newline. Was generated as follows:
+  //   echo "BBBBBBBBB" > a.zip
+  //   sha256sum a.zip
+  static const uint8_t kBZipDigest[];
+};
+
+const uint8_t DownloadProtectionServiceBrowserTest::kAZipDigest[] = {
+    0x70, 0x5d, 0x29, 0x0c, 0x12, 0x89, 0x59, 0x01, 0xf8, 0x09, 0xf6,
+    0xc2, 0xfe, 0x77, 0x2a, 0x94, 0xdb, 0x51, 0x81, 0xd7, 0x26, 0x46,
+    0x4d, 0x84, 0x06, 0x82, 0x10, 0x6f, 0x4a, 0x93, 0x4b, 0x87};
+
+const uint8_t DownloadProtectionServiceBrowserTest::kBZipDigest[] = {
+    0x94, 0x1e, 0x17, 0x3f, 0x62, 0xbc, 0x04, 0x50, 0x6f, 0xeb, 0xb5,
+    0xe2, 0x8c, 0x38, 0x6c, 0xb2, 0x11, 0x91, 0xf3, 0x77, 0xa7, 0x2c,
+    0x11, 0x92, 0xe0, 0x25, 0xb0, 0xe5, 0xc7, 0x70, 0x3b, 0x23};
+
+IN_PROC_BROWSER_TEST_F(DownloadProtectionServiceBrowserTest, VerifyZipHash) {
+  embedded_test_server()->ServeFilesFromDirectory(GetTestDataDirectory());
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  WebUIInfoSingleton::GetInstance()->AddListenerForTesting();
+
+  GURL url = embedded_test_server()->GetURL(
+      "/safe_browsing/download_protection/zipfile_two_archives.zip");
+  DownloadAndWait(url);
+
+  const std::vector<std::unique_ptr<ClientDownloadRequest>>& requests =
+      WebUIInfoSingleton::GetInstance()->client_download_requests_sent();
+
+  ASSERT_EQ(1u, requests.size());
+  ASSERT_EQ(2, requests[0]->archived_binary_size());
+
+  EXPECT_EQ("a.zip", requests[0]->archived_binary(0).file_basename());
+  EXPECT_EQ(std::string(kAZipDigest, kAZipDigest + crypto::kSHA256Length),
+            requests[0]->archived_binary(0).digests().sha256());
+
+  EXPECT_EQ("b.zip", requests[0]->archived_binary(1).file_basename());
+  EXPECT_EQ(std::string(kBZipDigest, kBZipDigest + crypto::kSHA256Length),
+            requests[0]->archived_binary(1).digests().sha256());
+}
+
+IN_PROC_BROWSER_TEST_F(DownloadProtectionServiceBrowserTest, VerifyRarHash) {
+  embedded_test_server()->ServeFilesFromDirectory(GetTestDataDirectory());
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  WebUIInfoSingleton::GetInstance()->AddListenerForTesting();
+
+  GURL url =
+      embedded_test_server()->GetURL("/safe_browsing/rar/has_two_archives.rar");
+  DownloadAndWait(url);
+
+  const std::vector<std::unique_ptr<ClientDownloadRequest>>& requests =
+      WebUIInfoSingleton::GetInstance()->client_download_requests_sent();
+
+  ASSERT_EQ(1u, requests.size());
+  ASSERT_EQ(2, requests[0]->archived_binary_size());
+
+  EXPECT_EQ("a.zip", requests[0]->archived_binary(0).file_basename());
+  EXPECT_EQ(std::string(kAZipDigest, kAZipDigest + crypto::kSHA256Length),
+            requests[0]->archived_binary(0).digests().sha256());
+
+  EXPECT_EQ("b.zip", requests[0]->archived_binary(1).file_basename());
+  EXPECT_EQ(std::string(kBZipDigest, kBZipDigest + crypto::kSHA256Length),
+            requests[0]->archived_binary(1).digests().sha256());
+}
+
+}  // namespace safe_browsing
diff --git a/chrome/browser/translate/android/translate_bridge.cc b/chrome/browser/translate/android/translate_bridge.cc
index a0f41d3..f9ca78e 100644
--- a/chrome/browser/translate/android/translate_bridge.cc
+++ b/chrome/browser/translate/android/translate_bridge.cc
@@ -3,9 +3,14 @@
 // found in the LICENSE file.
 
 #include "base/android/jni_string.h"
+#include "chrome/browser/language/language_model_manager_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/translate/chrome_translate_client.h"
+#include "chrome/browser/translate/translate_service.h"
+#include "components/language/core/browser/language_model.h"
+#include "components/language/core/browser/language_model_manager.h"
 #include "components/language/core/common/language_experiments.h"
-#include "components/translate/content/browser/content_translate_driver.h"
 #include "components/translate/core/browser/translate_manager.h"
 #include "components/translate/core/browser/translate_prefs.h"
 #include "content/public/browser/web_contents.h"
@@ -73,3 +78,55 @@
   DCHECK(client);
   client->SetPredefinedTargetLanguage(translate_language);
 }
+
+// Returns the preferred target language to translate into for this user.
+static base::android::ScopedJavaLocalRef<jstring>
+JNI_TranslateBridge_GetTargetLanguage(JNIEnv* env) {
+  Profile* profile = ProfileManager::GetActiveUserProfile();
+  language::LanguageModel* language_model =
+      LanguageModelManagerFactory::GetForBrowserContext(profile)
+          ->GetPrimaryModel();
+  DCHECK(language_model);
+  PrefService* pref_service = profile->GetPrefs();
+  std::string target_language =
+      TranslateService::GetTargetLanguage(pref_service, language_model);
+  DCHECK(!target_language.empty());
+  base::android::ScopedJavaLocalRef<jstring> j_target_language =
+      base::android::ConvertUTF8ToJavaString(env, target_language);
+  return j_target_language;
+}
+
+// Determines whether the given language is blocked for translation.
+static jboolean JNI_TranslateBridge_IsBlockedLanguage(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jstring>& j_language_string) {
+  std::string language_we_might_block =
+      ConvertJavaStringToUTF8(env, j_language_string);
+  Profile* profile = ProfileManager::GetActiveUserProfile();
+  PrefService* pref_service = profile->GetPrefs();
+  std::unique_ptr<translate::TranslatePrefs> translate_prefs =
+      ChromeTranslateClient::CreateTranslatePrefs(pref_service);
+  DCHECK(translate_prefs);
+  return translate_prefs->IsBlockedLanguage(language_we_might_block);
+}
+
+// Gets all the model languages and calls back to the Java
+// TranslateBridge#addModelLanguageToSet once for each language.
+static void JNI_TranslateBridge_GetModelLanguages(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& set) {
+  Profile* profile = ProfileManager::GetActiveUserProfile();
+  language::LanguageModel* language_model =
+      LanguageModelManagerFactory::GetForBrowserContext(profile)
+          ->GetPrimaryModel();
+  DCHECK(language_model);
+  std::string model_languages;
+  std::vector<language::LanguageModel::LanguageDetails> languageDetails =
+      language_model->GetLanguages();
+  DCHECK(!languageDetails.empty());
+  for (const auto& details : languageDetails) {
+    Java_TranslateBridge_addModelLanguageToSet(
+        env, set,
+        base::android::ConvertUTF8ToJavaString(env, details.lang_code));
+  }
+}
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index dcc5061..3de926fc 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1550,6 +1550,16 @@
       "webui/chromeos/bluetooth_dialog_localized_strings_provider.h",
       "webui/chromeos/bluetooth_pairing_dialog.cc",
       "webui/chromeos/bluetooth_pairing_dialog.h",
+      "webui/chromeos/cellular_setup/cellular_setup_dialog.cc",
+      "webui/chromeos/cellular_setup/cellular_setup_dialog.h",
+      "webui/chromeos/cellular_setup/cellular_setup_dialog_launcher.cc",
+      "webui/chromeos/cellular_setup/cellular_setup_dialog_launcher.h",
+      "webui/chromeos/cellular_setup/cellular_setup_localized_strings_provider.cc",
+      "webui/chromeos/cellular_setup/cellular_setup_localized_strings_provider.h",
+      "webui/chromeos/cellular_setup/mobile_setup_dialog.cc",
+      "webui/chromeos/cellular_setup/mobile_setup_dialog.h",
+      "webui/chromeos/cellular_setup/mobile_setup_ui.cc",
+      "webui/chromeos/cellular_setup/mobile_setup_ui.h",
       "webui/chromeos/certificate_manager_dialog_ui.cc",
       "webui/chromeos/certificate_manager_dialog_ui.h",
       "webui/chromeos/cryptohome_ui.cc",
@@ -1684,10 +1694,6 @@
       "webui/chromeos/login/welcome_screen_handler.h",
       "webui/chromeos/login/wrong_hwid_screen_handler.cc",
       "webui/chromeos/login/wrong_hwid_screen_handler.h",
-      "webui/chromeos/mobile_setup_dialog.cc",
-      "webui/chromeos/mobile_setup_dialog.h",
-      "webui/chromeos/mobile_setup_ui.cc",
-      "webui/chromeos/mobile_setup_ui.h",
       "webui/chromeos/multidevice_setup/multidevice_setup_dialog.cc",
       "webui/chromeos/multidevice_setup/multidevice_setup_dialog.h",
       "webui/chromeos/multidevice_setup/multidevice_setup_handler.cc",
@@ -2631,8 +2637,6 @@
       "views/location_bar/location_bar_bubble_delegate_view.h",
       "views/location_bar/location_bar_layout.cc",
       "views/location_bar/location_bar_layout.h",
-      "views/location_bar/location_bar_separator_view.cc",
-      "views/location_bar/location_bar_separator_view.h",
       "views/location_bar/location_bar_view.cc",
       "views/location_bar/location_bar_view.h",
       "views/location_bar/location_icon_view.cc",
diff --git a/chrome/browser/ui/app_list/extension_app_context_menu.cc b/chrome/browser/ui/app_list/extension_app_context_menu.cc
index 89df9b0..1c5513f 100644
--- a/chrome/browser/ui/app_list/extension_app_context_menu.cc
+++ b/chrome/browser/ui/app_list/extension_app_context_menu.cc
@@ -7,7 +7,6 @@
 #include "ash/public/cpp/app_menu_constants.h"
 #include "base/bind.h"
 #include "chrome/browser/extensions/context_menu_matcher.h"
-#include "chrome/browser/extensions/extension_util.h"
 #include "chrome/browser/extensions/menu_manager.h"
 #include "chrome/browser/prefs/incognito_mode_prefs.h"
 #include "chrome/browser/profiles/profile.h"
@@ -54,11 +53,7 @@
   // If --enable-new-bookmark-apps is enabled, then only check if
   // USE_LAUNCH_TYPE_WINDOW is checked, as USE_LAUNCH_TYPE_PINNED (i.e. open
   // as pinned tab) and fullscreen-by-default windows do not exist.
-  bool launch_in_window =
-      extensions::util::IsNewBookmarkAppsEnabled()
-          ? IsCommandIdChecked(ash::USE_LAUNCH_TYPE_WINDOW)
-          : !(IsCommandIdChecked(ash::USE_LAUNCH_TYPE_PINNED) ||
-              IsCommandIdChecked(ash::USE_LAUNCH_TYPE_REGULAR));
+  bool launch_in_window = IsCommandIdChecked(ash::USE_LAUNCH_TYPE_WINDOW);
   return launch_in_window ? IDS_APP_LIST_CONTEXT_MENU_NEW_WINDOW
                           : IDS_APP_LIST_CONTEXT_MENU_NEW_TAB;
 }
@@ -115,7 +110,7 @@
     if (controller()->CanDoShowAppInfoFlow()) {
       AddContextMenuOption(menu_model, ash::SHOW_APP_INFO,
                            IDS_APP_CONTEXT_MENU_SHOW_INFO);
-  }
+    }
   }
 }
 
@@ -189,17 +184,13 @@
     controller()->DoShowAppInfoFlow(profile(), app_id());
   } else if (command_id >= ash::USE_LAUNCH_TYPE_COMMAND_START &&
              command_id < ash::USE_LAUNCH_TYPE_COMMAND_END) {
-    extensions::LaunchType launch_type = static_cast<extensions::LaunchType>(
-        command_id - ash::USE_LAUNCH_TYPE_COMMAND_START);
-    // When bookmark apps are enabled, hosted apps can only toggle between
-    // LAUNCH_TYPE_WINDOW and LAUNCH_TYPE_REGULAR.
-    if (extensions::util::IsNewBookmarkAppsEnabled()) {
-      launch_type = (controller()->GetExtensionLaunchType(profile(),
-                                                          app_id()) ==
-                     extensions::LAUNCH_TYPE_WINDOW)
-                        ? extensions::LAUNCH_TYPE_REGULAR
-                        : extensions::LAUNCH_TYPE_WINDOW;
-    }
+    // Hosted apps can only toggle between LAUNCH_TYPE_WINDOW and
+    // LAUNCH_TYPE_REGULAR.
+    extensions::LaunchType launch_type =
+        (controller()->GetExtensionLaunchType(profile(), app_id()) ==
+         extensions::LAUNCH_TYPE_WINDOW)
+            ? extensions::LAUNCH_TYPE_REGULAR
+            : extensions::LAUNCH_TYPE_WINDOW;
     controller()->SetExtensionLaunchType(profile(), app_id(), launch_type);
   } else if (command_id == ash::OPTIONS) {
     controller()->ShowOptionsPage(profile(), app_id());
diff --git a/chrome/browser/ui/ash/kiosk_next_shell_client_browsertest.cc b/chrome/browser/ui/ash/kiosk_next_shell_client_browsertest.cc
index f87920a..f5be1eca 100644
--- a/chrome/browser/ui/ash/kiosk_next_shell_client_browsertest.cc
+++ b/chrome/browser/ui/ash/kiosk_next_shell_client_browsertest.cc
@@ -17,10 +17,12 @@
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/extensions/component_loader.h"
 #include "chrome/browser/ui/ash/kiosk_next_shell_client.h"
+#include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
 #include "chrome/common/extensions/extension_constants.h"
 #include "components/prefs/pref_service.h"
 #include "components/user_manager/user.h"
+#include "components/user_manager/user_manager.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/test/test_utils.h"
 #include "extensions/browser/app_window/app_window.h"
@@ -96,6 +98,19 @@
   EXPECT_TRUE(waiter.WaitForShownWithTimeout(TestTimeouts::action_timeout()));
 }
 
+IN_PROC_BROWSER_TEST_F(KioskNextShellClientTest, PRE_BrowserNotLaunched) {
+  LoginAndEnableKioskNextShellPref();
+}
+
+// Ensures that browser is not launched when feature is enabled and prefs allow.
+IN_PROC_BROWSER_TEST_F(KioskNextShellClientTest, BrowserNotLaunched) {
+  extensions::ComponentLoader::EnableBackgroundExtensionsForTesting();
+  Login("username");
+  Profile* profile = ProfileHelper::Get()->GetProfileByUser(
+      user_manager::UserManager::Get()->GetActiveUser());
+  EXPECT_EQ(0u, chrome::GetBrowserCount(profile));
+}
+
 // Checks that the Kiosk Next Home window does not launch in sign-in when
 // its pref is disabled.
 IN_PROC_BROWSER_TEST_F(KioskNextShellClientTest, KioskNextShellNotLaunched) {
diff --git a/chrome/browser/ui/ash/launcher/extension_launcher_context_menu.cc b/chrome/browser/ui/ash/launcher/extension_launcher_context_menu.cc
index 73bd4c5e..454b57d 100644
--- a/chrome/browser/ui/ash/launcher/extension_launcher_context_menu.cc
+++ b/chrome/browser/ui/ash/launcher/extension_launcher_context_menu.cc
@@ -123,14 +123,11 @@
       SetLaunchType(extensions::LAUNCH_TYPE_REGULAR);
       break;
     case ash::LAUNCH_TYPE_WINDOW: {
-      extensions::LaunchType launch_type = extensions::LAUNCH_TYPE_WINDOW;
-      // With bookmark apps enabled, hosted apps can only toggle between
-      // LAUNCH_WINDOW and LAUNCH_REGULAR.
-      if (extensions::util::IsNewBookmarkAppsEnabled()) {
-        launch_type = GetLaunchType() == extensions::LAUNCH_TYPE_WINDOW
-                          ? extensions::LAUNCH_TYPE_REGULAR
-                          : extensions::LAUNCH_TYPE_WINDOW;
-      }
+      // Hosted apps can only toggle between LAUNCH_WINDOW and LAUNCH_REGULAR.
+      extensions::LaunchType launch_type =
+          GetLaunchType() == extensions::LAUNCH_TYPE_WINDOW
+              ? extensions::LAUNCH_TYPE_REGULAR
+              : extensions::LAUNCH_TYPE_WINDOW;
       SetLaunchType(launch_type);
       break;
     }
diff --git a/chrome/browser/ui/ash/launcher/launcher_controller_helper.cc b/chrome/browser/ui/ash/launcher/launcher_controller_helper.cc
index 4b28e031..bdbba5e 100644
--- a/chrome/browser/ui/ash/launcher/launcher_controller_helper.cc
+++ b/chrome/browser/ui/ash/launcher/launcher_controller_helper.cc
@@ -75,14 +75,12 @@
 
   // Bookmark app windows should match their launch url extension despite
   // their web extents.
-  if (extensions::util::IsNewBookmarkAppsEnabled()) {
-    for (const auto& i : extensions) {
-      if (i.get()->from_bookmark() &&
-          extensions::IsInNavigationScopeForLaunchUrl(
-              extensions::AppLaunchInfo::GetLaunchWebURL(i.get()), url) &&
-          !extensions::LaunchesInWindow(profile, i.get())) {
-        return i.get();
-      }
+  for (const auto& i : extensions) {
+    if (i.get()->from_bookmark() &&
+        extensions::IsInNavigationScopeForLaunchUrl(
+            extensions::AppLaunchInfo::GetLaunchWebURL(i.get()), url) &&
+        !extensions::LaunchesInWindow(profile, i.get())) {
+      return i.get();
     }
   }
   return nullptr;
diff --git a/chrome/browser/ui/ash/network/network_connect_delegate_chromeos.cc b/chrome/browser/ui/ash/network/network_connect_delegate_chromeos.cc
index 0c054a32..79da39cb 100644
--- a/chrome/browser/ui/ash/network/network_connect_delegate_chromeos.cc
+++ b/chrome/browser/ui/ash/network/network_connect_delegate_chromeos.cc
@@ -8,7 +8,7 @@
 #include "chrome/browser/ui/ash/network/enrollment_dialog_view.h"
 #include "chrome/browser/ui/ash/network/network_state_notifier.h"
 #include "chrome/browser/ui/ash/system_tray_client.h"
-#include "chrome/browser/ui/webui/chromeos/mobile_setup_dialog.h"
+#include "chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog_launcher.h"
 
 namespace {
 
@@ -50,7 +50,7 @@
     const std::string& network_id) {
   if (!IsUIAvailable())
     return;
-  chromeos::MobileSetupDialog::ShowByNetworkId(network_id);
+  chromeos::cellular_setup::OpenCellularSetupDialog(network_id);
 }
 
 void NetworkConnectDelegateChromeOS::ShowNetworkConnectError(
diff --git a/chrome/browser/ui/browser_browsertest.cc b/chrome/browser/ui/browser_browsertest.cc
index aed3350..03d5f11 100644
--- a/chrome/browser/ui/browser_browsertest.cc
+++ b/chrome/browser/ui/browser_browsertest.cc
@@ -34,7 +34,6 @@
 #include "chrome/browser/devtools/devtools_window_testing.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/extensions/extension_util.h"
 #include "chrome/browser/extensions/tab_helper.h"
 #include "chrome/browser/first_run/first_run.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
@@ -1216,19 +1215,14 @@
       chrome::startup::IS_FIRST_RUN : chrome::startup::IS_NOT_FIRST_RUN;
   StartupBrowserCreatorImpl launch(base::FilePath(), command_line, first_run);
 
-  bool new_bookmark_apps_enabled = extensions::util::IsNewBookmarkAppsEnabled();
-
-  // If the new bookmark app flow is enabled, the app should open as an tab.
-  // Otherwise the app should open as an app window.
-  EXPECT_EQ(!new_bookmark_apps_enabled,
-            launch.OpenApplicationWindow(browser()->profile()));
-  EXPECT_EQ(new_bookmark_apps_enabled,
-            launch.OpenApplicationTab(browser()->profile()));
+  // The app should open as a tab.
+  EXPECT_FALSE(launch.OpenApplicationWindow(browser()->profile()));
+  EXPECT_TRUE(launch.OpenApplicationTab(browser()->profile()));
 
   // Check that a the number of browsers and tabs is correct.
   unsigned int expected_browsers = 1;
   int expected_tabs = 1;
-  new_bookmark_apps_enabled ? expected_tabs++ : expected_browsers++;
+  expected_tabs++;
 
   EXPECT_EQ(expected_browsers, chrome::GetBrowserCount(browser()->profile()));
   EXPECT_EQ(expected_tabs, browser()->tab_strip_model()->count());
diff --git a/chrome/browser/ui/browser_command_controller.cc b/chrome/browser/ui/browser_command_controller.cc
index fac29c30..409fb26 100644
--- a/chrome/browser/ui/browser_command_controller.cc
+++ b/chrome/browser/ui/browser_command_controller.cc
@@ -23,7 +23,6 @@
 #include "chrome/browser/defaults.h"
 #include "chrome/browser/devtools/devtools_window.h"
 #include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/extensions/extension_util.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
 #include "chrome/browser/prefs/incognito_mode_prefs.h"
 #include "chrome/browser/profiles/profile.h"
@@ -934,10 +933,8 @@
   UpdateShowSyncState(true);
 
   // Navigation commands
-  command_updater_.UpdateCommandEnabled(
-      IDC_HOME,
-      normal_window ||
-          (extensions::util::IsNewBookmarkAppsEnabled() && browser_->is_app()));
+  command_updater_.UpdateCommandEnabled(IDC_HOME,
+                                        normal_window || browser_->is_app());
 
   const bool is_experimental_hosted_app =
       extensions::HostedAppBrowserController::IsForExperimentalHostedAppBrowser(
diff --git a/chrome/browser/ui/cocoa/apps/app_shim_menu_controller_mac_browsertest.mm b/chrome/browser/ui/cocoa/apps/app_shim_menu_controller_mac_browsertest.mm
index 791746d5..d1a5e13 100644
--- a/chrome/browser/ui/cocoa/apps/app_shim_menu_controller_mac_browsertest.mm
+++ b/chrome/browser/ui/cocoa/apps/app_shim_menu_controller_mac_browsertest.mm
@@ -12,7 +12,6 @@
 #import "base/mac/scoped_objc_class_swizzler.h"
 #include "base/macros.h"
 #include "base/strings/sys_string_conversions.h"
-#include "base/test/scoped_feature_list.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/apps/app_shim/extension_app_shim_handler_mac.h"
 #include "chrome/browser/apps/platform_apps/app_browsertest_util.h"
@@ -43,12 +42,6 @@
         hosted_app_(nullptr),
         initial_menu_item_count_(0) {}
 
-  // testing::Test:
-  void SetUp() override {
-    scoped_feature_list_.InitAndEnableFeature(features::kBookmarkApps);
-    PlatformAppBrowserTest::SetUp();
-  }
-
   // Start testing apps and wait for them to launch. |flags| is a bitmask of
   // AvailableApps.
   void SetUpApps(int flags) {
@@ -136,8 +129,6 @@
   NSUInteger initial_menu_item_count_;
 
  private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-
   DISALLOW_COPY_AND_ASSIGN(AppShimMenuControllerBrowserTest);
 };
 
diff --git a/chrome/browser/ui/extensions/hosted_app_browsertest.cc b/chrome/browser/ui/extensions/hosted_app_browsertest.cc
index 4776c0f5..4be0617 100644
--- a/chrome/browser/ui/extensions/hosted_app_browsertest.cc
+++ b/chrome/browser/ui/extensions/hosted_app_browsertest.cc
@@ -282,9 +282,6 @@
       enabled_features.push_back(features::kDesktopPWAWindowing);
     } else {
       disabled_features.push_back(features::kDesktopPWAWindowing);
-#if defined(OS_MACOSX)
-      enabled_features.push_back(features::kBookmarkApps);
-#endif
     }
 
     auto& features = use_custom_tab_flag ? enabled_features : disabled_features;
diff --git a/chrome/browser/ui/omnibox/chrome_omnibox_edit_controller.cc b/chrome/browser/ui/omnibox/chrome_omnibox_edit_controller.cc
index 3d5d03aa..9600709 100644
--- a/chrome/browser/ui/omnibox/chrome_omnibox_edit_controller.cc
+++ b/chrome/browser/ui/omnibox/chrome_omnibox_edit_controller.cc
@@ -38,7 +38,6 @@
 }
 
 void ChromeOmniboxEditController::OnInputInProgress(bool in_progress) {
-  GetLocationBarModel()->set_input_in_progress(in_progress);
   UpdateWithoutTabRestore();
 }
 
diff --git a/chrome/browser/ui/startup/startup_browser_creator.cc b/chrome/browser/ui/startup/startup_browser_creator.cc
index fc57da6b..1aa0191 100644
--- a/chrome/browser/ui/startup/startup_browser_creator.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator.cc
@@ -69,8 +69,11 @@
 #include "printing/buildflags/buildflags.h"
 
 #if defined(OS_CHROMEOS)
+#include "ash/public/cpp/ash_features.h"
+#include "ash/public/cpp/ash_pref_names.h"
 #include "chrome/browser/chromeos/app_mode/app_launch_utils.h"
 #include "chrome/browser/chromeos/login/demo_mode/demo_app_launcher.h"
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
 #include "chromeos/constants/chromeos_switches.h"
 #include "chromeos/cryptohome/cryptohome_parameters.h"
@@ -277,6 +280,27 @@
 #endif  // !defined(OS_CHROMEOS)
 }
 
+bool IsSilentLaunchEnabled(const base::CommandLine& command_line,
+                           const Profile* profile) {
+  // Note: This check should have been done in ProcessCmdLineImpl()
+  // before calling this function. However chromeos/login/login_utils.cc
+  // calls this function directly (see comments there) so it has to be checked
+  // again.
+  bool silent_launch = command_line.HasSwitch(switches::kSilentLaunch);
+
+#if defined(CHROMEOS)
+  DCHECK(!chromeos::ProfileHelper::IsSigninProfile(profile));
+  if (base::FeatureList::IsEnabled(ash::features::kKioskNextShell)) {
+    const PrefService* prefs = profile->GetPrefs();
+    if (prefs->GetBoolean(ash::prefs::kKioskNextShellEnabled)) {
+      silent_launch = true;
+    }
+  }
+#endif
+
+  return silent_launch;
+}
+
 }  // namespace
 
 StartupBrowserCreator::StartupBrowserCreator()
@@ -336,13 +360,7 @@
     profile = profile->GetOffTheRecordProfile();
 #endif
 
-  // Note: This check should have been done in ProcessCmdLineImpl()
-  // before calling this function. However chromeos/login/login_utils.cc
-  // calls this function directly (see comments there) so it has to be checked
-  // again.
-  const bool silent_launch = command_line.HasSwitch(switches::kSilentLaunch);
-
-  if (!silent_launch) {
+  if (!IsSilentLaunchEnabled(command_line, profile)) {
     StartupBrowserCreatorImpl lwp(cur_dir, command_line, this, is_first_run);
     const std::vector<GURL> urls_to_launch =
         GetURLsFromCommandLine(command_line, cur_dir, profile);
diff --git a/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc b/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc
index 0881e5ff..2cb6b925 100644
--- a/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc
@@ -22,7 +22,6 @@
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/extensions/extension_util.h"
 #include "chrome/browser/extensions/launch_util.h"
 #include "chrome/browser/first_run/first_run.h"
 #include "chrome/browser/infobars/infobar_service.h"
@@ -403,11 +402,9 @@
   EXPECT_EQ(2, tab_strip->count());
   EXPECT_EQ(tab_strip->GetActiveWebContents(), tab_strip->GetWebContentsAt(1));
 
-  // If new bookmark apps are enabled, it should be a standard tabbed window,
-  // not an app window; otherwise the reverse should be true.
-  bool new_bookmark_apps_enabled = extensions::util::IsNewBookmarkAppsEnabled();
-  EXPECT_EQ(!new_bookmark_apps_enabled, browser()->is_app());
-  EXPECT_EQ(new_bookmark_apps_enabled, browser()->is_type_tabbed());
+  // It should be a standard tabbed window, not an app window.
+  EXPECT_FALSE(browser()->is_app());
+  EXPECT_TRUE(browser()->is_type_tabbed());
 }
 
 IN_PROC_BROWSER_TEST_F(StartupBrowserCreatorTest, OpenAppShortcutWindowPref) {
diff --git a/chrome/browser/ui/thumbnails/thumbnail_page_event_adapter.cc b/chrome/browser/ui/thumbnails/thumbnail_page_event_adapter.cc
index 1dfa7ce..3af5260 100644
--- a/chrome/browser/ui/thumbnails/thumbnail_page_event_adapter.cc
+++ b/chrome/browser/ui/thumbnails/thumbnail_page_event_adapter.cc
@@ -77,6 +77,11 @@
     observer.PageLoadStarted();
 }
 
+void ThumbnailPageEventAdapter::DidFirstVisuallyNonEmptyPaint() {
+  for (auto& observer : observers_)
+    observer.PagePainted();
+}
+
 void ThumbnailPageEventAdapter::DidFinishLoad(
     content::RenderFrameHost* render_frame_host,
     const GURL& validated_url) {
diff --git a/chrome/browser/ui/thumbnails/thumbnail_page_event_adapter.h b/chrome/browser/ui/thumbnails/thumbnail_page_event_adapter.h
index 7df0da26..4314772 100644
--- a/chrome/browser/ui/thumbnails/thumbnail_page_event_adapter.h
+++ b/chrome/browser/ui/thumbnails/thumbnail_page_event_adapter.h
@@ -41,6 +41,7 @@
   void DidFinishNavigation(
       content::NavigationHandle* navigation_handle) override;
   void DocumentAvailableInMainFrame() override;
+  void DidFirstVisuallyNonEmptyPaint() override;
   void DidFinishLoad(content::RenderFrameHost* render_frame_host,
                      const GURL& validated_url) override;
   void DidFailLoad(content::RenderFrameHost* render_frame_host,
diff --git a/chrome/browser/ui/thumbnails/thumbnail_page_observer.h b/chrome/browser/ui/thumbnails/thumbnail_page_observer.h
index 2255ee3..70c03a2b 100644
--- a/chrome/browser/ui/thumbnails/thumbnail_page_observer.h
+++ b/chrome/browser/ui/thumbnails/thumbnail_page_observer.h
@@ -32,6 +32,9 @@
   // Called when the page/tab's visibility changes.
   virtual void VisibilityChanged(bool visible) = 0;
 
+  // Called when the page is painted for the first time.
+  virtual void PagePainted() = 0;
+
   // Called when a page begins to load.
   virtual void PageLoadStarted() = 0;
 
diff --git a/chrome/browser/ui/thumbnails/thumbnail_tab_helper.cc b/chrome/browser/ui/thumbnails/thumbnail_tab_helper.cc
index 21185ea..5665176 100644
--- a/chrome/browser/ui/thumbnails/thumbnail_tab_helper.cc
+++ b/chrome/browser/ui/thumbnails/thumbnail_tab_helper.cc
@@ -62,6 +62,10 @@
                          web_contents()->GetVisibleURL());
 }
 
+void ThumbnailTabHelper::PagePainted() {
+  page_painted_ = true;
+}
+
 void ThumbnailTabHelper::PageLoadFinished() {
   TransitionLoadingState(LoadingState::kLoadFinished,
                          web_contents()->GetVisibleURL());
@@ -73,9 +77,7 @@
   // tab did when the user last visited it.
   const bool was_visible = view_is_visible_;
   view_is_visible_ = visible;
-  if (!was_visible && visible) {
-    last_visible_start_time_ = base::TimeTicks::Now();
-  } else if (was_visible && !visible) {
+  if (was_visible && !visible) {
     StartThumbnailCapture();
   }
 }
@@ -97,11 +99,9 @@
   if (web_contents()->GetVisibleURL().is_empty())
     return;
 
-  // Don't capture pages that have been visible for a short time (e.g. during a
-  // tab scrub).
-  base::TimeTicks start_time = base::TimeTicks::Now();
-  constexpr base::TimeDelta kMinVisibleTime = base::TimeDelta::FromSeconds(2);
-  if (start_time - last_visible_start_time_ < kMinVisibleTime)
+  // Don't capture pages that have not been loading and visible long enough to
+  // actually paint.
+  if (!page_painted_)
     return;
 
   content::RenderWidgetHostView* const source_view =
@@ -128,7 +128,7 @@
   source_view->CopyFromSurface(
       copy_info.copy_rect, copy_info.target_size,
       base::BindOnce(&ThumbnailTabHelper::ProcessCapturedThumbnail,
-                     weak_factory_.GetWeakPtr(), start_time));
+                     weak_factory_.GetWeakPtr(), base::TimeTicks::Now()));
 }
 
 void ThumbnailTabHelper::ProcessCapturedThumbnail(
@@ -176,6 +176,7 @@
       if (!is_similar_url) {
         current_url_ = url;
         ClearThumbnail();
+        page_painted_ = false;
         loading_state_ = state;
       } else {
         loading_state_ = std::max(loading_state_, state);
diff --git a/chrome/browser/ui/thumbnails/thumbnail_tab_helper.h b/chrome/browser/ui/thumbnails/thumbnail_tab_helper.h
index b540367..d55098d 100644
--- a/chrome/browser/ui/thumbnails/thumbnail_tab_helper.h
+++ b/chrome/browser/ui/thumbnails/thumbnail_tab_helper.h
@@ -26,6 +26,7 @@
   // ThumbnailWebContentsObserver:
   void TopLevelNavigationStarted(const GURL& url) override;
   void TopLevelNavigationEnded(const GURL& url) override;
+  void PagePainted() override;
   void PageLoadStarted() override;
   void PageLoadFinished() override;
   void VisibilityChanged(bool visible) override;
@@ -62,8 +63,7 @@
   // VisibilityChanged() for more information.
   bool view_is_visible_;  // set in constructor
 
-  // When the page last became visible.
-  base::TimeTicks last_visible_start_time_;
+  bool page_painted_ = false;
 
   LoadingState loading_state_ = LoadingState::kNone;
   GURL current_url_;
diff --git a/chrome/browser/ui/toolbar/app_menu_model.cc b/chrome/browser/ui/toolbar/app_menu_model.cc
index ea6d9c8..316e5b0 100644
--- a/chrome/browser/ui/toolbar/app_menu_model.cc
+++ b/chrome/browser/ui/toolbar/app_menu_model.cc
@@ -206,9 +206,7 @@
 // - Option to enable profiling.
 void ToolsMenuModel::Build(Browser* browser) {
   AddItemWithStringId(IDC_SAVE_PAGE, IDS_SAVE_PAGE);
-
-  if (extensions::util::IsNewBookmarkAppsEnabled())
-    AddItemWithStringId(IDC_CREATE_SHORTCUT, IDS_ADD_TO_OS_LAUNCH_SURFACE);
+  AddItemWithStringId(IDC_CREATE_SHORTCUT, IDS_ADD_TO_OS_LAUNCH_SURFACE);
 
   AddSeparator(ui::NORMAL_SEPARATOR);
   AddItemWithStringId(IDC_CLEAR_BROWSING_DATA, IDS_CLEAR_BROWSING_DATA);
@@ -766,8 +764,7 @@
 
   AddItemWithStringId(IDC_FIND, IDS_FIND);
 
-  if (extensions::util::IsNewBookmarkAppsEnabled() &&
-      banners::AppBannerManager::IsExperimentalAppBannersEnabled()) {
+  if (banners::AppBannerManager::IsExperimentalAppBannersEnabled()) {
     const extensions::Extension* pwa =
         base::FeatureList::IsEnabled(features::kDesktopPWAWindowing)
             ? extensions::util::GetPwaForSecureActiveTab(browser_)
diff --git a/chrome/browser/ui/toolbar/back_forward_menu_model_unittest.cc b/chrome/browser/ui/toolbar/back_forward_menu_model_unittest.cc
index 1ed3931..1c53b55 100644
--- a/chrome/browser/ui/toolbar/back_forward_menu_model_unittest.cc
+++ b/chrome/browser/ui/toolbar/back_forward_menu_model_unittest.cc
@@ -468,10 +468,19 @@
   EXPECT_EQ(0, back_model->GetItemCount());
   EXPECT_FALSE(back_model->ItemHasCommand(1));
 
+  // Note: Multiple navigations to the same URL in a row have to be
+  // renderer-initiated.  If they were browser-initiated, the
+  // NavigationController would treat them as reloads.
   LoadURLAndUpdateState("http://www.a.com/1", "A B");
-  LoadURLAndUpdateState("http://www.a.com/1", "A & B");
+  NavigationSimulator::NavigateAndCommitFromDocument(GURL("http://www.a.com/1"),
+                                                     main_rfh());
+  web_contents()->UpdateTitleForEntry(controller().GetLastCommittedEntry(),
+                                      base::UTF8ToUTF16("A & B"));
   LoadURLAndUpdateState("http://www.a.com/2", "A && B");
-  LoadURLAndUpdateState("http://www.a.com/2", "A &&& B");
+  NavigationSimulator::NavigateAndCommitFromDocument(GURL("http://www.a.com/2"),
+                                                     main_rfh());
+  web_contents()->UpdateTitleForEntry(controller().GetLastCommittedEntry(),
+                                      base::UTF8ToUTF16("A &&& B"));
   LoadURLAndUpdateState("http://www.a.com/3", "");
 
   EXPECT_EQ(6, back_model->GetItemCount());
diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views_browsertest.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views_browsertest.cc
index d8891a2..3c670af 100644
--- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views_browsertest.cc
+++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views_browsertest.cc
@@ -15,11 +15,6 @@
 #include "chrome/browser/ui/test/test_browser_dialog.h"
 #include "chrome/test/base/testing_profile.h"
 
-#if defined(OS_MACOSX)
-#include "base/command_line.h"
-#include "chrome/common/chrome_switches.h"
-#endif
-
 class AppInfoDialogBrowserTest : public DialogBrowserTest {
  public:
   AppInfoDialogBrowserTest() {}
@@ -40,13 +35,6 @@
 
   void TearDownOnMainThread() override { extension_environment_ = nullptr; }
 
-#if defined(OS_MACOSX)
-  // content::BrowserTestBase:
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    command_line->AppendSwitch(switches::kEnableAppInfoDialogMac);
-  }
-#endif
-
  private:
   std::unique_ptr<extensions::TestExtensionEnvironment> extension_environment_;
   scoped_refptr<const extensions::Extension> extension_;
diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_summary_panel.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_summary_panel.cc
index 97e3deb..2493308 100644
--- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_summary_panel.cc
+++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_summary_panel.cc
@@ -12,9 +12,7 @@
 #include "base/callback_forward.h"
 #include "base/logging.h"
 #include "base/strings/utf_string_conversions.h"
-#include "build/build_config.h"
 #include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/extensions/extension_util.h"
 #include "chrome/browser/extensions/launch_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
@@ -60,47 +58,14 @@
 };
 
 LaunchOptionsComboboxModel::LaunchOptionsComboboxModel() {
-  if (extensions::util::IsNewBookmarkAppsEnabled()) {
-    // When bookmark apps are enabled, hosted apps can only toggle between
-    // LAUNCH_TYPE_WINDOW and LAUNCH_TYPE_REGULAR.
-    // TODO(sashab): Use a checkbox for this choice instead of combobox.
-    launch_types_.push_back(extensions::LAUNCH_TYPE_REGULAR);
-    launch_type_messages_.push_back(
-        l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_TAB));
-
-    if (extensions::util::CanHostedAppsOpenInWindows()) {
-      launch_types_.push_back(extensions::LAUNCH_TYPE_WINDOW);
-      launch_type_messages_.push_back(
-          l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_WINDOW));
-    }
-  } else {
-    launch_types_.push_back(extensions::LAUNCH_TYPE_REGULAR);
-    launch_type_messages_.push_back(
-        l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_REGULAR));
-
-    launch_types_.push_back(extensions::LAUNCH_TYPE_PINNED);
-    launch_type_messages_.push_back(
-        l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_PINNED));
-
-    if (extensions::util::CanHostedAppsOpenInWindows()) {
-      launch_types_.push_back(extensions::LAUNCH_TYPE_WINDOW);
-      launch_type_messages_.push_back(
-          l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_WINDOW));
-    }
-#if defined(OS_MACOSX)
-    // Mac does not support standalone web app browser windows or maximize
-    // unless the new bookmark apps system is enabled.
-    launch_types_.push_back(extensions::LAUNCH_TYPE_FULLSCREEN);
-    launch_type_messages_.push_back(
-        l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_FULLSCREEN));
-#else
-    // Even though the launch type is Full Screen, it is more accurately
-    // described as Maximized in non-Mac OSs.
-    launch_types_.push_back(extensions::LAUNCH_TYPE_FULLSCREEN);
-    launch_type_messages_.push_back(
-        l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_MAXIMIZED));
-#endif
-  }
+  // Hosted apps can only toggle between LAUNCH_TYPE_WINDOW and
+  // LAUNCH_TYPE_REGULAR.
+  launch_types_.push_back(extensions::LAUNCH_TYPE_REGULAR);
+  launch_type_messages_.push_back(
+      l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_TAB));
+  launch_types_.push_back(extensions::LAUNCH_TYPE_WINDOW);
+  launch_type_messages_.push_back(
+      l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_WINDOW));
 }
 
 LaunchOptionsComboboxModel::~LaunchOptionsComboboxModel() {
@@ -162,8 +127,6 @@
               DISTANCE_RELATED_CONTROL_VERTICAL_SMALL)));
 
   if (!app_->description().empty()) {
-    // TODO(sashab): Clip the app's description to 4 lines, and use Label's
-    // built-in elide behavior to add ellipses at the end: crbug.com/358053
     const size_t max_length = 400;
     base::string16 text = base::UTF8ToUTF16(app_->description());
     if (text.length() > max_length) {
diff --git a/chrome/browser/ui/views/extensions/bookmark_app_confirmation_view.cc b/chrome/browser/ui/views/extensions/bookmark_app_confirmation_view.cc
index eef0c6a..51f29db9 100644
--- a/chrome/browser/ui/views/extensions/bookmark_app_confirmation_view.cc
+++ b/chrome/browser/ui/views/extensions/bookmark_app_confirmation_view.cc
@@ -12,7 +12,6 @@
 #include "base/strings/string16.h"
 #include "base/strings/string_util.h"
 #include "build/build_config.h"
-#include "chrome/browser/extensions/extension_util.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/extensions/web_app_info_image_source.h"
 #include "chrome/common/chrome_features.h"
@@ -87,17 +86,12 @@
   layout->AddPaddingRow(
       views::GridLayout::kFixedSize,
       layout_provider->GetDistanceMetric(DISTANCE_CONTROL_LIST_VERTICAL));
-
-  // When CanHostedAppsOpenInWindows() returns false, do not show the open as
-  // window checkbox to avoid confusing users.
-  if (extensions::util::CanHostedAppsOpenInWindows()) {
-    open_as_window_checkbox_ = new views::Checkbox(
-        l10n_util::GetStringUTF16(IDS_BOOKMARK_APP_BUBBLE_OPEN_AS_WINDOW));
-    open_as_window_checkbox_->SetChecked(web_app_info_.open_as_window);
-    layout->StartRow(views::GridLayout::kFixedSize, kColumnSetId);
-    layout->SkipColumns(1);
-    layout->AddView(open_as_window_checkbox_);
-  }
+  open_as_window_checkbox_ = new views::Checkbox(
+      l10n_util::GetStringUTF16(IDS_BOOKMARK_APP_BUBBLE_OPEN_AS_WINDOW));
+  open_as_window_checkbox_->SetChecked(web_app_info_.open_as_window);
+  layout->StartRow(views::GridLayout::kFixedSize, kColumnSetId);
+  layout->SkipColumns(1);
+  layout->AddView(open_as_window_checkbox_);
 
   title_tf_->SelectAll(true);
   chrome::RecordDialogCreation(
diff --git a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
index 83883887..de8f1d0 100644
--- a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
+++ b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
@@ -8,10 +8,8 @@
 #include <memory>
 #include <utility>
 
-#include "base/time/time.h"
 #include "chrome/browser/ui/layout_constants.h"
 #include "chrome/browser/ui/omnibox/omnibox_theme.h"
-#include "chrome/browser/ui/views/location_bar/location_bar_separator_view.h"
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/material_design/material_design_controller.h"
@@ -46,6 +44,10 @@
 // or label.
 constexpr int kIconLabelBubbleSpaceBesideSeparator = 8;
 
+// The length of the separator's fade animation. These values are empirical.
+constexpr int kIconLabelBubbleFadeInDurationMs = 250;
+constexpr int kIconLabelBubbleFadeOutDurationMs = 175;
+
 // The type of tweening for the animation.
 const gfx::Tween::Type kIconLabelBubbleTweenType = gfx::Tween::EASE_IN_OUT;
 
@@ -56,14 +58,77 @@
 const double kIconLabelBubbleOpenTimeFraction = 0.2;
 }  // namespace
 
+//////////////////////////////////////////////////////////////////
+// SeparatorView class
+
+IconLabelBubbleView::SeparatorView::SeparatorView(IconLabelBubbleView* owner) {
+  DCHECK(owner);
+  owner_ = owner;
+
+  SetPaintToLayer();
+  layer()->SetFillsBoundsOpaquely(false);
+}
+
+void IconLabelBubbleView::SeparatorView::OnPaint(gfx::Canvas* canvas) {
+  const SkColor background_color = owner_->GetParentBackgroundColor();
+  const SkColor separator_color =
+      SkColorSetA(color_utils::GetColorWithMaxContrast(background_color), 0x69);
+  const float x = GetLocalBounds().right() -
+                  owner_->GetEndPaddingWithSeparator() -
+                  1.0f / canvas->image_scale();
+  canvas->DrawLine(gfx::PointF(x, GetLocalBounds().y()),
+                   gfx::PointF(x, GetLocalBounds().bottom()), separator_color);
+}
+
+void IconLabelBubbleView::SeparatorView::UpdateOpacity() {
+  if (!visible())
+    return;
+
+  // When using focus rings are visible we should hide the separator instantly
+  // when the IconLabelBubbleView is focused. Otherwise we should follow the
+  // inkdrop.
+  if (owner_->focus_ring() && owner_->HasFocus()) {
+    layer()->SetOpacity(0.0f);
+    return;
+  }
+
+  views::InkDrop* ink_drop = owner_->GetInkDrop();
+  DCHECK(ink_drop);
+
+  // If an inkdrop highlight or ripple is animating in or visible, the
+  // separator should fade out.
+  views::InkDropState state = ink_drop->GetTargetInkDropState();
+  float opacity = 0.0f;
+  float duration = kIconLabelBubbleFadeOutDurationMs;
+  if (!ink_drop->IsHighlightFadingInOrVisible() &&
+      (state == views::InkDropState::HIDDEN ||
+       state == views::InkDropState::ACTION_TRIGGERED ||
+       state == views::InkDropState::DEACTIVATED)) {
+    opacity = 1.0f;
+    duration = kIconLabelBubbleFadeInDurationMs;
+  }
+
+  if (disable_animation_for_test_) {
+    layer()->SetOpacity(opacity);
+  } else {
+    ui::ScopedLayerAnimationSettings animation(layer()->GetAnimator());
+    animation.SetTransitionDuration(
+        base::TimeDelta::FromMilliseconds(duration));
+    animation.SetTweenType(gfx::Tween::Type::EASE_IN);
+    layer()->SetOpacity(opacity);
+  }
+}
+
+//////////////////////////////////////////////////////////////////
+// IconLabelBubbleView class
+
 IconLabelBubbleView::IconLabelBubbleView(const gfx::FontList& font_list)
     : LabelButton(nullptr, base::string16()),
-      separator_view_(new LocationBarSeparatorView()) {
+      separator_view_(new SeparatorView(this)) {
   SetFontList(font_list);
   SetHorizontalAlignment(gfx::ALIGN_LEFT);
 
-  separator_view_->SetBackgroundColor(GetParentBackgroundColor());
-  UpdateSeparator();
+  separator_view_->SetVisible(ShouldShowSeparator());
   AddChildView(separator_view_);
 
   set_ink_drop_visible_opacity(
@@ -84,7 +149,7 @@
 IconLabelBubbleView::~IconLabelBubbleView() {}
 
 void IconLabelBubbleView::InkDropAnimationStarted() {
-  UpdateSeparator();
+  separator_view_->UpdateOpacity();
 }
 
 void IconLabelBubbleView::InkDropRippleAnimationEnded(
@@ -99,7 +164,8 @@
 void IconLabelBubbleView::SetLabel(const base::string16& label_text) {
   SetAccessibleName(label_text);
   label()->SetText(label_text);
-  UpdateSeparator();
+  separator_view_->SetVisible(ShouldShowSeparator());
+  separator_view_->UpdateOpacity();
 }
 
 void IconLabelBubbleView::SetImage(const gfx::ImageSkia& image_skia) {
@@ -188,17 +254,17 @@
                                     GetWidthBetweenIconAndSeparator());
   label()->SetBounds(label_x, 0, label_width, height());
 
-  const int icon_end = label()->text().empty() ? image()->bounds().right()
-                                               : label()->bounds().right();
-
   // The separator should be the same height as the icons.
-  const gfx::Size separator_size(kIconLabelBubbleSeparatorWidth,
-                                 GetLayoutConstant(LOCATION_BAR_ICON_SIZE));
-  const int separator_x = icon_end + GetWidthBetweenIconAndSeparator();
-  const int separator_y = (height() - separator_size.height()) / 2;
-  const gfx::Rect separator_bounds(gfx::Point(separator_x, separator_y),
-                                   separator_size);
-  separator_view_->SetBoundsRect(separator_bounds);
+  const int separator_height = GetLayoutConstant(LOCATION_BAR_ICON_SIZE);
+  gfx::Rect separator_bounds(label()->bounds());
+  separator_bounds.Inset(0, (separator_bounds.height() - separator_height) / 2);
+
+  float separator_width =
+      GetWidthBetweenIconAndSeparator() + GetEndPaddingWithSeparator();
+  int separator_x = label()->text().empty() ? image()->bounds().right()
+                                            : label()->bounds().right();
+  separator_view_->SetBounds(separator_x, separator_bounds.y(), separator_width,
+                             separator_height);
 
   UpdateHighlightPath();
 }
@@ -213,7 +279,6 @@
   LabelButton::OnNativeThemeChanged(native_theme);
   SetEnabledTextColors(GetTextColor());
   label()->SetBackgroundColor(GetParentBackgroundColor());
-  separator_view_->SetBackgroundColor(GetParentBackgroundColor());
   SchedulePaint();
 }
 
@@ -244,12 +309,12 @@
 }
 
 void IconLabelBubbleView::OnFocus() {
-  UpdateSeparator();
+  separator_view_->UpdateOpacity();
   LabelButton::OnFocus();
 }
 
 void IconLabelBubbleView::OnBlur() {
-  UpdateSeparator();
+  separator_view_->UpdateOpacity();
   LabelButton::OnBlur();
 }
 
@@ -445,34 +510,3 @@
     focus_ring()->SchedulePaint();
   }
 }
-
-void IconLabelBubbleView::UpdateSeparator() {
-  if (!ShouldShowSeparator()) {
-    separator_view_->SetVisible(false);
-    return;
-  }
-
-  separator_view_->SetVisible(true);
-
-  // When using focus rings, we should hide the separator instantly upon
-  // focus. Otherwise we should follow the inkdrop.
-  if (focus_ring() && HasFocus()) {
-    separator_view_->Hide();
-    return;
-  }
-
-  views::InkDrop* const ink_drop = GetInkDrop();
-  DCHECK(ink_drop);
-
-  // If an inkdrop highlight or ripple is animating in or visible, the
-  // separator should fade out.
-  const views::InkDropState state = ink_drop->GetTargetInkDropState();
-  if (!ink_drop->IsHighlightFadingInOrVisible() &&
-      (state == views::InkDropState::HIDDEN ||
-       state == views::InkDropState::ACTION_TRIGGERED ||
-       state == views::InkDropState::DEACTIVATED)) {
-    separator_view_->FadeIn();
-  } else {
-    separator_view_->FadeOut();
-  }
-}
diff --git a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h
index 3e09f4a..53ec4df 100644
--- a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h
+++ b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h
@@ -35,8 +35,6 @@
 class ImageView;
 }
 
-class LocationBarSeparatorView;
-
 // View used to draw a bubble, containing an icon and a label. We use this as a
 // base for the classes that handle the location icon (including the EV bubble),
 // tab-to-search UI, and content settings.
@@ -46,6 +44,30 @@
  public:
   static constexpr int kTrailingPaddingPreMd = 2;
 
+  // A view that draws the separator.
+  class SeparatorView : public views::View {
+   public:
+    explicit SeparatorView(IconLabelBubbleView* owner);
+
+    // views::View:
+    void OnPaint(gfx::Canvas* canvas) override;
+
+    // Updates the opacity based on the ink drop's state.
+    void UpdateOpacity();
+
+    void set_disable_animation_for_test(bool disable_animation_for_test) {
+      disable_animation_for_test_ = disable_animation_for_test;
+    }
+
+   private:
+    // Weak.
+    IconLabelBubbleView* owner_;
+
+    bool disable_animation_for_test_ = false;
+
+    DISALLOW_COPY_AND_ASSIGN(SeparatorView);
+  };
+
   explicit IconLabelBubbleView(const gfx::FontList& font_list);
   ~IconLabelBubbleView() override;
 
@@ -67,7 +89,7 @@
   SkColor GetParentBackgroundColor() const;
 
   // Exposed for testing.
-  LocationBarSeparatorView* separator_view() const { return separator_view_; }
+  SeparatorView* separator_view() const { return separator_view_; }
 
   // Exposed for testing.
   bool is_animating_label() const { return slide_animation_.is_animating(); }
@@ -199,10 +221,8 @@
   // bounds and the separator visibility.
   void UpdateHighlightPath();
 
-  void UpdateSeparator();
-
   // The contents of the bubble.
-  LocationBarSeparatorView* separator_view_;
+  SeparatorView* separator_view_;
 
   // The padding of the element that will be displayed after |this|. This value
   // is relevant for calculating the amount of space to reserve after the
diff --git a/chrome/browser/ui/views/location_bar/icon_label_bubble_view_unittest.cc b/chrome/browser/ui/views/location_bar/icon_label_bubble_view_unittest.cc
index 9fde86a..45a899d2 100644
--- a/chrome/browser/ui/views/location_bar/icon_label_bubble_view_unittest.cc
+++ b/chrome/browser/ui/views/location_bar/icon_label_bubble_view_unittest.cc
@@ -7,7 +7,6 @@
 #include "base/optional.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/ui/layout_constants.h"
-#include "chrome/browser/ui/views/location_bar/location_bar_separator_view.h"
 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
 #include "chrome/test/views/chrome_views_test_base.h"
 #include "components/strings/grit/components_strings.h"
diff --git a/chrome/browser/ui/views/location_bar/location_bar_separator_view.cc b/chrome/browser/ui/views/location_bar/location_bar_separator_view.cc
deleted file mode 100644
index ec44799..0000000
--- a/chrome/browser/ui/views/location_bar/location_bar_separator_view.cc
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/views/location_bar/location_bar_separator_view.h"
-
-#include "ui/compositor/layer.h"
-#include "ui/compositor/layer_animator.h"
-#include "ui/compositor/scoped_layer_animation_settings.h"
-#include "ui/gfx/animation/tween.h"
-#include "ui/gfx/canvas.h"
-#include "ui/gfx/color_utils.h"
-#include "ui/gfx/geometry/point_f.h"
-
-namespace {
-
-// These values are empirical.
-constexpr base::TimeDelta kLocationBarSeparatorFadeInDuration =
-    base::TimeDelta::FromMilliseconds(250);
-constexpr base::TimeDelta kLocationBarSeparatorFadeOutDuration =
-    base::TimeDelta::FromMilliseconds(175);
-
-}  //  namespace
-
-LocationBarSeparatorView::LocationBarSeparatorView() {
-  SetPaintToLayer();
-  layer()->SetFillsBoundsOpaquely(false);
-}
-
-void LocationBarSeparatorView::OnPaint(gfx::Canvas* canvas) {
-  DCHECK_NE(gfx::kPlaceholderColor, background_color_);
-  const SkColor separator_color = SkColorSetA(
-      color_utils::GetColorWithMaxContrast(background_color_), 0x69);
-  const float x = 1.f - 1.f / canvas->image_scale();
-  canvas->DrawLine(gfx::PointF(x, GetLocalBounds().y()),
-                   gfx::PointF(x, GetLocalBounds().bottom()), separator_color);
-}
-
-void LocationBarSeparatorView::Show() {
-  layer()->SetOpacity(0.0f);
-}
-
-void LocationBarSeparatorView::Hide() {
-  layer()->SetOpacity(1.0f);
-}
-
-void LocationBarSeparatorView::FadeIn() {
-  FadeTo(1.0f, kLocationBarSeparatorFadeInDuration);
-}
-
-void LocationBarSeparatorView::FadeOut() {
-  FadeTo(0.0f, kLocationBarSeparatorFadeOutDuration);
-}
-
-void LocationBarSeparatorView::FadeTo(float opacity, base::TimeDelta duration) {
-  if (disable_animation_for_test_) {
-    layer()->SetOpacity(opacity);
-  } else {
-    ui::ScopedLayerAnimationSettings animation(layer()->GetAnimator());
-    animation.SetTransitionDuration(duration);
-    animation.SetTweenType(gfx::Tween::Type::EASE_IN);
-    layer()->SetOpacity(opacity);
-  }
-}
diff --git a/chrome/browser/ui/views/location_bar/location_bar_separator_view.h b/chrome/browser/ui/views/location_bar/location_bar_separator_view.h
deleted file mode 100644
index 800d233..0000000
--- a/chrome/browser/ui/views/location_bar/location_bar_separator_view.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2019 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_UI_VIEWS_LOCATION_BAR_LOCATION_BAR_SEPARATOR_VIEW_H_
-#define CHROME_BROWSER_UI_VIEWS_LOCATION_BAR_LOCATION_BAR_SEPARATOR_VIEW_H_
-
-#include "base/time/time.h"
-#include "third_party/skia/include/core/SkColor.h"
-#include "ui/gfx/color_palette.h"
-#include "ui/views/view.h"
-
-namespace gfx {
-class Canvas;
-}
-
-class LocationBarSeparatorView : public views::View {
- public:
-  LocationBarSeparatorView();
-
-  void SetBackgroundColor(SkColor color) { background_color_ = color; }
-
-  // Show or hide immediately.
-  void Show();
-  void Hide();
-
-  // Animate in or out.
-  void FadeIn();
-  void FadeOut();
-
-  // views::View:
-  void OnPaint(gfx::Canvas* canvas) override;
-
-  void set_disable_animation_for_test(bool disable_animation_for_test) {
-    disable_animation_for_test_ = disable_animation_for_test;
-  }
-
- private:
-  void FadeTo(float opacity, base::TimeDelta duration);
-
-  SkColor background_color_ = gfx::kPlaceholderColor;
-  bool disable_animation_for_test_ = false;
-
-  DISALLOW_COPY_AND_ASSIGN(LocationBarSeparatorView);
-};
-
-#endif  // CHROME_BROWSER_UI_VIEWS_LOCATION_BAR_LOCATION_BAR_SEPARATOR_VIEW_H_
diff --git a/chrome/browser/ui/views/location_bar/location_bar_view.cc b/chrome/browser/ui/views/location_bar/location_bar_view.cc
index 59b844af..93ec4cb 100644
--- a/chrome/browser/ui/views/location_bar/location_bar_view.cc
+++ b/chrome/browser/ui/views/location_bar/location_bar_view.cc
@@ -698,8 +698,7 @@
 }
 
 content::WebContents* LocationBarView::GetContentSettingWebContents() {
-  return GetLocationBarModel()->input_in_progress() ? nullptr
-                                                    : GetWebContents();
+  return IsLocationBarUserInputInProgress() ? nullptr : GetWebContents();
 }
 
 ContentSettingBubbleModelDelegate*
@@ -1012,7 +1011,7 @@
   if (star_view_) {
     star_view_->SetVisible(browser_defaults::bookmarks_enabled &&
                            !is_popup_mode_ &&
-                           !GetLocationBarModel()->input_in_progress() &&
+                           !IsLocationBarUserInputInProgress() &&
                            edit_bookmarks_enabled_.GetValue() &&
                            !IsBookmarkStarHiddenByExtension());
   }
@@ -1149,7 +1148,7 @@
 
 void LocationBarView::OnChanged() {
   location_icon_view_->Update(/*suppress_animations=*/false);
-  clear_all_button_->SetVisible(GetLocationBarModel()->input_in_progress() &&
+  clear_all_button_->SetVisible(IsLocationBarUserInputInProgress() &&
                                 !omnibox_view_->text().empty() &&
                                 IsVirtualKeyboardVisible(GetWidget()));
   Layout();
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view.cc b/chrome/browser/ui/views/toolbar/toolbar_view.cc
index 6650e23a..3d740c7 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_view.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_view.cc
@@ -18,7 +18,6 @@
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/browser_features.h"
 #include "chrome/browser/command_updater.h"
-#include "chrome/browser/extensions/extension_util.h"
 #include "chrome/browser/media/router/media_router_feature.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/themes/theme_properties.h"
@@ -839,7 +838,6 @@
 
 void ToolbarView::UpdateHomeButtonVisibility() {
   const bool show_home_button =
-      show_home_button_.GetValue() ||
-      (browser_->is_app() && extensions::util::IsNewBookmarkAppsEnabled());
+      show_home_button_.GetValue() || browser_->is_app();
   home_->SetVisible(show_home_button);
 }
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index 56cb558..89c6ea0 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -151,6 +151,8 @@
 #include "chrome/browser/ui/webui/chromeos/arc_graphics_tracing/arc_graphics_tracing_ui.h"
 #include "chrome/browser/ui/webui/chromeos/assistant_optin/assistant_optin_ui.h"
 #include "chrome/browser/ui/webui/chromeos/bluetooth_pairing_dialog.h"
+#include "chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog.h"
+#include "chrome/browser/ui/webui/chromeos/cellular_setup/mobile_setup_ui.h"
 #include "chrome/browser/ui/webui/chromeos/certificate_manager_dialog_ui.h"
 #include "chrome/browser/ui/webui/chromeos/cryptohome_ui.h"
 #include "chrome/browser/ui/webui/chromeos/drive_internals_ui.h"
@@ -158,7 +160,6 @@
 #include "chrome/browser/ui/webui/chromeos/internet_config_dialog.h"
 #include "chrome/browser/ui/webui/chromeos/internet_detail_dialog.h"
 #include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
-#include "chrome/browser/ui/webui/chromeos/mobile_setup_ui.h"
 #include "chrome/browser/ui/webui/chromeos/multidevice_setup/multidevice_setup_dialog.h"
 #include "chrome/browser/ui/webui/chromeos/network_ui.h"
 #include "chrome/browser/ui/webui/chromeos/power_ui.h"
@@ -499,6 +500,8 @@
     return &NewWebUI<chromeos::AccountManagerWelcomeUI>;
   if (url.host_piece() == chrome::kChromeUIBluetoothPairingHost)
     return &NewWebUI<chromeos::BluetoothPairingDialogUI>;
+  if (url.host_piece() == chrome::kChromeUICellularSetupHost)
+    return &NewWebUI<chromeos::cellular_setup::CellularSetupDialogUI>;
   if (url.host_piece() == chrome::kChromeUICertificateManagerHost)
     return &NewWebUI<chromeos::CertificateManagerDialogUI>;
   if (url.host_piece() == chrome::kChromeUICryptohomeHost)
@@ -508,7 +511,7 @@
   if (url.host_piece() == chrome::kChromeUIFirstRunHost)
     return &NewWebUI<chromeos::FirstRunUI>;
   if (url.host_piece() == chrome::kChromeUIMobileSetupHost)
-    return &NewWebUI<chromeos::MobileSetupUI>;
+    return &NewWebUI<chromeos::cellular_setup::MobileSetupUI>;
   if (url.host_piece() == chrome::kChromeUIMultiDeviceSetupHost)
     return &NewWebUI<chromeos::multidevice_setup::MultiDeviceSetupDialogUI>;
   if (url.host_piece() == chrome::kChromeUINetworkHost)
diff --git a/chrome/browser/ui/webui/chromeos/cellular_setup/OWNERS b/chrome/browser/ui/webui/chromeos/cellular_setup/OWNERS
new file mode 100644
index 0000000..e26edaf
--- /dev/null
+++ b/chrome/browser/ui/webui/chromeos/cellular_setup/OWNERS
@@ -0,0 +1,2 @@
+khorimoto@chromium.org
+tbarzic@chromium.org
diff --git a/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog.cc b/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog.cc
new file mode 100644
index 0000000..6dbe658
--- /dev/null
+++ b/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog.cc
@@ -0,0 +1,99 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog.h"
+
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/browser_dialogs.h"
+#include "chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_localized_strings_provider.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/grit/cellular_setup_resources.h"
+#include "chrome/grit/cellular_setup_resources_map.h"
+#include "content/public/browser/web_ui_data_source.h"
+#include "ui/aura/window.h"
+
+namespace chromeos {
+
+namespace cellular_setup {
+
+namespace {
+
+// TODO(azeemarshad): Determine the exact height and width of the dialog. The
+// current mocks are unclear, so these are just a guess.
+constexpr int kDialogHeightPx = 850;
+constexpr int kDialogWidthPx = 650;
+
+CellularSetupDialog* dialog_instance = nullptr;
+
+}  // namespace
+
+// static
+void CellularSetupDialog::ShowDialog(const std::string& cellular_network_guid) {
+  if (dialog_instance) {
+    dialog_instance->dialog_window()->Focus();
+    return;
+  }
+
+  dialog_instance = new CellularSetupDialog();
+
+  // Note: chrome::ShowWebDialog() is used instead of
+  // dialog_instance->ShowSystemDialog() because it provides the dialog to
+  // ability to switch to full-screen in tablet mode.
+  chrome::ShowWebDialog(nullptr /* parent */,
+                        ProfileManager::GetActiveUserProfile(),
+                        dialog_instance);
+}
+
+CellularSetupDialog::CellularSetupDialog()
+    : SystemWebDialogDelegate(GURL(chrome::kChromeUICellularSetupUrl),
+                              base::string16()) {}
+
+CellularSetupDialog::~CellularSetupDialog() = default;
+
+void CellularSetupDialog::GetDialogSize(gfx::Size* size) const {
+  size->SetSize(kDialogWidthPx, kDialogHeightPx);
+}
+
+bool CellularSetupDialog::CanResizeDialog() const {
+  return false;
+}
+
+void CellularSetupDialog::OnDialogClosed(const std::string& json_retval) {
+  DCHECK(this == dialog_instance);
+  dialog_instance = nullptr;
+
+  // Note: The call below deletes |this|, so there is no further need to keep
+  // track of the pointer.
+  SystemWebDialogDelegate::OnDialogClosed(json_retval);
+}
+
+CellularSetupDialogUI::CellularSetupDialogUI(content::WebUI* web_ui)
+    : ui::MojoWebDialogUI(web_ui) {
+  content::WebUIDataSource* source =
+      content::WebUIDataSource::Create(chrome::kChromeUICellularSetupHost);
+
+  chromeos::cellular_setup::AddLocalizedStrings(source);
+  source->SetJsonPath("strings.js");
+  source->SetDefaultResource(IDR_CELLULAR_SETUP_CELLULAR_SETUP_DIALOG_HTML);
+
+  // Note: The |kCellularSetupResourcesSize| and |kCellularSetupResources|
+  // fields are defined in the generated file
+  // chrome/grit/cellular_setup_resources_map.h.
+  for (size_t i = 0; i < kCellularSetupResourcesSize; ++i) {
+    source->AddResourcePath(kCellularSetupResources[i].name,
+                            kCellularSetupResources[i].value);
+  }
+
+  content::WebUIDataSource::Add(Profile::FromWebUI(web_ui), source);
+
+  // TODO(khorimoto): Add Mojo bindings to this WebUI so that Mojo calls can
+  // occur in JavaScript.
+}
+
+CellularSetupDialogUI::~CellularSetupDialogUI() = default;
+
+}  // namespace cellular_setup
+
+}  // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog.h b/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog.h
new file mode 100644
index 0000000..164c12f
--- /dev/null
+++ b/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog.h
@@ -0,0 +1,48 @@
+// Copyright 2019 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_UI_WEBUI_CHROMEOS_CELLULAR_SETUP_CELLULAR_SETUP_DIALOG_H_
+#define CHROME_BROWSER_UI_WEBUI_CHROMEOS_CELLULAR_SETUP_CELLULAR_SETUP_DIALOG_H_
+
+#include "base/macros.h"
+#include "chrome/browser/ui/webui/chromeos/system_web_dialog_delegate.h"
+#include "ui/web_dialogs/web_dialog_ui.h"
+
+namespace chromeos {
+
+namespace cellular_setup {
+
+// Dialog which displays the cellular setup flow which allows users to
+// activate their un-activated SIM cards. This dialog is only used when the
+// kUpdatedCellularActivationUi flag is enabled; see go/cros-cellular-design.
+class CellularSetupDialog : public SystemWebDialogDelegate {
+ protected:
+  CellularSetupDialog();
+  ~CellularSetupDialog() override;
+
+  // ui::WebDialogDelegate
+  void GetDialogSize(gfx::Size* size) const override;
+  bool CanResizeDialog() const override;
+  void OnDialogClosed(const std::string& json_retval) override;
+
+ private:
+  friend void OpenCellularSetupDialog(const std::string& cellular_network_guid);
+  static void ShowDialog(const std::string& cellular_network_guid);
+
+  DISALLOW_COPY_AND_ASSIGN(CellularSetupDialog);
+};
+
+class CellularSetupDialogUI : public ui::MojoWebDialogUI {
+ public:
+  explicit CellularSetupDialogUI(content::WebUI* web_ui);
+  ~CellularSetupDialogUI() override;
+
+  DISALLOW_COPY_AND_ASSIGN(CellularSetupDialogUI);
+};
+
+}  // namespace cellular_setup
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_UI_WEBUI_CHROMEOS_CELLULAR_SETUP_CELLULAR_SETUP_DIALOG_H_
diff --git a/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog_launcher.cc b/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog_launcher.cc
new file mode 100644
index 0000000..a95ee83
--- /dev/null
+++ b/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog_launcher.cc
@@ -0,0 +1,27 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog_launcher.h"
+
+#include "base/feature_list.h"
+#include "chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog.h"
+#include "chrome/browser/ui/webui/chromeos/cellular_setup/mobile_setup_dialog.h"
+#include "chromeos/constants/chromeos_features.h"
+
+namespace chromeos {
+
+namespace cellular_setup {
+
+void OpenCellularSetupDialog(const std::string& cellular_network_guid) {
+  if (base::FeatureList::IsEnabled(
+          chromeos::features::kUpdatedCellularActivationUi)) {
+    CellularSetupDialog::ShowDialog(cellular_network_guid);
+  } else {
+    MobileSetupDialog::ShowByNetworkId(cellular_network_guid);
+  }
+}
+
+}  // namespace cellular_setup
+
+}  // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog_launcher.h b/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog_launcher.h
new file mode 100644
index 0000000..e8477d8
--- /dev/null
+++ b/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog_launcher.h
@@ -0,0 +1,24 @@
+// Copyright 2019 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_UI_WEBUI_CHROMEOS_CELLULAR_SETUP_CELLULAR_SETUP_DIALOG_LAUNCHER_H_
+#define CHROME_BROWSER_UI_WEBUI_CHROMEOS_CELLULAR_SETUP_CELLULAR_SETUP_DIALOG_LAUNCHER_H_
+
+#include <string>
+
+namespace chromeos {
+
+namespace cellular_setup {
+
+// Opens the cellular setup dialog for the cellular network with the provided
+// GUID; if the dialog is already open, this function focuses it. Note that this
+// function may open a different dialog depending on whether the
+// kUpdatedCellularActivationUi flag is enabled.
+void OpenCellularSetupDialog(const std::string& cellular_network_guid);
+
+}  // namespace cellular_setup
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_UI_WEBUI_CHROMEOS_CELLULAR_SETUP_CELLULAR_SETUP_DIALOG_LAUNCHER_H_
diff --git a/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_localized_strings_provider.cc b/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_localized_strings_provider.cc
new file mode 100644
index 0000000..c8112a8
--- /dev/null
+++ b/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_localized_strings_provider.cc
@@ -0,0 +1,38 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_localized_strings_provider.h"
+
+#include "base/stl_util.h"
+#include "chrome/browser/ui/webui/localized_string.h"
+#include "components/login/localized_values_builder.h"
+#include "components/strings/grit/components_strings.h"
+#include "content/public/browser/web_ui_data_source.h"
+
+namespace chromeos {
+
+namespace cellular_setup {
+
+namespace {
+
+// TODO(azeemarshad): Add localized strings for cellular setup flow.
+constexpr LocalizedString kLocalizedStringsWithoutPlaceholders[] = {
+    {"cancel", IDS_CANCEL},
+};
+
+}  //  namespace
+
+void AddLocalizedStrings(content::WebUIDataSource* html_source) {
+  AddLocalizedStringsBulk(html_source, kLocalizedStringsWithoutPlaceholders,
+                          base::size(kLocalizedStringsWithoutPlaceholders));
+}
+
+void AddLocalizedValuesToBuilder(::login::LocalizedValuesBuilder* builder) {
+  for (const auto& entry : kLocalizedStringsWithoutPlaceholders)
+    builder->Add(entry.name, entry.id);
+}
+
+}  // namespace cellular_setup
+
+}  // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_localized_strings_provider.h b/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_localized_strings_provider.h
new file mode 100644
index 0000000..6ab522f
--- /dev/null
+++ b/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_localized_strings_provider.h
@@ -0,0 +1,30 @@
+// Copyright 2019 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_UI_WEBUI_CHROMEOS_CELLULAR_SETUP_CELLULAR_SETUP_LOCALIZED_STRINGS_PROVIDER_H_
+#define CHROME_BROWSER_UI_WEBUI_CHROMEOS_CELLULAR_SETUP_CELLULAR_SETUP_LOCALIZED_STRINGS_PROVIDER_H_
+
+namespace login {
+class LocalizedValuesBuilder;
+}
+
+namespace content {
+class WebUIDataSource;
+}
+
+namespace chromeos {
+
+namespace cellular_setup {
+
+// Adds the strings needed for the cellular setup flow to |html_source|.
+void AddLocalizedStrings(content::WebUIDataSource* html_source);
+
+// Same as AddLocalizedStrings() but for a LocalizedValuesBuilder.
+void AddLocalizedValuesToBuilder(::login::LocalizedValuesBuilder* builder);
+
+}  // namespace cellular_setup
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_UI_WEBUI_CHROMEOS_CELLULAR_SETUP_CELLULAR_SETUP_LOCALIZED_STRINGS_PROVIDER_H_
diff --git a/chrome/browser/ui/webui/chromeos/mobile_setup_dialog.cc b/chrome/browser/ui/webui/chromeos/cellular_setup/mobile_setup_dialog.cc
similarity index 95%
rename from chrome/browser/ui/webui/chromeos/mobile_setup_dialog.cc
rename to chrome/browser/ui/webui/chromeos/cellular_setup/mobile_setup_dialog.cc
index eaa3962..542665cb 100644
--- a/chrome/browser/ui/webui/chromeos/mobile_setup_dialog.cc
+++ b/chrome/browser/ui/webui/chromeos/cellular_setup/mobile_setup_dialog.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/webui/chromeos/mobile_setup_dialog.h"
+#include "chrome/browser/ui/webui/chromeos/cellular_setup/mobile_setup_dialog.h"
 
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/chromeos/mobile/mobile_activator.h"
@@ -21,6 +21,8 @@
 
 namespace chromeos {
 
+namespace cellular_setup {
+
 namespace {
 
 constexpr int kMobileSetupDialogWidth = 850;
@@ -98,4 +100,6 @@
       l10n_util::GetStringUTF16(IDS_MOBILE_CANCEL_ACTIVATION));
 }
 
+}  // namespace cellular_setup
+
 }  // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/mobile_setup_dialog.h b/chrome/browser/ui/webui/chromeos/cellular_setup/mobile_setup_dialog.h
similarity index 61%
rename from chrome/browser/ui/webui/chromeos/mobile_setup_dialog.h
rename to chrome/browser/ui/webui/chromeos/cellular_setup/mobile_setup_dialog.h
index 8243bf7..4739b83 100644
--- a/chrome/browser/ui/webui/chromeos/mobile_setup_dialog.h
+++ b/chrome/browser/ui/webui/chromeos/cellular_setup/mobile_setup_dialog.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_WEBUI_CHROMEOS_MOBILE_SETUP_DIALOG_H_
-#define CHROME_BROWSER_UI_WEBUI_CHROMEOS_MOBILE_SETUP_DIALOG_H_
+#ifndef CHROME_BROWSER_UI_WEBUI_CHROMEOS_CELLULAR_SETUP_MOBILE_SETUP_DIALOG_H_
+#define CHROME_BROWSER_UI_WEBUI_CHROMEOS_CELLULAR_SETUP_MOBILE_SETUP_DIALOG_H_
 
 #include "base/macros.h"
 #include "chrome/browser/ui/webui/chromeos/system_web_dialog_delegate.h"
@@ -12,10 +12,12 @@
 
 class NetworkState;
 
-class MobileSetupDialog : public SystemWebDialogDelegate {
- public:
-  static void ShowByNetworkId(const std::string& network_id);
+namespace cellular_setup {
 
+// Dialog used for cellular activation flow when the
+// kUpdatedCellularActivationUi flag is disabled.
+// DEPRECATED: Being replaced by new UI; see https://crbug.com/778021.
+class MobileSetupDialog : public SystemWebDialogDelegate {
  protected:
   explicit MobileSetupDialog(const NetworkState& network);
   ~MobileSetupDialog() override;
@@ -27,9 +29,15 @@
   void OnCloseContents(content::WebContents* source,
                        bool* out_close_dialog) override;
 
+ private:
+  friend void OpenCellularSetupDialog(const std::string& cellular_network_guid);
+  static void ShowByNetworkId(const std::string& network_id);
+
   DISALLOW_COPY_AND_ASSIGN(MobileSetupDialog);
 };
 
+}  // namespace cellular_setup
+
 }  // namespace chromeos
 
-#endif  // CHROME_BROWSER_UI_WEBUI_CHROMEOS_MOBILE_SETUP_DIALOG_H_
+#endif  // CHROME_BROWSER_UI_WEBUI_CHROMEOS_CELLULAR_SETUP_MOBILE_SETUP_DIALOG_H_
diff --git a/chrome/browser/ui/webui/chromeos/mobile_setup_ui.cc b/chrome/browser/ui/webui/chromeos/cellular_setup/mobile_setup_ui.cc
similarity index 99%
rename from chrome/browser/ui/webui/chromeos/mobile_setup_ui.cc
rename to chrome/browser/ui/webui/chromeos/cellular_setup/mobile_setup_ui.cc
index 8fb50f2e..060cab4 100644
--- a/chrome/browser/ui/webui/chromeos/mobile_setup_ui.cc
+++ b/chrome/browser/ui/webui/chromeos/cellular_setup/mobile_setup_ui.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/webui/chromeos/mobile_setup_ui.h"
+#include "chrome/browser/ui/webui/chromeos/cellular_setup/mobile_setup_ui.h"
 
 #include <stddef.h>
 
@@ -46,6 +46,8 @@
 
 namespace chromeos {
 
+namespace cellular_setup {
+
 namespace {
 
 // Host page JS API function names.
@@ -604,4 +606,6 @@
 
 MobileSetupUI::~MobileSetupUI() = default;
 
+}  // namespace cellular_setup
+
 }  // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/mobile_setup_ui.h b/chrome/browser/ui/webui/chromeos/cellular_setup/mobile_setup_ui.h
similarity index 60%
rename from chrome/browser/ui/webui/chromeos/mobile_setup_ui.h
rename to chrome/browser/ui/webui/chromeos/cellular_setup/mobile_setup_ui.h
index 8334f11..a9d6fcc 100644
--- a/chrome/browser/ui/webui/chromeos/mobile_setup_ui.h
+++ b/chrome/browser/ui/webui/chromeos/cellular_setup/mobile_setup_ui.h
@@ -2,16 +2,20 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_WEBUI_CHROMEOS_MOBILE_SETUP_UI_H_
-#define CHROME_BROWSER_UI_WEBUI_CHROMEOS_MOBILE_SETUP_UI_H_
+#ifndef CHROME_BROWSER_UI_WEBUI_CHROMEOS_CELLULAR_SETUP_MOBILE_SETUP_UI_H_
+#define CHROME_BROWSER_UI_WEBUI_CHROMEOS_CELLULAR_SETUP_MOBILE_SETUP_UI_H_
 
 #include "base/macros.h"
 #include "ui/web_dialogs/web_dialog_ui.h"
 
 namespace chromeos {
 
+namespace cellular_setup {
+
 // A custom WebUI that defines datasource for mobile setup registration page
 // that is used in Chrome OS activate modem and perform plan subscription tasks.
+// This WebUI is being replaced and is only shown when the
+// kUpdatedCellularActivationUi flag is disabled; see go/cros-cellular-design.
 class MobileSetupUI : public ui::WebDialogUI {
  public:
   explicit MobileSetupUI(content::WebUI* web_ui);
@@ -21,6 +25,8 @@
   DISALLOW_COPY_AND_ASSIGN(MobileSetupUI);
 };
 
+}  // namespace cellular_setup
+
 }  // namespace chromeos
 
-#endif  // CHROME_BROWSER_UI_WEBUI_CHROMEOS_MOBILE_SETUP_UI_H_
+#endif  // CHROME_BROWSER_UI_WEBUI_CHROMEOS_CELLULAR_SETUP_MOBILE_SETUP_UI_H_
diff --git a/chrome/browser/ui/webui/chromeos/network_ui.cc b/chrome/browser/ui/webui/chromeos/network_ui.cc
index 7494e78..1513366c 100644
--- a/chrome/browser/ui/webui/chromeos/network_ui.cc
+++ b/chrome/browser/ui/webui/chromeos/network_ui.cc
@@ -14,6 +14,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/values.h"
 #include "chrome/browser/extensions/tab_helper.h"
+#include "chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog_launcher.h"
 #include "chrome/browser/ui/webui/chromeos/internet_config_dialog.h"
 #include "chrome/browser/ui/webui/chromeos/network_element_localized_strings_provider.h"
 #include "chrome/common/url_constants.h"
@@ -37,8 +38,10 @@
 
 namespace {
 
+constexpr char kAddNetwork[] = "addNetwork";
 constexpr char kGetNetworkProperties[] = "getShillNetworkProperties";
 constexpr char kGetDeviceProperties[] = "getShillDeviceProperties";
+constexpr char kOpenCellularActivationUi[] = "openCellularActivationUi";
 
 bool GetServicePathFromGuid(const std::string& guid,
                             std::string* service_path) {
@@ -84,6 +87,10 @@
   // WebUIMessageHandler implementation.
   void RegisterMessages() override {
     web_ui()->RegisterMessageCallback(
+        kAddNetwork,
+        base::BindRepeating(&NetworkConfigMessageHandler::AddNetwork,
+                            base::Unretained(this)));
+    web_ui()->RegisterMessageCallback(
         kGetNetworkProperties,
         base::BindRepeating(
             &NetworkConfigMessageHandler::GetShillNetworkProperties,
@@ -94,9 +101,10 @@
             &NetworkConfigMessageHandler::GetShillDeviceProperties,
             base::Unretained(this)));
     web_ui()->RegisterMessageCallback(
-        "addNetwork",
-        base::BindRepeating(&NetworkConfigMessageHandler::AddNetwork,
-                            base::Unretained(this)));
+        kOpenCellularActivationUi,
+        base::BindRepeating(
+            &NetworkConfigMessageHandler::OpenCellularActivationUi,
+            base::Unretained(this)));
   }
 
  private:
@@ -165,6 +173,19 @@
                    weak_ptr_factory_.GetWeakPtr(), type, kGetDeviceProperties));
   }
 
+  void OpenCellularActivationUi(const base::ListValue* arg_list) {
+    const NetworkState* cellular_network =
+        NetworkHandler::Get()->network_state_handler()->FirstNetworkByType(
+            NetworkTypePattern::Cellular());
+    if (cellular_network)
+      cellular_setup::OpenCellularSetupDialog(cellular_network->guid());
+
+    AllowJavascript();
+    CallJavascriptFunction(
+        base::StringPrintf("NetworkUI.%sResult", kOpenCellularActivationUi),
+        base::Value(cellular_network != nullptr));
+  }
+
   void GetShillDevicePropertiesSuccess(
       const std::string& device_path,
       const base::DictionaryValue& dictionary) {
@@ -263,6 +284,17 @@
       l10n_util::GetStringUTF16(IDS_NETWORK_UI_FAVORITE_NETWORKS));
   localized_strings->SetString(
       "devicesLabel", l10n_util::GetStringUTF16(IDS_NETWORK_UI_DEVICES));
+
+  localized_strings->SetString(
+      "cellularActivationLabel",
+      l10n_util::GetStringUTF16(IDS_NETWORK_UI_NO_CELLULAR_ACTIVATION_LABEL));
+  localized_strings->SetString(
+      "cellularActivationButtonText",
+      l10n_util::GetStringUTF16(
+          IDS_NETWORK_UI_OPEN_CELLULAR_ACTIVATION_BUTTON_TEXT));
+  localized_strings->SetString(
+      "noCellularErrorText",
+      l10n_util::GetStringUTF16(IDS_NETWORK_UI_NO_CELLULAR_ERROR_TEXT));
 }
 
 NetworkUI::NetworkUI(content::WebUI* web_ui)
diff --git a/chrome/browser/ui/webui/management_ui_handler.cc b/chrome/browser/ui/webui/management_ui_handler.cc
index 3565d325..d289c26 100644
--- a/chrome/browser/ui/webui/management_ui_handler.cc
+++ b/chrome/browser/ui/webui/management_ui_handler.cc
@@ -134,10 +134,19 @@
 std::string GetAccountDomain(Profile* profile) {
   auto username = profile->GetProfileUserName();
   size_t email_separator_pos = username.find('@');
-  auto is_email = email_separator_pos != username.npos &&
+  bool is_email = email_separator_pos != std::string::npos &&
                   email_separator_pos < username.length() - 1;
-  return is_email ? gaia::ExtractDomainName(std::move(username))
-                  : std::string();
+
+  if (!is_email)
+    return std::string();
+
+  const std::string domain = gaia::ExtractDomainName(std::move(username));
+
+  auto consumer_domain_pos = domain.find("gmail.com");
+  if (consumer_domain_pos == std::string::npos)
+    consumer_domain_pos = domain.find("googlemail.com");
+
+  return consumer_domain_pos == std::string::npos ? domain : std::string();
 }
 
 #if !defined(OS_CHROMEOS)
diff --git a/chrome/browser/ui/webui/management_ui_handler_unittest.cc b/chrome/browser/ui/webui/management_ui_handler_unittest.cc
index 36a2abb..212d62f 100644
--- a/chrome/browser/ui/webui/management_ui_handler_unittest.cc
+++ b/chrome/browser/ui/webui/management_ui_handler_unittest.cc
@@ -150,6 +150,32 @@
 }
 
 TEST_F(ManagementUIHandlerTests,
+       ManagementContextualSourceUpdateManagedConsumerDomain) {
+  TestingProfile::Builder builder;
+  builder.SetProfileName("managed@gmail.com");
+  auto profile = builder.Build();
+
+  base::string16 extensions_installed;
+  base::string16 browser_management_notice;
+  base::string16 title;
+  ContextualManagementSourceUpdate extracted{
+      &extensions_installed, &browser_management_notice, &title};
+
+  handler_.SetManagedForTesting(true);
+  auto data = handler_.GetDataSourceUpdate(profile.get());
+  ExtractContextualSourceUpdate(data.get(), extracted);
+
+  EXPECT_EQ(data->DictSize(), 3u);
+  EXPECT_EQ(extensions_installed,
+            l10n_util::GetStringUTF16(IDS_MANAGEMENT_EXTENSIONS_INSTALLED));
+  EXPECT_EQ(browser_management_notice,
+            l10n_util::GetStringFUTF16(
+                IDS_MANAGEMENT_BROWSER_NOTICE,
+                base::UTF8ToUTF16(chrome::kManagedUiLearnMoreUrl)));
+  EXPECT_EQ(title, l10n_util::GetStringUTF16(IDS_MANAGEMENT_TITLE));
+}
+
+TEST_F(ManagementUIHandlerTests,
        ManagementContextualSourceUpdateUnmanagedKnownDomain) {
   TestingProfile::Builder builder;
   builder.SetProfileName("managed@manager.com");
@@ -178,6 +204,33 @@
 }
 
 TEST_F(ManagementUIHandlerTests,
+       ManagementContextualSourceUpdateUnmanagedCustomerDomain) {
+  TestingProfile::Builder builder;
+  builder.SetProfileName("managed@googlemail.com");
+  auto profile = builder.Build();
+
+  base::string16 extensions_installed;
+  base::string16 browser_management_notice;
+  base::string16 title;
+  ContextualManagementSourceUpdate extracted{
+      &extensions_installed, &browser_management_notice, &title};
+
+  handler_.SetManagedForTesting(false);
+
+  auto data = handler_.GetDataSourceUpdate(profile.get());
+  ExtractContextualSourceUpdate(data.get(), extracted);
+
+  EXPECT_EQ(data->DictSize(), 3u);
+  EXPECT_EQ(extensions_installed,
+            l10n_util::GetStringUTF16(IDS_MANAGEMENT_EXTENSIONS_INSTALLED));
+  EXPECT_EQ(browser_management_notice,
+            l10n_util::GetStringFUTF16(
+                IDS_MANAGEMENT_NOT_MANAGED_NOTICE,
+                base::UTF8ToUTF16(chrome::kManagedUiLearnMoreUrl)));
+  EXPECT_EQ(title, l10n_util::GetStringUTF16(IDS_MANAGEMENT_NOT_MANAGED_TITLE));
+}
+
+TEST_F(ManagementUIHandlerTests,
        ManagementContextualSourceUpdateManagedKnownDomain) {
   TestingProfile::Builder builder;
   builder.SetProfileName("managed@manager.com");
diff --git a/chrome/browser/ui/webui/ntp/ntp_resource_cache.cc b/chrome/browser/ui/webui/ntp/ntp_resource_cache.cc
index 78c46d6..17e7580 100644
--- a/chrome/browser/ui/webui/ntp/ntp_resource_cache.cc
+++ b/chrome/browser/ui/webui/ntp/ntp_resource_cache.cc
@@ -17,7 +17,6 @@
 #include "chrome/browser/browser_features.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
-#include "chrome/browser/extensions/extension_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/themes/theme_properties.h"
@@ -455,12 +454,6 @@
   load_time_data.SetBoolean("showWebStoreIcon",
                             !prefs->GetBoolean(prefs::kHideWebStoreIcon));
 
-  load_time_data.SetBoolean("enableNewBookmarkApps",
-                            extensions::util::IsNewBookmarkAppsEnabled());
-
-  load_time_data.SetBoolean("canHostedAppsOpenInWindows",
-                            extensions::util::CanHostedAppsOpenInWindows());
-
   load_time_data.SetBoolean("canShowAppInfoDialog",
                             CanShowAppInfoDialog());
 
diff --git a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
index 907b6e67..50f3c74 100644
--- a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
@@ -30,9 +30,11 @@
 #include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/grit/locale_settings.h"
+#include "components/autofill/core/browser/autofill_experiments.h"
 #include "components/autofill/core/browser/payments/payments_service_url.h"
 #include "components/autofill/core/browser/payments/payments_util.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
+#include "components/autofill/core/browser/sync_utils.h"
 #include "components/autofill/core/common/autofill_constants.h"
 #include "components/autofill/core/common/autofill_features.h"
 #include "components/autofill/core/common/autofill_payments_features.h"
@@ -1560,37 +1562,6 @@
                          autofill::payments::GetManageInstrumentsUrl().spec());
   html_source->AddString("paymentMethodsLearnMoreURL",
                          chrome::kPaymentMethodsLearnMoreURL);
-  html_source->AddBoolean(
-      "migrationEnabled",
-      autofill::features::GetLocalCardMigrationExperimentalFlag() ==
-          autofill::features::LocalCardMigrationExperimentalFlag::
-              kMigrationIncludeSettingsPage);
-  html_source->AddBoolean(
-      "upstreamEnabled",
-      base::FeatureList::IsEnabled(autofill::features::kAutofillUpstream));
-
-  autofill::PersonalDataManager* personal_data_manager_ =
-      autofill::PersonalDataManagerFactory::GetForProfile(profile);
-  html_source->AddBoolean(
-      "hasGooglePaymentsAccount",
-      autofill::payments::GetBillingCustomerId(personal_data_manager_) != 0);
-
-  syncer::SyncService* sync_service =
-      ProfileSyncServiceFactory::GetForProfile(profile);
-  if (sync_service && sync_service->CanSyncFeatureStart() &&
-      sync_service->GetPreferredDataTypes().Has(syncer::AUTOFILL_PROFILE)) {
-    html_source->AddBoolean(
-        "isUsingSecondaryPassphrase",
-        sync_service->GetUserSettings()->IsUsingSecondaryPassphrase());
-    html_source->AddBoolean(
-        "uploadToGoogleActive",
-        syncer::GetUploadToGoogleState(
-            sync_service, syncer::ModelType::AUTOFILL_WALLET_DATA) ==
-            syncer::UploadState::ACTIVE);
-  } else {
-    html_source->AddBoolean("isUsingSecondaryPassphrase", false);
-    html_source->AddBoolean("uploadToGoogleActive", false);
-  }
 
   bool is_guest_mode = false;
 #if defined(OS_CHROMEOS)
@@ -1599,24 +1570,14 @@
 #else   // !defined(OS_CHROMEOS)
   is_guest_mode = profile->IsOffTheRecord();
 #endif  // defined(OS_CHROMEOS)
-
-  if (is_guest_mode) {
-    html_source->AddBoolean("userEmailDomainAllowed", false);
-  } else {
-    const std::string& user_email =
-        personal_data_manager_->GetAccountInfoForPaymentsServer().email;
-    if (user_email.empty()) {
-      html_source->AddBoolean("userEmailDomainAllowed", false);
-    } else {
-      std::string domain = gaia::ExtractDomainName(user_email);
-      html_source->AddBoolean(
-          "userEmailDomainAllowed",
-          base::FeatureList::IsEnabled(
-              autofill::features::kAutofillUpstreamAllowAllEmailDomains) ||
-              (domain == "googlemail.com" || domain == "gmail.com" ||
-               domain == "google.com" || domain == "chromium.org"));
-    }
-  }
+  html_source->AddBoolean(
+      "migrationEnabled",
+      !is_guest_mode &&
+          autofill::IsCreditCardMigrationEnabled(
+              autofill::PersonalDataManagerFactory::GetForProfile(profile),
+              profile->GetPrefs(),
+              ProfileSyncServiceFactory::GetForProfile(profile),
+              /*is_test_mode=*/false));
 
   AddLocalizedStringsBulk(html_source, kLocalizedStrings,
                           base::size(kLocalizedStrings));
diff --git a/chrome/browser/ui/webui/welcome/OWNERS b/chrome/browser/ui/webui/welcome/OWNERS
index c497ef2..a0bd3ec 100644
--- a/chrome/browser/ui/webui/welcome/OWNERS
+++ b/chrome/browser/ui/webui/welcome/OWNERS
@@ -1,4 +1,3 @@
 hcarmona@chromium.org
-scottchen@chromium.org
 
 # COMPONENT: UI>Browser>FirstRun
diff --git a/chrome/browser/ui/webui/welcome/welcome_ui_unittest.cc b/chrome/browser/ui/webui/welcome/welcome_ui_unittest.cc
index dc45f0d..9f8592e 100644
--- a/chrome/browser/ui/webui/welcome/welcome_ui_unittest.cc
+++ b/chrome/browser/ui/webui/welcome/welcome_ui_unittest.cc
@@ -19,7 +19,7 @@
   EXPECT_TRUE(TestWelcomeUI::IsGzipped("returning-user"));
 
   // Images are intentionally not gzipped.
-  EXPECT_FALSE(TestWelcomeUI::IsGzipped("images/set_as_default_1x.png"));
+  EXPECT_FALSE(TestWelcomeUI::IsGzipped("images/youtube_1x.png"));
 
   // This is a dynamic path that fetches from the network and should not be
   // considered gzipped.
diff --git a/chrome/browser/vr/BUILD.gn b/chrome/browser/vr/BUILD.gn
index a813b89..d4859f7b 100644
--- a/chrome/browser/vr/BUILD.gn
+++ b/chrome/browser/vr/BUILD.gn
@@ -679,6 +679,7 @@
       "//chrome/test:browser_tests_runner",
       "//chrome/test:test_support",
       "//chrome/test:test_support_ui",
+      "//device/base",
       "//device/vr:vr",
       "//device/vr/buildflags:buildflags",
       "//device/vr/public/mojom:mojom",
diff --git a/chrome/browser/vr/test/webxr_vr_browser_test.h b/chrome/browser/vr/test/webxr_vr_browser_test.h
index 644ce4c..80bbf99c 100644
--- a/chrome/browser/vr/test/webxr_vr_browser_test.h
+++ b/chrome/browser/vr/test/webxr_vr_browser_test.h
@@ -11,6 +11,7 @@
 #include "chrome/common/chrome_features.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_features.h"
+#include "device/base/features.h"
 #include "device/vr/buildflags/buildflags.h"
 
 #if defined(OS_WIN)
@@ -53,6 +54,25 @@
   }
 };
 
+// WebXrOrientationSensorDevice is only defined when the enable_vr flag is set.
+#if BUILDFLAG(ENABLE_VR)
+class WebXrVrBrowserTestSensorless : public WebXrVrBrowserTestBase {
+ public:
+  WebXrVrBrowserTestSensorless() {
+    enable_features_.push_back(features::kWebXr);
+    disable_features_.push_back(device::kWebXrOrientationSensorDevice);
+
+#if BUILDFLAG(ENABLE_WINDOWS_MR)
+    disable_features_.push_back(features::kWindowsMixedReality);
+#endif
+
+#if defined(OS_WIN)
+    disable_features_.push_back(service_manager::features::kXRSandbox);
+#endif
+  }
+};
+#endif
+
 // OpenVR feature only defined on Windows.
 #ifdef OS_WIN
 // Test class with standard features enabled: WebXR and OpenVR.
diff --git a/chrome/browser/vr/test/xr_browser_test.cc b/chrome/browser/vr/test/xr_browser_test.cc
index ae2ea5e..23d7fda 100644
--- a/chrome/browser/vr/test/xr_browser_test.cc
+++ b/chrome/browser/vr/test/xr_browser_test.cc
@@ -103,13 +103,13 @@
   }
 
   // Set the environment variable to use the mock OpenVR client.
-  EXPECT_TRUE(
+  ASSERT_TRUE(
       env_->SetVar(kVrOverrideEnvVar, MakeExecutableRelative(kVrOverrideVal)))
       << "Failed to set OpenVR mock client location environment variable";
-  EXPECT_TRUE(env_->SetVar(kVrConfigPathEnvVar,
+  ASSERT_TRUE(env_->SetVar(kVrConfigPathEnvVar,
                            MakeExecutableRelative(kVrConfigPathVal)))
       << "Failed to set OpenVR config location environment variable";
-  EXPECT_TRUE(
+  ASSERT_TRUE(
       env_->SetVar(kVrLogPathEnvVar, MakeExecutableRelative(kVrLogPathVal)))
       << "Failed to set OpenVR log location environment variable";
 
@@ -155,7 +155,7 @@
 
 void XrBrowserTestBase::LoadUrlAndAwaitInitialization(const GURL& url) {
   ui_test_utils::NavigateToURL(browser(), url);
-  EXPECT_TRUE(PollJavaScriptBoolean("isInitializationComplete()",
+  ASSERT_TRUE(PollJavaScriptBoolean("isInitializationComplete()",
                                     kPollTimeoutMedium,
                                     GetCurrentWebContents()))
       << "Timed out waiting for JavaScript test initialization.";
@@ -164,7 +164,7 @@
 void XrBrowserTestBase::RunJavaScriptOrFail(
     const std::string& js_expression,
     content::WebContents* web_contents) {
-  EXPECT_TRUE(content::ExecuteScript(web_contents, js_expression))
+  ASSERT_TRUE(content::ExecuteScript(web_contents, js_expression))
       << "Failed to run given JavaScript: " << js_expression;
 }
 
@@ -215,7 +215,7 @@
     const std::string& bool_expression,
     const base::TimeDelta& timeout,
     content::WebContents* web_contents) {
-  EXPECT_TRUE(PollJavaScriptBoolean(bool_expression, timeout, web_contents))
+  ASSERT_TRUE(PollJavaScriptBoolean(bool_expression, timeout, web_contents))
       << "Timed out polling JavaScript boolean expression: " << bool_expression;
 }
 
@@ -267,7 +267,7 @@
   // code to do so.
   bool code_available = RunJavaScriptAndExtractBoolOrFail(
       "typeof javascriptDone !== 'undefined'", web_contents);
-  EXPECT_TRUE(code_available) << "Attempted to wait on a JavaScript test step "
+  ASSERT_TRUE(code_available) << "Attempted to wait on a JavaScript test step "
                               << "without the code to do so. You either forgot "
                               << "to import webxr_e2e.js or "
                               << "are incorrectly using a C++ function.";
diff --git a/chrome/browser/vr/test/xr_browser_test.h b/chrome/browser/vr/test/xr_browser_test.h
index dc9160c..683be2c5 100644
--- a/chrome/browser/vr/test/xr_browser_test.h
+++ b/chrome/browser/vr/test/xr_browser_test.h
@@ -84,7 +84,7 @@
   void LoadUrlAndAwaitInitialization(const GURL& url);
 
   // Convenience function for ensuring the given JavaScript runs successfully
-  // without having to always surround in EXPECT_TRUE.
+  // without having to always surround in ASSERT_TRUE.
   void RunJavaScriptOrFail(const std::string& js_expression,
                            content::WebContents* web_contents);
 
diff --git a/chrome/browser/vr/webxr_vr_frame_pose_browser_test.cc b/chrome/browser/vr/webxr_vr_frame_pose_browser_test.cc
index 2334652..526af32 100644
--- a/chrome/browser/vr/webxr_vr_frame_pose_browser_test.cc
+++ b/chrome/browser/vr/webxr_vr_frame_pose_browser_test.cc
@@ -98,7 +98,7 @@
     wait_loop_ = nullptr;
   }
 
-  EXPECT_TRUE(!!last_immersive_frame_data)
+  ASSERT_TRUE(!!last_immersive_frame_data)
       << "Frame submitted without any frame data provided";
 
   // We expect a waitGetPoses, then 2 submits (one for each eye), so after 2
@@ -171,7 +171,7 @@
   t->EnterSessionWithUserGestureOrFail();
 
   // Wait for JavaScript to submit at least one frame.
-  EXPECT_TRUE(
+  ASSERT_TRUE(
       t->PollJavaScriptBoolean("hasPresentedFrame", t->kPollTimeoutShort))
       << "No frame submitted";
 
@@ -202,27 +202,27 @@
     // Validate that each frame is only seen once for each eye.
     DLOG(ERROR) << "Frame id: " << frame_id;
     if (data->eye == device_test::mojom::Eye::LEFT) {
-      EXPECT_TRUE(seen_left.find(frame_id) == seen_left.end())
+      ASSERT_TRUE(seen_left.find(frame_id) == seen_left.end())
           << "Frame for left eye submitted more than once";
       seen_left.insert(frame_id);
     } else {
-      EXPECT_TRUE(seen_right.find(frame_id) == seen_right.end())
+      ASSERT_TRUE(seen_right.find(frame_id) == seen_right.end())
           << "Frame for right eye submitted more than once";
       seen_right.insert(frame_id);
     }
 
     // Validate that frames arrive in order.
-    EXPECT_TRUE(frame_id >= max_frame_id) << "Frame received out of order";
+    ASSERT_TRUE(frame_id >= max_frame_id) << "Frame received out of order";
     max_frame_id = std::max(frame_id, max_frame_id);
 
     // Validate that the JavaScript-side cache of frames contains our submitted
     // frame.
-    EXPECT_TRUE(t->RunJavaScriptAndExtractBoolOrFail(
+    ASSERT_TRUE(t->RunJavaScriptAndExtractBoolOrFail(
         base::StringPrintf("checkFrameOccurred(%d)", frame_id)))
         << "JavaScript-side frame cache does not contain submitted frame";
 
     // Validate that the JavaScript-side cache of frames has the correct pose.
-    EXPECT_TRUE(t->RunJavaScriptAndExtractBoolOrFail(base::StringPrintf(
+    ASSERT_TRUE(t->RunJavaScriptAndExtractBoolOrFail(base::StringPrintf(
         "checkFramePose(%d, %s)", frame_id, GetPoseAsString(frame).c_str())))
         << "JavaScript-side frame cache has incorrect pose";
   }
diff --git a/chrome/browser/vr/webxr_vr_pixel_browser_test.cc b/chrome/browser/vr/webxr_vr_pixel_browser_test.cc
index a452c46..8d92634 100644
--- a/chrome/browser/vr/webxr_vr_pixel_browser_test.cc
+++ b/chrome/browser/vr/webxr_vr_pixel_browser_test.cc
@@ -66,7 +66,7 @@
   t->EnterSessionWithUserGestureOrFail();
 
   // Wait for JavaScript to submit at least one frame.
-  EXPECT_TRUE(
+  ASSERT_TRUE(
       t->PollJavaScriptBoolean("hasPresentedFrame", t->kPollTimeoutMedium))
       << "No frame submitted";
 
diff --git a/chrome/browser/vr/webxr_vr_spatial_tracking_test.cc b/chrome/browser/vr/webxr_vr_spatial_tracking_test.cc
index 145578f..041990b 100644
--- a/chrome/browser/vr/webxr_vr_spatial_tracking_test.cc
+++ b/chrome/browser/vr/webxr_vr_spatial_tracking_test.cc
@@ -19,4 +19,13 @@
   WaitOnJavaScriptStep();
   EndTest();
 }
+
+#if BUILDFLAG(ENABLE_VR)
+IN_PROC_BROWSER_TEST_F(WebXrVrBrowserTestSensorless, TestSensorlessRejections) {
+  LoadUrlAndAwaitInitialization(
+      GetFileUrlForHtmlTestFile("test_stationary_reference_space_rejects"));
+  WaitOnJavaScriptStep();
+  EndTest();
+}
+#endif
 }  // namespace vr
diff --git a/chrome/chrome_paks.gni b/chrome/chrome_paks.gni
index 803db7cc..f54552a 100644
--- a/chrome/chrome_paks.gni
+++ b/chrome/chrome_paks.gni
@@ -150,6 +150,7 @@
     if (is_chromeos) {
       sources += [
         "$root_gen_dir/ash/public/cpp/resources/ash_public_unscaled_resources.pak",
+        "$root_gen_dir/chrome/cellular_setup_resources.pak",
         "$root_gen_dir/chrome/multidevice_setup_resources.pak",
         "$root_gen_dir/chromeos/chromeos_resources.pak",
         "$root_gen_dir/third_party/ink/ink_resources.pak",
@@ -157,6 +158,7 @@
       ]
       deps += [
         "//ash/public/cpp/resources:ash_public_unscaled_resources",
+        "//chrome/browser/resources/chromeos:cellular_setup_resources",
         "//chrome/browser/resources/chromeos:multidevice_setup_resources",
         "//chromeos/resources",
         "//third_party/ink:ink_resources",
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 43b7382..b61a480 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -110,12 +110,6 @@
 const base::Feature kBlockPromptsIfIgnoredOften{
     "BlockPromptsIfIgnoredOften", base::FEATURE_DISABLED_BY_DEFAULT};
 
-#if defined(OS_MACOSX)
-// Enables the new bookmark app system (e.g. Add To Applications on Mac).
-const base::Feature kBookmarkApps{"BookmarkAppsMac",
-                                  base::FEATURE_ENABLED_BY_DEFAULT};
-#endif
-
 // Fixes for browser hang bugs are deployed in a field trial in order to measure
 // their impact. See crbug.com/478209.
 const base::Feature kBrowserHangFixesExperiment{
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index cc03713..9406822 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -70,10 +70,6 @@
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kBlockPromptsIfIgnoredOften;
 
-#if defined(OS_MACOSX)
-COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kBookmarkApps;
-#endif
-
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kBrowserHangFixesExperiment;
 
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index 775e7716..97e786f 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -786,33 +786,16 @@
 // Prevents Chrome from quitting when Chrome Apps are open.
 const char kAppsKeepChromeAliveInTests[]    = "apps-keep-chrome-alive-in-tests";
 
-// Disable the toolkit-views App Info dialog for Mac.
-const char kDisableAppInfoDialogMac[] = "disable-app-info-dialog-mac";
-
 // Disables app shim creation for hosted apps on Mac.
 const char kDisableHostedAppShimCreation[] = "disable-hosted-app-shim-creation";
 
-// Prevents hosted apps from being opened in windows on Mac.
-const char kDisableHostedAppsInWindows[] = "disable-hosted-apps-in-windows";
-
-// Disables use of toolkit-views based native app windows.
-const char kDisableMacViewsNativeAppWindows[] =
-    "disable-mac-views-native-app-windows";
-
 // Enable user metrics from within the installer.
 const char kEnableUserMetrics[] = "enable-user-metrics";
 
-// Enable the toolkit-views App Info dialog for Mac. This is accessible from
-// chrome://apps and chrome://extensions and is already enabled on non-mac.
-const char kEnableAppInfoDialogMac[] = "enable-app-info-dialog-mac";
-
 // Enables the fullscreen toolbar to reveal itself for tab strip changes.
 const char kEnableFullscreenToolbarReveal[] =
     "enable-fullscreen-toolbar-reveal";
 
-// Allows hosted apps to be opened in windows on Mac.
-const char kEnableHostedAppsInWindows[] = "enable-hosted-apps-in-windows";
-
 // Shows a notification when quitting Chrome with hosted apps running. Default
 // behavior is to also quit all hosted apps.
 const char kHostedAppQuitNotification[] = "enable-hosted-app-quit-notification";
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index b5d6a55..0c44a8c 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -240,13 +240,8 @@
 
 #if defined(OS_MACOSX)
 extern const char kAppsKeepChromeAliveInTests[];
-extern const char kDisableAppInfoDialogMac[];
 extern const char kDisableHostedAppShimCreation[];
-extern const char kDisableHostedAppsInWindows[];
-extern const char kDisableMacViewsNativeAppWindows[];
-extern const char kEnableAppInfoDialogMac[];
 extern const char kEnableFullscreenToolbarReveal[];
-extern const char kEnableHostedAppsInWindows[];
 extern const char kEnableUserMetrics[];
 extern const char kHostedAppQuitNotification[];
 extern const char kMetricsClientID[];
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 4f1495f..d805619 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -1977,6 +1977,12 @@
     "auto_screen_brightness.metrics.supported_als_user_adjustment_count";
 const char kAutoScreenBrightnessMetricsUnsupportedAlsUserAdjustmentCount[] =
     "auto_screen_brightness.metrics.unsupported_als_user_adjustment_count";
+
+// Dictionary pref containing the configuration used to verify Parent Access
+// Code. The data is sent through the ParentAccessCodeConfig policy, which is
+// set for child users only, and kept on the known user storage.
+const char kKnownUserParentAccessCodeConfig[] =
+    "child_user.parent_access_code.config";
 #endif  // defined(OS_CHROMEOS)
 
 // Whether there is a Flash version installed that supports clearing LSO data.
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 6ea8b24cc..403842e 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -655,6 +655,7 @@
 extern const char kAutoScreenBrightnessMetricsSupportedAlsUserAdjustmentCount[];
 extern const char
     kAutoScreenBrightnessMetricsUnsupportedAlsUserAdjustmentCount[];
+extern const char kKnownUserParentAccessCodeConfig[];
 #endif  // defined(OS_CHROMEOS)
 
 extern const char kClearPluginLSODataEnabled[];
diff --git a/chrome/common/thread_profiler.cc b/chrome/common/thread_profiler.cc
index 74372d49..f77b3f2 100644
--- a/chrome/common/thread_profiler.cc
+++ b/chrome/common/thread_profiler.cc
@@ -158,6 +158,13 @@
   ScheduleNextPeriodicCollection();
 }
 
+void ThreadProfiler::AddAuxUnwinder(std::unique_ptr<base::Unwinder> unwinder) {
+  aux_unwinder_ = std::move(unwinder);
+  startup_profiler_->AddAuxUnwinder(aux_unwinder_.get());
+  if (periodic_profiler_)
+    periodic_profiler_->AddAuxUnwinder(aux_unwinder_.get());
+}
+
 // static
 void ThreadProfiler::StartOnChildThread(CallStackProfileParams::Thread thread) {
   if (!StackSamplingConfiguration::Get()->IsProfilerEnabledForCurrentProcess())
@@ -274,6 +281,8 @@
           base::BindOnce(&ThreadProfiler::OnPeriodicCollectionCompleted,
                          owning_thread_task_runner_,
                          weak_factory_.GetWeakPtr())));
+  if (aux_unwinder_)
+    periodic_profiler_->AddAuxUnwinder(aux_unwinder_.get());
 
   periodic_profiler_->Start();
 }
diff --git a/chrome/common/thread_profiler.h b/chrome/common/thread_profiler.h
index 5a3de1f..16edefe 100644
--- a/chrome/common/thread_profiler.h
+++ b/chrome/common/thread_profiler.h
@@ -10,6 +10,7 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/profiler/stack_sampling_profiler.h"
+#include "base/profiler/unwinder.h"
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread.h"
 #include "base/threading/thread_checker.h"
@@ -66,6 +67,11 @@
   void SetMainThreadTaskRunner(
       scoped_refptr<base::SingleThreadTaskRunner> task_runner);
 
+  // Adds an auxiliary unwinder to supply to the StackSamplingProfiler to handle
+  // additional, non-native-code unwind scenarios. Currently used to support
+  // unwinding V8 JavaScript frames.
+  void AddAuxUnwinder(std::unique_ptr<base::Unwinder> unwinder);
+
   // Creates a profiler for a child thread and immediately starts it. This
   // should be called from a task posted on the child thread immediately after
   // thread start. The thread will be profiled until exit.
@@ -118,6 +124,8 @@
 
   std::unique_ptr<WorkIdRecorder> work_id_recorder_;
 
+  std::unique_ptr<base::Unwinder> aux_unwinder_;
+
   std::unique_ptr<base::StackSamplingProfiler> startup_profiler_;
 
   std::unique_ptr<base::StackSamplingProfiler> periodic_profiler_;
diff --git a/chrome/common/webui_url_constants.cc b/chrome/common/webui_url_constants.cc
index cf6f7dd..9c301c7 100644
--- a/chrome/common/webui_url_constants.cc
+++ b/chrome/common/webui_url_constants.cc
@@ -191,6 +191,8 @@
 const char kChromeUIActivationMessageHost[] = "activationmessage";
 const char kChromeUIBluetoothPairingHost[] = "bluetooth-pairing";
 const char kChromeUIBluetoothPairingURL[] = "chrome://bluetooth-pairing/";
+const char kChromeUICellularSetupHost[] = "cellular-setup";
+const char kChromeUICellularSetupUrl[] = "chrome://cellular-setup";
 const char kChromeUICertificateManagerDialogURL[] =
     "chrome://certificate-manager/";
 const char kChromeUICertificateManagerHost[] = "certificate-manager";
diff --git a/chrome/common/webui_url_constants.h b/chrome/common/webui_url_constants.h
index 1a06412..acd86f9 100644
--- a/chrome/common/webui_url_constants.h
+++ b/chrome/common/webui_url_constants.h
@@ -189,6 +189,8 @@
 extern const char kChromeUIActivationMessageHost[];
 extern const char kChromeUIBluetoothPairingHost[];
 extern const char kChromeUIBluetoothPairingURL[];
+extern const char kChromeUICellularSetupHost[];
+extern const char kChromeUICellularSetupUrl[];
 extern const char kChromeUICertificateManagerDialogURL[];
 extern const char kChromeUICertificateManagerHost[];
 extern const char kChromeUICryptohomeHost[];
diff --git a/chrome/renderer/url_loader_throttle_provider_impl.cc b/chrome/renderer/url_loader_throttle_provider_impl.cc
index 4374489..f124847 100644
--- a/chrome/renderer/url_loader_throttle_provider_impl.cc
+++ b/chrome/renderer/url_loader_throttle_provider_impl.cc
@@ -178,7 +178,8 @@
   // Don't add them for frame requests.
   bool is_frame_resource = content::IsResourceTypeFrame(resource_type);
 
-  DCHECK(!is_frame_resource || IsTypeFrame());
+  DCHECK(!is_frame_resource ||
+         type_ == content::URLLoaderThrottleProviderType::kFrame);
 
   if (data_reduction_proxy_manager_) {
     throttles.push_back(
@@ -191,28 +192,15 @@
        base::FeatureList::IsEnabled(
            safe_browsing::kCheckByURLLoaderThrottle)) &&
       !is_frame_resource) {
-    if (safe_browsing_info_) {
-      scoped_refptr<base::SingleThreadTaskRunner> task_runner =
-          base::ThreadTaskRunnerHandle::Get();
-      if (IsTypeFrame()) {
-        DCHECK(content::RenderThread::IsMainThread());
-        content::RenderFrame* render_frame =
-            content::RenderFrame::FromRoutingID(render_frame_id);
-        if (render_frame) {
-          task_runner =
-              render_frame->GetTaskRunner(blink::TaskType::kInternalDefault);
-        }
-      }
-      safe_browsing_.Bind(std::move(safe_browsing_info_),
-                          std::move(task_runner));
-    }
+    if (safe_browsing_info_)
+      safe_browsing_.Bind(std::move(safe_browsing_info_));
     throttles.push_back(
         std::make_unique<safe_browsing::RendererURLLoaderThrottle>(
             safe_browsing_.get(), render_frame_id));
   }
 
-  if (IsTypeFrame() && !is_frame_resource) {
-    DCHECK(content::RenderThread::IsMainThread());
+  if (type_ == content::URLLoaderThrottleProviderType::kFrame &&
+      !is_frame_resource) {
     content::RenderFrame* render_frame =
         content::RenderFrame::FromRoutingID(render_frame_id);
     auto* prerender_helper =
@@ -239,7 +227,8 @@
   }
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
-  if (network_service_enabled && IsTypeFrame() &&
+  if (network_service_enabled &&
+      type_ == content::URLLoaderThrottleProviderType::kFrame &&
       resource_type == content::RESOURCE_TYPE_OBJECT) {
     content::RenderFrame* render_frame =
         content::RenderFrame::FromRoutingID(render_frame_id);
@@ -303,7 +292,3 @@
     extension_throttle_manager_->SetOnline(is_online);
 #endif
 }
-
-bool URLLoaderThrottleProviderImpl::IsTypeFrame() const {
-  return type_ == content::URLLoaderThrottleProviderType::kFrame;
-}
diff --git a/chrome/renderer/url_loader_throttle_provider_impl.h b/chrome/renderer/url_loader_throttle_provider_impl.h
index f3ec637..f2b78d68 100644
--- a/chrome/renderer/url_loader_throttle_provider_impl.h
+++ b/chrome/renderer/url_loader_throttle_provider_impl.h
@@ -48,11 +48,6 @@
   // general use.
   URLLoaderThrottleProviderImpl(const URLLoaderThrottleProviderImpl& other);
 
-  // Reports whether the provider type is frame or not. When this is true, the
-  // member functions are called on the main thread. Otherwise, they are called
-  // on worker threads.
-  bool IsTypeFrame() const;
-
   std::unique_ptr<subresource_filter::AdDelayThrottle::Factory>
       ad_delay_factory_;
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index f8e14bc..3978031 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -868,6 +868,7 @@
       "../browser/resource_coordinator/tab_activity_watcher_browsertest.cc",
       "../browser/resource_coordinator/tab_manager_browsertest.cc",
       "../browser/safe_browsing/chrome_cleaner/reporter_runner_browsertest_win.cc",
+      "../browser/safe_browsing/download_protection/download_protection_service_browsertest.cc",
       "../browser/safe_browsing/test_safe_browsing_database_helper.cc",
       "../browser/safe_browsing/test_safe_browsing_database_helper.h",
       "../browser/safe_json_parser_browsertest.cc",
diff --git a/chrome/test/data/safe_browsing/download_protection/zipfile_two_archives.zip b/chrome/test/data/safe_browsing/download_protection/zipfile_two_archives.zip
new file mode 100644
index 0000000..d7af463
--- /dev/null
+++ b/chrome/test/data/safe_browsing/download_protection/zipfile_two_archives.zip
Binary files differ
diff --git a/chrome/test/data/safe_browsing/rar/has_two_archives.rar b/chrome/test/data/safe_browsing/rar/has_two_archives.rar
new file mode 100644
index 0000000..416bd852
--- /dev/null
+++ b/chrome/test/data/safe_browsing/rar/has_two_archives.rar
Binary files differ
diff --git a/chrome/test/data/webui/settings/payments_section_test.js b/chrome/test/data/webui/settings/payments_section_test.js
index ccb978b..4e2397d 100644
--- a/chrome/test/data/webui/settings/payments_section_test.js
+++ b/chrome/test/data/webui/settings/payments_section_test.js
@@ -19,20 +19,10 @@
   });
 
   suite('PaymentsSection', function() {
-    /** @type {settings.SyncBrowserProxy} */
-    let syncBrowserProxy = null;
-
     setup(function() {
-      syncBrowserProxy = new TestSyncBrowserProxy();
-      settings.SyncBrowserProxyImpl.instance_ = syncBrowserProxy;
       PolymerTest.clearBody();
       loadTimeData.overrideValues({
         migrationEnabled: true,
-        hasGooglePaymentsAccount: true,
-        upstreamEnabled: true,
-        isUsingSecondaryPassphrase: false,
-        uploadToGoogleActive: true,
-        userEmailDomainAllowed: true,
       });
     });
 
@@ -409,8 +399,7 @@
     });
 
     test('verifyMigrationButtonNotShownIfMigrationNotEnabled', function() {
-      // Mock the Google Payments account. Disable the migration experimental
-      // flag. Won't show migration button.
+      // Mock prerequisites are not met.
       loadTimeData.overrideValues({migrationEnabled: false});
 
       // Add one migratable credit card.
@@ -419,196 +408,31 @@
       const section = createPaymentsSection(
           [creditCard], {credit_card_enabled: {value: true}});
 
-      // Simulate Signed-in and Synced status.
-      sync_test_util.simulateSyncStatus({
-        signedIn: true,
-        syncSystemEnabled: true,
-      });
-
-      // All migration requirements are met but migration experimental flag is
-      // not enabled, verify migration button is hidden.
       assertTrue(section.$$('#migrateCreditCards').hidden);
     });
 
-    test('verifyMigrationButtonNotShownIfNotSignedIn', function() {
+    test('verifyMigrationButtonNotShownIfCreditCardDisabled', function() {
       // Add one migratable credit card.
       const creditCard = FakeDataMaker.creditCardEntry();
       creditCard.metadata.isMigratable = true;
+      // Mock credit card save toggle is turned off by users.
       const section = createPaymentsSection(
-          [creditCard], {credit_card_enabled: {value: true}});
+          [creditCard], {credit_card_enabled: {value: false}});
 
-      // Simulate not Signed-in status. Won't show migration button.
-      sync_test_util.simulateSyncStatus({
-        signedIn: false,
-        syncSystemEnabled: true,
-      });
-
-      // All migration requirements are met but not signed in, verify migration
-      // button is hidden.
       assertTrue(section.$$('#migrateCreditCards').hidden);
     });
 
-    test('verifyMigrationButtonNotShownIfNotSynced', function() {
+    test('verifyMigrationButtonNotShownIfNoCardIsMigratable', function() {
       // Add one migratable credit card.
       const creditCard = FakeDataMaker.creditCardEntry();
-      creditCard.metadata.isMigratable = true;
-      const section = createPaymentsSection(
-          [creditCard], {credit_card_enabled: {value: true}});
-
-      // Simulate not Synced status. Won't show migration button.
-      sync_test_util.simulateSyncStatus({
-        signedIn: true,
-        syncSystemEnabled: false,
-      });
-
-      // All migration requirements are met but not Synced, verify migration
-      // button is hidden.
-      assertTrue(section.$$('#migrateCreditCards').hidden);
-    });
-
-    test('verifyMigrationButtonNotShownIfNoMigratableCard', function() {
-      // Add one credit card but not migratable. Won't show migration button.
-      const creditCard = FakeDataMaker.creditCardEntry();
+      // Mock credit card is not valid.
       creditCard.metadata.isMigratable = false;
       const section = createPaymentsSection(
           [creditCard], {credit_card_enabled: {value: true}});
 
-      // Simulate Signed-in and Synced status.
-      sync_test_util.simulateSyncStatus({
-        signedIn: true,
-        syncSystemEnabled: true,
-      });
-
-      // All migration requirements are met but no migratable credi card, verify
-      // migration button is hidden.
       assertTrue(section.$$('#migrateCreditCards').hidden);
     });
 
-    test('verifyMigrationButtonNotShownWhenCreditCardDisabled', function() {
-      // Add one migratable credit card.
-      const creditCard = FakeDataMaker.creditCardEntry();
-      creditCard.metadata.isMigratable = true;
-      const section = createPaymentsSection(
-          [creditCard], {credit_card_enabled: {value: false}});
-
-      // Simulate Signed-in and Synced status.
-      sync_test_util.simulateSyncStatus({
-        signedIn: true,
-        syncSystemEnabled: true,
-      });
-
-      // All migration requirements are met but credit card is disable, verify
-      // migration button is hidden.
-      assertTrue(section.$$('#migrateCreditCards').hidden);
-    });
-
-    test('verifyMigrationButtonNotShownIfNoGooglePaymentsAccount', function() {
-      // Mocks no Google payments account. Won't show migration button.
-      loadTimeData.overrideValues({hasGooglePaymentsAccount: false});
-
-      // Add one migratable credit card.
-      const creditCard = FakeDataMaker.creditCardEntry();
-      creditCard.metadata.isMigratable = true;
-      const section = createPaymentsSection(
-          [creditCard], {credit_card_enabled: {value: true}});
-
-      // Simulate Signed-in and Synced status.
-      sync_test_util.simulateSyncStatus({
-        signedIn: true,
-        syncSystemEnabled: true,
-      });
-
-      // All migration requirements are met but no Google Payments account,
-      // verify migration button is hidden.
-      assertTrue(section.$$('#migrateCreditCards').hidden);
-    });
-
-    test('verifyMigrationButtonNotShownIfAutofillUpstreamDisabled', function() {
-      loadTimeData.overrideValues({upstreamEnabled: false});
-
-      // Add one migratable credit card.
-      const creditCard = FakeDataMaker.creditCardEntry();
-      creditCard.metadata.isMigratable = true;
-      const section = createPaymentsSection(
-          [creditCard], {credit_card_enabled: {value: true}});
-
-      // Simulate Signed-in and Synced status.
-      sync_test_util.simulateSyncStatus({
-        signedIn: true,
-        syncSystemEnabled: true,
-      });
-
-      // All migration requirements are met but Autofill Upstream is disabled,
-      // verify migration button is hidden.
-      assertTrue(section.$$('#migrateCreditCards').hidden);
-    });
-
-    test(
-        'verifyMigrationButtonNotShownIfUserHasSecondaryPassphrase',
-        function() {
-          loadTimeData.overrideValues({isUsingSecondaryPassphrase: true});
-
-          // Add one migratable credit card.
-          const creditCard = FakeDataMaker.creditCardEntry();
-          creditCard.metadata.isMigratable = true;
-          const section = createPaymentsSection(
-              [creditCard], {credit_card_enabled: {value: true}});
-
-          // Simulate Signed-in and Synced status.
-          sync_test_util.simulateSyncStatus({
-            signedIn: true,
-            syncSystemEnabled: true,
-          });
-
-          // All migration requirements are met but the user has a secondary
-          // passphrase, verify migration button is hidden.
-          assertTrue(section.$$('#migrateCreditCards').hidden);
-        });
-
-    test(
-        'verifyMigrationButtonNotShownIfUploadToGoogleStateIsInactive',
-        function() {
-          loadTimeData.overrideValues({uploadToGoogleActive: false});
-
-          // Add one migratable credit card.
-          const creditCard = FakeDataMaker.creditCardEntry();
-          creditCard.metadata.isMigratable = true;
-          const section = createPaymentsSection(
-              [creditCard], {credit_card_enabled: {value: true}});
-
-          // Simulate Signed-in and Synced status.
-          sync_test_util.simulateSyncStatus({
-            signedIn: true,
-            syncSystemEnabled: true,
-          });
-
-          // All migration requirements are met but upload to Google is
-          // inactive, verify migration button is hidden.
-          assertTrue(section.$$('#migrateCreditCards').hidden);
-        });
-
-    test(
-        'verifyMigrationButtonNotShownIfUserEmailDomainIsNotAllowed',
-        function() {
-          loadTimeData.overrideValues({userEmailDomainAllowed: false});
-
-          // Add one migratable credit card.
-          const creditCard = FakeDataMaker.creditCardEntry();
-          creditCard.metadata.isMigratable = true;
-          const section = createPaymentsSection(
-              [creditCard], {credit_card_enabled: {value: true}});
-
-          // Simulate Signed-in and Synced status.
-          sync_test_util.simulateSyncStatus({
-            signedIn: true,
-            syncSystemEnabled: true,
-          });
-
-          // All migration requirements are met but the user's email domain is
-          // not allowed, verify migration button is hidden.
-          assertTrue(section.$$('#migrateCreditCards').hidden);
-        });
-
     test('verifyMigrationButtonShown', function() {
       // Add one migratable credit card.
       const creditCard = FakeDataMaker.creditCardEntry();
@@ -616,13 +440,6 @@
       const section = createPaymentsSection(
           [creditCard], {credit_card_enabled: {value: true}});
 
-      // Simulate Signed-in and Synced status.
-      sync_test_util.simulateSyncStatus({
-        signedIn: true,
-        syncSystemEnabled: true,
-      });
-
-      // All migration requirements are met, verify migration button is shown.
       assertFalse(section.$$('#migrateCreditCards').hidden);
     });
   });
diff --git a/chrome/test/data/xr/e2e_test_files/html/test_stationary_reference_space_rejects.html b/chrome/test/data/xr/e2e_test_files/html/test_stationary_reference_space_rejects.html
new file mode 100644
index 0000000..12cf99a
--- /dev/null
+++ b/chrome/test/data/xr/e2e_test_files/html/test_stationary_reference_space_rejects.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<!--
+Tests that a request for a stationary reference space is rejected.
+-->
+<html>
+  <head>
+    <link rel="stylesheet" type="text/css" href="../resources/webxr_e2e.css">
+  </head>
+  <body>
+    <script src="../../../../../../third_party/blink/web_tests/resources/testharness.js"></script>
+    <script src="../resources/webxr_e2e.js"></script>
+    <script>
+      let session_ = null;
+      navigator.xr.requestSession()
+      .then((xrSession) => {
+        session_ = xrSession;
+        return xrSession.requestReferenceSpace({ type: 'stationary', subtype: 'floor-level'});
+      })
+      .then((refSpace) => {
+        assert_unreached("Should not be able to get a stationary reference space");
+      }, (err) => {
+        assert_not_equals(session_, null, "Ensure session was created");
+        assert_equals(err.name, "NotSupportedError", "Ensure we got the right error");
+        done();
+      });
+    </script>
+  </body>
+</html>
diff --git a/chrome/test/mini_installer/test_chrome_with_chromedriver.py b/chrome/test/mini_installer/test_chrome_with_chromedriver.py
index b492bb4..fd8fd80 100644
--- a/chrome/test/mini_installer/test_chrome_with_chromedriver.py
+++ b/chrome/test/mini_installer/test_chrome_with_chromedriver.py
@@ -45,13 +45,13 @@
 
 @contextlib.contextmanager
 def CreateChromedriver(args):
-  """Create a webdriver object ad close it after."""
+  """Create a webdriver object and close it after."""
 
   def DeleteWithRetry(path, func):
     # There seems to be a race condition on the bots that causes the paths
-    # to not delete because they are being used. This allows up to 2 seconds
+    # to not delete because they are being used. This allows up to 4 seconds
     # to delete
-    for _ in xrange(4):
+    for _ in xrange(8):
       try:
         return func(path)
       except WindowsError:
@@ -129,18 +129,17 @@
           target = os.path.join(args.output_dir, os.path.basename(log_file))
           shutil.copyfile(log_file, target)
           logging.error('Saved Chrome log to %s', target)
-      DeleteWithRetry(log_file, os.remove)
+      try:
+        DeleteWithRetry(log_file, os.remove)
+      except WindowsError:
+        # Don't fail the test if the log file couldn't be deleted.
+        logging.exception('Failed to delete log file %s' % log_file)
     if report_count:
       raise Exception('Failing test due to %s crash reports found' %
                       report_count)
 
 
 def main():
-  # DISABLING
-  # https://crbug.com/949727
-  # DISABLING
-  return 0
-
   """Main entry point."""
   parser = parser = argparse.ArgumentParser(
     description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
diff --git a/chromeos/dbus/BUILD.gn b/chromeos/dbus/BUILD.gn
index 8f5ff9e..9a62e07 100644
--- a/chromeos/dbus/BUILD.gn
+++ b/chromeos/dbus/BUILD.gn
@@ -158,6 +158,8 @@
     "shill/modem_messaging_client.h",
     "shill/shill_client_helper.cc",
     "shill/shill_client_helper.h",
+    "shill/shill_clients.cc",
+    "shill/shill_clients.h",
     "shill/shill_device_client.cc",
     "shill/shill_device_client.h",
     "shill/shill_ipconfig_client.cc",
diff --git a/chromeos/dbus/README.md b/chromeos/dbus/README.md
index e664e10f..48bb688b 100644
--- a/chromeos/dbus/README.md
+++ b/chromeos/dbus/README.md
@@ -41,6 +41,15 @@
     (Many existing clients provide additional test functionality in the fake
     implementation, however this complicates tests and the fake implementation).
 
+## Shill clients
+
+Shill clients will eventually only be available to Chrome. As such, the
+DBusThreadManager::GetShill*Client() methods have been left intact for now.
+However, the clients are no longer owned by DBusClientsCommon so that they can
+be initialized independent of DBusThreadManager.
+
+New code should prefer Shill*Client::Get() over the DBusThreadManager accessors.
+
 ## Older clients that have been removed:
 
 *   Amplifier (`amplifier_client.cc`)
diff --git a/chromeos/dbus/dbus_clients_common.cc b/chromeos/dbus/dbus_clients_common.cc
index 1a628086..6367250e 100644
--- a/chromeos/dbus/dbus_clients_common.cc
+++ b/chromeos/dbus/dbus_clients_common.cc
@@ -4,30 +4,10 @@
 
 #include "chromeos/dbus/dbus_clients_common.h"
 
-#include "base/command_line.h"
-#include "chromeos/dbus/constants/dbus_switches.h"
 #include "chromeos/dbus/cras_audio_client.h"
 #include "chromeos/dbus/dbus_client_implementation_type.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/fake_cras_audio_client.h"
-#include "chromeos/dbus/shill/fake_gsm_sms_client.h"
-#include "chromeos/dbus/shill/fake_modem_messaging_client.h"
-#include "chromeos/dbus/shill/fake_shill_device_client.h"
-#include "chromeos/dbus/shill/fake_shill_ipconfig_client.h"
-#include "chromeos/dbus/shill/fake_shill_manager_client.h"
-#include "chromeos/dbus/shill/fake_shill_profile_client.h"
-#include "chromeos/dbus/shill/fake_shill_service_client.h"
-#include "chromeos/dbus/shill/fake_shill_third_party_vpn_driver_client.h"
-#include "chromeos/dbus/shill/fake_sms_client.h"
-#include "chromeos/dbus/shill/gsm_sms_client.h"
-#include "chromeos/dbus/shill/modem_messaging_client.h"
-#include "chromeos/dbus/shill/shill_device_client.h"
-#include "chromeos/dbus/shill/shill_ipconfig_client.h"
-#include "chromeos/dbus/shill/shill_manager_client.h"
-#include "chromeos/dbus/shill/shill_profile_client.h"
-#include "chromeos/dbus/shill/shill_service_client.h"
-#include "chromeos/dbus/shill/shill_third_party_vpn_driver_client.h"
-#include "chromeos/dbus/shill/sms_client.h"
 
 namespace chromeos {
 
@@ -36,44 +16,6 @@
     cras_audio_client_.reset(CrasAudioClient::Create());
   else
     cras_audio_client_.reset(new FakeCrasAudioClient);
-
-  if (use_real_clients) {
-    shill_manager_client_.reset(ShillManagerClient::Create());
-    shill_device_client_.reset(ShillDeviceClient::Create());
-    shill_ipconfig_client_.reset(ShillIPConfigClient::Create());
-    shill_service_client_.reset(ShillServiceClient::Create());
-    shill_profile_client_.reset(ShillProfileClient::Create());
-    shill_third_party_vpn_driver_client_.reset(
-        ShillThirdPartyVpnDriverClient::Create());
-  } else {
-    shill_manager_client_.reset(new FakeShillManagerClient);
-    shill_device_client_.reset(new FakeShillDeviceClient);
-    shill_ipconfig_client_.reset(new FakeShillIPConfigClient);
-    shill_service_client_.reset(new FakeShillServiceClient);
-    shill_profile_client_.reset(new FakeShillProfileClient);
-    shill_third_party_vpn_driver_client_.reset(
-        new FakeShillThirdPartyVpnDriverClient);
-  }
-
-  if (use_real_clients) {
-    gsm_sms_client_.reset(GsmSMSClient::Create());
-  } else {
-    FakeGsmSMSClient* gsm_sms_client = new FakeGsmSMSClient();
-    gsm_sms_client->set_sms_test_message_switch_present(
-        base::CommandLine::ForCurrentProcess()->HasSwitch(
-            chromeos::switches::kSmsTestMessages));
-    gsm_sms_client_.reset(gsm_sms_client);
-  }
-
-  if (use_real_clients)
-    modem_messaging_client_.reset(ModemMessagingClient::Create());
-  else
-    modem_messaging_client_.reset(new FakeModemMessagingClient);
-
-  if (use_real_clients)
-    sms_client_.reset(SMSClient::Create());
-  else
-    sms_client_.reset(new FakeSMSClient);
 }
 
 DBusClientsCommon::~DBusClientsCommon() = default;
@@ -82,20 +24,6 @@
   DCHECK(DBusThreadManager::IsInitialized());
 
   cras_audio_client_->Init(system_bus);
-  gsm_sms_client_->Init(system_bus);
-  modem_messaging_client_->Init(system_bus);
-  shill_device_client_->Init(system_bus);
-  shill_ipconfig_client_->Init(system_bus);
-  shill_manager_client_->Init(system_bus);
-  shill_service_client_->Init(system_bus);
-  shill_profile_client_->Init(system_bus);
-  shill_third_party_vpn_driver_client_->Init(system_bus);
-  sms_client_->Init(system_bus);
-
-  ShillManagerClient::TestInterface* manager =
-      shill_manager_client_->GetTestInterface();
-  if (manager)
-    manager->SetupDefaultEnvironment();
 }
 
 }  // namespace chromeos
diff --git a/chromeos/dbus/dbus_clients_common.h b/chromeos/dbus/dbus_clients_common.h
index bbbe6d1..f8d26ce 100644
--- a/chromeos/dbus/dbus_clients_common.h
+++ b/chromeos/dbus/dbus_clients_common.h
@@ -17,15 +17,6 @@
 namespace chromeos {
 
 class CrasAudioClient;
-class GsmSMSClient;
-class ModemMessagingClient;
-class ShillDeviceClient;
-class ShillIPConfigClient;
-class ShillManagerClient;
-class ShillProfileClient;
-class ShillServiceClient;
-class ShillThirdPartyVpnDriverClient;
-class SMSClient;
 
 // D-Bus clients used in multiple processes (e.g. ash, browser, mus).
 class COMPONENT_EXPORT(CHROMEOS_DBUS) DBusClientsCommon {
@@ -43,16 +34,6 @@
   friend class DBusThreadManagerSetter;
 
   std::unique_ptr<CrasAudioClient> cras_audio_client_;
-  std::unique_ptr<GsmSMSClient> gsm_sms_client_;
-  std::unique_ptr<ModemMessagingClient> modem_messaging_client_;
-  std::unique_ptr<ShillDeviceClient> shill_device_client_;
-  std::unique_ptr<ShillIPConfigClient> shill_ipconfig_client_;
-  std::unique_ptr<ShillManagerClient> shill_manager_client_;
-  std::unique_ptr<ShillServiceClient> shill_service_client_;
-  std::unique_ptr<ShillProfileClient> shill_profile_client_;
-  std::unique_ptr<ShillThirdPartyVpnDriverClient>
-      shill_third_party_vpn_driver_client_;
-  std::unique_ptr<SMSClient> sms_client_;
 
   DISALLOW_COPY_AND_ASSIGN(DBusClientsCommon);
 };
diff --git a/chromeos/dbus/dbus_thread_manager.cc b/chromeos/dbus/dbus_thread_manager.cc
index 15846c2..de4e5cd4 100644
--- a/chromeos/dbus/dbus_thread_manager.cc
+++ b/chromeos/dbus/dbus_thread_manager.cc
@@ -32,6 +32,7 @@
 #include "chromeos/dbus/seneschal_client.h"
 #include "chromeos/dbus/shill/gsm_sms_client.h"
 #include "chromeos/dbus/shill/modem_messaging_client.h"
+#include "chromeos/dbus/shill/shill_clients.h"
 #include "chromeos/dbus/shill/shill_device_client.h"
 #include "chromeos/dbus/shill/shill_ipconfig_client.h"
 #include "chromeos/dbus/shill/shill_manager_client.h"
@@ -166,32 +167,32 @@
 }
 
 ShillDeviceClient* DBusThreadManager::GetShillDeviceClient() {
-  return clients_common_->shill_device_client_.get();
+  return ShillDeviceClient::Get();
 }
 
 ShillIPConfigClient* DBusThreadManager::GetShillIPConfigClient() {
-  return clients_common_->shill_ipconfig_client_.get();
+  return ShillIPConfigClient::Get();
 }
 
 ShillManagerClient* DBusThreadManager::GetShillManagerClient() {
-  return clients_common_->shill_manager_client_.get();
+  return ShillManagerClient::Get();
 }
 
 ShillServiceClient* DBusThreadManager::GetShillServiceClient() {
-  return clients_common_->shill_service_client_.get();
+  return ShillServiceClient::Get();
 }
 
 ShillProfileClient* DBusThreadManager::GetShillProfileClient() {
-  return clients_common_->shill_profile_client_.get();
+  return ShillProfileClient::Get();
 }
 
 ShillThirdPartyVpnDriverClient*
 DBusThreadManager::GetShillThirdPartyVpnDriverClient() {
-  return clients_common_->shill_third_party_vpn_driver_client_.get();
+  return ShillThirdPartyVpnDriverClient::Get();
 }
 
 GsmSMSClient* DBusThreadManager::GetGsmSMSClient() {
-  return clients_common_->gsm_sms_client_.get();
+  return GsmSMSClient::Get();
 }
 
 ImageBurnerClient* DBusThreadManager::GetImageBurnerClient() {
@@ -210,7 +211,7 @@
 }
 
 ModemMessagingClient* DBusThreadManager::GetModemMessagingClient() {
-  return clients_common_->modem_messaging_client_.get();
+  return ModemMessagingClient::Get();
 }
 
 OobeConfigurationClient* DBusThreadManager::GetOobeConfigurationClient() {
@@ -232,7 +233,7 @@
 }
 
 SMSClient* DBusThreadManager::GetSMSClient() {
-  return clients_common_->sms_client_.get();
+  return SMSClient::Get();
 }
 
 UpdateEngineClient* DBusThreadManager::GetUpdateEngineClient() {
@@ -251,6 +252,14 @@
   DCHECK(g_dbus_thread_manager);
 
   clients_common_->Initialize(GetSystemBus());
+
+  // TODO(stevenjb): Move these to dbus_helper.cc in src/chrome and any tests
+  // that require Shill clients. https://crbug.com/948390.
+  if (use_real_clients_)
+    shill_clients::Initialize(GetSystemBus());
+  else
+    shill_clients::InitializeFakes();
+
   if (clients_browser_)
     clients_browser_->Initialize(GetSystemBus());
 
@@ -309,6 +318,10 @@
 void DBusThreadManager::Shutdown() {
   // Ensure that we only shutdown DBusThreadManager once.
   CHECK(g_dbus_thread_manager);
+
+  // TODO(stevenjb): Remove. https://crbug.com/948390.
+  shill_clients::Shutdown();
+
   DBusThreadManager* dbus_thread_manager = g_dbus_thread_manager;
   g_dbus_thread_manager = nullptr;
   g_using_dbus_thread_manager_for_testing = false;
@@ -369,43 +382,6 @@
       std::move(client);
 }
 
-void DBusThreadManagerSetter::SetShillDeviceClient(
-    std::unique_ptr<ShillDeviceClient> client) {
-  DBusThreadManager::Get()->clients_common_->shill_device_client_ =
-      std::move(client);
-}
-
-void DBusThreadManagerSetter::SetShillIPConfigClient(
-    std::unique_ptr<ShillIPConfigClient> client) {
-  DBusThreadManager::Get()->clients_common_->shill_ipconfig_client_ =
-      std::move(client);
-}
-
-void DBusThreadManagerSetter::SetShillManagerClient(
-    std::unique_ptr<ShillManagerClient> client) {
-  DBusThreadManager::Get()->clients_common_->shill_manager_client_ =
-      std::move(client);
-}
-
-void DBusThreadManagerSetter::SetShillServiceClient(
-    std::unique_ptr<ShillServiceClient> client) {
-  DBusThreadManager::Get()->clients_common_->shill_service_client_ =
-      std::move(client);
-}
-
-void DBusThreadManagerSetter::SetShillProfileClient(
-    std::unique_ptr<ShillProfileClient> client) {
-  DBusThreadManager::Get()->clients_common_->shill_profile_client_ =
-      std::move(client);
-}
-
-void DBusThreadManagerSetter::SetShillThirdPartyVpnDriverClient(
-    std::unique_ptr<ShillThirdPartyVpnDriverClient> client) {
-  DBusThreadManager::Get()
-      ->clients_common_->shill_third_party_vpn_driver_client_ =
-      std::move(client);
-}
-
 void DBusThreadManagerSetter::SetImageBurnerClient(
     std::unique_ptr<ImageBurnerClient> client) {
   DBusThreadManager::Get()->clients_browser_->image_burner_client_ =
diff --git a/chromeos/dbus/dbus_thread_manager.h b/chromeos/dbus/dbus_thread_manager.h
index 63ff079e8..b5efc2e 100644
--- a/chromeos/dbus/dbus_thread_manager.h
+++ b/chromeos/dbus/dbus_thread_manager.h
@@ -128,24 +128,27 @@
   DebugDaemonClient* GetDebugDaemonClient();
   DiagnosticsdClient* GetDiagnosticsdClient();
   EasyUnlockClient* GetEasyUnlockClient();
-  GsmSMSClient* GetGsmSMSClient();
   ImageBurnerClient* GetImageBurnerClient();
   ImageLoaderClient* GetImageLoaderClient();
   LorgnetteManagerClient* GetLorgnetteManagerClient();
-  ModemMessagingClient* GetModemMessagingClient();
   OobeConfigurationClient* GetOobeConfigurationClient();
   RuntimeProbeClient* GetRuntimeProbeClient();
   SeneschalClient* GetSeneschalClient();
+  SmbProviderClient* GetSmbProviderClient();
+  UpdateEngineClient* GetUpdateEngineClient();
+  VirtualFileProviderClient* GetVirtualFileProviderClient();
+
+  // DEPRECATED, DO NOT USE. The static getter for each of these classes should
+  // be used instead. TODO(stevenjb): Remove. https://crbug.com/948390.
+  GsmSMSClient* GetGsmSMSClient();
+  ModemMessagingClient* GetModemMessagingClient();
+  SMSClient* GetSMSClient();
   ShillDeviceClient* GetShillDeviceClient();
   ShillIPConfigClient* GetShillIPConfigClient();
   ShillManagerClient* GetShillManagerClient();
   ShillProfileClient* GetShillProfileClient();
   ShillServiceClient* GetShillServiceClient();
   ShillThirdPartyVpnDriverClient* GetShillThirdPartyVpnDriverClient();
-  SmbProviderClient* GetSmbProviderClient();
-  SMSClient* GetSMSClient();
-  UpdateEngineClient* GetUpdateEngineClient();
-  VirtualFileProviderClient* GetVirtualFileProviderClient();
 
  private:
   friend class DBusThreadManagerSetter;
@@ -188,13 +191,6 @@
   void SetImageLoaderClient(std::unique_ptr<ImageLoaderClient> client);
   void SetSeneschalClient(std::unique_ptr<SeneschalClient> client);
   void SetRuntimeProbeClient(std::unique_ptr<RuntimeProbeClient> client);
-  void SetShillDeviceClient(std::unique_ptr<ShillDeviceClient> client);
-  void SetShillIPConfigClient(std::unique_ptr<ShillIPConfigClient> client);
-  void SetShillManagerClient(std::unique_ptr<ShillManagerClient> client);
-  void SetShillServiceClient(std::unique_ptr<ShillServiceClient> client);
-  void SetShillProfileClient(std::unique_ptr<ShillProfileClient> client);
-  void SetShillThirdPartyVpnDriverClient(
-      std::unique_ptr<ShillThirdPartyVpnDriverClient> client);
   void SetSmbProviderClient(std::unique_ptr<SmbProviderClient> client);
   void SetUpdateEngineClient(std::unique_ptr<UpdateEngineClient> client);
 
diff --git a/chromeos/dbus/shill/fake_gsm_sms_client.cc b/chromeos/dbus/shill/fake_gsm_sms_client.cc
index 5b3ad438..77301505 100644
--- a/chromeos/dbus/shill/fake_gsm_sms_client.cc
+++ b/chromeos/dbus/shill/fake_gsm_sms_client.cc
@@ -2,22 +2,26 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <stdint.h>
+#include "chromeos/dbus/shill/fake_gsm_sms_client.h"
 
+#include <stdint.h>
 #include <memory>
 #include <utility>
 
 #include "base/bind.h"
+#include "base/command_line.h"
 #include "base/location.h"
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
-#include "chromeos/dbus/shill/fake_gsm_sms_client.h"
+#include "chromeos/dbus/constants/dbus_switches.h"
 
 namespace chromeos {
 
 FakeGsmSMSClient::FakeGsmSMSClient()
     : test_index_(-1),
-      sms_test_message_switch_present_(false),
+      sms_test_message_switch_present_(
+          base::CommandLine::ForCurrentProcess()->HasSwitch(
+              chromeos::switches::kSmsTestMessages)),
       weak_ptr_factory_(this) {
   test_messages_.push_back("Test Message 0");
   test_messages_.push_back("Test Message 1");
@@ -32,8 +36,6 @@
 
 FakeGsmSMSClient::~FakeGsmSMSClient() = default;
 
-void FakeGsmSMSClient::Init(dbus::Bus* bus) {}
-
 void FakeGsmSMSClient::SetSmsReceivedHandler(
     const std::string& service_name,
     const dbus::ObjectPath& object_path,
diff --git a/chromeos/dbus/shill/fake_gsm_sms_client.h b/chromeos/dbus/shill/fake_gsm_sms_client.h
index 12d46a4..7d86470 100644
--- a/chromeos/dbus/shill/fake_gsm_sms_client.h
+++ b/chromeos/dbus/shill/fake_gsm_sms_client.h
@@ -25,7 +25,6 @@
   ~FakeGsmSMSClient() override;
 
   // GsmSMSClient overrides
-  void Init(dbus::Bus* bus) override;
   void SetSmsReceivedHandler(const std::string& service_name,
                              const dbus::ObjectPath& object_path,
                              const SmsReceivedHandler& handler) override;
@@ -45,12 +44,6 @@
   void RequestUpdate(const std::string& service_name,
                      const dbus::ObjectPath& object_path) override;
 
-  // Sets if the command line switch for test is present. RequestUpdate()
-  // changes its behavior depending on the switch.
-  void set_sms_test_message_switch_present(bool is_present) {
-    sms_test_message_switch_present_ = is_present;
-  }
-
  private:
   void PushTestMessageChain();
   void PushTestMessageDelayed();
diff --git a/chromeos/dbus/shill/fake_modem_messaging_client.cc b/chromeos/dbus/shill/fake_modem_messaging_client.cc
index c9d66e9..926236f 100644
--- a/chromeos/dbus/shill/fake_modem_messaging_client.cc
+++ b/chromeos/dbus/shill/fake_modem_messaging_client.cc
@@ -17,8 +17,6 @@
 FakeModemMessagingClient::FakeModemMessagingClient() = default;
 FakeModemMessagingClient::~FakeModemMessagingClient() = default;
 
-void FakeModemMessagingClient::Init(dbus::Bus* bus) {}
-
 void FakeModemMessagingClient::SetSmsReceivedHandler(
     const std::string& service_name,
     const dbus::ObjectPath& object_path,
diff --git a/chromeos/dbus/shill/fake_modem_messaging_client.h b/chromeos/dbus/shill/fake_modem_messaging_client.h
index 5dce655b..9cb26299 100644
--- a/chromeos/dbus/shill/fake_modem_messaging_client.h
+++ b/chromeos/dbus/shill/fake_modem_messaging_client.h
@@ -21,7 +21,6 @@
   FakeModemMessagingClient();
   ~FakeModemMessagingClient() override;
 
-  void Init(dbus::Bus* bus) override;
   void SetSmsReceivedHandler(const std::string& service_name,
                              const dbus::ObjectPath& object_path,
                              const SmsReceivedHandler& handler) override;
diff --git a/chromeos/dbus/shill/fake_shill_device_client.cc b/chromeos/dbus/shill/fake_shill_device_client.cc
index a3ef2f4..4bf5009 100644
--- a/chromeos/dbus/shill/fake_shill_device_client.cc
+++ b/chromeos/dbus/shill/fake_shill_device_client.cc
@@ -16,7 +16,6 @@
 #include "base/strings/stringprintf.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/values.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/shill/shill_manager_client.h"
 #include "chromeos/dbus/shill/shill_property_changed_observer.h"
 #include "dbus/bus.h"
@@ -73,8 +72,6 @@
 
 // ShillDeviceClient overrides.
 
-void FakeShillDeviceClient::Init(dbus::Bus* bus) {}
-
 void FakeShillDeviceClient::AddPropertyChangedObserver(
     const dbus::ObjectPath& device_path,
     ShillPropertyChangedObserver* observer) {
@@ -114,7 +111,7 @@
     const base::Closure& callback,
     const ErrorCallback& error_callback,
     bool notify_changed) {
-  base::DictionaryValue* device_properties = NULL;
+  base::DictionaryValue* device_properties = nullptr;
   if (!stub_devices_.GetDictionaryWithoutPathExpansion(device_path.value(),
                                                        &device_properties)) {
     PostNotFoundError(error_callback);
@@ -133,13 +130,13 @@
 void FakeShillDeviceClient::ClearProperty(const dbus::ObjectPath& device_path,
                                           const std::string& name,
                                           VoidDBusMethodCallback callback) {
-  base::DictionaryValue* device_properties = NULL;
+  base::DictionaryValue* device_properties = nullptr;
   if (!stub_devices_.GetDictionaryWithoutPathExpansion(device_path.value(),
                                                        &device_properties)) {
     PostVoidCallback(std::move(callback), false);
     return;
   }
-  device_properties->RemoveWithoutPathExpansion(name, NULL);
+  device_properties->RemoveWithoutPathExpansion(name, nullptr);
   PostVoidCallback(std::move(callback), true);
 }
 
@@ -428,10 +425,7 @@
 void FakeShillDeviceClient::AddDevice(const std::string& device_path,
                                       const std::string& type,
                                       const std::string& name) {
-  DBusThreadManager::Get()
-      ->GetShillManagerClient()
-      ->GetTestInterface()
-      ->AddDevice(device_path);
+  ShillManagerClient::Get()->GetTestInterface()->AddDevice(device_path);
 
   base::Value* properties = GetDeviceProperties(device_path);
   properties->SetKey(shill::kTypeProperty, base::Value(type));
@@ -446,20 +440,12 @@
 }
 
 void FakeShillDeviceClient::RemoveDevice(const std::string& device_path) {
-  DBusThreadManager::Get()
-      ->GetShillManagerClient()
-      ->GetTestInterface()
-      ->RemoveDevice(device_path);
-
-  stub_devices_.RemoveWithoutPathExpansion(device_path, NULL);
+  ShillManagerClient::Get()->GetTestInterface()->RemoveDevice(device_path);
+  stub_devices_.RemoveWithoutPathExpansion(device_path, nullptr);
 }
 
 void FakeShillDeviceClient::ClearDevices() {
-  DBusThreadManager::Get()
-      ->GetShillManagerClient()
-      ->GetTestInterface()
-      ->ClearDevices();
-
+  ShillManagerClient::Get()->GetTestInterface()->ClearDevices();
   stub_devices_.Clear();
 }
 
@@ -478,7 +464,7 @@
     const std::string& type) {
   for (base::DictionaryValue::Iterator iter(stub_devices_); !iter.IsAtEnd();
        iter.Advance()) {
-    const base::DictionaryValue* properties = NULL;
+    const base::DictionaryValue* properties = nullptr;
     if (!iter.value().GetAsDictionary(&properties))
       continue;
     std::string prop_type;
@@ -653,7 +639,7 @@
 void FakeShillDeviceClient::PassStubDeviceProperties(
     const dbus::ObjectPath& device_path,
     const DictionaryValueCallback& callback) const {
-  const base::DictionaryValue* device_properties = NULL;
+  const base::DictionaryValue* device_properties = nullptr;
   if (!stub_devices_.GetDictionaryWithoutPathExpansion(device_path.value(),
                                                        &device_properties)) {
     base::DictionaryValue empty_dictionary;
@@ -673,13 +659,13 @@
 void FakeShillDeviceClient::NotifyObserversPropertyChanged(
     const dbus::ObjectPath& device_path,
     const std::string& property) {
-  base::DictionaryValue* dict = NULL;
+  base::DictionaryValue* dict = nullptr;
   std::string path = device_path.value();
   if (!stub_devices_.GetDictionaryWithoutPathExpansion(path, &dict)) {
     LOG(ERROR) << "Notify for unknown device: " << path;
     return;
   }
-  base::Value* value = NULL;
+  base::Value* value = nullptr;
   if (!dict->GetWithoutPathExpansion(property, &value)) {
     LOG(ERROR) << "Notify for unknown property: " << path << " : " << property;
     return;
diff --git a/chromeos/dbus/shill/fake_shill_device_client.h b/chromeos/dbus/shill/fake_shill_device_client.h
index 1e658f76..f2ee167 100644
--- a/chromeos/dbus/shill/fake_shill_device_client.h
+++ b/chromeos/dbus/shill/fake_shill_device_client.h
@@ -27,7 +27,6 @@
   ~FakeShillDeviceClient() override;
 
   // ShillDeviceClient overrides
-  void Init(dbus::Bus* bus) override;
   void AddPropertyChangedObserver(
       const dbus::ObjectPath& device_path,
       ShillPropertyChangedObserver* observer) override;
diff --git a/chromeos/dbus/shill/fake_shill_ipconfig_client.cc b/chromeos/dbus/shill/fake_shill_ipconfig_client.cc
index 652f34015..57a8b6f 100644
--- a/chromeos/dbus/shill/fake_shill_ipconfig_client.cc
+++ b/chromeos/dbus/shill/fake_shill_ipconfig_client.cc
@@ -27,8 +27,6 @@
 
 FakeShillIPConfigClient::~FakeShillIPConfigClient() = default;
 
-void FakeShillIPConfigClient::Init(dbus::Bus* bus) {}
-
 void FakeShillIPConfigClient::AddPropertyChangedObserver(
     const dbus::ObjectPath& ipconfig_path,
     ShillPropertyChangedObserver* observer) {}
@@ -43,7 +41,7 @@
 void FakeShillIPConfigClient::GetProperties(
     const dbus::ObjectPath& ipconfig_path,
     const DictionaryValueCallback& callback) {
-  const base::DictionaryValue* dict = NULL;
+  const base::DictionaryValue* dict = nullptr;
   if (!ipconfigs_.GetDictionaryWithoutPathExpansion(ipconfig_path.value(),
                                                     &dict))
     return;
diff --git a/chromeos/dbus/shill/fake_shill_ipconfig_client.h b/chromeos/dbus/shill/fake_shill_ipconfig_client.h
index dd210429..7d332bb 100644
--- a/chromeos/dbus/shill/fake_shill_ipconfig_client.h
+++ b/chromeos/dbus/shill/fake_shill_ipconfig_client.h
@@ -22,7 +22,6 @@
   ~FakeShillIPConfigClient() override;
 
   // ShillIPConfigClient overrides
-  void Init(dbus::Bus* bus) override;
   void AddPropertyChangedObserver(
       const dbus::ObjectPath& ipconfig_path,
       ShillPropertyChangedObserver* observer) override;
diff --git a/chromeos/dbus/shill/fake_shill_manager_client.cc b/chromeos/dbus/shill/fake_shill_manager_client.cc
index c25967f..6c8aa4d0b 100644
--- a/chromeos/dbus/shill/fake_shill_manager_client.cc
+++ b/chromeos/dbus/shill/fake_shill_manager_client.cc
@@ -20,7 +20,6 @@
 #include "base/strings/stringprintf.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "chromeos/dbus/constants/dbus_switches.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/shill/fake_shill_device_client.h"
 #include "chromeos/dbus/shill/shill_device_client.h"
 #include "chromeos/dbus/shill/shill_ipconfig_client.h"
@@ -186,11 +185,8 @@
 }
 
 void UpdatePortaledWifiState(const std::string& service_path) {
-  DBusThreadManager::Get()
-      ->GetShillServiceClient()
-      ->GetTestInterface()
-      ->SetServiceProperty(service_path, shill::kStateProperty,
-                           base::Value(shill::kStatePortal));
+  ShillServiceClient::Get()->GetTestInterface()->SetServiceProperty(
+      service_path, shill::kStateProperty, base::Value(shill::kStatePortal));
 }
 
 bool IsCellularTechnology(const std::string& type) {
@@ -209,10 +205,8 @@
 void SetInitialDeviceProperty(const std::string& device_path,
                               const std::string& name,
                               const base::Value& value) {
-  DBusThreadManager::Get()
-      ->GetShillDeviceClient()
-      ->GetTestInterface()
-      ->SetDeviceProperty(device_path, name, value, /*notify_changed=*/false);
+  ShillDeviceClient::Get()->GetTestInterface()->SetDeviceProperty(
+      device_path, name, value, /*notify_changed=*/false);
 }
 
 const char kPathKey[] = "path";
@@ -241,8 +235,6 @@
 
 // ShillManagerClient overrides.
 
-void FakeShillManagerClient::Init(dbus::Bus* bus) {}
-
 void FakeShillManagerClient::AddPropertyChangedObserver(
     ShillPropertyChangedObserver* observer) {
   observer_list_.AddObserver(observer);
@@ -285,7 +277,7 @@
   // For Stub purposes, default to a Wifi scan.
   std::string device_type = type.empty() ? shill::kTypeWifi : type;
   ShillDeviceClient::TestInterface* device_client =
-      DBusThreadManager::Get()->GetShillDeviceClient()->GetTestInterface();
+      ShillDeviceClient::Get()->GetTestInterface();
   std::string device_path = device_client->GetDevicePathForType(device_type);
   if (!device_path.empty()) {
     device_client->SetDeviceProperty(device_path, shill::kScanningProperty,
@@ -345,7 +337,7 @@
     const ObjectPathCallback& callback,
     const ErrorCallback& error_callback) {
   ShillServiceClient::TestInterface* service_client =
-      DBusThreadManager::Get()->GetShillServiceClient()->GetTestInterface();
+      ShillServiceClient::Get()->GetTestInterface();
 
   std::string guid;
   std::string type;
@@ -394,8 +386,7 @@
   merged_properties->GetStringWithoutPathExpansion(shill::kProfileProperty,
                                                    &profile_path);
   if (!profile_path.empty()) {
-    auto* profile_client =
-        DBusThreadManager::Get()->GetShillProfileClient()->GetTestInterface();
+    auto* profile_client = ShillProfileClient::Get()->GetTestInterface();
     if (!profile_client->UpdateService(profile_path, service_path))
       profile_client->AddService(profile_path, service_path);
   }
@@ -431,8 +422,8 @@
     return;
   }
 
-  DBusThreadManager::Get()->GetShillServiceClient()->Connect(
-      dbus::ObjectPath(best_service_), callback, error_callback);
+  ShillServiceClient::Get()->Connect(dbus::ObjectPath(best_service_), callback,
+                                     error_callback);
 }
 
 ShillManagerClient::TestInterface* FakeShillManagerClient::GetTestInterface() {
@@ -586,10 +577,9 @@
   std::vector<base::Value> complete_dict_list;
   for (const base::Value& value : complete_path_list->GetList()) {
     std::string service_path = value.GetString();
-    const base::Value* properties = DBusThreadManager::Get()
-                                        ->GetShillServiceClient()
-                                        ->GetTestInterface()
-                                        ->GetServiceProperties(service_path);
+    const base::Value* properties =
+        ShillServiceClient::Get()->GetTestInterface()->GetServiceProperties(
+            service_path);
     if (!properties) {
       LOG(ERROR) << "Properties not found for service: " << service_path;
       continue;
@@ -673,18 +663,17 @@
   if (!base::ThreadTaskRunnerHandle::IsSet())
     return;
 
-  DBusThreadManager* dbus_manager = DBusThreadManager::Get();
   ShillServiceClient::TestInterface* services =
-      dbus_manager->GetShillServiceClient()->GetTestInterface();
+      ShillServiceClient::Get()->GetTestInterface();
   DCHECK(services);
   ShillProfileClient::TestInterface* profiles =
-      dbus_manager->GetShillProfileClient()->GetTestInterface();
+      ShillProfileClient::Get()->GetTestInterface();
   DCHECK(profiles);
   ShillDeviceClient::TestInterface* devices =
-      dbus_manager->GetShillDeviceClient()->GetTestInterface();
+      ShillDeviceClient::Get()->GetTestInterface();
   DCHECK(devices);
   ShillIPConfigClient::TestInterface* ip_configs =
-      dbus_manager->GetShillIPConfigClient()->GetTestInterface();
+      ShillIPConfigClient::Get()->GetTestInterface();
   DCHECK(ip_configs);
 
   const std::string shared_profile = ShillProfileClient::GetSharedProfilePath();
@@ -736,10 +725,8 @@
 
   // Wifi
   if (s_tdls_busy_count != 0) {
-    DBusThreadManager::Get()
-        ->GetShillDeviceClient()
-        ->GetTestInterface()
-        ->SetTDLSBusyCount(s_tdls_busy_count);
+    ShillDeviceClient::Get()->GetTestInterface()->SetTDLSBusyCount(
+        s_tdls_busy_count);
   }
 
   state = GetInitialStateForType(shill::kTypeWifi, &enabled);
@@ -1091,7 +1078,7 @@
   const base::ListValue* service_list;
   if (stub_properties_.GetListWithoutPathExpansion(property, &service_list)) {
     ShillServiceClient::TestInterface* service_client =
-        DBusThreadManager::Get()->GetShillServiceClient()->GetTestInterface();
+        ShillServiceClient::Get()->GetTestInterface();
     for (base::ListValue::const_iterator iter = service_list->begin();
          iter != service_list->end(); ++iter) {
       std::string service_path;
@@ -1115,11 +1102,9 @@
 void FakeShillManagerClient::ScanCompleted(const std::string& device_path,
                                            const base::Closure& callback) {
   if (!device_path.empty()) {
-    DBusThreadManager::Get()
-        ->GetShillDeviceClient()
-        ->GetTestInterface()
-        ->SetDeviceProperty(device_path, shill::kScanningProperty,
-                            base::Value(false), /*notify_changed=*/true);
+    ShillDeviceClient::Get()->GetTestInterface()->SetDeviceProperty(
+        device_path, shill::kScanningProperty, base::Value(false),
+        /*notify_changed=*/true);
   }
   VLOG(1) << "ScanCompleted";
   CallNotifyObserversPropertyChanged(shill::kServiceCompleteListProperty);
diff --git a/chromeos/dbus/shill/fake_shill_manager_client.h b/chromeos/dbus/shill/fake_shill_manager_client.h
index 71c90b8..703b1da 100644
--- a/chromeos/dbus/shill/fake_shill_manager_client.h
+++ b/chromeos/dbus/shill/fake_shill_manager_client.h
@@ -28,7 +28,6 @@
   ~FakeShillManagerClient() override;
 
   // ShillManagerClient overrides
-  void Init(dbus::Bus* bus) override;
   void AddPropertyChangedObserver(
       ShillPropertyChangedObserver* observer) override;
   void RemovePropertyChangedObserver(
diff --git a/chromeos/dbus/shill/fake_shill_profile_client.cc b/chromeos/dbus/shill/fake_shill_profile_client.cc
index 3cd86cf9..7e0dc1b0a 100644
--- a/chromeos/dbus/shill/fake_shill_profile_client.cc
+++ b/chromeos/dbus/shill/fake_shill_profile_client.cc
@@ -14,7 +14,6 @@
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/values.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/shill/shill_property_changed_observer.h"
 #include "chromeos/dbus/shill/shill_service_client.h"
 #include "dbus/bus.h"
@@ -45,8 +44,6 @@
 
 FakeShillProfileClient::~FakeShillProfileClient() = default;
 
-void FakeShillProfileClient::Init(dbus::Bus* bus) {}
-
 void FakeShillProfileClient::AddPropertyChangedObserver(
     const dbus::ObjectPath& profile_path,
     ShillPropertyChangedObserver* observer) {}
@@ -88,7 +85,7 @@
   if (!profile)
     return;
 
-  base::DictionaryValue* entry = NULL;
+  base::DictionaryValue* entry = nullptr;
   profile->entries.GetDictionaryWithoutPathExpansion(entry_path, &entry);
   if (!entry) {
     error_callback.Run("Error.InvalidProfileEntry", "Invalid profile entry");
@@ -108,17 +105,14 @@
   if (!profile)
     return;
 
-  if (!profile->entries.RemoveWithoutPathExpansion(entry_path, NULL)) {
+  if (!profile->entries.RemoveWithoutPathExpansion(entry_path, nullptr)) {
     error_callback.Run("Error.InvalidProfileEntry", entry_path);
     return;
   }
 
   base::Value profile_path_value("");
-  DBusThreadManager::Get()
-      ->GetShillServiceClient()
-      ->GetTestInterface()
-      ->SetServiceProperty(entry_path, shill::kProfileProperty,
-                           profile_path_value);
+  ShillServiceClient::Get()->GetTestInterface()->SetServiceProperty(
+      entry_path, shill::kProfileProperty, profile_path_value);
 
   base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, callback);
 }
@@ -142,10 +136,7 @@
   profile->path = profile_path;
   profiles_.emplace_back(std::move(profile));
 
-  DBusThreadManager::Get()
-      ->GetShillManagerClient()
-      ->GetTestInterface()
-      ->AddProfile(profile_path);
+  ShillManagerClient::Get()->GetTestInterface()->AddProfile(profile_path);
 }
 
 void FakeShillProfileClient::AddEntry(const std::string& profile_path,
@@ -155,10 +146,8 @@
       GetProfile(dbus::ObjectPath(profile_path), ErrorCallback());
   DCHECK(profile);
   profile->entries.SetKey(entry_path, properties.Clone());
-  DBusThreadManager::Get()
-      ->GetShillManagerClient()
-      ->GetTestInterface()
-      ->AddManagerService(entry_path, true);
+  ShillManagerClient::Get()->GetTestInterface()->AddManagerService(entry_path,
+                                                                   true);
 }
 
 bool FakeShillProfileClient::AddService(const std::string& profile_path,
@@ -197,7 +186,7 @@
     const std::string& service_path,
     ProfileProperties* profile) {
   ShillServiceClient::TestInterface* service_test =
-      DBusThreadManager::Get()->GetShillServiceClient()->GetTestInterface();
+      ShillServiceClient::Get()->GetTestInterface();
   const base::DictionaryValue* service_properties =
       service_test->GetServiceProperties(service_path);
   if (!service_properties) {
diff --git a/chromeos/dbus/shill/fake_shill_profile_client.h b/chromeos/dbus/shill/fake_shill_profile_client.h
index bd5b73f..71400f7 100644
--- a/chromeos/dbus/shill/fake_shill_profile_client.h
+++ b/chromeos/dbus/shill/fake_shill_profile_client.h
@@ -25,7 +25,6 @@
   ~FakeShillProfileClient() override;
 
   // ShillProfileClient overrides
-  void Init(dbus::Bus* bus) override;
   void AddPropertyChangedObserver(
       const dbus::ObjectPath& profile_path,
       ShillPropertyChangedObserver* observer) override;
diff --git a/chromeos/dbus/shill/fake_shill_service_client.cc b/chromeos/dbus/shill/fake_shill_service_client.cc
index e8348bb..4fc8536 100644
--- a/chromeos/dbus/shill/fake_shill_service_client.cc
+++ b/chromeos/dbus/shill/fake_shill_service_client.cc
@@ -38,17 +38,11 @@
 }
 
 void CallSortManagerServices() {
-  DBusThreadManager::Get()
-      ->GetShillManagerClient()
-      ->GetTestInterface()
-      ->SortManagerServices(true);
+  ShillManagerClient::Get()->GetTestInterface()->SortManagerServices(true);
 }
 
 int GetInteractiveDelay() {
-  return DBusThreadManager::Get()
-      ->GetShillManagerClient()
-      ->GetTestInterface()
-      ->GetInteractiveDelay();
+  return ShillManagerClient::Get()->GetTestInterface()->GetInteractiveDelay();
 }
 
 }  // namespace
@@ -59,8 +53,6 @@
 
 // ShillServiceClient overrides.
 
-void FakeShillServiceClient::Init(dbus::Bus* bus) {}
-
 void FakeShillServiceClient::AddPropertyChangedObserver(
     const dbus::ObjectPath& service_path,
     ShillPropertyChangedObserver* observer) {
@@ -261,7 +253,7 @@
     const dbus::ObjectPath& service_path,
     const DictionaryValueCallback& callback) {
   ShillProfileClient::TestInterface* profile_client =
-      DBusThreadManager::Get()->GetShillProfileClient()->GetTestInterface();
+      ShillProfileClient::Get()->GetTestInterface();
   std::vector<std::string> profiles;
   profile_client->GetProfilePathsContainingService(service_path.value(),
                                                    &profiles);
@@ -311,10 +303,8 @@
   if (!ipconfig_path.empty())
     properties->SetKey(shill::kIPConfigProperty, base::Value(ipconfig_path));
 
-  DBusThreadManager::Get()
-      ->GetShillManagerClient()
-      ->GetTestInterface()
-      ->AddManagerService(service_path, true);
+  ShillManagerClient::Get()->GetTestInterface()->AddManagerService(service_path,
+                                                                   true);
 }
 
 base::DictionaryValue* FakeShillServiceClient::SetServiceProperties(
@@ -335,10 +325,8 @@
   if (guid_to_set.empty()) {
     std::string profile_path;
     base::DictionaryValue profile_properties;
-    if (DBusThreadManager::Get()
-            ->GetShillProfileClient()
-            ->GetTestInterface()
-            ->GetService(service_path, &profile_path, &profile_properties)) {
+    if (ShillProfileClient::Get()->GetTestInterface()->GetService(
+            service_path, &profile_path, &profile_properties)) {
       profile_properties.GetStringWithoutPathExpansion(shill::kGuidProperty,
                                                        &guid_to_set);
     }
@@ -350,10 +338,8 @@
   properties->SetKey(shill::kWifiHexSsid,
                      base::Value(base::HexEncode(name.c_str(), name.size())));
   properties->SetKey(shill::kNameProperty, base::Value(name));
-  std::string device_path = DBusThreadManager::Get()
-                                ->GetShillDeviceClient()
-                                ->GetTestInterface()
-                                ->GetDevicePathForType(type);
+  std::string device_path =
+      ShillDeviceClient::Get()->GetTestInterface()->GetDevicePathForType(type);
   properties->SetKey(shill::kDeviceProperty, base::Value(device_path));
   properties->SetKey(shill::kTypeProperty, base::Value(type));
   properties->SetKey(shill::kStateProperty, base::Value(state));
@@ -374,10 +360,8 @@
 void FakeShillServiceClient::RemoveService(const std::string& service_path) {
   stub_services_.RemoveWithoutPathExpansion(service_path, nullptr);
   connect_behavior_.erase(service_path);
-  DBusThreadManager::Get()
-      ->GetShillManagerClient()
-      ->GetTestInterface()
-      ->RemoveManagerService(service_path);
+  ShillManagerClient::Get()->GetTestInterface()->RemoveManagerService(
+      service_path);
 }
 
 bool FakeShillServiceClient::SetServiceProperty(const std::string& service_path,
@@ -443,7 +427,7 @@
 
   // Add or update the profile entry.
   ShillProfileClient::TestInterface* profile_test =
-      DBusThreadManager::Get()->GetShillProfileClient()->GetTestInterface();
+      ShillProfileClient::Get()->GetTestInterface();
   if (property == shill::kProfileProperty) {
     std::string profile_path;
     if (value.GetAsString(&profile_path)) {
@@ -465,10 +449,8 @@
   if (property == shill::kStateProperty) {
     std::string state;
     value.GetAsString(&state);
-    DBusThreadManager::Get()
-        ->GetShillManagerClient()
-        ->GetTestInterface()
-        ->ServiceStateChanged(service_path, state);
+    ShillManagerClient::Get()->GetTestInterface()->ServiceStateChanged(
+        service_path, state);
   }
 
   // If the State or Visibility changes, the sort order of service lists may
@@ -496,11 +478,7 @@
 }
 
 void FakeShillServiceClient::ClearServices() {
-  DBusThreadManager::Get()
-      ->GetShillManagerClient()
-      ->GetTestInterface()
-      ->ClearManagerServices();
-
+  ShillManagerClient::Get()->GetTestInterface()->ClearManagerServices();
   stub_services_.Clear();
   connect_behavior_.clear();
 }
diff --git a/chromeos/dbus/shill/fake_shill_service_client.h b/chromeos/dbus/shill/fake_shill_service_client.h
index 5688a51f..084d487 100644
--- a/chromeos/dbus/shill/fake_shill_service_client.h
+++ b/chromeos/dbus/shill/fake_shill_service_client.h
@@ -28,7 +28,6 @@
   ~FakeShillServiceClient() override;
 
   // ShillServiceClient overrides
-  void Init(dbus::Bus* bus) override;
   void AddPropertyChangedObserver(
       const dbus::ObjectPath& service_path,
       ShillPropertyChangedObserver* observer) override;
diff --git a/chromeos/dbus/shill/fake_shill_third_party_vpn_driver_client.cc b/chromeos/dbus/shill/fake_shill_third_party_vpn_driver_client.cc
index 13aca81..944d5be 100644
--- a/chromeos/dbus/shill/fake_shill_third_party_vpn_driver_client.cc
+++ b/chromeos/dbus/shill/fake_shill_third_party_vpn_driver_client.cc
@@ -21,8 +21,6 @@
 FakeShillThirdPartyVpnDriverClient::~FakeShillThirdPartyVpnDriverClient() =
     default;
 
-void FakeShillThirdPartyVpnDriverClient::Init(dbus::Bus* bus) {}
-
 void FakeShillThirdPartyVpnDriverClient::AddShillThirdPartyVpnObserver(
     const std::string& object_path_value,
     ShillThirdPartyVpnObserver* observer) {
diff --git a/chromeos/dbus/shill/fake_shill_third_party_vpn_driver_client.h b/chromeos/dbus/shill/fake_shill_third_party_vpn_driver_client.h
index a7290c8..a70730f 100644
--- a/chromeos/dbus/shill/fake_shill_third_party_vpn_driver_client.h
+++ b/chromeos/dbus/shill/fake_shill_third_party_vpn_driver_client.h
@@ -29,7 +29,6 @@
   ~FakeShillThirdPartyVpnDriverClient() override;
 
   // ShillThirdPartyVpnDriverClient overrides
-  void Init(dbus::Bus* bus) override;
   void AddShillThirdPartyVpnObserver(
       const std::string& object_path_value,
       ShillThirdPartyVpnObserver* observer) override;
diff --git a/chromeos/dbus/shill/fake_sms_client.cc b/chromeos/dbus/shill/fake_sms_client.cc
index e132965..3575f84 100644
--- a/chromeos/dbus/shill/fake_sms_client.cc
+++ b/chromeos/dbus/shill/fake_sms_client.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "chromeos/dbus/shill/fake_sms_client.h"
+
 #include <string>
 
 #include "base/bind.h"
@@ -13,7 +15,6 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/values.h"
 #include "chromeos/dbus/constants/dbus_switches.h"
-#include "chromeos/dbus/shill/fake_sms_client.h"
 #include "dbus/object_path.h"
 
 namespace chromeos {
@@ -22,8 +23,6 @@
 
 FakeSMSClient::~FakeSMSClient() = default;
 
-void FakeSMSClient::Init(dbus::Bus* bus) {}
-
 void FakeSMSClient::GetAll(const std::string& service_name,
                            const dbus::ObjectPath& object_path,
                            GetAllCallback callback) {
diff --git a/chromeos/dbus/shill/fake_sms_client.h b/chromeos/dbus/shill/fake_sms_client.h
index 748dfd7..54a32b2b 100644
--- a/chromeos/dbus/shill/fake_sms_client.h
+++ b/chromeos/dbus/shill/fake_sms_client.h
@@ -18,7 +18,6 @@
   ~FakeSMSClient() override;
 
   // SMSClient overrides.
-  void Init(dbus::Bus* bus) override;
   void GetAll(const std::string& service_name,
               const dbus::ObjectPath& object_path,
               GetAllCallback callback) override;
diff --git a/chromeos/dbus/shill/gsm_sms_client.cc b/chromeos/dbus/shill/gsm_sms_client.cc
index f4bc4c8..8d49a31 100644
--- a/chromeos/dbus/shill/gsm_sms_client.cc
+++ b/chromeos/dbus/shill/gsm_sms_client.cc
@@ -1,10 +1,10 @@
 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
+
 #include "chromeos/dbus/shill/gsm_sms_client.h"
 
 #include <stdint.h>
-
 #include <map>
 #include <memory>
 #include <utility>
@@ -15,6 +15,8 @@
 #include "base/memory/weak_ptr.h"
 #include "base/strings/stringprintf.h"
 #include "base/values.h"
+#include "chromeos/dbus/shill/fake_gsm_sms_client.h"
+#include "chromeos/dbus/shill/fake_sms_client.h"
 #include "dbus/bus.h"
 #include "dbus/message.h"
 #include "dbus/object_proxy.h"
@@ -25,6 +27,8 @@
 
 namespace {
 
+GsmSMSClient* g_instance = nullptr;
+
 // A class actually making method calls for SMS services, used by
 // GsmSMSClientImpl.
 class SMSProxy {
@@ -161,7 +165,8 @@
 // The GsmSMSClient implementation.
 class GsmSMSClientImpl : public GsmSMSClient {
  public:
-  GsmSMSClientImpl() : bus_(NULL) {}
+  explicit GsmSMSClientImpl(dbus::Bus* bus) : bus_(bus) {}
+  ~GsmSMSClientImpl() override = default;
 
   // GsmSMSClient override.
   void SetSmsReceivedHandler(const std::string& service_name,
@@ -203,9 +208,6 @@
   void RequestUpdate(const std::string& service_name,
                      const dbus::ObjectPath& object_path) override {}
 
- protected:
-  void Init(dbus::Bus* bus) override { bus_ = bus; }
-
  private:
   using ProxyMap =
       std::map<std::pair<std::string, std::string>, std::unique_ptr<SMSProxy>>;
@@ -237,13 +239,36 @@
 ////////////////////////////////////////////////////////////////////////////////
 // GsmSMSClient
 
-GsmSMSClient::GsmSMSClient() = default;
+GsmSMSClient::GsmSMSClient() {
+  DCHECK(!g_instance);
+  g_instance = this;
+}
 
-GsmSMSClient::~GsmSMSClient() = default;
+GsmSMSClient::~GsmSMSClient() {
+  DCHECK_EQ(this, g_instance);
+  g_instance = nullptr;
+}
 
 // static
-GsmSMSClient* GsmSMSClient::Create() {
-  return new GsmSMSClientImpl();
+void GsmSMSClient::Initialize(dbus::Bus* bus) {
+  DCHECK(bus);
+  new GsmSMSClientImpl(bus);
+}
+
+// static
+void GsmSMSClient::InitializeFake() {
+  new FakeGsmSMSClient;
+}
+
+// static
+void GsmSMSClient::Shutdown() {
+  DCHECK(g_instance);
+  delete g_instance;
+}
+
+// static
+GsmSMSClient* GsmSMSClient::Get() {
+  return g_instance;
 }
 
 }  // namespace chromeos
diff --git a/chromeos/dbus/shill/gsm_sms_client.h b/chromeos/dbus/shill/gsm_sms_client.h
index a4a95f3..05aee39 100644
--- a/chromeos/dbus/shill/gsm_sms_client.h
+++ b/chromeos/dbus/shill/gsm_sms_client.h
@@ -12,7 +12,6 @@
 #include "base/callback.h"
 #include "base/component_export.h"
 #include "base/macros.h"
-#include "chromeos/dbus/dbus_client.h"
 #include "chromeos/dbus/dbus_method_call_status.h"
 
 namespace base {
@@ -21,8 +20,9 @@
 }  // namespace base
 
 namespace dbus {
+class Bus;
 class ObjectPath;
-}
+}  // namespace dbus
 
 namespace chromeos {
 
@@ -30,16 +30,22 @@
 // org.freedesktop.ModemManager.Modem.Gsm.SMS service.
 // All methods should be called from the origin thread (UI thread) which
 // initializes the DBusThreadManager instance.
-class COMPONENT_EXPORT(CHROMEOS_DBUS) GsmSMSClient : public DBusClient {
+class COMPONENT_EXPORT(CHROMEOS_DBUS) GsmSMSClient {
  public:
   typedef base::Callback<void(uint32_t index, bool complete)>
       SmsReceivedHandler;
 
-  ~GsmSMSClient() override;
+  // Creates and initializes the global instance. |bus| must not be null.
+  static void Initialize(dbus::Bus* bus);
 
-  // Factory function, creates a new instance and returns ownership.
-  // For normal usage, access the singleton via DBusThreadManager::Get().
-  static GsmSMSClient* Create();
+  // Creates the global instance with a fake implementation.
+  static void InitializeFake();
+
+  // Destroys the global instance which must have been initialized.
+  static void Shutdown();
+
+  // Returns the global instance if initialized. May return null.
+  static GsmSMSClient* Get();
 
   // Sets SmsReceived signal handler.
   virtual void SetSmsReceivedHandler(const std::string& service_name,
@@ -75,8 +81,9 @@
  protected:
   friend class GsmSMSClientTest;
 
-  // Create() should be used instead.
+  // Initialize/Shutdown should be used instead.
   GsmSMSClient();
+  virtual ~GsmSMSClient();
 
  private:
   DISALLOW_COPY_AND_ASSIGN(GsmSMSClient);
diff --git a/chromeos/dbus/shill/gsm_sms_client_unittest.cc b/chromeos/dbus/shill/gsm_sms_client_unittest.cc
index b8adc98..70b707f9 100644
--- a/chromeos/dbus/shill/gsm_sms_client_unittest.cc
+++ b/chromeos/dbus/shill/gsm_sms_client_unittest.cc
@@ -78,11 +78,14 @@
     EXPECT_CALL(*mock_bus_.get(), ShutdownAndBlock()).WillOnce(Return());
 
     // Create a client with the mock bus.
-    client_.reset(GsmSMSClient::Create());
-    client_->Init(mock_bus_.get());
+    GsmSMSClient::Initialize(mock_bus_.get());
+    client_ = GsmSMSClient::Get();
   }
 
-  void TearDown() override { mock_bus_->ShutdownAndBlock(); }
+  void TearDown() override {
+    mock_bus_->ShutdownAndBlock();
+    GsmSMSClient::Shutdown();
+  }
 
   // Handles Delete method call.
   void OnDelete(dbus::MethodCall* method_call,
@@ -133,8 +136,7 @@
   }
 
  protected:
-  // The client to be tested.
-  std::unique_ptr<GsmSMSClient> client_;
+  GsmSMSClient* client_ = nullptr;  // Unowned convenience pointer.
   // A message loop to emulate asynchronous behavior.
   base::MessageLoop message_loop_;
   // The mock bus.
diff --git a/chromeos/dbus/shill/modem_messaging_client.cc b/chromeos/dbus/shill/modem_messaging_client.cc
index 61ef07e..3379155 100644
--- a/chromeos/dbus/shill/modem_messaging_client.cc
+++ b/chromeos/dbus/shill/modem_messaging_client.cc
@@ -1,6 +1,7 @@
 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
+
 #include "chromeos/dbus/shill/modem_messaging_client.h"
 
 #include <map>
@@ -11,6 +12,7 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/values.h"
+#include "chromeos/dbus/shill/fake_modem_messaging_client.h"
 #include "dbus/bus.h"
 #include "dbus/message.h"
 #include "dbus/object_proxy.h"
@@ -20,6 +22,8 @@
 
 namespace {
 
+ModemMessagingClient* g_instance = nullptr;
+
 // A class which makes method calls for SMS services via the
 // org.freedesktop.ModemManager1.Messaging object.
 class ModemMessagingProxy {
@@ -133,7 +137,8 @@
 class COMPONENT_EXPORT(CHROMEOS_DBUS) ModemMessagingClientImpl
     : public ModemMessagingClient {
  public:
-  ModemMessagingClientImpl() : bus_(NULL) {}
+  explicit ModemMessagingClientImpl(dbus::Bus* bus) : bus_(bus) {}
+  ~ModemMessagingClientImpl() override = default;
 
   void SetSmsReceivedHandler(const std::string& service_name,
                              const dbus::ObjectPath& object_path,
@@ -159,9 +164,6 @@
     GetProxy(service_name, object_path)->List(std::move(callback));
   }
 
- protected:
-  void Init(dbus::Bus* bus) override { bus_ = bus; }
-
  private:
   using ProxyMap = std::map<std::pair<std::string, std::string>,
                             std::unique_ptr<ModemMessagingProxy>>;
@@ -193,13 +195,36 @@
 ////////////////////////////////////////////////////////////////////////////////
 // ModemMessagingClient
 
-ModemMessagingClient::ModemMessagingClient() = default;
+ModemMessagingClient::ModemMessagingClient() {
+  DCHECK(!g_instance);
+  g_instance = this;
+}
 
-ModemMessagingClient::~ModemMessagingClient() = default;
+ModemMessagingClient::~ModemMessagingClient() {
+  DCHECK_EQ(this, g_instance);
+  g_instance = nullptr;
+}
 
 // static
-ModemMessagingClient* ModemMessagingClient::Create() {
-  return new ModemMessagingClientImpl();
+void ModemMessagingClient::Initialize(dbus::Bus* bus) {
+  DCHECK(bus);
+  new ModemMessagingClientImpl(bus);
+}
+
+// static
+void ModemMessagingClient::InitializeFake() {
+  new FakeModemMessagingClient();
+}
+
+// static
+void ModemMessagingClient::Shutdown() {
+  DCHECK(g_instance);
+  delete g_instance;
+}
+
+// static
+ModemMessagingClient* ModemMessagingClient::Get() {
+  return g_instance;
 }
 
 }  // namespace chromeos
diff --git a/chromeos/dbus/shill/modem_messaging_client.h b/chromeos/dbus/shill/modem_messaging_client.h
index 264f81ad..b089be41 100644
--- a/chromeos/dbus/shill/modem_messaging_client.h
+++ b/chromeos/dbus/shill/modem_messaging_client.h
@@ -15,8 +15,9 @@
 #include "chromeos/dbus/dbus_method_call_status.h"
 
 namespace dbus {
+class Bus;
 class ObjectPath;
-}
+}  // namespace dbus
 
 namespace chromeos {
 
@@ -24,17 +25,23 @@
 // org.freedesktop.ModemManager1.Modem.Messaging service.  All methods
 // should be called from the origin thread (UI thread) which
 // initializes the DBusThreadManager instance.
-class COMPONENT_EXPORT(CHROMEOS_DBUS) ModemMessagingClient : public DBusClient {
+class COMPONENT_EXPORT(CHROMEOS_DBUS) ModemMessagingClient {
  public:
   typedef base::Callback<void(const dbus::ObjectPath& message_path,
                               bool complete)>
       SmsReceivedHandler;
 
-  ~ModemMessagingClient() override;
+  // Creates and initializes the global instance. |bus| must not be null.
+  static void Initialize(dbus::Bus* bus);
 
-  // Factory function, creates a new instance and returns ownership.
-  // For normal usage, access the singleton via DBusThreadManager::Get().
-  static ModemMessagingClient* Create();
+  // Creates the global instance with a fake implementation.
+  static void InitializeFake();
+
+  // Destroys the global instance which must have been initialized.
+  static void Shutdown();
+
+  // Returns the global instance if initialized. May return null.
+  static ModemMessagingClient* Get();
 
   // Sets SmsReceived signal handler.
   virtual void SetSmsReceivedHandler(const std::string& service_name,
@@ -60,8 +67,9 @@
  protected:
   friend class ModemMessagingClientTest;
 
-  // Create() should be used instead.
+  // Initialize/Shutdown should be used instead.
   ModemMessagingClient();
+  virtual ~ModemMessagingClient();
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ModemMessagingClient);
diff --git a/chromeos/dbus/shill/modem_messaging_client_unittest.cc b/chromeos/dbus/shill/modem_messaging_client_unittest.cc
index 9c0dace..0444546 100644
--- a/chromeos/dbus/shill/modem_messaging_client_unittest.cc
+++ b/chromeos/dbus/shill/modem_messaging_client_unittest.cc
@@ -77,11 +77,14 @@
     EXPECT_CALL(*mock_bus_.get(), ShutdownAndBlock()).WillOnce(Return());
 
     // Create a client with the mock bus.
-    client_.reset(ModemMessagingClient::Create());
-    client_->Init(mock_bus_.get());
+    ModemMessagingClient::Initialize(mock_bus_.get());
+    client_ = ModemMessagingClient::Get();
   }
 
-  void TearDown() override { mock_bus_->ShutdownAndBlock(); }
+  void TearDown() override {
+    mock_bus_->ShutdownAndBlock();
+    ModemMessagingClient::Shutdown();
+  }
 
   // Handles Delete method call.
   void OnDelete(dbus::MethodCall* method_call,
@@ -115,8 +118,7 @@
   }
 
  protected:
-  // The client to be tested.
-  std::unique_ptr<ModemMessagingClient> client_;
+  ModemMessagingClient* client_ = nullptr;  // Unowned convenience pointer.
   // A message loop to emulate asynchronous behavior.
   base::MessageLoop message_loop_;
   // The mock bus.
diff --git a/chromeos/dbus/shill/shill_clients.cc b/chromeos/dbus/shill/shill_clients.cc
new file mode 100644
index 0000000..d17dc38
--- /dev/null
+++ b/chromeos/dbus/shill/shill_clients.cc
@@ -0,0 +1,60 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/dbus/shill/shill_clients.h"
+
+#include "chromeos/dbus/shill/gsm_sms_client.h"
+#include "chromeos/dbus/shill/modem_messaging_client.h"
+#include "chromeos/dbus/shill/shill_device_client.h"
+#include "chromeos/dbus/shill/shill_ipconfig_client.h"
+#include "chromeos/dbus/shill/shill_manager_client.h"
+#include "chromeos/dbus/shill/shill_profile_client.h"
+#include "chromeos/dbus/shill/shill_service_client.h"
+#include "chromeos/dbus/shill/shill_third_party_vpn_driver_client.h"
+#include "chromeos/dbus/shill/sms_client.h"
+
+namespace chromeos {
+namespace shill_clients {
+
+void Initialize(dbus::Bus* system_bus) {
+  DCHECK(system_bus);
+  GsmSMSClient::Initialize(system_bus);
+  ModemMessagingClient::Initialize(system_bus);
+  SMSClient::Initialize(system_bus);
+  ShillDeviceClient::Initialize(system_bus);
+  ShillIPConfigClient::Initialize(system_bus);
+  ShillManagerClient::Initialize(system_bus);
+  ShillProfileClient::Initialize(system_bus);
+  ShillServiceClient::Initialize(system_bus);
+  ShillThirdPartyVpnDriverClient::Initialize(system_bus);
+}
+
+void InitializeFakes() {
+  GsmSMSClient::InitializeFake();
+  ModemMessagingClient::InitializeFake();
+  SMSClient::InitializeFake();
+  ShillDeviceClient::InitializeFake();
+  ShillIPConfigClient::InitializeFake();
+  ShillManagerClient::InitializeFake();
+  ShillProfileClient::InitializeFake();
+  ShillServiceClient::InitializeFake();
+  ShillThirdPartyVpnDriverClient::InitializeFake();
+
+  ShillManagerClient::Get()->GetTestInterface()->SetupDefaultEnvironment();
+}
+
+void Shutdown() {
+  ShillThirdPartyVpnDriverClient::Shutdown();
+  ShillServiceClient::Shutdown();
+  ShillProfileClient::Shutdown();
+  ShillManagerClient::Shutdown();
+  ShillIPConfigClient::Shutdown();
+  ShillDeviceClient::Shutdown();
+  SMSClient::Shutdown();
+  ModemMessagingClient::Shutdown();
+  GsmSMSClient::Shutdown();
+}
+
+}  // namespace shill_clients
+}  // namespace chromeos
diff --git a/chromeos/dbus/shill/shill_clients.h b/chromeos/dbus/shill/shill_clients.h
new file mode 100644
index 0000000..aca7564
--- /dev/null
+++ b/chromeos/dbus/shill/shill_clients.h
@@ -0,0 +1,29 @@
+// Copyright 2019 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 CHROMEOS_DBUS_SHILL_SHILL_CLIENTS_H_
+#define CHROMEOS_DBUS_SHILL_SHILL_CLIENTS_H_
+
+#include "base/component_export.h"
+
+namespace dbus {
+class Bus;
+}
+
+namespace chromeos {
+namespace shill_clients {
+
+// Initialize Shill and modemmanager related dbus clients.
+COMPONENT_EXPORT(CHROMEOS_DBUS) void Initialize(dbus::Bus* system_bus);
+
+// Initialize fake Shill and modemmanager related dbus clients.
+COMPONENT_EXPORT(CHROMEOS_DBUS) void InitializeFakes();
+
+// Shut down Shill and modemmanager related dbus clients.
+COMPONENT_EXPORT(CHROMEOS_DBUS) void Shutdown();
+
+}  // namespace shill_clients
+}  // namespace chromeos
+
+#endif  // CHROMEOS_DBUS_SHILL_SHILL_CLIENTS_H_
diff --git a/chromeos/dbus/shill/shill_device_client.cc b/chromeos/dbus/shill/shill_device_client.cc
index 9688128..b1acca8f 100644
--- a/chromeos/dbus/shill/shill_device_client.cc
+++ b/chromeos/dbus/shill/shill_device_client.cc
@@ -11,6 +11,7 @@
 #include "base/macros.h"
 #include "base/stl_util.h"
 #include "base/values.h"
+#include "chromeos/dbus/shill/fake_shill_device_client.h"
 #include "chromeos/dbus/shill/shill_property_changed_observer.h"
 #include "dbus/bus.h"
 #include "dbus/message.h"
@@ -24,10 +25,12 @@
 
 namespace {
 
+ShillDeviceClient* g_instance = nullptr;
+
 // The ShillDeviceClient implementation.
 class ShillDeviceClientImpl : public ShillDeviceClient {
  public:
-  ShillDeviceClientImpl() : bus_(NULL) {}
+  explicit ShillDeviceClientImpl(dbus::Bus* bus) : bus_(bus) {}
 
   ~ShillDeviceClientImpl() override {
     for (HelperMap::iterator iter = helpers_.begin(); iter != helpers_.end();
@@ -36,7 +39,7 @@
       // seem to imply that it does happen sometimes.  Adding CHECKs here
       // so we can determine more accurately where the problem lies.
       // See: http://crbug.com/170541
-      CHECK(iter->second) << "NULL Helper found in helper list.";
+      CHECK(iter->second) << "null Helper found in helper list.";
       delete iter->second;
     }
     helpers_.clear();
@@ -270,10 +273,7 @@
                                           error_callback);
   }
 
-  TestInterface* GetTestInterface() override { return NULL; }
-
- protected:
-  void Init(dbus::Bus* bus) override { bus_ = bus; }
+  TestInterface* GetTestInterface() override { return nullptr; }
 
  private:
   typedef std::map<std::string, ShillClientHelper*> HelperMap;
@@ -282,7 +282,7 @@
   ShillClientHelper* GetHelper(const dbus::ObjectPath& device_path) {
     HelperMap::iterator it = helpers_.find(device_path.value());
     if (it != helpers_.end()) {
-      CHECK(it->second) << "Found a NULL helper in the list.";
+      CHECK(it->second) << "Found a null helper in the list.";
       return it->second;
     }
 
@@ -304,13 +304,36 @@
 
 }  // namespace
 
-ShillDeviceClient::ShillDeviceClient() = default;
+ShillDeviceClient::ShillDeviceClient() {
+  DCHECK(!g_instance);
+  g_instance = this;
+}
 
-ShillDeviceClient::~ShillDeviceClient() = default;
+ShillDeviceClient::~ShillDeviceClient() {
+  DCHECK_EQ(this, g_instance);
+  g_instance = nullptr;
+}
 
 // static
-ShillDeviceClient* ShillDeviceClient::Create() {
-  return new ShillDeviceClientImpl();
+void ShillDeviceClient::Initialize(dbus::Bus* bus) {
+  DCHECK(bus);
+  new ShillDeviceClientImpl(bus);
+}
+
+// static
+void ShillDeviceClient::InitializeFake() {
+  new FakeShillDeviceClient();
+}
+
+// static
+void ShillDeviceClient::Shutdown() {
+  DCHECK(g_instance);
+  delete g_instance;
+}
+
+// static
+ShillDeviceClient* ShillDeviceClient::Get() {
+  return g_instance;
 }
 
 }  // namespace chromeos
diff --git a/chromeos/dbus/shill/shill_device_client.h b/chromeos/dbus/shill/shill_device_client.h
index cd22975a..c4f1b03d 100644
--- a/chromeos/dbus/shill/shill_device_client.h
+++ b/chromeos/dbus/shill/shill_device_client.h
@@ -11,25 +11,19 @@
 #include "base/callback.h"
 #include "base/component_export.h"
 #include "base/macros.h"
-#include "chromeos/dbus/dbus_client.h"
 #include "chromeos/dbus/shill/shill_client_helper.h"
 
 namespace base {
-
 class Value;
-
 }  // namespace base
 
 namespace dbus {
-
+class Bus;
 class ObjectPath;
-
 }  // namespace dbus
 
 namespace net {
-
 class IPEndPoint;
-
 }  // namespace net
 
 namespace chromeos {
@@ -39,7 +33,7 @@
 // ShillDeviceClient is used to communicate with the Shill Device service.
 // All methods should be called from the origin thread which initializes the
 // DBusThreadManager instance.
-class COMPONENT_EXPORT(CHROMEOS_DBUS) ShillDeviceClient : public DBusClient {
+class COMPONENT_EXPORT(CHROMEOS_DBUS) ShillDeviceClient {
  public:
   typedef ShillClientHelper::PropertyChangedHandler PropertyChangedHandler;
   typedef ShillClientHelper::DictionaryValueCallback DictionaryValueCallback;
@@ -73,11 +67,17 @@
     virtual ~TestInterface() {}
   };
 
-  ~ShillDeviceClient() override;
+  // Creates and initializes the global instance. |bus| must not be null.
+  static void Initialize(dbus::Bus* bus);
 
-  // Factory function, creates a new instance which is owned by the caller.
-  // For normal usage, access the singleton via DBusThreadManager::Get().
-  static ShillDeviceClient* Create();
+  // Creates the global instance with a fake implementation.
+  static void InitializeFake();
+
+  // Destroys the global instance which must have been initialized.
+  static void Shutdown();
+
+  // Returns the global instance if initialized. May return null.
+  static ShillDeviceClient* Get();
 
   // Adds a property changed |observer| for the device at |device_path|.
   virtual void AddPropertyChangedObserver(
@@ -209,14 +209,15 @@
       const base::Closure& callback,
       const ErrorCallback& error_callback) = 0;
 
-  // Returns an interface for testing (stub only), or returns NULL.
+  // Returns an interface for testing (stub only), or returns null.
   virtual TestInterface* GetTestInterface() = 0;
 
  protected:
   friend class ShillDeviceClientTest;
 
-  // Create() should be used instead.
+  // Initialize/Shutdown should be used instead.
   ShillDeviceClient();
+  virtual ~ShillDeviceClient();
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ShillDeviceClient);
diff --git a/chromeos/dbus/shill/shill_device_client_unittest.cc b/chromeos/dbus/shill/shill_device_client_unittest.cc
index 58fd0e1..1e192292 100644
--- a/chromeos/dbus/shill/shill_device_client_unittest.cc
+++ b/chromeos/dbus/shill/shill_device_client_unittest.cc
@@ -62,14 +62,19 @@
   void SetUp() override {
     ShillClientUnittestBase::SetUp();
     // Create a client with the mock bus.
-    client_.reset(ShillDeviceClient::Create());
-    client_->Init(mock_bus_.get());
+    ShillDeviceClient::Initialize(mock_bus_.get());
+    client_ = ShillDeviceClient::Get();
     // Run the message loop to run the signal connection result callback.
     base::RunLoop().RunUntilIdle();
   }
 
+  void TearDown() override {
+    ShillDeviceClient::Shutdown();
+    ShillClientUnittestBase::TearDown();
+  }
+
  protected:
-  std::unique_ptr<ShillDeviceClient> client_;
+  ShillDeviceClient* client_ = nullptr;  // Unowned convenience pointer.
 };
 
 TEST_F(ShillDeviceClientTest, PropertyChanged) {
diff --git a/chromeos/dbus/shill/shill_ipconfig_client.cc b/chromeos/dbus/shill/shill_ipconfig_client.cc
index a540a27..0070297 100644
--- a/chromeos/dbus/shill/shill_ipconfig_client.cc
+++ b/chromeos/dbus/shill/shill_ipconfig_client.cc
@@ -11,6 +11,7 @@
 #include "base/bind.h"
 #include "base/macros.h"
 #include "base/values.h"
+#include "chromeos/dbus/shill/fake_shill_ipconfig_client.h"
 #include "chromeos/dbus/shill/shill_property_changed_observer.h"
 #include "dbus/bus.h"
 #include "dbus/message.h"
@@ -23,10 +24,13 @@
 
 namespace {
 
+ShillIPConfigClient* g_instance = nullptr;
+
 // The ShillIPConfigClient implementation.
 class ShillIPConfigClientImpl : public ShillIPConfigClient {
  public:
-  ShillIPConfigClientImpl();
+  explicit ShillIPConfigClientImpl(dbus::Bus* bus) : bus_(bus) {}
+  ~ShillIPConfigClientImpl() override = default;
 
   ////////////////////////////////////
   // ShillIPConfigClient overrides.
@@ -56,9 +60,6 @@
               VoidDBusMethodCallback callback) override;
   ShillIPConfigClient::TestInterface* GetTestInterface() override;
 
- protected:
-  void Init(dbus::Bus* bus) override { bus_ = bus; }
-
  private:
   using HelperMap = std::map<std::string, std::unique_ptr<ShillClientHelper>>;
 
@@ -85,8 +86,6 @@
   DISALLOW_COPY_AND_ASSIGN(ShillIPConfigClientImpl);
 };
 
-ShillIPConfigClientImpl::ShillIPConfigClientImpl() : bus_(NULL) {}
-
 void ShillIPConfigClientImpl::GetProperties(
     const dbus::ObjectPath& ipconfig_path,
     const DictionaryValueCallback& callback) {
@@ -113,11 +112,11 @@
   // IPConfig supports writing basic type and string array properties.
   switch (value.type()) {
     case base::Value::Type::LIST: {
-      const base::ListValue* list_value = NULL;
+      const base::ListValue* list_value = nullptr;
       value.GetAsList(&list_value);
-      dbus::MessageWriter variant_writer(NULL);
+      dbus::MessageWriter variant_writer(nullptr);
       writer.OpenVariant("as", &variant_writer);
-      dbus::MessageWriter array_writer(NULL);
+      dbus::MessageWriter array_writer(nullptr);
       variant_writer.OpenArray("s", &array_writer);
       for (base::ListValue::const_iterator it = list_value->begin();
            it != list_value->end(); ++it) {
@@ -162,18 +161,41 @@
 
 ShillIPConfigClient::TestInterface*
 ShillIPConfigClientImpl::GetTestInterface() {
-  return NULL;
+  return nullptr;
 }
 
 }  // namespace
 
-ShillIPConfigClient::ShillIPConfigClient() = default;
+ShillIPConfigClient::ShillIPConfigClient() {
+  DCHECK(!g_instance);
+  g_instance = this;
+}
 
-ShillIPConfigClient::~ShillIPConfigClient() = default;
+ShillIPConfigClient::~ShillIPConfigClient() {
+  DCHECK_EQ(this, g_instance);
+  g_instance = nullptr;
+}
 
 // static
-ShillIPConfigClient* ShillIPConfigClient::Create() {
-  return new ShillIPConfigClientImpl();
+void ShillIPConfigClient::Initialize(dbus::Bus* bus) {
+  DCHECK(bus);
+  new ShillIPConfigClientImpl(bus);
+}
+
+// static
+void ShillIPConfigClient::InitializeFake() {
+  new FakeShillIPConfigClient();
+}
+
+// static
+void ShillIPConfigClient::Shutdown() {
+  DCHECK(g_instance);
+  delete g_instance;
+}
+
+// static
+ShillIPConfigClient* ShillIPConfigClient::Get() {
+  return g_instance;
 }
 
 }  // namespace chromeos
diff --git a/chromeos/dbus/shill/shill_ipconfig_client.h b/chromeos/dbus/shill/shill_ipconfig_client.h
index 9d01d8f..272545d 100644
--- a/chromeos/dbus/shill/shill_ipconfig_client.h
+++ b/chromeos/dbus/shill/shill_ipconfig_client.h
@@ -10,20 +10,16 @@
 #include "base/callback.h"
 #include "base/component_export.h"
 #include "base/macros.h"
-#include "chromeos/dbus/dbus_client.h"
 #include "chromeos/dbus/shill/shill_client_helper.h"
 
 namespace base {
-
 class Value;
 class DictionaryValue;
-
 }  // namespace base
 
 namespace dbus {
-
+class Bus;
 class ObjectPath;
-
 }  // namespace dbus
 
 namespace chromeos {
@@ -33,7 +29,7 @@
 // ShillIPConfigClient is used to communicate with the Shill IPConfig
 // service.  All methods should be called from the origin thread which
 // initializes the DBusThreadManager instance.
-class COMPONENT_EXPORT(CHROMEOS_DBUS) ShillIPConfigClient : public DBusClient {
+class COMPONENT_EXPORT(CHROMEOS_DBUS) ShillIPConfigClient {
  public:
   typedef ShillClientHelper::PropertyChangedHandler PropertyChangedHandler;
   typedef ShillClientHelper::DictionaryValueCallback DictionaryValueCallback;
@@ -48,7 +44,17 @@
     virtual ~TestInterface() {}
   };
 
-  ~ShillIPConfigClient() override;
+  // Creates and initializes the global instance. |bus| must not be null.
+  static void Initialize(dbus::Bus* bus);
+
+  // Creates the global instance with a fake implementation.
+  static void InitializeFake();
+
+  // Destroys the global instance which must have been initialized.
+  static void Shutdown();
+
+  // Returns the global instance if initialized. May return null.
+  static ShillIPConfigClient* Get();
 
   // Factory function, creates a new instance which is owned by the caller.
   // For normal usage, access the singleton via DBusThreadManager::Get().
@@ -92,14 +98,15 @@
   virtual void Remove(const dbus::ObjectPath& ipconfig_path,
                       VoidDBusMethodCallback callback) = 0;
 
-  // Returns an interface for testing (stub only), or returns NULL.
+  // Returns an interface for testing (stub only), or returns null.
   virtual ShillIPConfigClient::TestInterface* GetTestInterface() = 0;
 
  protected:
   friend class ShillIPConfigClientTest;
 
-  // Create() should be used instead.
+  // Initialize/Shutdown should be used instead.
   ShillIPConfigClient();
+  virtual ~ShillIPConfigClient();
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ShillIPConfigClient);
diff --git a/chromeos/dbus/shill/shill_ipconfig_client_unittest.cc b/chromeos/dbus/shill/shill_ipconfig_client_unittest.cc
index 05f1c37..274c4d0 100644
--- a/chromeos/dbus/shill/shill_ipconfig_client_unittest.cc
+++ b/chromeos/dbus/shill/shill_ipconfig_client_unittest.cc
@@ -36,14 +36,19 @@
   void SetUp() override {
     ShillClientUnittestBase::SetUp();
     // Create a client with the mock bus.
-    client_.reset(ShillIPConfigClient::Create());
-    client_->Init(mock_bus_.get());
+    ShillIPConfigClient::Initialize(mock_bus_.get());
+    client_ = ShillIPConfigClient::Get();
     // Run the message loop to run the signal connection result callback.
     base::RunLoop().RunUntilIdle();
   }
 
+  void TearDown() override {
+    ShillIPConfigClient::Shutdown();
+    ShillClientUnittestBase::TearDown();
+  }
+
  protected:
-  std::unique_ptr<ShillIPConfigClient> client_;
+  ShillIPConfigClient* client_ = nullptr;  // Unowned convenience pointer.
 };
 
 TEST_F(ShillIPConfigClientTest, PropertyChanged) {
diff --git a/chromeos/dbus/shill/shill_manager_client.cc b/chromeos/dbus/shill/shill_manager_client.cc
index e2c8f14..bb05c859 100644
--- a/chromeos/dbus/shill/shill_manager_client.cc
+++ b/chromeos/dbus/shill/shill_manager_client.cc
@@ -10,6 +10,7 @@
 #include "base/logging.h"
 #include "base/macros.h"
 #include "base/values.h"
+#include "chromeos/dbus/shill/fake_shill_manager_client.h"
 #include "chromeos/dbus/shill/shill_property_changed_observer.h"
 #include "dbus/bus.h"
 #include "dbus/message.h"
@@ -23,10 +24,13 @@
 
 namespace {
 
+ShillManagerClient* g_instance = nullptr;
+
 // The ShillManagerClient implementation.
 class ShillManagerClientImpl : public ShillManagerClient {
  public:
-  ShillManagerClientImpl() : proxy_(NULL) {}
+  ShillManagerClientImpl() = default;
+  ~ShillManagerClientImpl() override = default;
 
   ////////////////////////////////////
   // ShillManagerClient overrides.
@@ -157,10 +161,9 @@
                                              error_callback);
   }
 
-  TestInterface* GetTestInterface() override { return NULL; }
+  TestInterface* GetTestInterface() override { return nullptr; }
 
- protected:
-  void Init(dbus::Bus* bus) override {
+  void Init(dbus::Bus* bus) {
     proxy_ = bus->GetObjectProxy(shill::kFlimflamServiceName,
                                  dbus::ObjectPath(shill::kFlimflamServicePath));
     helper_.reset(new ShillClientHelper(proxy_));
@@ -168,7 +171,7 @@
   }
 
  private:
-  dbus::ObjectProxy* proxy_;
+  dbus::ObjectProxy* proxy_ = nullptr;
   std::unique_ptr<ShillClientHelper> helper_;
 
   DISALLOW_COPY_AND_ASSIGN(ShillManagerClientImpl);
@@ -176,13 +179,36 @@
 
 }  // namespace
 
-ShillManagerClient::ShillManagerClient() = default;
+ShillManagerClient::ShillManagerClient() {
+  DCHECK(!g_instance);
+  g_instance = this;
+}
 
-ShillManagerClient::~ShillManagerClient() = default;
+ShillManagerClient::~ShillManagerClient() {
+  DCHECK_EQ(this, g_instance);
+  g_instance = nullptr;
+}
 
 // static
-ShillManagerClient* ShillManagerClient::Create() {
-  return new ShillManagerClientImpl();
+void ShillManagerClient::Initialize(dbus::Bus* bus) {
+  DCHECK(bus);
+  (new ShillManagerClientImpl)->Init(bus);
+}
+
+// static
+void ShillManagerClient::InitializeFake() {
+  new FakeShillManagerClient();
+}
+
+// static
+void ShillManagerClient::Shutdown() {
+  DCHECK(g_instance);
+  delete g_instance;
+}
+
+// static
+ShillManagerClient* ShillManagerClient::Get() {
+  return g_instance;
 }
 
 }  // namespace chromeos
diff --git a/chromeos/dbus/shill/shill_manager_client.h b/chromeos/dbus/shill/shill_manager_client.h
index 2839ecf..02900c0 100644
--- a/chromeos/dbus/shill/shill_manager_client.h
+++ b/chromeos/dbus/shill/shill_manager_client.h
@@ -9,13 +9,13 @@
 
 #include "base/component_export.h"
 #include "base/macros.h"
-#include "chromeos/dbus/dbus_client.h"
 #include "chromeos/dbus/dbus_method_call_status.h"
 #include "chromeos/dbus/shill/shill_client_helper.h"
 
 namespace dbus {
+class Bus;
 class ObjectPath;
-}
+}  // namespace dbus
 
 namespace chromeos {
 
@@ -24,7 +24,7 @@
 // ShillManagerClient is used to communicate with the Shill Manager
 // service.  All methods should be called from the origin thread which
 // initializes the DBusThreadManager instance.
-class COMPONENT_EXPORT(CHROMEOS_DBUS) ShillManagerClient : public DBusClient {
+class COMPONENT_EXPORT(CHROMEOS_DBUS) ShillManagerClient {
  public:
   typedef ShillClientHelper::PropertyChangedHandler PropertyChangedHandler;
   typedef ShillClientHelper::DictionaryValueCallback DictionaryValueCallback;
@@ -107,11 +107,17 @@
     virtual ~TestInterface() {}
   };
 
-  ~ShillManagerClient() override;
+  // Creates and initializes the global instance. |bus| must not be null.
+  static void Initialize(dbus::Bus* bus);
 
-  // Factory function, creates a new instance which is owned by the caller.
-  // For normal usage, access the singleton via DBusThreadManager::Get().
-  static ShillManagerClient* Create();
+  // Creates the global instance with a fake implementation.
+  static void InitializeFake();
+
+  // Destroys the global instance which must have been initialized.
+  static void Shutdown();
+
+  // Returns the global instance if initialized. May return null.
+  static ShillManagerClient* Get();
 
   // Adds a property changed |observer|.
   virtual void AddPropertyChangedObserver(
@@ -187,14 +193,15 @@
       const base::Closure& callback,
       const ErrorCallback& error_callback) = 0;
 
-  // Returns an interface for testing (stub only), or returns NULL.
+  // Returns an interface for testing (stub only), or returns null.
   virtual TestInterface* GetTestInterface() = 0;
 
  protected:
   friend class ShillManagerClientTest;
 
-  // Create() should be used instead.
+  // Initialize/Shutdown should be used instead.
   ShillManagerClient();
+  virtual ~ShillManagerClient();
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ShillManagerClient);
diff --git a/chromeos/dbus/shill/shill_manager_client_unittest.cc b/chromeos/dbus/shill/shill_manager_client_unittest.cc
index ec0f8cb..eefd987 100644
--- a/chromeos/dbus/shill/shill_manager_client_unittest.cc
+++ b/chromeos/dbus/shill/shill_manager_client_unittest.cc
@@ -54,14 +54,19 @@
   void SetUp() override {
     ShillClientUnittestBase::SetUp();
     // Create a client with the mock bus.
-    client_.reset(ShillManagerClient::Create());
-    client_->Init(mock_bus_.get());
+    ShillManagerClient::Initialize(mock_bus_.get());
+    client_ = ShillManagerClient::Get();
     // Run the message loop to run the signal connection result callback.
     base::RunLoop().RunUntilIdle();
   }
 
+  void TearDown() override {
+    ShillManagerClient::Shutdown();
+    ShillClientUnittestBase::TearDown();
+  }
+
  protected:
-  std::unique_ptr<ShillManagerClient> client_;
+  ShillManagerClient* client_ = nullptr;  // Unowned convenience pointer.
 };
 
 TEST_F(ShillManagerClientTest, PropertyChanged) {
diff --git a/chromeos/dbus/shill/shill_profile_client.cc b/chromeos/dbus/shill/shill_profile_client.cc
index 759dcda7..170c422 100644
--- a/chromeos/dbus/shill/shill_profile_client.cc
+++ b/chromeos/dbus/shill/shill_profile_client.cc
@@ -12,6 +12,7 @@
 #include "base/macros.h"
 #include "base/values.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/shill/fake_shill_profile_client.h"
 #include "chromeos/dbus/shill/shill_property_changed_observer.h"
 #include "dbus/bus.h"
 #include "dbus/message.h"
@@ -25,9 +26,12 @@
 
 const char kSharedProfilePath[] = "/profile/default";
 
+ShillProfileClient* g_instance = nullptr;
+
 class ShillProfileClientImpl : public ShillProfileClient {
  public:
-  ShillProfileClientImpl();
+  explicit ShillProfileClientImpl(dbus::Bus* bus) : bus_(bus) {}
+  ~ShillProfileClientImpl() override = default;
 
   void AddPropertyChangedObserver(
       const dbus::ObjectPath& profile_path,
@@ -53,10 +57,7 @@
                    const base::Closure& callback,
                    const ErrorCallback& error_callback) override;
 
-  TestInterface* GetTestInterface() override { return NULL; }
-
- protected:
-  void Init(dbus::Bus* bus) override { bus_ = bus; }
+  TestInterface* GetTestInterface() override { return nullptr; }
 
  private:
   using HelperMap = std::map<std::string, std::unique_ptr<ShillClientHelper>>;
@@ -70,8 +71,6 @@
   DISALLOW_COPY_AND_ASSIGN(ShillProfileClientImpl);
 };
 
-ShillProfileClientImpl::ShillProfileClientImpl() : bus_(NULL) {}
-
 ShillClientHelper* ShillProfileClientImpl::GetHelper(
     const dbus::ObjectPath& profile_path) {
   HelperMap::const_iterator it = helpers_.find(profile_path.value());
@@ -128,13 +127,36 @@
 
 }  // namespace
 
-ShillProfileClient::ShillProfileClient() = default;
+ShillProfileClient::ShillProfileClient() {
+  DCHECK(!g_instance);
+  g_instance = this;
+}
 
-ShillProfileClient::~ShillProfileClient() = default;
+ShillProfileClient::~ShillProfileClient() {
+  DCHECK_EQ(this, g_instance);
+  g_instance = nullptr;
+}
 
 // static
-ShillProfileClient* ShillProfileClient::Create() {
-  return new ShillProfileClientImpl();
+void ShillProfileClient::Initialize(dbus::Bus* bus) {
+  DCHECK(bus);
+  new ShillProfileClientImpl(bus);
+}
+
+// static
+void ShillProfileClient::InitializeFake() {
+  new FakeShillProfileClient();
+}
+
+// static
+void ShillProfileClient::Shutdown() {
+  DCHECK(g_instance);
+  delete g_instance;
+}
+
+// static
+ShillProfileClient* ShillProfileClient::Get() {
+  return g_instance;
 }
 
 // static
diff --git a/chromeos/dbus/shill/shill_profile_client.h b/chromeos/dbus/shill/shill_profile_client.h
index d9a8a69..b75a452 100644
--- a/chromeos/dbus/shill/shill_profile_client.h
+++ b/chromeos/dbus/shill/shill_profile_client.h
@@ -11,19 +11,15 @@
 #include "base/callback.h"
 #include "base/component_export.h"
 #include "base/macros.h"
-#include "chromeos/dbus/dbus_client.h"
 #include "chromeos/dbus/shill/shill_client_helper.h"
 
 namespace base {
-
 class DictionaryValue;
-
 }  // namespace base
 
 namespace dbus {
-
+class Bus;
 class ObjectPath;
-
 }  // namespace dbus
 
 namespace chromeos {
@@ -33,7 +29,7 @@
 // ShillProfileClient is used to communicate with the Shill Profile
 // service.  All methods should be called from the origin thread which
 // initializes the DBusThreadManager instance.
-class COMPONENT_EXPORT(CHROMEOS_DBUS) ShillProfileClient : public DBusClient {
+class COMPONENT_EXPORT(CHROMEOS_DBUS) ShillProfileClient {
  public:
   typedef ShillClientHelper::PropertyChangedHandler PropertyChangedHandler;
   typedef ShillClientHelper::DictionaryValueCallbackWithoutStatus
@@ -97,11 +93,17 @@
     virtual ~TestInterface() {}
   };
 
-  ~ShillProfileClient() override;
+  // Creates and initializes the global instance. |bus| must not be null.
+  static void Initialize(dbus::Bus* bus);
 
-  // Factory function, creates a new instance which is owned by the caller.
-  // For normal usage, access the singleton via DBusThreadManager::Get().
-  static ShillProfileClient* Create();
+  // Creates the global instance with a fake implementation.
+  static void InitializeFake();
+
+  // Destroys the global instance which must have been initialized.
+  static void Shutdown();
+
+  // Returns the global instance if initialized. May return null.
+  static ShillProfileClient* Get();
 
   // Returns the shared profile path.
   static std::string GetSharedProfilePath();
@@ -137,14 +139,15 @@
                            const base::Closure& callback,
                            const ErrorCallback& error_callback) = 0;
 
-  // Returns an interface for testing (stub only), or returns NULL.
+  // Returns an interface for testing (stub only), or returns null.
   virtual TestInterface* GetTestInterface() = 0;
 
  protected:
   friend class ShillProfileClientTest;
 
-  // Create() should be used instead.
+  // Initialize/Shutdown should be used instead.
   ShillProfileClient();
+  virtual ~ShillProfileClient();
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ShillProfileClient);
diff --git a/chromeos/dbus/shill/shill_profile_client_unittest.cc b/chromeos/dbus/shill/shill_profile_client_unittest.cc
index 30384b8..8c71ca2 100644
--- a/chromeos/dbus/shill/shill_profile_client_unittest.cc
+++ b/chromeos/dbus/shill/shill_profile_client_unittest.cc
@@ -45,14 +45,19 @@
   void SetUp() override {
     ShillClientUnittestBase::SetUp();
     // Create a client with the mock bus.
-    client_.reset(ShillProfileClient::Create());
-    client_->Init(mock_bus_.get());
+    ShillProfileClient::Initialize(mock_bus_.get());
+    client_ = ShillProfileClient::Get();
     // Run the message loop to run the signal connection result callback.
     base::RunLoop().RunUntilIdle();
   }
 
+  void TearDown() override {
+    ShillProfileClient::Shutdown();
+    ShillClientUnittestBase::TearDown();
+  }
+
  protected:
-  std::unique_ptr<ShillProfileClient> client_;
+  ShillProfileClient* client_ = nullptr;  // Unowned convenience pointer.
 };
 
 TEST_F(ShillProfileClientTest, PropertyChanged) {
diff --git a/chromeos/dbus/shill/shill_service_client.cc b/chromeos/dbus/shill/shill_service_client.cc
index 1feb4f65..bb0833693 100644
--- a/chromeos/dbus/shill/shill_service_client.cc
+++ b/chromeos/dbus/shill/shill_service_client.cc
@@ -12,6 +12,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/stl_util.h"
 #include "base/values.h"
+#include "chromeos/dbus/shill/fake_shill_service_client.h"
 #include "chromeos/dbus/shill/shill_property_changed_observer.h"
 #include "components/device_event_log/device_event_log.h"
 #include "dbus/bus.h"
@@ -29,6 +30,8 @@
 #define DBUS_ERROR_UNKNOWN_OBJECT "org.freedesktop.DBus.Error.UnknownObject"
 #endif
 
+ShillServiceClient* g_instance = nullptr;
+
 // Error callback for GetProperties.
 void OnGetDictionaryError(
     const std::string& method_name,
@@ -55,7 +58,8 @@
 // The ShillServiceClient implementation.
 class ShillServiceClientImpl : public ShillServiceClient {
  public:
-  ShillServiceClientImpl() : bus_(NULL), weak_ptr_factory_(this) {}
+  explicit ShillServiceClientImpl(dbus::Bus* bus)
+      : bus_(bus), weak_ptr_factory_(this) {}
 
   ~ShillServiceClientImpl() override {
     for (HelperMap::iterator iter = helpers_.begin(); iter != helpers_.end();
@@ -213,12 +217,9 @@
   }
 
   ShillServiceClient::TestInterface* GetTestInterface() override {
-    return NULL;
+    return nullptr;
   }
 
- protected:
-  void Init(dbus::Bus* bus) override { bus_ = bus; }
-
  private:
   typedef std::map<std::string, ShillClientHelper*> HelperMap;
 
@@ -270,13 +271,36 @@
 
 }  // namespace
 
-ShillServiceClient::ShillServiceClient() = default;
+ShillServiceClient::ShillServiceClient() {
+  DCHECK(!g_instance);
+  g_instance = this;
+}
 
-ShillServiceClient::~ShillServiceClient() = default;
+ShillServiceClient::~ShillServiceClient() {
+  DCHECK_EQ(this, g_instance);
+  g_instance = nullptr;
+}
 
 // static
-ShillServiceClient* ShillServiceClient::Create() {
-  return new ShillServiceClientImpl();
+void ShillServiceClient::Initialize(dbus::Bus* bus) {
+  DCHECK(bus);
+  new ShillServiceClientImpl(bus);
+}
+
+// static
+void ShillServiceClient::InitializeFake() {
+  new FakeShillServiceClient();
+}
+
+// static
+void ShillServiceClient::Shutdown() {
+  DCHECK(g_instance);
+  delete g_instance;
+}
+
+// static
+ShillServiceClient* ShillServiceClient::Get() {
+  return g_instance;
 }
 
 }  // namespace chromeos
diff --git a/chromeos/dbus/shill/shill_service_client.h b/chromeos/dbus/shill/shill_service_client.h
index c0d6ef7b..c3a4658 100644
--- a/chromeos/dbus/shill/shill_service_client.h
+++ b/chromeos/dbus/shill/shill_service_client.h
@@ -11,20 +11,16 @@
 #include "base/callback.h"
 #include "base/component_export.h"
 #include "base/macros.h"
-#include "chromeos/dbus/dbus_client.h"
 #include "chromeos/dbus/shill/shill_client_helper.h"
 
 namespace base {
-
 class Value;
 class DictionaryValue;
-
 }  // namespace base
 
 namespace dbus {
-
+class Bus;
 class ObjectPath;
-
 }  // namespace dbus
 
 namespace chromeos {
@@ -33,7 +29,7 @@
 // service.
 // All methods should be called from the origin thread which initializes the
 // DBusThreadManager instance.
-class COMPONENT_EXPORT(CHROMEOS_DBUS) ShillServiceClient : public DBusClient {
+class COMPONENT_EXPORT(CHROMEOS_DBUS) ShillServiceClient {
  public:
   typedef ShillClientHelper::PropertyChangedHandler PropertyChangedHandler;
   typedef ShillClientHelper::DictionaryValueCallback DictionaryValueCallback;
@@ -76,7 +72,7 @@
                                     const std::string& property,
                                     const base::Value& value) = 0;
 
-    // Returns properties for |service_path| or NULL if no Service matches.
+    // Returns properties for |service_path| or null if no Service matches.
     virtual const base::DictionaryValue* GetServiceProperties(
         const std::string& service_path) const = 0;
 
@@ -89,11 +85,18 @@
    protected:
     virtual ~TestInterface() {}
   };
-  ~ShillServiceClient() override;
 
-  // Factory function, creates a new instance which is owned by the caller.
-  // For normal usage, access the singleton via DBusThreadManager::Get().
-  static ShillServiceClient* Create();
+  // Creates and initializes the global instance. |bus| must not be null.
+  static void Initialize(dbus::Bus* bus);
+
+  // Creates the global instance with a fake implementation.
+  static void InitializeFake();
+
+  // Destroys the global instance which must have been initialized.
+  static void Shutdown();
+
+  // Returns the global instance if initialized. May return null.
+  static ShillServiceClient* Get();
 
   // Adds a property changed |observer| to the service at |service_path|.
   virtual void AddPropertyChangedObserver(
@@ -177,14 +180,15 @@
       const dbus::ObjectPath& service_path,
       const DictionaryValueCallback& callback) = 0;
 
-  // Returns an interface for testing (stub only), or returns NULL.
+  // Returns an interface for testing (stub only), or returns null.
   virtual TestInterface* GetTestInterface() = 0;
 
  protected:
   friend class ShillServiceClientTest;
 
-  // Create() should be used instead.
+  // Initialize/Shutdown should be used instead.
   ShillServiceClient();
+  virtual ~ShillServiceClient();
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ShillServiceClient);
diff --git a/chromeos/dbus/shill/shill_service_client_unittest.cc b/chromeos/dbus/shill/shill_service_client_unittest.cc
index d8c6f9e..5027ac0c 100644
--- a/chromeos/dbus/shill/shill_service_client_unittest.cc
+++ b/chromeos/dbus/shill/shill_service_client_unittest.cc
@@ -36,14 +36,19 @@
   void SetUp() override {
     ShillClientUnittestBase::SetUp();
     // Create a client with the mock bus.
-    client_.reset(ShillServiceClient::Create());
-    client_->Init(mock_bus_.get());
+    ShillServiceClient::Initialize(mock_bus_.get());
+    client_ = ShillServiceClient::Get();
     // Run the message loop to run the signal connection result callback.
     base::RunLoop().RunUntilIdle();
   }
 
+  void TearDown() override {
+    ShillServiceClient::Shutdown();
+    ShillClientUnittestBase::TearDown();
+  }
+
  protected:
-  std::unique_ptr<ShillServiceClient> client_;
+  ShillServiceClient* client_ = nullptr;  // Unowned convenience pointer.
 };
 
 TEST_F(ShillServiceClientTest, PropertyChanged) {
diff --git a/chromeos/dbus/shill/shill_third_party_vpn_driver_client.cc b/chromeos/dbus/shill/shill_third_party_vpn_driver_client.cc
index ebfc703..b540adf 100644
--- a/chromeos/dbus/shill/shill_third_party_vpn_driver_client.cc
+++ b/chromeos/dbus/shill/shill_third_party_vpn_driver_client.cc
@@ -13,6 +13,7 @@
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/stl_util.h"
+#include "chromeos/dbus/shill/fake_shill_third_party_vpn_driver_client.h"
 #include "chromeos/dbus/shill/shill_third_party_vpn_observer.h"
 #include "dbus/bus.h"
 #include "dbus/message.h"
@@ -34,11 +35,13 @@
     shill::kDnsServersParameterThirdPartyVpn,
     shill::kReconnectParameterThirdPartyVpn};
 
+ShillThirdPartyVpnDriverClient* g_instance = nullptr;
+
 // The ShillThirdPartyVpnDriverClient implementation.
 class ShillThirdPartyVpnDriverClientImpl
     : public ShillThirdPartyVpnDriverClient {
  public:
-  ShillThirdPartyVpnDriverClientImpl();
+  explicit ShillThirdPartyVpnDriverClientImpl(dbus::Bus* bus);
   ~ShillThirdPartyVpnDriverClientImpl() override;
 
   // ShillThirdPartyVpnDriverClient overrides
@@ -67,12 +70,7 @@
       const base::Closure& callback,
       const ShillClientHelper::ErrorCallback& error_callback) override;
 
- protected:
-  void Init(dbus::Bus* bus) override { bus_ = bus; }
-
-  ShillThirdPartyVpnDriverClient::TestInterface* GetTestInterface() override {
-    return nullptr;
-  }
+  TestInterface* GetTestInterface() override { return nullptr; }
 
  private:
   class HelperInfo {
@@ -132,8 +130,9 @@
     dbus::ObjectProxy* object_proxy)
     : helper_(object_proxy), observer_(nullptr), weak_ptr_factory_(this) {}
 
-ShillThirdPartyVpnDriverClientImpl::ShillThirdPartyVpnDriverClientImpl()
-    : bus_(nullptr) {
+ShillThirdPartyVpnDriverClientImpl::ShillThirdPartyVpnDriverClientImpl(
+    dbus::Bus* bus)
+    : bus_(bus) {
   for (uint32_t i = 0; i < base::size(kSetParametersKeyList); ++i) {
     valid_keys_.insert(kSetParametersKeyList[i]);
   }
@@ -334,13 +333,38 @@
 
 }  // namespace
 
-ShillThirdPartyVpnDriverClient::ShillThirdPartyVpnDriverClient() = default;
+ShillThirdPartyVpnDriverClient::ShillThirdPartyVpnDriverClient() {
+  DCHECK(!g_instance);
+  g_instance = this;
+}
 
-ShillThirdPartyVpnDriverClient::~ShillThirdPartyVpnDriverClient() = default;
+ShillThirdPartyVpnDriverClient::~ShillThirdPartyVpnDriverClient() {
+  DCHECK_EQ(this, g_instance);
+  g_instance = nullptr;
+}
 
 // static
-ShillThirdPartyVpnDriverClient* ShillThirdPartyVpnDriverClient::Create() {
-  return new ShillThirdPartyVpnDriverClientImpl();
+void ShillThirdPartyVpnDriverClient::Initialize(dbus::Bus* bus) {
+  DCHECK(bus);
+  new ShillThirdPartyVpnDriverClientImpl(bus);
+}
+
+// static
+void ShillThirdPartyVpnDriverClient::InitializeFake() {
+  // If a browser test creates a custom fake, don't override it.
+  if (!FakeShillThirdPartyVpnDriverClient::Get())
+    new FakeShillThirdPartyVpnDriverClient();
+}
+
+// static
+void ShillThirdPartyVpnDriverClient::Shutdown() {
+  DCHECK(g_instance);
+  delete g_instance;
+}
+
+// static
+ShillThirdPartyVpnDriverClient* ShillThirdPartyVpnDriverClient::Get() {
+  return g_instance;
 }
 
 }  // namespace chromeos
diff --git a/chromeos/dbus/shill/shill_third_party_vpn_driver_client.h b/chromeos/dbus/shill/shill_third_party_vpn_driver_client.h
index 5d8af6d..89814686 100644
--- a/chromeos/dbus/shill/shill_third_party_vpn_driver_client.h
+++ b/chromeos/dbus/shill/shill_third_party_vpn_driver_client.h
@@ -12,9 +12,16 @@
 #include "base/callback.h"
 #include "base/component_export.h"
 #include "base/macros.h"
-#include "chromeos/dbus/dbus_client.h"
 #include "chromeos/dbus/shill/shill_client_helper.h"
 
+namespace base {
+class DictionaryValue;
+}  // namespace base
+
+namespace dbus {
+class Bus;
+}  // namespace dbus
+
 namespace chromeos {
 
 class ShillThirdPartyVpnObserver;
@@ -23,8 +30,7 @@
 // ThirdPartyVpnDriver service.
 // All methods should be called from the origin thread which initializes the
 // DBusThreadManager instance.
-class COMPONENT_EXPORT(CHROMEOS_DBUS) ShillThirdPartyVpnDriverClient
-    : public DBusClient {
+class COMPONENT_EXPORT(CHROMEOS_DBUS) ShillThirdPartyVpnDriverClient {
  public:
   class TestInterface {
    public:
@@ -37,11 +43,18 @@
     virtual ~TestInterface() {}
   };
 
-  ~ShillThirdPartyVpnDriverClient() override;
+  // Creates and initializes the global instance. |bus| must not be null.
+  static void Initialize(dbus::Bus* bus);
 
-  // Factory function, creates a new instance which is owned by the caller.
-  // For normal usage, access the singleton via DBusThreadManager::Get().
-  static ShillThirdPartyVpnDriverClient* Create();
+  // Creates the global instance with a fake implementation if not already
+  // created (e.g. in a browser test setup), otherwise does nothing.
+  static void InitializeFake();
+
+  // Destroys the global instance which must have been initialized.
+  static void Shutdown();
+
+  // Returns the global instance if initialized. May return null.
+  static ShillThirdPartyVpnDriverClient* Get();
 
   // Adds an |observer| for the third party vpn driver at |object_path_value|.
   virtual void AddShillThirdPartyVpnObserver(
@@ -83,8 +96,9 @@
  protected:
   friend class ShillThirdPartyVpnDriverClientTest;
 
-  // Create() should be used instead.
+  // Initialize/Shutdown should be used instead.
   ShillThirdPartyVpnDriverClient();
+  virtual ~ShillThirdPartyVpnDriverClient();
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ShillThirdPartyVpnDriverClient);
diff --git a/chromeos/dbus/shill/shill_third_party_vpn_driver_client_unittest.cc b/chromeos/dbus/shill/shill_third_party_vpn_driver_client_unittest.cc
index 6e30887..4185581 100644
--- a/chromeos/dbus/shill/shill_third_party_vpn_driver_client_unittest.cc
+++ b/chromeos/dbus/shill/shill_third_party_vpn_driver_client_unittest.cc
@@ -43,12 +43,17 @@
     ShillClientUnittestBase::SetUp();
 
     // Create a client with the mock bus.
-    client_.reset(ShillThirdPartyVpnDriverClient::Create());
-    client_->Init(mock_bus_.get());
+    ShillThirdPartyVpnDriverClient::Initialize(mock_bus_.get());
+    client_ = ShillThirdPartyVpnDriverClient::Get();
     // Run the message loop to run the signal connection result callback.
     base::RunLoop().RunUntilIdle();
   }
 
+  void TearDown() override {
+    ShillThirdPartyVpnDriverClient::Shutdown();
+    ShillClientUnittestBase::TearDown();
+  }
+
   MOCK_METHOD0(MockSuccess, void());
   MOCK_METHOD1(MockSuccessWithWarning, void(const std::string& warning));
   static void Failure(const std::string& error_name,
@@ -57,7 +62,7 @@
   }
 
  protected:
-  std::unique_ptr<ShillThirdPartyVpnDriverClient> client_;
+  ShillThirdPartyVpnDriverClient* client_ = nullptr;  // Unowned
 };
 
 TEST_F(ShillThirdPartyVpnDriverClientTest, PlatformSignal) {
diff --git a/chromeos/dbus/shill/sms_client.cc b/chromeos/dbus/shill/sms_client.cc
index ff4d131..f1188815 100644
--- a/chromeos/dbus/shill/sms_client.cc
+++ b/chromeos/dbus/shill/sms_client.cc
@@ -1,6 +1,7 @@
 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
+
 #include "chromeos/dbus/shill/sms_client.h"
 
 #include <map>
@@ -15,6 +16,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/values.h"
+#include "chromeos/dbus/shill/fake_sms_client.h"
 #include "dbus/bus.h"
 #include "dbus/message.h"
 #include "dbus/object_proxy.h"
@@ -29,6 +31,8 @@
 // See "enum MMSMSState" definition in ModemManager.
 constexpr uint32_t kSMSStateReceived = 3;  // MM_SMS_STATE_RECEIVED
 
+SMSClient* g_instance = nullptr;
+
 class SMSReceiveHandler {
  public:
   SMSReceiveHandler(dbus::ObjectProxy* object_proxy,
@@ -101,8 +105,7 @@
 // DBusThreadManager instance.
 class SMSClientImpl : public SMSClient {
  public:
-  SMSClientImpl() : bus_(NULL), weak_ptr_factory_(this) {}
-
+  explicit SMSClientImpl(dbus::Bus* bus) : bus_(bus), weak_ptr_factory_(this) {}
   ~SMSClientImpl() override = default;
 
   // Calls GetAll method.  |callback| is called after the method call succeeds.
@@ -116,9 +119,6 @@
                               std::move(callback)));
   }
 
- protected:
-  void Init(dbus::Bus* bus) override { bus_ = bus; }
-
  private:
   void OnSMSReceived(const dbus::ObjectPath& object_path,
                      GetAllCallback callback,
@@ -150,13 +150,36 @@
 const char SMSClient::kSMSPropertyText[] = "Text";
 const char SMSClient::kSMSPropertyTimestamp[] = "Timestamp";
 
-SMSClient::SMSClient() = default;
+SMSClient::SMSClient() {
+  DCHECK(!g_instance);
+  g_instance = this;
+}
 
-SMSClient::~SMSClient() = default;
+SMSClient::~SMSClient() {
+  DCHECK_EQ(this, g_instance);
+  g_instance = nullptr;
+}
 
 // static
-SMSClient* SMSClient::Create() {
-  return new SMSClientImpl();
+void SMSClient::Initialize(dbus::Bus* bus) {
+  DCHECK(bus);
+  new SMSClientImpl(bus);
+}
+
+// static
+void SMSClient::InitializeFake() {
+  new FakeSMSClient();
+}
+
+// static
+void SMSClient::Shutdown() {
+  DCHECK(g_instance);
+  delete g_instance;
+}
+
+// static
+SMSClient* SMSClient::Get() {
+  return g_instance;
 }
 
 }  // namespace chromeos
diff --git a/chromeos/dbus/shill/sms_client.h b/chromeos/dbus/shill/sms_client.h
index 478691fe..de50158 100644
--- a/chromeos/dbus/shill/sms_client.h
+++ b/chromeos/dbus/shill/sms_client.h
@@ -13,8 +13,9 @@
 #include "chromeos/dbus/dbus_client.h"
 
 namespace base {
+class Bus;
 class DictionaryValue;
-}
+}  // namespace base
 
 namespace dbus {
 class ObjectPath;
@@ -26,7 +27,7 @@
 // org.freedesktop.ModemManager1.SMS service.  All methods should be
 // called from the origin thread (UI thread) which initializes the
 // DBusThreadManager instance.
-class COMPONENT_EXPORT(CHROMEOS_DBUS) SMSClient : public DBusClient {
+class COMPONENT_EXPORT(CHROMEOS_DBUS) SMSClient {
  public:
   using GetAllCallback =
       base::OnceCallback<void(const base::DictionaryValue& sms)>;
@@ -36,11 +37,17 @@
   static const char kSMSPropertyText[];
   static const char kSMSPropertyTimestamp[];
 
-  ~SMSClient() override;
+  // Creates and initializes the global instance. |bus| must not be null.
+  static void Initialize(dbus::Bus* bus);
 
-  // Factory function, creates a new instance and returns ownership.
-  // For normal usage, access the singleton via DBusThreadManager::Get().
-  static SMSClient* Create();
+  // Creates the global instance with a fake implementation.
+  static void InitializeFake();
+
+  // Destroys the global instance which must have been initialized.
+  static void Shutdown();
+
+  // Returns the global instance if initialized. May return null.
+  static SMSClient* Get();
 
   // Calls GetAll method.  |callback| is called after the method call succeeds.
   virtual void GetAll(const std::string& service_name,
@@ -48,8 +55,9 @@
                       GetAllCallback callback) = 0;
 
  protected:
-  // Create() should be used instead.
+  // Initialize/Shutdown should be used instead.
   SMSClient();
+  virtual ~SMSClient();
 
  private:
   DISALLOW_COPY_AND_ASSIGN(SMSClient);
diff --git a/chromeos/geolocation/simple_geolocation_unittest.cc b/chromeos/geolocation/simple_geolocation_unittest.cc
index 7dc23e7..81ef9ca6 100644
--- a/chromeos/geolocation/simple_geolocation_unittest.cc
+++ b/chromeos/geolocation/simple_geolocation_unittest.cc
@@ -10,7 +10,7 @@
 #include "base/run_loop.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/shill/shill_clients.h"
 #include "chromeos/dbus/shill/shill_manager_client.h"
 #include "chromeos/geolocation/simple_geolocation_provider.h"
 #include "chromeos/geolocation/simple_geolocation_request_test_monitor.h"
@@ -273,8 +273,7 @@
 }
 
 TEST_F(SimpleGeolocationTest, NoWiFi) {
-  // This initializes DBusThreadManager and markes it "for tests only".
-  DBusThreadManager::GetSetterForTesting();
+  shill_clients::InitializeFakes();
   NetworkHandler::Initialize();
 
   WirelessTestMonitor requests_monitor;
@@ -301,7 +300,7 @@
   EXPECT_EQ(1U, url_factory.attempts());
 
   NetworkHandler::Shutdown();
-  DBusThreadManager::Shutdown();
+  shill_clients::Shutdown();
 }
 
 // Test sending of WiFi Access points and Cell Towers.
@@ -313,11 +312,9 @@
   ~SimpleGeolocationWirelessTest() override = default;
 
   void SetUp() override {
-    // This initializes DBusThreadManager and markes it "for tests only".
-    DBusThreadManager::GetSetterForTesting();
+    shill_clients::InitializeFakes();
     // Get the test interface for manager / device.
-    manager_test_ =
-        DBusThreadManager::Get()->GetShillManagerClient()->GetTestInterface();
+    manager_test_ = ShillManagerClient::Get()->GetTestInterface();
     ASSERT_TRUE(manager_test_);
     geolocation_handler_.reset(new GeolocationHandler());
     geolocation_handler_->Init();
@@ -326,7 +323,7 @@
 
   void TearDown() override {
     geolocation_handler_.reset();
-    DBusThreadManager::Shutdown();
+    shill_clients::Shutdown();
   }
 
   bool GetWifiAccessPoints() {
diff --git a/chromeos/network/auto_connect_handler.cc b/chromeos/network/auto_connect_handler.cc
index e75109a..ad31e7e 100644
--- a/chromeos/network/auto_connect_handler.cc
+++ b/chromeos/network/auto_connect_handler.cc
@@ -12,7 +12,6 @@
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/values.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/shill/shill_manager_client.h"
 #include "chromeos/dbus/shill/shill_service_client.h"
 #include "chromeos/network/device_state.h"
@@ -401,7 +400,7 @@
   NET_LOG(EVENT) << "ConnectToBestServices ["
                  << AutoConnectReasonsToString(auto_connect_reasons_) << "]";
 
-  DBusThreadManager::Get()->GetShillManagerClient()->ConnectToBestServices(
+  ShillManagerClient::Get()->ConnectToBestServices(
       base::Bind(&AutoConnectHandler::NotifyAutoConnectInitiated,
                  weak_ptr_factory_.GetWeakPtr(), auto_connect_reasons_),
       base::Bind(&network_handler::ShillErrorCallbackFunction,
diff --git a/chromeos/network/client_cert_resolver.cc b/chromeos/network/client_cert_resolver.cc
index 5a07452..e142d9c 100644
--- a/chromeos/network/client_cert_resolver.cc
+++ b/chromeos/network/client_cert_resolver.cc
@@ -20,7 +20,6 @@
 #include "base/stl_util.h"
 #include "base/task/post_task.h"
 #include "base/time/clock.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/shill/shill_service_client.h"
 #include "chromeos/network/certificate_helper.h"
 #include "chromeos/network/managed_network_configuration_handler.h"
@@ -723,7 +722,7 @@
       client_cert::SetEmptyShillProperties(match.cert_config_type,
                                            &shill_properties);
     }
-    DBusThreadManager::Get()->GetShillServiceClient()->SetProperties(
+    ShillServiceClient::Get()->SetProperties(
         dbus::ObjectPath(match.service_path), shill_properties,
         base::DoNothing(), base::BindRepeating(&LogError, match.service_path));
     network_state_handler_->RequestUpdateForNetwork(match.service_path);
diff --git a/chromeos/network/client_cert_resolver_unittest.cc b/chromeos/network/client_cert_resolver_unittest.cc
index 341d5bc..99e37416 100644
--- a/chromeos/network/client_cert_resolver_unittest.cc
+++ b/chromeos/network/client_cert_resolver_unittest.cc
@@ -21,7 +21,7 @@
 #include "base/test/scoped_task_environment.h"
 #include "base/test/simple_test_clock.h"
 #include "base/values.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/shill/shill_clients.h"
 #include "chromeos/dbus/shill/shill_manager_client.h"
 #include "chromeos/dbus/shill/shill_profile_client.h"
 #include "chromeos/dbus/shill/shill_service_client.h"
@@ -113,11 +113,9 @@
     test_system_nsscertdb_->SetSystemSlot(
         crypto::ScopedPK11Slot(PK11_ReferenceSlot(test_system_nssdb_.slot())));
 
-    DBusThreadManager::Initialize();
-    service_test_ =
-        DBusThreadManager::Get()->GetShillServiceClient()->GetTestInterface();
-    profile_test_ =
-        DBusThreadManager::Get()->GetShillProfileClient()->GetTestInterface();
+    shill_clients::InitializeFakes();
+    service_test_ = ShillServiceClient::Get()->GetTestInterface();
+    profile_test_ = ShillProfileClient::Get()->GetTestInterface();
     profile_test_->AddProfile(kUserProfilePath, kUserHash);
     scoped_task_environment_.RunUntilIdle();
     service_test_->ClearServices();
@@ -140,7 +138,7 @@
     network_profile_handler_.reset();
     network_state_handler_.reset();
     NetworkCertLoader::Shutdown();
-    DBusThreadManager::Shutdown();
+    shill_clients::Shutdown();
   }
 
  protected:
@@ -260,10 +258,8 @@
                                       base::Value("invalid id"));
     profile_test_->AddService(kUserProfilePath, kWifiStub);
 
-    DBusThreadManager::Get()
-        ->GetShillManagerClient()
-        ->GetTestInterface()
-        ->AddManagerService(kWifiStub, true);
+    ShillManagerClient::Get()->GetTestInterface()->AddManagerService(kWifiStub,
+                                                                     true);
   }
 
   // Sets up a policy with a certificate pattern that matches any client cert
diff --git a/chromeos/network/fast_transition_observer_unittest.cc b/chromeos/network/fast_transition_observer_unittest.cc
index 3ae70ea..dea7de6 100644
--- a/chromeos/network/fast_transition_observer_unittest.cc
+++ b/chromeos/network/fast_transition_observer_unittest.cc
@@ -6,7 +6,7 @@
 
 #include "base/message_loop/message_loop.h"
 #include "chromeos/constants/chromeos_pref_names.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/shill/shill_clients.h"
 #include "chromeos/dbus/shill/shill_manager_client.h"
 #include "chromeos/network/fast_transition_observer.h"
 #include "chromeos/network/network_state_handler.h"
@@ -21,7 +21,7 @@
 class FastTransitionObserverTest : public ::testing::Test {
  public:
   FastTransitionObserverTest() {
-    DBusThreadManager::Initialize();
+    shill_clients::InitializeFakes();
     network_state_handler_ = NetworkStateHandler::InitializeForTest();
     NetworkHandler::Initialize();
     local_state_ = std::make_unique<TestingPrefServiceSimple>();
@@ -36,14 +36,13 @@
     local_state_.reset();
     network_state_handler_.reset();
     NetworkHandler::Shutdown();
-    DBusThreadManager::Shutdown();
+    shill_clients::Shutdown();
   }
 
   TestingPrefServiceSimple* local_state() { return local_state_.get(); }
 
   bool GetFastTransitionStatus() {
-    return DBusThreadManager::Get()
-        ->GetShillManagerClient()
+    return ShillManagerClient::Get()
         ->GetTestInterface()
         ->GetFastTransitionStatus();
   }
diff --git a/chromeos/network/geolocation_handler.cc b/chromeos/network/geolocation_handler.cc
index c6e562ec..f4ea4bb8 100644
--- a/chromeos/network/geolocation_handler.cc
+++ b/chromeos/network/geolocation_handler.cc
@@ -10,7 +10,6 @@
 #include "base/bind.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/values.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/shill/shill_manager_client.h"
 #include "third_party/cros_system_api/dbus/service_constants.h"
 
@@ -31,19 +30,15 @@
     : cellular_enabled_(false), wifi_enabled_(false), weak_ptr_factory_(this) {}
 
 GeolocationHandler::~GeolocationHandler() {
-  ShillManagerClient* manager_client =
-      DBusThreadManager::Get()->GetShillManagerClient();
-  if (manager_client)
-    manager_client->RemovePropertyChangedObserver(this);
+  if (ShillManagerClient::Get())
+    ShillManagerClient::Get()->RemovePropertyChangedObserver(this);
 }
 
 void GeolocationHandler::Init() {
-  ShillManagerClient* manager_client =
-      DBusThreadManager::Get()->GetShillManagerClient();
-  manager_client->GetProperties(
+  ShillManagerClient::Get()->GetProperties(
       base::Bind(&GeolocationHandler::ManagerPropertiesCallback,
                  weak_ptr_factory_.GetWeakPtr()));
-  manager_client->AddPropertyChangedObserver(this);
+  ShillManagerClient::Get()->AddPropertyChangedObserver(this);
 }
 
 bool GeolocationHandler::GetWifiAccessPoints(
@@ -134,7 +129,7 @@
 }
 
 void GeolocationHandler::RequestGeolocationObjects() {
-  DBusThreadManager::Get()->GetShillManagerClient()->GetNetworksForGeolocation(
+  ShillManagerClient::Get()->GetNetworksForGeolocation(
       base::Bind(&GeolocationHandler::GeolocationCallback,
                  weak_ptr_factory_.GetWeakPtr()));
 }
diff --git a/chromeos/network/geolocation_handler_unittest.cc b/chromeos/network/geolocation_handler_unittest.cc
index b0cc696..9b16d75 100644
--- a/chromeos/network/geolocation_handler_unittest.cc
+++ b/chromeos/network/geolocation_handler_unittest.cc
@@ -12,7 +12,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/values.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/shill/shill_clients.h"
 #include "chromeos/dbus/shill/shill_manager_client.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/cros_system_api/dbus/service_constants.h"
@@ -28,11 +28,9 @@
   ~GeolocationHandlerTest() override = default;
 
   void SetUp() override {
-    // Initialize DBusThreadManager with a stub implementation.
-    DBusThreadManager::Initialize();
+    shill_clients::InitializeFakes();
     // Get the test interface for manager / device.
-    manager_test_ =
-        DBusThreadManager::Get()->GetShillManagerClient()->GetTestInterface();
+    manager_test_ = ShillManagerClient::Get()->GetTestInterface();
     ASSERT_TRUE(manager_test_);
     geolocation_handler_.reset(new GeolocationHandler());
     geolocation_handler_->Init();
@@ -41,7 +39,7 @@
 
   void TearDown() override {
     geolocation_handler_.reset();
-    DBusThreadManager::Shutdown();
+    shill_clients::Shutdown();
   }
 
   bool GetWifiAccessPoints() {
diff --git a/chromeos/network/managed_network_configuration_handler_unittest.cc b/chromeos/network/managed_network_configuration_handler_unittest.cc
index 395ee4e..a77a81cd 100644
--- a/chromeos/network/managed_network_configuration_handler_unittest.cc
+++ b/chromeos/network/managed_network_configuration_handler_unittest.cc
@@ -13,9 +13,9 @@
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "base/values.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
-#include "chromeos/dbus/shill/fake_shill_profile_client.h"
-#include "chromeos/dbus/shill/fake_shill_service_client.h"
+#include "chromeos/dbus/shill/shill_clients.h"
+#include "chromeos/dbus/shill/shill_profile_client.h"
+#include "chromeos/dbus/shill/shill_service_client.h"
 #include "chromeos/network/managed_network_configuration_handler_impl.h"
 #include "chromeos/network/mock_network_state_handler.h"
 #include "chromeos/network/network_configuration_handler.h"
@@ -88,7 +88,7 @@
 class ManagedNetworkConfigurationHandlerTest : public testing::Test {
  public:
   ManagedNetworkConfigurationHandlerTest() {
-    DBusThreadManager::Initialize();
+    shill_clients::InitializeFakes();
 
     network_state_handler_ = MockNetworkStateHandler::InitializeForTest();
     network_profile_handler_ = std::make_unique<TestNetworkProfileHandler>();
@@ -115,7 +115,7 @@
     network_configuration_handler_.reset();
     network_profile_handler_.reset();
     network_state_handler_.reset();
-    DBusThreadManager::Shutdown();
+    shill_clients::Shutdown();
   }
 
   TestNetworkPolicyObserver* policy_observer() { return &policy_observer_; }
@@ -124,14 +124,12 @@
     return managed_network_configuration_handler_.get();
   }
 
-  FakeShillServiceClient* GetShillServiceClient() {
-    return static_cast<FakeShillServiceClient*>(
-        DBusThreadManager::Get()->GetShillServiceClient());
+  ShillServiceClient::TestInterface* GetShillServiceClient() {
+    return ShillServiceClient::Get()->GetTestInterface();
   }
 
-  FakeShillProfileClient* GetShillProfileClient() {
-    return static_cast<FakeShillProfileClient*>(
-        DBusThreadManager::Get()->GetShillProfileClient());
+  ShillProfileClient::TestInterface* GetShillProfileClient() {
+    return ShillProfileClient::Get()->GetTestInterface();
   }
 
   void InitializeStandardProfiles() {
diff --git a/chromeos/network/network_activation_handler.cc b/chromeos/network/network_activation_handler.cc
index 2e53320..fd28870a 100644
--- a/chromeos/network/network_activation_handler.cc
+++ b/chromeos/network/network_activation_handler.cc
@@ -6,7 +6,6 @@
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/shill/shill_service_client.h"
 #include "chromeos/network/network_event_log.h"
 #include "chromeos/network/network_handler.h"
@@ -43,13 +42,12 @@
     const base::Closure& success_callback,
     const network_handler::ErrorCallback& error_callback) {
   NET_LOG_USER("Activation Request", service_path + ": '" + carrier + "'");
-  DBusThreadManager::Get()->GetShillServiceClient()->ActivateCellularModem(
-      dbus::ObjectPath(service_path),
-      carrier,
-      base::Bind(&NetworkActivationHandler::HandleShillSuccess,
-                 AsWeakPtr(), service_path, success_callback),
-      base::Bind(&network_handler::ShillErrorCallbackFunction,
-                 kErrorShillError, service_path, error_callback));
+  ShillServiceClient::Get()->ActivateCellularModem(
+      dbus::ObjectPath(service_path), carrier,
+      base::Bind(&NetworkActivationHandler::HandleShillSuccess, AsWeakPtr(),
+                 service_path, success_callback),
+      base::Bind(&network_handler::ShillErrorCallbackFunction, kErrorShillError,
+                 service_path, error_callback));
 }
 
 void NetworkActivationHandler::CallShillCompleteActivation(
@@ -57,12 +55,12 @@
     const base::Closure& success_callback,
     const network_handler::ErrorCallback& error_callback) {
   NET_LOG_USER("CompleteActivation Request", service_path);
-  DBusThreadManager::Get()->GetShillServiceClient()->CompleteCellularActivation(
+  ShillServiceClient::Get()->CompleteCellularActivation(
       dbus::ObjectPath(service_path),
-      base::Bind(&NetworkActivationHandler::HandleShillSuccess,
-                 AsWeakPtr(), service_path, success_callback),
-      base::Bind(&network_handler::ShillErrorCallbackFunction,
-                 kErrorShillError, service_path, error_callback));
+      base::Bind(&NetworkActivationHandler::HandleShillSuccess, AsWeakPtr(),
+                 service_path, success_callback),
+      base::Bind(&network_handler::ShillErrorCallbackFunction, kErrorShillError,
+                 service_path, error_callback));
 }
 
 void NetworkActivationHandler::HandleShillSuccess(
diff --git a/chromeos/network/network_cert_migrator.cc b/chromeos/network/network_cert_migrator.cc
index a0a199d..879cde1 100644
--- a/chromeos/network/network_cert_migrator.cc
+++ b/chromeos/network/network_cert_migrator.cc
@@ -12,7 +12,6 @@
 #include "base/bind_helpers.h"
 #include "base/location.h"
 #include "base/metrics/histogram_macros.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/shill/shill_service_client.h"
 #include "chromeos/network/client_cert_util.h"
 #include "chromeos/network/network_handler_callbacks.h"
@@ -62,12 +61,11 @@
         continue;
       }
 
-      DBusThreadManager::Get()->GetShillServiceClient()->GetProperties(
+      ShillServiceClient::Get()->GetProperties(
           dbus::ObjectPath(service_path),
           base::Bind(&network_handler::GetPropertiesCallback,
                      base::Bind(&MigrationTask::MigrateNetwork, this),
-                     network_handler::ErrorCallback(),
-                     service_path));
+                     network_handler::ErrorCallback(), service_path));
     }
   }
 
@@ -145,7 +143,7 @@
 
   void SendPropertiesToShill(const std::string& service_path,
                              const base::DictionaryValue& properties) {
-    DBusThreadManager::Get()->GetShillServiceClient()->SetProperties(
+    ShillServiceClient::Get()->SetProperties(
         dbus::ObjectPath(service_path), properties, base::DoNothing(),
         base::Bind(&LogError, service_path));
   }
diff --git a/chromeos/network/network_cert_migrator_unittest.cc b/chromeos/network/network_cert_migrator_unittest.cc
index 324969fb..adcf648 100644
--- a/chromeos/network/network_cert_migrator_unittest.cc
+++ b/chromeos/network/network_cert_migrator_unittest.cc
@@ -12,7 +12,7 @@
 #include "base/macros.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/test/scoped_task_environment.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/shill/shill_clients.h"
 #include "chromeos/dbus/shill/shill_profile_client.h"
 #include "chromeos/dbus/shill/shill_service_client.h"
 #include "chromeos/network/network_cert_loader.h"
@@ -64,13 +64,10 @@
     test_system_nsscertdb_->SetSystemSlot(
         crypto::ScopedPK11Slot(PK11_ReferenceSlot(test_system_nssdb_.slot())));
 
-    DBusThreadManager::Initialize();
-    service_test_ =
-        DBusThreadManager::Get()->GetShillServiceClient()->GetTestInterface();
-    DBusThreadManager::Get()
-        ->GetShillProfileClient()
-        ->GetTestInterface()
-        ->AddProfile(kUserShillProfile, "" /* userhash */);
+    shill_clients::InitializeFakes();
+    service_test_ = ShillServiceClient::Get()->GetTestInterface();
+    ShillProfileClient::Get()->GetTestInterface()->AddProfile(
+        kUserShillProfile, "" /* userhash */);
     scoped_task_environment_.RunUntilIdle();
     service_test_->ClearServices();
     scoped_task_environment_.RunUntilIdle();
@@ -83,7 +80,7 @@
     network_cert_migrator_.reset();
     network_state_handler_.reset();
     NetworkCertLoader::Shutdown();
-    DBusThreadManager::Shutdown();
+    shill_clients::Shutdown();
   }
 
  protected:
diff --git a/chromeos/network/network_configuration_handler.cc b/chromeos/network/network_configuration_handler.cc
index b594fcb..bb5fae5 100644
--- a/chromeos/network/network_configuration_handler.cc
+++ b/chromeos/network/network_configuration_handler.cc
@@ -18,7 +18,6 @@
 #include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/values.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/shill/shill_manager_client.h"
 #include "chromeos/dbus/shill/shill_profile_client.h"
 #include "chromeos/dbus/shill/shill_service_client.h"
@@ -118,12 +117,10 @@
   }
 
   void Run() {
-    DBusThreadManager::Get()
-        ->GetShillServiceClient()
-        ->GetLoadableProfileEntries(
-            dbus::ObjectPath(service_path_),
-            base::Bind(&ProfileEntryDeleter::GetProfileEntriesToDeleteCallback,
-                       weak_ptr_factory_.GetWeakPtr()));
+    ShillServiceClient::Get()->GetLoadableProfileEntries(
+        dbus::ObjectPath(service_path_),
+        base::Bind(&ProfileEntryDeleter::GetProfileEntriesToDeleteCallback,
+                   weak_ptr_factory_.GetWeakPtr()));
   }
 
  private:
@@ -166,7 +163,7 @@
       NET_LOG(DEBUG) << "Delete Profile Entry: " << profile_path << ": "
                      << entry_path;
       profile_delete_entries_[profile_path] = entry_path;
-      DBusThreadManager::Get()->GetShillProfileClient()->DeleteEntry(
+      ShillProfileClient::Get()->DeleteEntry(
           dbus::ObjectPath(profile_path), entry_path,
           base::Bind(&ProfileEntryDeleter::ProfileEntryDeletedCallback,
                      weak_ptr_factory_.GetWeakPtr(), profile_path, entry_path),
@@ -258,7 +255,7 @@
     callback.Run(service_path, dictionary);
     return;
   }
-  DBusThreadManager::Get()->GetShillServiceClient()->GetProperties(
+  ShillServiceClient::Get()->GetProperties(
       dbus::ObjectPath(service_path),
       base::Bind(&NetworkConfigurationHandler::GetPropertiesCallback,
                  weak_ptr_factory_.GetWeakPtr(), callback, error_callback,
@@ -297,7 +294,7 @@
 
   std::unique_ptr<base::DictionaryValue> properties_copy(
       properties_to_set->DeepCopy());
-  DBusThreadManager::Get()->GetShillServiceClient()->SetProperties(
+  ShillServiceClient::Get()->SetProperties(
       dbus::ObjectPath(service_path), *properties_to_set,
       base::Bind(&NetworkConfigurationHandler::SetPropertiesSuccessCallback,
                  weak_ptr_factory_.GetWeakPtr(), service_path,
@@ -326,7 +323,7 @@
        iter != names.end(); ++iter) {
     NET_LOG(DEBUG) << "ClearProperty: " << service_path << "." << *iter;
   }
-  DBusThreadManager::Get()->GetShillServiceClient()->ClearProperties(
+  ShillServiceClient::Get()->ClearProperties(
       dbus::ObjectPath(service_path), names,
       base::Bind(&NetworkConfigurationHandler::ClearPropertiesSuccessCallback,
                  weak_ptr_factory_.GetWeakPtr(), service_path, names, callback),
@@ -338,8 +335,7 @@
     const base::DictionaryValue& shill_properties,
     const network_handler::ServiceResultCallback& callback,
     const network_handler::ErrorCallback& error_callback) {
-  ShillManagerClient* manager =
-      DBusThreadManager::Get()->GetShillManagerClient();
+  ShillManagerClient* manager = ShillManagerClient::Get();
   std::string type;
   shill_properties.GetStringWithoutPathExpansion(shill::kTypeProperty, &type);
   DCHECK(!type.empty());
@@ -437,7 +433,7 @@
   NET_LOG(USER) << "SetNetworkProfile: " << service_path << ": "
                 << profile_path;
   base::Value profile_path_value(profile_path);
-  DBusThreadManager::Get()->GetShillServiceClient()->SetProperty(
+  ShillServiceClient::Get()->SetProperty(
       dbus::ObjectPath(service_path), shill::kProfileProperty,
       profile_path_value,
       base::Bind(&NetworkConfigurationHandler::SetNetworkProfileCompleted,
@@ -453,7 +449,7 @@
     const base::Closure& callback,
     const network_handler::ErrorCallback& error_callback) {
   NET_LOG(USER) << "SetManagerProperty: " << property_name << ": " << value;
-  DBusThreadManager::Get()->GetShillManagerClient()->SetProperty(
+  ShillManagerClient::Get()->SetProperty(
       property_name, value, callback,
       base::Bind(&ManagerSetPropertiesErrorCallback, error_callback));
 }
diff --git a/chromeos/network/network_configuration_handler_unittest.cc b/chromeos/network/network_configuration_handler_unittest.cc
index f32719b..9689665 100644
--- a/chromeos/network/network_configuration_handler_unittest.cc
+++ b/chromeos/network/network_configuration_handler_unittest.cc
@@ -18,9 +18,10 @@
 #include "base/stl_util.h"
 #include "base/strings/string_piece.h"
 #include "base/values.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
-#include "chromeos/dbus/shill/fake_shill_profile_client.h"
-#include "chromeos/dbus/shill/fake_shill_service_client.h"
+#include "chromeos/dbus/shill/shill_clients.h"
+#include "chromeos/dbus/shill/shill_manager_client.h"
+#include "chromeos/dbus/shill/shill_profile_client.h"
+#include "chromeos/dbus/shill/shill_service_client.h"
 #include "chromeos/network/network_configuration_handler.h"
 #include "chromeos/network/network_configuration_observer.h"
 #include "chromeos/network/network_profile_handler.h"
@@ -146,7 +147,7 @@
 class NetworkConfigurationHandlerTest : public testing::Test {
  public:
   NetworkConfigurationHandlerTest() {
-    DBusThreadManager::Initialize();
+    shill_clients::InitializeFakes();
 
     network_state_handler_ = NetworkStateHandler::InitializeForTest();
     // Note: NetworkConfigurationHandler's contructor is private, so
@@ -169,7 +170,7 @@
     network_configuration_handler_.reset();
     network_state_handler_.reset();
 
-    DBusThreadManager::Shutdown();
+    shill_clients::Shutdown();
   }
 
   void SuccessCallback(const std::string& callback_name) {
@@ -258,7 +259,7 @@
                                 const std::string& key,
                                 std::string* result) {
     ShillServiceClient::TestInterface* service_test =
-        DBusThreadManager::Get()->GetShillServiceClient()->GetTestInterface();
+        ShillServiceClient::Get()->GetTestInterface();
     const base::DictionaryValue* properties =
         service_test->GetServiceProperties(service_path);
     if (!properties)
@@ -296,14 +297,12 @@
     return true;
   }
 
-  FakeShillServiceClient* GetShillServiceClient() {
-    return static_cast<FakeShillServiceClient*>(
-        DBusThreadManager::Get()->GetShillServiceClient());
+  ShillServiceClient::TestInterface* GetShillServiceClient() {
+    return ShillServiceClient::Get()->GetTestInterface();
   }
 
-  FakeShillProfileClient* GetShillProfileClient() {
-    return static_cast<FakeShillProfileClient*>(
-        DBusThreadManager::Get()->GetShillProfileClient());
+  ShillProfileClient::TestInterface* GetShillProfileClient() {
+    return ShillProfileClient::Get()->GetTestInterface();
   }
 
   std::unique_ptr<NetworkStateHandler> network_state_handler_;
@@ -676,7 +675,7 @@
       shill::kAlwaysOnVpnPackageProperty, base::Value(vpn_package),
       base::DoNothing(), base::Bind(&ErrorCallback));
 
-  DBusThreadManager::Get()->GetShillManagerClient()->GetProperties(
+  ShillManagerClient::Get()->GetProperties(
       Bind(&NetworkConfigurationHandlerTest::ManagerGetPropertiesCallback,
            base::Unretained(this), "ManagerGetProperties"));
   base::RunLoop().RunUntilIdle();
diff --git a/chromeos/network/network_connect_unittest.cc b/chromeos/network/network_connect_unittest.cc
index fb4f2f7..d6365df 100644
--- a/chromeos/network/network_connect_unittest.cc
+++ b/chromeos/network/network_connect_unittest.cc
@@ -10,7 +10,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/shill/shill_clients.h"
 #include "chromeos/dbus/shill/shill_device_client.h"
 #include "chromeos/dbus/shill/shill_service_client.h"
 #include "chromeos/login/login_state/login_state.h"
@@ -92,7 +92,7 @@
 
   void SetUp() override {
     testing::Test::SetUp();
-    DBusThreadManager::Initialize();
+    shill_clients::InitializeFakes();
     LoginState::Initialize();
     SetupDefaultShillState();
     NetworkHandler::Initialize();
@@ -113,15 +113,14 @@
     mock_delegate_.reset();
     LoginState::Shutdown();
     NetworkHandler::Shutdown();
-    DBusThreadManager::Shutdown();
+    shill_clients::Shutdown();
     testing::Test::TearDown();
   }
 
  protected:
   void SetupDefaultShillState() {
     base::RunLoop().RunUntilIdle();
-    device_test_ =
-        DBusThreadManager::Get()->GetShillDeviceClient()->GetTestInterface();
+    device_test_ = ShillDeviceClient::Get()->GetTestInterface();
     device_test_->ClearDevices();
     device_test_->AddDevice("/device/stub_wifi_device1", shill::kTypeWifi,
                             "stub_wifi_device1");
@@ -131,8 +130,7 @@
         kCellular1DevicePath, shill::kTechnologyFamilyProperty,
         base::Value(shill::kNetworkTechnologyGsm), /*notify_changed=*/true);
 
-    service_test_ =
-        DBusThreadManager::Get()->GetShillServiceClient()->GetTestInterface();
+    service_test_ = ShillServiceClient::Get()->GetTestInterface();
     service_test_->ClearServices();
     const bool add_to_visible = true;
 
diff --git a/chromeos/network/network_connection_handler.cc b/chromeos/network/network_connection_handler.cc
index 4ee7ca1..d7df369 100644
--- a/chromeos/network/network_connection_handler.cc
+++ b/chromeos/network/network_connection_handler.cc
@@ -11,7 +11,6 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/shill/shill_manager_client.h"
 #include "chromeos/dbus/shill/shill_service_client.h"
 #include "chromeos/network/client_cert_resolver.h"
diff --git a/chromeos/network/network_connection_handler_impl.cc b/chromeos/network/network_connection_handler_impl.cc
index 6cc346e..a9ad926 100644
--- a/chromeos/network/network_connection_handler_impl.cc
+++ b/chromeos/network/network_connection_handler_impl.cc
@@ -12,7 +12,6 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/shill/shill_manager_client.h"
 #include "chromeos/dbus/shill/shill_service_client.h"
 #include "chromeos/network/client_cert_resolver.h"
@@ -678,7 +677,7 @@
     const std::string& service_path) {
   NET_LOG_EVENT("Sending Connect Request to Shill", service_path);
   network_state_handler_->ClearLastErrorForNetwork(service_path);
-  DBusThreadManager::Get()->GetShillServiceClient()->Connect(
+  ShillServiceClient::Get()->Connect(
       dbus::ObjectPath(service_path),
       base::Bind(&NetworkConnectionHandlerImpl::HandleShillConnectSuccess,
                  AsWeakPtr(), service_path),
@@ -843,7 +842,7 @@
     const base::Closure& success_callback,
     const network_handler::ErrorCallback& error_callback) {
   NET_LOG_USER("Disconnect Request", service_path);
-  DBusThreadManager::Get()->GetShillServiceClient()->Disconnect(
+  ShillServiceClient::Get()->Disconnect(
       dbus::ObjectPath(service_path),
       base::Bind(&NetworkConnectionHandlerImpl::HandleShillDisconnectSuccess,
                  AsWeakPtr(), service_path, success_callback),
diff --git a/chromeos/network/network_device_handler_impl.cc b/chromeos/network/network_device_handler_impl.cc
index 84e8533d..533c25f 100644
--- a/chromeos/network/network_device_handler_impl.cc
+++ b/chromeos/network/network_device_handler_impl.cc
@@ -21,7 +21,6 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "base/values.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/shill/shill_device_client.h"
 #include "chromeos/dbus/shill/shill_ipconfig_client.h"
 #include "chromeos/network/device_state.h"
@@ -97,7 +96,7 @@
     std::string ipconfig_path;
     if (!ip_configs->GetString(i, &ipconfig_path))
       continue;
-    DBusThreadManager::Get()->GetShillIPConfigClient()->Refresh(
+    ShillIPConfigClient::Get()->Refresh(
         dbus::ObjectPath(ipconfig_path),
         base::BindOnce(&IPConfigRefreshCallback, ipconfig_path));
   }
@@ -116,7 +115,7 @@
     const base::Closure& callback,
     const network_handler::ErrorCallback& error_callback) {
   NET_LOG(USER) << "Device.SetProperty: " << property_name << " = " << value;
-  DBusThreadManager::Get()->GetShillDeviceClient()->SetProperty(
+  ShillDeviceClient::Get()->SetProperty(
       dbus::ObjectPath(device_path), property_name, value, callback,
       base::Bind(&HandleShillCallFailure, device_path, error_callback));
 }
@@ -184,7 +183,7 @@
   new_params.ip_or_mac_address = params.ip_or_mac_address;
 
   base::TimeDelta request_delay;
-  if (!DBusThreadManager::Get()->GetShillDeviceClient()->GetTestInterface())
+  if (!ShillDeviceClient::Get()->GetTestInterface())
     request_delay = base::TimeDelta::FromMilliseconds(request_delay_ms);
 
   base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
@@ -212,7 +211,7 @@
                    << device_path;
     const int64_t kReRequestDelayMs = 1000;
     base::TimeDelta request_delay;
-    if (!DBusThreadManager::Get()->GetShillDeviceClient()->GetTestInterface())
+    if (!ShillDeviceClient::Get()->GetTestInterface())
       request_delay = base::TimeDelta::FromMilliseconds(kReRequestDelayMs);
 
     base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
@@ -249,7 +248,7 @@
   LOG(ERROR) << "TDLS: " << params.operation;
   NET_LOG(EVENT) << "CallPerformTDLSOperation: " << params.operation << ": "
                  << device_path;
-  DBusThreadManager::Get()->GetShillDeviceClient()->PerformTDLSOperation(
+  ShillDeviceClient::Get()->PerformTDLSOperation(
       dbus::ObjectPath(device_path), params.operation, params.ip_or_mac_address,
       base::Bind(&TDLSSuccessCallback, device_path, params, callback,
                  error_callback),
@@ -268,7 +267,7 @@
     const std::string& device_path,
     const network_handler::DictionaryResultCallback& callback,
     const network_handler::ErrorCallback& error_callback) const {
-  DBusThreadManager::Get()->GetShillDeviceClient()->GetProperties(
+  ShillDeviceClient::Get()->GetProperties(
       dbus::ObjectPath(device_path),
       base::Bind(&network_handler::GetPropertiesCallback, callback,
                  error_callback, device_path));
@@ -314,7 +313,7 @@
     const network_handler::ErrorCallback& error_callback) {
   NET_LOG(USER) << "Device.RegisterCellularNetwork: " << device_path
                 << " Id: " << network_id;
-  DBusThreadManager::Get()->GetShillDeviceClient()->Register(
+  ShillDeviceClient::Get()->Register(
       dbus::ObjectPath(device_path), network_id, callback,
       base::Bind(&HandleShillCallFailure, device_path, error_callback));
 }
@@ -326,7 +325,7 @@
     const network_handler::ErrorCallback& error_callback) {
   NET_LOG(USER) << "Device.SetCarrier: " << device_path
                 << " carrier: " << carrier;
-  DBusThreadManager::Get()->GetShillDeviceClient()->SetCarrier(
+  ShillDeviceClient::Get()->SetCarrier(
       dbus::ObjectPath(device_path), carrier, callback,
       base::Bind(&HandleShillCallFailure, device_path, error_callback));
 }
@@ -338,7 +337,7 @@
     const base::Closure& callback,
     const network_handler::ErrorCallback& error_callback) {
   NET_LOG(USER) << "Device.RequirePin: " << device_path << ": " << require_pin;
-  DBusThreadManager::Get()->GetShillDeviceClient()->RequirePin(
+  ShillDeviceClient::Get()->RequirePin(
       dbus::ObjectPath(device_path), pin, require_pin, callback,
       base::Bind(&HandleShillCallFailure, device_path, error_callback));
 }
@@ -349,7 +348,7 @@
     const base::Closure& callback,
     const network_handler::ErrorCallback& error_callback) {
   NET_LOG(USER) << "Device.EnterPin: " << device_path;
-  DBusThreadManager::Get()->GetShillDeviceClient()->EnterPin(
+  ShillDeviceClient::Get()->EnterPin(
       dbus::ObjectPath(device_path), pin, callback,
       base::Bind(&HandleShillCallFailure, device_path, error_callback));
 }
@@ -361,7 +360,7 @@
     const base::Closure& callback,
     const network_handler::ErrorCallback& error_callback) {
   NET_LOG(USER) << "Device.UnblockPin: " << device_path;
-  DBusThreadManager::Get()->GetShillDeviceClient()->UnblockPin(
+  ShillDeviceClient::Get()->UnblockPin(
       dbus::ObjectPath(device_path), puk, new_pin, callback,
       base::Bind(&HandleShillCallFailure, device_path, error_callback));
 }
@@ -373,7 +372,7 @@
     const base::Closure& callback,
     const network_handler::ErrorCallback& error_callback) {
   NET_LOG(USER) << "Device.ChangePin: " << device_path;
-  DBusThreadManager::Get()->GetShillDeviceClient()->ChangePin(
+  ShillDeviceClient::Get()->ChangePin(
       dbus::ObjectPath(device_path), old_pin, new_pin, callback,
       base::Bind(&HandleShillCallFailure, device_path, error_callback));
 }
@@ -431,7 +430,7 @@
     return;
 
   NET_LOG(USER) << "Device.AddWakeOnWifi: " << device_state->path();
-  DBusThreadManager::Get()->GetShillDeviceClient()->AddWakeOnPacketConnection(
+  ShillDeviceClient::Get()->AddWakeOnPacketConnection(
       dbus::ObjectPath(device_state->path()), ip_endpoint, callback,
       base::Bind(&HandleShillCallFailure, device_state->path(),
                  error_callback));
@@ -447,7 +446,7 @@
 
   NET_LOG(USER) << "Device.AddWifiWakeOnPacketOfTypes: " << device_state->path()
                 << " Types: " << base::JoinString(types, " ");
-  DBusThreadManager::Get()->GetShillDeviceClient()->AddWakeOnPacketOfTypes(
+  ShillDeviceClient::Get()->AddWakeOnPacketOfTypes(
       dbus::ObjectPath(device_state->path()), types, callback,
       base::Bind(&HandleShillCallFailure, device_state->path(),
                  error_callback));
@@ -462,12 +461,10 @@
     return;
 
   NET_LOG(USER) << "Device.RemoveWakeOnWifi: " << device_state->path();
-  DBusThreadManager::Get()
-      ->GetShillDeviceClient()
-      ->RemoveWakeOnPacketConnection(
-          dbus::ObjectPath(device_state->path()), ip_endpoint, callback,
-          base::Bind(&HandleShillCallFailure, device_state->path(),
-                     error_callback));
+  ShillDeviceClient::Get()->RemoveWakeOnPacketConnection(
+      dbus::ObjectPath(device_state->path()), ip_endpoint, callback,
+      base::Bind(&HandleShillCallFailure, device_state->path(),
+                 error_callback));
 }
 
 void NetworkDeviceHandlerImpl::RemoveWifiWakeOnPacketOfTypes(
@@ -481,7 +478,7 @@
   NET_LOG(USER) << "Device.RemoveWifiWakeOnPacketOfTypes: "
                 << device_state->path()
                 << " Types: " << base::JoinString(types, " ");
-  DBusThreadManager::Get()->GetShillDeviceClient()->RemoveWakeOnPacketOfTypes(
+  ShillDeviceClient::Get()->RemoveWakeOnPacketOfTypes(
       dbus::ObjectPath(device_state->path()), types, callback,
       base::Bind(&HandleShillCallFailure, device_state->path(),
                  error_callback));
@@ -495,12 +492,10 @@
     return;
 
   NET_LOG(USER) << "Device.RemoveAllWakeOnWifi: " << device_state->path();
-  DBusThreadManager::Get()
-      ->GetShillDeviceClient()
-      ->RemoveAllWakeOnPacketConnections(
-          dbus::ObjectPath(device_state->path()), callback,
-          base::Bind(&HandleShillCallFailure, device_state->path(),
-                     error_callback));
+  ShillDeviceClient::Get()->RemoveAllWakeOnPacketConnections(
+      dbus::ObjectPath(device_state->path()), callback,
+      base::Bind(&HandleShillCallFailure, device_state->path(),
+                 error_callback));
 }
 
 void NetworkDeviceHandlerImpl::DeviceListChanged() {
diff --git a/chromeos/network/network_device_handler_unittest.cc b/chromeos/network/network_device_handler_unittest.cc
index aa7eafd..fe11cd5 100644
--- a/chromeos/network/network_device_handler_unittest.cc
+++ b/chromeos/network/network_device_handler_unittest.cc
@@ -11,7 +11,8 @@
 #include "base/values.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/shill/fake_shill_device_client.h"
-#include "chromeos/dbus/shill/fake_shill_manager_client.h"
+#include "chromeos/dbus/shill/shill_clients.h"
+#include "chromeos/dbus/shill/shill_manager_client.h"
 #include "chromeos/network/network_device_handler_impl.h"
 #include "chromeos/network/network_state_handler.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -38,9 +39,9 @@
   ~NetworkDeviceHandlerTest() override = default;
 
   void SetUp() override {
-    fake_device_client_ = new FakeShillDeviceClient;
-    DBusThreadManager::GetSetterForTesting()->SetShillDeviceClient(
-        std::unique_ptr<ShillDeviceClient>(fake_device_client_));
+    shill_clients::InitializeFakes();
+    fake_device_client_ = ShillDeviceClient::Get();
+    fake_device_client_->GetTestInterface()->ClearDevices();
 
     success_callback_ = base::Bind(&NetworkDeviceHandlerTest::SuccessCallback,
                                    base::Unretained(this));
@@ -78,7 +79,7 @@
     network_state_handler_->Shutdown();
     network_device_handler_.reset();
     network_state_handler_.reset();
-    DBusThreadManager::Shutdown();
+    shill_clients::Shutdown();
   }
 
   void ErrorCallback(const std::string& error_name,
@@ -105,7 +106,7 @@
  protected:
   base::test::ScopedTaskEnvironment scoped_task_environment_;
   std::string result_;
-  FakeShillDeviceClient* fake_device_client_ = nullptr;
+  ShillDeviceClient* fake_device_client_ = nullptr;
   std::unique_ptr<NetworkDeviceHandler> network_device_handler_;
   std::unique_ptr<NetworkStateHandler> network_state_handler_;
   base::Closure success_callback_;
@@ -374,7 +375,8 @@
   const char kNewPin[] = "1234";
   const char kIncorrectPin[] = "9999";
 
-  fake_device_client_->SetSimLocked(kDefaultCellularDevicePath, true);
+  fake_device_client_->GetTestInterface()->SetSimLocked(
+      kDefaultCellularDevicePath, true);
 
   // Test that the success callback gets called.
   network_device_handler_->ChangePin(
diff --git a/chromeos/network/network_profile_handler.cc b/chromeos/network/network_profile_handler.cc
index 82713e72..b83d51b2 100644
--- a/chromeos/network/network_profile_handler.cc
+++ b/chromeos/network/network_profile_handler.cc
@@ -11,7 +11,6 @@
 #include "base/bind.h"
 #include "base/strings/string_util.h"
 #include "base/values.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/shill/shill_manager_client.h"
 #include "chromeos/dbus/shill/shill_profile_client.h"
 #include "chromeos/network/network_profile_observer.h"
@@ -127,11 +126,10 @@
     pending_profile_creations_.insert(*it);
 
     VLOG(2) << "Requesting properties of profile path " << *it << ".";
-    DBusThreadManager::Get()->GetShillProfileClient()->GetProperties(
+    ShillProfileClient::Get()->GetProperties(
         dbus::ObjectPath(*it),
         base::Bind(&NetworkProfileHandler::GetProfilePropertiesCallback,
-                   weak_ptr_factory_.GetWeakPtr(),
-                   *it),
+                   weak_ptr_factory_.GetWeakPtr(), *it),
         base::Bind(&LogProfileRequestError, *it));
   }
 }
@@ -209,18 +207,16 @@
 }
 
 void NetworkProfileHandler::Init() {
-  DBusThreadManager::Get()->GetShillManagerClient()->
-      AddPropertyChangedObserver(this);
+  ShillManagerClient::Get()->AddPropertyChangedObserver(this);
 
   // Request the initial profile list.
-  DBusThreadManager::Get()->GetShillManagerClient()->GetProperties(
+  ShillManagerClient::Get()->GetProperties(
       base::Bind(&NetworkProfileHandler::GetManagerPropertiesCallback,
                  weak_ptr_factory_.GetWeakPtr()));
 }
 
 NetworkProfileHandler::~NetworkProfileHandler() {
-  DBusThreadManager::Get()->GetShillManagerClient()->
-      RemovePropertyChangedObserver(this);
+  ShillManagerClient::Get()->RemovePropertyChangedObserver(this);
 }
 
 }  // namespace chromeos
diff --git a/chromeos/network/network_sms_handler.cc b/chromeos/network/network_sms_handler.cc
index 355e4b5..7ec3656 100644
--- a/chromeos/network/network_sms_handler.cc
+++ b/chromeos/network/network_sms_handler.cc
@@ -15,7 +15,6 @@
 #include "base/containers/circular_deque.h"
 #include "base/macros.h"
 #include "base/values.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/shill/gsm_sms_client.h"
 #include "chromeos/dbus/shill/modem_messaging_client.h"
 #include "chromeos/dbus/shill/shill_device_client.h"
@@ -88,13 +87,13 @@
       deleting_messages_(false),
       weak_ptr_factory_(this) {
   // Set the handler for received Sms messaages.
-  DBusThreadManager::Get()->GetGsmSMSClient()->SetSmsReceivedHandler(
+  GsmSMSClient::Get()->SetSmsReceivedHandler(
       service_name_, object_path_,
       base::Bind(&ModemManagerNetworkSmsDeviceHandler::SmsReceivedCallback,
                  weak_ptr_factory_.GetWeakPtr()));
 
   // List the existing messages.
-  DBusThreadManager::Get()->GetGsmSMSClient()->List(
+  GsmSMSClient::Get()->List(
       service_name_, object_path_,
       base::BindOnce(
           &NetworkSmsHandler::ModemManagerNetworkSmsDeviceHandler::ListCallback,
@@ -102,8 +101,7 @@
 }
 
 void NetworkSmsHandler::ModemManagerNetworkSmsDeviceHandler::RequestUpdate() {
-  DBusThreadManager::Get()->GetGsmSMSClient()->RequestUpdate(
-      service_name_, object_path_);
+  GsmSMSClient::Get()->RequestUpdate(service_name_, object_path_);
 }
 
 void NetworkSmsHandler::ModemManagerNetworkSmsDeviceHandler::ListCallback(
@@ -136,7 +134,7 @@
   deleting_messages_ = true;
   uint32_t index = delete_queue_.back();
   delete_queue_.pop_back();
-  DBusThreadManager::Get()->GetGsmSMSClient()->Delete(
+  GsmSMSClient::Get()->Delete(
       service_name_, object_path_, index,
       base::BindOnce(&NetworkSmsHandler::ModemManagerNetworkSmsDeviceHandler::
                          DeleteCallback,
@@ -155,7 +153,7 @@
   // Only handle complete messages.
   if (!complete)
     return;
-  DBusThreadManager::Get()->GetGsmSMSClient()->Get(
+  GsmSMSClient::Get()->Get(
       service_name_, object_path_, index,
       base::BindOnce(
           &NetworkSmsHandler::ModemManagerNetworkSmsDeviceHandler::GetCallback,
@@ -225,15 +223,14 @@
       retrieving_messages_(false),
       weak_ptr_factory_(this) {
   // Set the handler for received Sms messaages.
-  DBusThreadManager::Get()->GetModemMessagingClient()->SetSmsReceivedHandler(
+  ModemMessagingClient::Get()->SetSmsReceivedHandler(
       service_name_, object_path_,
-      base::Bind(
-          &NetworkSmsHandler::
-          ModemManager1NetworkSmsDeviceHandler::SmsReceivedCallback,
-          weak_ptr_factory_.GetWeakPtr()));
+      base::Bind(&NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler::
+                     SmsReceivedCallback,
+                 weak_ptr_factory_.GetWeakPtr()));
 
   // List the existing messages.
-  DBusThreadManager::Get()->GetModemMessagingClient()->List(
+  ModemMessagingClient::Get()->List(
       service_name_, object_path_,
       base::BindOnce(&NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler::
                          ListCallback,
@@ -243,7 +240,7 @@
 void NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler::RequestUpdate() {
   // Calling List using the service "AddSMS" causes the stub
   // implementation to deliver new sms messages.
-  DBusThreadManager::Get()->GetModemMessagingClient()->List(
+  ModemMessagingClient::Get()->List(
       std::string("AddSMS"), dbus::ObjectPath("/"),
       base::BindOnce(&NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler::
                          ListCallback,
@@ -277,7 +274,7 @@
   deleting_messages_ = true;
   dbus::ObjectPath sms_path = std::move(delete_queue_.back());
   delete_queue_.pop_back();
-  DBusThreadManager::Get()->GetModemMessagingClient()->Delete(
+  ModemMessagingClient::Get()->Delete(
       service_name_, object_path_, sms_path,
       base::BindOnce(&NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler::
                          DeleteCallback,
@@ -303,7 +300,7 @@
   retrieving_messages_ = true;
   dbus::ObjectPath sms_path = retrieval_queue_.front();
   retrieval_queue_.pop_front();
-  DBusThreadManager::Get()->GetSMSClient()->GetAll(
+  SMSClient::Get()->GetAll(
       service_name_, sms_path,
       base::BindOnce(
           &NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler::GetCallback,
@@ -358,17 +355,15 @@
 }
 
 NetworkSmsHandler::~NetworkSmsHandler() {
-  DBusThreadManager::Get()->GetShillManagerClient()->
-      RemovePropertyChangedObserver(this);
+  ShillManagerClient::Get()->RemovePropertyChangedObserver(this);
 }
 
 void NetworkSmsHandler::Init() {
   // Add as an observer here so that new devices added after this call are
   // recognized.
-  DBusThreadManager::Get()->GetShillManagerClient()->AddPropertyChangedObserver(
-      this);
+  ShillManagerClient::Get()->AddPropertyChangedObserver(this);
   // Request network manager properties so that we can get the list of devices.
-  DBusThreadManager::Get()->GetShillManagerClient()->GetProperties(
+  ShillManagerClient::Get()->GetProperties(
       base::Bind(&NetworkSmsHandler::ManagerPropertiesCallback,
                  weak_ptr_factory_.GetWeakPtr()));
 }
@@ -449,11 +444,10 @@
     if (!device_path.empty()) {
       // Request device properties.
       VLOG(1) << "GetDeviceProperties: " << device_path;
-      DBusThreadManager::Get()->GetShillDeviceClient()->GetProperties(
+      ShillDeviceClient::Get()->GetProperties(
           dbus::ObjectPath(device_path),
           base::Bind(&NetworkSmsHandler::DevicePropertiesCallback,
-                     weak_ptr_factory_.GetWeakPtr(),
-                     device_path));
+                     weak_ptr_factory_.GetWeakPtr(), device_path));
     }
   }
 }
diff --git a/chromeos/network/network_sms_handler_unittest.cc b/chromeos/network/network_sms_handler_unittest.cc
index d515c12..b1dd9b0 100644
--- a/chromeos/network/network_sms_handler_unittest.cc
+++ b/chromeos/network/network_sms_handler_unittest.cc
@@ -12,7 +12,7 @@
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
 #include "chromeos/constants/chromeos_switches.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/shill/shill_clients.h"
 #include "chromeos/dbus/shill/shill_device_client.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/cros_system_api/dbus/service_constants.h"
@@ -62,10 +62,9 @@
     base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
     command_line->AppendSwitch(chromeos::switches::kSmsTestMessages);
 
-    // Initialize DBusThreadManager with a stub implementation.
-    DBusThreadManager::Initialize();
+    shill_clients::InitializeFakes();
     ShillDeviceClient::TestInterface* device_test =
-        DBusThreadManager::Get()->GetShillDeviceClient()->GetTestInterface();
+        ShillDeviceClient::Get()->GetTestInterface();
     ASSERT_TRUE(device_test);
     device_test->AddDevice("/org/freedesktop/ModemManager1/stub/0",
                            shill::kTypeCellular,
@@ -86,7 +85,7 @@
   void TearDown() override {
     network_sms_handler_->RemoveObserver(test_observer_.get());
     network_sms_handler_.reset();
-    DBusThreadManager::Shutdown();
+    shill_clients::Shutdown();
   }
 
  protected:
diff --git a/chromeos/network/network_state_handler_unittest.cc b/chromeos/network/network_state_handler_unittest.cc
index b816a33..3989cc5e 100644
--- a/chromeos/network/network_state_handler_unittest.cc
+++ b/chromeos/network/network_state_handler_unittest.cc
@@ -21,7 +21,7 @@
 #include "base/test/scoped_task_environment.h"
 #include "base/values.h"
 #include "chromeos/constants/chromeos_switches.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/shill/shill_clients.h"
 #include "chromeos/dbus/shill/shill_device_client.h"
 #include "chromeos/dbus/shill/shill_ipconfig_client.h"
 #include "chromeos/dbus/shill/shill_manager_client.h"
@@ -101,11 +101,9 @@
 
   void NetworkListChanged() override {
     NetworkStateHandler::NetworkStateList networks;
-    handler_->GetNetworkListByType(chromeos::NetworkTypePattern::Default(),
-                                   false /* configured_only */,
-                                   false /* visible_only */,
-                                   0 /* no limit */,
-                                   &networks);
+    handler_->GetNetworkListByType(
+        chromeos::NetworkTypePattern::Default(), false /* configured_only */,
+        false /* visible_only */, 0 /* no limit */, &networks);
     network_count_ = networks.size();
     ++network_list_changed_count_;
   }
@@ -262,8 +260,7 @@
   ~NetworkStateHandlerTest() override = default;
 
   void SetUp() override {
-    // Initialize DBusThreadManager with a stub implementation.
-    DBusThreadManager::Initialize();
+    shill_clients::InitializeFakes();
     SetupDefaultShillState();
     network_state_handler_.reset(new NetworkStateHandler);
     test_observer_.reset(new TestObserver(network_state_handler_.get()));
@@ -278,7 +275,7 @@
     network_state_handler_->Shutdown();
     test_observer_.reset();
     network_state_handler_.reset();
-    DBusThreadManager::Shutdown();
+    shill_clients::Shutdown();
   }
 
  protected:
@@ -293,48 +290,32 @@
 
   void SetupDefaultShillState() {
     base::RunLoop().RunUntilIdle();  // Process any pending updates
-    device_test_ =
-        DBusThreadManager::Get()->GetShillDeviceClient()->GetTestInterface();
+    device_test_ = ShillDeviceClient::Get()->GetTestInterface();
     ASSERT_TRUE(device_test_);
     device_test_->ClearDevices();
-    device_test_->AddDevice(kShillManagerClientStubWifiDevice,
-                            shill::kTypeWifi, "stub_wifi_device1");
+    device_test_->AddDevice(kShillManagerClientStubWifiDevice, shill::kTypeWifi,
+                            "stub_wifi_device1");
     device_test_->AddDevice(kShillManagerClientStubCellularDevice,
                             shill::kTypeCellular, "stub_cellular_device1");
 
-    manager_test_ =
-        DBusThreadManager::Get()->GetShillManagerClient()->GetTestInterface();
+    manager_test_ = ShillManagerClient::Get()->GetTestInterface();
     ASSERT_TRUE(manager_test_);
 
-    profile_test_ =
-        DBusThreadManager::Get()->GetShillProfileClient()->GetTestInterface();
+    profile_test_ = ShillProfileClient::Get()->GetTestInterface();
     ASSERT_TRUE(profile_test_);
     profile_test_->ClearProfiles();
 
-    service_test_ =
-        DBusThreadManager::Get()->GetShillServiceClient()->GetTestInterface();
+    service_test_ = ShillServiceClient::Get()->GetTestInterface();
     ASSERT_TRUE(service_test_);
     service_test_->ClearServices();
-    AddService(kShillManagerClientStubDefaultService,
-               "eth1_guid",
-               "eth1",
-               shill::kTypeEthernet,
-               shill::kStateOnline);
-    AddService(kShillManagerClientStubDefaultWifi,
-               "wifi1_guid",
-               "wifi1",
-               shill::kTypeWifi,
-               shill::kStateOnline);
-    AddService(kShillManagerClientStubWifi2,
-               "wifi2_guid",
-               "wifi2",
-               shill::kTypeWifi,
-               shill::kStateIdle);
-    AddService(kShillManagerClientStubCellular,
-               "cellular1_guid",
-               "cellular1",
-               shill::kTypeCellular,
-               shill::kStateIdle);
+    AddService(kShillManagerClientStubDefaultService, "eth1_guid", "eth1",
+               shill::kTypeEthernet, shill::kStateOnline);
+    AddService(kShillManagerClientStubDefaultWifi, "wifi1_guid", "wifi1",
+               shill::kTypeWifi, shill::kStateOnline);
+    AddService(kShillManagerClientStubWifi2, "wifi2_guid", "wifi2",
+               shill::kTypeWifi, shill::kStateIdle);
+    AddService(kShillManagerClientStubCellular, "cellular1_guid", "cellular1",
+               shill::kTypeCellular, shill::kStateIdle);
   }
 
   void UpdateManagerProperties() { base::RunLoop().RunUntilIdle(); }
@@ -342,9 +323,9 @@
   void SetServiceProperty(const std::string& service_path,
                           const std::string& key,
                           const base::Value& value) {
-    DBusThreadManager::Get()->GetShillServiceClient()->SetProperty(
-        dbus::ObjectPath(service_path), key, value, base::DoNothing(),
-        base::Bind(&ErrorCallbackFunction));
+    ShillServiceClient::Get()->SetProperty(dbus::ObjectPath(service_path), key,
+                                           value, base::DoNothing(),
+                                           base::Bind(&ErrorCallbackFunction));
   }
 
   void SetProperties(NetworkState* network, const base::Value& properties) {
@@ -393,14 +374,17 @@
   EXPECT_EQ(kShillManagerClientStubDefaultService,
             network_state_handler_->DefaultNetwork()->path());
   EXPECT_EQ(kShillManagerClientStubDefaultService,
-            network_state_handler_->ConnectedNetworkByType(
-                NetworkTypePattern::Ethernet())->path());
-  EXPECT_EQ(kShillManagerClientStubDefaultWifi,
-            network_state_handler_->ConnectedNetworkByType(
-                NetworkTypePattern::WiFi())->path());
-  EXPECT_EQ(kShillManagerClientStubCellular,
-            network_state_handler_->FirstNetworkByType(
-                NetworkTypePattern::Mobile())->path());
+            network_state_handler_
+                ->ConnectedNetworkByType(NetworkTypePattern::Ethernet())
+                ->path());
+  EXPECT_EQ(
+      kShillManagerClientStubDefaultWifi,
+      network_state_handler_->ConnectedNetworkByType(NetworkTypePattern::WiFi())
+          ->path());
+  EXPECT_EQ(
+      kShillManagerClientStubCellular,
+      network_state_handler_->FirstNetworkByType(NetworkTypePattern::Mobile())
+          ->path());
   EXPECT_EQ(
       kShillManagerClientStubCellular,
       network_state_handler_->FirstNetworkByType(NetworkTypePattern::Cellular())
@@ -458,12 +442,9 @@
   // Add a non-visible network to the profile.
   const std::string profile = "/profile/profile1";
   const std::string wifi_favorite_path = "/service/wifi_faviorite";
-  service_test_->AddService(wifi_favorite_path,
-                            "wifi_faviorite_guid",
-                            "wifi_faviorite",
-                            shill::kTypeWifi,
-                            shill::kStateIdle,
-                            false /* add_to_visible */);
+  service_test_->AddService(wifi_favorite_path, "wifi_faviorite_guid",
+                            "wifi_faviorite", shill::kTypeWifi,
+                            shill::kStateIdle, false /* add_to_visible */);
   profile_test_->AddProfile(profile, "" /* userhash */);
   EXPECT_TRUE(profile_test_->AddService(profile, wifi_favorite_path));
   UpdateManagerProperties();
@@ -484,19 +465,15 @@
 
   // Get all networks.
   NetworkStateHandler::NetworkStateList networks;
-  network_state_handler_->GetNetworkListByType(NetworkTypePattern::Default(),
-                                               false /* configured_only */,
-                                               false /* visible_only */,
-                                               0 /* no limit */,
-                                               &networks);
+  network_state_handler_->GetNetworkListByType(
+      NetworkTypePattern::Default(), false /* configured_only */,
+      false /* visible_only */, 0 /* no limit */, &networks);
   EXPECT_EQ(kNumShillManagerClientStubImplServices + kNumTetherNetworks + 1,
             networks.size());
   // Limit number of results, including only Tether networks.
-  network_state_handler_->GetNetworkListByType(NetworkTypePattern::Default(),
-                                               false /* configured_only */,
-                                               false /* visible_only */,
-                                               2 /* limit */,
-                                               &networks);
+  network_state_handler_->GetNetworkListByType(
+      NetworkTypePattern::Default(), false /* configured_only */,
+      false /* visible_only */, 2 /* limit */, &networks);
   EXPECT_EQ(2u, networks.size());
   // Limit number of results, including more than only Tether networks.
   network_state_handler_->GetNetworkListByType(
@@ -509,29 +486,23 @@
       false /* visible_only */, 0 /* no limit */, &networks);
   EXPECT_EQ(2u, networks.size());
   // Get all wifi networks.
-  network_state_handler_->GetNetworkListByType(NetworkTypePattern::WiFi(),
-                                               false /* configured_only */,
-                                               false /* visible_only */,
-                                               0 /* no limit */,
-                                               &networks);
+  network_state_handler_->GetNetworkListByType(
+      NetworkTypePattern::WiFi(), false /* configured_only */,
+      false /* visible_only */, 0 /* no limit */, &networks);
   EXPECT_EQ(3u, networks.size());
   // Get visible networks.
-  network_state_handler_->GetNetworkListByType(NetworkTypePattern::Default(),
-                                               false /* configured_only */,
-                                               true /* visible_only */,
-                                               0 /* no limit */,
-                                               &networks);
+  network_state_handler_->GetNetworkListByType(
+      NetworkTypePattern::Default(), false /* configured_only */,
+      true /* visible_only */, 0 /* no limit */, &networks);
   EXPECT_EQ(kNumShillManagerClientStubImplServices + kNumTetherNetworks,
             networks.size());
   network_state_handler_->GetVisibleNetworkList(&networks);
   EXPECT_EQ(kNumShillManagerClientStubImplServices + kNumTetherNetworks,
             networks.size());
   // Get configured (profile) networks.
-  network_state_handler_->GetNetworkListByType(NetworkTypePattern::Default(),
-                                               true /* configured_only */,
-                                               false /* visible_only */,
-                                               0 /* no limit */,
-                                               &networks);
+  network_state_handler_->GetNetworkListByType(
+      NetworkTypePattern::Default(), true /* configured_only */,
+      false /* visible_only */, 0 /* no limit */, &networks);
   EXPECT_EQ(kNumTetherNetworks + 1u, networks.size());
 }
 
@@ -710,12 +681,12 @@
   // Set up two additional visible networks.
   const std::string wifi3 = "/service/wifi3";
   const std::string wifi4 = "/service/wifi4";
-  service_test_->SetServiceProperties(
-      wifi3, "wifi3_guid", "wifi3",
-      shill::kTypeWifi, shill::kStateIdle, true /* visible */);
-  service_test_->SetServiceProperties(
-      wifi4, "wifi4_guid", "wifi4",
-      shill::kTypeWifi, shill::kStateIdle, true /* visible */);
+  service_test_->SetServiceProperties(wifi3, "wifi3_guid", "wifi3",
+                                      shill::kTypeWifi, shill::kStateIdle,
+                                      true /* visible */);
+  service_test_->SetServiceProperties(wifi4, "wifi4_guid", "wifi4",
+                                      shill::kTypeWifi, shill::kStateIdle,
+                                      true /* visible */);
   // Add the services to the Manager. Only notify when the second service is
   // added.
   manager_test_->AddManagerService(wifi3, false);
@@ -736,12 +707,9 @@
   // Add a non-visible network to the profile.
   const std::string profile = "/profile/profile1";
   const std::string wifi_favorite_path = "/service/wifi_faviorite";
-  service_test_->AddService(wifi_favorite_path,
-                            "wifi_faviorite_guid",
-                            "wifi_faviorite",
-                            shill::kTypeWifi,
-                            shill::kStateIdle,
-                            false /* add_to_visible */);
+  service_test_->AddService(wifi_favorite_path, "wifi_faviorite_guid",
+                            "wifi_faviorite", shill::kTypeWifi,
+                            shill::kStateIdle, false /* add_to_visible */);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(kNumShillManagerClientStubImplServices + 1,
             test_observer_->network_count());
@@ -1601,7 +1569,7 @@
   // Change a network state.
   base::Value connection_state_idle_value(shill::kStateIdle);
   service_test_->SetServiceProperty(eth1, shill::kStateProperty,
-                                   connection_state_idle_value);
+                                    connection_state_idle_value);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(shill::kStateIdle,
             test_observer_->NetworkConnectionStateForService(eth1));
@@ -1609,7 +1577,7 @@
   // Confirm that changing the connection state to the same value does *not*
   // signal the observer.
   service_test_->SetServiceProperty(eth1, shill::kStateProperty,
-                                   connection_state_idle_value);
+                                    connection_state_idle_value);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1, test_observer_->ConnectionStateChangesForService(eth1));
 }
@@ -1821,12 +1789,12 @@
 TEST_F(NetworkStateHandlerTest, RequestUpdate) {
   // Request an update for kShillManagerClientStubDefaultWifi.
   EXPECT_EQ(1, test_observer_->PropertyUpdatesForService(
-      kShillManagerClientStubDefaultWifi));
+                   kShillManagerClientStubDefaultWifi));
   network_state_handler_->RequestUpdateForNetwork(
       kShillManagerClientStubDefaultWifi);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(2, test_observer_->PropertyUpdatesForService(
-      kShillManagerClientStubDefaultWifi));
+                   kShillManagerClientStubDefaultWifi));
 }
 
 TEST_F(NetworkStateHandlerTest, RequestScan) {
@@ -1938,13 +1906,13 @@
 TEST_F(NetworkStateHandlerTest, IPConfigChanged) {
   test_observer_->reset_updates();
   EXPECT_EQ(0, test_observer_->PropertyUpdatesForDevice(
-      kShillManagerClientStubWifiDevice));
+                   kShillManagerClientStubWifiDevice));
   EXPECT_EQ(0, test_observer_->PropertyUpdatesForService(
-      kShillManagerClientStubDefaultWifi));
+                   kShillManagerClientStubDefaultWifi));
 
   // Change IPConfigs property.
   ShillIPConfigClient::TestInterface* ip_config_test =
-      DBusThreadManager::Get()->GetShillIPConfigClient()->GetTestInterface();
+      ShillIPConfigClient::Get()->GetTestInterface();
   const std::string kIPConfigPath = "test_ip_config";
   base::DictionaryValue ip_config_properties;
   ip_config_test->AddIPConfig(kIPConfigPath, ip_config_properties);
@@ -1958,9 +1926,9 @@
                                     base::Value(kIPConfigPath));
   UpdateManagerProperties();
   EXPECT_EQ(1, test_observer_->PropertyUpdatesForDevice(
-      kShillManagerClientStubWifiDevice));
+                   kShillManagerClientStubWifiDevice));
   EXPECT_EQ(1, test_observer_->PropertyUpdatesForService(
-      kShillManagerClientStubDefaultWifi));
+                   kShillManagerClientStubDefaultWifi));
 }
 
 TEST_F(NetworkStateHandlerTest, UpdateGuid) {
diff --git a/chromeos/network/network_state_test_helper.cc b/chromeos/network/network_state_test_helper.cc
index 8246d8d..f96e80d 100644
--- a/chromeos/network/network_state_test_helper.cc
+++ b/chromeos/network/network_state_test_helper.cc
@@ -7,7 +7,7 @@
 #include "base/bind.h"
 #include "base/json/json_reader.h"
 #include "base/run_loop.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/shill/shill_clients.h"
 #include "chromeos/network/network_state_handler.h"
 #include "chromeos/network/onc/onc_utils.h"
 #include "dbus/object_path.h"
@@ -28,20 +28,14 @@
 NetworkStateTestHelper::NetworkStateTestHelper(
     bool use_default_devices_and_services)
     : weak_ptr_factory_(this) {
-  if (!DBusThreadManager::IsInitialized()) {
-    DBusThreadManager::Initialize();
-    dbus_thread_manager_initialized_ = true;
+  if (!ShillManagerClient::Get()) {
+    shill_clients::InitializeFakes();
+    shill_clients_initialized_ = true;
   }
-  DBusThreadManager* dbus_thread_manager = DBusThreadManager::Get();
-
-  manager_test_ =
-      dbus_thread_manager->GetShillManagerClient()->GetTestInterface();
-  profile_test_ =
-      dbus_thread_manager->GetShillProfileClient()->GetTestInterface();
-  device_test_ =
-      dbus_thread_manager->GetShillDeviceClient()->GetTestInterface();
-  service_test_ =
-      dbus_thread_manager->GetShillServiceClient()->GetTestInterface();
+  manager_test_ = ShillManagerClient::Get()->GetTestInterface();
+  profile_test_ = ShillProfileClient::Get()->GetTestInterface();
+  device_test_ = ShillDeviceClient::Get()->GetTestInterface();
+  service_test_ = ShillServiceClient::Get()->GetTestInterface();
 
   profile_test_->AddProfile("shared_profile_path",
                             std::string() /* shared profile */);
@@ -56,8 +50,8 @@
 
 NetworkStateTestHelper::~NetworkStateTestHelper() {
   ShutdownNetworkState();
-  if (dbus_thread_manager_initialized_)
-    DBusThreadManager::Shutdown();
+  if (shill_clients_initialized_)
+    shill_clients::Shutdown();
 }
 
 void NetworkStateTestHelper::ShutdownNetworkState() {
@@ -107,7 +101,7 @@
   // |last_created_service_path| will be set before it is returned. In
   // error cases, ConfigureCallback() will not run, resulting in "" being
   // returned from this function.
-  DBusThreadManager::Get()->GetShillManagerClient()->ConfigureService(
+  ShillManagerClient::Get()->ConfigureService(
       *shill_json_dict,
       base::Bind(&NetworkStateTestHelper::ConfigureCallback,
                  weak_ptr_factory_.GetWeakPtr()),
diff --git a/chromeos/network/network_state_test_helper.h b/chromeos/network/network_state_test_helper.h
index 0036865..0f22c197 100644
--- a/chromeos/network/network_state_test_helper.h
+++ b/chromeos/network/network_state_test_helper.h
@@ -78,7 +78,7 @@
  private:
   void ConfigureCallback(const dbus::ObjectPath& result);
 
-  bool dbus_thread_manager_initialized_ = false;
+  bool shill_clients_initialized_ = false;
   std::string last_created_service_path_;
 
   ShillManagerClient::TestInterface* manager_test_;
diff --git a/chromeos/network/policy_applicator.cc b/chromeos/network/policy_applicator.cc
index 0f98682..748cda1 100644
--- a/chromeos/network/policy_applicator.cc
+++ b/chromeos/network/policy_applicator.cc
@@ -12,7 +12,6 @@
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/shill/shill_profile_client.h"
 #include "chromeos/network/network_type_pattern.h"
 #include "chromeos/network/network_ui_data.h"
@@ -69,7 +68,7 @@
 }
 
 void PolicyApplicator::Run() {
-  DBusThreadManager::Get()->GetShillProfileClient()->GetProperties(
+  ShillProfileClient::Get()->GetProperties(
       dbus::ObjectPath(profile_.path),
       base::Bind(&PolicyApplicator::GetProfilePropertiesCallback,
                  weak_ptr_factory_.GetWeakPtr()),
@@ -110,15 +109,12 @@
       continue;
 
     pending_get_entry_calls_.insert(entry);
-    DBusThreadManager::Get()->GetShillProfileClient()->GetEntry(
-        dbus::ObjectPath(profile_.path),
-        entry,
+    ShillProfileClient::Get()->GetEntry(
+        dbus::ObjectPath(profile_.path), entry,
         base::Bind(&PolicyApplicator::GetEntryCallback,
-                   weak_ptr_factory_.GetWeakPtr(),
-                   entry),
+                   weak_ptr_factory_.GetWeakPtr(), entry),
         base::Bind(&PolicyApplicator::GetEntryError,
-                   weak_ptr_factory_.GetWeakPtr(),
-                   entry));
+                   weak_ptr_factory_.GetWeakPtr(), entry));
   }
   if (pending_get_entry_calls_.empty()) {
     ApplyRemainingPolicies();
@@ -276,7 +272,7 @@
 }
 
 void PolicyApplicator::DeleteEntry(const std::string& entry) {
-  DBusThreadManager::Get()->GetShillProfileClient()->DeleteEntry(
+  ShillProfileClient::Get()->DeleteEntry(
       dbus::ObjectPath(profile_.path), entry, base::DoNothing(),
       base::Bind(&LogErrorMessage, FROM_HERE));
 }
diff --git a/chromeos/network/proxy/proxy_config_handler.cc b/chromeos/network/proxy/proxy_config_handler.cc
index 71b73545..aeeff88 100644
--- a/chromeos/network/proxy/proxy_config_handler.cc
+++ b/chromeos/network/proxy/proxy_config_handler.cc
@@ -11,7 +11,6 @@
 #include "base/json/json_writer.h"
 #include "base/logging.h"
 #include "base/values.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/shill/shill_service_client.h"
 #include "chromeos/network/network_handler_callbacks.h"
 #include "chromeos/network/network_profile.h"
@@ -96,16 +95,13 @@
 
 void SetProxyConfigForNetwork(const ProxyConfigDictionary& proxy_config,
                               const NetworkState& network) {
-  chromeos::ShillServiceClient* shill_service_client =
-      DBusThreadManager::Get()->GetShillServiceClient();
-
   // The user's proxy setting is not stored in the Chrome preference yet. We
   // still rely on Shill storing it.
   ProxyPrefs::ProxyMode mode;
   if (!proxy_config.GetMode(&mode) || mode == ProxyPrefs::MODE_DIRECT) {
     // Return empty string for direct mode for portal check to work correctly.
     // TODO(pneubeck): Consider removing this legacy code.
-    shill_service_client->ClearProperty(
+    ShillServiceClient::Get()->ClearProperty(
         dbus::ObjectPath(network.path()), shill::kProxyConfigProperty,
         base::Bind(&NotifyNetworkStateHandler, network.path()),
         base::Bind(&network_handler::ShillErrorCallbackFunction,
@@ -114,7 +110,7 @@
   } else {
     std::string proxy_config_str;
     base::JSONWriter::Write(proxy_config.GetDictionary(), &proxy_config_str);
-    shill_service_client->SetProperty(
+    ShillServiceClient::Get()->SetProperty(
         dbus::ObjectPath(network.path()), shill::kProxyConfigProperty,
         base::Value(proxy_config_str),
         base::Bind(&NotifyNetworkStateHandler, network.path()),
diff --git a/chromeos/network/proxy/proxy_config_service_impl_unittest.cc b/chromeos/network/proxy/proxy_config_service_impl_unittest.cc
index 646128a..ae0d0e7 100644
--- a/chromeos/network/proxy/proxy_config_service_impl_unittest.cc
+++ b/chromeos/network/proxy/proxy_config_service_impl_unittest.cc
@@ -9,7 +9,7 @@
 #include "base/test/scoped_task_environment.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/values.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/shill/shill_clients.h"
 #include "chromeos/network/network_handler.h"
 #include "components/prefs/pref_service.h"
 #include "components/prefs/testing_pref_service.h"
@@ -48,14 +48,14 @@
 
 class ProxyConfigServiceImplTest : public testing::Test {
   void SetUp() override {
-    DBusThreadManager::Initialize();
+    shill_clients::InitializeFakes();
     chromeos::NetworkHandler::Initialize();
     base::RunLoop().RunUntilIdle();
   }
 
   void TearDown() override {
     chromeos::NetworkHandler::Shutdown();
-    DBusThreadManager::Shutdown();
+    shill_clients::Shutdown();
   }
 
  protected:
diff --git a/chromeos/network/proxy/ui_proxy_config_service_unittest.cc b/chromeos/network/proxy/ui_proxy_config_service_unittest.cc
index 4339901..c6675a0a 100644
--- a/chromeos/network/proxy/ui_proxy_config_service_unittest.cc
+++ b/chromeos/network/proxy/ui_proxy_config_service_unittest.cc
@@ -13,7 +13,7 @@
 #include "base/strings/string_util.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/values.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/shill/shill_clients.h"
 #include "chromeos/dbus/shill/shill_manager_client.h"
 #include "chromeos/network/network_handler.h"
 #include "chromeos/network/onc/onc_utils.h"
@@ -107,7 +107,7 @@
   }
 
   void SetUp() override {
-    DBusThreadManager::Initialize();
+    shill_clients::InitializeFakes();
     NetworkHandler::Initialize();
     ConfigureService(kTestUserWifiConfig);
     ConfigureService(kTestSharedWifiConfig);
@@ -116,7 +116,7 @@
 
   void TearDown() override {
     NetworkHandler::Shutdown();
-    DBusThreadManager::Shutdown();
+    shill_clients::Shutdown();
   }
 
   ~UIProxyConfigServiceTest() override = default;
@@ -126,7 +126,7 @@
         base::DictionaryValue::From(
             onc::ReadDictionaryFromJson(shill_json_string));
     ASSERT_TRUE(shill_json_dict);
-    DBusThreadManager::Get()->GetShillManagerClient()->ConfigureService(
+    ShillManagerClient::Get()->ConfigureService(
         *shill_json_dict, base::DoNothing(),
         base::Bind([](const std::string& name, const std::string& msg) {}));
     base::RunLoop().RunUntilIdle();
diff --git a/chromeos/network/shill_property_handler.cc b/chromeos/network/shill_property_handler.cc
index 4478a85..208db9f 100644
--- a/chromeos/network/shill_property_handler.cc
+++ b/chromeos/network/shill_property_handler.cc
@@ -15,7 +15,6 @@
 #include "base/macros.h"
 #include "base/strings/string_util.h"
 #include "base/values.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/shill/shill_device_client.h"
 #include "chromeos/dbus/shill/shill_ipconfig_client.h"
 #include "chromeos/dbus/shill/shill_manager_client.h"
@@ -63,14 +62,12 @@
       : type_(type), path_(path), handler_(handler) {
     if (type_ == ManagedState::MANAGED_TYPE_NETWORK) {
       DVLOG(2) << "ShillPropertyObserver: Network: " << path;
-      DBusThreadManager::Get()
-          ->GetShillServiceClient()
-          ->AddPropertyChangedObserver(dbus::ObjectPath(path_), this);
+      ShillServiceClient::Get()->AddPropertyChangedObserver(
+          dbus::ObjectPath(path_), this);
     } else if (type_ == ManagedState::MANAGED_TYPE_DEVICE) {
       DVLOG(2) << "ShillPropertyObserver: Device: " << path;
-      DBusThreadManager::Get()
-          ->GetShillDeviceClient()
-          ->AddPropertyChangedObserver(dbus::ObjectPath(path_), this);
+      ShillDeviceClient::Get()->AddPropertyChangedObserver(
+          dbus::ObjectPath(path_), this);
     } else {
       NOTREACHED();
     }
@@ -78,13 +75,11 @@
 
   ~ShillPropertyObserver() override {
     if (type_ == ManagedState::MANAGED_TYPE_NETWORK) {
-      DBusThreadManager::Get()
-          ->GetShillServiceClient()
-          ->RemovePropertyChangedObserver(dbus::ObjectPath(path_), this);
+      ShillServiceClient::Get()->RemovePropertyChangedObserver(
+          dbus::ObjectPath(path_), this);
     } else if (type_ == ManagedState::MANAGED_TYPE_DEVICE) {
-      DBusThreadManager::Get()
-          ->GetShillDeviceClient()
-          ->RemovePropertyChangedObserver(dbus::ObjectPath(path_), this);
+      ShillDeviceClient::Get()->RemovePropertyChangedObserver(
+          dbus::ObjectPath(path_), this);
     } else {
       NOTREACHED();
     }
@@ -108,12 +103,11 @@
 // ShillPropertyHandler
 
 ShillPropertyHandler::ShillPropertyHandler(Listener* listener)
-    : listener_(listener),
-      shill_manager_(DBusThreadManager::Get()->GetShillManagerClient()) {}
+    : listener_(listener), shill_manager_(ShillManagerClient::Get()) {}
 
 ShillPropertyHandler::~ShillPropertyHandler() {
   // Delete network service observers.
-  CHECK(shill_manager_ == DBusThreadManager::Get()->GetShillManagerClient());
+  CHECK(shill_manager_ == ShillManagerClient::Get());
   shill_manager_->RemovePropertyChangedObserver(this);
 }
 
@@ -278,12 +272,12 @@
                  << " For: " << path;
   pending_updates_[type].insert(path);
   if (type == ManagedState::MANAGED_TYPE_NETWORK) {
-    DBusThreadManager::Get()->GetShillServiceClient()->GetProperties(
+    ShillServiceClient::Get()->GetProperties(
         dbus::ObjectPath(path),
         base::Bind(&ShillPropertyHandler::GetPropertiesCallback, AsWeakPtr(),
                    type, path));
   } else if (type == ManagedState::MANAGED_TYPE_DEVICE) {
-    DBusThreadManager::Get()->GetShillDeviceClient()->GetProperties(
+    ShillDeviceClient::Get()->GetProperties(
         dbus::ObjectPath(path),
         base::Bind(&ShillPropertyHandler::GetPropertiesCallback, AsWeakPtr(),
                    type, path));
@@ -564,7 +558,7 @@
     NET_LOG(ERROR) << "Invalid IPConfig: " << path;
     return;
   }
-  DBusThreadManager::Get()->GetShillIPConfigClient()->GetProperties(
+  ShillIPConfigClient::Get()->GetProperties(
       dbus::ObjectPath(ip_config_path),
       base::Bind(&ShillPropertyHandler::GetIPConfigCallback, AsWeakPtr(), type,
                  path, ip_config_path));
diff --git a/chromeos/network/shill_property_handler_unittest.cc b/chromeos/network/shill_property_handler_unittest.cc
index d4f77717..78b5fce 100644
--- a/chromeos/network/shill_property_handler_unittest.cc
+++ b/chromeos/network/shill_property_handler_unittest.cc
@@ -17,7 +17,7 @@
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/values.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/shill/shill_clients.h"
 #include "chromeos/dbus/shill/shill_device_client.h"
 #include "chromeos/dbus/shill/shill_ipconfig_client.h"
 #include "chromeos/dbus/shill/shill_manager_client.h"
@@ -168,21 +168,16 @@
   ~ShillPropertyHandlerTest() override = default;
 
   void SetUp() override {
-    // Initialize DBusThreadManager with a stub implementation.
-    DBusThreadManager::Initialize();
+    shill_clients::InitializeFakes();
     // Get the test interface for manager / device / service and clear the
     // default stub properties.
-    manager_test_ =
-        DBusThreadManager::Get()->GetShillManagerClient()->GetTestInterface();
+    manager_test_ = ShillManagerClient::Get()->GetTestInterface();
     ASSERT_TRUE(manager_test_);
-    device_test_ =
-        DBusThreadManager::Get()->GetShillDeviceClient()->GetTestInterface();
+    device_test_ = ShillDeviceClient::Get()->GetTestInterface();
     ASSERT_TRUE(device_test_);
-    service_test_ =
-        DBusThreadManager::Get()->GetShillServiceClient()->GetTestInterface();
+    service_test_ = ShillServiceClient::Get()->GetTestInterface();
     ASSERT_TRUE(service_test_);
-    profile_test_ =
-        DBusThreadManager::Get()->GetShillProfileClient()->GetTestInterface();
+    profile_test_ = ShillProfileClient::Get()->GetTestInterface();
     ASSERT_TRUE(profile_test_);
     SetupShillPropertyHandler();
     base::RunLoop().RunUntilIdle();
@@ -191,7 +186,7 @@
   void TearDown() override {
     shill_property_handler_.reset();
     listener_.reset();
-    DBusThreadManager::Shutdown();
+    shill_clients::Shutdown();
   }
 
   void AddDevice(const std::string& type, const std::string& id) {
@@ -328,7 +323,7 @@
 
   // Enable the technology.
   listener_->reset_list_updates();
-  DBusThreadManager::Get()->GetShillManagerClient()->EnableTechnology(
+  ShillManagerClient::Get()->EnableTechnology(
       shill::kTypeWifi, base::DoNothing(), base::Bind(&ErrorCallbackFunction));
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1, listener_->technology_list_updates());
@@ -380,7 +375,7 @@
       shill::kServiceCompleteListProperty)[kTestServicePath]);
   // Change a property.
   base::Value scan_interval(3);
-  DBusThreadManager::Get()->GetShillServiceClient()->SetProperty(
+  ShillServiceClient::Get()->SetProperty(
       dbus::ObjectPath(kTestServicePath), shill::kScanIntervalProperty,
       scan_interval, base::DoNothing(), base::Bind(&ErrorCallbackFunction));
   base::RunLoop().RunUntilIdle();
@@ -391,7 +386,7 @@
   // Set the state of the service to Connected. This will trigger a service list
   // update.
   listener_->reset_list_updates();
-  DBusThreadManager::Get()->GetShillServiceClient()->SetProperty(
+  ShillServiceClient::Get()->SetProperty(
       dbus::ObjectPath(kTestServicePath), shill::kStateProperty,
       base::Value(shill::kStateReady), base::DoNothing(),
       base::Bind(&ErrorCallbackFunction));
@@ -415,23 +410,23 @@
   const std::string kTestIPConfigPath("test_ip_config_path");
 
   base::Value ip_address("192.168.1.1");
-  DBusThreadManager::Get()->GetShillIPConfigClient()->SetProperty(
-      dbus::ObjectPath(kTestIPConfigPath), shill::kAddressProperty, ip_address,
-      EmptyVoidDBusMethodCallback());
+  ShillIPConfigClient::Get()->SetProperty(dbus::ObjectPath(kTestIPConfigPath),
+                                          shill::kAddressProperty, ip_address,
+                                          EmptyVoidDBusMethodCallback());
   base::ListValue dns_servers;
   dns_servers.AppendString("192.168.1.100");
   dns_servers.AppendString("192.168.1.101");
-  DBusThreadManager::Get()->GetShillIPConfigClient()->SetProperty(
+  ShillIPConfigClient::Get()->SetProperty(
       dbus::ObjectPath(kTestIPConfigPath), shill::kNameServersProperty,
       dns_servers, EmptyVoidDBusMethodCallback());
   base::Value prefixlen(8);
-  DBusThreadManager::Get()->GetShillIPConfigClient()->SetProperty(
-      dbus::ObjectPath(kTestIPConfigPath), shill::kPrefixlenProperty, prefixlen,
-      EmptyVoidDBusMethodCallback());
+  ShillIPConfigClient::Get()->SetProperty(dbus::ObjectPath(kTestIPConfigPath),
+                                          shill::kPrefixlenProperty, prefixlen,
+                                          EmptyVoidDBusMethodCallback());
   base::Value gateway("192.0.0.1");
-  DBusThreadManager::Get()->GetShillIPConfigClient()->SetProperty(
-      dbus::ObjectPath(kTestIPConfigPath), shill::kGatewayProperty, gateway,
-      EmptyVoidDBusMethodCallback());
+  ShillIPConfigClient::Get()->SetProperty(dbus::ObjectPath(kTestIPConfigPath),
+                                          shill::kGatewayProperty, gateway,
+                                          EmptyVoidDBusMethodCallback());
   base::RunLoop().RunUntilIdle();
 
   // Add a service with an empty ipconfig and then update
@@ -442,7 +437,7 @@
   // This is the initial property update.
   EXPECT_EQ(1, listener_->initial_property_updates(
       shill::kServiceCompleteListProperty)[kTestServicePath1]);
-  DBusThreadManager::Get()->GetShillServiceClient()->SetProperty(
+  ShillServiceClient::Get()->SetProperty(
       dbus::ObjectPath(kTestServicePath1), shill::kIPConfigProperty,
       base::Value(kTestIPConfigPath), base::DoNothing(),
       base::Bind(&ErrorCallbackFunction));
diff --git a/components/README b/components/README
deleted file mode 100644
index a5053b2c..0000000
--- a/components/README
+++ /dev/null
@@ -1,56 +0,0 @@
-This directory is for features that are intended for reuse. Example use cases:
-- features that are shared by Chrome on iOS and other Chrome platforms (since
-  the iOS port doesn't use src/chrome)
-- features that are shared between multiple embedders of content (e.g., Android
-  WebView and Chrome)
-- features that are shared between Blink and the browser process
-  * Note: It is also possible to place code shared between Blink and the
-    browser process into //third_party/blink/common. The distinction comes
-    down to (a) whether Blink is the owner of the code in question or a consumer
-    of it and (b) whether the code in question is shared by Chrome on iOS as
-    well. If the code is conceptually its own cross-process feature with Blink
-    as a consumer, then //components can make sense. If it's conceptually
-    Blink code, then third_party/blink/common likely makes more sense. (In the
-    so-far hypothetical case where it's conceptually Blink code that is shared
-    by iOS, raise the question on chromium-dev@, where the right folks will see
-    it).
-
-In general, if some code is used by a directory "foo" and things above "foo" in
-the dependency tree, the code should probably live in "foo".
-
-By default, components can depend only on the lower layers of the Chromium
-codebase (e.g. base/, net/, etc.). Individual components may additionally allow
-dependencies on the content API and IPC; however, if such a component is used
-by Chrome for iOS (which does not use the content API or IPC), the component
-will have to be in the form of a layered component
-(http://www.chromium.org/developers/design-documents/layered-components-design).
-
-Components that have bits of code that need to live in different
-processes (e.g. some code in the browser process, some in the renderer
-process, etc.) should separate the code into different subdirectories.
-Hence for a component named 'foo' you might end up with a structure
-like the following (assuming that foo is not used by iOS and thus does not
-need to be a layered component):
-
-components/foo          - DEPS, OWNERS, foo.gypi
-components/foo/browser  - code that needs the browser process
-components/foo/common   - for e.g. IPC constants and such
-components/foo/renderer - code that needs renderer process
-
-These subdirectories should have DEPS files with the relevant
-restrictions in place, i.e. only components/*/browser should
-be allowed to #include from content/public/browser.
-
-Note that there may also be an 'android' subdir, with a Java source
-code structure underneath it where the package name is
-org.chromium.components.foo, and with subdirs after 'foo'
-to illustrate process, e.g. 'browser' or 'renderer':
-
-components/foo/android/OWNERS, DEPS
-components/foo/android/java/src/org/chromium/components/foo/browser/
-components/foo/android/javatests/src/org/chromium/components/foo/browser/
-
-Code in a component should be placed in a namespace corresponding to
-the name of the component; e.g. for a component living in
-//components/foo, code in that component should be in the foo::
-namespace.
diff --git a/components/README.md b/components/README.md
new file mode 100644
index 0000000..ea3b0eb
--- /dev/null
+++ b/components/README.md
@@ -0,0 +1,104 @@
+# About //components
+
+This directory is meant to house features or subsystems that are used in more
+than one part of the Chromium codebase.
+
+## Example use cases:
+
+  * Features that are shared by Chrome on iOS (`//ios/chrome`) and Chrome on
+    other platforms (`//chrome`).
+      * Note: `//ios` doesn't depend on `//chrome`.
+  * Features that are shared between multiple embedders of content. For example,
+    `//chrome` and `//android_webview`.
+  * Features that are shared between Blink and the browser process.
+      * Note: It is also possible to place code shared between Blink and the
+        browser process into `//third_party/blink/common`. The distinction comes
+        down to (a) whether Blink is the owner of the code in question or a
+        consumer of it and (b) whether the code in question is shared by Chrome
+        on iOS as well. If the code is conceptually its own cross-process
+        feature with Blink as a consumer, then `//components` can make sense. If
+        it's conceptually Blink code, then `//third_party/blink/common` likely
+        makes more sense. (In the so-far hypothetical case where it's
+        conceptually Blink code that is shared by iOS, raise the question on
+        chromium-dev@, where the right folks will see it).
+
+## Guidelines for adding a new component
+
+  * You will be added to an OWNERS file under `//components/{your component}`
+    and be responsible for maintaining your addition.
+  * A `//components/OWNER` must approve of the location of your code.
+  * Code must be needed in at least 2 places in Chrome that don't have a "higher
+    layered" directory that could facilitate sharing (e.g. `//content/common`,
+    `//chrome/utility`, etc.).
+
+## Dependencies of a component
+
+Components **cannot** depend on the higher layers of the Chromium codebase:
+
+  * `//android_webview`
+  * `//chrome`
+  * `//chromecast`
+  * `//headless`
+  * `//ios/chrome`
+  * `//content/shell`
+
+Components **can** depend on the lower layers of the Chromium codebase:
+
+  * `//base`
+  * `//gpu`
+  * `//mojo`
+  * `//net`
+  * `//ui`
+
+Components **can** depend on each other. This must be made explicit in the
+`DEPS` file of the component.
+
+Components **can** depend on `//content/public` and `//ipc`. This must be made
+explicit in the `DEPS` file of the component. If such a component is used by
+Chrome for iOS (which does not use content or IPC), the component will have to
+be in the form of a [layered
+component](http://www.chromium.org/developers/design-documents/layered-components-design).
+
+`//chrome`, `//ios/chrome`, `//content` and `//ios/web` **can** depend on
+individual components. The dependency might have to be made explicit in the
+`DEPS` file of the higher layer (e.g. in `//content/browser/DEPS`). Circular
+dependencies are not allowed: if `//content` depends on a component, then that
+component cannot depend on  `//content/public`, directly or indirectly.
+
+## Structure of a component
+
+As mentioned above, components that depend on `//content/public` or `//ipc`
+might have to be in the form of a [layered
+component](http://www.chromium.org/developers/design-documents/layered-components-design).
+
+Components that have bits of code that need to live in different processes (e.g.
+some code in the browser process, some in the renderer process, etc.) should
+separate the code into different subdirectories. Hence for a component named
+'foo' you might end up with a structure like the following (assuming that foo is
+not used by iOS and thus does not need to be a layered component):
+
+  * `components/foo`          - DEPS, OWNERS, BUILD.gn
+  * `components/foo/browser`  - code that needs the browser process
+  * `components/foo/common`   - for e.g. Mojo interfaces and such
+  * `components/foo/renderer` - code that needs renderer process
+
+These subdirectories should have DEPS files with the relevant restrictions in
+place, i.e. only `components/foo/browser` should be allowed to #include from
+`content/public/browser`.
+
+Note that there may also be an `android` subdir, with a Java source code
+structure underneath it where the package name is org.chromium.components.foo,
+and with subdirs after 'foo' to illustrate process, e.g. 'browser' or
+'renderer':
+
+  * `components/foo/android/OWNERS`, `DEPS`
+  * `components/foo/android/java/src/org/chromium/components/foo/browser/`
+  * `components/foo/android/javatests/src/org/chromium/components/foo/browser/`
+
+Code in a component should be placed in a namespace corresponding to the name of
+the component; e.g. for a component living in `//components/foo`, code in that
+component should be in the `foo::` namespace.
+
+## How does this differ from //base/util?
+
+See the explanation in [//base/util/README.md](https://chromium.googlesource.com/chromium/src/+/HEAD/base/util/README.md#how-does-this-differ-from-components).
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index 406d655..d8d463f 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -487,7 +487,6 @@
   testonly = true
   sources = [
     "address_combobox_model_unittest.cc",
-    "address_contact_form_label_formatter_unittest.cc",
     "address_email_form_label_formatter_unittest.cc",
     "address_field_unittest.cc",
     "address_form_label_formatter_unittest.cc",
diff --git a/components/autofill/core/browser/label_formatter.cc b/components/autofill/core/browser/label_formatter.cc
index 263df91..689e250 100644
--- a/components/autofill/core/browser/label_formatter.cc
+++ b/components/autofill/core/browser/label_formatter.cc
@@ -76,8 +76,7 @@
 
   switch (groups) {
     case kName | kAddress | kEmail | kPhone:
-      return std::make_unique<AddressContactFormLabelFormatter>(
-          app_locale, focused_field_type, field_types);
+      return nullptr;
     case kName | kAddress | kPhone:
       return std::make_unique<AddressPhoneFormLabelFormatter>(
           app_locale, focused_field_type, field_types);
diff --git a/components/autofill/core/browser/personal_data_manager.cc b/components/autofill/core/browser/personal_data_manager.cc
index 657e7864..7c1dab2 100644
--- a/components/autofill/core/browser/personal_data_manager.cc
+++ b/components/autofill/core/browser/personal_data_manager.cc
@@ -36,6 +36,8 @@
 #include "components/autofill/core/browser/country_data.h"
 #include "components/autofill/core/browser/country_names.h"
 #include "components/autofill/core/browser/form_structure.h"
+#include "components/autofill/core/browser/label_formatter.h"
+#include "components/autofill/core/browser/label_formatter_utils.h"
 #include "components/autofill/core/browser/personal_data_manager_observer.h"
 #include "components/autofill/core/browser/phone_number.h"
 #include "components/autofill/core/browser/phone_number_i18n.h"
@@ -1160,20 +1162,23 @@
                                                  matched_profiles, suggestions,
                                                  &unique_matched_profiles);
 
+  std::unique_ptr<LabelFormatter> formatter =
+      base::FeatureList::IsEnabled(
+          autofill::features::kAutofillUseImprovedLabelDisambiguation)
+          ? LabelFormatter::Create(app_locale_, type.GetStorableType(),
+                                   field_types)
+          : nullptr;
+
   // Generate disambiguating labels based on the list of matches.
   std::vector<base::string16> labels;
-  AutofillProfile::CreateInferredLabels(unique_matched_profiles, &field_types,
-                                        type.GetStorableType(), 1, app_locale_,
-                                        &labels);
-  DCHECK_EQ(unique_suggestions.size(), labels.size());
-  for (size_t i = 0; i < labels.size(); i++) {
-    // A suggestion's label has one line of disambiguating information to show
-    // to the user. However, when the two-line suggestion display experiment is
-    // enabled on desktop, label is replaced by additional label.
-    unique_suggestions[i].label = labels[i];
-    unique_suggestions[i].additional_label = labels[i];
+  if (formatter) {
+    labels = formatter->GetLabels(unique_matched_profiles);
+  } else {
+    AutofillProfile::CreateInferredLabels(unique_matched_profiles, &field_types,
+                                          type.GetStorableType(), 1,
+                                          app_locale_, &labels);
   }
-
+  suggestion_selection::PrepareSuggestions(labels, &unique_suggestions);
   return unique_suggestions;
 }
 
diff --git a/components/autofill/core/browser/personal_data_manager_unittest.cc b/components/autofill/core/browser/personal_data_manager_unittest.cc
index e362a13..f761fcc 100644
--- a/components/autofill/core/browser/personal_data_manager_unittest.cc
+++ b/components/autofill/core/browser/personal_data_manager_unittest.cc
@@ -38,6 +38,7 @@
 #include "components/autofill/core/browser/autofill_test_utils.h"
 #include "components/autofill/core/browser/field_types.h"
 #include "components/autofill/core/browser/form_structure.h"
+#include "components/autofill/core/browser/label_formatter_test_utils.h"
 #include "components/autofill/core/browser/personal_data_manager_observer.h"
 #include "components/autofill/core/browser/suggestion_selection.h"
 #include "components/autofill/core/browser/sync_utils.h"
@@ -2547,6 +2548,116 @@
   EXPECT_EQ(0U, personal_data_->GetProfiles().size());
 }
 
+TEST_F(PersonalDataManagerTest,
+       GetProfileSuggestionsWithImprovedLabelDisambiguationForContactForm) {
+  AutofillProfile profile(base::GenerateGUID(), test::kEmptyOrigin);
+  test::SetProfileInfo(&profile, "Hoa", "", "Pham", "hoa.pham@comcast.net", "",
+                       "401 Merrimack St", "", "Lowell", "MA", "01852", "US",
+                       "19786744120");
+  AddProfileToPersonalDataManager(profile);
+
+  base::test::ScopedFeatureList scoped_features;
+  scoped_features.InitWithFeatures(
+      /*enabled_features=*/{features::kAutofillUseImprovedLabelDisambiguation},
+      /*disabled_features=*/{});
+
+  EXPECT_THAT(
+      personal_data_->GetProfileSuggestions(
+          AutofillType(NAME_FIRST), base::string16(), false,
+          std::vector<ServerFieldType>{NAME_FIRST, NAME_LAST, EMAIL_ADDRESS,
+                                       PHONE_HOME_WHOLE_NUMBER}),
+      ElementsAre(AllOf(
+          testing::Field(
+              &Suggestion::label,
+              FormatExpectedLabel("(978) 674-4120", "hoa.pham@comcast.net")),
+          testing::Field(
+              &Suggestion::additional_label,
+              FormatExpectedLabel("(978) 674-4120", "hoa.pham@comcast.net")))));
+}
+
+TEST_F(PersonalDataManagerTest,
+       GetProfileSuggestionsWithImprovedLabelDisambiguationForAddressForm) {
+  AutofillProfile profile(base::GenerateGUID(), test::kEmptyOrigin);
+  test::SetProfileInfo(&profile, "Hoa", "", "Pham", "hoa.pham@comcast.net", "",
+                       "401 Merrimack St", "", "Lowell", "MA", "01852", "US",
+                       "19786744120");
+  AddProfileToPersonalDataManager(profile);
+
+  base::test::ScopedFeatureList scoped_features;
+  scoped_features.InitWithFeatures(
+      /*enabled_features=*/{features::kAutofillUseImprovedLabelDisambiguation},
+      /*disabled_features=*/{});
+
+  EXPECT_THAT(personal_data_->GetProfileSuggestions(
+                  AutofillType(NAME_FULL), base::string16(), false,
+                  std::vector<ServerFieldType>{
+                      NAME_FULL, ADDRESS_HOME_STREET_ADDRESS, ADDRESS_HOME_CITY,
+                      ADDRESS_HOME_STATE, ADDRESS_HOME_ZIP}),
+              ElementsAre(AllOf(
+                  testing::Field(
+                      &Suggestion::label,
+                      base::ASCIIToUTF16("401 Merrimack St, Lowell, MA 01852")),
+                  testing::Field(&Suggestion::additional_label,
+                                 base::ASCIIToUTF16(
+                                     "401 Merrimack St, Lowell, MA 01852")))));
+}
+
+TEST_F(
+    PersonalDataManagerTest,
+    GetProfileSuggestionsWithImprovedLabelDisambiguationForAddressPhoneForm) {
+  AutofillProfile profile(base::GenerateGUID(), test::kEmptyOrigin);
+  test::SetProfileInfo(&profile, "Hoa", "", "Pham", "hoa.pham@comcast.net", "",
+                       "401 Merrimack St", "", "Lowell", "MA", "01852", "US",
+                       "19786744120");
+  AddProfileToPersonalDataManager(profile);
+
+  base::test::ScopedFeatureList scoped_features;
+  scoped_features.InitWithFeatures(
+      /*enabled_features=*/{features::kAutofillUseImprovedLabelDisambiguation},
+      /*disabled_features=*/{});
+
+  EXPECT_THAT(
+      personal_data_->GetProfileSuggestions(
+          AutofillType(NAME_FULL), base::string16(), false,
+          std::vector<ServerFieldType>{NAME_FULL, ADDRESS_HOME_STREET_ADDRESS,
+                                       PHONE_HOME_WHOLE_NUMBER}),
+      ElementsAre(AllOf(
+          testing::Field(
+              &Suggestion::label,
+              FormatExpectedLabel("(978) 674-4120", "401 Merrimack St")),
+          testing::Field(
+              &Suggestion::additional_label,
+              FormatExpectedLabel("(978) 674-4120", "401 Merrimack St")))));
+}
+
+TEST_F(
+    PersonalDataManagerTest,
+    GetProfileSuggestionsWithImprovedLabelDisambiguationForAddressEmailForm) {
+  AutofillProfile profile(base::GenerateGUID(), test::kEmptyOrigin);
+  test::SetProfileInfo(&profile, "Hoa", "", "Pham", "hoa.pham@comcast.net", "",
+                       "401 Merrimack St", "", "Lowell", "MA", "01852", "US",
+                       "19786744120");
+  AddProfileToPersonalDataManager(profile);
+
+  base::test::ScopedFeatureList scoped_features;
+  scoped_features.InitWithFeatures(
+      /*enabled_features=*/{features::kAutofillUseImprovedLabelDisambiguation},
+      /*disabled_features=*/{});
+
+  EXPECT_THAT(
+      personal_data_->GetProfileSuggestions(
+          AutofillType(NAME_FULL), base::string16(), false,
+          std::vector<ServerFieldType>{NAME_FULL, ADDRESS_HOME_STREET_ADDRESS,
+                                       EMAIL_ADDRESS}),
+      ElementsAre(AllOf(
+          testing::Field(
+              &Suggestion::label,
+              FormatExpectedLabel("401 Merrimack St", "hoa.pham@comcast.net")),
+          testing::Field(&Suggestion::additional_label,
+                         FormatExpectedLabel("401 Merrimack St",
+                                             "hoa.pham@comcast.net")))));
+}
+
 TEST_F(PersonalDataManagerTest, IsKnownCard_MatchesMaskedServerCard) {
   // Add a masked server card.
   std::vector<CreditCard> server_cards;
diff --git a/components/autofill/core/browser/suggestion_selection.cc b/components/autofill/core/browser/suggestion_selection.cc
index 516899d..10d93bd 100644
--- a/components/autofill/core/browser/suggestion_selection.cc
+++ b/components/autofill/core/browser/suggestion_selection.cc
@@ -233,5 +233,15 @@
       num_profiles_supressed);
 }
 
+void PrepareSuggestions(const std::vector<base::string16>& labels,
+                        std::vector<Suggestion>* suggestions) {
+  DCHECK_EQ(suggestions->size(), labels.size());
+
+  for (size_t i = 0; i < labels.size(); ++i) {
+    (*suggestions)[i].additional_label = base::string16(labels[i]);
+    (*suggestions)[i].label = base::string16(labels[i]);
+  }
+}
+
 }  // namespace suggestion_selection
 }  // namespace autofill
diff --git a/components/autofill/core/browser/suggestion_selection.h b/components/autofill/core/browser/suggestion_selection.h
index 0e6909e..067538b 100644
--- a/components/autofill/core/browser/suggestion_selection.h
+++ b/components/autofill/core/browser/suggestion_selection.h
@@ -59,6 +59,12 @@
     base::Time min_last_used,
     std::vector<AutofillProfile*>* profiles);
 
+// Prepares a collection of Suggestions to show to the user. Adds |labels| to
+// their corresponding |suggestions|. A label corresponds to the suggestion with
+// the same index.
+void PrepareSuggestions(const std::vector<base::string16>& labels,
+                        std::vector<Suggestion>* suggestions);
+
 }  // namespace suggestion_selection
 }  // namespace autofill
 
diff --git a/components/country_codes/OWNERS b/components/country_codes/OWNERS
index 76dd81a..fc3fe26 100644
--- a/components/country_codes/OWNERS
+++ b/components/country_codes/OWNERS
@@ -1,5 +1,4 @@
 jdonnelly@chromium.org
-scottchen@chromium.org
 pkasting@chromium.org
 
 # COMPONENT: Internals>Core
diff --git a/components/data_reduction_proxy/content/common/data_reduction_proxy_url_loader_throttle.cc b/components/data_reduction_proxy/content/common/data_reduction_proxy_url_loader_throttle.cc
index 67db6a6..5006226 100644
--- a/components/data_reduction_proxy/content/common/data_reduction_proxy_url_loader_throttle.cc
+++ b/components/data_reduction_proxy/content/common/data_reduction_proxy_url_loader_throttle.cc
@@ -5,6 +5,7 @@
 #include "components/data_reduction_proxy/content/common/data_reduction_proxy_url_loader_throttle.h"
 
 #include "base/bind.h"
+#include "base/metrics/histogram_macros.h"
 #include "components/data_reduction_proxy/content/common/header_util.h"
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_bypass_protocol.h"
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_headers.h"
@@ -20,6 +21,17 @@
 
 namespace data_reduction_proxy {
 
+namespace {
+void RecordQuicProxyStatus(const net::ProxyServer& proxy_server) {
+  if (proxy_server.is_https() || proxy_server.is_quic()) {
+    RecordQuicProxyStatus(IsQuicProxy(proxy_server)
+                              ? QUIC_PROXY_STATUS_AVAILABLE
+                              : QUIC_PROXY_NOT_SUPPORTED);
+  }
+}
+
+}  // namespace
+
 DataReductionProxyURLLoaderThrottle::DataReductionProxyURLLoaderThrottle(
     const net::HttpRequestHeaders& post_cache_headers,
     DataReductionProxyThrottleManager* manager)
@@ -78,6 +90,7 @@
 
   before_will_process_response_received_ = true;
   MaybeRetry(proxy_server, response_head.headers.get(), net::OK, defer);
+  RecordQuicProxyStatus(proxy_server);
 }
 
 void DataReductionProxyURLLoaderThrottle::MaybeRetry(
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_data_use_observer.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_data_use_observer.cc
index 1ce2d3f..c382847 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_data_use_observer.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_data_use_observer.cc
@@ -57,8 +57,6 @@
     : data_reduction_proxy_io_data_(data_reduction_proxy_io_data),
       data_use_ascriber_(data_use_ascriber) {
   DCHECK(data_reduction_proxy_io_data_);
-  if (!data_reduction_proxy::params::IsDataSaverSiteBreakdownUsingPLMEnabled())
-    data_use_ascriber_->AddObserver(this);
 }
 
 DataReductionProxyDataUseObserver::~DataReductionProxyDataUseObserver() {
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate.cc
index 367c0b73..99a7bd7 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate.cc
@@ -15,6 +15,7 @@
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_io_data.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_request_options.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_util.h"
+#include "components/data_reduction_proxy/core/common/data_reduction_proxy_bypass_protocol.h"
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h"
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_switches.h"
 #include "net/base/host_port_pair.h"
@@ -26,12 +27,6 @@
 
 namespace data_reduction_proxy {
 
-namespace {
-
-static const char kDataReductionCoreProxy[] = "proxy.googlezip.net";
-
-}  // namespace
-
 DataReductionProxyDelegate::DataReductionProxyDelegate(
     DataReductionProxyConfig* config,
     const DataReductionProxyConfigurator* configurator,
@@ -204,18 +199,7 @@
 bool DataReductionProxyDelegate::SupportsQUIC(
     const net::ProxyServer& proxy_server) const {
   DCHECK(thread_checker_.CalledOnValidThread());
-  // Enable QUIC for whitelisted proxies.
-  return params::IsQuicEnabledForNonCoreProxies() ||
-         proxy_server ==
-             net::ProxyServer(net::ProxyServer::SCHEME_HTTPS,
-                              net::HostPortPair(kDataReductionCoreProxy, 443));
-}
-
-void DataReductionProxyDelegate::RecordQuicProxyStatus(
-    QuicProxyStatus status) const {
-  DCHECK(thread_checker_.CalledOnValidThread());
-  UMA_HISTOGRAM_ENUMERATION("DataReductionProxy.Quic.ProxyStatus", status,
-                            QUIC_PROXY_STATUS_BOUNDARY);
+  return IsQuicProxy(proxy_server);
 }
 
 }  // namespace data_reduction_proxy
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate.h b/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate.h
index 14ba5fa..8f5e0c4 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate.h
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate.h
@@ -56,20 +56,7 @@
   // Returns true if |proxy_server| supports QUIC.
   virtual bool SupportsQUIC(const net::ProxyServer& proxy_server) const;
 
-  // Availability status of data reduction QUIC proxy.
-  // Protected so that the enum values are accessible for testing.
-  enum QuicProxyStatus {
-    QUIC_PROXY_STATUS_AVAILABLE,
-    QUIC_PROXY_NOT_SUPPORTED,
-    QUIC_PROXY_STATUS_MARKED_AS_BROKEN,
-    QUIC_PROXY_DISABLED_VIA_FIELD_TRIAL,
-    QUIC_PROXY_STATUS_BOUNDARY
-  };
-
  private:
-  // Records the availability status of data reduction proxy.
-  void RecordQuicProxyStatus(QuicProxyStatus status) const;
-
   // Checks if the first proxy server in |result| supports QUIC and if so
   // adds an alternative proxy configuration to |result|.
   void GetAlternativeProxy(const GURL& url,
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate_unittest.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate_unittest.cc
index fc1049e..c38b9c9 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate_unittest.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate_unittest.cc
@@ -33,6 +33,7 @@
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_service.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_settings.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_test_utils.h"
+#include "components/data_reduction_proxy/core/common/data_reduction_proxy_bypass_protocol.h"
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_headers_test_utils.h"
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_params_test_utils.h"
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_pref_names.h"
@@ -104,27 +105,19 @@
     if (expect_alternative_proxy_server && !broken) {
       histogram_tester.ExpectUniqueSample(
           "DataReductionProxy.Quic.ProxyStatus",
-          TestDataReductionProxyDelegate::QuicProxyStatus::
-              QUIC_PROXY_STATUS_AVAILABLE,
-          1);
+          QuicProxyStatus::QUIC_PROXY_STATUS_AVAILABLE, 1);
     } else if (!supports_quic && !broken) {
       histogram_tester.ExpectUniqueSample(
           "DataReductionProxy.Quic.ProxyStatus",
-          TestDataReductionProxyDelegate::QuicProxyStatus::
-              QUIC_PROXY_NOT_SUPPORTED,
-          1);
+          QuicProxyStatus::QUIC_PROXY_NOT_SUPPORTED, 1);
     } else {
       ASSERT_TRUE(broken);
       histogram_tester.ExpectUniqueSample(
           "DataReductionProxy.Quic.ProxyStatus",
-          TestDataReductionProxyDelegate::QuicProxyStatus::
-              QUIC_PROXY_STATUS_MARKED_AS_BROKEN,
-          1);
+          QuicProxyStatus::QUIC_PROXY_STATUS_MARKED_AS_BROKEN, 1);
     }
   }
 
-  using DataReductionProxyDelegate::QuicProxyStatus;
-
  private:
   const bool proxy_supports_quic_;
 
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate.cc
index fcfad73..67a939a 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate.cc
@@ -575,8 +575,7 @@
           request),
       request.traffic_annotation().unique_id_hash_code);
 
-  if (params::IsDataSaverSiteBreakdownUsingPLMEnabled() &&
-      data_reduction_proxy_io_data_ &&
+  if (data_reduction_proxy_io_data_ &&
       data_reduction_proxy_io_data_->resource_type_provider() &&
       data_reduction_proxy_io_data_->resource_type_provider()
           ->IsNonContentInitiatedRequest(request)) {
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate_unittest.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate_unittest.cc
index 394055d..82a6bfa 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate_unittest.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate_unittest.cc
@@ -1999,10 +1999,6 @@
       "Via: 1.1 Chrome-Compression-Proxy\r\n"
       "\r\n";
 
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      data_reduction_proxy::features::
-          kDataSaverSiteBreakdownUsingPageLoadMetrics);
   Init(USE_INSECURE_PROXY);
   EnableDataUsageReporting();
   auto test_resource_type_provider =
diff --git a/components/data_reduction_proxy/core/common/data_reduction_proxy_bypass_protocol.cc b/components/data_reduction_proxy/core/common/data_reduction_proxy_bypass_protocol.cc
index 6ffe2d4..4bdb409 100644
--- a/components/data_reduction_proxy/core/common/data_reduction_proxy_bypass_protocol.cc
+++ b/components/data_reduction_proxy/core/common/data_reduction_proxy_bypass_protocol.cc
@@ -27,6 +27,8 @@
 
 namespace {
 
+static const char kDataReductionCoreProxy[] = "proxy.googlezip.net";
+
 // Returns the Data Reduction Proxy servers in |proxy_type_info| that should be
 // marked bad according to |data_reduction_proxy_info|.
 std::vector<net::ProxyServer> GetProxiesToMarkBad(
@@ -293,4 +295,17 @@
   return true;
 }
 
+bool IsQuicProxy(const net::ProxyServer& proxy_server) {
+  // Enable QUIC for whitelisted proxies.
+  return params::IsQuicEnabledForNonCoreProxies() ||
+         proxy_server ==
+             net::ProxyServer(net::ProxyServer::SCHEME_HTTPS,
+                              net::HostPortPair(kDataReductionCoreProxy, 443));
+}
+
+void RecordQuicProxyStatus(QuicProxyStatus status) {
+  UMA_HISTOGRAM_ENUMERATION("DataReductionProxy.Quic.ProxyStatus", status,
+                            QUIC_PROXY_STATUS_BOUNDARY);
+}
+
 }  // namespace data_reduction_proxy
diff --git a/components/data_reduction_proxy/core/common/data_reduction_proxy_bypass_protocol.h b/components/data_reduction_proxy/core/common/data_reduction_proxy_bypass_protocol.h
index d87153bb..b3ba446 100644
--- a/components/data_reduction_proxy/core/common/data_reduction_proxy_bypass_protocol.h
+++ b/components/data_reduction_proxy/core/common/data_reduction_proxy_bypass_protocol.h
@@ -15,6 +15,15 @@
 
 struct DataReductionProxyTypeInfo;
 
+// Availability status of data reduction QUIC proxy.
+enum QuicProxyStatus {
+  QUIC_PROXY_STATUS_AVAILABLE,
+  QUIC_PROXY_NOT_SUPPORTED,
+  QUIC_PROXY_STATUS_MARKED_AS_BROKEN,
+  QUIC_PROXY_DISABLED_VIA_FIELD_TRIAL,
+  QUIC_PROXY_STATUS_BOUNDARY
+};
+
 // Records a data reduction proxy bypass event as a "BlockType" if
 // |bypass_all| is true and as a "BypassType" otherwise. Records the event as
 // "Primary" if |is_primary| is true and "Fallback" otherwise.
@@ -110,6 +119,11 @@
                            base::TimeTicks t,
                            base::TimeDelta* retry_delay);
 
+// Returns true if the proxy supports QUIC.
+bool IsQuicProxy(const net::ProxyServer& proxy_server);
+
+void RecordQuicProxyStatus(QuicProxyStatus status);
+
 }  // namespace data_reduction_proxy
 
 #endif  // COMPONENTS_DATA_REDUCTION_PROXY_CORE_COMMON_DATA_REDUCTION_PROXY_BYPASS_PROTOCOL_H_
diff --git a/components/data_reduction_proxy/core/common/data_reduction_proxy_features.cc b/components/data_reduction_proxy/core/common/data_reduction_proxy_features.cc
index 5762b9f9..87d7321 100644
--- a/components/data_reduction_proxy/core/common/data_reduction_proxy_features.cc
+++ b/components/data_reduction_proxy/core/common/data_reduction_proxy_features.cc
@@ -31,12 +31,6 @@
 const base::Feature kDogfood{"DataReductionProxyDogfood",
                              base::FEATURE_DISABLED_BY_DEFAULT};
 
-// Enables recording of the site-breakdown metrics using the page load metrics
-// harness, and disables the observer for data use ascriber.
-const base::Feature kDataSaverSiteBreakdownUsingPageLoadMetrics{
-    "DataSaverSiteBreakdownUsingPageLoadMetrics",
-    base::FEATURE_ENABLED_BY_DEFAULT};
-
 // Enables data reduction proxy when network service is enabled.
 const base::Feature kDataReductionProxyEnabledWithNetworkService{
     "DataReductionProxyEnabledWithNetworkService",
diff --git a/components/data_reduction_proxy/core/common/data_reduction_proxy_features.h b/components/data_reduction_proxy/core/common/data_reduction_proxy_features.h
index 830d096..3212c6f2 100644
--- a/components/data_reduction_proxy/core/common/data_reduction_proxy_features.h
+++ b/components/data_reduction_proxy/core/common/data_reduction_proxy_features.h
@@ -13,7 +13,6 @@
 extern const base::Feature kDataReductionProxyDecidesTransform;
 extern const base::Feature kDataReductionProxyLowMemoryDevicePromo;
 extern const base::Feature kDogfood;
-extern const base::Feature kDataSaverSiteBreakdownUsingPageLoadMetrics;
 extern const base::Feature kDataReductionProxyEnabledWithNetworkService;
 extern const base::Feature kDataSaverUseOnDeviceSafeBrowsing;
 
diff --git a/components/data_reduction_proxy/core/common/data_reduction_proxy_params.cc b/components/data_reduction_proxy/core/common/data_reduction_proxy_params.cc
index a13dd24..c309efe 100644
--- a/components/data_reduction_proxy/core/common/data_reduction_proxy_params.cc
+++ b/components/data_reduction_proxy/core/common/data_reduction_proxy_params.cc
@@ -362,12 +362,6 @@
   return GURL(secure_proxy_check_url);
 }
 
-bool IsDataSaverSiteBreakdownUsingPLMEnabled() {
-  return base::FeatureList::IsEnabled(
-      data_reduction_proxy::features::
-          kDataSaverSiteBreakdownUsingPageLoadMetrics);
-}
-
 bool IsEnabledWithNetworkService() {
   return base::FeatureList::IsEnabled(
              data_reduction_proxy::features::
diff --git a/components/data_reduction_proxy/core/common/data_reduction_proxy_params.h b/components/data_reduction_proxy/core/common/data_reduction_proxy_params.h
index 4143ded..8b7bb88 100644
--- a/components/data_reduction_proxy/core/common/data_reduction_proxy_params.h
+++ b/components/data_reduction_proxy/core/common/data_reduction_proxy_params.h
@@ -137,10 +137,6 @@
 // unsuccessful.
 bool IsWhitelistedHttpResponseCodeForProbes(int http_response_code);
 
-// Returns if site-breakdown metrics should be recorded using the page load
-// metrics harness.
-bool IsDataSaverSiteBreakdownUsingPLMEnabled();
-
 // Returns whether network service is enabled and data reduction proxy should be
 // used.
 bool IsEnabledWithNetworkService();
diff --git a/components/download/internal/background_service/controller_impl.cc b/components/download/internal/background_service/controller_impl.cc
index 3c76cce..2177e6a8 100644
--- a/components/download/internal/background_service/controller_impl.cc
+++ b/components/download/internal/background_service/controller_impl.cc
@@ -134,6 +134,7 @@
 }
 
 ControllerImpl::~ControllerImpl() {
+  navigation_monitor_->SetObserver(nullptr);
   base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
       this);
 }
diff --git a/components/download/internal/background_service/navigation_monitor_impl_unittests.cc b/components/download/internal/background_service/navigation_monitor_impl_unittests.cc
index 0b23cef..19fb4dd 100644
--- a/components/download/internal/background_service/navigation_monitor_impl_unittests.cc
+++ b/components/download/internal/background_service/navigation_monitor_impl_unittests.cc
@@ -97,6 +97,15 @@
   DISALLOW_COPY_AND_ASSIGN(NavigationMonitorImplTest);
 };
 
+TEST_F(NavigationMonitorImplTest, NoObserver) {
+  SendNavigationEventAt(NavigationEvent::START_NAVIGATION, 5);
+
+  observer_->VerifyNavigationStateAt(false, 0);
+  observer_->VerifyNavigationStateAt(false, 10);
+  observer_->VerifyNavigationStateAt(false, 100);
+  WaitUntilDone();
+}
+
 TEST_F(NavigationMonitorImplTest, NavigationTimeout) {
   navigation_monitor_->SetObserver(observer_.get());
   SendNavigationEventAt(NavigationEvent::START_NAVIGATION, 5);
diff --git a/components/download/internal/common/download_path_reservation_tracker.cc b/components/download/internal/common/download_path_reservation_tracker.cc
index b5eccbd..5c02e27 100644
--- a/components/download/internal/common/download_path_reservation_tracker.cc
+++ b/components/download/internal/common/download_path_reservation_tracker.cc
@@ -252,13 +252,18 @@
 PathValidationResult ValidatePathAndResolveConflicts(
     const CreateReservationInfo& info,
     base::FilePath* target_path) {
-  // Check writability of the suggested path. If we can't write to it, default
-  // to the user's Documents directory. We'll prompt them in this case. No
-  // further amendments are made to the filename since the user is going to be
-  // prompted.
+  // Check writability of the suggested path. If we can't write to it, use
+  // the |default_download_path| if it is not empty or |fallback_directory|.
+  // We'll prompt them in this case. No further amendments are made to the
+  // filename since the user is going to be prompted.
   if (!IsPathWritable(info, *target_path)) {
     DVLOG(1) << "Unable to write to path \"" << target_path->value() << "\"";
-    *target_path = info.fallback_directory.Append(target_path->BaseName());
+    if (!info.default_download_path.empty() &&
+        target_path->DirName() != info.default_download_path) {
+      *target_path = info.default_download_path.Append(target_path->BaseName());
+    } else {
+      *target_path = info.fallback_directory.Append(target_path->BaseName());
+    }
     return PathValidationResult::PATH_NOT_WRITABLE;
   }
 
diff --git a/components/download/internal/common/download_path_reservation_tracker_unittest.cc b/components/download/internal/common/download_path_reservation_tracker_unittest.cc
index d9d8745..acdffd42 100644
--- a/components/download/internal/common/download_path_reservation_tracker_unittest.cc
+++ b/components/download/internal/common/download_path_reservation_tracker_unittest.cc
@@ -530,11 +530,23 @@
     bool create_directory = false;
     CallGetReservedPath(item.get(), path, create_directory, conflict_action,
                         &reserved_path, &result);
-    // Verification fails.
+    // Verification fails. If |dir| is the same as the default download dir,
+    // fallback_dir should be used.
     EXPECT_EQ(PathValidationResult::PATH_NOT_WRITABLE, result);
     EXPECT_EQ(path.BaseName().value(), reserved_path.BaseName().value());
     EXPECT_EQ(fallback_dir.value(), reserved_path.DirName().value());
+
+    // Change the default download dir to something else.
+    base::FilePath default_download_path =
+        GetPathInDownloadsDirectory(FILE_PATH_LITERAL("foo/foo.txt"));
+    set_default_download_path(default_download_path);
+    CallGetReservedPath(item.get(), path, create_directory, conflict_action,
+                        &reserved_path, &result);
+    EXPECT_EQ(PathValidationResult::PATH_NOT_WRITABLE, result);
+    EXPECT_EQ(path.BaseName().value(), reserved_path.BaseName().value());
+    EXPECT_EQ(default_download_path.value(), reserved_path.DirName().value());
   }
+
   SetDownloadItemState(item.get(), DownloadItem::COMPLETE);
 }
 
diff --git a/components/download/quarantine/quarantine_features_win.cc b/components/download/quarantine/quarantine_features_win.cc
index 7418593..6c67f2e1 100644
--- a/components/download/quarantine/quarantine_features_win.cc
+++ b/components/download/quarantine/quarantine_features_win.cc
@@ -6,11 +6,6 @@
 
 namespace download {
 
-// When manually setting the Zone Identifier, this feature controls whether the
-// HostUrl and ReferrerUrl values are set.
-const base::Feature kAugmentedZoneIdentifier{"AugmentedZoneIdentifier",
-                                             base::FEATURE_DISABLED_BY_DEFAULT};
-
 // This feature controls whether the InvokeAttachmentServices function will be
 // called. Has no effect on machines that are domain-joined, where the function
 // is always called.
diff --git a/components/download/quarantine/quarantine_features_win.h b/components/download/quarantine/quarantine_features_win.h
index b7db680..7363a0f4 100644
--- a/components/download/quarantine/quarantine_features_win.h
+++ b/components/download/quarantine/quarantine_features_win.h
@@ -9,8 +9,6 @@
 
 namespace download {
 
-extern const base::Feature kAugmentedZoneIdentifier;
-
 extern const base::Feature kInvokeAttachmentServices;
 
 }  // namespace download
diff --git a/components/download/quarantine/quarantine_win.cc b/components/download/quarantine/quarantine_win.cc
index 59701ee..eaf6370 100644
--- a/components/download/quarantine/quarantine_win.cc
+++ b/components/download/quarantine/quarantine_win.cc
@@ -29,6 +29,7 @@
 #include "base/threading/scoped_blocking_call.h"
 #include "base/win/scoped_handle.h"
 #include "base/win/win_util.h"
+#include "base/win/windows_version.h"
 #include "components/download/quarantine/common_win.h"
 #include "components/download/quarantine/quarantine_features_win.h"
 #include "url/gurl.h"
@@ -47,9 +48,8 @@
 // streams are not supported, like a file on a FAT32 filesystem.  This function
 // does not invoke Windows Attachment Execution Services.
 //
-// If the AugmentedZoneIdentifier feature is enabled, the ReferrerUrl and
-// HostUrl values are set according to the behavior of the IAttachmentExecute
-// interface on Windows 10.
+// On Windows 10 or higher, the ReferrerUrl and HostUrl values are set according
+// to the behavior of the IAttachmentExecute interface.
 //
 // |full_path| is the path to the downloaded file.
 QuarantineFileResult SetInternetZoneIdentifierDirectly(
@@ -68,7 +68,7 @@
   static const char kHostUrlFormat[] = "HostUrl=%s\r\n";
 
   std::string identifier = "[ZoneTransfer]\r\nZoneId=3\r\n";
-  if (base::FeatureList::IsEnabled(kAugmentedZoneIdentifier)) {
+  if (base::win::GetVersion() >= base::win::VERSION_WIN10) {
     // Match what the InvokeAttachmentServices() function will output, including
     // the order of the values.
     if (IsValidUrlForAttachmentServices(referrer_url)) {
diff --git a/components/download/quarantine/quarantine_win_unittest.cc b/components/download/quarantine/quarantine_win_unittest.cc
index c0e6283b..0c663a9 100644
--- a/components/download/quarantine/quarantine_win_unittest.cc
+++ b/components/download/quarantine/quarantine_win_unittest.cc
@@ -30,8 +30,6 @@
 const char kDummyReferrerUrl[] = "https://example.com/referrer";
 const char kDummyClientGuid[] = "A1B69307-8FA2-4B6F-9181-EA06051A48A7";
 
-const char kMotwForInternetZone[] = "[ZoneTransfer]\r\nZoneId=3\r\n";
-
 const char* const kUntrustedURLs[] = {
     "http://example.com/foo",
     "https://example.com/foo",
@@ -116,6 +114,7 @@
 
 // Sets the internet Zone.Identifier alternate data stream for |file_path|.
 bool AddInternetZoneIdentifierDirectly(const base::FilePath& file_path) {
+  static const char kMotwForInternetZone[] = "[ZoneTransfer]\r\nZoneId=3\r\n";
   return base::WriteFile(GetZoneIdentifierStreamPath(file_path),
                          kMotwForInternetZone,
                          base::size(kMotwForInternetZone)) ==
@@ -289,7 +288,10 @@
   std::string zone_identifier;
   ASSERT_TRUE(GetZoneIdentifierStreamContents(test_file, &zone_identifier));
 
-  EXPECT_STREQ(zone_identifier.c_str(), kMotwForInternetZone);
+  // The actual assigned zone could be anything and the contents of the zone
+  // identifier depends on the version of Windows. So only testing that there is
+  // a zone annotation.
+  EXPECT_FALSE(zone_identifier.empty());
 }
 
 // If there is no client GUID supplied to the QuarantineFile() call, then rather
@@ -307,7 +309,10 @@
   std::string zone_identifier;
   ASSERT_TRUE(GetZoneIdentifierStreamContents(test_file, &zone_identifier));
 
-  EXPECT_STREQ(zone_identifier.c_str(), kMotwForInternetZone);
+  // The actual assigned zone could be anything and the contents of the zone
+  // identifier depends on the version of Windows. So only testing that there is
+  // a zone annotation.
+  EXPECT_FALSE(zone_identifier.empty());
 }
 
 // URLs longer than INTERNET_MAX_URL_LENGTH are known to break URLMon. Such a
@@ -325,142 +330,10 @@
   std::string zone_identifier;
   ASSERT_TRUE(GetZoneIdentifierStreamContents(test_file, &zone_identifier));
 
-  EXPECT_STREQ(zone_identifier.c_str(), kMotwForInternetZone);
-}
-
-// On domain-joined machines, the IAttachmentExecute code path is taken, and the
-// output depends on the Windows version.
-TEST_F(QuarantineWinTest, EnterpriseUserZoneIdentifier) {
-  base::win::ScopedDomainStateForTesting scoped_domain(true);
-
-  base::FilePath test_file = GetTempDir().AppendASCII("foo.exe");
-  ASSERT_TRUE(CreateFile(test_file));
-
-  EXPECT_EQ(QuarantineFileResult::OK,
-            QuarantineFile(test_file, GURL(kDummySourceUrl),
-                           GURL(kDummyReferrerUrl), kDummyClientGuid));
-
-  std::string zone_identifier;
-  ASSERT_TRUE(GetZoneIdentifierStreamContents(test_file, &zone_identifier));
-
-  std::string expected = kMotwForInternetZone;
-  // On Win10, the MotW now contains the HostUrl and ReferrerUrl values.
-  if (base::win::GetVersion() >= base::win::VERSION_WIN10) {
-    expected.append(base::StringPrintf("ReferrerUrl=%s\r\nHostUrl=%s\r\n",
-                                       kDummyReferrerUrl, kDummySourceUrl));
-  }
-
-  EXPECT_EQ(zone_identifier, expected);
-}
-
-// When the InvokeAttachmentServices is disabled, the fallback code path that
-// manually sets the MotW is always invoked. The original fallback code only
-// sets the ZoneId value.
-TEST_F(QuarantineWinTest, DisableInvokeAttachmentServices) {
-  base::win::ScopedDomainStateForTesting scoped_domain(false);
-
-  base::test::ScopedFeatureList scoped_feature_list_;
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled features.
-      {},
-      // Disabled features.
-      {kInvokeAttachmentServices});
-
-  base::FilePath test_file = GetTempDir().AppendASCII("foo.exe");
-  ASSERT_TRUE(CreateFile(test_file));
-
-  EXPECT_EQ(QuarantineFileResult::OK,
-            QuarantineFile(test_file, GURL(kDummySourceUrl),
-                           GURL(kDummyReferrerUrl), kDummyClientGuid));
-
-  std::string zone_identifier;
-  ASSERT_TRUE(GetZoneIdentifierStreamContents(test_file, &zone_identifier));
-
-  EXPECT_STREQ(zone_identifier.c_str(), kMotwForInternetZone);
-}
-
-// Tests the expected MotW when the AugmentedZoneIdentifier feature is enabled.
-TEST_F(QuarantineWinTest, AugmentedZoneIdentifier) {
-  base::win::ScopedDomainStateForTesting scoped_domain(false);
-
-  base::test::ScopedFeatureList scoped_feature_list_;
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled features.
-      {kAugmentedZoneIdentifier},
-      // Disabled features.
-      {kInvokeAttachmentServices});
-
-  base::FilePath test_file = GetTempDir().AppendASCII("foo.exe");
-  ASSERT_TRUE(CreateFile(test_file));
-
-  EXPECT_EQ(QuarantineFileResult::OK,
-            QuarantineFile(test_file, GURL(kDummySourceUrl),
-                           GURL(kDummyReferrerUrl), kDummyClientGuid));
-
-  std::string zone_identifier;
-  ASSERT_TRUE(GetZoneIdentifierStreamContents(test_file, &zone_identifier));
-
-  std::string expected = kMotwForInternetZone;
-  expected.append(base::StringPrintf("ReferrerUrl=%s\r\nHostUrl=%s\r\n",
-                                     kDummyReferrerUrl, kDummySourceUrl));
-  EXPECT_EQ(zone_identifier, expected);
-}
-
-// Tests the expected MotW when the AugmentedZoneIdentifier feature is enabled
-// and no referrer is provided to the QuarantineFile() function.
-TEST_F(QuarantineWinTest, AugmentedZoneIdentifierNoReferrer) {
-  base::win::ScopedDomainStateForTesting scoped_domain(false);
-
-  base::test::ScopedFeatureList scoped_feature_list_;
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled features.
-      {kAugmentedZoneIdentifier},
-      // Disabled features.
-      {kInvokeAttachmentServices});
-
-  base::FilePath test_file = GetTempDir().AppendASCII("foo.exe");
-  ASSERT_TRUE(CreateFile(test_file));
-
-  EXPECT_EQ(QuarantineFileResult::OK,
-            QuarantineFile(test_file, GURL(kDummySourceUrl), GURL(),
-                           kDummyClientGuid));
-
-  std::string zone_identifier;
-  ASSERT_TRUE(GetZoneIdentifierStreamContents(test_file, &zone_identifier));
-
-  std::string expected = kMotwForInternetZone;
-  expected.append(base::StringPrintf("HostUrl=%s\r\n", kDummySourceUrl));
-
-  EXPECT_EQ(zone_identifier, expected);
-}
-
-// Tests the expected MotW when the AugmentedZoneIdentifier feature is enabled
-// and no source is provided to the QuarantineFile() function.
-TEST_F(QuarantineWinTest, AugmentedZoneIdentifierNoSource) {
-  base::win::ScopedDomainStateForTesting scoped_domain(false);
-
-  base::test::ScopedFeatureList scoped_feature_list_;
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled features.
-      {kAugmentedZoneIdentifier},
-      // Disabled features.
-      {kInvokeAttachmentServices});
-
-  base::FilePath test_file = GetTempDir().AppendASCII("foo.exe");
-  ASSERT_TRUE(CreateFile(test_file));
-
-  EXPECT_EQ(QuarantineFileResult::OK,
-            QuarantineFile(test_file, GURL(), GURL(kDummyReferrerUrl),
-                           kDummyClientGuid));
-
-  std::string zone_identifier;
-  ASSERT_TRUE(GetZoneIdentifierStreamContents(test_file, &zone_identifier));
-
-  std::string expected = kMotwForInternetZone;
-  expected.append(base::StringPrintf("ReferrerUrl=%s\r\nHostUrl=%s\r\n",
-                                     kDummyReferrerUrl, "about:internet"));
-
-  EXPECT_EQ(zone_identifier, expected);
+  // The actual assigned zone could be anything and the contents of the zone
+  // identifier depends on the version of Windows. So only testing that there is
+  // a zone annotation.
+  EXPECT_FALSE(zone_identifier.empty());
 }
 
 TEST_F(QuarantineWinTest, TrustedSite) {
diff --git a/components/exo/client_controlled_shell_surface.cc b/components/exo/client_controlled_shell_surface.cc
index afc35b88..61881c8 100644
--- a/components/exo/client_controlled_shell_surface.cc
+++ b/components/exo/client_controlled_shell_surface.cc
@@ -308,6 +308,10 @@
 }
 
 ClientControlledShellSurface::~ClientControlledShellSurface() {
+  // Reset the window delegate here so that we won't try to do any dragging
+  // operation on a to-be-destroyed window. |widget_| can be nullptr in tests.
+  if (GetWidget())
+    GetWindowState()->SetDelegate(nullptr);
   wide_frame_.reset();
   display::Screen::GetScreen()->RemoveObserver(this);
 }
diff --git a/components/exo/client_controlled_shell_surface_unittest.cc b/components/exo/client_controlled_shell_surface_unittest.cc
index eab34b4..cc21e3cf 100644
--- a/components/exo/client_controlled_shell_surface_unittest.cc
+++ b/components/exo/client_controlled_shell_surface_unittest.cc
@@ -1090,6 +1090,80 @@
 
 namespace {
 
+// This class is only meant to used by CloseWindowWhenDraggingTest.
+// When a ClientControlledShellSurface is destroyed, its natvie window will be
+// hidden first and at that time its window delegate should have been properly
+// reset.
+class ShellSurfaceWindowObserver : public aura::WindowObserver {
+ public:
+  explicit ShellSurfaceWindowObserver(aura::Window* window)
+      : window_(window),
+        has_delegate_(ash::wm::GetWindowState(window)->HasDelegate()) {
+    window_->AddObserver(this);
+  }
+  ~ShellSurfaceWindowObserver() override {
+    if (window_) {
+      window_->RemoveObserver(this);
+      window_ = nullptr;
+    }
+  }
+
+  bool has_delegate() const { return has_delegate_; }
+
+  // aura::WindowObserver:
+  void OnWindowVisibilityChanged(aura::Window* window, bool visible) override {
+    DCHECK_EQ(window_, window);
+
+    if (!visible) {
+      has_delegate_ = ash::wm::GetWindowState(window_)->HasDelegate();
+      window_->RemoveObserver(this);
+      window_ = nullptr;
+    }
+  }
+
+ private:
+  aura::Window* window_;
+  bool has_delegate_;
+
+  DISALLOW_COPY_AND_ASSIGN(ShellSurfaceWindowObserver);
+};
+
+}  // namespace
+
+// Test that when a shell surface is destroyed during its dragging, its window
+// delegate should be reset properly.
+TEST_F(ClientControlledShellSurfaceTest, CloseWindowWhenDraggingTest) {
+  gfx::Size buffer_size(256, 256);
+  std::unique_ptr<Buffer> buffer(
+      new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
+  std::unique_ptr<Surface> surface(new Surface());
+  auto shell_surface =
+      exo_test_helper()->CreateClientControlledShellSurface(surface.get());
+
+  const gfx::Rect original_bounds(0, 0, 256, 256);
+  shell_surface->SetGeometry(original_bounds);
+  shell_surface->set_client_controlled_move_resize(false);
+  surface->Attach(buffer.get());
+  surface->Commit();
+
+  // Press on the edge of the window and start dragging.
+  gfx::Point touch_location(256, 150);
+  ui::test::EventGenerator* event_generator = GetEventGenerator();
+  event_generator->MoveTouch(touch_location);
+  event_generator->PressTouch();
+
+  aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
+  EXPECT_TRUE(ash::wm::GetWindowState(window)->is_dragged());
+  auto observer = std::make_unique<ShellSurfaceWindowObserver>(window);
+  EXPECT_TRUE(observer->has_delegate());
+
+  // Destroy the window.
+  shell_surface.reset();
+  EXPECT_FALSE(observer->has_delegate());
+}
+
+namespace {
+
 class ClientControlledShellSurfaceDragTest : public test::ExoTestBase {
  public:
   ClientControlledShellSurfaceDragTest() = default;
diff --git a/components/image_fetcher/core/cache/image_cache_unittest.cc b/components/image_fetcher/core/cache/image_cache_unittest.cc
index 4802da6..bb57342 100644
--- a/components/image_fetcher/core/cache/image_cache_unittest.cc
+++ b/components/image_fetcher/core/cache/image_cache_unittest.cc
@@ -52,8 +52,8 @@
 
     auto db = std::make_unique<FakeDB<CachedImageMetadataProto>>(&db_store_);
     db_ = db.get();
-    auto metadata_store = std::make_unique<ImageMetadataStoreLevelDB>(
-        base::FilePath(), std::move(db), &clock_);
+    auto metadata_store =
+        std::make_unique<ImageMetadataStoreLevelDB>(std::move(db), &clock_);
     metadata_store_ = metadata_store.get();
 
     auto data_store = std::make_unique<ImageDataStoreDisk>(
@@ -68,7 +68,7 @@
 
   void InitializeImageCache() {
     image_cache_->MaybeStartInitialization();
-    db()->InitCallback(true);
+    db()->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
     RunUntilIdle();
     ASSERT_TRUE(metadata_store()->IsInitialized());
   }
@@ -213,7 +213,7 @@
 
   ASSERT_FALSE(IsCacheInitialized());
   image_cache()->SaveImage(kImageUrl, kImageData);
-  db()->InitCallback(true);
+  db()->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
   RunUntilIdle();
 
   ASSERT_TRUE(IsCacheInitialized());
diff --git a/components/image_fetcher/core/cache/image_metadata_store_leveldb.cc b/components/image_fetcher/core/cache/image_metadata_store_leveldb.cc
index d9f438f..11a669b 100644
--- a/components/image_fetcher/core/cache/image_metadata_store_leveldb.cc
+++ b/components/image_fetcher/core/cache/image_metadata_store_leveldb.cc
@@ -33,12 +33,6 @@
   return time.since_origin().InMicroseconds();
 }
 
-// Statistics are logged to UMA with this string as part of histogram name. They
-// can all be found under LevelDB.*.ImageDatabase. Changing this needs to
-// synchronize with histograms.xml, AND will also become incompatible with older
-// browsers still reporting the previous values.
-const char kImageDatabaseUMAClientName[] = "CachedImageFetcherDatabase";
-
 // The folder where the data will be stored on disk.
 const char kImageDatabaseFolder[] = "cached_image_fetcher_images";
 
@@ -62,23 +56,23 @@
     leveldb_proto::ProtoDatabase<CachedImageMetadataProto>::KeyEntryVector;
 
 ImageMetadataStoreLevelDB::ImageMetadataStoreLevelDB(
+    leveldb_proto::ProtoDatabaseProvider* proto_database_provider,
     const base::FilePath& database_dir,
     scoped_refptr<base::SequencedTaskRunner> task_runner,
     base::Clock* clock)
     : ImageMetadataStoreLevelDB(
-          database_dir,
-          leveldb_proto::ProtoDatabaseProvider::CreateUniqueDB<
-              CachedImageMetadataProto>(task_runner),
+          proto_database_provider->GetDB<CachedImageMetadataProto>(
+              leveldb_proto::ProtoDbType::CACHED_IMAGE_METADATA_STORE,
+              database_dir.AppendASCII(kImageDatabaseFolder),
+              task_runner),
           clock) {}
 
 ImageMetadataStoreLevelDB::ImageMetadataStoreLevelDB(
-    const base::FilePath& database_dir,
     std::unique_ptr<leveldb_proto::ProtoDatabase<CachedImageMetadataProto>>
         database,
     base::Clock* clock)
     : estimated_size_(0),
       initialization_status_(InitializationStatus::UNINITIALIZED),
-      database_dir_(database_dir),
       database_(std::move(database)),
       clock_(clock),
       weak_ptr_factory_(this) {}
@@ -93,9 +87,8 @@
     options.write_buffer_size = kDatabaseWriteBufferSizeBytes;
   }
 
-  base::FilePath image_dir = database_dir_.AppendASCII(kImageDatabaseFolder);
   database_->Init(
-      kImageDatabaseUMAClientName, image_dir, options,
+      options,
       base::BindOnce(&ImageMetadataStoreLevelDB::OnDatabaseInitialized,
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
 }
@@ -188,9 +181,10 @@
 
 void ImageMetadataStoreLevelDB::OnDatabaseInitialized(
     base::OnceClosure callback,
-    bool success) {
-  initialization_status_ = success ? InitializationStatus::INITIALIZED
-                                   : InitializationStatus::INIT_FAILURE;
+    leveldb_proto::Enums::InitStatus status) {
+  initialization_status_ = status == leveldb_proto::Enums::InitStatus::kOK
+                               ? InitializationStatus::INITIALIZED
+                               : InitializationStatus::INIT_FAILURE;
   std::move(callback).Run();
 }
 
diff --git a/components/image_fetcher/core/cache/image_metadata_store_leveldb.h b/components/image_fetcher/core/cache/image_metadata_store_leveldb.h
index 0a751bc..e5189a8d 100644
--- a/components/image_fetcher/core/cache/image_metadata_store_leveldb.h
+++ b/components/image_fetcher/core/cache/image_metadata_store_leveldb.h
@@ -20,6 +20,10 @@
 class SequencedTaskRunner;
 }  // namespace base
 
+namespace leveldb_proto {
+class ProtoDatabaseProvider;
+}  // namespace leveldb_proto
+
 namespace image_fetcher {
 
 class CachedImageMetadataProto;
@@ -27,16 +31,16 @@
 // Stores image metadata in leveldb.
 class ImageMetadataStoreLevelDB : public ImageMetadataStore {
  public:
-  // Initializes the database with |database_dir|.
+  // Initializes the database with |proto_database_provider|.
   ImageMetadataStoreLevelDB(
+      leveldb_proto::ProtoDatabaseProvider* proto_database_provider,
       const base::FilePath& database_dir,
       scoped_refptr<base::SequencedTaskRunner> task_runner,
       base::Clock* clock);
 
-  // Initializes the database with |database_dir|. Creates storage using the
-  // given |image_database| for local storage. Useful for testing.
+  // Creates storage using the given |database| for local storage. Useful for
+  // testing.
   ImageMetadataStoreLevelDB(
-      const base::FilePath& database_dir,
       std::unique_ptr<leveldb_proto::ProtoDatabase<CachedImageMetadataProto>>
           database,
       base::Clock* clock);
@@ -62,7 +66,8 @@
                           KeysCallback callback) override;
 
  private:
-  void OnDatabaseInitialized(base::OnceClosure callback, bool success);
+  void OnDatabaseInitialized(base::OnceClosure callback,
+                             leveldb_proto::Enums::InitStatus status);
   void OnImageUpdated(bool success);
   void UpdateImageMetadataImpl(
       bool success,
@@ -82,7 +87,6 @@
 
   int estimated_size_;
   InitializationStatus initialization_status_;
-  base::FilePath database_dir_;
   std::unique_ptr<leveldb_proto::ProtoDatabase<CachedImageMetadataProto>>
       database_;
   // Clock is owned by the service that creates this object.
diff --git a/components/image_fetcher/core/cache/image_metadata_store_leveldb_unittest.cc b/components/image_fetcher/core/cache/image_metadata_store_leveldb_unittest.cc
index d6971df1..52d68dfeb 100644
--- a/components/image_fetcher/core/cache/image_metadata_store_leveldb_unittest.cc
+++ b/components/image_fetcher/core/cache/image_metadata_store_leveldb_unittest.cc
@@ -47,8 +47,8 @@
     // Setup the fake db and the class under test.
     auto db = std::make_unique<FakeDB<CachedImageMetadataProto>>(&db_store_);
     db_ = db.get();
-    metadata_store_ = std::make_unique<ImageMetadataStoreLevelDB>(
-        base::FilePath(), std::move(db), clock_.get());
+    metadata_store_ = std::make_unique<ImageMetadataStoreLevelDB>(std::move(db),
+                                                                  clock_.get());
   }
 
   void InitializeDatabase() {
@@ -56,7 +56,7 @@
     metadata_store()->Initialize(base::BindOnce(
         &CachedImageFetcherImageMetadataStoreLevelDBTest::OnInitialized,
         base::Unretained(this)));
-    db()->InitCallback(true);
+    db()->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
 
     RunUntilIdle();
   }
diff --git a/components/image_fetcher/core/cached_image_fetcher_unittest.cc b/components/image_fetcher/core/cached_image_fetcher_unittest.cc
index a4e8251..2c92f1b 100644
--- a/components/image_fetcher/core/cached_image_fetcher_unittest.cc
+++ b/components/image_fetcher/core/cached_image_fetcher_unittest.cc
@@ -83,8 +83,8 @@
         std::make_unique<FakeDB<CachedImageMetadataProto>>(&metadata_store_);
     db_ = db.get();
 
-    auto metadata_store = std::make_unique<ImageMetadataStoreLevelDB>(
-        base::FilePath(), std::move(db), &clock_);
+    auto metadata_store =
+        std::make_unique<ImageMetadataStoreLevelDB>(std::move(db), &clock_);
     auto data_store = std::make_unique<ImageDataStoreDisk>(
         data_dir_.GetPath(), base::SequencedTaskRunnerHandle::Get());
 
@@ -95,7 +95,7 @@
     // Use an initial request to start the cache up.
     image_cache_->SaveImage(kImageUrl.spec(), kImageData);
     RunUntilIdle();
-    db_->InitCallback(true);
+    db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
     image_cache_->DeleteImage(kImageUrl.spec());
     RunUntilIdle();
 
diff --git a/components/leveldb_proto/public/shared_proto_database_client_list.cc b/components/leveldb_proto/public/shared_proto_database_client_list.cc
index 6623e266..2c515675 100644
--- a/components/leveldb_proto/public/shared_proto_database_client_list.cc
+++ b/components/leveldb_proto/public/shared_proto_database_client_list.cc
@@ -41,6 +41,8 @@
       return "UsageStatsTokenMapping";
     case ProtoDbType::DOM_DISTILLER_STORE:
       return "DomDistillerStore";
+    case ProtoDbType::CACHED_IMAGE_METADATA_STORE:
+      return "CachedImageFetcherDatabase";
     case ProtoDbType::LAST:
       NOTREACHED();
       break;
@@ -73,4 +75,4 @@
       kProtoDBSharedMigration, kDBNameParamPrefix + name, false);
 }
 
-}  // namespace leveldb_proto
\ No newline at end of file
+}  // namespace leveldb_proto
diff --git a/components/leveldb_proto/public/shared_proto_database_client_list.h b/components/leveldb_proto/public/shared_proto_database_client_list.h
index 5b7b9211..c5d3434a 100644
--- a/components/leveldb_proto/public/shared_proto_database_client_list.h
+++ b/components/leveldb_proto/public/shared_proto_database_client_list.h
@@ -27,6 +27,7 @@
   USAGE_STATS_TOKEN_MAPPING = 7,
   DOM_DISTILLER_STORE = 8,
   DOWNLOAD_STORE = 9,
+  CACHED_IMAGE_METADATA_STORE = 10,
 
   LAST,
 };
@@ -55,4 +56,4 @@
 
 }  // namespace leveldb_proto
 
-#endif  // COMPONENTS_LEVELDB_PROTO_PUBLIC_SHARED_PROTO_DATABASE_CLIENT_LIST_H_
\ No newline at end of file
+#endif  // COMPONENTS_LEVELDB_PROTO_PUBLIC_SHARED_PROTO_DATABASE_CLIENT_LIST_H_
diff --git a/components/ntp_snippets/category.h b/components/ntp_snippets/category.h
index a59a489..882cce3 100644
--- a/components/ntp_snippets/category.h
+++ b/components/ntp_snippets/category.h
@@ -23,7 +23,7 @@
   RECENT_TABS_DEPRECATED,
 
   // Pages downloaded by the user for offline consumption.
-  DOWNLOADS,
+  DOWNLOADS_DEPRECATED,
 
   // Recently used bookmarks.
   BOOKMARKS_DEPRECATED,
diff --git a/components/ntp_snippets/category_rankers/click_based_category_ranker_unittest.cc b/components/ntp_snippets/category_rankers/click_based_category_ranker_unittest.cc
index baf37ec..8b80ec3 100644
--- a/components/ntp_snippets/category_rankers/click_based_category_ranker_unittest.cc
+++ b/components/ntp_snippets/category_rankers/click_based_category_ranker_unittest.cc
@@ -113,7 +113,7 @@
 TEST_F(ClickBasedCategoryRankerTest, ShouldSortLocalCategoriesBeforeRemote) {
   const Category remote_category = AddUnusedRemoteCategory();
   const Category local_category =
-      Category::FromKnownCategory(KnownCategories::DOWNLOADS);
+      Category::FromKnownCategory(KnownCategories::READING_LIST);
   EXPECT_TRUE(CompareCategories(local_category, remote_category));
   EXPECT_FALSE(CompareCategories(remote_category, local_category));
 }
@@ -124,7 +124,7 @@
   EXPECT_FALSE(CompareCategories(remote_category, remote_category));
 
   const Category local_category =
-      Category::FromKnownCategory(KnownCategories::DOWNLOADS);
+      Category::FromKnownCategory(KnownCategories::READING_LIST);
   EXPECT_FALSE(CompareCategories(local_category, local_category));
 }
 
@@ -428,29 +428,6 @@
   EXPECT_TRUE(CompareCategories(first, second));
 }
 
-TEST_F(ClickBasedCategoryRankerTest,
-       ShouldReduceLastCategoryClicksWhenDismissed) {
-  Category first = AddUnusedRemoteCategory();
-  Category second = AddUnusedRemoteCategory();
-
-  ASSERT_TRUE(CompareCategories(first, second));
-
-  NotifyOnSuggestionOpened(/*times=*/1, second);
-
-  // This should reduce the click count back to 0.
-  NotifyOnCategoryDismissed(second);
-
-  // Try to move the second category up assuming that the previous click is
-  // still there.
-  NotifyOnSuggestionOpened(
-      /*times=*/ClickBasedCategoryRanker::GetPassingMargin() - 1, second);
-
-  EXPECT_TRUE(CompareCategories(first, second));
-
-  NotifyOnSuggestionOpened(/*times=*/1, second);
-  EXPECT_FALSE(CompareCategories(first, second));
-}
-
 TEST_F(ClickBasedCategoryRankerTest, ShouldRestoreDefaultOrderOnClearHistory) {
   std::vector<KnownCategories> default_order =
       ConstantCategoryRanker::GetKnownCategoriesDefaultOrder();
@@ -639,7 +616,7 @@
        ShouldNotChangeRemainingOrderWhenInsertingBeforeCategory) {
   std::vector<KnownCategories> default_order =
       ConstantCategoryRanker::GetKnownCategoriesDefaultOrder();
-  Category anchor = Category::FromKnownCategory(default_order[2]);
+  Category anchor = Category::FromKnownCategory(default_order[0]);
   Category inserted = GetUnusedRemoteCategory();
 
   ranker()->InsertCategoryBeforeIfNecessary(inserted, anchor);
@@ -657,9 +634,7 @@
       ConstantCategoryRanker::GetKnownCategoriesDefaultOrder();
   Category first = Category::FromKnownCategory(default_order[0]);
   Category second = Category::FromKnownCategory(default_order[1]);
-  Category third = Category::FromKnownCategory(default_order[2]);
   ASSERT_TRUE(CompareCategories(first, second));
-  ASSERT_TRUE(CompareCategories(second, third));
 
   Category first_before = GetUnusedRemoteCategory();
   ranker()->InsertCategoryBeforeIfNecessary(first_before, second);
@@ -677,7 +652,6 @@
   EXPECT_TRUE(CompareCategories(second_before, second));
   EXPECT_TRUE(CompareCategories(second, second_after));
   EXPECT_TRUE(CompareCategories(second_after, first_after));
-  EXPECT_TRUE(CompareCategories(first_after, third));
 }
 
 TEST_F(ClickBasedCategoryRankerTest,
@@ -776,7 +750,7 @@
        ShouldNotChangeRemainingOrderWhenInsertingAfterCategory) {
   std::vector<KnownCategories> default_order =
       ConstantCategoryRanker::GetKnownCategoriesDefaultOrder();
-  Category anchor = Category::FromKnownCategory(default_order[2]);
+  Category anchor = Category::FromKnownCategory(default_order[0]);
   Category inserted = GetUnusedRemoteCategory();
 
   ranker()->InsertCategoryAfterIfNecessary(inserted, anchor);
diff --git a/components/ntp_snippets/category_rankers/constant_category_ranker.cc b/components/ntp_snippets/category_rankers/constant_category_ranker.cc
index b42aa5d..6ddd284 100644
--- a/components/ntp_snippets/category_rankers/constant_category_ranker.cc
+++ b/components/ntp_snippets/category_rankers/constant_category_ranker.cc
@@ -17,7 +17,6 @@
 // ContentSuggestionsService.
 constexpr KnownCategories kKnownCategoriesDefaultOrder[] = {
     KnownCategories::READING_LIST,
-    KnownCategories::DOWNLOADS,
     KnownCategories::ARTICLES,
 };
 }  // namespace
diff --git a/components/ntp_snippets/category_rankers/constant_category_ranker_unittest.cc b/components/ntp_snippets/category_rankers/constant_category_ranker_unittest.cc
index 5f99d5d7..bd0421b 100644
--- a/components/ntp_snippets/category_rankers/constant_category_ranker_unittest.cc
+++ b/components/ntp_snippets/category_rankers/constant_category_ranker_unittest.cc
@@ -62,7 +62,7 @@
 TEST_F(ConstantCategoryRankerTest, ShouldSortLocalCategoriesBeforeRemote) {
   const Category remote_category = AddUnusedRemoteCategory();
   const Category local_category =
-      Category::FromKnownCategory(KnownCategories::DOWNLOADS);
+      Category::FromKnownCategory(KnownCategories::READING_LIST);
   EXPECT_TRUE(CompareCategories(local_category, remote_category));
   EXPECT_FALSE(CompareCategories(remote_category, local_category));
 }
@@ -72,7 +72,7 @@
   EXPECT_FALSE(CompareCategories(remote_category, remote_category));
 
   const Category local_category =
-      Category::FromKnownCategory(KnownCategories::DOWNLOADS);
+      Category::FromKnownCategory(KnownCategories::READING_LIST);
   EXPECT_FALSE(CompareCategories(local_category, local_category));
 }
 
diff --git a/components/ntp_snippets/category_unittest.cc b/components/ntp_snippets/category_unittest.cc
index a03034f1..f75c792 100644
--- a/components/ntp_snippets/category_unittest.cc
+++ b/components/ntp_snippets/category_unittest.cc
@@ -9,13 +9,6 @@
 
 namespace ntp_snippets {
 
-TEST(CategoryTest, FromKnownCategoryShouldReturnSameCategoryForSameInput) {
-  const KnownCategories known_category = KnownCategories::DOWNLOADS;
-  Category first = Category::FromKnownCategory(known_category);
-  Category second = Category::FromKnownCategory(known_category);
-  EXPECT_EQ(first, second);
-}
-
 TEST(CategoryTest, ShouldIdentifyValidIDValues) {
   EXPECT_TRUE(
       Category::IsValidIDValue(static_cast<int>(KnownCategories::ARTICLES)));
@@ -39,7 +32,7 @@
 
 TEST(CategoryFactoryTest, FromIDValueShouldReturnSameKnownCategory) {
   Category known_category =
-      Category::FromKnownCategory(KnownCategories::DOWNLOADS);
+      Category::FromKnownCategory(KnownCategories::READING_LIST);
   Category known_category_by_id = Category::FromIDValue(known_category.id());
   EXPECT_EQ(known_category, known_category_by_id);
 }
diff --git a/components/ntp_snippets/content_suggestion.cc b/components/ntp_snippets/content_suggestion.cc
index 5dd75b9..036efc4 100644
--- a/components/ntp_snippets/content_suggestion.cc
+++ b/components/ntp_snippets/content_suggestion.cc
@@ -8,13 +8,6 @@
 
 namespace ntp_snippets {
 
-DownloadSuggestionExtra::DownloadSuggestionExtra() = default;
-
-DownloadSuggestionExtra::DownloadSuggestionExtra(
-    const DownloadSuggestionExtra& other) = default;
-
-DownloadSuggestionExtra::~DownloadSuggestionExtra() = default;
-
 bool ContentSuggestion::ID::operator==(const ID& rhs) const {
   return category_ == rhs.category_ &&
          id_within_category_ == rhs.id_within_category_;
@@ -51,12 +44,6 @@
   return favicon_url.GetWithEmptyPath();
 }
 
-void ContentSuggestion::set_download_suggestion_extra(
-    std::unique_ptr<DownloadSuggestionExtra> download_suggestion_extra) {
-  DCHECK(id_.category().IsKnownCategory(KnownCategories::DOWNLOADS));
-  download_suggestion_extra_ = std::move(download_suggestion_extra);
-}
-
 void ContentSuggestion::set_reading_list_suggestion_extra(
     std::unique_ptr<ReadingListSuggestionExtra> reading_list_suggestion_extra) {
   DCHECK(id_.category().IsKnownCategory(KnownCategories::READING_LIST));
diff --git a/components/ntp_snippets/content_suggestion.h b/components/ntp_snippets/content_suggestion.h
index 8d19ec3d..63971207 100644
--- a/components/ntp_snippets/content_suggestion.h
+++ b/components/ntp_snippets/content_suggestion.h
@@ -19,27 +19,6 @@
 
 namespace ntp_snippets {
 
-// DownloadSuggestionExtra contains additional data which is only available for
-// download suggestions.
-struct DownloadSuggestionExtra {
-  DownloadSuggestionExtra();
-  DownloadSuggestionExtra(const DownloadSuggestionExtra&);
-  ~DownloadSuggestionExtra();
-
-  // The GUID for the downloaded file.
-  std::string download_guid;
-  // The file path of the downloaded file once download completes.
-  base::FilePath target_file_path;
-  // The effective MIME type of downloaded content.
-  std::string mime_type;
-  // Underlying offline page identifier.
-  int64_t offline_page_id = 0;
-  // Whether or not the download suggestion is a downloaded asset.
-  // When this is true, |offline_page_id| is ignored, otherwise
-  // |target_file_path| and |mime_type| are ignored.
-  bool is_download_asset = false;
-};
-
 // ReadingListSuggestionExtra contains additional data which is only available
 // for Reading List suggestions.
 struct ReadingListSuggestionExtra {
@@ -153,15 +132,6 @@
   float score() const { return score_; }
   void set_score(float score) { score_ = score; }
 
-  // Extra information for download suggestions. Only available for DOWNLOADS
-  // suggestions (i.e., if the associated category has the
-  // KnownCategories::DOWNLOADS id).
-  DownloadSuggestionExtra* download_suggestion_extra() const {
-    return download_suggestion_extra_.get();
-  }
-  void set_download_suggestion_extra(
-      std::unique_ptr<DownloadSuggestionExtra> download_suggestion_extra);
-
   // Extra information for reading list suggestions. Only available for
   // KnownCategories::READING_LIST suggestions.
   ReadingListSuggestionExtra* reading_list_suggestion_extra() const {
@@ -204,7 +174,6 @@
   base::Time publish_date_;
   base::string16 publisher_name_;
   float score_;
-  std::unique_ptr<DownloadSuggestionExtra> download_suggestion_extra_;
   std::unique_ptr<ReadingListSuggestionExtra> reading_list_suggestion_extra_;
   std::unique_ptr<NotificationExtra> notification_extra_;
 
diff --git a/components/ntp_snippets/content_suggestions_metrics.cc b/components/ntp_snippets/content_suggestions_metrics.cc
index 113a3a2..b55533e 100644
--- a/components/ntp_snippets/content_suggestions_metrics.cc
+++ b/components/ntp_snippets/content_suggestions_metrics.cc
@@ -82,7 +82,7 @@
 enum class HistogramCategories {
   EXPERIMENTAL,
   RECENT_TABS_DEPRECATED,
-  DOWNLOADS,
+  DOWNLOADS_DEPRECATED,
   BOOKMARKS_DEPRECATED,
   PHYSICAL_WEB_PAGES_DEPRECATED,
   FOREIGN_TABS_DEPRECATED,
@@ -104,18 +104,18 @@
   // listed here.
   auto known_category = static_cast<KnownCategories>(category.id());
   switch (known_category) {
-    case KnownCategories::DOWNLOADS:
-      return HistogramCategories::DOWNLOADS;
-    case KnownCategories::BOOKMARKS_DEPRECATED:
-      return HistogramCategories::BOOKMARKS_DEPRECATED;
-    case KnownCategories::FOREIGN_TABS_DEPRECATED:
-      return HistogramCategories::FOREIGN_TABS_DEPRECATED;
     case KnownCategories::ARTICLES:
       return HistogramCategories::ARTICLES;
     case KnownCategories::READING_LIST:
       return HistogramCategories::READING_LIST;
     case KnownCategories::CONTEXTUAL:
       return HistogramCategories::CONTEXTUAL;
+    case KnownCategories::BOOKMARKS_DEPRECATED:
+      return HistogramCategories::BOOKMARKS_DEPRECATED;
+    case KnownCategories::DOWNLOADS_DEPRECATED:
+      return HistogramCategories::DOWNLOADS_DEPRECATED;
+    case KnownCategories::FOREIGN_TABS_DEPRECATED:
+      return HistogramCategories::FOREIGN_TABS_DEPRECATED;
     case KnownCategories::RECENT_TABS_DEPRECATED:
     case KnownCategories::PHYSICAL_WEB_PAGES_DEPRECATED:
     case KnownCategories::LOCAL_CATEGORIES_COUNT:
@@ -132,8 +132,6 @@
 std::string GetCategorySuffix(Category category) {
   HistogramCategories histogram_category = GetHistogramCategory(category);
   switch (histogram_category) {
-    case HistogramCategories::DOWNLOADS:
-      return "Downloads";
     case HistogramCategories::ARTICLES:
       return "Articles";
     case HistogramCategories::EXPERIMENTAL:
@@ -143,6 +141,7 @@
     case HistogramCategories::CONTEXTUAL:
       return "Contextual";
     case HistogramCategories::BOOKMARKS_DEPRECATED:
+    case HistogramCategories::DOWNLOADS_DEPRECATED:
     case HistogramCategories::FOREIGN_TABS_DEPRECATED:
     case HistogramCategories::RECENT_TABS_DEPRECATED:
     case HistogramCategories::PHYSICAL_WEB_PAGES_DEPRECATED:
diff --git a/components/ntp_snippets/content_suggestions_metrics_unittest.cc b/components/ntp_snippets/content_suggestions_metrics_unittest.cc
index 82bb90939..d1f3bba 100644
--- a/components/ntp_snippets/content_suggestions_metrics_unittest.cc
+++ b/components/ntp_snippets/content_suggestions_metrics_unittest.cc
@@ -112,17 +112,13 @@
   base::HistogramTester histogram_tester;
   OnPageShown(std::vector<Category>(
                   {Category::FromKnownCategory(KnownCategories::ARTICLES),
-                   Category::FromKnownCategory(KnownCategories::DOWNLOADS)}),
+                   Category::FromKnownCategory(KnownCategories::READING_LIST)}),
               /*suggestions_per_category=*/{10, 5},
               /*is_category_visible=*/{true, true});
   EXPECT_THAT(
       histogram_tester.GetAllSamples(
           "NewTabPage.ContentSuggestions.CountOnNtpOpenedIfVisible.Articles"),
       ElementsAre(base::Bucket(/*min=*/10, /*count=*/1)));
-  EXPECT_THAT(
-      histogram_tester.GetAllSamples(
-          "NewTabPage.ContentSuggestions.CountOnNtpOpenedIfVisible.Downloads"),
-      ElementsAre(base::Bucket(/*min=*/5, /*count=*/1)));
   EXPECT_THAT(histogram_tester.GetAllSamples(
                   "NewTabPage.ContentSuggestions.CountOnNtpOpenedIfVisible"),
               ElementsAre(base::Bucket(/*min=*/15, /*count=*/1)));
diff --git a/components/ntp_snippets/content_suggestions_service_unittest.cc b/components/ntp_snippets/content_suggestions_service_unittest.cc
index 4cb6d0e..5fcd6a0 100644
--- a/components/ntp_snippets/content_suggestions_service_unittest.cc
+++ b/components/ntp_snippets/content_suggestions_service_unittest.cc
@@ -211,19 +211,14 @@
               Eq(ContentSuggestionsService::State::ENABLED));
   Category articles_category =
       Category::FromKnownCategory(KnownCategories::ARTICLES);
-  Category offline_pages_category =
-      Category::FromKnownCategory(KnownCategories::DOWNLOADS);
   ASSERT_THAT(providers(), IsEmpty());
   EXPECT_THAT(service()->GetCategories(), IsEmpty());
   EXPECT_THAT(service()->GetCategoryStatus(articles_category),
               Eq(CategoryStatus::NOT_PROVIDED));
-  EXPECT_THAT(service()->GetCategoryStatus(offline_pages_category),
-              Eq(CategoryStatus::NOT_PROVIDED));
 
   MockContentSuggestionsProvider* provider1 =
       MakeRegisteredMockProvider(articles_category);
   provider1->FireCategoryStatusChangedWithCurrentStatus(articles_category);
-  EXPECT_THAT(providers().count(offline_pages_category), Eq(0ul));
   ASSERT_THAT(providers().count(articles_category), Eq(1ul));
   EXPECT_THAT(providers().at(articles_category), Eq(provider1));
   EXPECT_THAT(providers().size(), Eq(1ul));
@@ -231,37 +226,16 @@
               UnorderedElementsAre(articles_category));
   EXPECT_THAT(service()->GetCategoryStatus(articles_category),
               Eq(CategoryStatus::AVAILABLE));
-  EXPECT_THAT(service()->GetCategoryStatus(offline_pages_category),
-              Eq(CategoryStatus::NOT_PROVIDED));
-
-  MockContentSuggestionsProvider* provider2 =
-      MakeRegisteredMockProvider(offline_pages_category);
-  provider2->FireCategoryStatusChangedWithCurrentStatus(offline_pages_category);
-  ASSERT_THAT(providers().count(offline_pages_category), Eq(1ul));
-  EXPECT_THAT(providers().at(articles_category), Eq(provider1));
-  ASSERT_THAT(providers().count(articles_category), Eq(1ul));
-  EXPECT_THAT(providers().at(offline_pages_category), Eq(provider2));
-  EXPECT_THAT(providers().size(), Eq(2ul));
-  EXPECT_THAT(service()->GetCategories(),
-              UnorderedElementsAre(offline_pages_category, articles_category));
-  EXPECT_THAT(service()->GetCategoryStatus(articles_category),
-              Eq(CategoryStatus::AVAILABLE));
-  EXPECT_THAT(service()->GetCategoryStatus(offline_pages_category),
-              Eq(CategoryStatus::AVAILABLE));
 }
 
 TEST_F(ContentSuggestionsServiceDisabledTest, ShouldDoNothingWhenDisabled) {
   Category articles_category =
       Category::FromKnownCategory(KnownCategories::ARTICLES);
-  Category offline_pages_category =
-      Category::FromKnownCategory(KnownCategories::DOWNLOADS);
   EXPECT_THAT(service()->state(),
               Eq(ContentSuggestionsService::State::DISABLED));
   EXPECT_THAT(providers(), IsEmpty());
   EXPECT_THAT(service()->GetCategoryStatus(articles_category),
               Eq(CategoryStatus::ALL_SUGGESTIONS_EXPLICITLY_DISABLED));
-  EXPECT_THAT(service()->GetCategoryStatus(offline_pages_category),
-              Eq(CategoryStatus::ALL_SUGGESTIONS_EXPLICITLY_DISABLED));
   EXPECT_THAT(service()->GetCategories(), IsEmpty());
   EXPECT_THAT(service()->GetSuggestionsForCategory(articles_category),
               IsEmpty());
@@ -270,19 +244,14 @@
 TEST_F(ContentSuggestionsServiceTest, ShouldRedirectFetchSuggestionImage) {
   Category articles_category =
       Category::FromKnownCategory(KnownCategories::ARTICLES);
-  Category offline_pages_category =
-      Category::FromKnownCategory(KnownCategories::DOWNLOADS);
   MockContentSuggestionsProvider* provider1 =
       MakeRegisteredMockProvider(articles_category);
-  MockContentSuggestionsProvider* provider2 =
-      MakeRegisteredMockProvider(offline_pages_category);
 
   provider1->FireSuggestionsChanged(articles_category,
                                     CreateSuggestions(articles_category, {1}));
   ContentSuggestion::ID suggestion_id(articles_category, "1");
 
   EXPECT_CALL(*provider1, FetchSuggestionImageMock(suggestion_id, _));
-  EXPECT_CALL(*provider2, FetchSuggestionImageMock(_, _)).Times(0);
   service()->FetchSuggestionImage(
       suggestion_id,
       base::BindOnce(&ContentSuggestionsServiceTest::OnImageFetched,
@@ -305,25 +274,6 @@
   run_loop.Run();
 }
 
-TEST_F(ContentSuggestionsServiceTest, ShouldRedirectDismissSuggestion) {
-  Category articles_category =
-      Category::FromKnownCategory(KnownCategories::ARTICLES);
-  Category offline_pages_category =
-      Category::FromKnownCategory(KnownCategories::DOWNLOADS);
-  MockContentSuggestionsProvider* provider1 =
-      MakeRegisteredMockProvider(articles_category);
-  MockContentSuggestionsProvider* provider2 =
-      MakeRegisteredMockProvider(offline_pages_category);
-
-  provider2->FireSuggestionsChanged(
-      offline_pages_category, CreateSuggestions(offline_pages_category, {11}));
-  ContentSuggestion::ID suggestion_id(offline_pages_category, "11");
-
-  EXPECT_CALL(*provider1, DismissSuggestion(_)).Times(0);
-  EXPECT_CALL(*provider2, DismissSuggestion(suggestion_id));
-  service()->DismissSuggestion(suggestion_id);
-}
-
 TEST_F(ContentSuggestionsServiceTest, ShouldRedirectSuggestionInvalidated) {
   Category articles_category =
       Category::FromKnownCategory(KnownCategories::ARTICLES);
@@ -358,20 +308,13 @@
 TEST_F(ContentSuggestionsServiceTest, ShouldForwardSuggestions) {
   Category articles_category =
       Category::FromKnownCategory(KnownCategories::ARTICLES);
-  Category offline_pages_category =
-      Category::FromKnownCategory(KnownCategories::DOWNLOADS);
 
   // Create and register providers
   MockContentSuggestionsProvider* provider1 =
       MakeRegisteredMockProvider(articles_category);
   provider1->FireCategoryStatusChangedWithCurrentStatus(articles_category);
-  MockContentSuggestionsProvider* provider2 =
-      MakeRegisteredMockProvider(offline_pages_category);
-  provider2->FireCategoryStatusChangedWithCurrentStatus(offline_pages_category);
   ASSERT_THAT(providers().count(articles_category), Eq(1ul));
   EXPECT_THAT(providers().at(articles_category), Eq(provider1));
-  ASSERT_THAT(providers().count(offline_pages_category), Eq(1ul));
-  EXPECT_THAT(providers().at(offline_pages_category), Eq(provider2));
 
   // Create and register observer
   MockServiceObserver observer;
@@ -389,15 +332,6 @@
   provider1->FireSuggestionsChanged(
       articles_category, CreateSuggestions(articles_category, {1, 2}));
   ExpectThatSuggestionsAre(articles_category, {1, 2});
-  ExpectThatSuggestionsAre(offline_pages_category, std::vector<int>());
-  Mock::VerifyAndClearExpectations(&observer);
-
-  // Send suggestions 13 and 14
-  EXPECT_CALL(observer, OnNewSuggestions(offline_pages_category));
-  provider2->FireSuggestionsChanged(
-      offline_pages_category, CreateSuggestions(articles_category, {13, 14}));
-  ExpectThatSuggestionsAre(articles_category, {1, 2});
-  ExpectThatSuggestionsAre(offline_pages_category, {13, 14});
   Mock::VerifyAndClearExpectations(&observer);
 
   // Send suggestion 1 only
@@ -405,21 +339,6 @@
   provider1->FireSuggestionsChanged(articles_category,
                                     CreateSuggestions(articles_category, {1}));
   ExpectThatSuggestionsAre(articles_category, {1});
-  ExpectThatSuggestionsAre(offline_pages_category, {13, 14});
-  Mock::VerifyAndClearExpectations(&observer);
-
-  // provider2 reports BOOKMARKS as unavailable
-  EXPECT_CALL(observer, OnCategoryStatusChanged(
-                            offline_pages_category,
-                            CategoryStatus::CATEGORY_EXPLICITLY_DISABLED));
-  provider2->FireCategoryStatusChanged(
-      offline_pages_category, CategoryStatus::CATEGORY_EXPLICITLY_DISABLED);
-  EXPECT_THAT(service()->GetCategoryStatus(articles_category),
-              Eq(CategoryStatus::AVAILABLE));
-  EXPECT_THAT(service()->GetCategoryStatus(offline_pages_category),
-              Eq(CategoryStatus::CATEGORY_EXPLICITLY_DISABLED));
-  ExpectThatSuggestionsAre(articles_category, {1});
-  ExpectThatSuggestionsAre(offline_pages_category, std::vector<int>());
   Mock::VerifyAndClearExpectations(&observer);
 
   // Shutdown the service
@@ -431,13 +350,15 @@
 
 TEST_F(ContentSuggestionsServiceTest,
        ShouldNotReturnCategoryInfoForNonexistentCategory) {
-  Category category = Category::FromKnownCategory(KnownCategories::DOWNLOADS);
+  Category category =
+      Category::FromKnownCategory(KnownCategories::READING_LIST);
   base::Optional<CategoryInfo> result = service()->GetCategoryInfo(category);
   EXPECT_FALSE(result.has_value());
 }
 
 TEST_F(ContentSuggestionsServiceTest, ShouldReturnCategoryInfo) {
-  Category category = Category::FromKnownCategory(KnownCategories::DOWNLOADS);
+  Category category =
+      Category::FromKnownCategory(KnownCategories::READING_LIST);
   MockContentSuggestionsProvider* provider =
       MakeRegisteredMockProvider(category);
   provider->FireCategoryStatusChangedWithCurrentStatus(category);
@@ -452,7 +373,8 @@
 
 TEST_F(ContentSuggestionsServiceTest,
        ShouldRegisterNewCategoryOnNewSuggestions) {
-  Category category = Category::FromKnownCategory(KnownCategories::DOWNLOADS);
+  Category category =
+      Category::FromKnownCategory(KnownCategories::READING_LIST);
   MockContentSuggestionsProvider* provider =
       MakeRegisteredMockProvider(category);
   provider->FireCategoryStatusChangedWithCurrentStatus(category);
@@ -488,7 +410,8 @@
 
 TEST_F(ContentSuggestionsServiceTest,
        ShouldRegisterNewCategoryOnCategoryStatusChanged) {
-  Category category = Category::FromKnownCategory(KnownCategories::DOWNLOADS);
+  Category category =
+      Category::FromKnownCategory(KnownCategories::READING_LIST);
   MockContentSuggestionsProvider* provider =
       MakeRegisteredMockProvider(category);
   provider->FireCategoryStatusChangedWithCurrentStatus(category);
@@ -518,7 +441,8 @@
 }
 
 TEST_F(ContentSuggestionsServiceTest, ShouldRemoveCategoryWhenNotProvided) {
-  Category category = Category::FromKnownCategory(KnownCategories::DOWNLOADS);
+  Category category =
+      Category::FromKnownCategory(KnownCategories::READING_LIST);
   MockContentSuggestionsProvider* provider =
       MakeRegisteredMockProvider(category);
   MockServiceObserver observer;
@@ -540,17 +464,6 @@
   service()->RemoveObserver(&observer);
 }
 
-TEST_F(ContentSuggestionsServiceTest, ShouldForwardClearHistoryToProviders) {
-  Category category = Category::FromKnownCategory(KnownCategories::DOWNLOADS);
-  MockContentSuggestionsProvider* provider =
-      MakeRegisteredMockProvider(category);
-  base::Time begin = base::Time::FromTimeT(123);
-  base::Time end = base::Time::FromTimeT(456);
-  EXPECT_CALL(*provider, ClearHistory(begin, end, _));
-  base::Callback<bool(const GURL& url)> filter;
-  service()->ClearHistory(begin, end, filter);
-}
-
 TEST_F(ContentSuggestionsServiceTest,
        ShouldForwardClearHistoryToCategoryRanker) {
   auto mock_ranker = std::make_unique<MockCategoryRanker>();
@@ -567,86 +480,6 @@
   service()->ClearHistory(begin, end, filter);
 }
 
-TEST_F(ContentSuggestionsServiceTest, ShouldForwardDeleteAllHistoryURLs) {
-  StrictMock<MockServiceObserver> observer;
-  service()->AddObserver(&observer);
-  EXPECT_CALL(observer, OnFullRefreshRequired());
-
-  Category category = Category::FromKnownCategory(KnownCategories::DOWNLOADS);
-  MockContentSuggestionsProvider* provider =
-      MakeRegisteredMockProvider(category);
-  EXPECT_CALL(*provider, ClearHistory(base::Time(), base::Time::Max(), _));
-  static_cast<history::HistoryServiceObserver*>(service())->OnURLsDeleted(
-      /*history_service=*/nullptr, history::DeletionInfo::ForAllHistory());
-
-  service()->RemoveObserver(&observer);
-}
-
-TEST_F(ContentSuggestionsServiceTest, ShouldIgnoreExpiredURLDeletions) {
-  Category category = Category::FromKnownCategory(KnownCategories::DOWNLOADS);
-  MakeRegisteredMockProvider(category);
-  // Create an observer to make sure OnFullRefresh() does not get triggered.
-  StrictMock<MockServiceObserver> observer;
-  service()->AddObserver(&observer);
-
-  static_cast<history::HistoryServiceObserver*>(service())->OnURLsDeleted(
-      /*history_service=*/nullptr,
-      history::DeletionInfo(history::DeletionTimeRange::AllTime(),
-                            /*expired=*/true, history::URLRows(),
-                            /*favicon_urls=*/std::set<GURL>(),
-                            /*restrict_urls=*/base::nullopt));
-  static_cast<history::HistoryServiceObserver*>(service())->OnURLsDeleted(
-      /*history_service=*/nullptr,
-      history::DeletionInfo(history::DeletionTimeRange::Invalid(),
-                            /*expired=*/true,
-                            {history::URLRow(GURL("http://google.com")),
-                             history::URLRow(GURL("http://maps.google.com"))},
-                            /*favicon_urls=*/std::set<GURL>(),
-                            /*restrict_urls=*/base::nullopt));
-
-  service()->RemoveObserver(&observer);
-}
-
-TEST_F(ContentSuggestionsServiceTest, ShouldIgnoreEmptyURLDeletions) {
-  Category category = Category::FromKnownCategory(KnownCategories::DOWNLOADS);
-  MakeRegisteredMockProvider(category);
-  static_cast<history::HistoryServiceObserver*>(service())->OnURLsDeleted(
-      /*history_service=*/nullptr, history::DeletionInfo::ForUrls({}, {}));
-}
-
-TEST_F(ContentSuggestionsServiceTest,
-       ShouldNotClearHistoryOnSingleURLDeletion) {
-  Category category = Category::FromKnownCategory(KnownCategories::DOWNLOADS);
-  MakeRegisteredMockProvider(category);
-  // Single URLs should not trigger
-  static_cast<history::HistoryServiceObserver*>(service())->OnURLsDeleted(
-      /*history_service=*/nullptr,
-      history::DeletionInfo::ForUrls(
-          {history::URLRow(GURL("http://google.com"))},
-          /*favicon_urls=*/std::set<GURL>()));
-}
-
-TEST_F(ContentSuggestionsServiceTest,
-       ShouldClearHistoryForMultipleURLDeletion) {
-  StrictMock<MockServiceObserver> observer;
-  service()->AddObserver(&observer);
-  EXPECT_CALL(observer, OnFullRefreshRequired());
-
-  Category category = Category::FromKnownCategory(KnownCategories::DOWNLOADS);
-  MockContentSuggestionsProvider* provider =
-      MakeRegisteredMockProvider(category);
-  EXPECT_CALL(*provider, ClearHistory(base::Time(), base::Time::Max(), _));
-
-  static_cast<history::HistoryServiceObserver*>(service())->OnURLsDeleted(
-      /*history_service=*/nullptr,
-      history::DeletionInfo::ForUrls(
-          {history::URLRow(GURL("http://google.com")),
-           history::URLRow(GURL("http://youtube.com"))},
-          /*favicon_urls=*/std::set<GURL>()));
-
-  service()->RemoveObserver(&observer);
-}
-
 TEST_F(ContentSuggestionsServiceTest, ShouldForwardFetch) {
   Category category = Category::FromKnownCategory(KnownCategories::ARTICLES);
   std::set<std::string> known_suggestions;
diff --git a/components/ntp_snippets/remote/remote_suggestion_unittest.cc b/components/ntp_snippets/remote/remote_suggestion_unittest.cc
index d7437fbb..be203fc 100644
--- a/components/ntp_snippets/remote/remote_suggestion_unittest.cc
+++ b/components/ntp_snippets/remote/remote_suggestion_unittest.cc
@@ -282,7 +282,6 @@
   EXPECT_THAT(sugg.score(), Eq(9001));
   EXPECT_THAT(sugg.salient_image_url(),
               Eq(GURL("http://localhost/foobar.jpg")));
-  EXPECT_THAT(sugg.download_suggestion_extra(), IsNull());
   EXPECT_THAT(sugg.notification_extra(), IsNull());
   EXPECT_THAT(sugg.fetch_date(), Eq(fetch_date));
 }
@@ -306,7 +305,6 @@
   EXPECT_THAT(sugg.publish_date().ToJavaTime(), Eq(1467284497000));
   EXPECT_THAT(sugg.publisher_name(), Eq(base::UTF8ToUTF16("Foo News")));
   EXPECT_THAT(sugg.score(), Eq(9001));
-  EXPECT_THAT(sugg.download_suggestion_extra(), IsNull());
   ASSERT_THAT(sugg.notification_extra(), NotNull());
   EXPECT_THAT(sugg.notification_extra()->deadline.ToJavaTime(),
               Eq(1467291697000));
diff --git a/components/omnibox/browser/location_bar_model.h b/components/omnibox/browser/location_bar_model.h
index 42af21c..544026f 100644
--- a/components/omnibox/browser/location_bar_model.h
+++ b/components/omnibox/browser/location_bar_model.h
@@ -18,9 +18,9 @@
 struct VectorIcon;
 }
 
-// This class is the model used by the toolbar, location bar and autocomplete
-// edit.  It populates its states from the current navigation entry retrieved
-// from the navigation controller returned by GetNavigationController().
+// This class provides information about the current navigation entry.
+// Its methods always return data related to the current page, and does not
+// account for the state of the omnibox, which is tracked by OmniboxEditModel.
 class LocationBarModel {
  public:
   virtual ~LocationBarModel() = default;
@@ -41,9 +41,7 @@
   // Returns the URL of the current navigation entry.
   virtual GURL GetURL() const = 0;
 
-  // Returns the security level that the toolbar should display.  This reflects
-  // the underlying state of the page without regard to any user edits that may
-  // be in progress in the omnibox.
+  // Returns the security level that the toolbar should display.
   virtual security_state::SecurityLevel GetSecurityLevel() const = 0;
 
   // Returns true if the toolbar should display the search terms. When this
@@ -56,20 +54,14 @@
 
   // Returns the id of the icon to show to the left of the address, based on the
   // current URL.  When search term replacement is active, this returns a search
-  // icon.  This always shows the icon based on the current page's security
-  // state, and doesn't account for user editing, or show any specialized icons
-  // for input in progress. See OmniboxView::GetIcon() for those.
+  // icon.
   virtual const gfx::VectorIcon& GetVectorIcon() const = 0;
 
   // Returns text for the omnibox secure verbose chip, displayed next to the
-  // security icon on certain platforms.  Always returns the text corresponding
-  // to the currently displayed page, irrespective of any user input in
-  // progress or displayed suggestions.
+  // security icon on certain platforms.
   virtual base::string16 GetSecureDisplayText() const = 0;
 
-  // Returns text describing the security state for accessibility.  Always
-  // returns the text corresponding to the currently displayed page,
-  // irrespective of any user input in progress or displayed suggestions.
+  // Returns text describing the security state for accessibility.
   virtual base::string16 GetSecureAccessibilityText() const = 0;
 
   // Returns whether the URL for the current navigation entry should be
@@ -80,18 +72,10 @@
   // previously-downloaded content.
   virtual bool IsOfflinePage() const = 0;
 
-  // Whether the text in the omnibox is currently being edited.
-  void set_input_in_progress(bool input_in_progress) {
-    input_in_progress_ = input_in_progress;
-  }
-  bool input_in_progress() const { return input_in_progress_; }
-
  protected:
-  LocationBarModel() : input_in_progress_(false) {}
+  LocationBarModel() = default;
 
  private:
-  bool input_in_progress_;
-
   DISALLOW_COPY_AND_ASSIGN(LocationBarModel);
 };
 
diff --git a/components/safe_browsing/web_ui/safe_browsing_ui.cc b/components/safe_browsing/web_ui/safe_browsing_ui.cc
index 60f878fd..340eca0 100644
--- a/components/safe_browsing/web_ui/safe_browsing_ui.cc
+++ b/components/safe_browsing/web_ui/safe_browsing_ui.cc
@@ -59,12 +59,13 @@
 
 // static
 bool WebUIInfoSingleton::HasListener() {
-  return !GetInstance()->webui_instances_.empty();
+  return GetInstance()->has_test_listener_ ||
+         !GetInstance()->webui_instances_.empty();
 }
 
 void WebUIInfoSingleton::AddToClientDownloadRequestsSent(
     std::unique_ptr<ClientDownloadRequest> client_download_request) {
-  if (webui_instances_.empty())
+  if (!HasListener())
     return;
 
   for (auto* webui_listener : webui_instances_)
@@ -80,7 +81,7 @@
 
 void WebUIInfoSingleton::AddToClientDownloadResponsesReceived(
     std::unique_ptr<ClientDownloadResponse> client_download_response) {
-  if (webui_instances_.empty())
+  if (!HasListener())
     return;
 
   for (auto* webui_listener : webui_instances_)
@@ -97,7 +98,7 @@
 
 void WebUIInfoSingleton::AddToCSBRRsSent(
     std::unique_ptr<ClientSafeBrowsingReportRequest> csbrr) {
-  if (webui_instances_.empty())
+  if (!HasListener())
     return;
 
   for (auto* webui_listener : webui_instances_)
@@ -112,7 +113,7 @@
 
 void WebUIInfoSingleton::AddToPGEvents(
     const sync_pb::UserEventSpecifics& event) {
-  if (webui_instances_.empty())
+  if (!HasListener())
     return;
 
   for (auto* webui_listener : webui_instances_)
@@ -127,7 +128,7 @@
 
 int WebUIInfoSingleton::AddToPGPings(
     const LoginReputationClientRequest& request) {
-  if (webui_instances_.empty())
+  if (!HasListener())
     return -1;
 
   for (auto* webui_listener : webui_instances_)
@@ -141,7 +142,7 @@
 void WebUIInfoSingleton::AddToPGResponses(
     int token,
     const LoginReputationClientResponse& response) {
-  if (webui_instances_.empty())
+  if (!HasListener())
     return;
 
   for (auto* webui_listener : webui_instances_)
@@ -156,7 +157,7 @@
 }
 
 void WebUIInfoSingleton::LogMessage(const std::string& message) {
-  if (webui_instances_.empty())
+  if (!HasListener())
     return;
 
   base::Time timestamp = base::Time::Now();
@@ -176,7 +177,7 @@
 
 void WebUIInfoSingleton::UnregisterWebUIInstance(SafeBrowsingUIHandler* webui) {
   base::Erase(webui_instances_, webui);
-  if (webui_instances_.empty()) {
+  if (!HasListener()) {
     ClearCSBRRsSent();
     ClearClientDownloadRequestsSent();
     ClearClientDownloadResponsesReceived();
diff --git a/components/safe_browsing/web_ui/safe_browsing_ui.h b/components/safe_browsing/web_ui/safe_browsing_ui.h
index dcd3e82..23d4515 100644
--- a/components/safe_browsing/web_ui/safe_browsing_ui.h
+++ b/components/safe_browsing/web_ui/safe_browsing_ui.h
@@ -251,6 +251,8 @@
     return log_messages_;
   }
 
+  void AddListenerForTesting() { has_test_listener_ = true; }
+
  private:
   WebUIInfoSingleton();
   ~WebUIInfoSingleton();
@@ -301,6 +303,9 @@
   // The current referrer chain provider, if any. Can be nullptr.
   ReferrerChainProvider* referrer_chain_provider_ = nullptr;
 
+  // Whether there is a test listener.
+  bool has_test_listener_ = false;
+
   DISALLOW_COPY_AND_ASSIGN(WebUIInfoSingleton);
 };
 
diff --git a/components/signin/core/browser/android/javatests/src/org/chromium/components/signin/test/util/FakeAccountManagerDelegate.java b/components/signin/core/browser/android/javatests/src/org/chromium/components/signin/test/util/FakeAccountManagerDelegate.java
index 1f34b1a..5009bcc 100644
--- a/components/signin/core/browser/android/javatests/src/org/chromium/components/signin/test/util/FakeAccountManagerDelegate.java
+++ b/components/signin/core/browser/android/javatests/src/org/chromium/components/signin/test/util/FakeAccountManagerDelegate.java
@@ -251,7 +251,12 @@
 
     @Override
     public boolean hasFeatures(Account account, String[] features) {
-        final AccountHolder accountHolder = getAccountHolder(account);
+        @Nullable
+        AccountHolder accountHolder = tryGetAccountHolder(account);
+        if (accountHolder == null) {
+            // Features status is queried asynchronously, so the account could have been removed.
+            return false;
+        }
         Set<String> accountFeatures = accountHolder.getFeatures();
         boolean hasAllFeatures = true;
         for (String feature : features) {
diff --git a/components/tracing/common/stack_sampler_android.cc b/components/tracing/common/stack_sampler_android.cc
index 01a69dd..9cb2349 100644
--- a/components/tracing/common/stack_sampler_android.cc
+++ b/components/tracing/common/stack_sampler_android.cc
@@ -17,6 +17,10 @@
 
 StackSamplerAndroid::~StackSamplerAndroid() = default;
 
+// Unimplemented. StackSamplerAndroid needs to be implemented in terms of
+// base::StackSamplerImpl to make use of this.
+void StackSamplerAndroid::AddAuxUnwinder(base::Unwinder* unwinder) {}
+
 void StackSamplerAndroid::RecordStackFrames(
     StackBuffer* stack_buffer,
     base::ProfileBuilder* profile_builder) {
diff --git a/components/tracing/common/stack_sampler_android.h b/components/tracing/common/stack_sampler_android.h
index 1022f3c..c8e6a6f64 100644
--- a/components/tracing/common/stack_sampler_android.h
+++ b/components/tracing/common/stack_sampler_android.h
@@ -24,7 +24,8 @@
   StackSamplerAndroid(const StackSamplerAndroid&) = delete;
   StackSamplerAndroid& operator=(const StackSamplerAndroid&) = delete;
 
-  // StackSamplingProfiler::StackSampler:
+  // StackSampler:
+  void AddAuxUnwinder(base::Unwinder* unwinder) override;
   void RecordStackFrames(StackBuffer* stack_buffer,
                          base::ProfileBuilder* profile_builder) override;
 
diff --git a/components/tracing/common/trace_startup_config.cc b/components/tracing/common/trace_startup_config.cc
index 47b396f..a8f1051 100644
--- a/components/tracing/common/trace_startup_config.cc
+++ b/components/tracing/common/trace_startup_config.cc
@@ -80,13 +80,14 @@
   return trace_config;
 }
 
-TraceStartupConfig::TraceStartupConfig()
-    : is_enabled_(false),
-      is_enabled_from_background_tracing_(false),
-      trace_config_(base::trace_event::TraceConfig()),
-      startup_duration_(0),
-      should_trace_to_result_file_(false),
-      finished_writing_to_file_(false) {
+TraceStartupConfig::TraceStartupConfig() {
+  auto* command_line = base::CommandLine::ForCurrentProcess();
+  if (!command_line->HasSwitch(switches::kDisablePerfetto) &&
+      command_line->GetSwitchValueASCII(switches::kTraceStartupOwner) ==
+          "devtools") {
+    session_owner_ = SessionOwner::kDevToolsTracingHandler;
+  }
+
   if (EnableFromCommandLine()) {
     DCHECK(IsEnabled());
   } else if (EnableFromConfigFile()) {
@@ -115,7 +116,8 @@
 }
 
 bool TraceStartupConfig::IsTracingStartupForDuration() const {
-  return IsEnabled() && startup_duration_ > 0;
+  return IsEnabled() && startup_duration_ > 0 &&
+         session_owner_ == SessionOwner::kTracingController;
 }
 
 base::trace_event::TraceConfig TraceStartupConfig::GetTraceConfig() const {
@@ -157,6 +159,20 @@
 #endif
 }
 
+TraceStartupConfig::SessionOwner TraceStartupConfig::GetSessionOwner() const {
+  DCHECK(IsEnabled());
+  return session_owner_;
+}
+
+bool TraceStartupConfig::AttemptAdoptBySessionOwner(SessionOwner owner) {
+  if (IsEnabled() && GetSessionOwner() == owner && !session_adopted_) {
+    // The session can only be adopted once.
+    session_adopted_ = true;
+    return true;
+  }
+  return false;
+}
+
 bool TraceStartupConfig::EnableFromCommandLine() {
   auto* command_line = base::CommandLine::ForCurrentProcess();
 
diff --git a/components/tracing/common/trace_startup_config.h b/components/tracing/common/trace_startup_config.h
index 675ce3e..bc132ab 100644
--- a/components/tracing/common/trace_startup_config.h
+++ b/components/tracing/common/trace_startup_config.h
@@ -76,6 +76,8 @@
 // TracingControllerAndroid::GenerateTracingFilePath.
 class TRACING_EXPORT TraceStartupConfig {
  public:
+  enum class SessionOwner { kTracingController, kDevToolsTracingHandler };
+
   static TraceStartupConfig* GetInstance();
 
   // Default minimum startup trace config with enough events to debug issues.
@@ -119,6 +121,13 @@
     return finished_writing_to_file_;
   }
 
+  SessionOwner GetSessionOwner() const;
+
+  // Called by a potential session owner to determine if it should take
+  // ownership of the startup tracing session and begin tracing. Returns |true|
+  // if the passed |owner| should adopt the session.
+  bool AttemptAdoptBySessionOwner(SessionOwner owner);
+
  private:
   // This allows constructor and destructor to be private and usable only
   // by the Singleton class.
@@ -136,13 +145,15 @@
 
   bool ParseTraceConfigFileContent(const std::string& content);
 
-  bool is_enabled_;
-  bool is_enabled_from_background_tracing_;
+  bool is_enabled_ = false;
+  bool is_enabled_from_background_tracing_ = false;
   base::trace_event::TraceConfig trace_config_;
-  int startup_duration_;
-  bool should_trace_to_result_file_;
+  int startup_duration_ = 0;
+  bool should_trace_to_result_file_ = false;
   base::FilePath result_file_;
-  bool finished_writing_to_file_;
+  bool finished_writing_to_file_ = false;
+  SessionOwner session_owner_ = SessionOwner::kTracingController;
+  bool session_adopted_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(TraceStartupConfig);
 };
diff --git a/components/tracing/common/tracing_switches.cc b/components/tracing/common/tracing_switches.cc
index 3899cf20..26125bf 100644
--- a/components/tracing/common/tracing_switches.cc
+++ b/components/tracing/common/tracing_switches.cc
@@ -54,6 +54,18 @@
 // "record-until-full" mode will be used.
 const char kTraceStartupRecordMode[] = "trace-startup-record-mode";
 
+// Specifies the coordinator of the startup tracing session. If the legacy
+// tracing backend is used instead of perfetto, providing this flag is not
+// necessary. Valid values: 'controller', 'devtools'. Defaults to 'controller'.
+//
+// If 'controller' is specified, the session is controlled and stopped via the
+// TracingController (e.g. to implement the timeout).
+//
+// If 'devtools' is specified, the startup tracing session will be owned by
+// DevTools and thus can be controlled (i.e. stopped) via the DevTools Tracing
+// domain on the first session connected to the browser endpoint.
+const char kTraceStartupOwner[] = "trace-startup-owner";
+
 // Disables the perfetto tracing backend. We need a separate command line
 // argument from the kTracingPerfettoBackend feature, because feature flags are
 // parsed too late during startup for early startup tracing support.
diff --git a/components/tracing/common/tracing_switches.h b/components/tracing/common/tracing_switches.h
index d81c6d0..4bc094be 100644
--- a/components/tracing/common/tracing_switches.h
+++ b/components/tracing/common/tracing_switches.h
@@ -17,6 +17,7 @@
 TRACING_EXPORT extern const char kTraceStartupDuration[];
 TRACING_EXPORT extern const char kTraceStartupFile[];
 TRACING_EXPORT extern const char kTraceStartupRecordMode[];
+TRACING_EXPORT extern const char kTraceStartupOwner[];
 TRACING_EXPORT extern const char kDisablePerfetto[];
 TRACING_EXPORT extern const char kPerfettoDisableInterning[];
 TRACING_EXPORT extern const char kPerfettoOutputFile[];
diff --git a/components/user_manager/known_user.cc b/components/user_manager/known_user.cc
index 8501c20..52c554b 100644
--- a/components/user_manager/known_user.cc
+++ b/components/user_manager/known_user.cc
@@ -25,7 +25,8 @@
 // placed in this list.
 const char kKnownUsers[] = "KnownUsers";
 
-// Known user preferences keys (stored in Local State).
+// Known user preferences keys (stored in Local State). All keys should be
+// listed in kReservedKeys below.
 
 // Key of canonical e-mail value.
 const char kCanonicalEmail[] = "email";
@@ -65,6 +66,20 @@
 // from the local state on logout.
 const char kIsEphemeral[] = "is_ephemeral";
 
+// List containing all the known user preferences keys.
+const char* kReservedKeys[] = {kCanonicalEmail,
+                               kGAIAIdKey,
+                               kObjGuidKey,
+                               kAccountTypeKey,
+                               kUsingSAMLKey,
+                               kDeviceId,
+                               kGAPSCookie,
+                               kReauthReasonKey,
+                               kGaiaIdMigration,
+                               kMinimalMigrationAttempted,
+                               kProfileRequiresPolicy,
+                               kIsEphemeral};
+
 PrefService* GetLocalState() {
   if (!UserManager::IsInitialized())
     return nullptr;
@@ -206,13 +221,6 @@
 void SetStringPref(const AccountId& account_id,
                    const std::string& path,
                    const std::string& in_value) {
-  PrefService* local_state = GetLocalState();
-
-  // Local State may not be initialized in tests.
-  if (!local_state)
-    return;
-
-  ListPrefUpdate update(local_state, kKnownUsers);
   base::DictionaryValue dict;
   dict.SetString(path, in_value);
   UpdatePrefs(account_id, dict, false);
@@ -231,13 +239,6 @@
 void SetBooleanPref(const AccountId& account_id,
                     const std::string& path,
                     const bool in_value) {
-  PrefService* local_state = GetLocalState();
-
-  // Local State may not be initialized in tests.
-  if (!local_state)
-    return;
-
-  ListPrefUpdate update(local_state, kKnownUsers);
   base::DictionaryValue dict;
   dict.SetBoolean(path, in_value);
   UpdatePrefs(account_id, dict, false);
@@ -255,18 +256,47 @@
 void SetIntegerPref(const AccountId& account_id,
                     const std::string& path,
                     const int in_value) {
-  PrefService* local_state = GetLocalState();
-
-  // Local State may not be initialized in tests.
-  if (!local_state)
-    return;
-
-  ListPrefUpdate update(local_state, kKnownUsers);
   base::DictionaryValue dict;
   dict.SetInteger(path, in_value);
   UpdatePrefs(account_id, dict, false);
 }
 
+bool GetPref(const AccountId& account_id,
+             const std::string& path,
+             const base::Value** out_value) {
+  const base::DictionaryValue* user_pref_dict = nullptr;
+  if (!FindPrefs(account_id, &user_pref_dict))
+    return false;
+
+  *out_value = user_pref_dict->FindPath(path);
+  return *out_value != nullptr;
+}
+
+void SetPref(const AccountId& account_id,
+             const std::string& path,
+             base::Value in_value) {
+  base::DictionaryValue dict;
+  dict.SetPath(path, std::move(in_value));
+  UpdatePrefs(account_id, dict, false);
+}
+
+void RemovePref(const AccountId& account_id, const std::string& path) {
+  // Prevent removing keys that are used internally.
+  for (const std::string& key : kReservedKeys)
+    CHECK_NE(path, key);
+
+  const base::DictionaryValue* user_pref_dict = nullptr;
+  if (!FindPrefs(account_id, &user_pref_dict))
+    return;
+
+  base::Value updated_user_pref = user_pref_dict->Clone();
+  base::DictionaryValue* updated_user_pref_dict;
+  updated_user_pref.GetAsDictionary(&updated_user_pref_dict);
+
+  updated_user_pref_dict->RemovePath(path);
+  UpdatePrefs(account_id, *updated_user_pref_dict, true);
+}
+
 AccountId GetAccountId(const std::string& user_email,
                        const std::string& id,
                        const AccountType& account_type) {
diff --git a/components/user_manager/known_user.h b/components/user_manager/known_user.h
index a76f7faa..1c65bf88 100644
--- a/components/user_manager/known_user.h
+++ b/components/user_manager/known_user.h
@@ -16,6 +16,7 @@
 
 namespace base {
 class DictionaryValue;
+class Value;
 }
 
 namespace user_manager {
@@ -68,6 +69,21 @@
                                         const std::string& path,
                                         const int in_value);
 
+// Returns true if |account_id| preference by |path| does exist,
+// fills in |out_value|. Otherwise returns false.
+bool USER_MANAGER_EXPORT GetPref(const AccountId& account_id,
+                                 const std::string& path,
+                                 const base::Value** out_value);
+
+// Updates user's identified by |account_id| value preference |path|.
+void USER_MANAGER_EXPORT SetPref(const AccountId& account_id,
+                                 const std::string& path,
+                                 base::Value in_value);
+
+// Removes user's identified by |account_id| preference |path|.
+void USER_MANAGER_EXPORT RemovePref(const AccountId& account_id,
+                                    const std::string& path);
+
 // Returns the list of known AccountIds.
 std::vector<AccountId> USER_MANAGER_EXPORT GetKnownAccountIds();
 
diff --git a/components/viz/service/display/renderer_pixeltest.cc b/components/viz/service/display/renderer_pixeltest.cc
index abdfcd6f..ea93536a 100644
--- a/components/viz/service/display/renderer_pixeltest.cc
+++ b/components/viz/service/display/renderer_pixeltest.cc
@@ -1111,6 +1111,8 @@
       cc::FuzzyPixelOffByOneComparator(true)));
 }
 
+// TODO(crbug.com/924369): SkiaRenderer should not ignore the color matrix on
+// the OutputSurface.
 TYPED_TEST(GLOnlyRendererPixelTest, SolidColorWithTemperature) {
   gfx::Rect rect(this->device_viewport_size_);
 
@@ -1137,6 +1139,8 @@
       cc::FuzzyPixelOffByOneComparator(true)));
 }
 
+// TODO(crbug.com/924369): SkiaRenderer should not ignore the color matrix on
+// the OutputSurface.
 TYPED_TEST(GLOnlyRendererPixelTest,
            SolidColorWithTemperature_NonRootRenderPass) {
   // Create a root and a child passes with two different solid color quads.
@@ -1179,7 +1183,7 @@
       cc::FuzzyPixelOffByOneComparator(true)));
 }
 
-TYPED_TEST(GLOnlyRendererPixelTest,
+TYPED_TEST(GLCapableRendererPixelTest,
            PremultipliedTextureWithBackgroundAndVertexOpacity) {
   gfx::Rect rect(this->device_viewport_size_);
 
@@ -4247,7 +4251,7 @@
       cc::FuzzyPixelOffByOneComparator(true)));
 }
 
-TYPED_TEST(GLOnlyRendererPixelTest, TileQuadClamping) {
+TYPED_TEST(GLCapableRendererPixelTest, TileQuadClamping) {
   gfx::Rect viewport(this->device_viewport_size_);
   bool swizzle_contents = true;
   bool contents_premultiplied = true;
@@ -4371,7 +4375,7 @@
   }
 }
 
-TYPED_TEST(GLOnlyRendererPixelTest, RoundedCornerSimpleTextureDrawQuad) {
+TYPED_TEST(GLCapableRendererPixelTest, RoundedCornerSimpleTextureDrawQuad) {
   gfx::Rect viewport_rect(this->device_viewport_size_);
   constexpr int kInset = 20;
   constexpr int kCornerRadius = 20;
@@ -4424,10 +4428,22 @@
   RenderPassList pass_list;
   pass_list.push_back(std::move(root_pass));
 
-  EXPECT_TRUE(this->RunPixelTest(
-      &pass_list,
-      base::FilePath(FILE_PATH_LITERAL("rounded_corner_simple.png")),
-      cc::ExactPixelComparator(true)));
+  if (std::is_same<TypeParam, GLRenderer>() ||
+      std::is_same<TypeParam, cc::GLRendererWithExpandedViewport>()) {
+    // GL Renderer should have an exact match as that is the reference point.
+    EXPECT_TRUE(this->RunPixelTest(
+        &pass_list,
+        base::FilePath(FILE_PATH_LITERAL("rounded_corner_simple.png")),
+        cc::ExactPixelComparator(true)));
+  } else {
+    // SkiaRenderer uses skia rrect to create rounded corner clip. This results
+    // in a different corner path due to a different anti aliasing approach than
+    // the fragment shader in gl renderer.
+    EXPECT_TRUE(this->RunPixelTest(
+        &pass_list,
+        base::FilePath(FILE_PATH_LITERAL("rounded_corner_simple.png")),
+        cc::FuzzyPixelComparator(true, 0.6f, 0.f, 255.f, 255, 0)));
+  }
 }
 
 // This draws a render pass with 2 solid color quads one of which has a rounded
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_non_ddl.cc b/components/viz/service/display_embedder/skia_output_surface_impl_non_ddl.cc
index 6b3c548..61a11a4e 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl_non_ddl.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl_non_ddl.cc
@@ -500,17 +500,7 @@
                    "with display usage.";
     return nullptr;
   }
-  if (!sk_surface_) {
-    // Create a dummy sk surface to make SharedImage happy.
-    // TODO(penghuang): remove the dummy sk surface when BeginReadAccess()
-    // doesn't need pass in a sk surface.
-    auto image_info =
-        SkImageInfo::Make(1, 1, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
-    sk_surface_ = SkSurface::MakeRenderTarget(
-        gr_context(), SkBudgeted::kNo, image_info, 0 /* sampleCount */,
-        kBottomLeft_GrSurfaceOrigin, nullptr /* surfaceProps */);
-  }
-  auto promise_texture = representation->BeginReadAccess(sk_surface_.get());
+  auto promise_texture = representation->BeginReadAccess();
   if (!promise_texture) {
     DLOG(ERROR)
         << "Failed to begin read access for SharedImageRepresentationSkia";
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
index 91e70c5..7416ad1 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
@@ -679,8 +679,7 @@
     *shared_image_out = std::move(shared_image);
   }
   if (*shared_image_out) {
-    auto promise_texture =
-        (*shared_image_out)->BeginReadAccess(output_sk_surface());
+    auto promise_texture = (*shared_image_out)->BeginReadAccess();
     DLOG_IF(ERROR, !promise_texture)
         << "Failed to begin read access for SharedImageRepresentationSkia";
     return promise_texture;
diff --git a/content/DEPS b/content/DEPS
index b010e3e..c33d096 100644
--- a/content/DEPS
+++ b/content/DEPS
@@ -16,7 +16,7 @@
 
   "-components",
   # Content can depend on components that are:
-  #   1) related to the implementation of the web platform
+  #   1) related to the implementation of the web platform, or,
   #   2) shared code between third_party/blink and content
   # It should not depend on chrome features or implementation details, i.e. the
   # original components/ directories which was code split out from chrome/ to be
diff --git a/content/browser/browser_main_loop.cc b/content/browser/browser_main_loop.cc
index 9168181a..ff63c8a 100644
--- a/content/browser/browser_main_loop.cc
+++ b/content/browser/browser_main_loop.cc
@@ -303,11 +303,6 @@
 }
 #endif  // defined(USE_GLIB)
 
-void OnStoppedStartupTracing(const base::FilePath& trace_file) {
-  VLOG(0) << "Completed startup tracing to " << trace_file.value();
-  tracing::TraceStartupConfig::GetInstance()->OnTraceToResultFileFinished();
-}
-
 // Tell compiler not to inline this function so it's possible to tell what
 // thread was unresponsive by inspecting the callstack.
 NOINLINE void ResetThread_IO(
@@ -1203,10 +1198,6 @@
 #endif
 }
 
-void BrowserMainLoop::StopStartupTracingTimer() {
-  startup_trace_timer_.Stop();
-}
-
 void BrowserMainLoop::InitializeMainThread() {
   TRACE_EVENT0("startup", "BrowserMainLoop::InitializeMainThread");
   base::PlatformThread::SetName("CrBrowserMain");
@@ -1617,25 +1608,9 @@
   // Start startup tracing through TracingController's interface. TraceLog has
   // been enabled in content_main_runner where threads are not available. Now We
   // need to start tracing for all other tracing agents, which require threads.
-  auto* trace_startup_config = tracing::TraceStartupConfig::GetInstance();
-  if (trace_startup_config->IsEnabled()) {
-    // This checks kTraceConfigFile switch.
-    TracingController::GetInstance()->StartTracing(
-        trace_startup_config->GetTraceConfig(),
-        TracingController::StartTracingDoneCallback());
-  } else if (parsed_command_line_.HasSwitch(switches::kTraceToConsole)) {
-    TracingController::GetInstance()->StartTracing(
-        tracing::GetConfigForTraceToConsole(),
-        TracingController::StartTracingDoneCallback());
-  }
-  // Start tracing to a file for certain duration if needed. Only do this after
-  // starting the main message loop to avoid calling
-  // MessagePumpForUI::ScheduleWork() before MessagePumpForUI::Start() as it
-  // will crash the browser.
-  if (trace_startup_config->IsTracingStartupForDuration()) {
-    TRACE_EVENT0("startup", "BrowserMainLoop::InitStartupTracingForDuration");
-    InitStartupTracingForDuration();
-  }
+  // We can only do this after starting the main message loop to avoid calling
+  // MessagePumpForUI::ScheduleWork() before MessagePumpForUI::Start().
+  TracingControllerImpl::GetInstance()->StartStartupTracingIfNeeded();
 
   if (parts_) {
     parts_->ServiceManagerConnectionStarted(
@@ -1647,46 +1622,6 @@
 #endif
 }
 
-base::FilePath BrowserMainLoop::GetStartupTraceFileName() const {
-  base::FilePath trace_file;
-
-  trace_file = tracing::TraceStartupConfig::GetInstance()->GetResultFile();
-  if (trace_file.empty()) {
-#if defined(OS_ANDROID)
-    TracingControllerAndroid::GenerateTracingFilePath(&trace_file);
-#else
-    // Default to saving the startup trace into the current dir.
-    trace_file = base::FilePath().AppendASCII("chrometrace.log");
-#endif
-  }
-
-  return trace_file;
-}
-
-void BrowserMainLoop::InitStartupTracingForDuration() {
-  DCHECK(tracing::TraceStartupConfig::GetInstance()
-             ->IsTracingStartupForDuration());
-
-  startup_trace_file_ = GetStartupTraceFileName();
-
-  startup_trace_timer_.Start(
-      FROM_HERE,
-      base::TimeDelta::FromSeconds(
-          tracing::TraceStartupConfig::GetInstance()->GetStartupDuration()),
-      this, &BrowserMainLoop::EndStartupTracing);
-}
-
-void BrowserMainLoop::EndStartupTracing() {
-  // Do nothing if startup tracing is already stopped.
-  if (!tracing::TraceStartupConfig::GetInstance()->IsEnabled())
-    return;
-
-  TracingController::GetInstance()->StopTracing(
-      TracingController::CreateFileEndpoint(
-          startup_trace_file_,
-          base::Bind(OnStoppedStartupTracing, startup_trace_file_)));
-}
-
 void BrowserMainLoop::InitializeAudio() {
   DCHECK(!audio_manager_);
 
diff --git a/content/browser/browser_main_loop.h b/content/browser/browser_main_loop.h
index 24cca0cf..d7939ebb 100644
--- a/content/browser/browser_main_loop.h
+++ b/content/browser/browser_main_loop.h
@@ -12,7 +12,6 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/task/task_scheduler/task_scheduler.h"
-#include "base/timer/timer.h"
 #include "build/build_config.h"
 #include "content/browser/browser_process_sub_thread.h"
 #include "content/public/browser/browser_main_runner.h"
@@ -33,7 +32,6 @@
 
 namespace base {
 class CommandLine;
-class FilePath;
 class HighResolutionTimerManager;
 class MemoryPressureMonitor;
 class PowerMonitor;
@@ -196,12 +194,6 @@
   }
   midi::MidiService* midi_service() const { return midi_service_.get(); }
 
-  base::FilePath GetStartupTraceFileName() const;
-
-  const base::FilePath& startup_trace_file() const {
-    return startup_trace_file_;
-  }
-
   // Returns the task runner for tasks that that are critical to producing a new
   // CompositorFrame on resize. On Mac this will be the task runner provided by
   // WindowResizeHelperMac, on other platforms it will just be the thread task
@@ -239,8 +231,6 @@
   void GetCompositingModeReporter(
       viz::mojom::CompositingModeReporterRequest request);
 
-  void StopStartupTracingTimer();
-
 #if defined(OS_MACOSX) && !defined(OS_IOS)
   media::DeviceMonitorMac* device_monitor_mac() const {
     return device_monitor_mac_.get();
@@ -271,8 +261,6 @@
   void MainMessageLoopRun();
 
   void InitializeMojo();
-  void InitStartupTracingForDuration();
-  void EndStartupTracing();
 
   void InitializeAudio();
 
@@ -295,7 +283,6 @@
   //   PostCreateThreads()
   //   BrowserThreadsStarted()
   //     InitializeMojo()
-  //     InitStartupTracingForDuration()
   //   PreMainMessageLoopRun()
 
   // Members initialized on construction ---------------------------------------
@@ -335,12 +322,6 @@
   std::unique_ptr<ScreenOrientationDelegate> screen_orientation_delegate_;
 #endif
 
-  // Members initialized in |InitStartupTracingForDuration()| ------------------
-  base::FilePath startup_trace_file_;
-
-  // This timer initiates trace file saving.
-  base::OneShotTimer startup_trace_timer_;
-
   // Members initialized in |Init()| -------------------------------------------
   // Destroy |parts_| before |main_message_loop_| (required) and before other
   // classes constructed in content (but after |main_thread_|).
diff --git a/content/browser/browser_main_runner_impl.cc b/content/browser/browser_main_runner_impl.cc
index bfc7649..db743ae 100644
--- a/content/browser/browser_main_runner_impl.cc
+++ b/content/browser/browser_main_runner_impl.cc
@@ -25,6 +25,7 @@
 #include "content/browser/browser_main_loop.h"
 #include "content/browser/browser_shutdown_profile_dumper.h"
 #include "content/browser/notification_service_impl.h"
+#include "content/browser/tracing/tracing_controller_impl.h"
 #include "content/common/content_switches_internal.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/main_function_params.h"
@@ -180,26 +181,9 @@
 
   main_loop_->PreShutdown();
 
-  // If startup tracing has not been finished yet, replace it's dumper
-  // with special version, which would save trace file on exit (i.e.
-  // startup tracing becomes a version of shutdown tracing).
-  // There are two cases:
-  // 1. Startup duration is not reached.
-  // 2. Or if the trace should be saved to file for --trace-config-file flag.
-  std::unique_ptr<BrowserShutdownProfileDumper> startup_profiler;
-  if (tracing::TraceStartupConfig::GetInstance()
-          ->IsTracingStartupForDuration()) {
-    main_loop_->StopStartupTracingTimer();
-    if (main_loop_->startup_trace_file() !=
-        base::FilePath().AppendASCII("none")) {
-      startup_profiler.reset(
-          new BrowserShutdownProfileDumper(main_loop_->startup_trace_file()));
-    }
-  } else if (tracing::TraceStartupConfig::GetInstance()
-                 ->ShouldTraceToResultFile()) {
-    base::FilePath result_file = main_loop_->GetStartupTraceFileName();
-    startup_profiler.reset(new BrowserShutdownProfileDumper(result_file));
-  }
+  // Finalize the startup tracing session if it is still active.
+  std::unique_ptr<BrowserShutdownProfileDumper> startup_profiler =
+      TracingControllerImpl::GetInstance()->FinalizeStartupTracingIfNeeded();
 
   // The shutdown tracing got enabled in AttemptUserExit earlier, but someone
   // needs to write the result to disc. For that a dumper needs to get created
diff --git a/content/browser/devtools/protocol/tracing_handler.cc b/content/browser/devtools/protocol/tracing_handler.cc
index 6b4d0e6..326069bf 100644
--- a/content/browser/devtools/protocol/tracing_handler.cc
+++ b/content/browser/devtools/protocol/tracing_handler.cc
@@ -24,6 +24,7 @@
 #include "base/trace_event/memory_dump_manager.h"
 #include "base/trace_event/trace_event_impl.h"
 #include "base/trace_event/tracing_agent.h"
+#include "build/build_config.h"
 #include "components/tracing/common/trace_startup_config.h"
 #include "content/browser/devtools/devtools_agent_host_impl.h"
 #include "content/browser/devtools/devtools_frame_trace_recorder.h"
@@ -41,7 +42,10 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "services/resource_coordinator/public/cpp/memory_instrumentation/memory_instrumentation.h"
+#include "services/tracing/public/cpp/perfetto/perfetto_config.h"
+#include "services/tracing/public/cpp/tracing_features.h"
 #include "services/tracing/public/mojom/constants.mojom.h"
+#include "services/tracing/public/mojom/perfetto_service.mojom.h"
 
 #ifdef OS_ANDROID
 #include "content/browser/renderer_host/compositor_impl_android.h"
@@ -122,7 +126,7 @@
   }
 
  private:
-  ~DevToolsTraceEndpointProxy() override {}
+  ~DevToolsTraceEndpointProxy() override = default;
 
   base::WeakPtr<TracingHandler> tracing_handler_;
 };
@@ -160,7 +164,7 @@
   }
 
  private:
-  ~DevToolsStreamEndpoint() override {}
+  ~DevToolsStreamEndpoint() override = default;
 
   scoped_refptr<DevToolsStreamFile> stream_;
   base::WeakPtr<TracingHandler> tracing_handler_;
@@ -208,8 +212,270 @@
   }
 }
 
+// We currently don't support concurrent tracing sessions, but are planning to.
+// For the time being, we're using this flag as a workaround to prevent devtools
+// users from accidentally starting two concurrent sessions.
+// TODO(eseckler): Remove once we add support for concurrent sessions to the
+// perfetto backend.
+static bool g_any_agent_tracing = false;
+
 }  // namespace
 
+class TracingHandler::TracingSession {
+ public:
+  TracingSession() = default;
+  virtual ~TracingSession() = default;
+
+  virtual void EnableTracing(
+      const base::trace_event::TraceConfig& chrome_config,
+      base::OnceClosure on_recording_enabled_callback) = 0;
+  virtual void AdoptStartupTracingSession() = 0;
+  virtual void ChangeTraceConfig(
+      const base::trace_event::TraceConfig& chrome_config) = 0;
+  virtual void DisableTracing(
+      bool use_proto_format,
+      const std::string& agent_label,
+      const scoped_refptr<TracingController::TraceDataEndpoint>& endpoint) = 0;
+  virtual void GetBufferUsage(
+      base::OnceCallback<void(float percent_full,
+                              size_t approximate_event_count)>
+          on_buffer_usage_callback) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TracingSession);
+};
+
+class TracingHandler::LegacyTracingSession
+    : public TracingHandler::TracingSession {
+ public:
+  void EnableTracing(const base::trace_event::TraceConfig& chrome_config,
+                     base::OnceClosure on_recording_enabled_callback) override {
+    DCHECK(!TracingController::GetInstance()->IsTracing());
+    TracingController::GetInstance()->StartTracing(
+        chrome_config, std::move(on_recording_enabled_callback));
+  }
+
+  void AdoptStartupTracingSession() override {
+    // Nothing to do for legacy tracing here (tracing is already active).
+    DCHECK(TracingController::GetInstance()->IsTracing());
+  }
+
+  void ChangeTraceConfig(
+      const base::trace_event::TraceConfig& chrome_config) override {
+    TracingController::GetInstance()->StartTracing(
+        chrome_config, TracingController::StartTracingDoneCallback());
+  }
+
+  void DisableTracing(bool use_proto_format,
+                      const std::string& agent_label,
+                      const scoped_refptr<TracingController::TraceDataEndpoint>&
+                          endpoint) override {
+    DCHECK(!use_proto_format);
+    TracingController::GetInstance()->StopTracing(endpoint, agent_label);
+  }
+
+  void GetBufferUsage(base::OnceCallback<void(float percent_full,
+                                              size_t approximate_event_count)>
+                          on_buffer_usage_callback) override {
+    TracingController::GetInstance()->GetTraceBufferUsage(
+        std::move(on_buffer_usage_callback));
+  }
+};
+
+class TracingHandler::PerfettoTracingSession
+    : public TracingHandler::TracingSession,
+      public tracing::mojom::TracingSession,
+      public mojo::DataPipeDrainer::Client {
+ public:
+  ~PerfettoTracingSession() override {
+#if DCHECK_IS_ON()
+    DCHECK(!tracing_active_);
+#endif
+  }
+
+  // TracingHandler::TracingSession implementation:
+  void EnableTracing(const base::trace_event::TraceConfig& chrome_config,
+                     base::OnceClosure on_recording_enabled_callback) override {
+#if DCHECK_IS_ON()
+    DCHECK(!tracing_active_);
+    tracing_active_ = true;
+#endif
+    ServiceManagerConnection::GetForProcess()->GetConnector()->BindInterface(
+        tracing::mojom::kServiceName, &consumer_host_);
+
+    perfetto::TraceConfig perfetto_config =
+        CreatePerfettoConfiguration(chrome_config);
+
+    tracing::mojom::TracingSessionPtr tracing_session;
+    binding_.Bind(mojo::MakeRequest(&tracing_session));
+    binding_.set_connection_error_handler(
+        base::BindOnce(&PerfettoTracingSession::OnTracingSessionEnded,
+                       base::Unretained(this)));
+
+    on_recording_enabled_callback_ = std::move(on_recording_enabled_callback);
+    consumer_host_->EnableTracing(std::move(tracing_session),
+                                  std::move(perfetto_config));
+  }
+
+  void AdoptStartupTracingSession() override {
+    // Start a perfetto tracing session, which will claim startup tracing data.
+    DCHECK(!TracingController::GetInstance()->IsTracing());
+    waiting_for_startup_tracing_enabled_ = true;
+    EnableTracing(
+        tracing::TraceStartupConfig::GetInstance()->GetTraceConfig(),
+        base::BindOnce(&PerfettoTracingSession::OnStartupTracingEnabled,
+                       base::Unretained(this)));
+  }
+
+  void ChangeTraceConfig(
+      const base::trace_event::TraceConfig& chrome_config) override {
+    auto perfetto_config = CreatePerfettoConfiguration(chrome_config);
+    consumer_host_->ChangeTraceConfig(perfetto_config);
+  }
+
+  void DisableTracing(bool use_proto_format,
+                      const std::string& agent_label,
+                      const scoped_refptr<TracingController::TraceDataEndpoint>&
+                          endpoint) override {
+    if (waiting_for_startup_tracing_enabled_) {
+      pending_disable_tracing_task_ = base::BindOnce(
+          &PerfettoTracingSession::DisableTracing, base::Unretained(this),
+          use_proto_format, agent_label, endpoint);
+      return;
+    }
+    use_proto_format_ = use_proto_format;
+    agent_label_ = agent_label;
+    endpoint_ = endpoint;
+#if DCHECK_IS_ON()
+    tracing_active_ = false;
+#endif
+
+    if (!use_proto_format_) {
+      mojo::DataPipe data_pipe;
+      drainer_ = std::make_unique<mojo::DataPipeDrainer>(
+          this, std::move(data_pipe.consumer_handle));
+      consumer_host_->DisableTracingAndEmitJson(
+          agent_label_, std::move(data_pipe.producer_handle),
+          base::BindOnce(&PerfettoTracingSession::OnReadBuffersComplete,
+                         base::Unretained(this)));
+    } else {
+      // The host will close the TracingSession connection, calling
+      // OnTracingSessionEnded(), once tracing was disabled.
+      consumer_host_->DisableTracing();
+    }
+  }
+
+  void GetBufferUsage(base::OnceCallback<void(float percent_full,
+                                              size_t approximate_event_count)>
+                          on_buffer_usage_callback) override {
+    DCHECK(on_buffer_usage_callback);
+    consumer_host_->RequestBufferUsage(base::BindOnce(
+        &PerfettoTracingSession::OnBufferUsage, base::Unretained(this),
+        std::move(on_buffer_usage_callback)));
+  }
+
+  void OnBufferUsage(base::OnceCallback<void(float percent_full,
+                                             size_t approximate_event_count)>
+                         on_buffer_usage_callback,
+                     bool success,
+                     float percent_full) {
+    if (!success) {
+      std::move(on_buffer_usage_callback).Run(0.0f, 0);
+      return;
+    }
+    std::move(on_buffer_usage_callback).Run(percent_full, 0);
+  }
+
+  // tracing::mojom::TracingSession implementation:
+  void OnTracingEnabled() override {
+    if (on_recording_enabled_callback_) {
+      std::move(on_recording_enabled_callback_).Run();
+    }
+  }
+
+ private:
+  perfetto::TraceConfig CreatePerfettoConfiguration(
+      const base::trace_event::TraceConfig& chrome_config) {
+#if DCHECK_IS_ON()
+    base::trace_event::TraceConfig processfilter_stripped_config(chrome_config);
+    processfilter_stripped_config.SetProcessFilterConfig(
+        base::trace_event::TraceConfig::ProcessFilterConfig());
+
+    // Ensure that the process filter is the only thing that gets changed
+    // in a configuration during a tracing session.
+    DCHECK((last_config_for_perfetto_.ToString() ==
+            base::trace_event::TraceConfig().ToString()) ||
+           (last_config_for_perfetto_.ToString() ==
+            processfilter_stripped_config.ToString()));
+    last_config_for_perfetto_ = std::move(processfilter_stripped_config);
+#endif
+
+    return tracing::GetDefaultPerfettoConfig(chrome_config);
+  }
+
+  void OnStartupTracingEnabled() {
+    waiting_for_startup_tracing_enabled_ = false;
+    if (pending_disable_tracing_task_)
+      std::move(pending_disable_tracing_task_).Run();
+  }
+
+  void OnTracingSessionEnded() {
+    // If we're converting to JSON, we will receive the data via
+    // ConsumerHost::DisableTracingAndEmitJson().
+    if (!use_proto_format_)
+      return;
+
+    DCHECK(agent_label_.empty());
+    mojo::DataPipe data_pipe;
+    drainer_ = std::make_unique<mojo::DataPipeDrainer>(
+        this, std::move(data_pipe.consumer_handle));
+    consumer_host_->ReadBuffers(
+        std::move(data_pipe.producer_handle),
+        base::BindOnce(&PerfettoTracingSession::OnReadBuffersComplete,
+                       base::Unretained(this)));
+  }
+
+  // mojo::DataPipeDrainer::Client implementation:
+  void OnDataAvailable(const void* data, size_t num_bytes) override {
+    auto data_string = std::make_unique<std::string>(
+        reinterpret_cast<const char*>(data), num_bytes);
+    endpoint_->ReceiveTraceChunk(std::move(data_string));
+  }
+
+  void OnDataComplete() override {
+    data_complete_ = true;
+    MaybeTraceComplete();
+  }
+
+  void OnReadBuffersComplete() {
+    read_buffers_complete_ = true;
+    MaybeTraceComplete();
+  }
+
+  void MaybeTraceComplete() {
+    if (read_buffers_complete_ && data_complete_)
+      endpoint_->ReceiveTraceFinalContents(nullptr);
+  }
+
+  mojo::Binding<tracing::mojom::TracingSession> binding_{this};
+  tracing::mojom::ConsumerHostPtr consumer_host_;
+
+  bool use_proto_format_;
+  std::string agent_label_;
+  base::OnceClosure on_recording_enabled_callback_;
+  base::OnceClosure pending_disable_tracing_task_;
+  bool waiting_for_startup_tracing_enabled_ = false;
+  scoped_refptr<TracingController::TraceDataEndpoint> endpoint_;
+  std::unique_ptr<mojo::DataPipeDrainer> drainer_;
+  bool data_complete_ = false;
+  bool read_buffers_complete_ = false;
+
+#if DCHECK_IS_ON()
+  bool tracing_active_ = false;
+  base::trace_event::TraceConfig last_config_for_perfetto_;
+#endif
+};
+
 TracingHandler::TracingHandler(FrameTreeNode* frame_tree_node_,
                                DevToolsIOContext* io_context,
                                bool use_binary_protocol)
@@ -233,6 +499,20 @@
         std::make_unique<DevToolsVideoConsumer>(base::BindRepeating(
             &TracingHandler::OnFrameFromVideoConsumer, base::Unretained(this)));
   }
+
+  auto* startup_config = tracing::TraceStartupConfig::GetInstance();
+  // Check if we should adopt the startup tracing session. Only the first
+  // session connected to the browser endpoint can own it.
+  if (frame_tree_node_ != nullptr ||
+      !startup_config->AttemptAdoptBySessionOwner(
+          tracing::TraceStartupConfig::SessionOwner::kDevToolsTracingHandler)) {
+    return;
+  }
+
+  DCHECK(tracing::TracingUsesPerfettoBackend());
+  session_ = std::make_unique<PerfettoTracingSession>();
+  session_->AdoptStartupTracingSession();
+  g_any_agent_tracing = true;
 }
 
 TracingHandler::~TracingHandler() = default;
@@ -257,7 +537,7 @@
 }
 
 Response TracingHandler::Disable() {
-  if (did_initiate_recording_)
+  if (session_)
     StopTracing(nullptr, "");
   return Response::OK();
 }
@@ -297,6 +577,7 @@
   DCHECK(!trace_data_buffer_state_.in_string);
   DCHECK(!trace_data_buffer_state_.slashed);
 
+  session_.reset();
   frontend_->TracingComplete();
 }
 
@@ -359,16 +640,20 @@
 }
 
 void TracingHandler::OnTraceToStreamComplete(const std::string& stream_handle) {
+  session_.reset();
+  std::string stream_format = (proto_format_ ? Tracing::StreamFormatEnum::Proto
+                                             : Tracing::StreamFormatEnum::Json);
   std::string stream_compression =
       (gzip_compression_ ? Tracing::StreamCompressionEnum::Gzip
                          : Tracing::StreamCompressionEnum::None);
-  frontend_->TracingComplete(stream_handle, stream_compression);
+  frontend_->TracingComplete(stream_handle, stream_format, stream_compression);
 }
 
 void TracingHandler::Start(Maybe<std::string> categories,
                            Maybe<std::string> options,
                            Maybe<double> buffer_usage_reporting_interval,
                            Maybe<std::string> transfer_mode,
+                           Maybe<std::string> transfer_format,
                            Maybe<std::string> transfer_compression,
                            Maybe<Tracing::TraceConfig> config,
                            std::unique_ptr<StartCallback> callback) {
@@ -376,13 +661,29 @@
       Tracing::Start::TransferModeEnum::ReturnAsStream;
   bool gzip_compression = transfer_compression.fromMaybe("") ==
                           Tracing::StreamCompressionEnum::Gzip;
+  bool proto_format =
+      transfer_format.fromMaybe("") == Tracing::StreamFormatEnum::Proto;
+
+  if (proto_format && !tracing::TracingUsesPerfettoBackend()) {
+    callback->sendFailure(Response::Error(
+        "Proto format is only supported with the perfetto backend."));
+    return;
+  }
+
+  if (proto_format && !return_as_stream) {
+    callback->sendFailure(Response::Error(
+        "Proto format is only supported when using stream transfer mode."));
+    return;
+  }
+
   if (IsTracing()) {
     if (!did_initiate_recording_ && IsStartupTracingActive()) {
       // If tracing is already running because it was initiated by startup
-      // tracing, honor the transfer mode update, as that's the only way
+      // tracing, honor the transfer mode/format update, as that's the only way
       // for the client to communicate it.
       return_as_stream_ = return_as_stream;
       gzip_compression_ = gzip_compression;
+      proto_format_ = proto_format;
     }
     callback->sendFailure(Response::Error("Tracing is already started"));
     return;
@@ -398,6 +699,7 @@
   did_initiate_recording_ = true;
   return_as_stream_ = return_as_stream;
   gzip_compression_ = gzip_compression;
+  proto_format_ = proto_format;
   buffer_usage_reporting_interval_ =
       buffer_usage_reporting_interval.fromMaybe(0);
 
@@ -439,10 +741,16 @@
 
   SetupProcessFilter(gpu_pid, nullptr);
 
-  TracingController::GetInstance()->StartTracing(
+  if (tracing::TracingUsesPerfettoBackend()) {
+    session_ = std::make_unique<PerfettoTracingSession>();
+  } else {
+    session_ = std::make_unique<LegacyTracingSession>();
+  }
+  session_->EnableTracing(
       trace_config_,
       base::BindOnce(&TracingHandler::OnRecordingEnabled,
                      weak_factory_.GetWeakPtr(), std::move(callback)));
+  g_any_agent_tracing = true;
 }
 
 void TracingHandler::SetupProcessFilter(
@@ -492,15 +800,21 @@
   trace_config_.SetProcessFilterConfig(
       base::trace_event::TraceConfig::ProcessFilterConfig(
           included_process_ids));
-  TracingController::GetInstance()->StartTracing(
-      trace_config_, TracingController::StartTracingDoneCallback());
+  session_->ChangeTraceConfig(trace_config_);
 }
 
 Response TracingHandler::End() {
   // Startup tracing triggered by --trace-config-file is a special case, where
   // tracing is started automatically upon browser startup and can be stopped
   // via DevTools.
-  if (!did_initiate_recording_ && !IsStartupTracingActive())
+  // TODO(eseckler): Remove this when we remove the legacy tracing backend.
+  if (!tracing::TracingUsesPerfettoBackend() && IsStartupTracingActive()) {
+    DCHECK(!session_ && !did_initiate_recording_);
+    session_ = std::make_unique<LegacyTracingSession>();
+    session_->AdoptStartupTracingSession();
+  }
+
+  if (!session_)
     return Response::Error("Tracing is not started");
 
   scoped_refptr<TracingController::TraceDataEndpoint> endpoint;
@@ -526,6 +840,7 @@
 
 void TracingHandler::GetCategories(
     std::unique_ptr<GetCategoriesCallback> callback) {
+  // TODO(eseckler): Support this via the perfetto service too.
   TracingController::GetInstance()->GetCategories(
       base::BindOnce(&TracingHandler::OnCategoriesReceived,
                      weak_factory_.GetWeakPtr(), std::move(callback)));
@@ -638,24 +953,29 @@
   buffer_usage_poll_timer_.reset(new base::RepeatingTimer());
   buffer_usage_poll_timer_->Start(
       FROM_HERE, interval,
-      base::Bind(base::IgnoreResult(&TracingController::GetTraceBufferUsage),
-                 base::Unretained(TracingController::GetInstance()),
-                 base::Bind(&TracingHandler::OnBufferUsage,
-                            weak_factory_.GetWeakPtr())));
+      base::BindRepeating(&TracingHandler::UpdateBufferUsage,
+                          weak_factory_.GetWeakPtr()));
+}
+
+void TracingHandler::UpdateBufferUsage() {
+  session_->GetBufferUsage(base::BindOnce(&TracingHandler::OnBufferUsage,
+                                          weak_factory_.GetWeakPtr()));
 }
 
 void TracingHandler::StopTracing(
     const scoped_refptr<TracingController::TraceDataEndpoint>& endpoint,
     const std::string& agent_label) {
+  DCHECK(session_);
   buffer_usage_poll_timer_.reset();
-  TracingController::GetInstance()->StopTracing(endpoint, agent_label);
+  session_->DisableTracing(proto_format_, agent_label, endpoint);
   did_initiate_recording_ = false;
+  g_any_agent_tracing = false;
   if (video_consumer_)
     video_consumer_->StopCapture();
 }
 
 bool TracingHandler::IsTracing() const {
-  return TracingController::GetInstance()->IsTracing();
+  return TracingController::GetInstance()->IsTracing() || g_any_agent_tracing;
 }
 
 void TracingHandler::EmitFrameTree() {
@@ -693,8 +1013,7 @@
 
   SetupProcessFilter(base::kNullProcessId,
                      navigation_handle->GetRenderFrameHost());
-  TracingController::GetInstance()->StartTracing(
-      trace_config_, TracingController::StartTracingDoneCallback());
+  session_->ChangeTraceConfig(trace_config_);
 }
 
 void TracingHandler::FrameDeleted(RenderFrameHostImpl* frame_host) {
diff --git a/content/browser/devtools/protocol/tracing_handler.h b/content/browser/devtools/protocol/tracing_handler.h
index 5c05b31..2584945 100644
--- a/content/browser/devtools/protocol/tracing_handler.h
+++ b/content/browser/devtools/protocol/tracing_handler.h
@@ -67,6 +67,7 @@
              Maybe<std::string> options,
              Maybe<double> buffer_usage_reporting_interval,
              Maybe<std::string> transfer_mode,
+             Maybe<std::string> transfer_format,
              Maybe<std::string> transfer_compression,
              Maybe<Tracing::TraceConfig> config,
              std::unique_ptr<StartCallback> callback) override;
@@ -83,6 +84,10 @@
  private:
   friend class TracingHandlerTest;
 
+  class TracingSession;
+  class LegacyTracingSession;
+  class PerfettoTracingSession;
+
   struct TraceDataBufferState {
    public:
     std::string data;
@@ -108,6 +113,7 @@
       const std::string& trace_fragment);
 
   void SetupTimer(double usage_reporting_interval);
+  void UpdateBufferUsage();
   void StopTracing(
       const scoped_refptr<TracingController::TraceDataEndpoint>& endpoint,
       const std::string& agent_label);
@@ -133,11 +139,13 @@
   bool did_initiate_recording_;
   bool return_as_stream_;
   bool gzip_compression_;
+  bool proto_format_;
   double buffer_usage_reporting_interval_;
   TraceDataBufferState trace_data_buffer_state_;
   std::unique_ptr<DevToolsVideoConsumer> video_consumer_;
   int number_of_screenshots_from_video_consumer_ = 0;
   base::trace_event::TraceConfig trace_config_;
+  std::unique_ptr<TracingSession> session_;
   base::WeakPtrFactory<TracingHandler> weak_factory_;
 
   FRIEND_TEST_ALL_PREFIXES(TracingHandlerTest,
diff --git a/content/browser/frame_host/navigation_controller_impl_unittest.cc b/content/browser/frame_host/navigation_controller_impl_unittest.cc
index 889191d..5eb56c0 100644
--- a/content/browser/frame_host/navigation_controller_impl_unittest.cc
+++ b/content/browser/frame_host/navigation_controller_impl_unittest.cc
@@ -732,14 +732,12 @@
 
   const GURL url1("http://foo1");
 
-  controller.LoadURL(
-      url1, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
-  int entry_id = controller.GetPendingEntry()->GetUniqueID();
+  auto navigation1 =
+      NavigationSimulator::CreateBrowserInitiated(url1, contents());
+  navigation1->Start();
   EXPECT_EQ(0U, navigation_entry_changed_counter_);
   EXPECT_EQ(0U, navigation_list_pruned_counter_);
-  main_test_rfh()->PrepareForCommit();
-  main_test_rfh()->SendNavigateWithTransition(
-      entry_id, true, url1, ui::PAGE_TRANSITION_TYPED);
+  navigation1->Commit();
   EXPECT_EQ(1U, navigation_entry_committed_counter_);
   navigation_entry_committed_counter_ = 0;
 
@@ -750,12 +748,10 @@
   const std::string new_extra_headers("Foo: Bar\nBar: Baz");
   controller.LoadURL(
       url1, Referrer(), ui::PAGE_TRANSITION_TYPED, new_extra_headers);
-  entry_id = controller.GetPendingEntry()->GetUniqueID();
+  auto navigation2 = NavigationSimulator::CreateFromPending(contents());
   EXPECT_EQ(0U, navigation_entry_changed_counter_);
   EXPECT_EQ(0U, navigation_list_pruned_counter_);
-  main_test_rfh()->PrepareForCommit();
-  main_test_rfh()->SendNavigateWithTransition(
-      entry_id, false, url1, ui::PAGE_TRANSITION_TYPED);
+  navigation2->Commit();
   EXPECT_EQ(1U, navigation_entry_committed_counter_);
   navigation_entry_committed_counter_ = 0;
 
@@ -1988,29 +1984,22 @@
   EXPECT_EQ(nullptr, controller_impl().GetPendingEntry());
 
   // Start a load of the same page again.
-  controller.LoadURL(
-      url1, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
+  auto navigation1 =
+      NavigationSimulatorImpl::CreateBrowserInitiated(url1, contents());
+  navigation1->ReadyToCommit();
   int entry_id1 = controller.GetPendingEntry()->GetUniqueID();
 
-  // Immediately start loading a different page...
-  controller.LoadURL(
-      url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
+  // Before it can commit, start loading a different page...
+  auto navigation2 =
+      NavigationSimulator::CreateBrowserInitiated(url2, contents());
+  navigation2->ReadyToCommit();
   int entry_id2 = controller.GetPendingEntry()->GetUniqueID();
   EXPECT_NE(entry_id1, entry_id2);
 
   // ... and now the renderer sends a commit for the first navigation.
-  FrameHostMsg_DidCommitProvisionalLoad_Params params;
-  params.nav_entry_id = entry_id1;
-  params.intended_as_new_entry = true;
-  params.did_create_new_entry = false;
-  params.url = url1;
-  params.transition = ui::PAGE_TRANSITION_TYPED;
-  params.page_state = PageState::CreateFromURL(url1);
-
   LoadCommittedDetailsObserver observer(contents());
-
-  main_test_rfh()->PrepareForCommit();
-  main_test_rfh()->SendNavigateWithParams(&params, false);
+  navigation1->set_intended_as_new_entry(true);
+  navigation1->Commit();
   EXPECT_EQ(NAVIGATION_TYPE_EXISTING_PAGE, observer.navigation_type());
 }
 
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index e42edef..1d9c1f5 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -5095,20 +5095,23 @@
   // MHTML subframes can supply URLs at commit time that do not match the
   // process lock. For example, it can be either "cid:..." or arbitrary URL at
   // which the frame was at the time of generating the MHTML
-  // (e.g. "http://localhost"). In such cases, don't verify the URL, but allow
+  // (e.g. "http://localhost"). In such cases, don't verify the URL, but require
   // the URL to commit in the process of the main frame.
-  // TODO(creis): We should also ensure that such MHTML subframes do not commit
-  // in OOPIFs in a process based on the subframe URL, since MHTML should not
-  // allow such cases to occur. However, we are currently seeing cases where
-  // that is happening. Investigate in https://crbug.com/948246.
   if (!frame_tree_node()->IsMainFrame()) {
-    bool is_in_mhtml = frame_tree_node_->frame_tree()
-                           ->root()
-                           ->current_frame_host()
-                           ->is_mhtml_document();
-    if (is_in_mhtml &&
-        IsSameSiteInstance(frame_tree_node()->parent()->current_frame_host())) {
-      return true;
+    RenderFrameHostImpl* main_frame =
+        frame_tree_node()->frame_tree()->GetMainFrame();
+    if (main_frame->is_mhtml_document()) {
+      if (IsSameSiteInstance(main_frame))
+        return true;
+
+      // If an MHTML subframe commits in a different process (even one that
+      // appears correct for the subframe's URL), then we aren't correctly
+      // loading it from the archive and should kill the renderer.
+      base::debug::SetCrashKeyString(
+          base::debug::AllocateCrashKeyString(
+              "oopif_in_mhtml_page", base::debug::CrashKeySize::Size32),
+          is_mhtml_document() ? "is_mhtml_doc" : "not_mhtml_doc");
+      return false;
     }
   }
 
diff --git a/content/browser/renderer_host/input/input_router_impl.cc b/content/browser/renderer_host/input/input_router_impl.cc
index 64bb4b9..8ffc145 100644
--- a/content/browser/renderer_host/input/input_router_impl.cc
+++ b/content/browser/renderer_host/input/input_router_impl.cc
@@ -55,12 +55,22 @@
   }
 }
 
-ui::WebScopedInputEvent ScaleEvent(const WebInputEvent& event, double scale) {
+std::unique_ptr<InputEvent> ScaleEvent(const WebInputEvent& event,
+                                       double scale,
+                                       const ui::LatencyInfo latency_info) {
   std::unique_ptr<blink::WebInputEvent> event_in_viewport =
       ui::ScaleWebInputEvent(event, scale);
-  if (event_in_viewport)
-    return ui::WebScopedInputEvent(event_in_viewport.release());
-  return ui::WebInputEventTraits::Clone(event);
+  if (event_in_viewport) {
+    ui::LatencyInfo scaled_latency_info(latency_info);
+    scaled_latency_info.set_scroll_update_delta(
+        latency_info.scroll_update_delta() * scale);
+    return std::make_unique<InputEvent>(
+        ui::WebScopedInputEvent(event_in_viewport.release()),
+        scaled_latency_info);
+  }
+
+  return std::make_unique<InputEvent>(ui::WebInputEventTraits::Clone(event),
+                                      latency_info);
 }
 
 }  // namespace
@@ -520,8 +530,8 @@
     return;
   }
 
-  std::unique_ptr<InputEvent> event = std::make_unique<InputEvent>(
-      ScaleEvent(input_event, device_scale_factor_), latency_info);
+  std::unique_ptr<InputEvent> event =
+      ScaleEvent(input_event, device_scale_factor_, latency_info);
   if (WebInputEventTraits::ShouldBlockEventStream(input_event)) {
     TRACE_EVENT_INSTANT0("input", "InputEventSentBlocking",
                          TRACE_EVENT_SCOPE_THREAD);
diff --git a/content/browser/renderer_host/input/render_widget_host_latency_tracker_unittest.cc b/content/browser/renderer_host/input/render_widget_host_latency_tracker_unittest.cc
index 33662f3c..fd04742 100644
--- a/content/browser/renderer_host/input/render_widget_host_latency_tracker_unittest.cc
+++ b/content/browser/renderer_host/input/render_widget_host_latency_tracker_unittest.cc
@@ -1204,115 +1204,4 @@
   EXPECT_TRUE(HistogramSizeEq("Event.Latency.EndToEnd.TouchpadPinch", 1));
 }
 
-TEST_F(RenderWidgetHostLatencyTrackerTest, TestScrollUpdateAverageLag) {
-  // Simulate a simple situation that events at every 10ms and start at t=15ms,
-  // frame swaps at every 10ms too and start at t=20ms.
-  base::TimeTicks event_time =
-      base::TimeTicks() + base::TimeDelta::FromMilliseconds(5);
-  base::TimeTicks frame_time =
-      base::TimeTicks() + base::TimeDelta::FromMilliseconds(10);
-  {
-    // ScrollBegin
-    SyntheticWebTouchEvent touch;
-    touch.PressPoint(0, 0);
-    ui::LatencyInfo touch_latency(ui::SourceEventType::TOUCH);
-
-    touch_latency.set_scroll_update_delta(10);
-    event_time += base::TimeDelta::FromMilliseconds(10);  // 15ms
-    touch_latency.AddLatencyNumberWithTimestamp(
-        ui::INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL_COMPONENT,
-        event_time, 1);
-    touch_latency.AddLatencyNumberWithTimestamp(
-        ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_LAST_EVENT_COMPONENT, event_time,
-        1);
-
-    frame_time += base::TimeDelta::FromMilliseconds(10);  // 20ms
-    AddFakeComponentsWithTimeStamp(*tracker(), &touch_latency, frame_time);
-    AddRenderingScheduledComponent(&touch_latency,
-                                   false /* rendering_on_main */, frame_time);
-    tracker()->OnInputEvent(touch, &touch_latency);
-    tracker()->OnInputEventAck(touch, &touch_latency,
-                               INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-    viz_tracker()->OnGpuSwapBuffersCompleted(touch_latency);
-  }
-  // Send 101 ScrollUpdate events to verify that there is 1 AverageLag record
-  // per 1 second.
-  const int kUpdates = 101;
-  for (int i = 0; i < kUpdates; i++) {
-    // ScrollUpdate
-    SyntheticWebTouchEvent touch;
-    touch.PressPoint(0, 0);
-    ui::LatencyInfo touch_latency(ui::SourceEventType::TOUCH);
-
-    const int sign = (i < kUpdates / 2) ? 1 : -1;
-    touch_latency.set_scroll_update_delta(sign * 10);
-    event_time += base::TimeDelta::FromMilliseconds(10);
-    touch_latency.AddLatencyNumberWithTimestamp(
-        ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT, event_time,
-        1);
-    touch_latency.AddLatencyNumberWithTimestamp(
-        ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_LAST_EVENT_COMPONENT, event_time,
-        1);
-
-    frame_time += base::TimeDelta::FromMilliseconds(10);
-    AddFakeComponentsWithTimeStamp(*tracker(), &touch_latency, frame_time);
-    AddRenderingScheduledComponent(&touch_latency, false /*rendering_on_main*/,
-                                   frame_time);
-    tracker()->OnInputEvent(touch, &touch_latency);
-    tracker()->OnInputEventAck(touch, &touch_latency,
-                               INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-    viz_tracker()->OnGpuSwapBuffersCompleted(touch_latency);
-  }
-  // ScrollBegin report_time is at 20ms, so the next ScrollUpdate report_time is
-  // at 1020ms. The last event_time that finish this report should be later than
-  // 1020ms.
-  EXPECT_EQ(event_time,
-            base::TimeTicks() + base::TimeDelta::FromMilliseconds(1025));
-  EXPECT_EQ(frame_time,
-            base::TimeTicks() + base::TimeDelta::FromMilliseconds(1030));
-
-  // ScrollBegin AverageLag are the area between the event original component
-  // (time=15ms, delta=10px) to the frame swap time (time=20ms, expect finger
-  // position at delta=15px). The AverageLag scaled to 1 second is
-  // (5ms*10px+5ms*5px/2)/5ms = 12.5px.
-  EXPECT_THAT(histogram_tester().GetAllSamples(
-                  "Event.Latency.ScrollBegin.Touch.AverageLag"),
-              ElementsAre(Bucket(12, 1)));
-  // This ScrollUpdate AverageLag are calculated as the finger uniformly scroll
-  // 10px each frame. For each frame, the Lag on the last frame start is 5px,
-  // and Lag on this frame swap is 15px, so the AverageLag in 1 second is 10px.
-  EXPECT_THAT(histogram_tester().GetAllSamples(
-                  "Event.Latency.ScrollUpdate.Touch.AverageLag"),
-              ElementsAre(Bucket(10, 1)));
-  ResetHistograms();
-
-  {
-    // Send another ScrollBegin to end the unfinished ScrollUpdate report.
-    SyntheticWebTouchEvent touch;
-    touch.PressPoint(0, 0);
-    ui::LatencyInfo touch_latency(ui::SourceEventType::TOUCH);
-
-    event_time += base::TimeDelta::FromMilliseconds(10);
-    touch_latency.AddLatencyNumberWithTimestamp(
-        ui::INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL_COMPONENT,
-        event_time, 1);
-    touch_latency.AddLatencyNumberWithTimestamp(
-        ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_LAST_EVENT_COMPONENT, event_time,
-        1);
-
-    frame_time += base::TimeDelta::FromMilliseconds(10);
-    AddFakeComponentsWithTimeStamp(*tracker(), &touch_latency, frame_time);
-    AddRenderingScheduledComponent(&touch_latency,
-                                   false /* rendering_on_main */, frame_time);
-    tracker()->OnInputEvent(touch, &touch_latency);
-    tracker()->OnInputEventAck(touch, &touch_latency,
-                               INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-    viz_tracker()->OnGpuSwapBuffersCompleted(touch_latency);
-  }
-  // The last frame's lag is 8.75px and truncated to 8.
-  EXPECT_THAT(histogram_tester().GetAllSamples(
-                  "Event.Latency.ScrollUpdate.Touch.AverageLag"),
-              ElementsAre(Bucket(8, 1)));
-}
-
 }  // namespace content
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index e465d41..cf7d6a6 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -2976,6 +2976,7 @@
     switches::kEnableAccessibilityObjectModel,
     switches::kEnableAutomation,
     switches::kEnableExperimentalAccessibilityLanguageDetection,
+    switches::kEnableExperimentalAccessibilityLabelsDebugging,
     switches::kEnableExperimentalWebPlatformFeatures,
     switches::kEnableGPUClientLogging,
     switches::kEnableGpuClientTracing,
diff --git a/content/browser/tracing/startup_tracing_browsertest.cc b/content/browser/tracing/startup_tracing_browsertest.cc
index cba2622..2bda794 100644
--- a/content/browser/tracing/startup_tracing_browsertest.cc
+++ b/content/browser/tracing/startup_tracing_browsertest.cc
@@ -74,8 +74,9 @@
   std::string trace;
   base::ScopedAllowBlockingForTesting allow_blocking;
   ASSERT_TRUE(base::ReadFileToString(temp_file_path_, &trace));
-  EXPECT_TRUE(trace.find("BrowserMainLoop::InitStartupTracingForDuration") !=
-              std::string::npos);
+  EXPECT_TRUE(
+      trace.find("TracingControllerImpl::InitStartupTracingForDuration") !=
+      std::string::npos);
 }
 
 }  // namespace content
diff --git a/content/browser/tracing/tracing_controller_impl.cc b/content/browser/tracing/tracing_controller_impl.cc
index 9c739f7..2b0e315 100644
--- a/content/browser/tracing/tracing_controller_impl.cc
+++ b/content/browser/tracing/tracing_controller_impl.cc
@@ -21,6 +21,9 @@
 #include "base/values.h"
 #include "build/build_config.h"
 #include "components/tracing/common/trace_startup_config.h"
+#include "components/tracing/common/trace_to_console.h"
+#include "components/tracing/common/tracing_switches.h"
+#include "content/browser/browser_shutdown_profile_dumper.h"
 #include "content/browser/gpu/gpu_data_manager_impl.h"
 #include "content/browser/tracing/file_tracing_provider_impl.h"
 #include "content/browser/tracing/perfetto_file_tracer.h"
@@ -57,6 +60,7 @@
 
 #if defined(OS_ANDROID)
 #include "base/debug/elf_reader.h"
+#include "content/browser/android/tracing_controller_android.h"
 
 // Symbol with virtual address of the start of ELF header of the current binary.
 extern char __ehdr_start;
@@ -137,6 +141,11 @@
 }
 #endif
 
+void OnStoppedStartupTracing(const base::FilePath& trace_file) {
+  VLOG(0) << "Completed startup tracing to " << trace_file.value();
+  tracing::TraceStartupConfig::GetInstance()->OnTraceToResultFileFinished();
+}
+
 }  // namespace
 
 TracingController* TracingController::GetInstance() {
@@ -382,6 +391,83 @@
   return true;
 }
 
+void TracingControllerImpl::StartStartupTracingIfNeeded() {
+  auto* trace_startup_config = tracing::TraceStartupConfig::GetInstance();
+  if (trace_startup_config->AttemptAdoptBySessionOwner(
+          tracing::TraceStartupConfig::SessionOwner::kTracingController)) {
+    StartTracing(trace_startup_config->GetTraceConfig(),
+                 StartTracingDoneCallback());
+  } else if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+                 switches::kTraceToConsole)) {
+    StartTracing(tracing::GetConfigForTraceToConsole(),
+                 StartTracingDoneCallback());
+  }
+
+  if (trace_startup_config->IsTracingStartupForDuration()) {
+    TRACE_EVENT0("startup",
+                 "TracingControllerImpl::InitStartupTracingForDuration");
+    InitStartupTracingForDuration();
+  }
+}
+
+base::FilePath TracingControllerImpl::GetStartupTraceFileName() const {
+  base::FilePath trace_file;
+
+  trace_file = tracing::TraceStartupConfig::GetInstance()->GetResultFile();
+  if (trace_file.empty()) {
+#if defined(OS_ANDROID)
+    TracingControllerAndroid::GenerateTracingFilePath(&trace_file);
+#else
+    // Default to saving the startup trace into the current dir.
+    trace_file = base::FilePath().AppendASCII("chrometrace.log");
+#endif
+  }
+
+  return trace_file;
+}
+
+void TracingControllerImpl::InitStartupTracingForDuration() {
+  DCHECK(tracing::TraceStartupConfig::GetInstance()
+             ->IsTracingStartupForDuration());
+
+  startup_trace_file_ = GetStartupTraceFileName();
+
+  startup_trace_timer_.Start(
+      FROM_HERE,
+      base::TimeDelta::FromSeconds(
+          tracing::TraceStartupConfig::GetInstance()->GetStartupDuration()),
+      this, &TracingControllerImpl::EndStartupTracing);
+}
+
+void TracingControllerImpl::EndStartupTracing() {
+  // Do nothing if startup tracing is already stopped.
+  if (!tracing::TraceStartupConfig::GetInstance()->IsEnabled())
+    return;
+
+  StopTracing(CreateFileEndpoint(
+      startup_trace_file_,
+      base::BindRepeating(OnStoppedStartupTracing, startup_trace_file_)));
+}
+
+std::unique_ptr<BrowserShutdownProfileDumper>
+TracingControllerImpl::FinalizeStartupTracingIfNeeded() {
+  // There are two cases:
+  // 1. Startup duration is not reached.
+  // 2. Or if the trace should be saved to file for --trace-config-file flag.
+  if (startup_trace_timer_.IsRunning()) {
+    startup_trace_timer_.Stop();
+    if (startup_trace_file_ != base::FilePath().AppendASCII("none")) {
+      return std::make_unique<BrowserShutdownProfileDumper>(
+          startup_trace_file_);
+    }
+  } else if (tracing::TraceStartupConfig::GetInstance()
+                 ->ShouldTraceToResultFile()) {
+    return std::make_unique<BrowserShutdownProfileDumper>(
+        GetStartupTraceFileName());
+  }
+  return nullptr;
+}
+
 bool TracingControllerImpl::StopTracing(
     const scoped_refptr<TraceDataEndpoint>& trace_data_endpoint) {
   return StopTracing(std::move(trace_data_endpoint), "");
diff --git a/content/browser/tracing/tracing_controller_impl.h b/content/browser/tracing/tracing_controller_impl.h
index 832446a..acbad2f 100644
--- a/content/browser/tracing/tracing_controller_impl.h
+++ b/content/browser/tracing/tracing_controller_impl.h
@@ -12,6 +12,7 @@
 
 #include "base/callback_forward.h"
 #include "base/memory/ref_counted.h"
+#include "base/timer/timer.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/tracing_controller.h"
 #include "mojo/public/cpp/system/data_pipe_drainer.h"
@@ -71,6 +72,21 @@
   CONTENT_EXPORT void SetTracingDelegateForTesting(
       std::unique_ptr<TracingDelegate> delegate);
 
+  // If command line flags specify startup tracing options, adopts the startup
+  // tracing session and relays it to all tracing agents. Note that the local
+  // TraceLog has already been enabled at this point by
+  // tracing::EnableStartupTracingIfNeeded(), before threads were available.
+  // Requires browser threads to have started and a started main message loop.
+  void StartStartupTracingIfNeeded();
+
+  // Should be called before browser main loop shutdown. If startup tracing is
+  // tracing to a file and is still active, this stops the duration timer if it
+  // exists and returns a BrowserShutdownProfileDumper that will finalize the
+  // trace file upon its destruction (i.e. startup tracing becomes a version of
+  // shutdown tracing).
+  std::unique_ptr<BrowserShutdownProfileDumper>
+  FinalizeStartupTracingIfNeeded();
+
  private:
   friend std::default_delete<TracingControllerImpl>;
 
@@ -88,6 +104,10 @@
 
   void CompleteFlush();
 
+  void InitStartupTracingForDuration();
+  void EndStartupTracing();
+  base::FilePath GetStartupTraceFileName() const;
+
   std::unique_ptr<PerfettoFileTracer> perfetto_file_tracer_;
   tracing::mojom::CoordinatorPtr coordinator_;
   std::vector<std::unique_ptr<tracing::BaseAgent>> agents_;
@@ -100,6 +120,10 @@
   bool is_data_complete_ = false;
   bool is_metadata_available_ = false;
 
+  base::FilePath startup_trace_file_;
+  // This timer initiates trace file saving.
+  base::OneShotTimer startup_trace_timer_;
+
   DISALLOW_COPY_AND_ASSIGN(TracingControllerImpl);
 };
 
diff --git a/content/renderer/accessibility/blink_ax_tree_source.cc b/content/renderer/accessibility/blink_ax_tree_source.cc
index 159c1b6..1c0e46c 100644
--- a/content/renderer/accessibility/blink_ax_tree_source.cc
+++ b/content/renderer/accessibility/blink_ax_tree_source.cc
@@ -39,6 +39,7 @@
 #include "third_party/blink/public/web/web_plugin.h"
 #include "third_party/blink/public/web/web_plugin_container.h"
 #include "third_party/blink/public/web/web_view.h"
+#include "ui/accessibility/accessibility_switches.h"
 #include "ui/accessibility/ax_enum_util.h"
 #include "ui/accessibility/ax_role_properties.h"
 #include "ui/gfx/geometry/vector2d_f.h"
@@ -268,7 +269,11 @@
 
 BlinkAXTreeSource::BlinkAXTreeSource(RenderFrameImpl* render_frame,
                                      ui::AXMode mode)
-    : render_frame_(render_frame), accessibility_mode_(mode), frozen_(false) {}
+    : render_frame_(render_frame), accessibility_mode_(mode), frozen_(false) {
+  image_annotation_debugging_ =
+      base::CommandLine::ForCurrentProcess()->HasSwitch(
+          ::switches::kEnableExperimentalAccessibilityLabelsDebugging);
+}
 
 BlinkAXTreeSource::~BlinkAXTreeSource() {
 }
@@ -1132,8 +1137,17 @@
   ax::mojom::NameFrom name_from;
   blink::WebVector<WebAXObject> name_objects;
   blink::WebString web_name = src.GetName(name_from, name_objects);
-  if (name_from == ax::mojom::NameFrom::kAttributeExplicitlyEmpty ||
-      !web_name.IsEmpty()) {
+
+  // When visual debugging is enabled, the "title" attribute is set to a
+  // string beginning with a "%". We need to ignore such strings when
+  // subsequently deciding whether an image should be annotated or not.
+  bool has_debug_title =
+      image_annotation_debugging_ &&
+      base::StartsWith(web_name.Utf8(), "%", base::CompareCase::SENSITIVE);
+
+  if ((name_from == ax::mojom::NameFrom::kAttributeExplicitlyEmpty ||
+       !web_name.IsEmpty()) &&
+      !has_debug_title) {
     dst->SetImageAnnotationStatus(
         ax::mojom::ImageAnnotationStatus::kIneligibleForAnnotation);
     return;
@@ -1143,7 +1157,8 @@
   // it if it already has text other than whitespace.
   if (!base::ContainsOnlyChars(
           dst->GetStringAttribute(ax::mojom::StringAttribute::kName),
-          base::kWhitespaceASCII)) {
+          base::kWhitespaceASCII) &&
+      !has_debug_title) {
     dst->SetImageAnnotationStatus(
         ax::mojom::ImageAnnotationStatus::kIneligibleForAnnotation);
     return;
diff --git a/content/renderer/accessibility/blink_ax_tree_source.h b/content/renderer/accessibility/blink_ax_tree_source.h
index 8f635658..49dfc266 100644
--- a/content/renderer/accessibility/blink_ax_tree_source.h
+++ b/content/renderer/accessibility/blink_ax_tree_source.h
@@ -149,6 +149,10 @@
 
   AXImageAnnotator* image_annotator_ = nullptr;
 
+  // Whether we should highlight annotation results visually on the page
+  // for debugging.
+  bool image_annotation_debugging_ = false;
+
   // These are updated when calling |Freeze|.
   bool frozen_ = false;
   blink::WebDocument document_;
diff --git a/content/renderer/accessibility/render_accessibility_impl.cc b/content/renderer/accessibility/render_accessibility_impl.cc
index 0039583..30f04d2 100644
--- a/content/renderer/accessibility/render_accessibility_impl.cc
+++ b/content/renderer/accessibility/render_accessibility_impl.cc
@@ -34,6 +34,7 @@
 #include "third_party/blink/public/web/web_settings.h"
 #include "third_party/blink/public/web/web_user_gesture_indicator.h"
 #include "third_party/blink/public/web/web_view.h"
+#include "ui/accessibility/accessibility_switches.h"
 #include "ui/accessibility/ax_enum_util.h"
 #include "ui/accessibility/ax_event.h"
 #include "ui/accessibility/ax_node.h"
@@ -159,6 +160,10 @@
     HandleAXEvent(WebAXObject::FromWebDocument(document),
                   ax::mojom::Event::kLayoutComplete);
   }
+
+  image_annotation_debugging_ =
+      base::CommandLine::ForCurrentProcess()->HasSwitch(
+          ::switches::kEnableExperimentalAccessibilityLabelsDebugging);
 }
 
 RenderAccessibilityImpl::~RenderAccessibilityImpl() = default;
@@ -172,6 +177,7 @@
 void RenderAccessibilityImpl::DidCommitProvisionalLoad(
     bool is_same_document_navigation,
     ui::PageTransition transition) {
+  has_injected_stylesheet_ = false;
   // Remove the image annotator if the page is loading and it was added for
   // the one-shot image annotation (i.e. AXMode for image annotation is not
   // set).
@@ -455,7 +461,7 @@
   TRACE_EVENT0("accessibility",
                "RenderAccessibilityImpl::SendPendingAccessibilityEvents");
 
-  const WebDocument& document = GetMainDocument();
+  WebDocument document = GetMainDocument();
   if (document.IsNull())
     return;
 
@@ -593,6 +599,12 @@
 
   if (had_layout_complete_messages)
     SendLocationChanges();
+
+  if (had_load_complete_messages)
+    has_injected_stylesheet_ = false;
+
+  if (image_annotation_debugging_)
+    AddImageAnnotationDebuggingAttributes(bundle.updates);
 }
 
 void RenderAccessibilityImpl::SendLocationChanges() {
@@ -1089,4 +1101,74 @@
   }
 }
 
+void RenderAccessibilityImpl::AddImageAnnotationDebuggingAttributes(
+    const std::vector<AXContentTreeUpdate>& updates) {
+  DCHECK(image_annotation_debugging_);
+
+  for (auto& update : updates) {
+    for (auto& node : update.nodes) {
+      if (!node.HasIntAttribute(
+              ax::mojom::IntAttribute::kImageAnnotationStatus))
+        continue;
+
+      ax::mojom::ImageAnnotationStatus status = node.GetImageAnnotationStatus();
+      bool should_set_attributes = false;
+      switch (status) {
+        case ax::mojom::ImageAnnotationStatus::kNone:
+        case ax::mojom::ImageAnnotationStatus::kIneligibleForAnnotation:
+        case ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation:
+          break;
+        case ax::mojom::ImageAnnotationStatus::kAnnotationPending:
+        case ax::mojom::ImageAnnotationStatus::kAnnotationAdult:
+        case ax::mojom::ImageAnnotationStatus::kAnnotationEmpty:
+        case ax::mojom::ImageAnnotationStatus::kAnnotationProcessFailed:
+        case ax::mojom::ImageAnnotationStatus::kAnnotationSucceeded:
+          should_set_attributes = true;
+          break;
+      }
+
+      if (!should_set_attributes)
+        continue;
+
+      WebDocument document = GetMainDocument();
+      if (document.IsNull())
+        continue;
+      WebAXObject obj = WebAXObject::FromWebDocumentByID(document, node.id);
+      if (obj.IsDetached())
+        continue;
+
+      if (!has_injected_stylesheet_) {
+        document.InsertStyleSheet(
+            "[imageannotation=annotationPending] { outline: 3px solid #9ff; } "
+            "[imageannotation=annotationSucceeded] { outline: 3px solid #3c3; "
+            "} "
+            "[imageannotation=annotationEmpty] { outline: 3px solid #ee6; } "
+            "[imageannotation=annotationAdult] { outline: 3px solid #f90; } "
+            "[imageannotation=annotationProcessFailed] { outline: 3px solid "
+            "#c00; } ");
+        has_injected_stylesheet_ = true;
+      }
+
+      WebNode web_node = obj.GetNode();
+      if (web_node.IsNull() || !web_node.IsElementNode())
+        continue;
+
+      WebElement element = web_node.To<WebElement>();
+      std::string status_str = ui::ToString(status);
+      if (element.GetAttribute("imageannotation").Utf8() != status_str)
+        element.SetAttribute("imageannotation",
+                             blink::WebString::FromUTF8(status_str));
+
+      std::string title = "%" + status_str;
+      std::string annotation =
+          node.GetStringAttribute(ax::mojom::StringAttribute::kImageAnnotation);
+      if (!annotation.empty())
+        title = title + ": " + annotation;
+      if (element.GetAttribute("title").Utf8() != title) {
+        element.SetAttribute("title", blink::WebString::FromUTF8(title));
+      }
+    }
+  }
+}
+
 }  // namespace content
diff --git a/content/renderer/accessibility/render_accessibility_impl.h b/content/renderer/accessibility/render_accessibility_impl.h
index 29a3ed5..aa86c84 100644
--- a/content/renderer/accessibility/render_accessibility_impl.h
+++ b/content/renderer/accessibility/render_accessibility_impl.h
@@ -159,6 +159,8 @@
   ax::mojom::EventFrom GetEventFrom();
   void ScheduleSendAccessibilityEventsIfNeeded();
   void RecordImageMetrics(AXContentTreeUpdate* update);
+  void AddImageAnnotationDebuggingAttributes(
+      const std::vector<AXContentTreeUpdate>& updates);
 
   // The RenderFrameImpl that owns us.
   RenderFrameImpl* render_frame_;
@@ -216,6 +218,14 @@
   // Token to send with event messages so we know when they're acknowledged.
   int ack_token_;
 
+  // Whether or not we've injected a stylesheet in this document
+  // (only when debugging flags are enabled, never under normal circumstances).
+  bool has_injected_stylesheet_ = false;
+
+  // Whether we should highlight annotation results visually on the page
+  // for debugging.
+  bool image_annotation_debugging_ = false;
+
   // So we can queue up tasks to be executed later.
   base::WeakPtrFactory<RenderAccessibilityImpl> weak_factory_;
 
diff --git a/content/renderer/compositor/layer_tree_view.cc b/content/renderer/compositor/layer_tree_view.cc
index 98e9d3db..22ea28e 100644
--- a/content/renderer/compositor/layer_tree_view.cc
+++ b/content/renderer/compositor/layer_tree_view.cc
@@ -235,10 +235,6 @@
   return layer_tree_host_->GetSettings().enable_surface_synchronization;
 }
 
-void LayerTreeView::SetNeedsForcedRedraw() {
-  layer_tree_host_->SetNeedsCommitWithForcedRedraw();
-}
-
 std::unique_ptr<cc::SwapPromiseMonitor>
 LayerTreeView::CreateLatencyInfoSwapPromiseMonitor(ui::LatencyInfo* latency) {
   return std::make_unique<cc::LatencyInfoSwapPromiseMonitor>(
@@ -333,14 +329,6 @@
   return layer_tree_host_->have_scroll_event_handlers();
 }
 
-bool LayerTreeView::CompositeIsSynchronous() const {
-  if (!compositor_thread_) {
-    DCHECK(!layer_tree_host_->GetSettings().single_thread_proxy_scheduler);
-    return true;
-  }
-  return false;
-}
-
 void LayerTreeView::SetLayerTreeFrameSink(
     std::unique_ptr<cc::LayerTreeFrameSink> layer_tree_frame_sink) {
   if (!layer_tree_frame_sink) {
@@ -350,26 +338,6 @@
   layer_tree_host_->SetLayerTreeFrameSink(std::move(layer_tree_frame_sink));
 }
 
-void LayerTreeView::SynchronouslyComposite(bool raster) {
-  DCHECK(CompositeIsSynchronous());
-  if (!layer_tree_host_->IsVisible())
-    return;
-
-  if (in_synchronous_compositor_update_) {
-    // Web tests can use a nested message loop to pump frames while inside a
-    // frame, but the compositor does not support this. In this case, we only
-    // run blink's lifecycle updates.
-    delegate_->BeginMainFrame(base::TimeTicks::Now());
-    delegate_->UpdateVisualState();
-    return;
-  }
-
-  DCHECK(!in_synchronous_compositor_update_);
-  base::AutoReset<bool> inside_composite(&in_synchronous_compositor_update_,
-                                         true);
-  layer_tree_host_->Composite(base::TimeTicks::Now(), raster);
-}
-
 std::unique_ptr<cc::ScopedDeferMainFrameUpdate>
 LayerTreeView::DeferMainFrameUpdate() {
   return layer_tree_host_->DeferMainFrameUpdate();
@@ -404,40 +372,6 @@
   layer_tree_host_->SetBrowserControlsShownRatio(ratio);
 }
 
-void LayerTreeView::RequestDecode(const cc::PaintImage& image,
-                                  base::OnceCallback<void(bool)> callback) {
-  layer_tree_host_->QueueImageDecode(image, std::move(callback));
-
-  // If we're compositing synchronously, the SetNeedsCommit call which will be
-  // issued by |layer_tree_host_| is not going to cause a commit, due to the
-  // fact that this would make web tests slow and cause flakiness. However,
-  // in this case we actually need a commit to transfer the decode requests to
-  // the impl side. So, force a commit to happen.
-  if (CompositeIsSynchronous()) {
-    const bool raster = true;
-    layer_tree_host_->GetTaskRunnerProvider()->MainThreadTaskRunner()->PostTask(
-        FROM_HERE, base::BindOnce(&LayerTreeView::SynchronouslyComposite,
-                                  weak_factory_.GetWeakPtr(), raster));
-  }
-}
-
-void LayerTreeView::RequestPresentationCallback(base::OnceClosure callback) {
-  layer_tree_host_->RequestPresentationTimeForNextFrame(base::BindOnce(
-      [](base::OnceClosure callback,
-         const gfx::PresentationFeedback& feedback) {
-        std::move(callback).Run();
-      },
-      std::move(callback)));
-  SetNeedsForcedRedraw();
-  if (CompositeIsSynchronous()) {
-    main_thread_->PostTask(
-        FROM_HERE, base::BindOnce(&LayerTreeView::SynchronouslyComposite,
-                                  weak_factory_.GetWeakPtr(), /*raster=*/true));
-  } else {
-    layer_tree_host_->SetNeedsCommit();
-  }
-}
-
 void LayerTreeView::SetOverscrollBehavior(
     const cc::OverscrollBehavior& behavior) {
   layer_tree_host_->SetOverscrollBehavior(behavior);
diff --git a/content/renderer/compositor/layer_tree_view.h b/content/renderer/compositor/layer_tree_view.h
index 5853ec0d..d629a203 100644
--- a/content/renderer/compositor/layer_tree_view.h
+++ b/content/renderer/compositor/layer_tree_view.h
@@ -89,9 +89,6 @@
   // WebWidgetClient::ScheduleAnimate() instead, or they can bypass test
   // overrides.
   void SetNeedsBeginFrame();
-  // Like SetNeedsRedraw but forces the frame to be drawn, without early-outs.
-  // Redraw will be forced after the next commit
-  void SetNeedsForcedRedraw();
   // Calling CreateLatencyInfoSwapPromiseMonitor() to get a scoped
   // LatencyInfoSwapPromiseMonitor. During the life time of the
   // LatencyInfoSwapPromiseMonitor, if SetNeedsCommit() or
@@ -155,9 +152,6 @@
                                 float bottom_height,
                                 bool shrink) override;
   void SetBrowserControlsShownRatio(float) override;
-  void RequestDecode(const cc::PaintImage& image,
-                     base::OnceCallback<void(bool)> callback) override;
-  void RequestPresentationCallback(base::OnceClosure callback) override;
 
   void SetOverscrollBehavior(const cc::OverscrollBehavior&) override;
 
@@ -201,11 +195,6 @@
 
   const cc::LayerTreeSettings& GetLayerTreeSettings() const;
 
-  // Performs a composite including a main frame and all lifecycle stages,
-  // immediately and synchronously. Should only be called in testing, when
-  // CompositeIsSynchronous() is true.
-  void SynchronouslyComposite(bool raster);
-
   // Sets the RenderFrameMetadataObserver, which is sent to the compositor
   // thread for binding.
   void SetRenderFrameObserver(
@@ -217,18 +206,12 @@
 
   cc::LayerTreeHost* layer_tree_host() { return layer_tree_host_.get(); }
 
-  // Exposed for the WebTest harness to query.
-  bool CompositeIsSynchronousForTesting() const {
-    return CompositeIsSynchronous();
-  }
-
  protected:
   friend class RenderViewImplScaleFactorTest;
 
  private:
   void SetLayerTreeFrameSink(
       std::unique_ptr<cc::LayerTreeFrameSink> layer_tree_frame_sink);
-  bool CompositeIsSynchronous() const;
 
   LayerTreeViewDelegate* const delegate_;
   const scoped_refptr<base::SingleThreadTaskRunner> main_thread_;
@@ -240,8 +223,6 @@
 
   bool layer_tree_frame_sink_request_failed_while_invisible_ = false;
 
-  bool in_synchronous_compositor_update_ = false;
-
   viz::FrameSinkId frame_sink_id_;
   base::circular_deque<
       std::pair<uint32_t,
diff --git a/content/renderer/compositor/layer_tree_view_unittest.cc b/content/renderer/compositor/layer_tree_view_unittest.cc
index 431243c..7085950 100644
--- a/content/renderer/compositor/layer_tree_view_unittest.cc
+++ b/content/renderer/compositor/layer_tree_view_unittest.cc
@@ -423,7 +423,8 @@
           std::move(callback).Run();
         },
         run_loop.QuitClosure(), &swap_time));
-    layer_tree_view_.SynchronouslyComposite(/*raster=*/true);
+    layer_tree_view_.layer_tree_host()->Composite(base::TimeTicks::Now(),
+                                                  /*raster=*/true);
     // The swap time notify comes as a posted task.
     run_loop.Run();
     return swap_time;
diff --git a/content/renderer/media/stream/apply_constraints_processor.h b/content/renderer/media/stream/apply_constraints_processor.h
index 1907819..ce34c656 100644
--- a/content/renderer/media/stream/apply_constraints_processor.h
+++ b/content/renderer/media/stream/apply_constraints_processor.h
@@ -14,11 +14,11 @@
 #include "media/capture/video_capture_types.h"
 #include "third_party/blink/public/mojom/mediastream/media_devices.mojom.h"
 #include "third_party/blink/public/web/modules/mediastream/media_stream_constraints_util.h"
+#include "third_party/blink/public/web/modules/mediastream/media_stream_video_source.h"
 #include "third_party/blink/public/web/web_apply_constraints_request.h"
 
 namespace blink {
 class MediaStreamAudioSource;
-class MediaStreamVideoSource;
 class MediaStreamVideoTrack;
 class WebString;
 }
diff --git a/content/renderer/media/stream/media_stream_video_source_unittest.cc b/content/renderer/media/stream/media_stream_video_source_unittest.cc
index 21baf254..700a34b 100644
--- a/content/renderer/media/stream/media_stream_video_source_unittest.cc
+++ b/content/renderer/media/stream/media_stream_video_source_unittest.cc
@@ -21,7 +21,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/web/modules/mediastream/media_stream_video_source.h"
 #include "third_party/blink/public/web/modules/mediastream/media_stream_video_track.h"
-#include "third_party/blink/public/web/modules/mediastream/video_track_adapter.h"
+#include "third_party/blink/public/web/modules/mediastream/video_track_adapter_settings.h"
 #include "third_party/blink/public/web/web_heap.h"
 
 using ::testing::_;
diff --git a/content/renderer/media/stream/media_stream_video_track_unittest.cc b/content/renderer/media/stream/media_stream_video_track_unittest.cc
index 425e1a8..e457c65c 100644
--- a/content/renderer/media/stream/media_stream_video_track_unittest.cc
+++ b/content/renderer/media/stream/media_stream_video_track_unittest.cc
@@ -19,7 +19,7 @@
 #include "media/base/video_frame.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/web/modules/mediastream/media_stream_video_track.h"
-#include "third_party/blink/public/web/modules/mediastream/video_track_adapter.h"
+#include "third_party/blink/public/web/modules/mediastream/video_track_adapter_settings.h"
 #include "third_party/blink/public/web/web_heap.h"
 
 namespace content {
diff --git a/content/renderer/media/stream/mock_media_stream_registry.cc b/content/renderer/media/stream/mock_media_stream_registry.cc
index a5353084..2a694349 100644
--- a/content/renderer/media/stream/mock_media_stream_registry.cc
+++ b/content/renderer/media/stream/mock_media_stream_registry.cc
@@ -15,7 +15,7 @@
 #include "third_party/blink/public/platform/web_string.h"
 #include "third_party/blink/public/platform/web_vector.h"
 #include "third_party/blink/public/web/modules/mediastream/media_stream_video_track.h"
-#include "third_party/blink/public/web/modules/mediastream/video_track_adapter.h"
+#include "third_party/blink/public/web/modules/mediastream/video_track_adapter_settings.h"
 
 namespace content {
 
diff --git a/content/renderer/media/stream/user_media_client_impl_unittest.cc b/content/renderer/media/stream/user_media_client_impl_unittest.cc
index ceb994f..b91aac6 100644
--- a/content/renderer/media/stream/user_media_client_impl_unittest.cc
+++ b/content/renderer/media/stream/user_media_client_impl_unittest.cc
@@ -37,6 +37,7 @@
 #include "third_party/blink/public/platform/web_string.h"
 #include "third_party/blink/public/platform/web_vector.h"
 #include "third_party/blink/public/web/modules/mediastream/media_stream_constraints_util.h"
+#include "third_party/blink/public/web/modules/mediastream/media_stream_video_track.h"
 #include "third_party/blink/public/web/web_heap.h"
 
 using testing::_;
diff --git a/content/renderer/media/stream/video_track_adapter_unittest.cc b/content/renderer/media/stream/video_track_adapter_unittest.cc
index a65018d9..1fae622 100644
--- a/content/renderer/media/stream/video_track_adapter_unittest.cc
+++ b/content/renderer/media/stream/video_track_adapter_unittest.cc
@@ -8,6 +8,7 @@
 
 #include "media/base/limits.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/web/modules/mediastream/video_track_adapter_settings.h"
 
 namespace content {
 
diff --git a/content/renderer/media/webrtc/media_stream_video_webrtc_sink_unittest.cc b/content/renderer/media/webrtc/media_stream_video_webrtc_sink_unittest.cc
index 3b32fea..fccb236 100644
--- a/content/renderer/media/webrtc/media_stream_video_webrtc_sink_unittest.cc
+++ b/content/renderer/media/webrtc/media_stream_video_webrtc_sink_unittest.cc
@@ -11,7 +11,7 @@
 #include "content/renderer/media/webrtc/mock_peer_connection_dependency_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
-#include "third_party/blink/public/web/modules/mediastream/video_track_adapter.h"
+#include "third_party/blink/public/web/modules/mediastream/video_track_adapter_settings.h"
 
 namespace content {
 namespace {
diff --git a/content/renderer/pepper/pepper_websocket_host.cc b/content/renderer/pepper/pepper_websocket_host.cc
index b0aa5db..7765b00 100644
--- a/content/renderer/pepper/pepper_websocket_host.cc
+++ b/content/renderer/pepper/pepper_websocket_host.cc
@@ -209,7 +209,8 @@
     return PP_ERROR_BADARGUMENT;
   if (gurl.has_ref())
     return PP_ERROR_BADARGUMENT;
-  if (!net::IsPortAllowedForScheme(gurl.EffectiveIntPort(), gurl.scheme()))
+  if (!net::IsPortAllowedForScheme(gurl.EffectiveIntPort(),
+                                   gurl.scheme_piece()))
     return PP_ERROR_BADARGUMENT;
   WebURL web_url(gurl);
 
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index ba98cf96..155a72f 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -977,11 +977,7 @@
 void RenderWidget::RequestPresentation(PresentationTimeCallback callback) {
   layer_tree_view_->layer_tree_host()->RequestPresentationTimeForNextFrame(
       std::move(callback));
-  layer_tree_view_->SetNeedsForcedRedraw();
-
-  // Need this since single thread mode doesn't have a scheduler so the above
-  // call won't cause us to generate a new frame.
-  ScheduleAnimation();
+  layer_tree_view_->layer_tree_host()->SetNeedsCommitWithForcedRedraw();
 }
 
 void RenderWidget::DidPresentForceDrawFrame(
@@ -2837,8 +2833,8 @@
       *result = int_value;
       return true;
     } else {
-      LOG(WARNING) << "Failed to parse switch " << switch_string << ": "
-                   << string_value;
+      DLOG(WARNING) << "Failed to parse switch " << switch_string << ": "
+                    << string_value;
       return false;
     }
   };
@@ -3321,6 +3317,12 @@
       target_offset, use_anchor, new_page_scale, duration);
 }
 
+void RenderWidget::RequestDecode(const cc::PaintImage& image,
+                                 base::OnceCallback<void(bool)> callback) {
+  layer_tree_view_->layer_tree_host()->QueueImageDecode(image,
+                                                        std::move(callback));
+}
+
 void RenderWidget::RequestUnbufferedInputEvents() {
   if (input_event_queue_)
     input_event_queue_->RequestUnbufferedInputEvents();
diff --git a/content/renderer/render_widget.h b/content/renderer/render_widget.h
index 98957193..fdb822b 100644
--- a/content/renderer/render_widget.h
+++ b/content/renderer/render_widget.h
@@ -446,6 +446,8 @@
                                bool use_anchor,
                                float new_page_scale,
                                double duration_sec) override;
+  void RequestDecode(const cc::PaintImage& image,
+                     base::OnceCallback<void(bool)> callback) override;
 
   // Override point to obtain that the current input method state and caret
   // position.
@@ -634,11 +636,18 @@
   // to the user.
   using PresentationTimeCallback =
       base::OnceCallback<void(const gfx::PresentationFeedback&)>;
-  void RequestPresentation(PresentationTimeCallback callback);
+  virtual void RequestPresentation(PresentationTimeCallback callback);
 
   // RenderWidget IPC message handler that can be overridden by subclasses.
   virtual void OnSynchronizeVisualProperties(const VisualProperties& params);
 
+  bool in_synchronous_composite_for_testing() const {
+    return in_synchronous_composite_for_testing_;
+  }
+  void set_in_synchronous_composite_for_testing(bool in) {
+    in_synchronous_composite_for_testing_ = in;
+  }
+
   // Called by Create() functions and subclasses to finish initialization.
   // |show_callback| will be invoked once WebWidgetClient::Show() occurs, and
   // should be null if Show() won't be triggered for this widget.
@@ -983,6 +992,9 @@
   // process, without the use of this mode, however it would be overridden by
   // the browser if they disagree.
   bool synchronous_resize_mode_for_testing_ = false;
+  // In web tests, synchronous composites should not be nested inside another
+  // composite, and this bool is used to guard against that.
+  bool in_synchronous_composite_for_testing_ = false;
 
   // Stores information about the current text input.
   blink::WebTextInputInfo text_input_info_;
diff --git a/content/renderer/renderer_main_platform_delegate_mac.mm b/content/renderer/renderer_main_platform_delegate_mac.mm
index d6e9a70..e59ac93 100644
--- a/content/renderer/renderer_main_platform_delegate_mac.mm
+++ b/content/renderer/renderer_main_platform_delegate_mac.mm
@@ -4,24 +4,11 @@
 
 #include "content/renderer/renderer_main_platform_delegate.h"
 
-#include <Carbon/Carbon.h>
 #import <Cocoa/Cocoa.h>
-#include <objc/runtime.h>
-#include <stdint.h>
 
-#include "base/bind.h"
-#include "base/callback.h"
-#include "base/command_line.h"
 #include "base/logging.h"
-#include "base/mac/mac_util.h"
-#include "base/mac/scoped_cftyperef.h"
-#include "base/strings/string_number_conversions.h"
-#include "base/strings/sys_string_conversions.h"
-#include "content/public/common/content_switches.h"
-#include "content/public/common/sandbox_init.h"
 #include "sandbox/mac/seatbelt.h"
 #include "sandbox/mac/system_services.h"
-#include "services/service_manager/sandbox/mac/sandbox_mac.h"
 
 extern "C" {
 CGError CGSSetDenyWindowServerConnections(bool);
@@ -44,81 +31,6 @@
   sandbox::DisableLaunchServices();
 }
 
-// You are about to read a pretty disgusting hack. In a static initializer,
-// CoreFoundation decides to connect with cfprefsd(8) using Mach IPC. There is
-// no public way to close this Mach port after-the-fact, nor a way to stop it
-// from happening since it is done pre-main in dyld. But the address of the
-// CFMachPort can be found in the run loop's string description. Below, that
-// address is parsed, cast, and then used to invalidate the Mach port to
-// disable communication with cfprefsd.
-void DisconnectCFNotificationCenter() {
-  base::ScopedCFTypeRef<CFStringRef> run_loop_description(
-      CFCopyDescription(CFRunLoopGetCurrent()));
-  const CFIndex length = CFStringGetLength(run_loop_description);
-  for (CFIndex i = 0; i < length; ) {
-    // Find the start of a CFMachPort run loop source, which looks like this,
-    // without new lines:
-    // 1 : <CFRunLoopSource 0x7d16ea90 [0xa160af80]>{signalled = No,
-    // valid = Yes, order = 0, context =
-    // <CFMachPort 0x7d16fe00 [0xa160af80]>{valid = Yes, port = 3a0f,
-    // source = 0x7d16ea90, callout =
-    // _ZL14MessageHandlerP12__CFMachPortPvlS1_ (0x96df59c2), context =
-    // <CFMachPort context 0x1475b>}}
-    CFRange run_loop_source_context_range;
-    if (!CFStringFindWithOptions(run_loop_description,
-            CFSTR(", context = <CFMachPort "), CFRangeMake(i, length - i),
-            0, &run_loop_source_context_range)) {
-      break;
-    }
-    i = run_loop_source_context_range.location +
-        run_loop_source_context_range.length;
-
-    // The address of the CFMachPort is the first hexadecimal address after the
-    // CF type name.
-    CFRange port_address_range = CFRangeMake(i, 0);
-    for (CFIndex j = port_address_range.location; j < length; ++j) {
-      UniChar c = CFStringGetCharacterAtIndex(run_loop_description, j);
-      if (c == ' ')
-        break;
-      ++port_address_range.length;
-    }
-
-    base::ScopedCFTypeRef<CFStringRef> port_address_string(
-        CFStringCreateWithSubstring(NULL, run_loop_description,
-            port_address_range));
-    if (!port_address_string)
-      continue;
-
-    // Convert the string to an address.
-    std::string port_address_std_string =
-        base::SysCFStringRefToUTF8(port_address_string);
-    uint64_t port_address = 0;
-    if (!base::HexStringToUInt64(port_address_std_string, &port_address))
-      continue;
-
-    // Cast the address to an object.
-    CFMachPortRef mach_port = reinterpret_cast<CFMachPortRef>(port_address);
-    if (CFGetTypeID(mach_port) != CFMachPortGetTypeID())
-      continue;
-
-    // Verify that this is the Mach port that needs to be disconnected by the
-    // name of its callout function. Example description (no new lines):
-    // <CFMachPort 0x7d16fe00 [0xa160af80]>{valid = Yes, port = 3a0f, source =
-    // 0x7d16ea90, callout = __CFXNotificationReceiveFromServer (0x96df59c2),
-    // context = <CFMachPort context 0x1475b>}
-    base::ScopedCFTypeRef<CFStringRef> port_description(
-        CFCopyDescription(mach_port));
-    if (CFStringFindWithOptions(port_description,
-            CFSTR(", callout = __CFXNotificationReceiveFromServer ("),
-            CFRangeMake(0, CFStringGetLength(port_description)),
-            0,
-            NULL)) {
-      CFMachPortInvalidate(mach_port);
-      return;
-    }
-  }
-}
-
 }  // namespace
 
 RendererMainPlatformDelegate::RendererMainPlatformDelegate(
@@ -143,23 +55,18 @@
 }
 
 bool RendererMainPlatformDelegate::EnableSandbox() {
-  bool sandbox_initialized = sandbox::Seatbelt::IsSandboxed();
+  // The sandbox is enabled as part of process launching, so assert that it has
+  // been initialized.
+  CHECK(sandbox::Seatbelt::IsSandboxed());
 
-  // If the sandbox is already engaged, just disable system services.
-  if (sandbox_initialized) {
-    DisableSystemServices();
-  } else {
-    sandbox_initialized =
-        InitializeSandbox(base::BindOnce(&DisableSystemServices));
-  }
+  // Inform various system services that they should not attempt to acquire
+  // resources that will be blocked by the sandbox.
+  DisableSystemServices();
 
-  // The sandbox is now engaged. Make sure that the renderer has not connected
-  // itself to Cocoa.
+  // Make sure that the renderer has not connected itself to Cocoa.
   CHECK(NSApp == nil);
 
-  DisconnectCFNotificationCenter();
-
-  return sandbox_initialized;
+  return true;
 }
 
 }  // namespace content
diff --git a/content/shell/renderer/web_test/blink_test_runner.cc b/content/shell/renderer/web_test/blink_test_runner.cc
index d0cf619..e256bcd 100644
--- a/content/shell/renderer/web_test/blink_test_runner.cc
+++ b/content/shell/renderer/web_test/blink_test_runner.cc
@@ -461,6 +461,14 @@
   // that this returns a value indicating if we should defer the pixel dump to
   // the browser instead. We want to switch all tests to use this for pixel
   // dumps.
+  // TODO(danakj): When not printing, this call doesn't do anything useful
+  // except cause the full viewport to be damaged and insert a presentation
+  // callback. Since we already get a callback from the browser side when the
+  // pixel capture is done, this callback seems redundant and probably this only
+  // really was needed for causing damage. Now WebWidgetTestProxy does that
+  // damage itself when a RequestPresentationForPixelDump() is performed so we
+  // could probably stop doing CaptureLocalLayoutDump() at all when not printing
+  // and not wait for the callback.
   bool browser_should_capture_pixels = CaptureLocalPixelsDump();
 
   // Add the current selection rect to the dump result, if requested.
@@ -526,9 +534,8 @@
   waiting_for_pixels_dump_result_ = true;
   bool browser_should_capture_pixels =
       interfaces->TestRunner()->DumpPixelsAsync(
-          render_view()->GetWebView()->MainFrame()->ToWebLocalFrame(),
-          base::BindOnce(&BlinkTestRunner::OnPixelsDumpCompleted,
-                         base::Unretained(this)));
+          render_view(), base::BindOnce(&BlinkTestRunner::OnPixelsDumpCompleted,
+                                        base::Unretained(this)));
 
   // If the browser should capture pixels, then we shouldn't be waiting for dump
   // results.
diff --git a/content/shell/test_runner/test_runner.cc b/content/shell/test_runner/test_runner.cc
index f39aaea..535e4ca3 100644
--- a/content/shell/test_runner/test_runner.cc
+++ b/content/shell/test_runner/test_runner.cc
@@ -1663,8 +1663,11 @@
 }
 
 bool TestRunner::DumpPixelsAsync(
-    blink::WebLocalFrame* frame,
+    content::RenderView* render_view,
     base::OnceCallback<void(const SkBitmap&)> callback) {
+  auto* view_proxy = static_cast<WebViewTestProxy*>(render_view);
+  CHECK(view_proxy->GetWebView()->MainFrame());
+
   if (web_test_runtime_flags_.dump_drag_image()) {
     if (drag_image_.isNull()) {
       // This means the test called dumpDragImage but did not initiate a drag.
@@ -1683,22 +1686,27 @@
   // If we need to do a display compositor pixel dump, then delegate that to the
   // browser by returning true. Note that printing case can be handled here.
   if (!web_test_runtime_flags_.is_printing()) {
-    frame->View()->MainFrameWidget()->RequestPresentationCallbackForTesting(
-        base::BindOnce(
-            [](base::OnceCallback<void(const SkBitmap&)> callback) {
-              SkBitmap bitmap;
-              bitmap.allocN32Pixels(1, 1);
-              bitmap.eraseColor(0);
-              std::move(callback).Run(bitmap);
-            },
-            std::move(callback)));
+    auto* widget_proxy =
+        static_cast<WebWidgetTestProxy*>(view_proxy->GetWidget());
+    widget_proxy->RequestPresentationForPixelDump(base::BindOnce(
+        [](base::OnceCallback<void(const SkBitmap&)> callback,
+           const gfx::PresentationFeedback& feedback) {
+          // Generate a 1x1 black bitmap.
+          SkBitmap bitmap;
+          bitmap.allocN32Pixels(1, 1);
+          bitmap.eraseColor(0);
+          std::move(callback).Run(bitmap);
+        },
+        std::move(callback)));
     return true;
   }
 
-  auto* target_frame = frame;
+  blink::WebLocalFrame* frame =
+      view_proxy->GetWebView()->MainFrame()->ToWebLocalFrame();
+  blink::WebLocalFrame* target_frame = frame;
   std::string frame_name = web_test_runtime_flags_.printing_frame();
   if (!frame_name.empty()) {
-    auto* frame_to_print =
+    blink::WebFrame* frame_to_print =
         frame->FindFrameByName(blink::WebString::FromUTF8(frame_name));
     if (frame_to_print && frame_to_print->IsWebLocalFrame())
       target_frame = frame_to_print->ToWebLocalFrame();
diff --git a/content/shell/test_runner/test_runner.h b/content/shell/test_runner/test_runner.h
index 49f1bd3a..fdea070 100644
--- a/content/shell/test_runner/test_runner.h
+++ b/content/shell/test_runner/test_runner.h
@@ -41,7 +41,6 @@
 }
 
 namespace test_runner {
-
 class MockContentSettingsClient;
 class MockScreenOrientationClient;
 class SpellCheckClient;
@@ -88,7 +87,7 @@
   std::string DumpLayout(blink::WebLocalFrame* frame) override;
   bool ShouldDumpSelectionRect() const override;
   bool DumpPixelsAsync(
-      blink::WebLocalFrame* frame,
+      content::RenderView* render_view,
       base::OnceCallback<void(const SkBitmap&)> callback) override;
   void ReplicateWebTestRuntimeFlagsChanges(
       const base::DictionaryValue& changed_values) override;
diff --git a/content/shell/test_runner/test_runner_for_specific_view.cc b/content/shell/test_runner/test_runner_for_specific_view.cc
index 2a36e84..0308305 100644
--- a/content/shell/test_runner/test_runner_for_specific_view.cc
+++ b/content/shell/test_runner/test_runner_for_specific_view.cc
@@ -227,7 +227,7 @@
       << "testRuner.capturePixelsAsyncThen from an OOPIF";
 
   web_view_test_proxy_->test_interfaces()->GetTestRunner()->DumpPixelsAsync(
-      web_view()->MainFrame()->ToWebLocalFrame(),
+      web_view_test_proxy_,
       base::BindOnce(&TestRunnerForSpecificView::CapturePixelsCallback,
                      weak_factory_.GetWeakPtr(),
                      std::move(persistent_callback)));
diff --git a/content/shell/test_runner/web_test_runner.h b/content/shell/test_runner/web_test_runner.h
index bbf2f2c..86c1a81 100644
--- a/content/shell/test_runner/web_test_runner.h
+++ b/content/shell/test_runner/web_test_runner.h
@@ -23,6 +23,10 @@
 class WebView;
 }
 
+namespace content {
+class RenderView;
+}
+
 namespace test_runner {
 
 class WebTestRunner {
@@ -56,13 +60,15 @@
   // pixels.
   virtual bool ShouldDumpSelectionRect() const = 0;
 
-  // Snapshots image of |web_view| using the mode requested by the current test
-  // and calls |callback| with the result.  Caller needs to ensure that
-  // |web_view| stays alive until |callback| is called.
+  // Snapshots the content of |render_view| using the mode requested by the
+  // current test and calls |callback| with the result.  Caller needs to ensure
+  // that |render_view| stays alive until |callback| is called.
   // Returns false if the request to capture pixels was processed locally, and
-  // true if the pixels need to be captured in the browser process instead..
+  // true if the pixels need to be captured in the browser process instead. In
+  // that case the |callback| is still called once the pixels are captures but
+  // the SkBitmap contained within is not useful.
   virtual bool DumpPixelsAsync(
-      blink::WebLocalFrame* frame,
+      content::RenderView* render_view,
       base::OnceCallback<void(const SkBitmap&)> callback) = 0;
 
   // Replicates changes to web test runtime flags
diff --git a/content/shell/test_runner/web_widget_test_proxy.cc b/content/shell/test_runner/web_widget_test_proxy.cc
index d18921a..d2d56e6 100644
--- a/content/shell/test_runner/web_widget_test_proxy.cc
+++ b/content/shell/test_runner/web_widget_test_proxy.cc
@@ -4,6 +4,7 @@
 
 #include "content/shell/test_runner/web_widget_test_proxy.h"
 
+#include "content/renderer/compositor/compositor_dependencies.h"
 #include "content/renderer/compositor/layer_tree_view.h"
 #include "content/renderer/input/widget_input_handler_manager.h"
 #include "content/shell/test_runner/test_interfaces.h"
@@ -21,21 +22,70 @@
 
 WebWidgetTestProxy::~WebWidgetTestProxy() = default;
 
-void WebWidgetTestProxy::ScheduleAnimation() {
-  if (!GetTestRunner()->TestIsRunning())
-    return;
+void WebWidgetTestProxy::RequestDecode(
+    const cc::PaintImage& image,
+    base::OnceCallback<void(bool)> callback) {
+  RenderWidget::RequestDecode(image, std::move(callback));
 
+  // In web tests the request does not actually cause a commit, because the
+  // compositor is scheduled by the test runner to avoid flakiness. So for this
+  // case we must request a main frame the way blink would.
+  //
+  // Pass true for |do_raster| to ensure the compositor is actually run, rather
+  // than just doing the main frame animate step.
+  if (GetTestRunner()->TestIsRunning())
+    ScheduleAnimationInternal(/*do_raster=*/true);
+}
+
+void WebWidgetTestProxy::RequestPresentation(
+    PresentationTimeCallback callback) {
+  RenderWidget::RequestPresentation(std::move(callback));
+
+  // Single threaded web tests must explicitly schedule commits.
+  //
+  // Pass true for |do_raster| to ensure the compositor is actually run, rather
+  // than just doing the main frame animate step. That way we know it will
+  // submit a frame and later trigger the presentation callback in order to make
+  // progress in the test.
+  if (GetTestRunner()->TestIsRunning())
+    ScheduleAnimationInternal(/*do_raster=*/true);
+}
+
+void WebWidgetTestProxy::RequestPresentationForPixelDump(
+    PresentationTimeCallback callback) {
+  RenderWidget::RequestPresentation(std::move(callback));
+
+  // Like WebWidgetTestProxy::RequestPresentation, except:
+  // We don't check TestIsRunning() here because this path is used to get a
+  // callback at end of each test, which can race with TestIsRunning().
+  //
+  // TestIsRunning() races with the presentation request because the first comes
+  // from various signals including IPC messages, while the second comes from a
+  // IPC message on a different mojo channel.
+  ScheduleAnimationInternal(/*do_raster=*/true);
+}
+
+void WebWidgetTestProxy::ScheduleAnimation() {
+  if (GetTestRunner()->TestIsRunning())
+    ScheduleAnimationInternal(GetTestRunner()->animation_requires_raster());
+}
+
+void WebWidgetTestProxy::ScheduleAnimationInternal(bool do_raster) {
   // When using threaded compositing, have the RenderWidget schedule a request
   // for a frame, as we use the compositor's scheduler. Otherwise the testing
   // WebWidgetClient schedules it.
   // Note that for WebWidgetTestProxy the RenderWidget is subclassed to override
   // the WebWidgetClient, so we must call up to the base class RenderWidget
   // explicitly here to jump out of the test harness as intended.
-  if (!RenderWidget::layer_tree_view()->CompositeIsSynchronousForTesting()) {
+  if (RenderWidget::compositor_deps()->GetCompositorImplThreadTaskRunner()) {
     RenderWidget::ScheduleAnimation();
     return;
   }
 
+  // If an animation already scheduled we'll make it composite, otherwise we'll
+  // schedule another animation step with composite now.
+  composite_requested_ |= do_raster;
+
   if (!animation_scheduled_) {
     animation_scheduled_ = true;
     GetWebViewTestProxy()->delegate()->PostDelayedTask(
@@ -110,8 +160,48 @@
   widget_input_handler_manager()->InvokeInputProcessedCallback();
 }
 
+TestRunnerForSpecificView* WebWidgetTestProxy::GetViewTestRunner() {
+  return GetWebViewTestProxy()->view_test_runner();
+}
+
+TestRunner* WebWidgetTestProxy::GetTestRunner() {
+  return GetWebViewTestProxy()->test_interfaces()->GetTestRunner();
+}
+
+static void DoComposite(content::RenderWidget* widget, bool do_raster) {
+  if (!widget->layer_tree_view()->layer_tree_host()->IsVisible())
+    return;
+
+  if (widget->in_synchronous_composite_for_testing()) {
+    // Web tests can use a nested message loop to pump frames while inside a
+    // frame, but the compositor does not support this. In this case, we only
+    // run blink's lifecycle updates.
+    widget->BeginMainFrame(base::TimeTicks::Now());
+    widget->UpdateVisualState();
+    return;
+  }
+
+  // Ensure that there is damage so that the compositor submits, and the display
+  // compositor draws this frame.
+  if (do_raster) {
+    content::LayerTreeView* layer_tree_view = widget->layer_tree_view();
+    layer_tree_view->layer_tree_host()->SetNeedsCommitWithForcedRedraw();
+  }
+
+  widget->set_in_synchronous_composite_for_testing(true);
+  widget->layer_tree_view()->layer_tree_host()->Composite(
+      base::TimeTicks::Now(), do_raster);
+  widget->set_in_synchronous_composite_for_testing(false);
+}
+
 void WebWidgetTestProxy::SynchronouslyComposite(bool do_raster) {
-  layer_tree_view()->SynchronouslyComposite(do_raster);
+  DCHECK(!compositor_deps()->GetCompositorImplThreadTaskRunner());
+  DCHECK(!layer_tree_view()
+              ->layer_tree_host()
+              ->GetSettings()
+              .single_thread_proxy_scheduler);
+
+  DoComposite(this, do_raster);
 
   // If the RenderWidget is for the main frame, we also composite the current
   // PagePopup afterward.
@@ -124,23 +214,12 @@
     if (blink::WebPagePopup* popup = view->GetPagePopup()) {
       auto* popup_render_widget =
           static_cast<RenderWidget*>(popup->GetClientForTesting());
-      popup_render_widget->layer_tree_view()->SynchronouslyComposite(do_raster);
+      DoComposite(popup_render_widget, do_raster);
     }
   }
 }
 
-TestRunnerForSpecificView* WebWidgetTestProxy::GetViewTestRunner() {
-  return GetWebViewTestProxy()->view_test_runner();
-}
-
-TestRunner* WebWidgetTestProxy::GetTestRunner() {
-  return GetWebViewTestProxy()->test_interfaces()->GetTestRunner();
-}
-
 void WebWidgetTestProxy::AnimateNow() {
-  if (!animation_scheduled_)
-    return;
-
   // For child local roots, it's possible that the backing WebWidget gets
   // closed between the ScheduleAnimation() call and this execution
   // leading to a nullptr.  This happens because child local roots are
@@ -156,10 +235,10 @@
   if (!GetWebWidget())
     return;
 
+  bool do_raster = composite_requested_;
   animation_scheduled_ = false;
-  CHECK(GetTestRunner());
-  bool animation_requires_raster = GetTestRunner()->animation_requires_raster();
-  SynchronouslyComposite(animation_requires_raster);
+  composite_requested_ = false;
+  SynchronouslyComposite(do_raster);
 }
 
 }  // namespace test_runner
diff --git a/content/shell/test_runner/web_widget_test_proxy.h b/content/shell/test_runner/web_widget_test_proxy.h
index 9fef1da8..61da7cd3 100644
--- a/content/shell/test_runner/web_widget_test_proxy.h
+++ b/content/shell/test_runner/web_widget_test_proxy.h
@@ -57,6 +57,11 @@
   explicit WebWidgetTestProxy(Args&&... args)
       : RenderWidget(std::forward<Args>(args)...) {}
 
+  // RenderWidget overrides.
+  void RequestDecode(const cc::PaintImage& image,
+                     base::OnceCallback<void(bool)> callback) override;
+  void RequestPresentation(PresentationTimeCallback callback) override;
+
   // WebWidgetClient implementation.
   void ScheduleAnimation() override;
   bool RequestPointerLock() override;
@@ -82,6 +87,13 @@
 
   void EndSyntheticGestures();
 
+  // Triggers a full composite, and always submits a new frame to the display
+  // compositor. The |callback| will be run when the display compositor has
+  // presented that frame as part of its global scene.
+  void RequestPresentationForPixelDump(PresentationTimeCallback callback);
+  // When |do_raster| is false, only a main frame animation step is performed,
+  // but when true, a full composite is performed and a frame submitted to the
+  // display compositor if there is any damage.
   void SynchronouslyComposite(bool do_raster);
 
  private:
@@ -91,12 +103,19 @@
   TestRunnerForSpecificView* GetViewTestRunner();
   TestRunner* GetTestRunner();
 
+  void ScheduleAnimationInternal(bool do_raster);
   void AnimateNow();
 
   EventSender event_sender_{this};
 
   // For collapsing multiple simulated ScheduleAnimation() calls.
   bool animation_scheduled_ = false;
+  // When true, the scheduled AnimateNow() will perform a full composite.
+  // Otherwise, it will only perform the animation step, which calls out to
+  // blink, for performance reasons. See setAnimationRequiresRaster() in
+  // https://chromium.googlesource.com/chromium/src/+/master/docs/testing/writing_web_tests.md
+  // for details on the optimization.
+  bool composite_requested_ = false;
 
   base::WeakPtrFactory<WebWidgetTestProxy> weak_factory_{this};
 
diff --git a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
index 6f0c240..14bd749 100644
--- a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
@@ -94,10 +94,9 @@
     self.Skip('conformance2/rendering/texture-switch-performance.html',
         bug=735483)
 
+    # TODO(shrekshao): Remove after updating this test case.
     self.Fail('conformance2/rendering/depth-stencil-feedback-loop.html',
         bug=660844) # WebGL 2.0.1
-    self.Fail('conformance/rendering/rendering-sampling-feedback-loop.html',
-        bug=660844) # WebGL 2.0.1
 
     # Nvidia bugs fixed in latest driver
     # TODO(http://crbug.com/887241): Upgrade the drivers on the bots.
@@ -348,6 +347,8 @@
     # Passthrough command decoder
     self.Fail('conformance/misc/webgl-specific-stencil-settings.html',
         ['passthrough'], bug=844349)
+    self.Fail('conformance/rendering/rendering-sampling-feedback-loop.html',
+        ['passthrough'], bug=660844) # WebGL 2.0.1
 
     # Passthrough command decoder / OpenGL
     self.Fail('conformance2/misc/uninitialized-test-2.html',
diff --git a/content/test/gpu/gpu_tests/webgl_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl_conformance_expectations.py
index 5674a3d..012a33ff 100644
--- a/content/test/gpu/gpu_tests/webgl_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl_conformance_expectations.py
@@ -107,10 +107,6 @@
     self.Fail('conformance/extensions/webgl-draw-buffers.html',
         bug=927908)
 
-    # Need to add detection of feedback loops with multiple render targets.
-    self.Fail('conformance/rendering/rendering-sampling-feedback-loop.html',
-        bug=660844)
-
     # Need to implement new error semantics
     # https://github.com/KhronosGroup/WebGL/pull/2607
     self.Fail('conformance/extensions/' +
@@ -147,6 +143,9 @@
     self.Fail('conformance/textures/canvas/' +
         'tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html',
         ['passthrough', 'opengl'], bug=2952) # angle bug ID
+    # Need to add detection of feedback loops with multiple render targets.
+    self.Fail('conformance/rendering/rendering-sampling-feedback-loop.html',
+        ['passthrough'], bug=660844)
 
     # Intel graphics driver issue. Passed on 25.20.100.6471
     self.Fail('conformance/glsl/constructors/glsl-construct-mat2.html',
@@ -314,6 +313,9 @@
         ['win', 'd3d9'], bug=617148)
     self.Skip('conformance/glsl/misc/large-loop-compile.html',
         ['win', 'd3d9'], bug=674572)
+    # Test needs GL_EXT_draw_buffers which is not supported
+    self.Skip('conformance/rendering/rendering-sampling-feedback-loop.html',
+        ['win', 'd3d9', 'no_passthrough'])
 
     # WIN / OpenGL / NVIDIA failures
     self.Fail('conformance/limits/gl-max-texture-dimensions.html',
@@ -792,6 +794,9 @@
         ['android', ('qualcomm', 'Adreno (TM) 418')], bug=610951)
     self.Flaky('conformance/limits/gl-max-texture-dimensions.html',
         ['android', ('qualcomm', 'Adreno (TM) 418'), 'passthrough'], bug=914631)
+    # Test needs GL_EXT_draw_buffers which is not supported
+    self.Skip('conformance/rendering/rendering-sampling-feedback-loop.html',
+        ['android', ('qualcomm', 'Adreno (TM) 418'), 'no_passthrough'])
 
     # Nexus 6 (Adreno 420) and 6P (Adreno 430)
     self.Fail('conformance/context/' +
diff --git a/content/test/navigation_simulator_impl.cc b/content/test/navigation_simulator_impl.cc
index f495c7e..f37f0f7 100644
--- a/content/test/navigation_simulator_impl.cc
+++ b/content/test/navigation_simulator_impl.cc
@@ -295,8 +295,6 @@
       static_cast<WebContentsImpl*>(contents), test_frame_host));
   simulator->frame_tree_node_ = frame_tree_node;
   simulator->InitializeFromStartedRequest(request);
-  simulator->set_did_create_new_entry(
-      contents->GetController().GetPendingEntryIndex() == -1);
   return simulator;
 }
 
@@ -1175,10 +1173,22 @@
   if (ui::PageTransitionCoreTypeIs(transition_,
                                    ui::PAGE_TRANSITION_AUTO_SUBFRAME))
     return false;
-  if (reload_type_ != ReloadType::NONE)
+  if (reload_type_ != ReloadType::NONE ||
+      (request_ && FrameMsg_Navigate_Type::IsReload(
+                       request_->common_params().navigation_type))) {
     return false;
-  if (session_history_offset_)
+  }
+  if (session_history_offset_ ||
+      (request_ && FrameMsg_Navigate_Type::IsHistory(
+                       request_->common_params().navigation_type))) {
     return false;
+  }
+  if (request_ && (request_->common_params().navigation_type ==
+                       FrameMsg_Navigate_Type::RESTORE ||
+                   request_->common_params().navigation_type ==
+                       FrameMsg_Navigate_Type::RESTORE_WITH_POST)) {
+    return false;
+  }
 
   return true;
 }
@@ -1218,6 +1228,9 @@
   params->did_create_new_entry = DidCreateNewEntry();
   params->should_replace_current_entry = should_replace_current_entry_;
 
+  if (intended_as_new_entry_.has_value())
+    params->intended_as_new_entry = intended_as_new_entry_.value();
+
   if (failed_navigation) {
     // Note: Error pages must commit in a unique origin. So it is left unset.
     params->url_is_unreachable = true;
diff --git a/content/test/navigation_simulator_impl.h b/content/test/navigation_simulator_impl.h
index 7f3f621..6c4cfff9 100644
--- a/content/test/navigation_simulator_impl.h
+++ b/content/test/navigation_simulator_impl.h
@@ -120,6 +120,12 @@
     should_replace_current_entry_ = should_replace_current_entry;
   }
 
+  // Manually force the value of intended_as_new_entry flag in DidCommit*Params
+  // to |intended_as_new_entry|.
+  void set_intended_as_new_entry(bool intended_as_new_entry) {
+    intended_as_new_entry_ = intended_as_new_entry;
+  }
+
   void set_http_connection_info(net::HttpResponseInfo::ConnectionInfo info) {
     http_connection_info_ = info;
   }
@@ -272,8 +278,9 @@
   NavigationController::LoadURLParams* load_url_params_;
 
   bool history_list_was_cleared_ = false;
-  base::Optional<bool> did_create_new_entry_;
   bool should_replace_current_entry_ = false;
+  base::Optional<bool> did_create_new_entry_;
+  base::Optional<bool> intended_as_new_entry_;
 
   // These are used to sanity check the content/public/ API calls emitted as
   // part of the navigation.
diff --git a/device/vr/windows/compositor_base.cc b/device/vr/windows/compositor_base.cc
index d4e0807..992804a 100644
--- a/device/vr/windows/compositor_base.cc
+++ b/device/vr/windows/compositor_base.cc
@@ -138,6 +138,7 @@
   presentation_binding_.Close();
   frame_data_binding_.Close();
   gamepad_provider_.Close();
+  overlay_binding_.Close();
   StopRuntime();
 }
 
diff --git a/docs/security/permissions-for-powerful-web-platform-features.md b/docs/security/permissions-for-powerful-web-platform-features.md
new file mode 100644
index 0000000..cb42175
--- /dev/null
+++ b/docs/security/permissions-for-powerful-web-platform-features.md
@@ -0,0 +1,388 @@
+# Controlling Access to Powerful Web Platform Features
+
+_Author: [dominickn@chromium.org](mailto:dominickn@chromium.org)_  
+_Contributors: [rorymcclelland@chromium.org](mailto:rorymcclelland@chromium.org)_  
+
+# Overview
+
+[Fugu](https://blog.chromium.org/2018/11/our-commitment-to-more-capable-web.html)
+ is a renewed effort to bring
+ [powerful new capabilities](https://bugs.chromium.org/p/chromium/issues/list?can=2&q=Proj%3DFugu+&colspec=ID+Pri+M+Stars+ReleaseBlock+Component+Status+Owner+Summary+OS+Modified&x=m&y=releaseblock&cells=ids)
+to the web -- e.g. filesystem read/write access. Allowing users to control
+which sites are able to access such APIs is crucial for maintaining the
+security and privacy properties of the web. The impact of restrictions on the
+developer ergonomics and user utility of the API and the web platform overall
+must also be considered.
+
+This document explores approaches to guarding powerful APIs, e.g. using
+[installed web app state](https://developers.google.com/web/progressive-web-apps/)
+or some other proxy for high user
+[engagement](https://www.chromium.org/developers/design-documents/site-engagement).
+The following general principles summarise the overall approach of the
+Chromium project to evaluating how powerful new features should be controlled
+on the web:
+
++   __Access to powerful APIs__ should be available to the entire web platform
+    of secure contexts, with control managed exclusively by choosers, prompts,
+    or other user consent UX at time-of-use.
++   __API-specific restrictions__ on the scope of access may also be used to
+    guard against potential abuse (e.g. constraints on available directories /
+    files, no access without a currently open foreground tab).
++   Usage of powerful APIs should be clearly __disclosed__ to users, ideally
+    using a central hub that offers users __control__ over what sites can use
+    which capabilities.
++   Installing a web app is associated with __persistence__, and thus
+    persistent and/or background access to powerful APIs may only be granted
+    (possibly subject to additional requirements) to installed web apps.
+    Non-installed sites may still request and be granted permission to use
+    powerful APIs, but should not have their access persisted.
++   Installation or engagement alone __should not act as a vote of trust__ for
+    either granting access or enabling the ability to ask for access to
+    powerful APIs.
++   Separately, efforts should be made to __curtail the existing persistency__
+    on the web platform outside of installed web apps, e.g. time-limiting
+    permission grants, more aggressively expiring cookies, and restricting
+    background task execution.
+
+The remainder of this document explains the reasoning behind these principles,
+and summarises why alternative proposals were not taken up.
+
+# Definition of Terms
+
++   [__Powerful Web Platform APIs__](https://www.chromium.org/Home/chromium-security/prefer-secure-origins-for-powerful-new-features)
+    are capabilities which carry inherent security or privacy risks when used,
+    but also provide users of web apps with significant utility. A canonical
+    example is native file system access (i.e. allowing web sites to directly
+    read and write from certain locations on the user's device). Many such
+    capabilities already exist on the web in every browser (e.g. access to
+    camera and microphone hardware), while new capabilities are under constant
+    development.
++   [__Installation__](https://www.w3.org/TR/appmanifest/#installable-web-applications)
+    is the process where a web site may be elevated to run with a more native
+    UX treatment on a given platform and device. It is usually tied with being
+    granted a presence in the platform launcher (e.g. the desktop).
++   [__Progressive Web Apps__](https://developers.google.com/web/progressive-web-apps/)
+    (PWAs) are web sites which are designed to be installable.
++   [__Engagement__](https://www.chromium.org/developers/design-documents/site-engagement)
+    is a mechanism for measuring how much users interact with a site. Higher
+    engagement may be a signal that the user derives significant utility from
+    a site.
+
+
+# Principles for Access to Powerful APIs
+
+This section outlines the general principles that the Chromium team believes
+are critical when designing access to powerful new web platform APIs. These
+principles will be considered when evaluating how new APIs are designed.
+
+## Control and Transparency
+
++   Users must be able to see when the powerful APIs in use, and what sites
+    are using them.
++   Users may revoke access to powerful APIs at any time, and preferably,
+    the revocation UI should be intuitive to locate based on how permission is
+    granted and/or disclosed to the user.
++   The principle of least privilege should apply as broadly as possible:
+    users should not have to grant access to more resources than necessary to
+    achieve their goals.
+
+## User Ergonomics
+
++   The web platform should be fully functional without requiring
+    installation.
++   Sites must not be able to easily socially-engineer the user into
+    granting permissions they wouldn't otherwise want to.
++   As much as is practical, users should not be bombarded with permission
+    prompts, as this leads to decision fatigue.
+
+## Developer Predictability
+
++   Developers must be able to know when they can and cannot access
+    powerful APIs.
++   Access mechanisms should be cross-browser compatible.
+
+# Proposal
+
+### Baseline: secure contexts, top-level frames, user gesture
+
+Minimally, all new powerful APIs must only be available in
+[secure contexts](https://www.chromium.org/Home/chromium-security/deprecating-powerful-features-on-insecure-origins).
+Ideally, availability is restricted to top-level frames and requires a user
+gesture to trigger. When a webpage is running in a secure context in
+a top-level frame with an active user gesture, we call this situation
+a __baseline context__.
+
+[Browser extensions](https://developer.chrome.com/extensions) are also
+included as a baseline context, as they can already make use of web platform
+APIs (subject to the same access checks as web sites).
+
+The
+[permission delegation](https://docs.google.com/document/d/1x5QejvpyQ71LPWhMLsaM1lWCfSsBsSQ8Dap9kJ6uLv0/preview?ts=5b857603#heading=h.ib6rctasbt3y)
+mechanism may be used to extend privileges to iframes on a page if it makes
+sense for a powerful capability to be delegated in this way.
+
+### The entire web platform may access new powerful APIs
+
+In general, any baseline context may access powerful APIs, regardless of its
+windowing state (in the tabbed browser or in a standalone app window),
+installation state (installed or not), or user engagement (highly interacted
+with or not). This avoids the __fragmentation of the web__ into different,
+sometimes unpredictable states, and encourages careful consideration of new
+API surfaces such that they are exposed in a way that is safe for the web at
+large.
+
+### Session-based access is granted by direct user consent
+
+In general, access to powerful APIs must be mediated by direct, informed user
+consent while the requesting site is open in a foreground tab via mechanisms
+which may include:
+
++   choosers
++   prompts
+
+These mechanisms must clearly disclose the origin of the request, and follow
+Chromium's
+[guidelines on displaying URLs](https://chromium.googlesource.com/chromium/src/+/master/docs/security/url_display_guidelines/url_display_guidelines.md).
+Implementations may be tested using tools such as
+[Trickuri](https://github.com/chromium/trickuri).
+
+As much as possible, APIs should avoid a "double prompt", e.g. a permission
+prompt requesting access to the file system, followed by a chooser to pick the
+file/directory to access. There is little security or privacy benefit to such
+a double prompt, and it detrimentally affects user and developer ergonomics.
+
+There are cases where double prompts are unavoidable, e.g. a web site may
+request access to contact information, and if the user grants access, the
+browser may need to request OS-level permission to service the request.
+
+In some cases, Chromium may implicitly grant access to an API if it is not
+particularly dangerous or does not make sense to guard behind a permission
+consent. An example of this is the
+[Badging API](https://github.com/WICG/badging/blob/master/explainer.md), which
+only works for installed web apps, and results in a subtle badging effect on
+the installed app icon that is not invasive or privacy-sensitive. These cases
+should be relatively rare considering the powerful APIs that are covered by
+this document.
+
+The scope of access to APIs follows the web's same-origin model.
+
+### Persistent and/or background access is restricted to installed apps
+
+The only definite capability granted by installation is __persistence__. By
+installing, the user has explicitly indicated that they want the web app to
+have a persistent presence on their system.
+
+New powerful APIs should exclusively use session-based permissions for web
+sites that are not installed. In particular, there should be no access while
+the site is not open in a tab, and access cannot be requested from a
+non-foreground tab. When the site is closed or navigated away from, it loses
+any granted access to powerful APIs it had, and must re-ask for access the
+next time the user visits.
+
+Installed web sites _may_ instead receive a permanent grant, which is removed
+when the site is uninstalled. In this way, installed web sites _may_ be
+granted the ability to access capabilities in the background, depending on the
+particular details of each capability. It also avoids overloading the
+installation decision with consequences that users may not expect.
+
+Persistency for installed web sites may have other requirements, but
+non-installed sites may never receive persistent grants to access powerful new
+APIs.
+
+Some powerful APIs act as a proxy for persistence (e.g. a web site with
+permission to write files to disk). We distinguish persistence via a currently
+granted capability from __persistent access to the capability itself__; it is
+the latter privilege which is granted by being installed.
+
+### Disclose access and provide obvious user controls
+
+It should be obvious to users when sites are using powerful APIs, and they
+should be given the tools needed to effectively manage and revoke access when
+necessary.
+
+### Improve permissions and installation UX to avoid decision fatigue
+
+Removing persistent access from the drive-by web may drive up
+[decision fatigue](https://en.wikipedia.org/wiki/Decision_fatigue) due to
+overprompting. However, we anticipate that many of these capabilities will
+have relatively niche applications that the drive-by web should not commonly
+access.
+
+Repeated granting of access to powerful APIs can be used as a signal for
+installation. For instance, after two successful powerful permission grants,
+Chromium could present the user with the option to install the app on the
+third permission request.
+
+### Administrator policies may override prompts and enforce persistence
+
+Powerful new capabilities may be paired with
+[Chromium policies](https://www.chromium.org/administrators/policy-list-3)
+which permit administrators to enforce persisted access to capabilities
+without prompts. Capabilities may also be restricted or blocked by such
+policies. This is in line with how many existing permissions have admin policy
+overrides.
+
+### Explore ways of curtailing the lifetime of existing persistence
+
+Currently, web sites which are not installed have access to significant
+persistence mechanisms:
+
++   all existing permissions have their decisions persisted indefinitely
++   cookie and local storage lifetime may be indefinite
+
+To better align the existing web to the proposal presented here, we suggest a
+parallel effort to apply new lifetime limits on existing persistence
+mechanisms. For example, some of the following measures could be explored:
+
++   forget any granted permissions if the site has not been visited in X weeks
+    +   this could be challenging to apply for some capabilities such as push
+        notifications, where there is a use case for a site being able to send
+        informative notifications without ever needing to be opened.
++   ignore cookie Max-Age headers for non-installed sites, and erase cookies
+    when the associated URL is no longer in browser history.
++   restrict durable/persistent storage to installed apps.
++   limit the storage quota available to sites unless they are installed (and
+    conversely, raise storage quota limits for installed sites).
++   restrict what Service Workers may do in the background unless they control
+    a site that is installed.
+
+# Case Study -- Native File System Access
+
+We describe a high level case study based on the principles in this document
+for granting a web site access to a) read any file in a certain directory; b)
+write files to a directory.
+
+## Granting access
+
++   The user must give direct consent via a file picker.
++   The browser should disclose that access to the chosen file or directory
+    will be granted to the web site.
++   A non-installed site will trigger the picker each page load that the API
+    to read or write to a directory is invoked.
++   An installed site will have its access to reading or writing persisted.
+
+## Additional considerations for writing to a directory
+
++   Existing Downloads UI and protections (e.g. malicious file scanning)
+    could be employed to ensure sites cannot use writing to a directory as a
+    bypass.
++   Where such scanning isn't accessible, confirmation prompts for opening
+    dangerous files in the Downloads UI may be employed instead to ensure the
+    user knows if a potentially malicious file type is being stored.
++   Non-installed sites may have additional restrictions on which folders
+    are available to choose (e.g. only allowing the user's Downloads directory,
+    or excluding the user's Documents directory).
+
+# Alternatives Considered
+
+## Guard API Access Behind Installation
+
+Apps on any desktop or mobile platform require installation to run, and when
+installed, apps are automatically granted many privileges. We could extend
+this concept to the web by restricting powerful APIs like native file system
+access only to installed web apps. That is, the drive-by web could not even
+ask for permission to access an API -- the site would need to be installed.
+
+A key argument for using installation in this manner is that some APIs are
+simply so powerful that the drive-by web should not be able to ask for them.
+However, this document takes the position that installation alone as a
+restriction is undesirable.
+
+### Pros:
+
++   This is a simple model that is both user controllable and
+    developer-accessible (via installation APIs such as
+    [beforeinstallpromptevent](https://developers.google.com/web/fundamentals/app-install-banners/)).
++   Installation is already synonymous with some amount of elevated privilege
+    on most platforms.
+
+### Cons:
+
++   Fragments the web platform into installed and not installed, with
+    different APIs available depending on installed state.
++   Disempowers the drive-by web, and undermines the "try-before-you-buy"
+    ability that the web affords today. Users may also not be willing to
+    install a site from which they cannot ascertain any benefit.
++   Forces users to install a site to use it, even if they don't want to
+    install.
++   May encourage web sites to prompt users on every visit to install the PWA
+    to get access to powerful features.
++   Creates confusing scenarios when web apps are running in tabs:
+    +   If a web app is installed but the user opens the site in a
+        tab, does it get access to the capability or not?
+    +   What about if the web app starts off in a standalone window, but
+        the user reparents it into a tab? Or if a user sets an installed app to
+        open in a tab?
++   Platforms where installation grants privilege all incur additional
+    friction during installation that the web currently does not exhibit.
+    Examples include:
+    +   requiring something to be downloaded,
+    +   requiring a confirmation prompt, installer, or some explicit
+        privilege or grant during installation,
+    +   requiring an explicit display of permissions that are implicitly
+        granted.
++   The implicit granting of privilege by installation has proven to be a
+    security and privacy challenge on many platforms, e.g. Android native apps
+    can access many
+    [powerful features](https://developer.android.com/guide/topics/permissions/overview#normal-dangerous)
+    with no permission prompt and without user recourse to revoke access.
++   The amount of friction generated by PWA installation over the drive-by
+    web is unclear, and gating APIs behind installation increases the
+    incentive for tricking users into installing.
+
+### Security considerations:
+
+Restricting APIs to installed web apps is not a meaningful security improvement
+for users for several reasons:
+
++   While it eliminates the drive-by web as an attack surface, per-API
+    security mitigations (e.g. restricting which directories are accessible
+    for reading and writing) would still be necessary to protect users of
+    installed web apps.
++   The effectiveness of installation as a gate on access to powerful APIs
+    weakens as the installed web app model becomes more successful.
++   Developers are incentivised to ask for installation to utilise powerful
+    capabilities, contributing to the erosion of installation as an effective
+    security mitigation.
++   Per-API permission requests would still be necessary in the installed
+    state, making installation effectively equivalent to an implicit permission
+    grant to ask for permission to access powerful features. We should simply
+    ask users directly if they wish to grant permission, rather than use such a
+    two-tiered requirement.
+
+## Guard API Access Behind Engagement
+
+This is a more general concept than installation: that continual, significant
+usage of a web site should allow that site to access more powerful APIs.
+
+### Pros:
+
++   Continual usage of a web site can be taken as a signal of trust
+
+### Cons:
+
++   Engagement is not standardised or exposed to the web platform, making
+    it a highly unpredictable and unergonomic mechanism of controlling access.
+    +   Standardisation and a web-exposed API would both be requirements for
+        using engagement in this way. Chromium's current engagement
+        implementation is local-only and not web-exposed. There are serious
+        privacy questions about exposing such data to the web.
++   Solving the first-run problem is non-trivial: engagement requires usage
+    to accumulate, but there are apps which legitimately require access to a
+    powerful API immediately to function (e.g. an editor -> files, or a
+    weather site -> location).
+
+### Security considerations:
+
++   There is no real evidence to support the implicit assumption under
+    this model that continual usage of a web site correlates with user consent
+    to access powerful features.
+    +   Consider users who frequently use some web site for which location
+        data is useful, but denies that site persistent, background access to
+        geolocation permission.
+    +   A proportion of web traffic goes to sites which users may interact
+        with frequently, but may not necessarily want to grant powerful
+        capabilities.
+
+Similar to installation, the Chromium team does not regard engagement as a
+robust way of controlling access to APIs.
diff --git a/extensions/browser/api/management/management_api.cc b/extensions/browser/api/management/management_api.cc
index 651f5744..0a131a33 100644
--- a/extensions/browser/api/management/management_api.cc
+++ b/extensions/browser/api/management/management_api.cc
@@ -82,15 +82,7 @@
   }
 
   launch_type_list.push_back(management::LAUNCH_TYPE_OPEN_AS_REGULAR_TAB);
-
-  // TODO(dominickn): remove check when hosted apps can open in windows on Mac.
-  if (delegate->CanHostedAppsOpenInWindows())
-    launch_type_list.push_back(management::LAUNCH_TYPE_OPEN_AS_WINDOW);
-
-  if (!delegate->IsNewBookmarkAppsEnabled()) {
-    launch_type_list.push_back(management::LAUNCH_TYPE_OPEN_AS_PINNED_TAB);
-    launch_type_list.push_back(management::LAUNCH_TYPE_OPEN_FULL_SCREEN);
-  }
+  launch_type_list.push_back(management::LAUNCH_TYPE_OPEN_AS_WINDOW);
   return launch_type_list;
 }
 
diff --git a/extensions/browser/api/management/management_api_delegate.h b/extensions/browser/api/management/management_api_delegate.h
index 13a46e5..3136455 100644
--- a/extensions/browser/api/management/management_api_delegate.h
+++ b/extensions/browser/api/management/management_api_delegate.h
@@ -54,14 +54,6 @@
       const Extension* extension,
       content::BrowserContext* context) const = 0;
 
-  // Forwards the call to extensions::util::IsNewBookmarkAppsEnabled in
-  // chrome.
-  virtual bool IsNewBookmarkAppsEnabled() const = 0;
-
-  // Forwards the call to extensions::util::CanHostedAppsOpenInWindows in
-  // chrome.
-  virtual bool CanHostedAppsOpenInWindows() const = 0;
-
   // Forwards the call to AppLaunchInfo::GetFullLaunchURL in chrome.
   virtual GURL GetFullLaunchURL(const Extension* extension) const = 0;
 
diff --git a/gpu/command_buffer/service/external_vk_image_skia_representation.cc b/gpu/command_buffer/service/external_vk_image_skia_representation.cc
index 9650cd99..4f3c162 100644
--- a/gpu/command_buffer/service/external_vk_image_skia_representation.cc
+++ b/gpu/command_buffer/service/external_vk_image_skia_representation.cc
@@ -47,7 +47,8 @@
     GrContext* gr_context,
     int final_msaa_count,
     const SkSurfaceProps& surface_props) {
-  DCHECK(!surface_) << "Previous access hasn't ended yet";
+  DCHECK_EQ(access_mode_, kNone) << "Previous access hasn't ended yet";
+  DCHECK(!surface_);
 
   auto promise_texture = BeginAccess(false /* readonly */);
   if (!promise_texture)
@@ -58,38 +59,42 @@
       gr_context, promise_texture->backendTexture(), kTopLeft_GrSurfaceOrigin,
       final_msaa_count, sk_color_type,
       backing_impl()->color_space().ToSkColorSpace(), &surface_props);
+  access_mode_ = kWrite;
   return surface_;
 }
 
 void ExternalVkImageSkiaRepresentation::EndWriteAccess(
     sk_sp<SkSurface> surface) {
-  DCHECK(surface_) << "EndWriteAccess is called before BeginWriteAccess";
+  DCHECK_EQ(access_mode_, kWrite)
+      << "EndWriteAccess is called before BeginWriteAccess";
+  DCHECK(surface_);
   surface_ = nullptr;
   EndAccess(false /* readonly */);
+  access_mode_ = kNone;
 }
 
-sk_sp<SkPromiseImageTexture> ExternalVkImageSkiaRepresentation::BeginReadAccess(
-    SkSurface* sk_surface) {
-  DCHECK(!surface_) << "Previous access hasn't ended yet";
+sk_sp<SkPromiseImageTexture>
+ExternalVkImageSkiaRepresentation::BeginReadAccess() {
+  DCHECK_EQ(access_mode_, kNone) << "Previous access hasn't ended yet";
+  DCHECK(!surface_);
 
   auto promise_texture = BeginAccess(true /* readonly */);
   if (!promise_texture)
     return nullptr;
-
-  // Cache the sk surface in the representation so that it can be used in the
-  // EndReadAccess.
-  surface_ = sk_ref_sp(sk_surface);
+  access_mode_ = kRead;
   return promise_texture;
 }
 
 void ExternalVkImageSkiaRepresentation::EndReadAccess() {
-  DCHECK(surface_) << "EndReadAccess is called before BeginReadAccess";
-  surface_ = nullptr;
+  DCHECK_EQ(access_mode_, kRead)
+      << "EndReadAccess is called before BeginReadAccess";
   EndAccess(true /* readonly */);
+  access_mode_ = kNone;
 }
 
 sk_sp<SkPromiseImageTexture> ExternalVkImageSkiaRepresentation::BeginAccess(
     bool readonly) {
+  DCHECK_EQ(access_mode_, kNone);
   DestroySemaphores(std::move(begin_access_semaphores_), begin_access_fence_);
   begin_access_semaphores_.clear();
 
@@ -131,6 +136,7 @@
 }
 
 void ExternalVkImageSkiaRepresentation::EndAccess(bool readonly) {
+  DCHECK_NE(access_mode_, kNone);
   // Cleanup resources for previous accessing.
   DestroySemaphore(end_access_semaphore_, end_access_fence_);
 
diff --git a/gpu/command_buffer/service/external_vk_image_skia_representation.h b/gpu/command_buffer/service/external_vk_image_skia_representation.h
index 029d88e..5dddc5f 100644
--- a/gpu/command_buffer/service/external_vk_image_skia_representation.h
+++ b/gpu/command_buffer/service/external_vk_image_skia_representation.h
@@ -29,7 +29,7 @@
       int final_msaa_count,
       const SkSurfaceProps& surface_props) override;
   void EndWriteAccess(sk_sp<SkSurface> surface) override;
-  sk_sp<SkPromiseImageTexture> BeginReadAccess(SkSurface* sk_surface) override;
+  sk_sp<SkPromiseImageTexture> BeginReadAccess() override;
   void EndReadAccess() override;
 
  private:
@@ -69,6 +69,12 @@
 
   VkFence CreateFence();
 
+  enum AccessMode {
+    kNone = 0,
+    kRead = 1,
+    kWrite = 2,
+  };
+  AccessMode access_mode_ = kNone;
   sk_sp<SkSurface> surface_;
 
   std::vector<VkSemaphore> begin_access_semaphores_;
diff --git a/gpu/command_buffer/service/framebuffer_manager.cc b/gpu/command_buffer/service/framebuffer_manager.cc
index dab9457..f801bb4 100644
--- a/gpu/command_buffer/service/framebuffer_manager.cc
+++ b/gpu/command_buffer/service/framebuffer_manager.cc
@@ -385,6 +385,7 @@
       draw_buffer_float32_mask_(0u),
       draw_buffer_bound_mask_(0u),
       adjusted_draw_buffer_bound_mask_(0u),
+      last_color_attachment_id_(-1),
       read_buffer_(GL_COLOR_ATTACHMENT0) {
   manager->StartTracking(this);
   DCHECK_GT(manager->max_draw_buffers_, 0u);
@@ -969,7 +970,6 @@
     size_t shift_bits = index * 2;
     draw_buffer_type_mask_ |= base_type << shift_bits;
     draw_buffer_bound_mask_ |= 0x3 << shift_bits;
-
     if (GLES2Util::IsFloat32Format(internal_format)) {
       draw_buffer_float32_mask_ |= 0x3 << shift_bits;
     }
@@ -998,6 +998,29 @@
   }
 }
 
+void Framebuffer::OnInsertUpdateLastColorAttachmentId(GLenum attachment) {
+  if (attachment >= GL_COLOR_ATTACHMENT0 &&
+      attachment < GL_COLOR_ATTACHMENT0 + manager_->max_color_attachments_) {
+    last_color_attachment_id_ =
+        std::max(last_color_attachment_id_,
+                 static_cast<GLsizei>(attachment - GL_COLOR_ATTACHMENT0));
+  }
+}
+
+void Framebuffer::OnEraseUpdateLastColorAttachmentId(GLenum attachment) {
+  if (attachment >= GL_COLOR_ATTACHMENT0 &&
+      attachment < GL_COLOR_ATTACHMENT0 + manager_->max_color_attachments_ &&
+      static_cast<GLsizei>(attachment - GL_COLOR_ATTACHMENT0) ==
+          last_color_attachment_id_) {
+    for (last_color_attachment_id_--; last_color_attachment_id_ >= 0;
+         last_color_attachment_id_--) {
+      if (attachments_.find(GL_COLOR_ATTACHMENT0 + last_color_attachment_id_) !=
+          attachments_.end())
+        break;
+    }
+  }
+}
+
 void Framebuffer::AttachRenderbuffer(
     GLenum attachment, Renderbuffer* renderbuffer) {
   DCHECK_NE(static_cast<GLenum>(GL_DEPTH_STENCIL_ATTACHMENT), attachment);
@@ -1008,8 +1031,10 @@
     attachments_[attachment] = scoped_refptr<Attachment>(
         new RenderbufferAttachment(renderbuffer));
     renderbuffer->AddFramebufferAttachmentPoint(this, attachment);
+    OnInsertUpdateLastColorAttachmentId(attachment);
   } else {
     attachments_.erase(attachment);
+    OnEraseUpdateLastColorAttachmentId(attachment);
   }
   UnmarkAsComplete();
 }
@@ -1025,8 +1050,10 @@
     attachments_[attachment] = scoped_refptr<Attachment>(
         new TextureAttachment(texture_ref, target, level, samples, 0));
     texture_ref->texture()->AttachToFramebuffer();
+    OnInsertUpdateLastColorAttachmentId(attachment);
   } else {
     attachments_.erase(attachment);
+    OnEraseUpdateLastColorAttachmentId(attachment);
   }
   UnmarkAsComplete();
 }
@@ -1042,8 +1069,10 @@
     attachments_[attachment] = scoped_refptr<Attachment>(
         new TextureAttachment(texture_ref, target, level, 0, layer));
     texture_ref->texture()->AttachToFramebuffer();
+    OnInsertUpdateLastColorAttachmentId(attachment);
   } else {
     attachments_.erase(attachment);
+    OnEraseUpdateLastColorAttachmentId(attachment);
   }
   UnmarkAsComplete();
 }
diff --git a/gpu/command_buffer/service/framebuffer_manager.h b/gpu/command_buffer/service/framebuffer_manager.h
index f48538e..794df67 100644
--- a/gpu/command_buffer/service/framebuffer_manager.h
+++ b/gpu/command_buffer/service/framebuffer_manager.h
@@ -156,6 +156,7 @@
   bool HasDepthAttachment() const;
   bool HasStencilAttachment() const;
   bool HasActiveFloat32ColorAttachment() const;
+  GLsizei last_color_attachment_id() const { return last_color_attachment_id_; }
   GLenum GetDepthFormat() const;
   GLenum GetStencilFormat() const;
   GLenum GetDrawBufferInternalFormat() const;
@@ -241,6 +242,11 @@
 
   ~Framebuffer();
 
+  // Helper function updating cached last color attachment id bound.
+  // Called when attachments_ changed
+  void OnInsertUpdateLastColorAttachmentId(GLenum attachment);
+  void OnEraseUpdateLastColorAttachmentId(GLenum attachment);
+
   void MarkAsDeleted();
 
   void MarkAttachmentsAsCleared(
@@ -304,6 +310,8 @@
   uint32_t draw_buffer_bound_mask_;
   // This is the mask for the actual draw buffers sent to driver.
   uint32_t adjusted_draw_buffer_bound_mask_;
+  // The largest i of all GL_COLOR_ATTACHMENTi
+  GLsizei last_color_attachment_id_;
 
   GLenum read_buffer_;
 
diff --git a/gpu/command_buffer/service/framebuffer_manager_unittest.cc b/gpu/command_buffer/service/framebuffer_manager_unittest.cc
index 787123b..a19ffa39 100644
--- a/gpu/command_buffer/service/framebuffer_manager_unittest.cc
+++ b/gpu/command_buffer/service/framebuffer_manager_unittest.cc
@@ -1419,6 +1419,96 @@
   EXPECT_TRUE(framebuffer_->GetAttachment(GL_DEPTH_ATTACHMENT) == nullptr);
 }
 
+TEST_F(FramebufferInfoTest, LastColorAttachmentIdTest) {
+  const GLuint kTextureClient1Id = 33;
+  const GLuint kTextureService1Id = 333;
+  const GLuint kTextureClient2Id = 34;
+  const GLuint kTextureService2Id = 334;
+  const GLuint kTextureClient3Id = 35;
+  const GLuint kTextureService3Id = 335;
+  const GLuint kRenderbufferClientId = 36;
+  const GLuint kRenderbufferServiceId = 336;
+  const GLuint kTextureLayerClientId = 37;
+  const GLuint kTextureLayerServiceId = 337;
+
+  const GLenum kTarget1 = GL_TEXTURE_2D;
+  const GLint kLevel1 = 0;
+  const GLint kSamples1 = 0;
+
+  const GLenum kTargetTextureLayer = GL_TEXTURE_2D_ARRAY;
+  const GLint kBorder = 0;
+  const GLenum kType = GL_UNSIGNED_BYTE;
+  const GLsizei kWidth = 16;
+  const GLsizei kHeight = 32;
+  const GLint kDepth = 2;
+  const GLint kLevel = 0;
+  const GLenum kFormat = GL_RGBA;
+  const GLsizei kLayer = 0;
+
+  texture_manager_->CreateTexture(kTextureClient1Id, kTextureService1Id);
+  scoped_refptr<TextureRef> texture1(
+      texture_manager_->GetTexture(kTextureClient1Id));
+  ASSERT_TRUE(texture1.get() != nullptr);
+  texture_manager_->CreateTexture(kTextureClient2Id, kTextureService2Id);
+  scoped_refptr<TextureRef> texture2(
+      texture_manager_->GetTexture(kTextureClient2Id));
+  ASSERT_TRUE(texture2.get() != nullptr);
+  texture_manager_->CreateTexture(kTextureClient3Id, kTextureService3Id);
+  scoped_refptr<TextureRef> texture3(
+      texture_manager_->GetTexture(kTextureClient3Id));
+  ASSERT_TRUE(texture3.get() != nullptr);
+
+  renderbuffer_manager_->CreateRenderbuffer(kRenderbufferClientId,
+                                            kRenderbufferServiceId);
+  Renderbuffer* renderbuffer =
+      renderbuffer_manager_->GetRenderbuffer(kRenderbufferClientId);
+  ASSERT_TRUE(renderbuffer != nullptr);
+
+  texture_manager_->CreateTexture(kTextureLayerClientId,
+                                  kTextureLayerServiceId);
+  scoped_refptr<TextureRef> textureLayer(
+      texture_manager_->GetTexture(kTextureLayerClientId));
+  ASSERT_TRUE(textureLayer.get());
+
+  texture_manager_->SetTarget(textureLayer.get(), kTargetTextureLayer);
+  texture_manager_->SetLevelInfo(textureLayer.get(), kTargetTextureLayer,
+                                 kLevel, kFormat, kWidth, kHeight, kDepth,
+                                 kBorder, kFormat, kType, gfx::Rect());
+
+  EXPECT_EQ(framebuffer_->last_color_attachment_id(), -1);
+  framebuffer_->AttachTexture(GL_COLOR_ATTACHMENT0, texture1.get(), kTarget1,
+                              kLevel1, kSamples1);
+  EXPECT_EQ(framebuffer_->last_color_attachment_id(), 0);
+  framebuffer_->AttachTexture(GL_COLOR_ATTACHMENT2, texture3.get(), kTarget1,
+                              kLevel1, kSamples1);
+  EXPECT_EQ(framebuffer_->last_color_attachment_id(), 2);
+  framebuffer_->AttachTexture(GL_COLOR_ATTACHMENT1, texture2.get(), kTarget1,
+                              kLevel1, kSamples1);
+  EXPECT_EQ(framebuffer_->last_color_attachment_id(), 2);
+  framebuffer_->AttachRenderbuffer(GL_DEPTH_ATTACHMENT, renderbuffer);
+  EXPECT_EQ(framebuffer_->last_color_attachment_id(), 2);
+  framebuffer_->AttachRenderbuffer(GL_COLOR_ATTACHMENT3, renderbuffer);
+  EXPECT_EQ(framebuffer_->last_color_attachment_id(), 3);
+  framebuffer_->AttachTexture(GL_COLOR_ATTACHMENT4, texture1.get(), kTarget1,
+                              kLevel1, kSamples1);
+  EXPECT_EQ(framebuffer_->last_color_attachment_id(), 4);
+  EXPECT_TRUE(framebuffer_->GetAttachment(GL_COLOR_ATTACHMENT0) != nullptr);
+  framebuffer_->AttachTextureLayer(GL_COLOR_ATTACHMENT5, textureLayer.get(),
+                                   kTargetTextureLayer, kLevel, kLayer);
+  EXPECT_EQ(framebuffer_->last_color_attachment_id(), 5);
+
+  framebuffer_->UnbindTexture(kTargetTextureLayer, textureLayer.get());
+  EXPECT_EQ(framebuffer_->last_color_attachment_id(), 4);
+  framebuffer_->UnbindTexture(kTarget1, texture2.get());
+  EXPECT_EQ(framebuffer_->last_color_attachment_id(), 4);
+  framebuffer_->UnbindTexture(kTarget1, texture1.get());
+  EXPECT_EQ(framebuffer_->last_color_attachment_id(), 3);
+  framebuffer_->UnbindRenderbuffer(GL_COLOR_ATTACHMENT3, renderbuffer);
+  EXPECT_EQ(framebuffer_->last_color_attachment_id(), 2);
+  framebuffer_->UnbindTexture(kTarget1, texture3.get());
+  EXPECT_EQ(framebuffer_->last_color_attachment_id(), -1);
+}
+
 TEST_F(FramebufferInfoTest, IsCompleteMarkAsComplete) {
   const GLuint kRenderbufferClient1Id = 33;
   const GLuint kRenderbufferService1Id = 333;
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
index 0d85a6f..2f2e98b2 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
@@ -1536,10 +1536,12 @@
   // location is not -1.
   bool CheckCurrentProgramForUniform(GLint location, const char* function_name);
 
-  // Checks if the current program samples a texture that is also the color
-  // image of the current bound framebuffer, i.e., the source and destination
-  // of the draw operation are the same.
-  bool CheckDrawingFeedbackLoops();
+  // Helper for CheckDrawingFeedbackLoops. Returns true if the attachment is
+  // the same one where it samples from during drawing.
+  bool CheckDrawingFeedbackLoopsHelper(
+      const Framebuffer::Attachment* attachment,
+      TextureRef* texture_ref,
+      const char* function_name);
 
   bool SupportsDrawBuffers() const;
 
@@ -9935,32 +9937,15 @@
       location);
 }
 
-bool GLES2DecoderImpl::CheckDrawingFeedbackLoops() {
-  Framebuffer* framebuffer = GetFramebufferInfoForTarget(GL_FRAMEBUFFER);
-  if (!framebuffer)
-    return false;
-  const Framebuffer::Attachment* attachment =
-      framebuffer->GetAttachment(GL_COLOR_ATTACHMENT0);
-  if (!attachment)
-    return false;
-
-  DCHECK(state_.current_program.get());
-  const Program::SamplerIndices& sampler_indices =
-      state_.current_program->sampler_indices();
-  for (size_t ii = 0; ii < sampler_indices.size(); ++ii) {
-    const Program::UniformInfo* uniform_info =
-        state_.current_program->GetUniformInfo(sampler_indices[ii]);
-    DCHECK(uniform_info);
-    for (size_t jj = 0; jj < uniform_info->texture_units.size(); ++jj) {
-      GLuint texture_unit_index = uniform_info->texture_units[jj];
-      if (texture_unit_index >= state_.texture_units.size())
-        continue;
-      TextureUnit& texture_unit = state_.texture_units[texture_unit_index];
-      TextureRef* texture_ref =
-          texture_unit.GetInfoForSamplerType(uniform_info->type);
-      if (attachment->IsTexture(texture_ref))
-        return true;
-    }
+bool GLES2DecoderImpl::CheckDrawingFeedbackLoopsHelper(
+    const Framebuffer::Attachment* attachment,
+    TextureRef* texture_ref,
+    const char* function_name) {
+  if (attachment && attachment->IsTexture(texture_ref)) {
+    LOCAL_SET_GL_ERROR(
+        GL_INVALID_OPERATION, function_name,
+        "Source and destination textures of the draw are the same.");
+    return true;
   }
   return false;
 }
@@ -10691,6 +10676,27 @@
         TextureUnit& texture_unit = state_.texture_units[texture_unit_index];
         TextureRef* texture_ref =
             texture_unit.GetInfoForSamplerType(uniform_info->type);
+
+        // Find if the texture is also a depth or stencil attachment
+        // Regardless of whether depth/stencil writes and masks are enabled
+        // If so, there's a drawing feedback loop.
+
+        Framebuffer* framebuffer =
+            GetFramebufferInfoForTarget(GL_DRAW_FRAMEBUFFER);
+        if (framebuffer) {
+          if (CheckDrawingFeedbackLoopsHelper(
+                  framebuffer->GetAttachment(GL_DEPTH_ATTACHMENT), texture_ref,
+                  function_name)) {
+            return false;
+          }
+
+          if (CheckDrawingFeedbackLoopsHelper(
+                  framebuffer->GetAttachment(GL_STENCIL_ATTACHMENT),
+                  texture_ref, function_name)) {
+            return false;
+          }
+        }
+
         GLenum textarget = GetBindTargetForSamplerType(uniform_info->type);
         const SamplerState& sampler_state = GetSamplerStateForTextureUnit(
             uniform_info->type, texture_unit_index);
@@ -10728,6 +10734,21 @@
           return false;
         }
 
+        // Find if the texture is also a color attachment
+        // If so, there's a drawing feedback loop.
+
+        if (framebuffer) {
+          for (GLsizei kk = 0; kk <= framebuffer->last_color_attachment_id();
+               ++kk) {
+            GLenum attachment = static_cast<GLenum>(GL_COLOR_ATTACHMENT0 + kk);
+            if (CheckDrawingFeedbackLoopsHelper(
+                    framebuffer->GetAttachment(attachment), texture_ref,
+                    function_name)) {
+              return false;
+            }
+          }
+        }
+
         if (textarget != GL_TEXTURE_CUBE_MAP) {
           Texture* texture = texture_ref->texture();
           if (DoBindOrCopyTexImageIfNeeded(texture, textarget,
@@ -10865,13 +10886,6 @@
     }
   }
 
-  if (CheckDrawingFeedbackLoops()) {
-    LOCAL_SET_GL_ERROR(
-        GL_INVALID_OPERATION, function_name,
-        "Source and destination textures of the draw are the same.");
-    return false;
-  }
-
   if (!state_.vertex_attrib_manager->ValidateBindings(
           function_name, this, feature_info_.get(), buffer_manager(),
           state_.current_program.get(), max_vertex_accessed, instanced,
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer.cc b/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer.cc
index b60e67a..e64c55c 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer.cc
@@ -57,6 +57,21 @@
   kWrite,
 };
 
+std::ostream& operator<<(std::ostream& os, RepresentationAccessMode mode) {
+  switch (mode) {
+    case RepresentationAccessMode::kNone:
+      os << "kNone";
+      break;
+    case RepresentationAccessMode::kRead:
+      os << "kRead";
+      break;
+    case RepresentationAccessMode::kWrite:
+      os << "kWrite";
+      break;
+  }
+  return os;
+}
+
 void DestroySemaphore(VkDevice vk_device,
                       VkQueue vk_queue,
                       VkSemaphore semaphore) {
@@ -121,7 +136,6 @@
 void EndVulkanAccess(gpu::VulkanImplementation* vk_implementation,
                      VkDevice vk_device,
                      VkQueue vk_queue,
-                     SkSurface** surface,
                      base::ScopedFD* sync_fd) {
   // Create a vk semaphore which can be exported.
   VkExportSemaphoreCreateInfo export_info;
@@ -138,23 +152,15 @@
       vkCreateSemaphore(vk_device, &sem_info, nullptr, &vk_semaphore);
   if (result != VK_SUCCESS) {
     LOG(ERROR) << "vkCreateSemaphore failed";
-    (*surface) = nullptr;
     return;
   }
-  GrBackendSemaphore gr_semaphore;
-  gr_semaphore.initVulkan(vk_semaphore);
 
-  // If GrSemaphoresSubmitted::kNo is returned, the GPU back-end did not
-  // create or add any semaphores to signal on the GPU; the caller should not
-  // instruct the GPU to wait on any of the semaphores.
-  if ((*surface)->flushAndSignalSemaphores(1, &gr_semaphore) ==
-      GrSemaphoresSubmitted::kNo) {
+  if (!SubmitSignalVkSemaphore(vk_queue, vk_semaphore,
+                               VK_NULL_HANDLE /* vk_fence */)) {
+    LOG(ERROR) << "Failed to wait on semaphore";
     vkDestroySemaphore(vk_device, vk_semaphore, nullptr);
-    (*surface) = nullptr;
     return;
   }
-  // TODO(ericrk): Keep the surface around for re-use.
-  (*surface) = nullptr;
 
   // Export a sync fd from the semaphore.
   SemaphoreHandle semaphore_handle =
@@ -345,11 +351,12 @@
       GrContext* gr_context,
       int final_msaa_count,
       const SkSurfaceProps& surface_props) override {
+    DCHECK_EQ(mode_, RepresentationAccessMode::kNone);
     CheckContext();
+
     // if there is already a surface_, it means previous BeginWriteAccess
     // doesn't have a corresponding EndWriteAccess.
-    if (surface_)
-      return nullptr;
+    DCHECK(!surface_);
 
     base::ScopedFD sync_fd;
     if (!ahb_backing()->BeginWrite(&sync_fd))
@@ -374,6 +381,7 @@
   }
 
   void EndWriteAccess(sk_sp<SkSurface> surface) override {
+    DCHECK_EQ(mode_, RepresentationAccessMode::kWrite);
     DCHECK(surface_);
     DCHECK_EQ(surface.get(), surface_);
     DCHECK(surface->unique());
@@ -383,8 +391,10 @@
     EndWriteAccessInternal();
   }
 
-  sk_sp<SkPromiseImageTexture> BeginReadAccess(SkSurface* sk_surface) override {
+  sk_sp<SkPromiseImageTexture> BeginReadAccess() override {
+    DCHECK_EQ(mode_, RepresentationAccessMode::kNone);
     CheckContext();
+
     base::ScopedFD write_sync_fd;
     if (!ahb_backing()->BeginRead(this, &write_sync_fd))
       return nullptr;
@@ -397,6 +407,7 @@
   }
 
   void EndReadAccess() override {
+    DCHECK_EQ(mode_, RepresentationAccessMode::kRead);
     CheckContext();
 
     base::ScopedFD sync_fd = CreateEglFenceAndExportFd();
@@ -459,12 +470,7 @@
   }
 
   ~SharedImageRepresentationSkiaVkAHB() override {
-    if (mode_ == RepresentationAccessMode::kRead) {
-      EndReadAccess();
-    } else if (mode_ == RepresentationAccessMode::kWrite) {
-      EndWriteAccessInternal();
-    }
-
+    DCHECK_EQ(mode_, RepresentationAccessMode::kNone);
     DCHECK(!surface_);
   }
 
@@ -472,9 +478,9 @@
       GrContext* gr_context,
       int final_msaa_count,
       const SkSurfaceProps& surface_props) override {
+    DCHECK_EQ(mode_, RepresentationAccessMode::kNone);
     // If previous access has not ended.
-    if (surface_)
-      return nullptr;
+    DCHECK(!surface_);
 
     base::ScopedFD sync_fd;
     if (!ahb_backing()->BeginWrite(&sync_fd))
@@ -520,16 +526,14 @@
   }
 
   void EndWriteAccess(sk_sp<SkSurface> surface) override {
+    DCHECK_EQ(mode_, RepresentationAccessMode::kWrite);
     DCHECK_EQ(surface.get(), surface_);
     DCHECK(surface->unique());
     EndWriteAccessInternal();
   }
 
-  sk_sp<SkPromiseImageTexture> BeginReadAccess(SkSurface* sk_surface) override {
-    // If previous access has not ended.
-    if (surface_)
-      return nullptr;
-    DCHECK(sk_surface);
+  sk_sp<SkPromiseImageTexture> BeginReadAccess() override {
+    DCHECK_EQ(mode_, RepresentationAccessMode::kNone);
 
     // Synchronise the read access with the writes.
     base::ScopedFD sync_fd;
@@ -559,24 +563,17 @@
         vk_implementation(), vk_device(), vk_phy_device(), vk_queue(),
         ahb_backing()->GetAhbHandle(), size(), format(), std::move(semaphore));
 
-    // Cache the sk surface in the representation so that it can be used in the
-    // EndReadAccess. Also make sure previous surface_ have been consumed by
-    // EndReadAccess() call.
-    surface_ = sk_surface;
-
     mode_ = RepresentationAccessMode::kRead;
 
     return promise_texture;
   }
 
   void EndReadAccess() override {
-    // There should be a surface_ from the BeginReadAccess().
-    DCHECK_EQ(RepresentationAccessMode::kRead, mode_);
-    DCHECK(surface_);
+    DCHECK_EQ(mode_, RepresentationAccessMode::kRead);
+    DCHECK(!surface_);
 
     base::ScopedFD sync_fd;
-    EndVulkanAccess(vk_implementation(), vk_device(), vk_queue(), &surface_,
-                    &sync_fd);
+    EndVulkanAccess(vk_implementation(), vk_device(), vk_queue(), &sync_fd);
     // pass this sync fd to the backing.
     ahb_backing()->EndRead(this, std::move(sync_fd));
 
@@ -616,8 +613,8 @@
     DCHECK(surface_);
 
     base::ScopedFD sync_fd;
-    EndVulkanAccess(vk_implementation(), vk_device(), vk_queue(), &surface_,
-                    &sync_fd);
+    EndVulkanAccess(vk_implementation(), vk_device(), vk_queue(), &sync_fd);
+    surface_ = nullptr;
     ahb_backing()->EndWrite(std::move(sync_fd));
 
     mode_ = RepresentationAccessMode::kNone;
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer_unittest.cc b/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer_unittest.cc
index a899c7f..52f6782 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer_unittest.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer_unittest.cc
@@ -126,7 +126,7 @@
   EXPECT_EQ(gl_legacy_shared_image.size().width(), surface->width());
   EXPECT_EQ(gl_legacy_shared_image.size().height(), surface->height());
   skia_representation->EndWriteAccess(std::move(surface));
-  auto promise_texture = skia_representation->BeginReadAccess(nullptr);
+  auto promise_texture = skia_representation->BeginReadAccess();
   EXPECT_TRUE(promise_texture);
   if (promise_texture) {
     GrBackendTexture backend_texture = promise_texture->backendTexture();
@@ -187,7 +187,7 @@
   auto skia_representation = shared_image_representation_factory_->ProduceSkia(
       mailbox, context_state_.get());
   EXPECT_TRUE(skia_representation);
-  auto promise_texture = skia_representation->BeginReadAccess(nullptr);
+  auto promise_texture = skia_representation->BeginReadAccess();
   EXPECT_TRUE(promise_texture);
   if (promise_texture) {
     GrBackendTexture backend_texture = promise_texture->backendTexture();
@@ -320,14 +320,8 @@
   auto skia_representation2 = shared_image_representation_factory_->ProduceSkia(
       gl_legacy_shared_image.mailbox(), context_state_.get());
 
-  sk_sp<SkSurface> surface =
-      SkSurface::MakeNull(gl_legacy_shared_image.size().width(),
-                          gl_legacy_shared_image.size().height());
-  EXPECT_TRUE(skia_representation->BeginReadAccess(surface.get()));
-  sk_sp<SkSurface> surface2 =
-      SkSurface::MakeNull(gl_legacy_shared_image.size().width(),
-                          gl_legacy_shared_image.size().height());
-  EXPECT_TRUE(skia_representation2->BeginReadAccess(surface2.get()));
+  EXPECT_TRUE(skia_representation->BeginReadAccess());
+  EXPECT_TRUE(skia_representation2->BeginReadAccess());
 
   skia_representation2->EndReadAccess();
   skia_representation2.reset();
@@ -348,14 +342,8 @@
 
   auto skia_representation = shared_image_representation_factory_->ProduceSkia(
       gl_legacy_shared_image.mailbox(), context_state_.get());
-  sk_sp<SkSurface> surface =
-      SkSurface::MakeNull(gl_legacy_shared_image.size().width(),
-                          gl_legacy_shared_image.size().height());
-  EXPECT_TRUE(skia_representation->BeginReadAccess(surface.get()));
-  sk_sp<SkSurface> surface2 =
-      SkSurface::MakeNull(gl_legacy_shared_image.size().width(),
-                          gl_legacy_shared_image.size().height());
-  EXPECT_FALSE(skia_representation->BeginReadAccess(surface2.get()));
+  EXPECT_TRUE(skia_representation->BeginReadAccess());
+  EXPECT_FALSE(skia_representation->BeginReadAccess());
 
   skia_representation->EndReadAccess();
   skia_representation.reset();
@@ -373,10 +361,7 @@
 
   auto skia_representation = shared_image_representation_factory_->ProduceSkia(
       gl_legacy_shared_image.mailbox(), context_state_.get());
-  sk_sp<SkSurface> surface =
-      SkSurface::MakeNull(gl_legacy_shared_image.size().width(),
-                          gl_legacy_shared_image.size().height());
-  EXPECT_TRUE(skia_representation->BeginReadAccess(surface.get()));
+  EXPECT_TRUE(skia_representation->BeginReadAccess());
 
   EXPECT_FALSE(skia_representation->BeginWriteAccess(
       gr_context(), 0, SkSurfaceProps(0, kUnknown_SkPixelGeometry)));
@@ -400,10 +385,7 @@
   auto surface = skia_representation->BeginWriteAccess(
       gr_context(), 0, SkSurfaceProps(0, kUnknown_SkPixelGeometry));
 
-  sk_sp<SkSurface> surface2 =
-      SkSurface::MakeNull(gl_legacy_shared_image.size().width(),
-                          gl_legacy_shared_image.size().height());
-  EXPECT_FALSE(skia_representation->BeginReadAccess(surface2.get()));
+  EXPECT_FALSE(skia_representation->BeginReadAccess());
 
   skia_representation->EndWriteAccess(std::move(surface));
   skia_representation.reset();
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.cc b/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.cc
index d67d90c..90bb53f 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.cc
@@ -312,7 +312,7 @@
     write_surface_ = nullptr;
   }
 
-  sk_sp<SkPromiseImageTexture> BeginReadAccess(SkSurface* sk_surface) override {
+  sk_sp<SkPromiseImageTexture> BeginReadAccess() override {
     CheckContext();
     static_cast<SharedImageBackingWithReadAccess*>(backing())
         ->BeginReadAccess();
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_gl_texture_unittest.cc b/gpu/command_buffer/service/shared_image_backing_factory_gl_texture_unittest.cc
index 445ea21..80d188e 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_gl_texture_unittest.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_gl_texture_unittest.cc
@@ -186,7 +186,7 @@
   EXPECT_EQ(size.width(), surface->width());
   EXPECT_EQ(size.height(), surface->height());
   skia_representation->EndWriteAccess(std::move(surface));
-  auto promise_texture = skia_representation->BeginReadAccess(nullptr);
+  auto promise_texture = skia_representation->BeginReadAccess();
   EXPECT_TRUE(promise_texture);
   if (promise_texture) {
     GrBackendTexture backend_texture = promise_texture->backendTexture();
@@ -285,7 +285,7 @@
   EXPECT_EQ(size.width(), surface->width());
   EXPECT_EQ(size.height(), surface->height());
   skia_representation->EndWriteAccess(std::move(surface));
-  auto promise_texture = skia_representation->BeginReadAccess(nullptr);
+  auto promise_texture = skia_representation->BeginReadAccess();
   EXPECT_TRUE(promise_texture);
   if (promise_texture) {
     GrBackendTexture backend_texture = promise_texture->backendTexture();
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_iosurface.mm b/gpu/command_buffer/service/shared_image_backing_factory_iosurface.mm
index 2432ad9..106e681 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_iosurface.mm
+++ b/gpu/command_buffer/service/shared_image_backing_factory_iosurface.mm
@@ -148,7 +148,7 @@
     }
   }
 
-  sk_sp<SkPromiseImageTexture> BeginReadAccess(SkSurface* sk_surface) override {
+  sk_sp<SkPromiseImageTexture> BeginReadAccess() override {
     return promise_texture_;
   }
 
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_iosurface_unittest.cc b/gpu/command_buffer/service/shared_image_backing_factory_iosurface_unittest.cc
index d37b3da..21d087ad 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_iosurface_unittest.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_iosurface_unittest.cc
@@ -135,7 +135,7 @@
   EXPECT_EQ(size.width(), surface->width());
   EXPECT_EQ(size.height(), surface->height());
   skia_representation->EndWriteAccess(std::move(surface));
-  auto promise_texture = skia_representation->BeginReadAccess(nullptr);
+  auto promise_texture = skia_representation->BeginReadAccess();
   EXPECT_TRUE(promise_texture);
   if (promise_texture) {
     GrBackendTexture backend_texture = promise_texture->backendTexture();
@@ -196,7 +196,7 @@
   auto skia_representation = shared_image_representation_factory_->ProduceSkia(
       mailbox, context_state_);
   EXPECT_TRUE(skia_representation);
-  auto promise_texture = skia_representation->BeginReadAccess(nullptr);
+  auto promise_texture = skia_representation->BeginReadAccess();
   EXPECT_TRUE(promise_texture);
   if (promise_texture) {
     GrBackendTexture backend_texture = promise_texture->backendTexture();
diff --git a/gpu/command_buffer/service/shared_image_representation.h b/gpu/command_buffer/service/shared_image_representation.h
index a1c5e550..4b6fb436 100644
--- a/gpu/command_buffer/service/shared_image_representation.h
+++ b/gpu/command_buffer/service/shared_image_representation.h
@@ -154,8 +154,7 @@
       int final_msaa_count,
       const SkSurfaceProps& surface_props) = 0;
   virtual void EndWriteAccess(sk_sp<SkSurface> surface) = 0;
-  virtual sk_sp<SkPromiseImageTexture> BeginReadAccess(
-      SkSurface* sk_surface) = 0;
+  virtual sk_sp<SkPromiseImageTexture> BeginReadAccess() = 0;
   virtual void EndReadAccess() = 0;
 };
 
diff --git a/gpu/command_buffer/service/wrapped_sk_image.cc b/gpu/command_buffer/service/wrapped_sk_image.cc
index be608c9..fb16184 100644
--- a/gpu/command_buffer/service/wrapped_sk_image.cc
+++ b/gpu/command_buffer/service/wrapped_sk_image.cc
@@ -210,7 +210,7 @@
     write_surface_ = nullptr;
   }
 
-  sk_sp<SkPromiseImageTexture> BeginReadAccess(SkSurface* sk_surface) override {
+  sk_sp<SkPromiseImageTexture> BeginReadAccess() override {
     return wrapped_sk_image()->promise_texture();
   }
 
diff --git a/infra/config/cr-buildbucket.cfg b/infra/config/cr-buildbucket.cfg
index 3345a58..c42f673 100644
--- a/infra/config/cr-buildbucket.cfg
+++ b/infra/config/cr-buildbucket.cfg
@@ -3506,7 +3506,6 @@
       name: "gpu-manual-try-linux-nvidia-tsn"
     }
     builders { mixins: "linux-try" name: "leak_detection_linux" }
-    builders { mixins: "linux-angle-try" name: "linux-angle-rel" }
     builders { mixins: "linux-angle-try" name: "linux_angle_compile_dbg_ng" }
     builders { mixins: "linux-angle-try" name: "linux_angle_dbg_ng" }
     builders { mixins: "linux-angle-try" name: "linux_angle_deqp_rel_ng" }
@@ -3626,7 +3625,6 @@
     builders { mixins: "ios-try" name: "ios-simulator-eg" }
     builders { mixins: "ios-try" name: "ios-simulator-xcode-clang" }
     builders { mixins: "ios-try" name: "ios-slimnav" }
-    builders { mixins: "mac-angle-try" name: "mac-angle-rel" }
     builders { mixins: "mac-angle-try" name: "mac_angle_compile_dbg_ng" }
     builders { mixins: "mac-angle-try" name: "mac_angle_dbg_ng" }
     builders { mixins: "mac-angle-try" name: "mac_angle_rel_ng" }
diff --git a/infra/config/luci-milo.cfg b/infra/config/luci-milo.cfg
index 79a77ad..ba67371 100644
--- a/infra/config/luci-milo.cfg
+++ b/infra/config/luci-milo.cfg
@@ -4478,9 +4478,6 @@
     name: "buildbucket/luci.chromium.try/android_angle_vk64_rel_ng"
   }
   builders {
-    name: "buildbucket/luci.chromium.try/linux-angle-rel"
-  }
-  builders {
     name: "buildbucket/luci.chromium.try/linux_angle_dbg_ng"
   }
   builders {
@@ -4493,9 +4490,6 @@
     name: "buildbucket/luci.chromium.try/linux_angle_rel_ng"
   }
   builders {
-    name: "buildbucket/luci.chromium.try/mac-angle-rel"
-  }
-  builders {
     name: "buildbucket/luci.chromium.try/mac_angle_dbg_ng"
   }
   builders {
@@ -4725,9 +4719,6 @@
     name: "buildbucket/luci.chromium.try/gpu-manual-try-win10-nvidia-rel"
   }
   builders {
-    name: "buildbucket/luci.chromium.try/linux-angle-rel"
-  }
-  builders {
     name: "buildbucket/luci.chromium.try/linux-blink-heap-concurrent-marking-tsan-rel"
   }
   builders {
@@ -4867,9 +4858,6 @@
     name: "buildbucket/luci.chromium.try/ios12-sdk-simulator"
   }
   builders {
-    name: "buildbucket/luci.chromium.try/mac-angle-rel"
-  }
-  builders {
     name: "buildbucket/luci.chromium.try/mac-jumbo-rel"
   }
   builders {
diff --git a/ios/chrome/browser/autofill/automation/automation_egtest.mm b/ios/chrome/browser/autofill/automation/automation_egtest.mm
index bd85c3ce..d4b9899 100644
--- a/ios/chrome/browser/autofill/automation/automation_egtest.mm
+++ b/ios/chrome/browser/autofill/automation/automation_egtest.mm
@@ -26,7 +26,6 @@
 #include "components/autofill/ios/browser/autofill_driver_ios.h"
 #import "ios/chrome/browser/autofill/form_suggestion_label.h"
 #import "ios/chrome/test/app/chrome_test_util.h"
-#import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
 #import "ios/web/public/test/earl_grey/web_view_actions.h"
 #import "ios/web/public/test/earl_grey/web_view_matchers.h"
 #include "ios/web/public/test/element_selector.h"
diff --git a/ios/chrome/browser/metrics/tab_usage_recorder_egtest.mm b/ios/chrome/browser/metrics/tab_usage_recorder_egtest.mm
index e98557fb..0b6ac7f 100644
--- a/ios/chrome/browser/metrics/tab_usage_recorder_egtest.mm
+++ b/ios/chrome/browser/metrics/tab_usage_recorder_egtest.mm
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#import <EarlGrey/EarlGrey.h>
+
 #include <memory>
 
 #include "base/bind.h"
diff --git a/ios/chrome/browser/metrics/tab_usage_recorder_test_util.mm b/ios/chrome/browser/metrics/tab_usage_recorder_test_util.mm
index 060c56e..e5a4c5f 100644
--- a/ios/chrome/browser/metrics/tab_usage_recorder_test_util.mm
+++ b/ios/chrome/browser/metrics/tab_usage_recorder_test_util.mm
@@ -4,6 +4,7 @@
 
 #include "ios/chrome/browser/metrics/tab_usage_recorder_test_util.h"
 
+#import <EarlGrey/EarlGrey.h>
 #import <Foundation/Foundation.h>
 
 #import "base/ios/block_types.h"
diff --git a/ios/chrome/browser/overlays/BUILD.gn b/ios/chrome/browser/overlays/BUILD.gn
new file mode 100644
index 0000000..0f7c67d
--- /dev/null
+++ b/ios/chrome/browser/overlays/BUILD.gn
@@ -0,0 +1,52 @@
+# Copyright 2019 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.
+
+source_set("overlays") {
+  sources = [
+    "overlay_request.h",
+    "overlay_response.h",
+    "overlay_user_data.h",
+  ]
+
+  configs += [ "//build/config/compiler:enable_arc" ]
+
+  allow_circular_includes_from = [ ":internal" ]
+
+  deps = [
+    ":internal",
+    "//base",
+  ]
+}
+
+source_set("internal") {
+  sources = [
+    "overlay_request_impl.cc",
+    "overlay_request_impl.h",
+    "overlay_response_impl.cc",
+    "overlay_response_impl.h",
+  ]
+
+  deps = [
+    "//base",
+  ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [
+    "overlay_request_unittest.cc",
+    "overlay_response_unittest.cc",
+  ]
+
+  configs += [ "//build/config/compiler:enable_arc" ]
+
+  deps = [
+    ":overlays",
+    "//base/test:test_support",
+    "//ios/chrome/browser/overlays/test",
+    "//ios/web/public",
+    "//ios/web/public/test/fakes",
+    "//testing/gtest",
+  ]
+}
diff --git a/ios/chrome/browser/overlays/README.md b/ios/chrome/browser/overlays/README.md
new file mode 100644
index 0000000..f9a41fa
--- /dev/null
+++ b/ios/chrome/browser/overlays/README.md
@@ -0,0 +1,71 @@
+# OverlayService
+
+OverlayService is used to schedule the display of UI over the content area of a
+WebState.
+
+## Classes of note:
+
+##### OverlayRequest
+
+OverlayRequests are passed to OverlayService to trigger the display of overlay
+UI over a WebState's content area.  Service clients should create
+OverlayRequests with an OverlayUserData subclass with the information necessary
+to configure the overlay UI.  The config user data can later be extracted from
+OverlayRequests by the service's observers and UI delegate.
+
+##### OverlayResponse
+
+OverlayResponses are provided to each OverlayRequest to describe the user's
+interaction with the overlay UI.  Service clients should create OverlayResponses
+with an OverlayUserData subclass with the overlay UI user interaction
+information necessary to execute the callback for that overlay.
+
+## Example usage of service:
+
+### Showing an alert with a title, message, an OK button, and a Cancel button
+
+#####1. Create OverlayUserData subclasses for the requests and responses:
+
+A request configuration user data should be created with the information
+necessary to set up the overlay UI being requested.
+
+    class AlertConfig : public OverlayUserData<AlertConfig> {
+     public:
+      const std::string& title() const;
+      const std::string& message() const;
+      const std::vector<std::string>& button\_titles() const;
+     private:
+      OVERLAY\_USER\_DATA\_SETUP(AlertConfig);
+      AlertConfig(const std::string& title, const std::string& message);
+    };
+
+A response ino user data should be created with the information necessary to
+execute the callback for the overlay.
+
+    class AlertInfo : public OverlayUserData<AlertInfo> {
+     public:
+      const size\_t tapped\_button\_index() const;
+     private:
+      OVERLAY\_USER\_DATA\_SETUP(AlertInfo);
+      AlertInfo(size\_t tapped\_button\_index);
+    };
+
+#####2. Request an overlay using the request config user data.
+
+An OverlayRequest for the alert can be created using:
+
+    OverlayRequest::CreateWithConfig<AlertConfig>(
+        "alert title", "message text");
+
+*TODO: insert OverlayService calls when interface is added.*
+
+#####3. Supply a response to the request.
+
+An OverlayResponse for the alert can be created using:
+
+    OverlayResponse::CreateWithInfo<AlertInfo>(0);
+
+
+*NOTE: this service is a work-in-progress, and this file only outlines how to
+use the files currently in the repository.  It will be updated with more
+complete instructions as more of the service lands.*
diff --git a/ios/chrome/browser/overlays/overlay_request.h b/ios/chrome/browser/overlays/overlay_request.h
new file mode 100644
index 0000000..66e5daac
--- /dev/null
+++ b/ios/chrome/browser/overlays/overlay_request.h
@@ -0,0 +1,62 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_OVERLAYS_OVERLAY_REQUEST_H_
+#define IOS_CHROME_BROWSER_OVERLAYS_OVERLAY_REQUEST_H_
+
+#include <memory>
+
+#include "base/supports_user_data.h"
+
+class OverlayResponse;
+
+// Model object used to track overlays requested for OverlayService.
+class OverlayRequest {
+ public:
+  OverlayRequest() = default;
+  virtual ~OverlayRequest() = default;
+
+  // Creates an OverlayRequest configured with an OverlayUserData of type
+  // ConfigType.  The ConfigType is constructed using the arguments passed to
+  // this function.  For example, if a configuration of type StringConfig has
+  // a constructor that takes a string, a request configured with a StringConfig
+  // can be created using:
+  //
+  // OverlayRequest::CreateWithConfig<StringConfig>("configuration string");
+  template <class ConfigType, typename... Args>
+  static std::unique_ptr<OverlayRequest> CreateWithConfig(Args&&... args) {
+    std::unique_ptr<OverlayRequest> request = OverlayRequest::Create();
+    request->data().SetUserData(
+        ConfigType::UserDataKey(),
+        ConfigType::Create(std::forward<Args>(args)...));
+    return request;
+  }
+
+  // Returns the OverlayUserData of type ConfigType stored in the request's
+  // user data, or nullptr if it is not found. For example, a configuration of
+  // type Config can be retrieved using:
+  //
+  // request->GetConfig<Config>();
+  template <class ConfigType>
+  ConfigType* GetConfig() {
+    return static_cast<ConfigType*>(
+        data().GetUserData(ConfigType::UserDataKey()));
+  }
+
+  // Setter for the response object for this request.
+  virtual void set_response(std::unique_ptr<OverlayResponse> response) = 0;
+  // The response for this request.  It is constructed with an
+  // OverlayResponseInfo containing user interaction information for the overlay
+  // UI resulting from this request.
+  virtual OverlayResponse* response() const = 0;
+
+ private:
+  // Creates an unconfigured OverlayRequest.
+  static std::unique_ptr<OverlayRequest> Create();
+
+  // The container used to hold the user data.
+  virtual base::SupportsUserData& data() = 0;
+};
+
+#endif  // IOS_CHROME_BROWSER_OVERLAYS_OVERLAY_REQUEST_H_
diff --git a/ios/chrome/browser/overlays/overlay_request_impl.cc b/ios/chrome/browser/overlays/overlay_request_impl.cc
new file mode 100644
index 0000000..98c5be8
--- /dev/null
+++ b/ios/chrome/browser/overlays/overlay_request_impl.cc
@@ -0,0 +1,28 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ios/chrome/browser/overlays/overlay_request_impl.h"
+
+#include "ios/chrome/browser/overlays/overlay_response.h"
+
+// static
+std::unique_ptr<OverlayRequest> OverlayRequest::Create() {
+  return std::make_unique<OverlayRequestImpl>();
+}
+
+OverlayRequestImpl::OverlayRequestImpl() {}
+OverlayRequestImpl::~OverlayRequestImpl() {}
+
+base::SupportsUserData& OverlayRequestImpl::data() {
+  return *this;
+}
+
+void OverlayRequestImpl::set_response(
+    std::unique_ptr<OverlayResponse> response) {
+  response_ = std::move(response);
+}
+
+OverlayResponse* OverlayRequestImpl::response() const {
+  return response_.get();
+}
diff --git a/ios/chrome/browser/overlays/overlay_request_impl.h b/ios/chrome/browser/overlays/overlay_request_impl.h
new file mode 100644
index 0000000..5d2c4990
--- /dev/null
+++ b/ios/chrome/browser/overlays/overlay_request_impl.h
@@ -0,0 +1,30 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_OVERLAYS_OVERLAY_REQUEST_IMPL_H_
+#define IOS_CHROME_BROWSER_OVERLAYS_OVERLAY_REQUEST_IMPL_H_
+
+#include <memory>
+
+#include "ios/chrome/browser/overlays/overlay_request.h"
+
+// Internal implementation of OverlayRequest.
+class OverlayRequestImpl : public OverlayRequest,
+                           public base::SupportsUserData {
+ public:
+  OverlayRequestImpl();
+  ~OverlayRequestImpl() override;
+
+ private:
+  // OverlayRequest:
+  void set_response(std::unique_ptr<OverlayResponse> response) override;
+  OverlayResponse* response() const override;
+  base::SupportsUserData& data() override;
+
+  // The response containing the user interaction information for the overlay
+  // resulting from this response.
+  std::unique_ptr<OverlayResponse> response_;
+};
+
+#endif  // IOS_CHROME_BROWSER_OVERLAYS_OVERLAY_REQUEST_IMPL_H_
diff --git a/ios/chrome/browser/overlays/overlay_request_unittest.cc b/ios/chrome/browser/overlays/overlay_request_unittest.cc
new file mode 100644
index 0000000..feee9d6
--- /dev/null
+++ b/ios/chrome/browser/overlays/overlay_request_unittest.cc
@@ -0,0 +1,20 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ios/chrome/browser/overlays/overlay_request.h"
+
+#include "ios/chrome/browser/overlays/test/fake_overlay_user_data.h"
+#include "testing/platform_test.h"
+
+using OverlayRequestTest = PlatformTest;
+
+// Tests that OverlayRequests can be created.
+TEST_F(OverlayRequestTest, CreateWithConfig) {
+  int value = 0;
+  std::unique_ptr<OverlayRequest> request =
+      OverlayRequest::CreateWithConfig<FakeOverlayUserData>(&value);
+  FakeOverlayUserData* config = request->GetConfig<FakeOverlayUserData>();
+  ASSERT_TRUE(config);
+  EXPECT_EQ(config->value(), &value);
+}
diff --git a/ios/chrome/browser/overlays/overlay_response.h b/ios/chrome/browser/overlays/overlay_response.h
new file mode 100644
index 0000000..b302ce7
--- /dev/null
+++ b/ios/chrome/browser/overlays/overlay_response.h
@@ -0,0 +1,51 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_OVERLAYS_OVERLAY_RESPONSE_H_
+#define IOS_CHROME_BROWSER_OVERLAYS_OVERLAY_RESPONSE_H_
+
+#include <memory>
+
+#include "base/supports_user_data.h"
+
+// Model object used to store information about user interaction with overlay
+// UI.
+class OverlayResponse {
+ public:
+  OverlayResponse() = default;
+  virtual ~OverlayResponse() = default;
+
+  // Creates an OverlayResponse with an OverlayUserData of type InfoType.
+  // The InfoType is constructed using the arguments passed to this function.
+  // For example, if an info of type IntInfo has a constructor that takes an
+  // int, a response with an IntInfo can be created using:
+  //
+  // OverlayResponse::CreateWithInfo<IntInfo>(0);
+  template <class InfoType, typename... Args>
+  static std::unique_ptr<OverlayResponse> CreateWithInfo(Args&&... args) {
+    std::unique_ptr<OverlayResponse> response = OverlayResponse::Create();
+    response->data().SetUserData(InfoType::UserDataKey(),
+                                 InfoType::Create(std::forward<Args>(args)...));
+    return response;
+  }
+
+  // Returns the OverlayResponseInfo of type InfoType stored in the reponse's
+  // user data, or nullptr if it is not found.  For example, an info of type
+  // Info can be retrieved using:
+  //
+  // response->GetInfo<Info>();
+  template <class InfoType>
+  InfoType* GetInfo() {
+    return static_cast<InfoType*>(data().GetUserData(InfoType::UserDataKey()));
+  }
+
+ private:
+  // Creates an OverlayResponse with no info attached to it.
+  static std::unique_ptr<OverlayResponse> Create();
+
+  // The container used to hold the user data.
+  virtual base::SupportsUserData& data() = 0;
+};
+
+#endif  // IOS_CHROME_BROWSER_OVERLAYS_OVERLAY_RESPONSE_H_
diff --git a/ios/chrome/browser/overlays/overlay_response_impl.cc b/ios/chrome/browser/overlays/overlay_response_impl.cc
new file mode 100644
index 0000000..c28defe
--- /dev/null
+++ b/ios/chrome/browser/overlays/overlay_response_impl.cc
@@ -0,0 +1,17 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ios/chrome/browser/overlays/overlay_response_impl.h"
+
+// static
+std::unique_ptr<OverlayResponse> OverlayResponse::Create() {
+  return std::make_unique<OverlayResponseImpl>();
+}
+
+OverlayResponseImpl::OverlayResponseImpl() {}
+OverlayResponseImpl::~OverlayResponseImpl() {}
+
+base::SupportsUserData& OverlayResponseImpl::data() {
+  return *this;
+}
diff --git a/ios/chrome/browser/overlays/overlay_response_impl.h b/ios/chrome/browser/overlays/overlay_response_impl.h
new file mode 100644
index 0000000..1a26cdf
--- /dev/null
+++ b/ios/chrome/browser/overlays/overlay_response_impl.h
@@ -0,0 +1,22 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_OVERLAYS_OVERLAY_RESPONSE_IMPL_H_
+#define IOS_CHROME_BROWSER_OVERLAYS_OVERLAY_RESPONSE_IMPL_H_
+
+#include "ios/chrome/browser/overlays/overlay_response.h"
+
+// Internal implementation of OverlayResponse.
+class OverlayResponseImpl : public OverlayResponse,
+                            public base::SupportsUserData {
+ public:
+  OverlayResponseImpl();
+  ~OverlayResponseImpl() override;
+
+ private:
+  // OverlayResponse:
+  base::SupportsUserData& data() override;
+};
+
+#endif  // IOS_CHROME_BROWSER_OVERLAYS_OVERLAY_RESPONSE_IMPL_H_
diff --git a/ios/chrome/browser/overlays/overlay_response_unittest.cc b/ios/chrome/browser/overlays/overlay_response_unittest.cc
new file mode 100644
index 0000000..8bb1e83
--- /dev/null
+++ b/ios/chrome/browser/overlays/overlay_response_unittest.cc
@@ -0,0 +1,20 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ios/chrome/browser/overlays/overlay_response.h"
+
+#include "ios/chrome/browser/overlays/test/fake_overlay_user_data.h"
+#include "testing/platform_test.h"
+
+using OverlayResponseTest = PlatformTest;
+
+// Tests that OverlayResponses can be constructed.
+TEST_F(OverlayResponseTest, CreateWithInfo) {
+  int value = 0;
+  std::unique_ptr<OverlayResponse> request =
+      OverlayResponse::CreateWithInfo<FakeOverlayUserData>(&value);
+  FakeOverlayUserData* info = request->GetInfo<FakeOverlayUserData>();
+  ASSERT_TRUE(info);
+  EXPECT_EQ(info->value(), &value);
+}
diff --git a/ios/chrome/browser/overlays/overlay_user_data.h b/ios/chrome/browser/overlays/overlay_user_data.h
new file mode 100644
index 0000000..418a4fe
--- /dev/null
+++ b/ios/chrome/browser/overlays/overlay_user_data.h
@@ -0,0 +1,61 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_OVERLAYS_OVERLAY_USER_DATA_H_
+#define IOS_CHROME_BROWSER_OVERLAYS_OVERLAY_USER_DATA_H_
+
+#include "base/memory/ptr_util.h"
+#include "base/supports_user_data.h"
+
+// Macro for OverlayUserData setup [add to .h file]:
+// - Declares a static variable inside subclasses.  The address of this static
+//   variable is used as the key to associate the OverlayUserData with its
+//   user data container.
+// - Adds a friend specification for OverlayUserData so it can access
+//   specializations' private constructors and user data keys.
+#define OVERLAY_USER_DATA_SETUP(Type)    \
+  static constexpr int kUserDataKey = 0; \
+  friend class OverlayUserData<Type>
+
+// Macro for OverlayUserData setup implementation [add to .cc/.mm file]:
+// - Instantiates the static variable declared by the previous macro. It must
+//   live in a .cc/.mm file to ensure that there is only one instantiation of
+//   the static variable.
+#define OVERLAY_USER_DATA_SETUP_IMPL(Type) const int Type::kUserDataKey
+
+// A base class for classes attached to, and scoped to, the lifetime of a user
+// data container (e.g. OverlayRequest, OverlayResponse).
+//
+// --- in data.h ---
+// class Data : public OverlayUserData<Data> {
+//  public:
+//   ~Data() override;
+//   // ... more public stuff here ...
+//  private:
+//   OVERLAY_USER_DATA_SETUP(Data);
+//   explicit Data( \* ANY ARGUMENT LIST SUPPORTED *\);
+//   // ... more private stuff here ...
+// };
+//
+// --- in data.cc ---
+// OVERLAY_USER_DATA_SETUP_IMPL(Data);
+template <class DataType>
+class OverlayUserData : public base::SupportsUserData::Data {
+ public:
+  // Creates an OverlayUserData of type DataType.  The DataType instance is
+  // constructed using the arguments passed to this function.  For example, if
+  // the constructor for an OverlayUserData of type StringData takes a string,
+  // one can be created using:
+  //
+  // StringData::Create("string");
+  template <typename... Args>
+  static std::unique_ptr<DataType> Create(Args&&... args) {
+    return base::WrapUnique(new DataType(std::forward<Args>(args)...));
+  }
+
+  // The key under which to store the user data.
+  static const void* UserDataKey() { return &DataType::kUserDataKey; }
+};
+
+#endif  // IOS_CHROME_BROWSER_OVERLAYS_OVERLAY_USER_DATA_H_
diff --git a/ios/chrome/browser/overlays/test/BUILD.gn b/ios/chrome/browser/overlays/test/BUILD.gn
new file mode 100644
index 0000000..851c72a
--- /dev/null
+++ b/ios/chrome/browser/overlays/test/BUILD.gn
@@ -0,0 +1,19 @@
+# Copyright 2019 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.
+
+source_set("test") {
+  testonly = true
+  sources = [
+    "fake_overlay_user_data.cc",
+    "fake_overlay_user_data.h",
+  ]
+
+  configs += [ "//build/config/compiler:enable_arc" ]
+
+  deps = [
+    "//base",
+    "//ios/chrome/browser/overlays",
+    "//testing/gtest",
+  ]
+}
diff --git a/ios/chrome/browser/overlays/test/fake_overlay_user_data.cc b/ios/chrome/browser/overlays/test/fake_overlay_user_data.cc
new file mode 100644
index 0000000..2c833395
--- /dev/null
+++ b/ios/chrome/browser/overlays/test/fake_overlay_user_data.cc
@@ -0,0 +1,9 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ios/chrome/browser/overlays/test/fake_overlay_user_data.h"
+
+OVERLAY_USER_DATA_SETUP_IMPL(FakeOverlayUserData);
+
+FakeOverlayUserData::FakeOverlayUserData(void* value) : value_(value) {}
diff --git a/ios/chrome/browser/overlays/test/fake_overlay_user_data.h b/ios/chrome/browser/overlays/test/fake_overlay_user_data.h
new file mode 100644
index 0000000..f8b5da3
--- /dev/null
+++ b/ios/chrome/browser/overlays/test/fake_overlay_user_data.h
@@ -0,0 +1,24 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_OVERLAYS_TEST_FAKE_OVERLAY_USER_DATA_H_
+#define IOS_CHROME_BROWSER_OVERLAYS_TEST_FAKE_OVERLAY_USER_DATA_H_
+
+#include "ios/chrome/browser/overlays/overlay_user_data.h"
+
+// Test OverlayUserData that can be used to store arbitrary pointers in
+// OverlayRequests and OverlayResponses.
+class FakeOverlayUserData : public OverlayUserData<FakeOverlayUserData> {
+ public:
+  // Accessor for value pointer.
+  void* value() const { return value_; }
+
+ private:
+  OVERLAY_USER_DATA_SETUP(FakeOverlayUserData);
+  FakeOverlayUserData(void* value);
+
+  void* value_ = nullptr;
+};
+
+#endif  // IOS_CHROME_BROWSER_OVERLAYS_TEST_FAKE_OVERLAY_USER_DATA_H_
diff --git a/ios/chrome/browser/translate/legacy_translate_infobar_egtest.mm b/ios/chrome/browser/translate/legacy_translate_infobar_egtest.mm
index 24a03bf3..f17e748 100644
--- a/ios/chrome/browser/translate/legacy_translate_infobar_egtest.mm
+++ b/ios/chrome/browser/translate/legacy_translate_infobar_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.
 
+#import <EarlGrey/EarlGrey.h>
 #import <XCTest/XCTest.h>
 
 #include <memory>
diff --git a/ios/chrome/browser/translate/translate_egtest.mm b/ios/chrome/browser/translate/translate_egtest.mm
index a046993..8bdb71c9 100644
--- a/ios/chrome/browser/translate/translate_egtest.mm
+++ b/ios/chrome/browser/translate/translate_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.
 
+#import <EarlGrey/EarlGrey.h>
 #import <XCTest/XCTest.h>
 
 #include <memory>
diff --git a/ios/chrome/browser/ui/activity_services/activity_service_controller_egtest.mm b/ios/chrome/browser/ui/activity_services/activity_service_controller_egtest.mm
index 8d53563..2cb9054 100644
--- a/ios/chrome/browser/ui/activity_services/activity_service_controller_egtest.mm
+++ b/ios/chrome/browser/ui/activity_services/activity_service_controller_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.
 
+#import <EarlGrey/EarlGrey.h>
 #import <XCTest/XCTest.h>
 
 #include <memory>
diff --git a/ios/chrome/browser/ui/autofill/save_card_infobar_egtest.mm b/ios/chrome/browser/ui/autofill/save_card_infobar_egtest.mm
index e8700bb6..67d7ab91 100644
--- a/ios/chrome/browser/ui/autofill/save_card_infobar_egtest.mm
+++ b/ios/chrome/browser/ui/autofill/save_card_infobar_egtest.mm
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#import <EarlGrey/EarlGrey.h>
+
 #include <memory>
 
 #include "base/logging.h"
diff --git a/ios/chrome/browser/ui/autofill/save_profile_egtest.mm b/ios/chrome/browser/ui/autofill/save_profile_egtest.mm
index f39bada..bae084c 100644
--- a/ios/chrome/browser/ui/autofill/save_profile_egtest.mm
+++ b/ios/chrome/browser/ui/autofill/save_profile_egtest.mm
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#import <EarlGrey/EarlGrey.h>
+
 #include <memory>
 
 #include "base/logging.h"
diff --git a/ios/chrome/browser/ui/download/download_manager_egtest.mm b/ios/chrome/browser/ui/download/download_manager_egtest.mm
index 17fc9c3..a12f702 100644
--- a/ios/chrome/browser/ui/download/download_manager_egtest.mm
+++ b/ios/chrome/browser/ui/download/download_manager_egtest.mm
@@ -19,6 +19,7 @@
 #import "ios/chrome/test/scoped_eg_synchronization_disabler.h"
 #include "ios/testing/embedded_test_server_handlers.h"
 #import "ios/web/public/test/earl_grey/web_view_matchers.h"
+#include "ios/web/public/test/element_selector.h"
 #include "net/test/embedded_test_server/http_request.h"
 #include "net/test/embedded_test_server/http_response.h"
 #include "net/test/embedded_test_server/request_handler_util.h"
diff --git a/ios/chrome/browser/ui/find_bar/find_in_page_egtest.mm b/ios/chrome/browser/ui/find_bar/find_in_page_egtest.mm
index b504966..02696a7 100644
--- a/ios/chrome/browser/ui/find_bar/find_in_page_egtest.mm
+++ b/ios/chrome/browser/ui/find_bar/find_in_page_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.
 
+#import <EarlGrey/EarlGrey.h>
 #import <XCTest/XCTest.h>
 
 #include "base/strings/string_number_conversions.h"
diff --git a/ios/chrome/browser/ui/infobars/banners/infobar_banner_view_controller.mm b/ios/chrome/browser/ui/infobars/banners/infobar_banner_view_controller.mm
index 0b99d19..ef801d04 100644
--- a/ios/chrome/browser/ui/infobars/banners/infobar_banner_view_controller.mm
+++ b/ios/chrome/browser/ui/infobars/banners/infobar_banner_view_controller.mm
@@ -37,6 +37,7 @@
 
 // Container Stack constants.
 const CGFloat kContainerStackSpacing = 18.0;
+const CGFloat kContainerStackVerticalPadding = 18.0;
 
 // Icon constants.
 const CGFloat kIconWidth = 25.0;
@@ -126,10 +127,6 @@
   UIStackView* labelsStackView = [[UIStackView alloc]
       initWithArrangedSubviews:@[ self.titleLabel, self.subTitleLabel ]];
   labelsStackView.axis = UILayoutConstraintAxisVertical;
-  labelsStackView.alignment = UIStackViewAlignmentLeading;
-  labelsStackView.distribution = UIStackViewDistributionEqualCentering;
-  [labelsStackView setContentHuggingPriority:UILayoutPriorityRequired
-                                     forAxis:UILayoutConstraintAxisVertical];
 
   // Button setup.
   self.infobarButton = [UIButton buttonWithType:UIButtonTypeSystem];
@@ -156,6 +153,9 @@
   containerStack.distribution = UIStackViewDistributionFill;
   containerStack.alignment = UIStackViewAlignmentFill;
   containerStack.translatesAutoresizingMaskIntoConstraints = NO;
+  containerStack.layoutMarginsRelativeArrangement = YES;
+  containerStack.directionalLayoutMargins = NSDirectionalEdgeInsetsMake(
+      kContainerStackVerticalPadding, 0, kContainerStackVerticalPadding, 0);
   [self.view addSubview:containerStack];
 
   // Constraints setup.
@@ -166,8 +166,9 @@
                        constant:kContainerStackSpacing],
     [containerStack.trailingAnchor
         constraintEqualToAnchor:self.view.trailingAnchor],
-    [containerStack.centerYAnchor
-        constraintEqualToAnchor:self.view.centerYAnchor],
+    [containerStack.topAnchor constraintEqualToAnchor:self.view.topAnchor],
+    [containerStack.bottomAnchor
+        constraintEqualToAnchor:self.view.bottomAnchor],
     // Icon.
     [iconImageView.widthAnchor constraintEqualToConstant:kIconWidth],
     // Button.
diff --git a/ios/chrome/browser/ui/infobars/coordinators/infobar_coordinator.mm b/ios/chrome/browser/ui/infobars/coordinators/infobar_coordinator.mm
index 4b81840c..d5ff2f1 100644
--- a/ios/chrome/browser/ui/infobars/coordinators/infobar_coordinator.mm
+++ b/ios/chrome/browser/ui/infobars/coordinators/infobar_coordinator.mm
@@ -174,6 +174,10 @@
          kBannerOverlapWithOmnibox;
 }
 
+- (UIView*)bannerView {
+  return self.bannerViewController.view;
+}
+
 #pragma mark InfobarModalDelegate
 
 - (void)modalInfobarButtonWasPressed:(UIButton*)sender {
diff --git a/ios/chrome/browser/ui/infobars/presentation/infobar_banner_positioner.h b/ios/chrome/browser/ui/infobars/presentation/infobar_banner_positioner.h
index 1f9f0d7..8fdeec2 100644
--- a/ios/chrome/browser/ui/infobars/presentation/infobar_banner_positioner.h
+++ b/ios/chrome/browser/ui/infobars/presentation/infobar_banner_positioner.h
@@ -13,6 +13,11 @@
 // Y axis value used to position the InfobarBanner.
 - (CGFloat)bannerYPosition;
 
+// The InfobarBanner view that will be presented. Used to calculate the
+// intrinsic size of the content in order to set the container view height
+// appropriately.
+- (UIView*)bannerView;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_INFOBARS_PRESENTATION_INFOBAR_BANNER_POSITIONER_H_
diff --git a/ios/chrome/browser/ui/infobars/presentation/infobar_banner_presentation_controller.mm b/ios/chrome/browser/ui/infobars/presentation/infobar_banner_presentation_controller.mm
index d304bde..a13e2202 100644
--- a/ios/chrome/browser/ui/infobars/presentation/infobar_banner_presentation_controller.mm
+++ b/ios/chrome/browser/ui/infobars/presentation/infobar_banner_presentation_controller.mm
@@ -4,6 +4,7 @@
 
 #import "ios/chrome/browser/ui/infobars/presentation/infobar_banner_presentation_controller.h"
 
+#include "base/logging.h"
 #import "ios/chrome/browser/ui/infobars/presentation/infobar_banner_positioner.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -11,14 +12,12 @@
 #endif
 
 namespace {
-// The presented view height.
-const CGFloat kDefaultContainerHeight = 70;
 // The presented view outer horizontal margins.
 const CGFloat kContainerHorizontalPadding = 8;
 // The presented view maximum width.
-const CGFloat kContainerWidthMaxSize = 398;
-// The presented view default Y axis value.
-const CGFloat kDefaultBannerYPosition = 85;
+const CGFloat kContainerMaxWidth = 398;
+// The presented view maximum height.
+const CGFloat kContainerMaxHeight = 200;
 }
 
 @implementation InfobarBannerPresentationController
@@ -33,25 +32,32 @@
 }
 
 - (UIView*)viewForPresentedView {
+  DCHECK(self.bannerPositioner);
   UIWindow* window = UIApplication.sharedApplication.keyWindow;
 
   // Calculate the Banner container width.
   CGFloat safeAreaWidth = CGRectGetWidth(window.bounds);
   CGFloat maxAvailableWidth = safeAreaWidth - 2 * kContainerHorizontalPadding;
-  CGFloat frameWidth = fmin(maxAvailableWidth, kContainerWidthMaxSize);
+  CGFloat frameWidth = fmin(maxAvailableWidth, kContainerMaxWidth);
 
   // Based on the container width, calculate the value in order to center the
   // Banner in the X axis.
   CGFloat bannerXPosition = (safeAreaWidth / 2) - (frameWidth / 2);
-
   CGFloat bannerYPosition = [self.bannerPositioner bannerYPosition];
-  if (!bannerYPosition) {
-    bannerYPosition = kDefaultBannerYPosition;
-  }
 
-  return [[UIView alloc]
-      initWithFrame:CGRectMake(bannerXPosition, bannerYPosition, frameWidth,
-                               kDefaultContainerHeight)];
+  // Calculate the Banner height needed to fit its content with frameWidth.
+  UIView* bannerView = [self.bannerPositioner bannerView];
+  [bannerView setNeedsLayout];
+  [bannerView layoutIfNeeded];
+  CGSize frameThatFits =
+      [bannerView systemLayoutSizeFittingSize:CGSizeMake(frameWidth, 0)
+                withHorizontalFittingPriority:UILayoutPriorityRequired
+                      verticalFittingPriority:1];
+  CGFloat frameHeight = fmin(kContainerMaxHeight, frameThatFits.height);
+
+  return
+      [[UIView alloc] initWithFrame:CGRectMake(bannerXPosition, bannerYPosition,
+                                               frameWidth, frameHeight)];
 }
 
 @end
diff --git a/ios/chrome/browser/ui/omnibox/web_omnibox_edit_controller_impl.mm b/ios/chrome/browser/ui/omnibox/web_omnibox_edit_controller_impl.mm
index 5f4068f..5f08422 100644
--- a/ios/chrome/browser/ui/omnibox/web_omnibox_edit_controller_impl.mm
+++ b/ios/chrome/browser/ui/omnibox/web_omnibox_edit_controller_impl.mm
@@ -53,8 +53,6 @@
 }
 
 void WebOmniboxEditControllerImpl::OnInputInProgress(bool in_progress) {
-  if ([delegate_ locationBarModel])
-    [delegate_ locationBarModel]->set_input_in_progress(in_progress);
   // TODO(crbug.com/818649): see if this is really used.
   if (in_progress)
     [delegate_ locationBarBeganEdit];
diff --git a/ios/chrome/browser/ui/payments/payment_request_accessibility_egtest.mm b/ios/chrome/browser/ui/payments/payment_request_accessibility_egtest.mm
index e49dc959..430692b3 100644
--- a/ios/chrome/browser/ui/payments/payment_request_accessibility_egtest.mm
+++ b/ios/chrome/browser/ui/payments/payment_request_accessibility_egtest.mm
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#import <EarlGrey/EarlGrey.h>
+
 #include "base/strings/sys_string_conversions.h"
 #include "components/autofill/core/browser/autofill_profile.h"
 #include "components/autofill/core/browser/autofill_test_utils.h"
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 932a9b1..d8346ac5 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,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#import <EarlGrey/EarlGrey.h>
+
 #include "base/ios/ios_util.h"
 #include "components/autofill/core/browser/autofill_test_utils.h"
 #include "components/autofill/core/browser/credit_card.h"
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 c0c6ad37..911e6f2 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
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#import <EarlGrey/EarlGrey.h>
+
 #include <vector>
 
 #include "base/ios/ios_util.h"
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 559142f..5c80300 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,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#import <EarlGrey/EarlGrey.h>
+
 #import "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"
diff --git a/ios/chrome/browser/ui/payments/payment_request_debit_egtest.mm b/ios/chrome/browser/ui/payments/payment_request_debit_egtest.mm
index 2ac8772..9a8e1cb 100644
--- a/ios/chrome/browser/ui/payments/payment_request_debit_egtest.mm
+++ b/ios/chrome/browser/ui/payments/payment_request_debit_egtest.mm
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#import <EarlGrey/EarlGrey.h>
+
 #include <memory>
 #include <vector>
 
diff --git a/ios/chrome/browser/ui/payments/payment_request_egtest_base.mm b/ios/chrome/browser/ui/payments/payment_request_egtest_base.mm
index 88ad71a..e4e008e 100644
--- a/ios/chrome/browser/ui/payments/payment_request_egtest_base.mm
+++ b/ios/chrome/browser/ui/payments/payment_request_egtest_base.mm
@@ -4,6 +4,8 @@
 
 #import "ios/chrome/browser/ui/payments/payment_request_egtest_base.h"
 
+#import <EarlGrey/EarlGrey.h>
+
 #include <algorithm>
 #include <memory>
 
diff --git a/ios/chrome/browser/ui/payments/payment_request_journey_logger_egtest.mm b/ios/chrome/browser/ui/payments/payment_request_journey_logger_egtest.mm
index f74c22a..cb924445 100644
--- a/ios/chrome/browser/ui/payments/payment_request_journey_logger_egtest.mm
+++ b/ios/chrome/browser/ui/payments/payment_request_journey_logger_egtest.mm
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#import <EarlGrey/EarlGrey.h>
+
 #include "base/ios/ios_util.h"
 #include "components/autofill/core/browser/autofill_profile.h"
 #include "components/autofill/core/browser/autofill_test_utils.h"
diff --git a/ios/chrome/browser/ui/payments/payment_request_misc_egtest.mm b/ios/chrome/browser/ui/payments/payment_request_misc_egtest.mm
index 3279a41..64efcca 100644
--- a/ios/chrome/browser/ui/payments/payment_request_misc_egtest.mm
+++ b/ios/chrome/browser/ui/payments/payment_request_misc_egtest.mm
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#import <EarlGrey/EarlGrey.h>
+
 #import "ios/chrome/browser/payments/payment_request_cache.h"
 #import "ios/chrome/browser/ui/payments/payment_request_egtest_base.h"
 #import "ios/chrome/test/app/chrome_test_util.h"
diff --git a/ios/chrome/browser/ui/payments/payment_request_modifiers_egtest.mm b/ios/chrome/browser/ui/payments/payment_request_modifiers_egtest.mm
index 88045a6..3147c5a 100644
--- a/ios/chrome/browser/ui/payments/payment_request_modifiers_egtest.mm
+++ b/ios/chrome/browser/ui/payments/payment_request_modifiers_egtest.mm
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#import <EarlGrey/EarlGrey.h>
+
 #include "base/strings/sys_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
 #include "components/autofill/core/browser/autofill_test_utils.h"
diff --git a/ios/chrome/browser/ui/payments/payment_request_payment_app_egtest.mm b/ios/chrome/browser/ui/payments/payment_request_payment_app_egtest.mm
index 917dff1b..61601c0 100644
--- a/ios/chrome/browser/ui/payments/payment_request_payment_app_egtest.mm
+++ b/ios/chrome/browser/ui/payments/payment_request_payment_app_egtest.mm
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#import <EarlGrey/EarlGrey.h>
+
 #include "base/ios/ios_util.h"
 #import "ios/chrome/browser/ui/payments/payment_request_egtest_base.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
diff --git a/ios/chrome/browser/ui/payments/payment_request_payment_method_identifier_egtest.mm b/ios/chrome/browser/ui/payments/payment_request_payment_method_identifier_egtest.mm
index c3d1b922..da02630 100644
--- a/ios/chrome/browser/ui/payments/payment_request_payment_method_identifier_egtest.mm
+++ b/ios/chrome/browser/ui/payments/payment_request_payment_method_identifier_egtest.mm
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#import <EarlGrey/EarlGrey.h>
+
 #include <vector>
 
 #import "ios/chrome/browser/payments/payment_request_cache.h"
diff --git a/ios/chrome/browser/ui/payments/payment_request_payment_response_egtest.mm b/ios/chrome/browser/ui/payments/payment_request_payment_response_egtest.mm
index 0151ef6..b0e02df1 100644
--- a/ios/chrome/browser/ui/payments/payment_request_payment_response_egtest.mm
+++ b/ios/chrome/browser/ui/payments/payment_request_payment_response_egtest.mm
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#import <EarlGrey/EarlGrey.h>
+
 #include <vector>
 
 #include "base/strings/utf_string_conversions.h"
diff --git a/ios/chrome/browser/ui/payments/payment_request_show_egtest.mm b/ios/chrome/browser/ui/payments/payment_request_show_egtest.mm
index 5515b147..5db5641 100644
--- a/ios/chrome/browser/ui/payments/payment_request_show_egtest.mm
+++ b/ios/chrome/browser/ui/payments/payment_request_show_egtest.mm
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#import <EarlGrey/EarlGrey.h>
+
 #import "base/test/ios/wait_util.h"
 #include "components/autofill/core/browser/autofill_test_utils.h"
 #include "components/strings/grit/components_strings.h"
diff --git a/ios/chrome/browser/ui/payments/payment_request_use_stats_egtest.mm b/ios/chrome/browser/ui/payments/payment_request_use_stats_egtest.mm
index ff0e9105..bf8b07a 100644
--- a/ios/chrome/browser/ui/payments/payment_request_use_stats_egtest.mm
+++ b/ios/chrome/browser/ui/payments/payment_request_use_stats_egtest.mm
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#import <EarlGrey/EarlGrey.h>
+
 #include "base/time/time.h"
 #include "components/autofill/core/browser/autofill_profile.h"
 #include "components/autofill/core/browser/autofill_test_utils.h"
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_egtest.mm b/ios/chrome/browser/ui/popup_menu/popup_menu_egtest.mm
index a8c21d5..5613455 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_egtest.mm
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_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.
 
+#import <EarlGrey/EarlGrey.h>
 #import <XCTest/XCTest.h>
 
 #include "base/strings/sys_string_conversions.h"
diff --git a/ios/chrome/browser/ui/settings/autofill/autofill_credit_card_settings_egtest.mm b/ios/chrome/browser/ui/settings/autofill/autofill_credit_card_settings_egtest.mm
index b8ea908..f83a0c0 100644
--- a/ios/chrome/browser/ui/settings/autofill/autofill_credit_card_settings_egtest.mm
+++ b/ios/chrome/browser/ui/settings/autofill/autofill_credit_card_settings_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.
 
+#import <EarlGrey/EarlGrey.h>
 #import <XCTest/XCTest.h>
 
 #include "base/strings/sys_string_conversions.h"
diff --git a/ios/chrome/browser/ui/settings/autofill/autofill_profile_settings_egtest.mm b/ios/chrome/browser/ui/settings/autofill/autofill_profile_settings_egtest.mm
index be033bc3..6a3fa0f 100644
--- a/ios/chrome/browser/ui/settings/autofill/autofill_profile_settings_egtest.mm
+++ b/ios/chrome/browser/ui/settings/autofill/autofill_profile_settings_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.
 
+#import <EarlGrey/EarlGrey.h>
 #import <XCTest/XCTest.h>
 
 #include "base/ios/ios_util.h"
diff --git a/ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_egtest.mm b/ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_egtest.mm
index b254f61..1525abe 100644
--- a/ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_egtest.mm
+++ b/ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_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.
 
+#import <EarlGrey/EarlGrey.h>
 #import <XCTest/XCTest.h>
 
 #include "base/test/scoped_feature_list.h"
diff --git a/ios/chrome/browser/ui/settings/password/passwords_settings_egtest.mm b/ios/chrome/browser/ui/settings/password/passwords_settings_egtest.mm
index 4376744..5649674 100644
--- a/ios/chrome/browser/ui/settings/password/passwords_settings_egtest.mm
+++ b/ios/chrome/browser/ui/settings/password/passwords_settings_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.
 
+#import <EarlGrey/EarlGrey.h>
 #include <TargetConditionals.h>
 
 #include <utility>
diff --git a/ios/chrome/browser/ui/tab_grid/tab_grid_egtest.mm b/ios/chrome/browser/ui/tab_grid/tab_grid_egtest.mm
index 0561c2e..0bd2c17 100644
--- a/ios/chrome/browser/ui/tab_grid/tab_grid_egtest.mm
+++ b/ios/chrome/browser/ui/tab_grid/tab_grid_egtest.mm
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#import <EarlGrey/EarlGrey.h>
+
 #include "base/strings/stringprintf.h"
 #import "ios/chrome/browser/ui/tab_grid/tab_grid_constants.h"
 #import "ios/chrome/browser/ui/tab_grid/tab_grid_egtest_util.h"
diff --git a/ios/chrome/browser/ui/webui/inspect/inspect_ui_egtest.mm b/ios/chrome/browser/ui/webui/inspect/inspect_ui_egtest.mm
index 2dc87219..61c69d1 100644
--- a/ios/chrome/browser/ui/webui/inspect/inspect_ui_egtest.mm
+++ b/ios/chrome/browser/ui/webui/inspect/inspect_ui_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.
 
+#import <EarlGrey/EarlGrey.h>
 #import <Foundation/Foundation.h>
 #import <XCTest/XCTest.h>
 
diff --git a/ios/chrome/browser/ui/webui/web_ui_egtest.mm b/ios/chrome/browser/ui/webui/web_ui_egtest.mm
index 0924539..ca3f7d2 100644
--- a/ios/chrome/browser/ui/webui/web_ui_egtest.mm
+++ b/ios/chrome/browser/ui/webui/web_ui_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.
 
+#import <EarlGrey/EarlGrey.h>
 #import <XCTest/XCTest.h>
 
 #include "base/mac/foundation_util.h"
diff --git a/ios/chrome/browser/web/browsing_egtest.mm b/ios/chrome/browser/web/browsing_egtest.mm
index 9aed63b..0ca5dc8 100644
--- a/ios/chrome/browser/web/browsing_egtest.mm
+++ b/ios/chrome/browser/web/browsing_egtest.mm
@@ -2,7 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#import <EarlGrey/EarlGrey.h>
 #import <XCTest/XCTest.h>
+
 #include <map>
 #include <memory>
 #include <string>
diff --git a/ios/chrome/browser/web/forms_egtest.mm b/ios/chrome/browser/web/forms_egtest.mm
index 258e66ec..019dad58 100644
--- a/ios/chrome/browser/web/forms_egtest.mm
+++ b/ios/chrome/browser/web/forms_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.
 
+#import <EarlGrey/EarlGrey.h>
 #import <XCTest/XCTest.h>
 
 #include <memory>
diff --git a/ios/chrome/browser/web/navigation_egtest.mm b/ios/chrome/browser/web/navigation_egtest.mm
index 1714233..2d828d7 100644
--- a/ios/chrome/browser/web/navigation_egtest.mm
+++ b/ios/chrome/browser/web/navigation_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.
 
+#import <EarlGrey/EarlGrey.h>
 #import <XCTest/XCTest.h>
 
 #include "base/bind.h"
diff --git a/ios/chrome/browser/web/push_and_replace_state_navigation_egtest.mm b/ios/chrome/browser/web/push_and_replace_state_navigation_egtest.mm
index d26c02b..d66d606 100644
--- a/ios/chrome/browser/web/push_and_replace_state_navigation_egtest.mm
+++ b/ios/chrome/browser/web/push_and_replace_state_navigation_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.
 
+#import <EarlGrey/EarlGrey.h>
 #import <XCTest/XCTest.h>
 
 #include "base/strings/sys_string_conversions.h"
diff --git a/ios/chrome/browser/web/tab_order_egtest.mm b/ios/chrome/browser/web/tab_order_egtest.mm
index e90ff88..e75aa3c 100644
--- a/ios/chrome/browser/web/tab_order_egtest.mm
+++ b/ios/chrome/browser/web/tab_order_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.
 
+#import <EarlGrey/EarlGrey.h>
 #import <XCTest/XCTest.h>
 
 #import "ios/chrome/test/app/chrome_test_util.h"
@@ -11,6 +12,7 @@
 #import "ios/chrome/test/earl_grey/chrome_matchers.h"
 #import "ios/chrome/test/earl_grey/chrome_test_case.h"
 #import "ios/web/public/test/earl_grey/web_view_matchers.h"
+#import "ios/web/public/test/element_selector.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
diff --git a/ios/chrome/test/BUILD.gn b/ios/chrome/test/BUILD.gn
index 502e223..8b3c4c03 100644
--- a/ios/chrome/test/BUILD.gn
+++ b/ios/chrome/test/BUILD.gn
@@ -160,6 +160,7 @@
     "//ios/chrome/browser/net:unit_tests",
     "//ios/chrome/browser/ntp:unit_tests",
     "//ios/chrome/browser/omaha:unit_tests",
+    "//ios/chrome/browser/overlays:unit_tests",
     "//ios/chrome/browser/overscroll_actions:unit_tests",
     "//ios/chrome/browser/passwords:unit_tests",
     "//ios/chrome/browser/payments:unit_tests",
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey.h b/ios/chrome/test/earl_grey/chrome_earl_grey.h
index 49b2658e..954db9f0 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey.h
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey.h
@@ -5,11 +5,15 @@
 #ifndef IOS_CHROME_TEST_EARL_GREY_CHROME_EARL_GREY_H_
 #define IOS_CHROME_TEST_EARL_GREY_CHROME_EARL_GREY_H_
 
-#import <EarlGrey/EarlGrey.h>
+#import <Foundation/Foundation.h>
 
-#include "ios/web/public/test/element_selector.h"
+#include <string>
+
 #include "url/gurl.h"
 
+@class ElementSelector;
+@protocol GREYMatcher;
+
 namespace chrome_test_util {
 
 // TODO(crbug.com/788813): Evaluate if JS helpers can be consolidated.
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey.mm b/ios/chrome/test/earl_grey/chrome_earl_grey.mm
index 567629c..93e4111 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey.mm
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey.mm
@@ -4,6 +4,7 @@
 
 #import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
 
+#import <EarlGrey/EarlGrey.h>
 #import <Foundation/Foundation.h>
 #import <WebKit/WebKit.h>
 
@@ -19,7 +20,6 @@
 #include "ios/chrome/test/app/navigation_test_util.h"
 #import "ios/chrome/test/app/static_html_view_test_util.h"
 #import "ios/chrome/test/app/tab_test_util.h"
-#import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
 #import "ios/web/public/test/earl_grey/js_test_util.h"
 #include "ios/web/public/test/element_selector.h"
 #import "ios/web/public/test/web_view_content_test_util.h"
diff --git a/ios/chrome/test/earl_grey/device_check_egtest.mm b/ios/chrome/test/earl_grey/device_check_egtest.mm
index e9f596f..2b01a6a 100644
--- a/ios/chrome/test/earl_grey/device_check_egtest.mm
+++ b/ios/chrome/test/earl_grey/device_check_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.
 
+#import <EarlGrey/EarlGrey.h>
 #import <XCTest/XCTest.h>
 
 #import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
diff --git a/ios/showcase/infobars/sc_infobar_banner_coordinator.mm b/ios/showcase/infobars/sc_infobar_banner_coordinator.mm
index 171f8aa..b5d6a98 100644
--- a/ios/showcase/infobars/sc_infobar_banner_coordinator.mm
+++ b/ios/showcase/infobars/sc_infobar_banner_coordinator.mm
@@ -8,6 +8,7 @@
 #import "ios/chrome/browser/ui/infobars/banners/infobar_banner_view_controller.h"
 #import "ios/chrome/browser/ui/infobars/modals/infobar_modal_delegate.h"
 #import "ios/chrome/browser/ui/infobars/modals/infobar_modal_view_controller.h"
+#import "ios/chrome/browser/ui/infobars/presentation/infobar_banner_positioner.h"
 #import "ios/chrome/browser/ui/infobars/presentation/infobar_banner_transition_driver.h"
 #import "ios/chrome/browser/ui/infobars/presentation/infobar_modal_transition_driver.h"
 
@@ -20,7 +21,9 @@
 NSString* const kInfobarBannerButtonLabel = @"Accept";
 NSString* const kInfobarBannerPresentedModalLabel = @"Modal Infobar";
 
-@interface ContainerViewController : UIViewController
+#pragma mark - ContainerViewController
+
+@interface ContainerViewController : UIViewController <InfobarBannerPositioner>
 @property(nonatomic, strong) InfobarBannerViewController* bannerViewController;
 @property(nonatomic, strong)
     InfobarBannerTransitionDriver* bannerTransitionDriver;
@@ -32,13 +35,26 @@
   [self.bannerViewController
       setModalPresentationStyle:UIModalPresentationCustom];
   self.bannerTransitionDriver = [[InfobarBannerTransitionDriver alloc] init];
+  self.bannerTransitionDriver.bannerPositioner = self;
   self.bannerViewController.transitioningDelegate = self.bannerTransitionDriver;
   [self presentViewController:self.bannerViewController
                      animated:YES
                    completion:nil];
 }
+
+#pragma mark InfobarBannerPositioner
+
+- (CGFloat)bannerYPosition {
+  return 100;
+}
+
+- (UIView*)bannerView {
+  return self.bannerViewController.view;
+}
 @end
 
+#pragma mark - SCInfobarBannerCoordinator
+
 @interface SCInfobarBannerCoordinator () <InfobarBannerDelegate,
                                           InfobarModalDelegate>
 @property(nonatomic, strong) InfobarBannerViewController* bannerViewController;
@@ -68,6 +84,10 @@
                                      animated:YES];
 }
 
+- (void)dealloc {
+  [self dismissInfobarBanner:nil animated:YES completion:nil];
+}
+
 #pragma mark InfobarBannerDelegate
 
 - (void)bannerInfobarButtonWasPressed:(id)sender {
diff --git a/ios/web/test/web_int_test.mm b/ios/web/test/web_int_test.mm
index 727dad95..62dc6c6a 100644
--- a/ios/web/test/web_int_test.mm
+++ b/ios/web/test/web_int_test.mm
@@ -77,6 +77,7 @@
       [UIApplication sharedApplication].keyWindow.bounds;
 
   web_state()->SetDelegate(&web_state_delegate_);
+  web_state()->SetKeepRenderProcessAlive(true);
 }
 
 void WebIntTest::TearDown() {
diff --git a/ios/web/web_state/web_state_observer_inttest.mm b/ios/web/web_state/web_state_observer_inttest.mm
index 090709e..3b0420c 100644
--- a/ios/web/web_state/web_state_observer_inttest.mm
+++ b/ios/web/web_state/web_state_observer_inttest.mm
@@ -1901,13 +1901,6 @@
       .WillOnce(Return(true));
 
   EXPECT_CALL(observer_, DidStartNavigation(web_state(), _));
-  if (@available(iOS 12.2, *)) {
-    // ShouldAllowResponse is called on iOS 12.0 and iOS 12.1,
-    // but not on iOS 12.2 or iOS 11.
-  } else if (@available(iOS 12, *)) {
-    EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
-        .WillOnce(Return(true));
-  }
   EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _));
   EXPECT_CALL(observer_, TitleWasSet(web_state()))
       .WillOnce(VerifyTitle(url.GetContent()));
@@ -2443,6 +2436,8 @@
   CRWSessionStorage* session_storage = [[CRWSessionStorage alloc] init];
   session_storage.itemStorages = item_storages;
   auto web_state = WebState::CreateWithStorageSession(params, session_storage);
+  web_state->SetKeepRenderProcessAlive(true);
+
   StrictMock<WebStateObserverMock> observer;
   ScopedObserver<WebState, WebStateObserver> scoped_observer(&observer);
   scoped_observer.Add(web_state.get());
diff --git a/ios/web/web_state/web_state_unittest.mm b/ios/web/web_state/web_state_unittest.mm
index 97ebd47..a243eebc 100644
--- a/ios/web/web_state/web_state_unittest.mm
+++ b/ios/web/web_state/web_state_unittest.mm
@@ -449,6 +449,7 @@
   CRWSessionStorage* session_storage = [[CRWSessionStorage alloc] init];
   session_storage.itemStorages = item_storages;
   auto web_state = WebState::CreateWithStorageSession(params, session_storage);
+  web_state->SetKeepRenderProcessAlive(true);
   WebState* web_state_ptr = web_state.get();
   NavigationManager* navigation_manager = web_state->GetNavigationManager();
   // TODO(crbug.com/873729): The session will not be restored until
@@ -492,6 +493,7 @@
   CRWSessionStorage* session_storage = [[CRWSessionStorage alloc] init];
   session_storage.itemStorages = item_storages;
   auto web_state = WebState::CreateWithStorageSession(params, session_storage);
+  web_state->SetKeepRenderProcessAlive(true);
   WebState* web_state_ptr = web_state.get();
   NavigationManager* navigation_manager = web_state->GetNavigationManager();
   // TODO(crbug.com/873729): The session will not be restored until
@@ -542,6 +544,7 @@
   CRWSessionStorage* session_storage = [[CRWSessionStorage alloc] init];
   session_storage.itemStorages = item_storages;
   auto web_state = WebState::CreateWithStorageSession(params, session_storage);
+  web_state->SetKeepRenderProcessAlive(true);
   WebState* web_state_ptr = web_state.get();
   NavigationManager* navigation_manager = web_state->GetNavigationManager();
   // TODO(crbug.com/873729): The session will not be restored until
diff --git a/ios/web_view/shell/test/earl_grey/web_view_shell_matchers.h b/ios/web_view/shell/test/earl_grey/web_view_shell_matchers.h
index 35598eb..fa36b91 100644
--- a/ios/web_view/shell/test/earl_grey/web_view_shell_matchers.h
+++ b/ios/web_view/shell/test/earl_grey/web_view_shell_matchers.h
@@ -5,9 +5,11 @@
 #ifndef IOS_WEB_VIEW_SHELL_TEST_EARL_GREY_WEB_VIEW_SHELL_MATCHERS_H_
 #define IOS_WEB_VIEW_SHELL_TEST_EARL_GREY_WEB_VIEW_SHELL_MATCHERS_H_
 
+#import <Foundation/Foundation.h>
+
 #import <string>
 
-#import <EarlGrey/EarlGrey.h>
+@protocol GREYMatcher;
 
 NS_ASSUME_NONNULL_BEGIN
 
diff --git a/ios/web_view/shell/test/earl_grey/web_view_shell_matchers.mm b/ios/web_view/shell/test/earl_grey/web_view_shell_matchers.mm
index 2dc8a79..5c3c6ff 100644
--- a/ios/web_view/shell/test/earl_grey/web_view_shell_matchers.mm
+++ b/ios/web_view/shell/test/earl_grey/web_view_shell_matchers.mm
@@ -4,6 +4,8 @@
 
 #import "ios/web_view/shell/test/earl_grey/web_view_shell_matchers.h"
 
+#import <EarlGrey/EarlGrey.h>
+
 #include "base/mac/foundation_util.h"
 #include "base/strings/sys_string_conversions.h"
 #import "base/test/ios/wait_util.h"
diff --git a/media/blink/webcontentdecryptionmoduleaccess_impl.cc b/media/blink/webcontentdecryptionmoduleaccess_impl.cc
index fdc2b25..4e4a7a6 100644
--- a/media/blink/webcontentdecryptionmoduleaccess_impl.cc
+++ b/media/blink/webcontentdecryptionmoduleaccess_impl.cc
@@ -34,14 +34,14 @@
   client->CreateCdm(key_system, security_origin, cdm_config, std::move(result));
 }
 
-WebContentDecryptionModuleAccessImpl*
+std::unique_ptr<WebContentDecryptionModuleAccessImpl>
 WebContentDecryptionModuleAccessImpl::Create(
     const blink::WebString& key_system,
     const blink::WebSecurityOrigin& security_origin,
     const blink::WebMediaKeySystemConfiguration& configuration,
     const CdmConfig& cdm_config,
     const base::WeakPtr<WebEncryptedMediaClientImpl>& client) {
-  return new WebContentDecryptionModuleAccessImpl(
+  return std::make_unique<WebContentDecryptionModuleAccessImpl>(
       key_system, security_origin, configuration, cdm_config, client);
 }
 
diff --git a/media/blink/webcontentdecryptionmoduleaccess_impl.h b/media/blink/webcontentdecryptionmoduleaccess_impl.h
index 821d1f3..cde6fc7 100644
--- a/media/blink/webcontentdecryptionmoduleaccess_impl.h
+++ b/media/blink/webcontentdecryptionmoduleaccess_impl.h
@@ -5,6 +5,8 @@
 #ifndef MEDIA_BLINK_WEBCONTENTDECRYPTIONMODULEACCESS_IMPL_H_
 #define MEDIA_BLINK_WEBCONTENTDECRYPTIONMODULEACCESS_IMPL_H_
 
+#include <memory>
+
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "media/base/cdm_config.h"
@@ -21,7 +23,13 @@
 class WebContentDecryptionModuleAccessImpl
     : public blink::WebContentDecryptionModuleAccess {
  public:
-  static WebContentDecryptionModuleAccessImpl* Create(
+  static std::unique_ptr<WebContentDecryptionModuleAccessImpl> Create(
+      const blink::WebString& key_system,
+      const blink::WebSecurityOrigin& security_origin,
+      const blink::WebMediaKeySystemConfiguration& configuration,
+      const CdmConfig& cdm_config,
+      const base::WeakPtr<WebEncryptedMediaClientImpl>& client);
+  WebContentDecryptionModuleAccessImpl(
       const blink::WebString& key_system,
       const blink::WebSecurityOrigin& security_origin,
       const blink::WebMediaKeySystemConfiguration& configuration,
@@ -37,13 +45,6 @@
       scoped_refptr<base::SingleThreadTaskRunner> task_runner) override;
 
  private:
-  WebContentDecryptionModuleAccessImpl(
-      const blink::WebString& key_system,
-      const blink::WebSecurityOrigin& security_origin,
-      const blink::WebMediaKeySystemConfiguration& configuration,
-      const CdmConfig& cdm_config,
-      const base::WeakPtr<WebEncryptedMediaClientImpl>& client);
-
   const blink::WebString key_system_;
   const blink::WebSecurityOrigin security_origin_;
   const blink::WebMediaKeySystemConfiguration configuration_;
diff --git a/media/blink/webmediacapabilitiesclient_impl.cc b/media/blink/webmediacapabilitiesclient_impl.cc
index 86015eb9..90121d9 100644
--- a/media/blink/webmediacapabilitiesclient_impl.cc
+++ b/media/blink/webmediacapabilitiesclient_impl.cc
@@ -120,9 +120,9 @@
 
     info->supported = info->smooth = info->power_efficient = true;
     info->content_decryption_module_access =
-        base::WrapUnique(WebContentDecryptionModuleAccessImpl::Create(
+        WebContentDecryptionModuleAccessImpl::Create(
             key_system_configuration->key_system, blink::WebSecurityOrigin(),
-            blink::WebMediaKeySystemConfiguration(), {}, nullptr));
+            blink::WebMediaKeySystemConfiguration(), {}, nullptr);
 
     callbacks->OnSuccess(std::move(info));
     return;
diff --git a/media/media_options.gni b/media/media_options.gni
index 4460ffc..b3b9bd9 100644
--- a/media/media_options.gni
+++ b/media/media_options.gni
@@ -87,7 +87,7 @@
   if (is_win && target_cpu == "arm64") {
     # TODO: Enable dav1d for Windows ARM64. https://crbug.com/941022
     enable_dav1d_decoder = false
-  } else if (is_fuchsia && target_cpu == "x64") {
+  } else if (is_fuchsia) {
     # TODO: Fix media_unittests for fuchsia X64. https://crbug.com/930300
     enable_dav1d_decoder = false
   } else {
diff --git a/mojo/core/node_controller.cc b/mojo/core/node_controller.cc
index 5a796e9..c0d2346 100644
--- a/mojo/core/node_controller.cc
+++ b/mojo/core/node_controller.cc
@@ -731,16 +731,16 @@
     OnBroadcast(name_, std::move(channel_message));
 }
 
-void NodeController::PortStatusChanged(const ports::PortRef& port) {
+void NodeController::SlotStatusChanged(const ports::SlotRef& slot_ref) {
   scoped_refptr<ports::UserData> user_data;
-  node_->GetUserData(port, &user_data);
+  node_->GetUserData(slot_ref.port(), &user_data);
 
   PortObserver* observer = static_cast<PortObserver*>(user_data.get());
   if (observer) {
     observer->OnPortStatusChanged();
   } else {
-    DVLOG(2) << "Ignoring status change for " << port.name() << " because it "
-             << "doesn't have an observer.";
+    DVLOG(2) << "Ignoring status change for " << slot_ref.port().name()
+             << " because it doesn't have an observer.";
   }
 }
 
diff --git a/mojo/core/node_controller.h b/mojo/core/node_controller.h
index 0b44a6a..fcf66b9 100644
--- a/mojo/core/node_controller.h
+++ b/mojo/core/node_controller.h
@@ -187,7 +187,7 @@
   void ForwardEvent(const ports::NodeName& node,
                     ports::ScopedEvent event) override;
   void BroadcastEvent(ports::ScopedEvent event) override;
-  void PortStatusChanged(const ports::PortRef& port) override;
+  void SlotStatusChanged(const ports::SlotRef& slot) override;
 
   // NodeChannel::Delegate:
   void OnAcceptInvitee(const ports::NodeName& from_node,
diff --git a/mojo/core/ports/BUILD.gn b/mojo/core/ports/BUILD.gn
index 68dce3f..cadaea3 100644
--- a/mojo/core/ports/BUILD.gn
+++ b/mojo/core/ports/BUILD.gn
@@ -24,6 +24,8 @@
     "port_locker.h",
     "port_ref.cc",
     "port_ref.h",
+    "slot_ref.cc",
+    "slot_ref.h",
     "user_data.h",
     "user_message.cc",
     "user_message.h",
diff --git a/mojo/core/ports/event.cc b/mojo/core/ports/event.cc
index f3cf74e..6da6aed 100644
--- a/mojo/core/ports/event.cc
+++ b/mojo/core/ports/event.cc
@@ -29,9 +29,15 @@
 struct UserMessageEventData {
   uint64_t sequence_num;
   uint32_t num_ports;
-  uint32_t padding;
+  SlotId slot_id;
 };
 
+// Sanity check to ensure that we aren't breaking the binary structure of
+// UserMessageEventData for older Mojo core versions. The structure has always
+// been 16 bytes wide.
+static_assert(sizeof(UserMessageEventData) == 16,
+              "Bad UserMessageEventData size");
+
 struct ObserveProxyEventData {
   NodeName proxy_node_name;
   PortName proxy_port_name;
@@ -52,6 +58,12 @@
   Event::PortDescriptor new_port_descriptor;
 };
 
+struct SlotClosedEventData {
+  uint64_t last_sequence_num;
+  SlotId slot_id;
+  char padding[4];
+};
+
 #pragma pack(pop)
 
 static_assert(sizeof(Event::PortDescriptor) % kPortsMessageAlignment == 0,
@@ -75,6 +87,9 @@
 static_assert(sizeof(MergePortEventData) % kPortsMessageAlignment == 0,
               "Invalid MergePortEventData size.");
 
+static_assert(sizeof(SlotClosedEventData) % kPortsMessageAlignment == 0,
+              "Invalid SlotClosedEventData size.");
+
 }  // namespace
 
 Event::PortDescriptor::PortDescriptor() {
@@ -105,6 +120,8 @@
       return ObserveClosureEvent::Deserialize(port_name, header + 1, data_size);
     case Type::kMergePort:
       return MergePortEvent::Deserialize(port_name, header + 1, data_size);
+    case Type::kSlotClosed:
+      return SlotClosedEvent::Deserialize(port_name, header + 1, data_size);
     default:
       DVLOG(2) << "Ingoring unknown port event type: "
                << static_cast<uint32_t>(header->type);
@@ -174,6 +191,7 @@
   auto event =
       base::WrapUnique(new UserMessageEvent(port_name, data->sequence_num));
   event->ReservePorts(data->num_ports);
+  event->set_slot_id(data->slot_id);
   const auto* in_descriptors =
       reinterpret_cast<const PortDescriptor*>(data + 1);
   std::copy(in_descriptors, in_descriptors + data->num_ports,
@@ -210,7 +228,7 @@
   data->sequence_num = sequence_num_;
   DCHECK(base::IsValueInRangeForNumericType<uint32_t>(ports_.size()));
   data->num_ports = static_cast<uint32_t>(ports_.size());
-  data->padding = 0;
+  data->slot_id = slot_id_;
 
   auto* ports_data = reinterpret_cast<PortDescriptor*>(data + 1);
   std::copy(port_descriptors_.begin(), port_descriptors_.end(), ports_data);
@@ -378,6 +396,37 @@
   data->new_port_descriptor = new_port_descriptor_;
 }
 
+SlotClosedEvent::SlotClosedEvent(const PortName& port_name,
+                                 SlotId slot_id,
+                                 uint64_t last_sequence_num)
+    : Event(Type::kSlotClosed, port_name),
+      slot_id_(slot_id),
+      last_sequence_num_(last_sequence_num) {}
+
+SlotClosedEvent::~SlotClosedEvent() = default;
+
+// static
+ScopedEvent SlotClosedEvent::Deserialize(const PortName& port_name,
+                                         const void* buffer,
+                                         size_t num_bytes) {
+  if (num_bytes < sizeof(SlotClosedEventData))
+    return nullptr;
+
+  const auto* data = static_cast<const SlotClosedEventData*>(buffer);
+  return std::make_unique<SlotClosedEvent>(port_name, data->slot_id,
+                                           data->last_sequence_num);
+}
+
+size_t SlotClosedEvent::GetSerializedDataSize() const {
+  return sizeof(SlotClosedEventData);
+}
+
+void SlotClosedEvent::SerializeData(void* buffer) const {
+  auto* data = static_cast<SlotClosedEventData*>(buffer);
+  data->slot_id = slot_id_;
+  data->last_sequence_num = last_sequence_num_;
+}
+
 }  // namespace ports
 }  // namespace core
 }  // namespace mojo
diff --git a/mojo/core/ports/event.h b/mojo/core/ports/event.h
index c9a7d6a..8a4852c9 100644
--- a/mojo/core/ports/event.h
+++ b/mojo/core/ports/event.h
@@ -21,6 +21,26 @@
 
 class Event;
 
+using SlotId = uint32_t;
+
+// The default slot on an entangled port pair. When a new port pair is created,
+// this is the only slot available to either endpoint.
+constexpr SlotId kDefaultSlotId = 0;
+
+// Bit toggled on non-default slot IDs when referring to the remote peer's
+// equivalent of a local slot. For example, for entangled ports A and B, if A
+// establishes a new slot 5 when sending a message to B, the slot will always be
+// known to B as (kPeerAllocatedSlotIdBit | 5).
+//
+// Likewise when B sends a message on slot (kPeerAllocatedSlotIdBit | 5), its
+// peer A will receive and queue that message on local slot 5.
+//
+// This allows each endpoint to allocate new slots independently. For any given
+// local slot ID value, having this bit set means the slot was initially
+// established by a message from the remote peer to the local port. Unset means
+// the slot was established by a message from the local port to the remote port.
+constexpr SlotId kPeerAllocatedSlotIdBit = 0x80000000u;
+
 using ScopedEvent = std::unique_ptr<Event>;
 
 // A Event is the fundamental unit of operation and communication within and
@@ -56,6 +76,14 @@
     // Used to request the merging of two routes via two sacrificial receiving
     // ports, one from each route.
     kMergePort,
+
+    // Used to signal that a slot on a port has been closed.
+    //
+    // NOTE: This event type is not supported by older versions of Mojo core
+    // which lack support for port slots. It is therefore important to ensure
+    // that this event never gets generated unless a port has more than the
+    // single default slot.
+    kSlotClosed,
   };
 
 #pragma pack(push, 1)
@@ -131,6 +159,9 @@
   uint64_t sequence_num() const { return sequence_num_; }
   void set_sequence_num(uint64_t sequence_num) { sequence_num_ = sequence_num; }
 
+  SlotId slot_id() const { return slot_id_; }
+  void set_slot_id(SlotId id) { slot_id_ = id; }
+
   size_t num_ports() const { return ports_.size(); }
   PortDescriptor* port_descriptors() { return port_descriptors_.data(); }
   PortName* ports() { return ports_.data(); }
@@ -148,6 +179,7 @@
   void SerializeData(void* buffer) const override;
 
   uint64_t sequence_num_ = 0;
+  SlotId slot_id_ = 0;
   std::vector<PortDescriptor> port_descriptors_;
   std::vector<PortName> ports_;
   std::unique_ptr<UserMessage> message_;
@@ -277,6 +309,30 @@
   DISALLOW_COPY_AND_ASSIGN(MergePortEvent);
 };
 
+class COMPONENT_EXPORT(MOJO_CORE_PORTS) SlotClosedEvent : public Event {
+ public:
+  SlotClosedEvent(const PortName& port_name,
+                  SlotId slot_id,
+                  uint64_t last_sequence_num);
+  ~SlotClosedEvent() override;
+
+  SlotId slot_id() const { return slot_id_; }
+  uint64_t last_sequence_num() const { return last_sequence_num_; }
+
+  static ScopedEvent Deserialize(const PortName& port_name,
+                                 const void* buffer,
+                                 size_t num_bytes);
+
+ private:
+  size_t GetSerializedDataSize() const override;
+  void SerializeData(void* buffer) const override;
+
+  const SlotId slot_id_;
+  const uint64_t last_sequence_num_;
+
+  DISALLOW_COPY_AND_ASSIGN(SlotClosedEvent);
+};
+
 }  // namespace ports
 }  // namespace core
 }  // namespace mojo
diff --git a/mojo/core/ports/message_queue.cc b/mojo/core/ports/message_queue.cc
index 0abb713..58c5ad9 100644
--- a/mojo/core/ports/message_queue.cc
+++ b/mojo/core/ports/message_queue.cc
@@ -15,9 +15,9 @@
 namespace ports {
 
 // Used by std::{push,pop}_heap functions
-inline bool operator<(const std::unique_ptr<UserMessageEvent>& a,
-                      const std::unique_ptr<UserMessageEvent>& b) {
-  return a->sequence_num() > b->sequence_num();
+inline bool operator<(const MessageQueue::Entry& a,
+                      const MessageQueue::Entry& b) {
+  return a.message->sequence_num() > b.message->sequence_num();
 }
 
 MessageQueue::MessageQueue() : MessageQueue(kInitialSequenceNum) {}
@@ -31,27 +31,93 @@
 MessageQueue::~MessageQueue() {
 #if DCHECK_IS_ON()
   size_t num_leaked_ports = 0;
-  for (const auto& message : heap_)
-    num_leaked_ports += message->num_ports();
+  for (const auto& entry : heap_)
+    num_leaked_ports += entry.message->num_ports();
   DVLOG_IF(1, num_leaked_ports > 0)
       << "Leaking " << num_leaked_ports << " ports in unreceived messages";
 #endif
 }
 
-bool MessageQueue::HasNextMessage() const {
-  return !heap_.empty() && heap_[0]->sequence_num() == next_sequence_num_;
+bool MessageQueue::HasNextMessage(base::Optional<SlotId> slot_id) {
+  DropNextIgnoredMessages();
+  return !heap_.empty() &&
+         heap_[0].message->sequence_num() == next_sequence_num_ &&
+         (!slot_id || heap_[0].message->slot_id() == slot_id);
 }
 
-void MessageQueue::GetNextMessage(std::unique_ptr<UserMessageEvent>* message,
+base::Optional<SlotId> MessageQueue::GetNextMessageSlot() {
+  DropNextIgnoredMessages();
+  if (heap_.empty() || heap_[0].message->sequence_num() != next_sequence_num_)
+    return base::nullopt;
+  return heap_[0].message->slot_id();
+}
+
+void MessageQueue::GetNextMessage(base::Optional<SlotId> slot_id,
+                                  std::unique_ptr<UserMessageEvent>* message,
                                   MessageFilter* filter) {
-  if (!HasNextMessage() || (filter && !filter->Match(*heap_[0]))) {
+  if (!HasNextMessage(slot_id) ||
+      (filter && !filter->Match(*heap_[0].message))) {
     message->reset();
     return;
   }
 
+  *message = DequeueMessage();
+}
+
+void MessageQueue::AcceptMessage(
+    std::unique_ptr<UserMessageEvent> message,
+    base::Optional<SlotId>* slot_with_next_message) {
+  // TODO: Handle sequence number roll-over.
+
+  EnqueueMessage(std::move(message), false /* ignored */);
+  if (heap_[0].message->sequence_num() == next_sequence_num_)
+    *slot_with_next_message = heap_[0].message->slot_id();
+  else
+    slot_with_next_message->reset();
+}
+
+void MessageQueue::IgnoreMessage(std::unique_ptr<UserMessageEvent>* message) {
+  if ((*message)->sequence_num() == next_sequence_num_) {
+    // Fast path -- if we're ignoring the next message in the sequence, just
+    // advance the queue's sequence number.
+    next_sequence_num_++;
+    return;
+  }
+
+  EnqueueMessage(std::move(*message), true /* ignored */);
+}
+
+void MessageQueue::TakeAllMessages(
+    std::vector<std::unique_ptr<UserMessageEvent>>* messages) {
+  for (auto& entry : heap_)
+    messages->emplace_back(std::move(entry.message));
+  heap_.clear();
+  total_queued_bytes_ = 0;
+}
+
+void MessageQueue::TakeAllLeadingMessagesForSlot(
+    SlotId slot_id,
+    std::vector<std::unique_ptr<UserMessageEvent>>* messages) {
+  std::unique_ptr<UserMessageEvent> message;
+  for (;;) {
+    GetNextMessage(slot_id, &message, nullptr);
+    if (!message)
+      return;
+    messages->push_back(std::move(message));
+  }
+}
+
+void MessageQueue::EnqueueMessage(std::unique_ptr<UserMessageEvent> message,
+                                  bool ignored) {
+  total_queued_bytes_ += message->GetSizeIfSerialized();
+  heap_.emplace_back(ignored, std::move(message));
+  std::push_heap(heap_.begin(), heap_.end());
+}
+
+std::unique_ptr<UserMessageEvent> MessageQueue::DequeueMessage() {
   std::pop_heap(heap_.begin(), heap_.end());
-  *message = std::move(heap_.back());
-  total_queued_bytes_ -= (*message)->GetSizeIfSerialized();
+  auto message = std::move(heap_.back().message);
+  total_queued_bytes_ -= message->GetSizeIfSerialized();
   heap_.pop_back();
 
   // We keep the capacity of |heap_| in check so that a large batch of incoming
@@ -65,28 +131,27 @@
   }
 
   next_sequence_num_++;
+
+  return message;
 }
 
-void MessageQueue::AcceptMessage(std::unique_ptr<UserMessageEvent> message,
-                                 bool* has_next_message) {
-  // TODO: Handle sequence number roll-over.
-
-  total_queued_bytes_ += message->GetSizeIfSerialized();
-  heap_.emplace_back(std::move(message));
-  std::push_heap(heap_.begin(), heap_.end());
-
-  if (!signalable_) {
-    *has_next_message = false;
-  } else {
-    *has_next_message = (heap_[0]->sequence_num() == next_sequence_num_);
+void MessageQueue::DropNextIgnoredMessages() {
+  while (!heap_.empty() &&
+         heap_[0].message->sequence_num() == next_sequence_num_ &&
+         heap_[0].ignored) {
+    DequeueMessage();
   }
 }
 
-void MessageQueue::TakeAllMessages(
-    std::vector<std::unique_ptr<UserMessageEvent>>* messages) {
-  *messages = std::move(heap_);
-  total_queued_bytes_ = 0;
-}
+MessageQueue::Entry::Entry(bool ignored,
+                           std::unique_ptr<UserMessageEvent> message)
+    : ignored(ignored), message(std::move(message)) {}
+
+MessageQueue::Entry::Entry(Entry&&) = default;
+
+MessageQueue::Entry::~Entry() = default;
+
+MessageQueue::Entry& MessageQueue::Entry::operator=(Entry&&) = default;
 
 }  // namespace ports
 }  // namespace core
diff --git a/mojo/core/ports/message_queue.h b/mojo/core/ports/message_queue.h
index 1d34222..d8a03cf 100644
--- a/mojo/core/ports/message_queue.h
+++ b/mojo/core/ports/message_queue.h
@@ -13,6 +13,7 @@
 
 #include "base/component_export.h"
 #include "base/macros.h"
+#include "base/optional.h"
 #include "mojo/core/ports/event.h"
 
 namespace mojo {
@@ -30,19 +31,38 @@
 // enforcing it for the producer (see AcceptMessage() below.)
 class COMPONENT_EXPORT(MOJO_CORE_PORTS) MessageQueue {
  public:
+  struct Entry {
+    Entry(bool ignored, std::unique_ptr<UserMessageEvent> message);
+    Entry(Entry&&);
+    ~Entry();
+
+    Entry& operator=(Entry&&);
+
+    bool ignored;
+    std::unique_ptr<UserMessageEvent> message;
+  };
+
   explicit MessageQueue();
   explicit MessageQueue(uint64_t next_sequence_num);
   ~MessageQueue();
 
-  void set_signalable(bool value) { signalable_ = value; }
-
   uint64_t next_sequence_num() const { return next_sequence_num_; }
 
-  bool HasNextMessage() const;
+  // Indicates if the next message in the queue's sequence is available and
+  // is targeting slot |slot_id|, if given.
+  bool HasNextMessage(base::Optional<SlotId> slot_id);
+
+  // Indicates if the next message in the queue's sequence is available,
+  // regardless of targeted slot ID. If it is, this returns the SlotId of the
+  // next available message. Otherwise it returns |base::nullopt|.
+  base::Optional<SlotId> GetNextMessageSlot();
 
   // Gives ownership of the message. If |filter| is non-null, the next message
-  // will only be retrieved if the filter successfully matches it.
-  void GetNextMessage(std::unique_ptr<UserMessageEvent>* message,
+  // will only be retrieved if the filter successfully matches it. If |slot_id|
+  // is given, this next message will only be retrieved if it belongs to that
+  // slot.
+  void GetNextMessage(base::Optional<SlotId> slot_id,
+                      std::unique_ptr<UserMessageEvent>* message,
                       MessageFilter* filter);
 
   // Takes ownership of the message. Note: Messages are ordered, so while we
@@ -50,18 +70,33 @@
   // ahead of this one before we can let any of the messages be returned by
   // GetNextMessage.
   //
-  // Furthermore, once has_next_message is set to true, it will remain false
-  // until GetNextMessage is called enough times to return a null message.
-  // In other words, has_next_message acts like an edge trigger.
-  //
+  // |*slot_with_next_message| is set to the SlotId of the slot which has the
+  // next message in the sequence available to it, iff the next message in the
+  // sequence is available.
   void AcceptMessage(std::unique_ptr<UserMessageEvent> message,
-                     bool* has_next_message);
+                     base::Optional<SlotId>* slot_with_next_message);
+
+  // Places |*message| in the queue to be ignored. It is important that the
+  // message not simply be discarded because messages can arrive out of
+  // sequential order. The queue will retain this message until all preceding
+  // messages in the sequence are read from the queue, at which point |*message|
+  // can be safely discarded and the sequence can be advanced beyond it.
+  //
+  // Note that if |*message| is the next expected message in this queue, the
+  // queue is simply advanced beyond its sequence number and ownership of
+  // |*message| is *not* transferred to the queue.
+  void IgnoreMessage(std::unique_ptr<UserMessageEvent>* message);
 
   // Takes all messages from this queue. Used to safely destroy queued messages
   // without holding any Port lock.
   void TakeAllMessages(
       std::vector<std::unique_ptr<UserMessageEvent>>* messages);
 
+  // Takes all leading messages from the queue which target |slot_id|.
+  void TakeAllLeadingMessagesForSlot(
+      SlotId slot_id,
+      std::vector<std::unique_ptr<UserMessageEvent>>* messages);
+
   // The number of messages queued here, regardless of whether the next expected
   // message has arrived yet.
   size_t queued_message_count() const { return heap_.size(); }
@@ -71,9 +106,12 @@
   size_t queued_num_bytes() const { return total_queued_bytes_; }
 
  private:
-  std::vector<std::unique_ptr<UserMessageEvent>> heap_;
+  void EnqueueMessage(std::unique_ptr<UserMessageEvent> message, bool ignored);
+  std::unique_ptr<UserMessageEvent> DequeueMessage();
+  void DropNextIgnoredMessages();
+
+  std::vector<Entry> heap_;
   uint64_t next_sequence_num_;
-  bool signalable_ = true;
   size_t total_queued_bytes_ = 0;
 
   DISALLOW_COPY_AND_ASSIGN(MessageQueue);
diff --git a/mojo/core/ports/node.cc b/mojo/core/ports/node.cc
index 767e37c..c95739a 100644
--- a/mojo/core/ports/node.cc
+++ b/mojo/core/ports/node.cc
@@ -80,7 +80,7 @@
 
 #define OOPS(x) DebugError(#x, x)
 
-bool CanAcceptMoreMessages(const Port* port) {
+bool CanAcceptMoreMessages(Port* port, SlotId slot_id) {
   // Have we already doled out the last message (i.e., do we expect to NOT
   // receive further messages)?
   uint64_t next_sequence_num = port->message_queue.next_sequence_num();
@@ -90,7 +90,13 @@
     if (port->last_sequence_num_to_receive == next_sequence_num - 1)
       return false;
   }
-  return true;
+
+  Port::Slot* slot = port->GetSlot(slot_id);
+  if (!slot)
+    return false;
+
+  return !slot->peer_closed ||
+         slot->last_sequence_num_to_receive >= next_sequence_num;
 }
 
 void GenerateRandomPortName(PortName* name) {
@@ -193,9 +199,15 @@
     port->state = Port::kReceiving;
     UpdatePortPeerAddress(port_ref.name(), port, peer_node_name,
                           peer_port_name);
+
+    Port::Slot& default_slot = port->slots[kDefaultSlotId];
+    default_slot.can_signal = true;
+    default_slot.peer_closed = false;
+    default_slot.last_sequence_num_sent = 0;
+    default_slot.last_sequence_num_to_receive = 0;
   }
 
-  delegate_->PortStatusChanged(port_ref);
+  delegate_->SlotStatusChanged(SlotRef(port_ref, kDefaultSlotId));
 
   return OK;
 }
@@ -246,85 +258,55 @@
   return OK;
 }
 
+int Node::ClosePortSlot(const SlotRef& slot_ref) {
+  return ClosePortOrSlotImpl(slot_ref.port(), slot_ref.slot_id());
+}
+
 int Node::ClosePort(const PortRef& port_ref) {
-  std::vector<std::unique_ptr<UserMessageEvent>> undelivered_messages;
-  NodeName peer_node_name;
-  PortName peer_port_name;
-  uint64_t last_sequence_num = 0;
-  bool was_initialized = false;
-  {
-    SinglePortLocker locker(&port_ref);
-    auto* port = locker.port();
-    switch (port->state) {
-      case Port::kUninitialized:
-        break;
+  return ClosePortOrSlotImpl(port_ref, base::nullopt);
+}
 
-      case Port::kReceiving:
-        was_initialized = true;
-        port->state = Port::kClosed;
+int Node::GetStatus(const SlotRef& slot_ref, SlotStatus* slot_status) {
+  SinglePortLocker locker(&slot_ref.port());
+  auto* port = locker.port();
+  if (port->state != Port::kReceiving)
+    return ERROR_PORT_STATE_UNEXPECTED;
 
-        // We pass along the sequence number of the last message sent from this
-        // port to allow the peer to have the opportunity to consume all inbound
-        // messages before notifying the embedder that this port is closed.
-        last_sequence_num = port->next_sequence_num_to_send - 1;
+  slot_status->has_messages =
+      port->message_queue.HasNextMessage(slot_ref.slot_id());
+  slot_status->receiving_messages =
+      CanAcceptMoreMessages(port, slot_ref.slot_id());
+  slot_status->peer_remote = port->peer_node_name != name_;
+  slot_status->queued_message_count =
+      port->message_queue.queued_message_count();
+  slot_status->queued_num_bytes = port->message_queue.queued_num_bytes();
 
-        peer_node_name = port->peer_node_name;
-        peer_port_name = port->peer_port_name;
-
-        // If the port being closed still has unread messages, then we need to
-        // take care to close those ports so as to avoid leaking memory.
-        port->message_queue.TakeAllMessages(&undelivered_messages);
-        break;
-
-      default:
-        return ERROR_PORT_STATE_UNEXPECTED;
-    }
-  }
-
-  ErasePort(port_ref.name());
-
-  if (was_initialized) {
-    DVLOG(2) << "Sending ObserveClosure from " << port_ref.name() << "@"
-             << name_ << " to " << peer_port_name << "@" << peer_node_name;
-    delegate_->ForwardEvent(peer_node_name,
-                            std::make_unique<ObserveClosureEvent>(
-                                peer_port_name, last_sequence_num));
-    for (const auto& message : undelivered_messages) {
-      for (size_t i = 0; i < message->num_ports(); ++i) {
-        PortRef ref;
-        if (GetPort(message->ports()[i], &ref) == OK)
-          ClosePort(ref);
-      }
-    }
+  if (port->peer_closed) {
+    slot_status->peer_closed = port->peer_closed;
+  } else {
+    Port::Slot* slot = port->GetSlot(slot_ref.slot_id());
+    if (!slot)
+      return ERROR_PORT_STATE_UNEXPECTED;
+    slot_status->peer_closed = slot->peer_closed;
   }
   return OK;
 }
 
 int Node::GetStatus(const PortRef& port_ref, PortStatus* port_status) {
-  SinglePortLocker locker(&port_ref);
-  auto* port = locker.port();
-  if (port->state != Port::kReceiving)
-    return ERROR_PORT_STATE_UNEXPECTED;
-
-  port_status->has_messages = port->message_queue.HasNextMessage();
-  port_status->receiving_messages = CanAcceptMoreMessages(port);
-  port_status->peer_closed = port->peer_closed;
-  port_status->peer_remote = port->peer_node_name != name_;
-  port_status->queued_message_count =
-      port->message_queue.queued_message_count();
-  port_status->queued_num_bytes = port->message_queue.queued_num_bytes();
-  return OK;
+  return GetStatus(SlotRef(port_ref, kDefaultSlotId), port_status);
 }
 
-int Node::GetMessage(const PortRef& port_ref,
+int Node::GetMessage(const SlotRef& slot_ref,
                      std::unique_ptr<UserMessageEvent>* message,
                      MessageFilter* filter) {
   *message = nullptr;
 
-  DVLOG(4) << "GetMessage for " << port_ref.name() << "@" << name_;
+  DVLOG(4) << "GetMessage for " << slot_ref.port().name() << "@" << name_;
+
+  bool peer_closed = false;
 
   {
-    SinglePortLocker locker(&port_ref);
+    SinglePortLocker locker(&slot_ref.port());
     auto* port = locker.port();
 
     // This could also be treated like the port being unknown since the
@@ -334,13 +316,16 @@
 
     // Let the embedder get messages until there are no more before reporting
     // that the peer closed its end.
-    if (!CanAcceptMoreMessages(port))
-      return ERROR_PORT_PEER_CLOSED;
-
-    port->message_queue.GetNextMessage(message, filter);
+    if (CanAcceptMoreMessages(port, slot_ref.slot_id()))
+      port->message_queue.GetNextMessage(slot_ref.slot_id(), message, filter);
+    else
+      peer_closed = true;
   }
 
-  // Allow referenced ports to trigger PortStatusChanged calls.
+  // Allow referenced ports to trigger SlotStatusChanged calls now that the
+  // message which contains them is actually being read. A consumer who cares
+  // about the status updates can ensure that they are properly watching for
+  // these events before making any calls to |GetMessage()|.
   if (*message) {
     for (size_t i = 0; i < (*message)->num_ports(); ++i) {
       PortRef new_port_ref;
@@ -351,26 +336,47 @@
 
       SinglePortLocker locker(&new_port_ref);
       DCHECK(locker.port()->state == Port::kReceiving);
-      locker.port()->message_queue.set_signalable(true);
+
+      Port::Slot* slot = locker.port()->GetSlot(kDefaultSlotId);
+      DCHECK(slot);
+      slot->can_signal = true;
     }
 
     // The user may retransmit this message from another port. We reset the
     // sequence number so that the message will get a new one if that happens.
     (*message)->set_sequence_num(0);
+
+    // If we read a message, we may need to flush subsequent unreadable messages
+    // to unblock the rest of the message sequence. Note that we only notify
+    // the slot with the next available message (if any) when it's different
+    // from the slot we just read.
+    base::Optional<SlotId> slot_to_notify =
+        FlushUnreadableMessages(slot_ref.port());
+    if (slot_to_notify && slot_to_notify != slot_ref.slot_id())
+      delegate_->SlotStatusChanged(SlotRef(slot_ref.port(), *slot_to_notify));
   }
 
+  if (peer_closed)
+    return ERROR_PORT_PEER_CLOSED;
+
   return OK;
 }
 
-int Node::SendUserMessage(const PortRef& port_ref,
+int Node::GetMessage(const PortRef& port_ref,
+                     std::unique_ptr<UserMessageEvent>* message,
+                     MessageFilter* filter) {
+  return GetMessage(SlotRef(port_ref, kDefaultSlotId), message, filter);
+}
+
+int Node::SendUserMessage(const SlotRef& slot_ref,
                           std::unique_ptr<UserMessageEvent> message) {
-  int rv = SendUserMessageInternal(port_ref, &message);
+  int rv = SendUserMessageInternal(slot_ref, &message);
   if (rv != OK) {
     // If send failed, close all carried ports. Note that we're careful not to
     // close the sending port itself if it happened to be one of the encoded
     // ports (an invalid but possible condition.)
     for (size_t i = 0; i < message->num_ports(); ++i) {
-      if (message->ports()[i] == port_ref.name())
+      if (message->ports()[i] == slot_ref.port().name())
         continue;
 
       PortRef port;
@@ -381,6 +387,21 @@
   return rv;
 }
 
+int Node::SendUserMessage(const PortRef& port_ref,
+                          std::unique_ptr<UserMessageEvent> message) {
+  return SendUserMessage(SlotRef(port_ref, kDefaultSlotId), std::move(message));
+}
+
+SlotId Node::AllocateSlot(const PortRef& port_ref) {
+  SinglePortLocker locker(&port_ref);
+  return locker.port()->AllocateSlot();
+}
+
+bool Node::AddSlotFromPeer(const PortRef& port_ref, SlotId peer_slot_id) {
+  SinglePortLocker locker(&port_ref);
+  return locker.port()->AddSlotFromPeer(peer_slot_id);
+}
+
 int Node::AcceptEvent(ScopedEvent event) {
   switch (event->type()) {
     case Event::Type::kUserMessage:
@@ -395,6 +416,8 @@
       return OnObserveClosure(Event::Cast<ObserveClosureEvent>(&event));
     case Event::Type::kMergePort:
       return OnMergePort(Event::Cast<MergePortEvent>(&event));
+    case Event::Type::kSlotClosed:
+      return OnSlotClosed(Event::Cast<SlotClosedEvent>(&event));
   }
   return OOPS(ERROR_NOT_IMPLEMENTED);
 }
@@ -427,7 +450,7 @@
     // update so it notices that its peer is now remote.
     PortRef local_peer;
     if (GetPort(new_port_descriptor.peer_port_name, &local_peer) == OK)
-      delegate_->PortStatusChanged(local_peer);
+      delegate_->SlotStatusChanged(SlotRef(local_peer, kDefaultSlotId));
   }
 
   delegate_->ForwardEvent(
@@ -455,9 +478,91 @@
   return OK;
 }
 
+int Node::ClosePortOrSlotImpl(const PortRef& port_ref,
+                              base::Optional<SlotId> slot_id) {
+  std::vector<std::unique_ptr<UserMessageEvent>> undelivered_messages;
+  NodeName peer_node_name;
+  PortName peer_port_name;
+  uint64_t last_sequence_num = 0;
+  bool was_initialized = false;
+  bool port_closed = false;
+  {
+    SinglePortLocker locker(&port_ref);
+    auto* port = locker.port();
+    switch (port->state) {
+      case Port::kUninitialized:
+        port_closed = true;
+        break;
+
+      case Port::kReceiving: {
+        was_initialized = true;
+
+        Port::Slot* slot = slot_id ? port->GetSlot(*slot_id) : nullptr;
+        if (!slot_id || (slot && port->slots.size() == 1)) {
+          // If no SlotId was given or we are closing the last slot on the port,
+          // close the whole port.
+          port->state = Port::kClosed;
+          port_closed = true;
+
+          // We pass along the sequence number of the last message sent from
+          // this port to allow the peer to have the opportunity to consume all
+          // inbound messages before notifying the embedder that the port or
+          // slot is closed.
+          last_sequence_num = port->next_sequence_num_to_send - 1;
+        } else {
+          last_sequence_num = slot->last_sequence_num_sent;
+          port->slots.erase(*slot_id);
+        }
+
+        peer_node_name = port->peer_node_name;
+        peer_port_name = port->peer_port_name;
+
+        // If the port being closed still has unread messages, then we need to
+        // take care to close those ports so as to avoid leaking memory.
+        if (port_closed) {
+          port->message_queue.TakeAllMessages(&undelivered_messages);
+        } else {
+          port->message_queue.TakeAllLeadingMessagesForSlot(
+              *slot_id, &undelivered_messages);
+        }
+        break;
+      }
+
+      default:
+        return ERROR_PORT_STATE_UNEXPECTED;
+    }
+  }
+
+  if (port_closed)
+    ErasePort(port_ref.name());
+
+  base::Optional<SlotId> slot_to_notify;
+  if (was_initialized) {
+    if (port_closed) {
+      DVLOG(2) << "Sending ObserveClosure from " << port_ref.name() << "@"
+               << name_ << " to " << peer_port_name << "@" << peer_node_name;
+      delegate_->ForwardEvent(peer_node_name,
+                              std::make_unique<ObserveClosureEvent>(
+                                  peer_port_name, last_sequence_num));
+    } else {
+      // This path is only hit when closing a non-default slot of a port with
+      // multiple slots.
+      delegate_->ForwardEvent(peer_node_name,
+                              std::make_unique<SlotClosedEvent>(
+                                  peer_port_name, *slot_id, last_sequence_num));
+      slot_to_notify = FlushUnreadableMessages(port_ref);
+    }
+    DiscardUnreadMessages(std::move(undelivered_messages));
+  }
+
+  if (slot_to_notify)
+    delegate_->SlotStatusChanged(SlotRef(port_ref, *slot_to_notify));
+
+  return OK;
+}
+
 int Node::OnUserMessage(std::unique_ptr<UserMessageEvent> message) {
   PortName port_name = message->port_name();
-
 #if DCHECK_IS_ON()
   std::ostringstream ports_buf;
   for (size_t i = 0; i < message->num_ports(); ++i) {
@@ -496,50 +601,64 @@
     }
   }
 
-  PortRef port_ref;
-  GetPort(port_name, &port_ref);
-  bool has_next_message = false;
+  PortRef receiving_port_ref;
+  GetPort(port_name, &receiving_port_ref);
+  base::Optional<SlotId> slot_with_next_message;
   bool message_accepted = false;
   bool should_forward_messages = false;
-  if (port_ref.is_valid()) {
-    SinglePortLocker locker(&port_ref);
+  if (receiving_port_ref.is_valid()) {
+    SinglePortLocker locker(&receiving_port_ref);
     auto* port = locker.port();
 
     // Reject spurious messages if we've already received the last expected
     // message.
-    if (CanAcceptMoreMessages(port)) {
+    SlotId slot_id = message->slot_id();
+    if (CanAcceptMoreMessages(port, slot_id)) {
       message_accepted = true;
-      port->message_queue.AcceptMessage(std::move(message), &has_next_message);
+      port->message_queue.AcceptMessage(std::move(message),
+                                        &slot_with_next_message);
 
       if (port->state == Port::kBuffering) {
-        has_next_message = false;
+        slot_with_next_message.reset();
       } else if (port->state == Port::kProxying) {
-        has_next_message = false;
+        slot_with_next_message.reset();
         should_forward_messages = true;
+      } else {
+        Port::Slot* slot = port->GetSlot(slot_id);
+        if (!slot || !slot->can_signal)
+          slot_with_next_message.reset();
       }
     }
   }
 
   if (should_forward_messages) {
-    int rv = ForwardUserMessagesFromProxy(port_ref);
+    int rv = ForwardUserMessagesFromProxy(receiving_port_ref);
     if (rv != OK)
       return rv;
-    TryRemoveProxy(port_ref);
+    TryRemoveProxy(receiving_port_ref);
   }
 
   if (!message_accepted) {
     DVLOG(2) << "Message not accepted!\n";
-    // Close all newly accepted ports as they are effectively orphaned.
-    for (size_t i = 0; i < message->num_ports(); ++i) {
-      PortRef attached_port_ref;
-      if (GetPort(message->ports()[i], &attached_port_ref) == OK) {
-        ClosePort(attached_port_ref);
-      } else {
-        DLOG(WARNING) << "Cannot close non-existent port!\n";
+    DiscardPorts(message.get());
+
+    if (receiving_port_ref.is_valid()) {
+      {
+        // We still have to inform the MessageQueue about this message so it can
+        // keep the sequence progressing forward.
+        SinglePortLocker locker(&receiving_port_ref);
+        locker.port()->message_queue.IgnoreMessage(&message);
       }
+
+      // It's possible that some later message in the sequence was already in
+      // queue, and it may now be unblocked by the discarding of this message.
+      slot_with_next_message = FlushUnreadableMessages(receiving_port_ref);
     }
-  } else if (has_next_message) {
-    delegate_->PortStatusChanged(port_ref);
+  }
+
+  if (slot_with_next_message) {
+    delegate_->SlotStatusChanged(
+        SlotRef(receiving_port_ref, *slot_with_next_message));
   }
 
   return OK;
@@ -593,7 +712,7 @@
            << event->proxy_target_port_name() << "@"
            << event->proxy_target_node_name();
 
-  bool update_status = false;
+  base::StackVector<SlotId, 2> slots_to_update;
   ScopedEvent event_to_forward;
   NodeName event_target_node;
   {
@@ -613,7 +732,9 @@
         event_target_node = event->proxy_node_name();
         event_to_forward = std::make_unique<ObserveProxyAckEvent>(
             event->proxy_port_name(), port->next_sequence_num_to_send - 1);
-        update_status = true;
+        slots_to_update.container().reserve(port->slots.size());
+        for (const auto& entry : port->slots)
+          slots_to_update.container().push_back(entry.first);
         DVLOG(2) << "Forwarding ObserveProxyAck from " << event->port_name()
                  << "@" << name_ << " to " << event->proxy_port_name() << "@"
                  << event_target_node;
@@ -648,8 +769,8 @@
   if (event_to_forward)
     delegate_->ForwardEvent(event_target_node, std::move(event_to_forward));
 
-  if (update_status)
-    delegate_->PortStatusChanged(port_ref);
+  for (auto slot_id : slots_to_update.container())
+    delegate_->SlotStatusChanged(SlotRef(port_ref, slot_id));
 
   return OK;
 }
@@ -701,7 +822,7 @@
   // the receiving end, and this message serves as an equivalent to
   // ObserveProxyAck.
 
-  bool notify_delegate = false;
+  base::StackVector<SlotId, 2> slots_to_update;
   NodeName peer_node_name;
   PortName peer_port_name;
   bool try_remove_proxy = false;
@@ -722,8 +843,9 @@
     // are notified to remove themselves.
 
     if (port->state == Port::kReceiving) {
-      notify_delegate = true;
-
+      slots_to_update.container().reserve(port->slots.size());
+      for (const auto& entry : port->slots)
+        slots_to_update.container().push_back(entry.first);
       // When forwarding along the other half of the port cycle, this will only
       // reach dead-end proxies. Tell them we've sent our last message so they
       // can go away.
@@ -757,8 +879,8 @@
   event->set_port_name(peer_port_name);
   delegate_->ForwardEvent(peer_node_name, std::move(event));
 
-  if (notify_delegate)
-    delegate_->PortStatusChanged(port_ref);
+  for (auto slot_id : slots_to_update.container())
+    delegate_->SlotStatusChanged(SlotRef(port_ref, slot_id));
 
   return OK;
 }
@@ -799,6 +921,34 @@
                             false /* allow_close_on_bad_state */);
 }
 
+int Node::OnSlotClosed(std::unique_ptr<SlotClosedEvent> event) {
+  // OK if the port doesn't exist, as it may have been closed already.
+  PortRef port_ref;
+  if (GetPort(event->port_name(), &port_ref) != OK)
+    return OK;
+
+  SlotId local_slot_id = event->slot_id() == kDefaultSlotId
+                             ? kDefaultSlotId
+                             : (event->slot_id() ^ kPeerAllocatedSlotIdBit);
+  {
+    SinglePortLocker locker(&port_ref);
+    Port* port = locker.port();
+
+    // The local slot may have been closed already. No need to take further
+    // action here.
+    Port::Slot* slot = port->GetSlot(local_slot_id);
+    if (!slot)
+      return OK;
+
+    slot->peer_closed = true;
+    slot->last_sequence_num_to_receive = event->last_sequence_num();
+  }
+
+  delegate_->SlotStatusChanged(SlotRef(port_ref, local_slot_id));
+
+  return OK;
+}
+
 int Node::AddPortWithName(const PortName& port_name, scoped_refptr<Port> port) {
   PortLocker::AssertNoPortsLockedOnCurrentThread();
   base::AutoLock lock(ports_lock_);
@@ -837,16 +987,19 @@
   DVLOG(2) << "Deleted port " << port_name << "@" << name_;
 }
 
-int Node::SendUserMessageInternal(const PortRef& port_ref,
+int Node::SendUserMessageInternal(const SlotRef& slot_ref,
                                   std::unique_ptr<UserMessageEvent>* message) {
   std::unique_ptr<UserMessageEvent>& m = *message;
   for (size_t i = 0; i < m->num_ports(); ++i) {
-    if (m->ports()[i] == port_ref.name())
+    if (m->ports()[i] == slot_ref.port().name())
       return ERROR_PORT_CANNOT_SEND_SELF;
   }
 
+  if (slot_ref.slot_id() != kDefaultSlotId)
+    m->set_slot_id(slot_ref.slot_id() ^ kPeerAllocatedSlotIdBit);
+
   NodeName target_node;
-  int rv = PrepareToForwardUserMessage(port_ref, Port::kReceiving,
+  int rv = PrepareToForwardUserMessage(slot_ref, Port::kReceiving,
                                        false /* ignore_closed_peer */, m.get(),
                                        &target_node);
   if (rv != OK)
@@ -1040,9 +1193,15 @@
            << "; last_sequence_num_to_receive="
            << port->last_sequence_num_to_receive << "]";
 
-  // A newly accepted port is not signalable until the message referencing the
-  // new port finds its way to the consumer (see GetMessage).
-  port->message_queue.set_signalable(false);
+  // Initialize the default slot on this port. Newly accepted ports must have
+  // only the default slot, as ports with additional slots are non-transferrable
+  // and thus can't be the subject of an |AcceptPort()| call.
+  Port::Slot& slot = port->slots[kDefaultSlotId];
+  slot.can_signal = false;
+  slot.peer_closed = port_descriptor.peer_closed;
+  slot.last_sequence_num_to_receive =
+      port_descriptor.last_sequence_num_to_receive;
+  slot.last_sequence_num_sent = port_descriptor.next_sequence_num_to_send - 1;
 
   int rv = AddPortWithName(port_name, std::move(port));
   if (rv != OK)
@@ -1055,7 +1214,7 @@
   return OK;
 }
 
-int Node::PrepareToForwardUserMessage(const PortRef& forwarding_port_ref,
+int Node::PrepareToForwardUserMessage(const SlotRef& forwarding_slot_ref,
                                       Port::State expected_port_state,
                                       bool ignore_closed_peer,
                                       UserMessageEvent* message,
@@ -1064,7 +1223,7 @@
   for (;;) {
     NodeName target_node_name;
     {
-      SinglePortLocker locker(&forwarding_port_ref);
+      SinglePortLocker locker(&forwarding_slot_ref.port());
       target_node_name = locker.port()->peer_node_name;
     }
 
@@ -1086,7 +1245,7 @@
     base::StackVector<const PortRef*, 5> ports_to_lock;
     attached_port_refs.container().resize(message->num_ports());
     ports_to_lock.container().resize(message->num_ports() + 1);
-    ports_to_lock[0] = &forwarding_port_ref;
+    ports_to_lock[0] = &forwarding_slot_ref.port();
     for (size_t i = 0; i < message->num_ports(); ++i) {
       const PortName& attached_port_name = message->ports()[i];
       auto iter = ports_.find(attached_port_name);
@@ -1096,7 +1255,7 @@
     }
     PortLocker locker(ports_to_lock.container().data(),
                       ports_to_lock.container().size());
-    auto* forwarding_port = locker.GetPort(forwarding_port_ref);
+    auto* forwarding_port = locker.GetPort(forwarding_slot_ref.port());
 
     if (forwarding_port->peer_node_name != target_node_name) {
       // The target node has already changed since we last held the lock.
@@ -1118,7 +1277,7 @@
     // Messages may already have a sequence number if they're being forwarded by
     // a proxy. Otherwise, use the next outgoing sequence number.
     if (message->sequence_num() == 0)
-      message->set_sequence_num(forwarding_port->next_sequence_num_to_send++);
+      message->set_sequence_num(forwarding_port->next_sequence_num_to_send);
 #if DCHECK_IS_ON()
     std::ostringstream ports_buf;
     for (size_t i = 0; i < message->num_ports(); ++i) {
@@ -1130,23 +1289,18 @@
 
     if (message->num_ports() > 0) {
       // Sanity check to make sure we can actually send all the attached ports.
-      // They must all be in the |kReceiving| state and must not be the sender's
-      // own peer.
+      // They must all be in the |kReceiving| state, must not be the sender's
+      // own peer, and must have no slots aside from the default slot.
       DCHECK_EQ(message->num_ports(), attached_port_refs.container().size());
       for (size_t i = 0; i < message->num_ports(); ++i) {
         auto* attached_port = locker.GetPort(attached_port_refs[i]);
-        int error = OK;
-        if (attached_port->state != Port::kReceiving) {
-          error = ERROR_PORT_STATE_UNEXPECTED;
+        if (attached_port->state != Port::kReceiving ||
+            attached_port->slots.size() != 1 ||
+            attached_port->slots.count(kDefaultSlotId) != 1) {
+          return ERROR_PORT_STATE_UNEXPECTED;
         } else if (attached_port_refs[i].name() ==
                    forwarding_port->peer_port_name) {
-          error = ERROR_PORT_CANNOT_SEND_PEER;
-        }
-
-        if (error != OK) {
-          // Not going to send. Backpedal on the sequence number.
-          forwarding_port->next_sequence_num_to_send--;
-          return error;
+          return ERROR_PORT_CANNOT_SEND_PEER;
         }
       }
 
@@ -1168,10 +1322,21 @@
 #if DCHECK_IS_ON()
     DVLOG(4) << "Sending message " << message->sequence_num()
              << " [ports=" << ports_buf.str() << "]"
-             << " from " << forwarding_port_ref.name() << "@" << name_ << " to "
-             << forwarding_port->peer_port_name << "@" << target_node_name;
+             << " from " << forwarding_slot_ref.port().name() << "@" << name_
+             << " to " << forwarding_port->peer_port_name << "@"
+             << target_node_name;
 #endif
 
+    // We're definitely going to send this message, so we can bump the port's
+    // and slot's outgoing sequence number now.
+    Port::Slot* forwarding_slot =
+        forwarding_port->GetSlot(forwarding_slot_ref.slot_id());
+    if (forwarding_slot) {
+      forwarding_slot->last_sequence_num_sent =
+          forwarding_port->next_sequence_num_to_send;
+    }
+    ++forwarding_port->next_sequence_num_to_send;
+
     *forward_to_node = target_node_name;
     message->set_port_name(forwarding_port->peer_port_name);
     break;
@@ -1186,7 +1351,7 @@
       if (descriptor.peer_node_name == name_) {
         PortRef local_peer;
         if (GetPort(descriptor.peer_port_name, &local_peer) == OK)
-          delegate_->PortStatusChanged(local_peer);
+          delegate_->SlotStatusChanged(SlotRef(local_peer, kDefaultSlotId));
       }
     }
   }
@@ -1244,15 +1409,16 @@
     std::unique_ptr<UserMessageEvent> message;
     {
       SinglePortLocker locker(&port_ref);
-      locker.port()->message_queue.GetNextMessage(&message, nullptr);
+      locker.port()->message_queue.GetNextMessage(base::nullopt, &message,
+                                                  nullptr);
       if (!message)
         break;
     }
 
     NodeName target_node;
-    int rv = PrepareToForwardUserMessage(port_ref, Port::kProxying,
-                                         true /* ignore_closed_peer */,
-                                         message.get(), &target_node);
+    int rv = PrepareToForwardUserMessage(
+        SlotRef(port_ref, kDefaultSlotId), Port::kProxying,
+        true /* ignore_closed_peer */, message.get(), &target_node);
     if (rv != OK)
       return rv;
 
@@ -1295,7 +1461,7 @@
     if (!port->remove_proxy_on_last_message)
       return;
 
-    if (!CanAcceptMoreMessages(port)) {
+    if (!CanAcceptMoreMessages(port, kDefaultSlotId)) {
       should_erase = true;
       if (port->send_on_proxy_removal) {
         removal_target_node = port->send_on_proxy_removal->first;
@@ -1395,9 +1561,18 @@
     DVLOG(2) << "Forcibly deleted port " << proxy_name << "@" << name_;
   }
 
-  // Wake up any receiving ports who have just observed simulated peer closure.
-  for (const auto& port : ports_to_notify)
-    delegate_->PortStatusChanged(port);
+  // Wake up any receiving slots who have just observed simulated peer closure.
+  for (const auto& port : ports_to_notify) {
+    base::StackVector<SlotId, 2> slots_to_update;
+    {
+      SinglePortLocker locker(&port);
+      slots_to_update.container().reserve(locker.port()->slots.size());
+      for (const auto& entry : locker.port()->slots)
+        slots_to_update.container().push_back(entry.first);
+    }
+    for (auto slot_id : slots_to_update)
+      delegate_->SlotStatusChanged(SlotRef(port, slot_id));
+  }
 
   for (const auto& proxy_name : dead_proxies_to_broadcast) {
     // Broadcast an event signifying that this proxy is no longer functioning.
@@ -1412,14 +1587,7 @@
     DestroyAllPortsWithPeer(name_, proxy_name);
   }
 
-  // Close any ports referenced by undelivered messages.
-  for (const auto& message : undelivered_messages) {
-    for (size_t i = 0; i < message->num_ports(); ++i) {
-      PortRef ref;
-      if (GetPort(message->ports()[i], &ref) == OK)
-        ClosePort(ref);
-    }
-  }
+  DiscardUnreadMessages(std::move(undelivered_messages));
 }
 
 void Node::UpdatePortPeerAddress(const PortName& local_port_name,
@@ -1489,7 +1657,56 @@
   DCHECK(node_);
 }
 
-Node::DelegateHolder::~DelegateHolder() {}
+void Node::DiscardUnreadMessages(
+    std::vector<std::unique_ptr<UserMessageEvent>> messages) {
+  PortLocker::AssertNoPortsLockedOnCurrentThread();
+  for (const auto& message : messages)
+    DiscardPorts(message.get());
+}
+
+void Node::DiscardPorts(UserMessageEvent* message) {
+  PortLocker::AssertNoPortsLockedOnCurrentThread();
+  for (size_t i = 0; i < message->num_ports(); ++i) {
+    PortRef ref;
+    if (GetPort(message->ports()[i], &ref) == OK)
+      ClosePort(ref);
+  }
+}
+
+base::Optional<SlotId> Node::FlushUnreadableMessages(const PortRef& port_ref) {
+  std::vector<std::unique_ptr<UserMessageEvent>> unread_messages;
+  base::Optional<SlotId> slot_to_notify;
+
+  {
+    SinglePortLocker locker(&port_ref);
+    Port* port = locker.port();
+
+    base::Optional<SlotId> next_message_slot;
+    while ((next_message_slot = port->message_queue.GetNextMessageSlot())) {
+      if (port->GetSlot(*next_message_slot)) {
+        // The next message goes to a valid port slot, leave it in queue and
+        // make sure the slot knows about this.
+        slot_to_notify = *next_message_slot;
+        break;
+      }
+
+      std::vector<std::unique_ptr<UserMessageEvent>> messages;
+      port->message_queue.TakeAllLeadingMessagesForSlot(*next_message_slot,
+                                                        &messages);
+      std::move(messages.begin(), messages.end(),
+                std::back_inserter(unread_messages));
+    }
+  }
+
+  // If we discarded some messages and a new message is now available, notify
+  // its slot that this is the case.
+  if (unread_messages.empty())
+    return base::nullopt;
+
+  return slot_to_notify;
+}
+
+Node::DelegateHolder::~DelegateHolder() = default;
 
 #if DCHECK_IS_ON()
 void Node::DelegateHolder::EnsureSafeDelegateAccess() const {
diff --git a/mojo/core/ports/node.h b/mojo/core/ports/node.h
index 9c771eb2..2e0c24ee 100644
--- a/mojo/core/ports/node.h
+++ b/mojo/core/ports/node.h
@@ -15,11 +15,13 @@
 #include "base/containers/flat_map.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/optional.h"
 #include "base/synchronization/lock.h"
 #include "mojo/core/ports/event.h"
 #include "mojo/core/ports/name.h"
 #include "mojo/core/ports/port.h"
 #include "mojo/core/ports/port_ref.h"
+#include "mojo/core/ports/slot_ref.h"
 #include "mojo/core/ports/user_data.h"
 
 namespace mojo {
@@ -37,7 +39,7 @@
   ERROR_NOT_IMPLEMENTED = -100,
 };
 
-struct PortStatus {
+struct SlotStatus {
   bool has_messages;
   bool receiving_messages;
   bool peer_closed;
@@ -46,6 +48,10 @@
   size_t queued_num_bytes;
 };
 
+// TODO(https://crbug.com/941809): Remove this alias, which only exists to
+// reduce churn while switching Mojo core from ports to slots.
+using PortStatus = SlotStatus;
+
 class MessageFilter;
 class NodeDelegate;
 
@@ -111,17 +117,25 @@
   int SetUserData(const PortRef& port_ref, scoped_refptr<UserData> user_data);
   int GetUserData(const PortRef& port_ref, scoped_refptr<UserData>* user_data);
 
+  // Closes a single slot on port. No more messages can be sent from or
+  // delivered to the slot. If it's the last slot on its port, the port is also
+  // closed.
+  int ClosePortSlot(const SlotRef& slot_ref);
+
   // Prevents further messages from being sent from this port or delivered to
   // this port. The port is removed, and the port's peer is notified of the
   // closure after it has consumed all pending messages.
   int ClosePort(const PortRef& port_ref);
 
-  // Returns the current status of the port.
+  // Returns the current status of the port slot.
+  int GetStatus(const SlotRef& slot_ref, SlotStatus* slot_status);
+
+  // Returns the current status of the default slot on the port.
   int GetStatus(const PortRef& port_ref, PortStatus* port_status);
 
-  // Returns the next available message on the specified port or returns a null
-  // message if there are none available. Returns ERROR_PORT_PEER_CLOSED to
-  // indicate that this port's peer has closed. In such cases GetMessage may
+  // Returns the next available message on the specified port slot or returns a
+  // null message if there are none available. Returns ERROR_PORT_PEER_CLOSED to
+  // indicate that this slot's peer has closed. In such cases GetMessage may
   // be called until it yields a null message, indicating that no more messages
   // may be read from the port.
   //
@@ -130,16 +144,44 @@
   // available message, GetMessage() behaves as if there is no message
   // available. Ownership of |filter| is not taken, and it must outlive the
   // extent of this call.
+  int GetMessage(const SlotRef& slot_ref,
+                 std::unique_ptr<UserMessageEvent>* message,
+                 MessageFilter* filter);
+
+  // TODO(https://crbug.com/941809): Remove this helper, which exists only to
+  // reduce intermediate churn while switching to SlotRef in other places. This
+  // retrieves a message from the default slot of |port_ref|. See above for more
+  // details.
   int GetMessage(const PortRef& port_ref,
                  std::unique_ptr<UserMessageEvent>* message,
                  MessageFilter* filter);
 
-  // Sends a message from the specified port to its peer. Note that the message
-  // notification may arrive synchronously (via PortStatusChanged() on the
-  // delegate) if the peer is local to this Node.
+  // Sends a message from the specified port slot to its peer. Note that the
+  // message notification may arrive synchronously (via SlotStatusChanged() on
+  // the delegate) if the peer is local to this Node.
+  int SendUserMessage(const SlotRef& slot_ref,
+                      std::unique_ptr<UserMessageEvent> message);
+
+  // TODO(https://crbug.com/941809): Remove this helper, which exists only to
+  // reduce intermediate churn while switching to SlotRef in other places. This
+  // sends a message on the default slot of |port_ref|. See above for more
+  // details.
   int SendUserMessage(const PortRef& port_ref,
                       std::unique_ptr<UserMessageEvent> message);
 
+  // Allocates a new slot on the given port and returns its SlotId. Note that
+  // in order to get end-to-end communication on this slot, the port's peer must
+  // also add a slot with the same ID plus |kPeerAllocatedSlotIdBit| set. This
+  // can be achieved by calling AddSlotFromPeer on the peer slot with the same
+  // ID returned by this call.
+  SlotId AllocateSlot(const PortRef& port_ref);
+
+  // Adds a new slot on the given port, corresponding to the port's peer slot
+  // |peer_slot_id|. The local ID of this added slot will always be
+  // |peer_slot_id | kPeerAllocatedSlotIdBit|. Returns |true| iff |port_ref| was
+  // valid and a corresponding slot on the port did not already exist.
+  bool AddSlotFromPeer(const PortRef& port_ref, SlotId peer_slot_id);
+
   // Corresponding to NodeDelegate::ForwardEvent.
   int AcceptEvent(ScopedEvent event);
 
@@ -194,17 +236,23 @@
     DISALLOW_COPY_AND_ASSIGN(DelegateHolder);
   };
 
+  // Closes a specific slot or an entire Port, depending on whether |slot_id|
+  // has a value.
+  int ClosePortOrSlotImpl(const PortRef& port_ref,
+                          base::Optional<SlotId> slot_id);
+
   int OnUserMessage(std::unique_ptr<UserMessageEvent> message);
   int OnPortAccepted(std::unique_ptr<PortAcceptedEvent> event);
   int OnObserveProxy(std::unique_ptr<ObserveProxyEvent> event);
   int OnObserveProxyAck(std::unique_ptr<ObserveProxyAckEvent> event);
   int OnObserveClosure(std::unique_ptr<ObserveClosureEvent> event);
   int OnMergePort(std::unique_ptr<MergePortEvent> event);
+  int OnSlotClosed(std::unique_ptr<SlotClosedEvent> event);
 
   int AddPortWithName(const PortName& port_name, scoped_refptr<Port> port);
   void ErasePort(const PortName& port_name);
 
-  int SendUserMessageInternal(const PortRef& port_ref,
+  int SendUserMessageInternal(const SlotRef& port_ref,
                               std::unique_ptr<UserMessageEvent>* message);
   int MergePortsInternal(const PortRef& port0_ref,
                          const PortRef& port1_ref,
@@ -216,7 +264,7 @@
   int AcceptPort(const PortName& port_name,
                  const Event::PortDescriptor& port_descriptor);
 
-  int PrepareToForwardUserMessage(const PortRef& forwarding_port_ref,
+  int PrepareToForwardUserMessage(const SlotRef& forwarding_slot_ref,
                                   Port::State expected_port_state,
                                   bool ignore_closed_peer,
                                   UserMessageEvent* message,
@@ -249,6 +297,27 @@
                      const PortName& port1_name,
                      Port* port1);
 
+  // Safely discards a collection of UserMessageEvent objects which may contain
+  // unclaimed local Port references. Ensures that any such ports are properly
+  // cleaned up.
+  void DiscardUnreadMessages(
+      std::vector<std::unique_ptr<UserMessageEvent>> messages);
+
+  // Closes all ports carried by a message. If a message is being discarded
+  // without anyone reading it, its carried ports cannot possibly be useful.
+  // Discarding them avoids leaking memory.
+  void DiscardPorts(UserMessageEvent* message);
+
+  // Flushes any unreadable messages for dead slots on |port_ref|. This is
+  // called any time a port's MessageQueue is changed in a way that might make
+  // a new message available (e.g. a slot is closed, or a message is read).
+  //
+  // If this returns a valid SlotId, the port's MessageQueue was modified by
+  // this call, the next message in the queue is now available, and the message
+  // targets the returned slot. Returns nullopt if either the queue was
+  // unchanged by the call or the next message in queue is not available yet.
+  base::Optional<SlotId> FlushUnreadableMessages(const PortRef& port_ref);
+
   const NodeName name_;
   const DelegateHolder delegate_;
 
diff --git a/mojo/core/ports/node_delegate.h b/mojo/core/ports/node_delegate.h
index afe1c4c..9415af9 100644
--- a/mojo/core/ports/node_delegate.h
+++ b/mojo/core/ports/node_delegate.h
@@ -25,10 +25,10 @@
   // Broadcast an event to all nodes.
   virtual void BroadcastEvent(ScopedEvent event) = 0;
 
-  // Indicates that the port's status has changed recently. Use Node::GetStatus
-  // to query the latest status of the port. Note, this event could be spurious
-  // if another thread is simultaneously modifying the status of the port.
-  virtual void PortStatusChanged(const PortRef& port_ref) = 0;
+  // Indicates that the slot's status has changed recently. Use Node::GetStatus
+  // to query the latest status of the slot. Note, this event could be spurious
+  // if another thread is simultaneously modifying the status of the slot.
+  virtual void SlotStatusChanged(const SlotRef& slot_ref) = 0;
 };
 
 }  // namespace ports
diff --git a/mojo/core/ports/port.cc b/mojo/core/ports/port.cc
index 7186979..c6e136d 100644
--- a/mojo/core/ports/port.cc
+++ b/mojo/core/ports/port.cc
@@ -19,6 +19,27 @@
 
 Port::~Port() {}
 
+Port::Slot* Port::GetSlot(SlotId slot_id) {
+  auto it = slots.find(slot_id);
+  if (it == slots.end())
+    return nullptr;
+  return &it->second;
+}
+
+SlotId Port::AllocateSlot() {
+  DCHECK_EQ(state, kReceiving);
+  SlotId id = ++last_allocated_slot_id;
+  CHECK_EQ(id & kPeerAllocatedSlotIdBit, 0u);
+  slots.emplace(id, Slot{});
+  return id;
+}
+
+bool Port::AddSlotFromPeer(SlotId peer_slot_id) {
+  if (state != kReceiving || (peer_slot_id & kPeerAllocatedSlotIdBit) != 0)
+    return false;
+  return slots.emplace(peer_slot_id | kPeerAllocatedSlotIdBit, Slot{}).second;
+}
+
 }  // namespace ports
 }  // namespace core
 }  // namespace mojo
diff --git a/mojo/core/ports/port.h b/mojo/core/ports/port.h
index d1a825e2..cba3382 100644
--- a/mojo/core/ports/port.h
+++ b/mojo/core/ports/port.h
@@ -5,13 +5,15 @@
 #ifndef MOJO_CORE_PORTS_PORT_H_
 #define MOJO_CORE_PORTS_PORT_H_
 
+#include <map>
 #include <memory>
-#include <queue>
+#include <set>
 #include <utility>
 #include <vector>
 
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/optional.h"
 #include "base/synchronization/lock.h"
 #include "mojo/core/ports/event.h"
 #include "mojo/core/ports/message_queue.h"
@@ -107,7 +109,7 @@
   PortName peer_port_name;
 
   // The next available sequence number to use for outgoing user message events
-  // originating from this port.
+  // originating from any slot on this port.
   uint64_t next_sequence_num_to_send;
 
   // The sequence number of the last message this Port should ever expect to
@@ -148,9 +150,36 @@
   // non-zero cyclic routing distance) receiving Port has been closed.
   bool peer_closed;
 
+  // The next available slot ID to allocate for a new slot on this port.
+  SlotId last_allocated_slot_id = kDefaultSlotId;
+
+  // Structure for status related to a single slot of this port.
+  struct Slot {
+    // Indicates that the slot can signal the embedder about available messages.
+    bool can_signal = true;
+
+    // Indicates that the peer slot for this slot is closed.
+    bool peer_closed = false;
+
+    // The last sequence number expected for this slot to receive if the peer is
+    // closed.
+    uint64_t last_sequence_num_to_receive;
+
+    // The last sequence number sent on this slot. Will always be less than
+    // the Port's own |next_sequence_num_to_send|.
+    uint64_t last_sequence_num_sent;
+  };
+
+  // Status information for each slot on this port.
+  std::map<SlotId, Slot> slots;
+
   Port(uint64_t next_sequence_num_to_send,
        uint64_t next_sequence_num_to_receive);
 
+  Slot* GetSlot(SlotId slot_id);
+  SlotId AllocateSlot();
+  bool AddSlotFromPeer(SlotId peer_slot_id);
+
   void AssertLockAcquired() {
 #if DCHECK_IS_ON()
     lock_.AssertAcquired();
diff --git a/mojo/core/ports/ports_unittest.cc b/mojo/core/ports/ports_unittest.cc
index e3a2a085..3e8b20d 100644
--- a/mojo/core/ports/ports_unittest.cc
+++ b/mojo/core/ports/ports_unittest.cc
@@ -139,6 +139,10 @@
     return node_.SendUserMessage(port, NewUserMessageEvent(s, 0));
   }
 
+  int SendStringMessage(const SlotRef& slot, const std::string& s) {
+    return node_.SendUserMessage(slot, NewUserMessageEvent(s, 0));
+  }
+
   int SendStringMessageWithPort(const PortRef& port,
                                 const std::string& s,
                                 const PortName& sent_port_name) {
@@ -167,6 +171,10 @@
     return node_.GetMessage(port, message, nullptr) == OK && *message;
   }
 
+  bool ReadMessage(const SlotRef& slot, ScopedMessage* message) {
+    return node_.GetMessage(slot, message, nullptr) == OK && *message;
+  }
+
   bool GetSavedMessage(ScopedMessage* message) {
     base::AutoLock lock(lock_);
     if (saved_messages_.empty()) {
@@ -210,7 +218,7 @@
     router_->BroadcastEvent(this, std::move(event));
   }
 
-  void PortStatusChanged(const PortRef& port) override {
+  void SlotStatusChanged(const SlotRef& slot) override {
     // The port may be closed, in which case we ignore the notification.
     base::AutoLock lock(lock_);
     if (!save_messages_)
@@ -220,7 +228,7 @@
       ScopedMessage message;
       {
         base::AutoUnlock unlock(lock_);
-        if (!ReadMessage(port, &message))
+        if (!ReadMessage(slot.port(), &message))
           break;
       }
 
@@ -1638,6 +1646,411 @@
   EXPECT_EQ(OK, node0.node().ClosePort(b));
 }
 
+TEST_F(PortsTest, BasicSlotUsage) {
+  TestNode node0(0);
+  AddNode(&node0);
+
+  PortRef a, b;
+  node0.node().CreatePortPair(&a, &b);
+
+  SlotId slot_id = node0.node().AllocateSlot(a);
+  ASSERT_TRUE(node0.node().AddSlotFromPeer(b, slot_id));
+
+  // Test the default slot.
+  const char* kMessage1 = "hey";
+  ScopedMessage message;
+  EXPECT_EQ(OK, node0.SendStringMessage(a, kMessage1));
+  ASSERT_TRUE(node0.ReadMessage(b, &message));
+  EXPECT_TRUE(MessageEquals(message, kMessage1));
+
+  // Test our newly added slot.
+  const char* kMessage2 = "hey again";
+  EXPECT_EQ(OK, node0.SendStringMessage(SlotRef(a, slot_id), kMessage2));
+  ASSERT_TRUE(node0.ReadMessage(SlotRef(b, slot_id | kPeerAllocatedSlotIdBit),
+                                &message));
+  EXPECT_TRUE(MessageEquals(message, kMessage2));
+
+  // Also test it in the reverse direction.
+  const char* kMessage3 = "hey one more time";
+  EXPECT_EQ(OK, node0.SendStringMessage(
+                    SlotRef(b, slot_id | kPeerAllocatedSlotIdBit), kMessage3));
+  ASSERT_TRUE(node0.ReadMessage(SlotRef(a, slot_id), &message));
+  EXPECT_TRUE(MessageEquals(message, kMessage3));
+
+  EXPECT_EQ(OK, node0.node().ClosePort(a));
+  EXPECT_EQ(OK, node0.node().ClosePort(b));
+}
+
+TEST_F(PortsTest, MultipleSlots) {
+  TestNode node0(0);
+  AddNode(&node0);
+
+  PortRef a, b;
+  node0.node().CreatePortPair(&a, &b);
+
+  SlotId slot_id1 = node0.node().AllocateSlot(a);
+  ASSERT_TRUE(node0.node().AddSlotFromPeer(b, slot_id1));
+
+  SlotId slot_id2 = node0.node().AllocateSlot(a);
+  ASSERT_TRUE(node0.node().AddSlotFromPeer(b, slot_id2));
+
+  // Test our newly added slots.
+  const char* kMessage1 = "hey";
+  ScopedMessage message;
+  EXPECT_EQ(OK, node0.SendStringMessage(SlotRef(a, slot_id1), kMessage1));
+  ASSERT_TRUE(node0.ReadMessage(SlotRef(b, slot_id1 | kPeerAllocatedSlotIdBit),
+                                &message));
+  EXPECT_TRUE(MessageEquals(message, kMessage1));
+
+  const char* kMessage2 = "hey again";
+  EXPECT_EQ(OK, node0.SendStringMessage(SlotRef(a, slot_id2), kMessage2));
+  ASSERT_TRUE(node0.ReadMessage(SlotRef(b, slot_id2 | kPeerAllocatedSlotIdBit),
+                                &message));
+  EXPECT_TRUE(MessageEquals(message, kMessage2));
+
+  EXPECT_EQ(OK, node0.node().ClosePort(a));
+  EXPECT_EQ(OK, node0.node().ClosePort(b));
+}
+
+TEST_F(PortsTest, RemoteSlotUsage) {
+  TestNode node0(0);
+  AddNode(&node0);
+
+  TestNode node1(1);
+  AddNode(&node1);
+
+  PortRef a, b;
+  EXPECT_EQ(OK, node0.node().CreateUninitializedPort(&a));
+  EXPECT_EQ(OK, node1.node().CreateUninitializedPort(&b));
+  EXPECT_EQ(OK, node0.node().InitializePort(a, node1.name(), b.name()));
+  EXPECT_EQ(OK, node1.node().InitializePort(b, node0.name(), a.name()));
+
+  SlotId slot_id = node0.node().AllocateSlot(a);
+  ASSERT_TRUE(node1.node().AddSlotFromPeer(b, slot_id));
+
+  // Test the default slot.
+  const char* kMessage1 = "hey";
+  ScopedMessage message;
+  EXPECT_EQ(OK, node0.SendStringMessage(a, kMessage1));
+  WaitForIdle();
+  ASSERT_TRUE(node1.ReadMessage(b, &message));
+  EXPECT_TRUE(MessageEquals(message, kMessage1));
+
+  // Test our newly added slot.
+  const char* kMessage2 = "hey again";
+  EXPECT_EQ(OK, node0.SendStringMessage(SlotRef(a, slot_id), kMessage2));
+  WaitForIdle();
+  ASSERT_TRUE(node1.ReadMessage(SlotRef(b, slot_id | kPeerAllocatedSlotIdBit),
+                                &message));
+  EXPECT_TRUE(MessageEquals(message, kMessage2));
+
+  // Also test it in the reverse direction.
+  const char* kMessage3 = "hey one more time";
+  EXPECT_EQ(OK, node1.SendStringMessage(
+                    SlotRef(b, slot_id | kPeerAllocatedSlotIdBit), kMessage3));
+  WaitForIdle();
+  ASSERT_TRUE(node0.ReadMessage(SlotRef(a, slot_id), &message));
+  EXPECT_TRUE(MessageEquals(message, kMessage3));
+
+  EXPECT_EQ(OK, node0.node().ClosePort(a));
+  EXPECT_EQ(OK, node1.node().ClosePort(b));
+}
+
+TEST_F(PortsTest, SlotsStrictOrdering) {
+  TestNode node0(0);
+  AddNode(&node0);
+
+  TestNode node1(1);
+  AddNode(&node1);
+
+  PortRef a, b;
+  EXPECT_EQ(OK, node0.node().CreateUninitializedPort(&a));
+  EXPECT_EQ(OK, node1.node().CreateUninitializedPort(&b));
+  EXPECT_EQ(OK, node0.node().InitializePort(a, node1.name(), b.name()));
+  EXPECT_EQ(OK, node1.node().InitializePort(b, node0.name(), a.name()));
+
+  SlotId slot_id1 = node0.node().AllocateSlot(a);
+  ASSERT_TRUE(node1.node().AddSlotFromPeer(b, slot_id1));
+  SlotId slot_id2 = node0.node().AllocateSlot(a);
+  ASSERT_TRUE(node1.node().AddSlotFromPeer(b, slot_id2));
+
+  const char* kMessage1 = "hey";
+  EXPECT_EQ(OK, node0.SendStringMessage(a, kMessage1));
+
+  const char* kMessage2 = "hey again";
+  EXPECT_EQ(OK, node0.SendStringMessage(SlotRef(a, slot_id1), kMessage2));
+
+  const char* kMessage3 = "hey one more time";
+  EXPECT_EQ(OK, node0.SendStringMessage(SlotRef(a, slot_id2), kMessage3));
+
+  const char* kMessage4 = "last hey";
+  EXPECT_EQ(OK, node0.SendStringMessage(a, kMessage4));
+
+  WaitForIdle();
+
+  // Verify that we can only observe the received messages in precise order,
+  // despite spanning many slot endpoints.
+  ScopedMessage message;
+  EXPECT_FALSE(node1.ReadMessage(SlotRef(b, slot_id1 | kPeerAllocatedSlotIdBit),
+                                 &message));
+  EXPECT_FALSE(node1.ReadMessage(SlotRef(b, slot_id2 | kPeerAllocatedSlotIdBit),
+                                 &message));
+  ASSERT_TRUE(node1.ReadMessage(b, &message));
+  EXPECT_TRUE(MessageEquals(message, kMessage1));
+
+  EXPECT_FALSE(node1.ReadMessage(b, &message));
+  EXPECT_FALSE(node1.ReadMessage(SlotRef(b, slot_id2 | kPeerAllocatedSlotIdBit),
+                                 &message));
+  ASSERT_TRUE(node1.ReadMessage(SlotRef(b, slot_id1 | kPeerAllocatedSlotIdBit),
+                                &message));
+  EXPECT_TRUE(MessageEquals(message, kMessage2));
+
+  EXPECT_FALSE(node1.ReadMessage(b, &message));
+  EXPECT_FALSE(node1.ReadMessage(SlotRef(b, slot_id1 | kPeerAllocatedSlotIdBit),
+                                 &message));
+  ASSERT_TRUE(node1.ReadMessage(SlotRef(b, slot_id2 | kPeerAllocatedSlotIdBit),
+                                &message));
+  EXPECT_TRUE(MessageEquals(message, kMessage3));
+
+  EXPECT_FALSE(node1.ReadMessage(SlotRef(b, slot_id1 | kPeerAllocatedSlotIdBit),
+                                 &message));
+  EXPECT_FALSE(node1.ReadMessage(SlotRef(b, slot_id2 | kPeerAllocatedSlotIdBit),
+                                 &message));
+  ASSERT_TRUE(node1.ReadMessage(b, &message));
+  EXPECT_TRUE(MessageEquals(message, kMessage4));
+
+  EXPECT_EQ(OK, node0.node().ClosePort(a));
+  EXPECT_EQ(OK, node1.node().ClosePort(b));
+}
+
+TEST_F(PortsTest, ClosedSlotDiscardsNewMessages) {
+  TestNode node0(0);
+  AddNode(&node0);
+
+  TestNode node1(1);
+  AddNode(&node1);
+
+  PortRef a, b;
+  EXPECT_EQ(OK, node0.node().CreateUninitializedPort(&a));
+  EXPECT_EQ(OK, node1.node().CreateUninitializedPort(&b));
+  EXPECT_EQ(OK, node0.node().InitializePort(a, node1.name(), b.name()));
+  EXPECT_EQ(OK, node1.node().InitializePort(b, node0.name(), a.name()));
+
+  SlotId slot_id1 = node0.node().AllocateSlot(a);
+  ASSERT_TRUE(node1.node().AddSlotFromPeer(b, slot_id1));
+
+  const char* kMessage1 = "message 1";
+  EXPECT_EQ(OK, node0.SendStringMessage(a, kMessage1));
+
+  node1.node().ClosePortSlot(SlotRef(b, slot_id1 | kPeerAllocatedSlotIdBit));
+
+  const char* kMessage2 = "message 2";
+  EXPECT_EQ(OK, node0.SendStringMessage(SlotRef(a, slot_id1), kMessage2));
+
+  const char* kMessage3 = "message 3";
+  EXPECT_EQ(OK, node0.SendStringMessage(a, kMessage3));
+
+  WaitForIdle();
+
+  ScopedMessage message;
+  ASSERT_TRUE(node1.ReadMessage(b, &message));
+  EXPECT_TRUE(MessageEquals(message, kMessage1));
+
+  ASSERT_TRUE(node1.ReadMessage(b, &message));
+  EXPECT_TRUE(MessageEquals(message, kMessage3));
+
+  EXPECT_EQ(OK, node0.node().ClosePort(a));
+  EXPECT_EQ(OK, node1.node().ClosePort(b));
+}
+
+TEST_F(PortsTest, ClosedSlotDiscardsQueuedMessages) {
+  TestNode node0(0);
+  AddNode(&node0);
+
+  TestNode node1(1);
+  AddNode(&node1);
+
+  PortRef a, b;
+  EXPECT_EQ(OK, node0.node().CreateUninitializedPort(&a));
+  EXPECT_EQ(OK, node1.node().CreateUninitializedPort(&b));
+  EXPECT_EQ(OK, node0.node().InitializePort(a, node1.name(), b.name()));
+  EXPECT_EQ(OK, node1.node().InitializePort(b, node0.name(), a.name()));
+
+  SlotId slot_id1 = node0.node().AllocateSlot(a);
+  ASSERT_TRUE(node1.node().AddSlotFromPeer(b, slot_id1));
+
+  const char* kMessage1 = "message 1";
+  EXPECT_EQ(OK, node0.SendStringMessage(a, kMessage1));
+
+  const char* kMessage2 = "message 2";
+  EXPECT_EQ(OK, node0.SendStringMessage(SlotRef(a, slot_id1), kMessage2));
+
+  const char* kMessage3 = "message 3";
+  EXPECT_EQ(OK, node0.SendStringMessage(a, kMessage3));
+
+  WaitForIdle();
+
+  node1.node().ClosePortSlot(SlotRef(b, slot_id1 | kPeerAllocatedSlotIdBit));
+
+  ScopedMessage message;
+  ASSERT_TRUE(node1.ReadMessage(b, &message));
+  EXPECT_TRUE(MessageEquals(message, kMessage1));
+
+  ASSERT_TRUE(node1.ReadMessage(b, &message));
+  EXPECT_TRUE(MessageEquals(message, kMessage3));
+
+  EXPECT_EQ(OK, node0.node().ClosePort(a));
+  EXPECT_EQ(OK, node1.node().ClosePort(b));
+}
+
+TEST_F(PortsTest, CanCloseDefaultSlot) {
+  TestNode node0(0);
+  AddNode(&node0);
+
+  TestNode node1(1);
+  AddNode(&node1);
+
+  PortRef a, b;
+  EXPECT_EQ(OK, node0.node().CreateUninitializedPort(&a));
+  EXPECT_EQ(OK, node1.node().CreateUninitializedPort(&b));
+  EXPECT_EQ(OK, node0.node().InitializePort(a, node1.name(), b.name()));
+  EXPECT_EQ(OK, node1.node().InitializePort(b, node0.name(), a.name()));
+
+  SlotId slot_id1 = node0.node().AllocateSlot(a);
+  ASSERT_TRUE(node1.node().AddSlotFromPeer(b, slot_id1));
+
+  const char* kMessage1 = "message 1";
+  EXPECT_EQ(OK, node0.SendStringMessage(a, kMessage1));
+
+  const char* kMessage2 = "message 2";
+  EXPECT_EQ(OK, node0.SendStringMessage(SlotRef(a, slot_id1), kMessage2));
+
+  const char* kMessage3 = "message 3";
+  EXPECT_EQ(OK, node0.SendStringMessage(a, kMessage3));
+
+  WaitForIdle();
+
+  node1.node().ClosePortSlot(SlotRef(b, kDefaultSlotId));
+
+  ScopedMessage message;
+  EXPECT_FALSE(node1.ReadMessage(b, &message));
+  ASSERT_TRUE(node1.ReadMessage(SlotRef(b, slot_id1 | kPeerAllocatedSlotIdBit),
+                                &message));
+  EXPECT_TRUE(MessageEquals(message, kMessage2));
+  EXPECT_FALSE(node1.ReadMessage(b, &message));
+
+  EXPECT_EQ(OK, node0.node().ClosePort(a));
+  EXPECT_EQ(OK, node1.node().ClosePort(b));
+}
+
+TEST_F(PortsTest, ClosingAllSlotsClosesPort) {
+  TestNode node0(0);
+  AddNode(&node0);
+
+  TestNode node1(1);
+  AddNode(&node1);
+
+  PortRef a, b;
+  EXPECT_EQ(OK, node0.node().CreateUninitializedPort(&a));
+  EXPECT_EQ(OK, node1.node().CreateUninitializedPort(&b));
+  EXPECT_EQ(OK, node0.node().InitializePort(a, node1.name(), b.name()));
+  EXPECT_EQ(OK, node1.node().InitializePort(b, node0.name(), a.name()));
+
+  SlotId slot_id1 = node0.node().AllocateSlot(a);
+  ASSERT_TRUE(node1.node().AddSlotFromPeer(b, slot_id1));
+
+  node0.node().ClosePortSlot(SlotRef(a, slot_id1));
+  node0.node().ClosePortSlot(SlotRef(a, kDefaultSlotId));
+
+  node1.node().ClosePortSlot(SlotRef(b, slot_id1 | kPeerAllocatedSlotIdBit));
+  node1.node().ClosePortSlot(SlotRef(b, kDefaultSlotId));
+
+  EXPECT_EQ(ERROR_PORT_UNKNOWN, node1.node().GetPort(a.name(), &a));
+  EXPECT_EQ(ERROR_PORT_UNKNOWN, node1.node().GetPort(b.name(), &b));
+}
+
+TEST_F(PortsTest, SlotPeerClosureDetectedSequentially) {
+  // This test verifies that when a slot is closed its peer can still read
+  // messages up to the point in the sequence where closure occurred.
+
+  TestNode node0(0);
+  AddNode(&node0);
+
+  TestNode node1(1);
+  AddNode(&node1);
+
+  PortRef a, b;
+  EXPECT_EQ(OK, node0.node().CreateUninitializedPort(&a));
+  EXPECT_EQ(OK, node1.node().CreateUninitializedPort(&b));
+  EXPECT_EQ(OK, node0.node().InitializePort(a, node1.name(), b.name()));
+  EXPECT_EQ(OK, node1.node().InitializePort(b, node0.name(), a.name()));
+
+  SlotId slot_id1 = node0.node().AllocateSlot(a);
+  ASSERT_TRUE(node1.node().AddSlotFromPeer(b, slot_id1));
+
+  const char* kMessage1 = "message 1";
+  EXPECT_EQ(OK, node0.SendStringMessage(a, kMessage1));
+
+  const char* kMessage2 = "message 2";
+  EXPECT_EQ(OK, node0.SendStringMessage(SlotRef(a, slot_id1), kMessage2));
+
+  node0.node().ClosePortSlot(SlotRef(a, slot_id1));
+
+  const char* kMessage3 = "message 3";
+  EXPECT_EQ(OK, node0.SendStringMessage(a, kMessage3));
+
+  WaitForIdle();
+
+  // |slot1_id|'s peer in |b| should still appear to be receiving messages
+  // despite |slot_id| being closed in |a|. This is because the system expects
+  // the slot in |b| to have at least one more message currently or imminently
+  // in queue.
+  SlotStatus status;
+  EXPECT_EQ(OK, node1.node().GetStatus(
+                    SlotRef(b, slot_id1 | kPeerAllocatedSlotIdBit), &status));
+  EXPECT_TRUE(status.peer_closed);
+  EXPECT_TRUE(status.receiving_messages);
+  EXPECT_FALSE(status.has_messages);
+
+  // Sanity check the default slot's status while we're here too.
+  EXPECT_EQ(OK, node1.node().GetStatus(SlotRef(b, kDefaultSlotId), &status));
+  EXPECT_FALSE(status.peer_closed);
+  EXPECT_TRUE(status.receiving_messages);
+  EXPECT_TRUE(status.has_messages);
+
+  ScopedMessage message;
+
+  EXPECT_FALSE(node1.ReadMessage(SlotRef(b, slot_id1 | kPeerAllocatedSlotIdBit),
+                                 &message));
+  ASSERT_TRUE(node1.ReadMessage(b, &message));
+  EXPECT_TRUE(MessageEquals(message, kMessage1));
+
+  // The queued message for this slot should now be readable. The peer still
+  // appears to be closed.
+  EXPECT_EQ(OK, node1.node().GetStatus(
+                    SlotRef(b, slot_id1 | kPeerAllocatedSlotIdBit), &status));
+  EXPECT_TRUE(status.peer_closed);
+  EXPECT_TRUE(status.receiving_messages);
+  EXPECT_TRUE(status.has_messages);
+
+  EXPECT_FALSE(node1.ReadMessage(b, &message));
+  ASSERT_TRUE(node1.ReadMessage(SlotRef(b, slot_id1 | kPeerAllocatedSlotIdBit),
+                                &message));
+  EXPECT_TRUE(MessageEquals(message, kMessage2));
+
+  // Now that the last message has been read from the slot, the slot should
+  // appear to no longer have or be receiving new messages. In conjunction with
+  // peer closure this implies the slot will never receive messages again.
+  EXPECT_EQ(OK, node1.node().GetStatus(
+                    SlotRef(b, slot_id1 | kPeerAllocatedSlotIdBit), &status));
+  EXPECT_TRUE(status.peer_closed);
+  EXPECT_FALSE(status.receiving_messages);
+  EXPECT_FALSE(status.has_messages);
+
+  EXPECT_EQ(OK, node0.node().ClosePort(a));
+  EXPECT_EQ(OK, node1.node().ClosePort(b));
+}
+
 }  // namespace test
 }  // namespace ports
 }  // namespace core
diff --git a/mojo/core/ports/slot_ref.cc b/mojo/core/ports/slot_ref.cc
new file mode 100644
index 0000000..255db366
--- /dev/null
+++ b/mojo/core/ports/slot_ref.cc
@@ -0,0 +1,28 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/core/ports/slot_ref.h"
+
+namespace mojo {
+namespace core {
+namespace ports {
+
+SlotRef::SlotRef() = default;
+
+SlotRef::SlotRef(const PortRef& port, SlotId slot_id)
+    : port_(port), slot_id_(slot_id) {}
+
+SlotRef::~SlotRef() = default;
+
+SlotRef::SlotRef(const SlotRef&) = default;
+
+SlotRef::SlotRef(SlotRef&&) = default;
+
+SlotRef& SlotRef::operator=(const SlotRef&) = default;
+
+SlotRef& SlotRef::operator=(SlotRef&&) = default;
+
+}  // namespace ports
+}  // namespace core
+}  // namespace mojo
diff --git a/mojo/core/ports/slot_ref.h b/mojo/core/ports/slot_ref.h
new file mode 100644
index 0000000..4a17f98
--- /dev/null
+++ b/mojo/core/ports/slot_ref.h
@@ -0,0 +1,43 @@
+// Copyright 2019 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 MOJO_CORE_PORTS_SLOT_REF_H_
+#define MOJO_CORE_PORTS_SLOT_REF_H_
+
+#include "base/component_export.h"
+#include "mojo/core/ports/event.h"
+#include "mojo/core/ports/port_ref.h"
+
+namespace mojo {
+namespace core {
+namespace ports {
+
+// A reference to a specific slot on a specific port.
+class COMPONENT_EXPORT(MOJO_CORE_PORTS) SlotRef {
+ public:
+  SlotRef();
+  SlotRef(const PortRef& port, SlotId slot_id);
+  ~SlotRef();
+
+  SlotRef(const SlotRef& other);
+  SlotRef(SlotRef&& other);
+
+  SlotRef& operator=(const SlotRef& other);
+  SlotRef& operator=(SlotRef&& other);
+
+  const PortRef& port() const { return port_; }
+  SlotId slot_id() const { return slot_id_; }
+
+  bool is_valid() const { return port_.is_valid(); }
+
+ private:
+  PortRef port_;
+  SlotId slot_id_;
+};
+
+}  // namespace ports
+}  // namespace core
+}  // namespace mojo
+
+#endif  // MOJO_CORE_PORTS_SLOT_REF_H_
diff --git a/net/base/port_util.cc b/net/base/port_util.cc
index ea3e30b..f6ab41c8 100644
--- a/net/base/port_util.cc
+++ b/net/base/port_util.cc
@@ -107,7 +107,7 @@
   return port >= 0 && port < 1024;
 }
 
-bool IsPortAllowedForScheme(int port, const std::string& url_scheme) {
+bool IsPortAllowedForScheme(int port, base::StringPiece url_scheme) {
   // Reject invalid ports.
   if (!IsPortValid(port))
     return false;
diff --git a/net/base/port_util.h b/net/base/port_util.h
index d1cc5fe..72d4b39 100644
--- a/net/base/port_util.h
+++ b/net/base/port_util.h
@@ -10,6 +10,7 @@
 #include <string>
 
 #include "base/macros.h"
+#include "base/strings/string_piece.h"
 #include "net/base/net_export.h"
 
 namespace net {
@@ -25,7 +26,7 @@
 // Checks if the port is allowed for the specified scheme.  Ports set as allowed
 // with SetExplicitlyAllowedPorts() or by using ScopedPortException() will be
 // considered allowed for any scheme.
-NET_EXPORT bool IsPortAllowedForScheme(int port, const std::string& url_scheme);
+NET_EXPORT bool IsPortAllowedForScheme(int port, base::StringPiece url_scheme);
 
 // Returns the number of explicitly allowed ports; for testing.
 NET_EXPORT_PRIVATE size_t GetCountOfExplicitlyAllowedPorts();
diff --git a/net/disk_cache/blockfile/entry_impl.h b/net/disk_cache/blockfile/entry_impl.h
index 867b24f2..616673e 100644
--- a/net/disk_cache/blockfile/entry_impl.h
+++ b/net/disk_cache/blockfile/entry_impl.h
@@ -295,6 +295,9 @@
   // Logs this entry to the internal trace buffer.
   void Log(const char* msg);
 
+  // |net_log_| should be early since some field destructors (at least
+  // ~SparseControl) can touch it.
+  net::NetLogWithSource net_log_;
   CacheEntryBlock entry_;     // Key related information for this entry.
   CacheRankingsBlock node_;   // Rankings related information for this entry.
   base::WeakPtr<BackendImpl> backend_;  // Back pointer to the cache.
@@ -309,8 +312,6 @@
   bool dirty_;                // True if we detected that this is a dirty entry.
   std::unique_ptr<SparseControl> sparse_;  // Support for sparse entries.
 
-  net::NetLogWithSource net_log_;
-
   DISALLOW_COPY_AND_ASSIGN(EntryImpl);
 };
 
diff --git a/net/disk_cache/entry_unittest.cc b/net/disk_cache/entry_unittest.cc
index 3bc8f26..2fe639b 100644
--- a/net/disk_cache/entry_unittest.cc
+++ b/net/disk_cache/entry_unittest.cc
@@ -5208,6 +5208,36 @@
   entry2->Close();
 }
 
+TEST_F(DiskCacheEntryTest, BlockFileSparsePendingAfterDtor) {
+  // Test of behavior of ~EntryImpl for sparse entry that runs after backend
+  // destruction.
+  //
+  // Hand-creating the backend for realistic shutdown behavior.
+  CleanupCacheDir();
+  CreateBackend(disk_cache::kNone);
+
+  disk_cache::Entry* entry = nullptr;
+  ASSERT_THAT(CreateEntry("key", &entry), IsOk());
+  ASSERT_TRUE(entry != nullptr);
+
+  const int kSize = 61184;
+
+  scoped_refptr<net::IOBuffer> buf = base::MakeRefCounted<net::IOBuffer>(kSize);
+  CacheTestFillBuffer(buf->data(), kSize, false);
+
+  // The write pattern here avoids the second write being handled by the
+  // buffering layer, making SparseControl have to deal with its asynchrony.
+  EXPECT_EQ(1, WriteSparseData(entry, 65535, buf.get(), 1));
+  EXPECT_EQ(net::ERR_IO_PENDING,
+            entry->WriteSparseData(2560, buf.get(), kSize, base::DoNothing()));
+  entry->Close();
+  cache_.reset();
+
+  // Create a new instance as a way of flushing the thread.
+  InitCache();
+  FlushQueueForTest();
+}
+
 class DiskCacheSimplePrefetchTest : public DiskCacheEntryTest {
  public:
   DiskCacheSimplePrefetchTest()
diff --git a/net/http/http_stream_factory_job.cc b/net/http/http_stream_factory_job.cc
index 6932f03..2dcc8c3 100644
--- a/net/http/http_stream_factory_job.cc
+++ b/net/http/http_stream_factory_job.cc
@@ -708,7 +708,7 @@
 
   // Don't connect to restricted ports.
   if (!IsPortAllowedForScheme(destination_.port(),
-                              request_info_.url.scheme())) {
+                              request_info_.url.scheme_piece())) {
     return ERR_UNSAFE_PORT;
   }
 
diff --git a/net/nqe/network_quality_estimator_params.cc b/net/nqe/network_quality_estimator_params.cc
index ca44e02..6b036c0 100644
--- a/net/nqe/network_quality_estimator_params.cc
+++ b/net/nqe/network_quality_estimator_params.cc
@@ -501,7 +501,7 @@
           GetStringValueForVariationParamWithDefaultValue(
               params_,
               "cap_ect_based_on_signal_strength",
-              "false") == "true"),
+              "true") != "false"),
       upper_bound_typical_kbps_multiplier_(
           GetDoubleValueForVariationParamWithDefaultValue(
               params_,
diff --git a/net/quic/quic_stream_factory.cc b/net/quic/quic_stream_factory.cc
index 1f445ccd..506b040 100644
--- a/net/quic/quic_stream_factory.cc
+++ b/net/quic/quic_stream_factory.cc
@@ -575,6 +575,7 @@
 
   if (fresh_resolve_host_request_) {
     DCHECK(race_stale_dns_on_connection_);
+    dns_resolution_end_time_ = base::TimeTicks::Now();
     if (rv != OK) {
       CloseStaleHostConnection();
       resolve_host_request_ = std::move(fresh_resolve_host_request_);
diff --git a/net/socket/udp_socket_unittest.cc b/net/socket/udp_socket_unittest.cc
index 94f59034..17f7d11 100644
--- a/net/socket/udp_socket_unittest.cc
+++ b/net/socket/udp_socket_unittest.cc
@@ -682,7 +682,13 @@
 
 #if !defined(OS_FUCHSIA)
 // TODO(https://crbug.com/900709): SO_REUSEPORT doesn't work on Fuchsia.
-TEST_F(UDPSocketTest, SharedMulticastAddress) {
+#if defined(OS_IOS)
+// TODO(https://crbug.com/947115): failing on device on iOS 12.2.
+#define MAYBE_SharedMulticastAddress DISABLED_SharedMulticastAddress
+#else
+#define MAYBE_SharedMulticastAddress SharedMulticastAddress
+#endif
+TEST_F(UDPSocketTest, MAYBE_SharedMulticastAddress) {
   const char kGroup[] = "224.0.0.251";
 
   IPAddress group_ip;
diff --git a/net/url_request/ftp_protocol_handler.cc b/net/url_request/ftp_protocol_handler.cc
index f8b4b56..9334ca7 100644
--- a/net/url_request/ftp_protocol_handler.cc
+++ b/net/url_request/ftp_protocol_handler.cc
@@ -36,7 +36,7 @@
   DCHECK_EQ("ftp", request->url().scheme());
 
   if (!IsPortAllowedForScheme(request->url().EffectiveIntPort(),
-                              request->url().scheme())) {
+                              request->url().scheme_piece())) {
     return new URLRequestErrorJob(request, network_delegate, ERR_UNSAFE_PORT);
   }
 
diff --git a/remoting/signaling/BUILD.gn b/remoting/signaling/BUILD.gn
index 971d7a9c..613a72e0 100644
--- a/remoting/signaling/BUILD.gn
+++ b/remoting/signaling/BUILD.gn
@@ -90,6 +90,7 @@
 
 cc_grpc_library("ftl_grpc_library") {
   sources = [
+    "chromoting_message.proto",
     "ftl.proto",
     "ftl_services.proto",
   ]
diff --git a/remoting/signaling/chromoting_message.proto b/remoting/signaling/chromoting_message.proto
new file mode 100644
index 0000000..de29884
--- /dev/null
+++ b/remoting/signaling/chromoting_message.proto
@@ -0,0 +1,21 @@
+// Copyright 2019 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.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package remoting.ftl;
+
+// Next Id: 2
+message ChromotingMessage {
+  oneof message { ChromotingXmppMessage xmpp = 1; }
+}
+
+// Chromoting Legacy XMPP message.
+// Next Id: 2
+message ChromotingXmppMessage {
+  // A serialized version of the IQ stanza.
+  optional string stanza = 1;
+}
diff --git a/remoting/signaling/ftl.proto b/remoting/signaling/ftl.proto
index 97b4755..9cbba67 100644
--- a/remoting/signaling/ftl.proto
+++ b/remoting/signaling/ftl.proto
@@ -150,10 +150,6 @@
   Id receiver_id = 2;
 }
 
-message ChromotingMessage {
-  string message = 1;
-}
-
 // Requests and responses
 
 message GetICEServerRequest {
diff --git a/remoting/signaling/ftl_message_reception_channel.cc b/remoting/signaling/ftl_message_reception_channel.cc
index ef436d0..e1a2237 100644
--- a/remoting/signaling/ftl_message_reception_channel.cc
+++ b/remoting/signaling/ftl_message_reception_channel.cc
@@ -66,12 +66,15 @@
   on_incoming_msg_ = on_incoming_msg;
 }
 
-void FtlMessageReceptionChannel::StartReceivingMessages(DoneCallback on_done) {
+void FtlMessageReceptionChannel::StartReceivingMessages(
+    base::OnceClosure on_ready,
+    DoneCallback on_closed) {
+  stream_closed_callbacks_.push_back(std::move(on_closed));
   if (state_ == State::STARTED) {
-    std::move(on_done).Run(grpc::Status::OK);
+    std::move(on_ready).Run();
     return;
   }
-  start_receiving_messages_callbacks_.push_back(std::move(on_done));
+  stream_ready_callbacks_.push_back(std::move(on_ready));
   if (state_ == State::STARTING) {
     return;
   }
@@ -83,7 +86,7 @@
     return;
   }
   StopReceivingMessagesInternal();
-  RunStartReceivingMessagesCallbacks(grpc::Status::CANCELLED);
+  RunStreamClosedCallbacks(grpc::Status::CANCELLED);
 }
 
 const net::BackoffEntry&
@@ -106,7 +109,7 @@
     return;
   }
   StopReceivingMessagesInternal();
-  RunStartReceivingMessagesCallbacks(status);
+  RunStreamClosedCallbacks(status);
 }
 
 void FtlMessageReceptionChannel::OnMessageReceived(
@@ -123,7 +126,7 @@
       break;
     case ftl::ReceiveMessagesResponse::BodyCase::kStartOfBatch:
       state_ = State::STARTED;
-      RunStartReceivingMessagesCallbacks(grpc::Status::OK);
+      RunStreamReadyCallbacks();
       BeginStreamTimers();
       break;
     case ftl::ReceiveMessagesResponse::BodyCase::kEndOfBatch:
@@ -135,15 +138,25 @@
   }
 }
 
-void FtlMessageReceptionChannel::RunStartReceivingMessagesCallbacks(
-    const grpc::Status& status) {
-  if (start_receiving_messages_callbacks_.empty()) {
+void FtlMessageReceptionChannel::RunStreamReadyCallbacks() {
+  if (stream_ready_callbacks_.empty()) {
     return;
   }
-  for (DoneCallback& callback : start_receiving_messages_callbacks_) {
+  for (base::OnceClosure& callback : stream_ready_callbacks_) {
+    std::move(callback).Run();
+  }
+  stream_ready_callbacks_.clear();
+}
+
+void FtlMessageReceptionChannel::RunStreamClosedCallbacks(
+    const grpc::Status& status) {
+  if (stream_closed_callbacks_.empty()) {
+    return;
+  }
+  for (DoneCallback& callback : stream_closed_callbacks_) {
     std::move(callback).Run(status);
   }
-  start_receiving_messages_callbacks_.clear();
+  stream_closed_callbacks_.clear();
 }
 
 void FtlMessageReceptionChannel::RetryStartReceivingMessagesWithBackoff() {
diff --git a/remoting/signaling/ftl_message_reception_channel.h b/remoting/signaling/ftl_message_reception_channel.h
index 37e2ed1..5c4731b9 100644
--- a/remoting/signaling/ftl_message_reception_channel.h
+++ b/remoting/signaling/ftl_message_reception_channel.h
@@ -36,7 +36,8 @@
   // MessageReceptionChannel implementations.
   void Initialize(const StreamOpener& stream_opener,
                   const MessageCallback& on_incoming_msg) override;
-  void StartReceivingMessages(DoneCallback on_done) override;
+  void StartReceivingMessages(base::OnceClosure on_ready,
+                              DoneCallback on_closed) override;
   void StopReceivingMessages() override;
 
   const net::BackoffEntry& GetReconnectRetryBackoffEntryForTesting() const;
@@ -57,7 +58,8 @@
   void OnReceiveMessagesStreamClosed(const grpc::Status& status);
   void OnMessageReceived(const ftl::ReceiveMessagesResponse& response);
 
-  void RunStartReceivingMessagesCallbacks(const grpc::Status& status);
+  void RunStreamReadyCallbacks();
+  void RunStreamClosedCallbacks(const grpc::Status& status);
   void RetryStartReceivingMessagesWithBackoff();
   void RetryStartReceivingMessages();
   void StartReceivingMessagesInternal();
@@ -70,7 +72,8 @@
   StreamOpener stream_opener_;
   MessageCallback on_incoming_msg_;
   std::unique_ptr<ScopedGrpcServerStream> receive_messages_stream_;
-  std::list<DoneCallback> start_receiving_messages_callbacks_;
+  std::list<base::OnceClosure> stream_ready_callbacks_;
+  std::list<DoneCallback> stream_closed_callbacks_;
   State state_ = State::STOPPED;
   net::BackoffEntry reconnect_retry_backoff_;
   base::OneShotTimer reconnect_retry_timer_;
diff --git a/remoting/signaling/ftl_message_reception_channel_unittest.cc b/remoting/signaling/ftl_message_reception_channel_unittest.cc
index 63f32fb7..00bee86c 100644
--- a/remoting/signaling/ftl_message_reception_channel_unittest.cc
+++ b/remoting/signaling/ftl_message_reception_channel_unittest.cc
@@ -64,11 +64,6 @@
   return response;
 }
 
-base::OnceCallback<void(const grpc::Status& status)> AssertOkCallback() {
-  return base::BindOnce(
-      [](const grpc::Status& status) { ASSERT_TRUE(status.ok()); });
-}
-
 // Creates a gmock EXPECT_CALL action that:
 //   1. Creates a fake server stream and returns it as the start stream result
 //   2. Posts a task to call |on_stream_opened| at the end of current sequence
@@ -97,6 +92,14 @@
   return StartStream(on_stream_opened, nullptr);
 }
 
+base::OnceClosure NotReachedClosure() {
+  return base::BindOnce([]() { NOTREACHED(); });
+}
+
+base::RepeatingCallback<void(const grpc::Status&)> NotReachedStatusCallback() {
+  return base::BindRepeating([](const grpc::Status&) { NOTREACHED(); });
+}
+
 }  // namespace
 
 class FtlMessageReceptionChannelTest : public testing::Test {
@@ -148,8 +151,10 @@
             channel_->StopReceivingMessages();
           }));
 
-  channel_->StartReceivingMessages(test::CheckStatusThenQuitRunLoopCallback(
-      FROM_HERE, grpc::StatusCode::CANCELLED, &run_loop));
+  channel_->StartReceivingMessages(
+      NotReachedClosure(),
+      test::CheckStatusThenQuitRunLoopCallback(
+          FROM_HERE, grpc::StatusCode::CANCELLED, &run_loop));
 
   run_loop.Run();
 }
@@ -166,8 +171,10 @@
                 .Run(grpc::Status(grpc::StatusCode::UNAUTHENTICATED, ""));
           }));
 
-  channel_->StartReceivingMessages(test::CheckStatusThenQuitRunLoopCallback(
-      FROM_HERE, grpc::StatusCode::UNAUTHENTICATED, &run_loop));
+  channel_->StartReceivingMessages(
+      NotReachedClosure(),
+      test::CheckStatusThenQuitRunLoopCallback(
+          FROM_HERE, grpc::StatusCode::UNAUTHENTICATED, &run_loop));
 
   run_loop.Run();
 }
@@ -183,8 +190,8 @@
             on_incoming_msg.Run(CreateStartOfBatchResponse());
           }));
 
-  channel_->StartReceivingMessages(test::CheckStatusThenQuitRunLoopCallback(
-      FROM_HERE, grpc::StatusCode::OK, &run_loop));
+  channel_->StartReceivingMessages(run_loop.QuitClosure(),
+                                   NotReachedStatusCallback());
 
   run_loop.Run();
 }
@@ -227,8 +234,8 @@
             ASSERT_EQ(0, GetRetryFailureCount());
           }));
 
-  channel_->StartReceivingMessages(test::CheckStatusThenQuitRunLoopCallback(
-      FROM_HERE, grpc::StatusCode::OK, &run_loop));
+  channel_->StartReceivingMessages(run_loop.QuitClosure(),
+                                   NotReachedStatusCallback());
 
   run_loop.Run();
 }
@@ -237,15 +244,13 @@
        TestStartReceivingMessages_MultipleCalls) {
   base::RunLoop run_loop;
 
-  base::MockCallback<FtlMessageReceptionChannel::DoneCallback>
-      start_receiving_messages_callback;
+  base::MockCallback<base::OnceClosure> stream_ready_callback;
 
   // Exits the run loop iff the callback is called three times with OK.
-  EXPECT_CALL(start_receiving_messages_callback,
-              Run(Property(&grpc::Status::error_code, grpc::StatusCode::OK)))
+  EXPECT_CALL(stream_ready_callback, Run())
       .WillOnce(Return())
       .WillOnce(Return())
-      .WillOnce(Invoke([&](const grpc::Status& status) { run_loop.Quit(); }));
+      .WillOnce([&]() { run_loop.Quit(); });
 
   EXPECT_CALL(mock_stream_opener_, Run(_, _))
       .WillOnce(StartStream(
@@ -254,9 +259,12 @@
             on_incoming_msg.Run(CreateStartOfBatchResponse());
           }));
 
-  channel_->StartReceivingMessages(start_receiving_messages_callback.Get());
-  channel_->StartReceivingMessages(start_receiving_messages_callback.Get());
-  channel_->StartReceivingMessages(start_receiving_messages_callback.Get());
+  channel_->StartReceivingMessages(stream_ready_callback.Get(),
+                                   NotReachedStatusCallback());
+  channel_->StartReceivingMessages(stream_ready_callback.Get(),
+                                   NotReachedStatusCallback());
+  channel_->StartReceivingMessages(stream_ready_callback.Get(),
+                                   NotReachedStatusCallback());
 
   run_loop.Run();
 }
@@ -297,7 +305,9 @@
             std::move(on_channel_closed).Run(grpc::Status::OK);
           }));
 
-  channel_->StartReceivingMessages(AssertOkCallback());
+  channel_->StartReceivingMessages(
+      base::DoNothing(), test::CheckStatusThenQuitRunLoopCallback(
+                             FROM_HERE, grpc::StatusCode::OK, &run_loop));
 
   run_loop.Run();
 }
@@ -338,7 +348,8 @@
             run_loop.Quit();
           }));
 
-  channel_->StartReceivingMessages(AssertOkCallback());
+  channel_->StartReceivingMessages(base::DoNothing(),
+                                   NotReachedStatusCallback());
 
   run_loop.Run();
 }
@@ -380,7 +391,8 @@
             run_loop.Quit();
           }));
 
-  channel_->StartReceivingMessages(AssertOkCallback());
+  channel_->StartReceivingMessages(base::DoNothing(),
+                                   NotReachedStatusCallback());
 
   run_loop.Run();
 }
@@ -426,7 +438,8 @@
             scoped_task_environment_.FastForwardBy(time_until_retry);
           }));
 
-  channel_->StartReceivingMessages(AssertOkCallback());
+  channel_->StartReceivingMessages(base::DoNothing(),
+                                   NotReachedStatusCallback());
 
   run_loop.Run();
 }
diff --git a/remoting/signaling/ftl_messaging_client.cc b/remoting/signaling/ftl_messaging_client.cc
index da06bfc..249ab05 100644
--- a/remoting/signaling/ftl_messaging_client.cc
+++ b/remoting/signaling/ftl_messaging_client.cc
@@ -84,7 +84,7 @@
 void FtlMessagingClient::SendMessage(
     const std::string& destination,
     const std::string& destination_registration_id,
-    const std::string& message_text,
+    const ftl::ChromotingMessage& message,
     DoneCallback on_done) {
   ftl::InboxSendRequest request;
   *request.mutable_header() = FtlGrpcContext::CreateRequestHeader(
@@ -93,10 +93,8 @@
   // TODO(yuweih): See if we need to set requester_id
   *request.mutable_dest_id() = FtlGrpcContext::CreateIdFromString(destination);
 
-  ftl::ChromotingMessage crd_message;
-  crd_message.set_message(message_text);
   std::string serialized_message;
-  bool succeeded = crd_message.SerializeToString(&serialized_message);
+  bool succeeded = message.SerializeToString(&serialized_message);
   DCHECK(succeeded);
 
   request.mutable_message()->set_message(serialized_message);
@@ -118,8 +116,10 @@
   executor_->ExecuteRpc(std::move(grpc_request));
 }
 
-void FtlMessagingClient::StartReceivingMessages(DoneCallback on_done) {
-  reception_channel_->StartReceivingMessages(std::move(on_done));
+void FtlMessagingClient::StartReceivingMessages(base::OnceClosure on_ready,
+                                                DoneCallback on_closed) {
+  reception_channel_->StartReceivingMessages(std::move(on_ready),
+                                             std::move(on_closed));
 }
 
 void FtlMessagingClient::StopReceivingMessages() {
@@ -213,8 +213,7 @@
   ftl::ChromotingMessage chromoting_message;
   chromoting_message.ParseFromString(message.message());
   callback_list_.Notify(message.sender_id().id(),
-                        message.sender_registration_id(),
-                        chromoting_message.message());
+                        message.sender_registration_id(), chromoting_message);
 }
 
 void FtlMessagingClient::OnMessageReceived(const ftl::InboxMessage& message) {
diff --git a/remoting/signaling/ftl_messaging_client.h b/remoting/signaling/ftl_messaging_client.h
index 1b175abe..e5d526a 100644
--- a/remoting/signaling/ftl_messaging_client.h
+++ b/remoting/signaling/ftl_messaging_client.h
@@ -11,6 +11,7 @@
 #include "base/callback_forward.h"
 #include "base/callback_list.h"
 #include "base/macros.h"
+#include "remoting/signaling/chromoting_message.pb.h"
 #include "remoting/signaling/ftl_services.grpc.pb.h"
 
 namespace remoting {
@@ -27,11 +28,12 @@
   using MessageCallback =
       base::RepeatingCallback<void(const std::string& sender_id,
                                    const std::string& sender_registration_id,
-                                   const std::string& message)>;
-  using MessageCallbackSubscription =
+                                   const ftl::ChromotingMessage& message)>;
+  using MessageCallbackList =
       base::CallbackList<void(const std::string&,
                               const std::string&,
-                              const std::string&)>::Subscription;
+                              const ftl::ChromotingMessage&)>;
+  using MessageCallbackSubscription = MessageCallbackList::Subscription;
   using DoneCallback = base::OnceCallback<void(const grpc::Status& status)>;
 
   // |token_getter| and |registration_manager| must outlive |this|.
@@ -52,14 +54,17 @@
   void PullMessages(DoneCallback on_done);
   void SendMessage(const std::string& destination,
                    const std::string& destination_registration_id,
-                   const std::string& message_text,
+                   const ftl::ChromotingMessage& message,
                    DoneCallback on_done);
 
   // Opens a stream to continuously receive new messages from the server and
   // calls the registered MessageCallback once a new message is received.
-  // |on_done| is called once the stream is successfully opened or failed to
-  // open due to an error.
-  void StartReceivingMessages(DoneCallback on_done);
+  // |on_ready| is called once the stream is successfully started.
+  // |on_closed| is called if the stream fails to start, in which case
+  // |on_ready| will not be called, or when the stream is closed or dropped,
+  // in which case it is called after |on_ready| is called.
+  void StartReceivingMessages(base::OnceClosure on_ready,
+                              DoneCallback on_closed);
 
   // Stops the stream for continuously receiving new messages.
   void StopReceivingMessages();
@@ -102,9 +107,7 @@
   RegistrationManager* registration_manager_;
   std::unique_ptr<Messaging::Stub> messaging_stub_;
   std::unique_ptr<MessageReceptionChannel> reception_channel_;
-  base::CallbackList<
-      void(const std::string&, const std::string&, const std::string&)>
-      callback_list_;
+  MessageCallbackList callback_list_;
 
   DISALLOW_COPY_AND_ASSIGN(FtlMessagingClient);
 };
diff --git a/remoting/signaling/ftl_messaging_client_unittest.cc b/remoting/signaling/ftl_messaging_client_unittest.cc
index f51badfd..b6020591 100644
--- a/remoting/signaling/ftl_messaging_client_unittest.cc
+++ b/remoting/signaling/ftl_messaging_client_unittest.cc
@@ -37,6 +37,7 @@
 using ::testing::Invoke;
 using ::testing::Property;
 using ::testing::Return;
+using ::testing::Truly;
 
 using PullMessagesResponder =
     test::GrpcServerResponder<ftl::PullMessagesResponse>;
@@ -51,16 +52,21 @@
 constexpr char kMessage1Text[] = "Message 1";
 constexpr char kMessage2Text[] = "Message 2";
 
-ftl::InboxMessage CreateMessage(const std::string& message_id,
-                                const std::string& message_text) {
+ftl::ChromotingMessage CreateXmppMessage(const std::string& message_text) {
+  ftl::ChromotingMessage crd_message;
+  crd_message.mutable_xmpp()->set_stanza(message_text);
+  return crd_message;
+}
+
+ftl::InboxMessage CreateInboxMessage(const std::string& message_id,
+                                     const std::string& message_text) {
   ftl::InboxMessage message;
   message.mutable_sender_id()->set_id(kFakeSenderId);
   message.mutable_receiver_id()->set_id(kFakeReceiverId);
   message.set_sender_registration_id(kFakeSenderRegId);
   message.set_message_type(ftl::InboxMessage_MessageType_CHROMOTING_MESSAGE);
   message.set_message_id(message_id);
-  ftl::ChromotingMessage crd_message;
-  crd_message.set_message(message_text);
+  ftl::ChromotingMessage crd_message = CreateXmppMessage(message_text);
   std::string serialized_message;
   bool succeeded = crd_message.SerializeToString(&serialized_message);
   EXPECT_TRUE(succeeded);
@@ -73,7 +79,7 @@
             message.message_type());
   ftl::ChromotingMessage chromoting_message;
   chromoting_message.ParseFromString(message.message());
-  return chromoting_message.message();
+  return chromoting_message.xmpp().stanza();
 }
 
 class MockMessageReceptionChannel : public MessageReceptionChannel {
@@ -88,7 +94,7 @@
     on_incoming_msg_ = on_incoming_msg;
   }
 
-  MOCK_METHOD1(StartReceivingMessages, void(DoneCallback));
+  MOCK_METHOD2(StartReceivingMessages, void(base::OnceClosure, DoneCallback));
   MOCK_METHOD0(StopReceivingMessages, void());
 
   StreamOpener* stream_opener() { return &stream_opener_; }
@@ -113,6 +119,12 @@
   MOCK_CONST_METHOD0(GetFtlAuthToken, std::string());
 };
 
+decltype(auto) StanzaTextMatches(const std::string& expected_stanza) {
+  return Truly([=](const ftl::ChromotingMessage& message) {
+    return expected_stanza == message.xmpp().stanza();
+  });
+}
+
 }  // namespace
 
 class FtlMessagingClientTest : public testing::Test {
@@ -222,10 +234,11 @@
 
 TEST_F(FtlMessagingClientTest, TestPullMessages_ReturnsNoMessage) {
   base::RunLoop run_loop;
-  auto subscription = messaging_client_->RegisterMessageCallback(
-      base::BindRepeating([](const std::string& sender_id,
-                             const std::string& sender_registration_id,
-                             const std::string& message) { NOTREACHED(); }));
+  auto subscription =
+      messaging_client_->RegisterMessageCallback(base::BindRepeating(
+          [](const std::string& sender_id,
+             const std::string& sender_registration_id,
+             const ftl::ChromotingMessage& message) { NOTREACHED(); }));
   messaging_client_->PullMessages(test::CheckStatusThenQuitRunLoopCallback(
       FROM_HERE, grpc::StatusCode::OK, &run_loop));
   ServerWaitAndRespondToPullMessagesRequest(ftl::PullMessagesResponse(),
@@ -235,10 +248,11 @@
 
 TEST_F(FtlMessagingClientTest, TestPullMessages_Unauthenticated) {
   base::RunLoop run_loop;
-  auto subscription = messaging_client_->RegisterMessageCallback(
-      base::BindRepeating([](const std::string& sender_id,
-                             const std::string& sender_registration_id,
-                             const std::string& message) { NOTREACHED(); }));
+  auto subscription =
+      messaging_client_->RegisterMessageCallback(base::BindRepeating(
+          [](const std::string& sender_id,
+             const std::string& sender_registration_id,
+             const ftl::ChromotingMessage& message) { NOTREACHED(); }));
   messaging_client_->PullMessages(test::CheckStatusThenQuitRunLoopCallback(
       FROM_HERE, grpc::StatusCode::UNAUTHENTICATED, &run_loop));
   ServerWaitAndRespondToPullMessagesRequest(
@@ -250,10 +264,11 @@
 TEST_F(FtlMessagingClientTest, TestPullMessages_IgnoresUnknownMessageType) {
   base::RunLoop run_loop;
 
-  auto subscription = messaging_client_->RegisterMessageCallback(
-      base::BindRepeating([](const std::string& sender_id,
-                             const std::string& sender_registration_id,
-                             const std::string& message) { NOTREACHED(); }));
+  auto subscription =
+      messaging_client_->RegisterMessageCallback(base::BindRepeating(
+          [](const std::string& sender_id,
+             const std::string& sender_registration_id,
+             const ftl::ChromotingMessage& message) { NOTREACHED(); }));
 
   messaging_client_->PullMessages(test::CheckStatusThenQuitRunLoopCallback(
       FROM_HERE, grpc::StatusCode::OK, &run_loop));
@@ -281,11 +296,11 @@
 
   base::MockCallback<FtlMessagingClient::MessageCallback> mock_on_incoming_msg;
 
-  EXPECT_CALL(mock_on_incoming_msg,
-              Run(kFakeSenderId, kFakeSenderRegId, kMessage1Text))
+  EXPECT_CALL(mock_on_incoming_msg, Run(kFakeSenderId, kFakeSenderRegId,
+                                        StanzaTextMatches(kMessage1Text)))
       .WillOnce(Return());
-  EXPECT_CALL(mock_on_incoming_msg,
-              Run(kFakeSenderId, kFakeSenderRegId, kMessage2Text))
+  EXPECT_CALL(mock_on_incoming_msg, Run(kFakeSenderId, kFakeSenderRegId,
+                                        StanzaTextMatches(kMessage2Text)))
       .WillOnce(Return());
 
   auto subscription =
@@ -296,9 +311,9 @@
 
   ftl::PullMessagesResponse pull_messages_response;
   ftl::InboxMessage* message = pull_messages_response.add_messages();
-  *message = CreateMessage(kMessage1Id, kMessage1Text);
+  *message = CreateInboxMessage(kMessage1Id, kMessage1Text);
   message = pull_messages_response.add_messages();
-  *message = CreateMessage(kMessage2Id, kMessage2Text);
+  *message = CreateInboxMessage(kMessage2Id, kMessage2Text);
   ServerWaitAndRespondToPullMessagesRequest(pull_messages_response,
                                             grpc::Status::OK);
 
@@ -319,7 +334,7 @@
 TEST_F(FtlMessagingClientTest, TestSendMessage_Unauthenticated) {
   base::RunLoop run_loop;
   messaging_client_->SendMessage(
-      kFakeReceiverId, kFakeSenderRegId, kMessage1Text,
+      kFakeReceiverId, kFakeSenderRegId, CreateXmppMessage(kMessage1Text),
       test::CheckStatusThenQuitRunLoopCallback(
           FROM_HERE, grpc::StatusCode::UNAUTHENTICATED, &run_loop));
   ServerWaitAndRespondToInboxSendRequest(
@@ -334,7 +349,7 @@
 TEST_F(FtlMessagingClientTest, TestSendMessage_SendOneMessageWithoutRegId) {
   base::RunLoop run_loop;
   messaging_client_->SendMessage(
-      kFakeReceiverId, "", kMessage1Text,
+      kFakeReceiverId, "", CreateXmppMessage(kMessage1Text),
       test::CheckStatusThenQuitRunLoopCallback(FROM_HERE, grpc::StatusCode::OK,
                                                &run_loop));
   ServerWaitAndRespondToInboxSendRequest(
@@ -353,7 +368,7 @@
 TEST_F(FtlMessagingClientTest, TestSendMessage_SendOneMessageWithRegId) {
   base::RunLoop run_loop;
   messaging_client_->SendMessage(
-      kFakeReceiverId, kFakeSenderRegId, kMessage1Text,
+      kFakeReceiverId, kFakeSenderRegId, CreateXmppMessage(kMessage1Text),
       test::CheckStatusThenQuitRunLoopCallback(FROM_HERE, grpc::StatusCode::OK,
                                                &run_loop));
   ServerWaitAndRespondToInboxSendRequest(
@@ -370,17 +385,22 @@
   run_loop.Run();
 }
 
-TEST_F(FtlMessagingClientTest,
-       TestStartReceivingMessages_DoneCallbackForwarded) {
+TEST_F(FtlMessagingClientTest, TestStartReceivingMessages_CallbacksForwarded) {
   base::RunLoop run_loop;
 
-  EXPECT_CALL(*mock_message_reception_channel_, StartReceivingMessages(_))
-      .WillOnce(Invoke([&](FtlMessagingClient::DoneCallback callback) {
-        std::move(callback).Run(
+  EXPECT_CALL(*mock_message_reception_channel_, StartReceivingMessages(_, _))
+      .WillOnce(Invoke([&](base::OnceClosure on_ready,
+                           FtlMessagingClient::DoneCallback on_closed) {
+        std::move(on_ready).Run();
+        std::move(on_closed).Run(
             grpc::Status(grpc::StatusCode::UNAUTHENTICATED, ""));
       }));
 
+  base::MockCallback<base::OnceClosure> mock_on_ready_closure;
+  EXPECT_CALL(mock_on_ready_closure, Run()).WillOnce(Return());
+
   messaging_client_->StartReceivingMessages(
+      mock_on_ready_closure.Get(),
       base::BindLambdaForTesting([&](const grpc::Status& status) {
         ASSERT_EQ(grpc::StatusCode::UNAUTHENTICATED, status.error_code());
         run_loop.Quit();
@@ -452,7 +472,7 @@
        TestOnMessageReceived_MessagePassedToSubscriberAndAcked) {
   base::RunLoop run_loop;
 
-  ftl::InboxMessage message = CreateMessage(kMessage1Id, kMessage1Text);
+  ftl::InboxMessage message = CreateInboxMessage(kMessage1Id, kMessage1Text);
   mock_message_reception_channel_->on_incoming_msg()->Run(message);
 
   ServerWaitAndRespondToAckMessagesRequest(
diff --git a/remoting/signaling/message_reception_channel.h b/remoting/signaling/message_reception_channel.h
index 910dcffc..962f8fe 100644
--- a/remoting/signaling/message_reception_channel.h
+++ b/remoting/signaling/message_reception_channel.h
@@ -37,9 +37,13 @@
                           const MessageCallback& on_incoming_msg) = 0;
 
   // Opens a server streaming channel to the FTL API to enable message reception
-  // over the fast path. |on_done| is called to signal success or failure for
-  // starting the stream.
-  virtual void StartReceivingMessages(DoneCallback on_done) = 0;
+  // over the fast path.
+  // |on_ready| is called once the stream is successfully started.
+  // |on_closed| is called if the stream fails to start, in which case
+  // |on_ready| will not be called, or when the stream is closed or dropped,
+  // in which case it is called after |on_ready| is called.
+  virtual void StartReceivingMessages(base::OnceClosure on_ready,
+                                      DoneCallback on_closed) = 0;
 
   // Closes the streaming channel.
   virtual void StopReceivingMessages() = 0;
diff --git a/remoting/test/ftl_services_playground.cc b/remoting/test/ftl_services_playground.cc
index 8bd7d337..da4da5f 100644
--- a/remoting/test/ftl_services_playground.cc
+++ b/remoting/test/ftl_services_playground.cc
@@ -251,8 +251,10 @@
                                     weak_factory_.GetWeakPtr(), receiver_id,
                                     registration_id, std::move(on_done));
 
+  ftl::ChromotingMessage crd_message;
+  crd_message.mutable_xmpp()->set_stanza(message);
   messaging_client_->SendMessage(
-      receiver_id, registration_id, message,
+      receiver_id, registration_id, crd_message,
       base::BindOnce(&FtlServicesPlayground::OnSendMessageResponse,
                      weak_factory_.GetWeakPtr(), std::move(on_continue)));
 }
@@ -272,9 +274,12 @@
 
 void FtlServicesPlayground::StartReceivingMessages(base::OnceClosure on_done) {
   VLOG(0) << "Running StartReceivingMessages...";
+  receive_messages_done_callback_ = std::move(on_done);
   messaging_client_->StartReceivingMessages(
-      base::BindOnce(&FtlServicesPlayground::OnStartReceivingMessagesDone,
-                     weak_factory_.GetWeakPtr(), std::move(on_done)));
+      base::BindOnce(&FtlServicesPlayground::OnReceiveMessagesStreamReady,
+                     weak_factory_.GetWeakPtr()),
+      base::BindOnce(&FtlServicesPlayground::OnReceiveMessagesStreamClosed,
+                     weak_factory_.GetWeakPtr()));
 }
 
 void FtlServicesPlayground::StopReceivingMessages(base::OnceClosure on_done) {
@@ -285,32 +290,48 @@
 void FtlServicesPlayground::OnMessageReceived(
     const std::string& sender_id,
     const std::string& sender_registration_id,
-    const std::string& message) {
+    const ftl::ChromotingMessage& message) {
+  std::string message_text = message.xmpp().stanza();
   printf(
       "Received message:\n"
       "  Sender ID=%s\n"
       "  Sender Registration ID=%s\n"
       "  Message=%s\n",
-      sender_id.c_str(), sender_registration_id.c_str(), message.c_str());
+      sender_id.c_str(), sender_registration_id.c_str(), message_text.c_str());
 }
 
-void FtlServicesPlayground::OnStartReceivingMessagesDone(
-    base::OnceClosure on_done,
+void FtlServicesPlayground::OnReceiveMessagesStreamReady() {
+  printf("Started receiving messages. Press enter to stop streaming...\n");
+  test::WaitForEnterKey(base::BindOnce(
+      &FtlServicesPlayground::StopReceivingMessages, weak_factory_.GetWeakPtr(),
+      std::move(receive_messages_done_callback_)));
+}
+
+void FtlServicesPlayground::OnReceiveMessagesStreamClosed(
     const grpc::Status& status) {
+  base::OnceClosure callback = std::move(receive_messages_done_callback_);
+  bool is_callback_null = callback.is_null();
+  if (is_callback_null) {
+    callback = base::DoNothing::Once();
+  }
   if (status.error_code() == grpc::StatusCode::CANCELLED) {
     printf("ReceiveMessages stream canceled by client.\n");
-    std::move(on_done).Run();
+    std::move(callback).Run();
     return;
   }
 
   if (!status.ok()) {
-    HandleGrpcStatusError(std::move(on_done), status);
-    return;
+    HandleGrpcStatusError(std::move(callback), status);
+  } else {
+    printf("Stream closed by server.\n");
+    std::move(callback).Run();
   }
-  printf("Started receiving messages. Press enter to stop streaming...\n");
-  test::WaitForEnterKey(
-      base::BindOnce(&FtlServicesPlayground::StopReceivingMessages,
-                     weak_factory_.GetWeakPtr(), std::move(on_done)));
+
+  if (is_callback_null) {
+    // Stream had been started and callback has been passed to wait for the
+    // enter key.
+    printf("Please press enter to continue...\n");
+  }
 }
 
 void FtlServicesPlayground::HandleGrpcStatusError(base::OnceClosure on_done,
diff --git a/remoting/test/ftl_services_playground.h b/remoting/test/ftl_services_playground.h
index 563d615..8b10970 100644
--- a/remoting/test/ftl_services_playground.h
+++ b/remoting/test/ftl_services_playground.h
@@ -66,9 +66,9 @@
   void StopReceivingMessages(base::OnceClosure on_done);
   void OnMessageReceived(const std::string& sender_id,
                          const std::string& sender_registration_id,
-                         const std::string& message);
-  void OnStartReceivingMessagesDone(base::OnceClosure on_done,
-                                    const grpc::Status& status);
+                         const ftl::ChromotingMessage& message);
+  void OnReceiveMessagesStreamReady();
+  void OnReceiveMessagesStreamClosed(const grpc::Status& status);
 
   void HandleGrpcStatusError(base::OnceClosure on_done,
                              const grpc::Status& status);
@@ -86,6 +86,8 @@
 
   std::unique_ptr<PeerToPeer::Stub> peer_to_peer_stub_;
 
+  base::OnceClosure receive_messages_done_callback_;
+
   base::WeakPtrFactory<FtlServicesPlayground> weak_factory_;
   DISALLOW_COPY_AND_ASSIGN(FtlServicesPlayground);
 };
diff --git a/services/tracing/BUILD.gn b/services/tracing/BUILD.gn
index fde436c..f03baab 100644
--- a/services/tracing/BUILD.gn
+++ b/services/tracing/BUILD.gn
@@ -2,8 +2,6 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//build/config/chromecast_build.gni")
-
 # There should be only one tracing service. It is currently
 # in the browser process. So, only //content/browser should link to this target.
 # Others modules should only need the public targets.
@@ -45,10 +43,6 @@
   deps = [
     "//third_party/perfetto/protos/perfetto/trace/chrome:minimal_complete_lite",
   ]
-
-  if (is_chromecast) {
-    defines = [ "IS_CHROMECAST" ]
-  }
 }
 
 source_set("manifest") {
@@ -123,8 +117,4 @@
       "//third_party/perfetto/protos/perfetto/trace/track_event:lite",
     ]
   }
-
-  if (is_chromecast) {
-    defines = [ "IS_CHROMECAST" ]
-  }
 }
diff --git a/services/tracing/perfetto/consumer_host.cc b/services/tracing/perfetto/consumer_host.cc
index e72cfe8..1726aee 100644
--- a/services/tracing/perfetto/consumer_host.cc
+++ b/services/tracing/perfetto/consumer_host.cc
@@ -4,6 +4,7 @@
 
 #include "services/tracing/perfetto/consumer_host.h"
 
+#include <algorithm>
 #include <cstring>
 #include <string>
 #include <tuple>
@@ -18,16 +19,21 @@
 #include "build/build_config.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "mojo/public/cpp/system/wait.h"
+#include "services/tracing/perfetto/json_trace_exporter.h"
 #include "services/tracing/perfetto/perfetto_service.h"
+#include "services/tracing/perfetto/track_event_json_exporter.h"
 #include "third_party/perfetto/include/perfetto/tracing/core/observable_events.h"
 #include "third_party/perfetto/include/perfetto/tracing/core/trace_config.h"
 #include "third_party/perfetto/include/perfetto/tracing/core/trace_packet.h"
+#include "third_party/perfetto/include/perfetto/tracing/core/trace_stats.h"
 #include "third_party/perfetto/protos/perfetto/config/trace_config.pb.h"
 
 namespace tracing {
 
 namespace {
 
+const int32_t kEnableTracingTimeoutSeconds = 10;
+
 bool StringToProcessId(const std::string& input, base::ProcessId* output) {
   // Pid is encoded as uint in the string.
   return base::StringToUint(input, reinterpret_cast<uint32_t*>(output));
@@ -84,8 +90,10 @@
 
   tracing_session_ = std::move(tracing_session);
 
+  perfetto::TraceConfig trace_config_copy = AdjustTraceConfig(trace_config);
+
   filtered_pids_.clear();
-  for (const auto& ds_config : trace_config.data_sources()) {
+  for (const auto& ds_config : trace_config_copy.data_sources()) {
     if (ds_config.config().name() == mojom::kTraceEventDataSourceName) {
       for (const auto& filter : ds_config.producer_name_filter()) {
         base::ProcessId pid;
@@ -101,8 +109,35 @@
   base::EraseIf(*pending_enable_tracing_ack_pids_,
                 [this](base::ProcessId pid) { return !IsExpectedPid(pid); });
 
-  consumer_endpoint_->EnableTracing(trace_config);
+  consumer_endpoint_->EnableTracing(trace_config_copy);
   MaybeSendEnableTracingAck();
+
+  if (pending_enable_tracing_ack_pids_) {
+    // We can't know for sure whether all processes we request to connect to the
+    // tracing service will connect back, or if all the connected services will
+    // ACK our EnableTracing request eventually, so we'll add a timeout for that
+    // case.
+    enable_tracing_ack_timer_.Start(
+        FROM_HERE, base::TimeDelta::FromSeconds(kEnableTracingTimeoutSeconds),
+        this, &ConsumerHost::OnEnableTracingTimeout);
+  }
+}
+
+void ConsumerHost::ChangeTraceConfig(
+    const perfetto::TraceConfig& trace_config) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  perfetto::TraceConfig trace_config_copy = AdjustTraceConfig(trace_config);
+  consumer_endpoint_->ChangeTraceConfig(trace_config_copy);
+}
+
+perfetto::TraceConfig ConsumerHost::AdjustTraceConfig(
+    const perfetto::TraceConfig& trace_config) {
+  perfetto::TraceConfig trace_config_copy(trace_config);
+  // Clock snapshotting is incompatible with chrome's process sandboxing.
+  // Telemetry uses its own way of snapshotting clocks anyway.
+  trace_config_copy.set_disable_clock_snapshotting(true);
+  return trace_config_copy;
 }
 
 void ConsumerHost::DisableTracing() {
@@ -135,11 +170,44 @@
   consumer_endpoint_->ReadBuffers();
 }
 
+void ConsumerHost::DisableTracingAndEmitJson(
+    const std::string& agent_label_filter,
+    mojo::ScopedDataPipeProducerHandle stream,
+    DisableTracingAndEmitJsonCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(!read_buffers_stream_ && !read_buffers_callback_ &&
+         !json_trace_exporter_);
+
+  read_buffers_stream_ = std::move(stream);
+  read_buffers_callback_ = std::move(callback);
+
+  // TODO(eseckler): Support argument/metadata filtering.
+  json_trace_exporter_ = std::make_unique<TrackEventJSONExporter>(
+      JSONTraceExporter::ArgumentFilterPredicate(),
+      JSONTraceExporter::MetadataFilterPredicate(),
+      base::BindRepeating(&ConsumerHost::OnJSONTraceData,
+                          base::Unretained(this)));
+
+  json_trace_exporter_->set_label_filter(agent_label_filter);
+
+  consumer_endpoint_->DisableTracing();
+}
+
 void ConsumerHost::FreeBuffers() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   consumer_endpoint_->FreeBuffers();
 }
 
+void ConsumerHost::RequestBufferUsage(RequestBufferUsageCallback callback) {
+  if (!request_buffer_usage_callback_.is_null()) {
+    std::move(callback).Run(false, 0);
+    return;
+  }
+
+  request_buffer_usage_callback_ = std::move(callback);
+  consumer_endpoint_->GetTraceStats();
+}
+
 void ConsumerHost::OnConnect() {}
 
 void ConsumerHost::OnDisconnect() {}
@@ -147,13 +215,30 @@
 void ConsumerHost::OnTracingDisabled() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(tracing_session_);
+
+  if (enable_tracing_ack_timer_.IsRunning()) {
+    enable_tracing_ack_timer_.FireNow();
+  }
+  DCHECK(!pending_enable_tracing_ack_pids_);
+
   tracing_session_.reset();
-  pending_enable_tracing_ack_pids_.reset();
+
+  if (json_trace_exporter_) {
+    consumer_endpoint_->ReadBuffers();
+  }
 }
 
 void ConsumerHost::OnTraceData(std::vector<perfetto::TracePacket> packets,
                                bool has_more) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (json_trace_exporter_) {
+    json_trace_exporter_->OnTraceData(std::move(packets), has_more);
+    if (!has_more) {
+      json_trace_exporter_.reset();
+    }
+    return;
+  }
+
   for (auto& packet : packets) {
     char* data;
     size_t size;
@@ -222,6 +307,16 @@
   MaybeSendEnableTracingAck();
 }
 
+void ConsumerHost::OnEnableTracingTimeout() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!pending_enable_tracing_ack_pids_) {
+    return;
+  }
+  DCHECK(tracing_session_);
+  tracing_session_->OnTracingEnabled();
+  pending_enable_tracing_ack_pids_.reset();
+}
+
 void ConsumerHost::MaybeSendEnableTracingAck() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!pending_enable_tracing_ack_pids_ ||
@@ -233,12 +328,50 @@
   DCHECK(tracing_session_);
   tracing_session_->OnTracingEnabled();
   pending_enable_tracing_ack_pids_.reset();
+  enable_tracing_ack_timer_.Stop();
 }
 
 bool ConsumerHost::IsExpectedPid(base::ProcessId pid) const {
   return filtered_pids_.empty() || base::ContainsKey(filtered_pids_, pid);
 }
 
+void ConsumerHost::OnTraceStats(bool success,
+                                const perfetto::TraceStats& stats) {
+  if (!request_buffer_usage_callback_) {
+    return;
+  }
+
+  if (!success || stats.buffer_stats_size() != 1) {
+    std::move(request_buffer_usage_callback_).Run(false, 0.0f);
+    return;
+  }
+
+  const perfetto::TraceStats::BufferStats& buf_stats = stats.buffer_stats()[0];
+  size_t bytes_in_buffer = buf_stats.bytes_written() - buf_stats.bytes_read() -
+                           buf_stats.bytes_overwritten() +
+                           buf_stats.padding_bytes_written() -
+                           buf_stats.padding_bytes_cleared();
+  double percent_full =
+      bytes_in_buffer / static_cast<double>(buf_stats.buffer_size());
+  percent_full = std::min(std::max(0.0, percent_full), 1.0);
+  std::move(request_buffer_usage_callback_).Run(true, percent_full);
+}
+
+void ConsumerHost::OnJSONTraceData(const std::string& json,
+                                   base::DictionaryValue* metadata,
+                                   bool has_more) {
+  WriteToStream(json.data(), json.size());
+
+  if (has_more) {
+    return;
+  }
+
+  read_buffers_stream_.reset();
+  if (read_buffers_callback_) {
+    std::move(read_buffers_callback_).Run();
+  }
+}
+
 void ConsumerHost::WriteToStream(const void* start, size_t size) {
   TRACE_EVENT0("ipc", "ConsumerHost::WriteToStream");
   DCHECK(read_buffers_stream_.is_valid());
diff --git a/services/tracing/perfetto/consumer_host.h b/services/tracing/perfetto/consumer_host.h
index 633cacc6..d88d8a8 100644
--- a/services/tracing/perfetto/consumer_host.h
+++ b/services/tracing/perfetto/consumer_host.h
@@ -12,6 +12,7 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
+#include "base/timer/timer.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "services/tracing/public/mojom/perfetto_service.mojom.h"
 #include "third_party/perfetto/include/perfetto/tracing/core/consumer.h"
@@ -23,6 +24,7 @@
 
 namespace tracing {
 
+class JSONTraceExporter;
 class PerfettoService;
 
 // This is a Mojo interface which enables any client
@@ -45,10 +47,16 @@
   // mojom::ConsumerHost implementation.
   void EnableTracing(mojom::TracingSessionPtr tracing_session,
                      const perfetto::TraceConfig& config) override;
+  void ChangeTraceConfig(const perfetto::TraceConfig& config) override;
+  void DisableTracing() override;
   void ReadBuffers(mojo::ScopedDataPipeProducerHandle stream,
                    ReadBuffersCallback callback) override;
+  void DisableTracingAndEmitJson(
+      const std::string& agent_label_filter,
+      mojo::ScopedDataPipeProducerHandle stream,
+      DisableTracingAndEmitJsonCallback callback) override;
+  void RequestBufferUsage(RequestBufferUsageCallback callback) override;
 
-  void DisableTracing();
   void Flush(uint32_t timeout, base::OnceCallback<void(bool)> callback);
   void FreeBuffers();
 
@@ -61,11 +69,11 @@
   void OnTraceData(std::vector<perfetto::TracePacket> packets,
                    bool has_more) override;
   void OnObservableEvents(const perfetto::ObservableEvents&) override;
+  void OnTraceStats(bool success, const perfetto::TraceStats&) override;
 
   // Unused in Chrome.
   void OnDetach(bool success) override {}
   void OnAttach(bool success, const perfetto::TraceConfig&) override {}
-  void OnTraceStats(bool success, const perfetto::TraceStats&) override {}
 
   // Called by TracingService.
   void OnActiveServicePidAdded(base::ProcessId pid);
@@ -73,8 +81,14 @@
   void OnActiveServicePidsInitialized();
 
  private:
+  perfetto::TraceConfig AdjustTraceConfig(
+      const perfetto::TraceConfig& trace_config);
+  void OnEnableTracingTimeout();
   void MaybeSendEnableTracingAck();
   bool IsExpectedPid(base::ProcessId pid) const;
+  void OnJSONTraceData(const std::string& json,
+                       base::DictionaryValue* metadata,
+                       bool has_more);
   void WriteToStream(const void* start, size_t size);
 
   PerfettoService* const service_;
@@ -86,6 +100,9 @@
   // If set, we didn't issue OnTracingEnabled() on the session yet. If set and
   // empty, no more pids are pending and we should issue OnTracingEnabled().
   base::Optional<std::set<base::ProcessId>> pending_enable_tracing_ack_pids_;
+  base::OneShotTimer enable_tracing_ack_timer_;
+  RequestBufferUsageCallback request_buffer_usage_callback_;
+  std::unique_ptr<JSONTraceExporter> json_trace_exporter_;
 
   SEQUENCE_CHECKER(sequence_checker_);
 
diff --git a/services/tracing/perfetto/perfetto_tracing_coordinator.cc b/services/tracing/perfetto/perfetto_tracing_coordinator.cc
index 73afadb..591727e 100644
--- a/services/tracing/perfetto/perfetto_tracing_coordinator.cc
+++ b/services/tracing/perfetto/perfetto_tracing_coordinator.cc
@@ -20,6 +20,7 @@
 #include "services/tracing/perfetto/consumer_host.h"
 #include "services/tracing/perfetto/perfetto_service.h"
 #include "services/tracing/perfetto/track_event_json_exporter.h"
+#include "services/tracing/public/cpp/perfetto/perfetto_config.h"
 #include "services/tracing/public/cpp/trace_event_args_whitelist.h"
 #include "services/tracing/public/mojom/constants.mojom.h"
 #include "services/tracing/public/mojom/perfetto_service.mojom.h"
@@ -97,34 +98,11 @@
 
   perfetto::TraceConfig CreatePerfettoConfiguration(
       const base::trace_event::TraceConfig& chrome_config) {
-    perfetto::TraceConfig perfetto_config;
-    size_t size_limit = chrome_config.GetTraceBufferSizeInKb();
-    if (size_limit == 0) {
-      size_limit = 100 * 1024;
-    }
-    perfetto_config.add_buffers()->set_size_kb(size_limit);
-
-    // Perfetto uses clock_gettime for its internal snapshotting, which gets
-    // blocked by the sandboxed and isn't needed for Chrome regardless.
-    perfetto_config.set_disable_clock_snapshotting(true);
-
-    auto* trace_event_data_source = perfetto_config.add_data_sources();
-    for (auto& enabled_pid :
-         chrome_config.process_filter_config().included_process_ids()) {
-      *trace_event_data_source->add_producer_name_filter() = base::StrCat(
-          {mojom::kPerfettoProducerNamePrefix,
-           base::NumberToString(static_cast<uint32_t>(enabled_pid))});
-    }
-
-    // We strip the process filter from the config string we send to Perfetto,
-    // so perfetto doesn't reject it from a future
-    // TracingService::ChangeTraceConfig call due to being an unsupported
-    // update.
+#if DCHECK_IS_ON()
     base::trace_event::TraceConfig processfilter_stripped_config(chrome_config);
     processfilter_stripped_config.SetProcessFilterConfig(
         base::trace_event::TraceConfig::ProcessFilterConfig());
 
-#if DCHECK_IS_ON()
     // Ensure that the process filter is the only thing that gets changed
     // in a configuration during a tracing session.
     DCHECK((last_config_for_perfetto_.ToString() ==
@@ -134,40 +112,7 @@
     last_config_for_perfetto_ = processfilter_stripped_config;
 #endif
 
-    auto* trace_event_config = trace_event_data_source->mutable_config();
-    trace_event_config->set_name(mojom::kTraceEventDataSourceName);
-    trace_event_config->set_target_buffer(0);
-    auto* chrome_proto_config = trace_event_config->mutable_chrome_config();
-    chrome_proto_config->set_trace_config(
-        processfilter_stripped_config.ToString());
-
-// Only CrOS and Cast support system tracing.
-#if defined(OS_CHROMEOS) || (defined(IS_CHROMECAST) && defined(OS_LINUX))
-    auto* system_trace_config =
-        perfetto_config.add_data_sources()->mutable_config();
-    system_trace_config->set_name(mojom::kSystemTraceDataSourceName);
-    system_trace_config->set_target_buffer(0);
-    auto* system_chrome_config = system_trace_config->mutable_chrome_config();
-    system_chrome_config->set_trace_config(
-        processfilter_stripped_config.ToString());
-#endif
-
-#if defined(OS_CHROMEOS)
-    auto* arc_trace_config =
-        perfetto_config.add_data_sources()->mutable_config();
-    arc_trace_config->set_name(mojom::kArcTraceDataSourceName);
-    arc_trace_config->set_target_buffer(0);
-    auto* arc_chrome_config = arc_trace_config->mutable_chrome_config();
-    arc_chrome_config->set_trace_config(
-        processfilter_stripped_config.ToString());
-#endif
-
-    auto* trace_metadata_config =
-        perfetto_config.add_data_sources()->mutable_config();
-    trace_metadata_config->set_name(mojom::kMetaDataSourceName);
-    trace_metadata_config->set_target_buffer(0);
-
-    return perfetto_config;
+    return GetDefaultPerfettoConfig(chrome_config);
   }
 
   void StopAndFlush(mojo::ScopedDataPipeProducerHandle stream,
diff --git a/services/tracing/public/cpp/BUILD.gn b/services/tracing/public/cpp/BUILD.gn
index a5bd532..30942414 100644
--- a/services/tracing/public/cpp/BUILD.gn
+++ b/services/tracing/public/cpp/BUILD.gn
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//build/config/chromecast_build.gni")
+
 source_set("traced_process") {
   sources = [
     "traced_process.cc",
@@ -25,6 +27,8 @@
       "base_agent.cc",
       "base_agent.h",
       "perfetto/interning_index.h",
+      "perfetto/perfetto_config.cc",
+      "perfetto/perfetto_config.h",
       "perfetto/producer_client.cc",
       "perfetto/producer_client.h",
       "perfetto/shared_memory.cc",
@@ -68,5 +72,9 @@
       "//third_party/perfetto/include/perfetto/protozero:protozero",
       "//third_party/perfetto/protos/perfetto/trace/chrome:minimal_complete_lite",
     ]
+
+    if (is_chromecast) {
+      defines += [ "IS_CHROMECAST" ]
+    }
   }
 }  # !is_nacl && !is_ios
diff --git a/services/tracing/public/cpp/perfetto/perfetto_config.cc b/services/tracing/public/cpp/perfetto/perfetto_config.cc
new file mode 100644
index 0000000..2d73c53
--- /dev/null
+++ b/services/tracing/public/cpp/perfetto/perfetto_config.cc
@@ -0,0 +1,83 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/tracing/public/cpp/perfetto/perfetto_config.h"
+
+#include <cstdint>
+
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/trace_event/trace_config.h"
+#include "build/build_config.h"
+#include "services/tracing/public/mojom/perfetto_service.mojom.h"
+
+namespace tracing {
+
+perfetto::TraceConfig GetDefaultPerfettoConfig(
+    const base::trace_event::TraceConfig& chrome_config) {
+  perfetto::TraceConfig perfetto_config;
+
+  size_t size_limit = chrome_config.GetTraceBufferSizeInKb();
+  if (size_limit == 0) {
+    size_limit = 100 * 1024;
+  }
+  perfetto_config.add_buffers()->set_size_kb(size_limit);
+
+  // Perfetto uses clock_gettime for its internal snapshotting, which gets
+  // blocked by the sandboxed and isn't needed for Chrome regardless.
+  perfetto_config.set_disable_clock_snapshotting(true);
+
+  // Capture actual trace events.
+  auto* trace_event_data_source = perfetto_config.add_data_sources();
+  for (auto& enabled_pid :
+       chrome_config.process_filter_config().included_process_ids()) {
+    *trace_event_data_source->add_producer_name_filter() = base::StrCat(
+        {mojom::kPerfettoProducerNamePrefix,
+         base::NumberToString(static_cast<uint32_t>(enabled_pid))});
+  }
+
+  // We strip the process filter from the config string we send to Perfetto,
+  // so perfetto doesn't reject it from a future
+  // TracingService::ChangeTraceConfig call due to being an unsupported
+  // update.
+  base::trace_event::TraceConfig processfilter_stripped_config(chrome_config);
+  processfilter_stripped_config.SetProcessFilterConfig(
+      base::trace_event::TraceConfig::ProcessFilterConfig());
+  std::string chrome_config_string = processfilter_stripped_config.ToString();
+
+  auto* trace_event_config = trace_event_data_source->mutable_config();
+  trace_event_config->set_name(tracing::mojom::kTraceEventDataSourceName);
+  trace_event_config->set_target_buffer(0);
+  auto* chrome_proto_config = trace_event_config->mutable_chrome_config();
+  chrome_proto_config->set_trace_config(chrome_config_string);
+
+// Capture system trace events if supported and enabled. The datasources will
+// only emit events if system tracing is enabled in |chrome_config|.
+#if defined(OS_CHROMEOS) || (defined(IS_CHROMECAST) && defined(OS_LINUX))
+  auto* system_trace_config =
+      perfetto_config.add_data_sources()->mutable_config();
+  system_trace_config->set_name(tracing::mojom::kSystemTraceDataSourceName);
+  system_trace_config->set_target_buffer(0);
+  auto* system_chrome_config = system_trace_config->mutable_chrome_config();
+  system_chrome_config->set_trace_config(chrome_config_string);
+#endif
+
+#if defined(OS_CHROMEOS)
+  auto* arc_trace_config = perfetto_config.add_data_sources()->mutable_config();
+  arc_trace_config->set_name(tracing::mojom::kArcTraceDataSourceName);
+  arc_trace_config->set_target_buffer(0);
+  auto* arc_chrome_config = arc_trace_config->mutable_chrome_config();
+  arc_chrome_config->set_trace_config(chrome_config_string);
+#endif
+
+  // Also capture global metadata.
+  auto* trace_metadata_config =
+      perfetto_config.add_data_sources()->mutable_config();
+  trace_metadata_config->set_name(tracing::mojom::kMetaDataSourceName);
+  trace_metadata_config->set_target_buffer(0);
+
+  return perfetto_config;
+}
+
+}  // namespace tracing
diff --git a/services/tracing/public/cpp/perfetto/perfetto_config.h b/services/tracing/public/cpp/perfetto/perfetto_config.h
new file mode 100644
index 0000000..84fef0f
--- /dev/null
+++ b/services/tracing/public/cpp/perfetto/perfetto_config.h
@@ -0,0 +1,24 @@
+// Copyright 2019 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 SERVICES_TRACING_PUBLIC_CPP_PERFETTO_PERFETTO_CONFIG_H_
+#define SERVICES_TRACING_PUBLIC_CPP_PERFETTO_PERFETTO_CONFIG_H_
+
+#include "base/component_export.h"
+#include "third_party/perfetto/include/perfetto/tracing/core/trace_config.h"
+
+namespace base {
+namespace trace_event {
+class TraceConfig;
+}  // namespace trace_event
+}  // namespace base
+
+namespace tracing {
+
+perfetto::TraceConfig COMPONENT_EXPORT(TRACING_CPP) GetDefaultPerfettoConfig(
+    const base::trace_event::TraceConfig& chrome_config);
+
+}  // namespace tracing
+
+#endif  // SERVICES_TRACING_PUBLIC_CPP_PERFETTO_PERFETTO_CONFIG_H_
diff --git a/services/tracing/public/mojom/perfetto_service.mojom b/services/tracing/public/mojom/perfetto_service.mojom
index a85ce30..97b707f 100644
--- a/services/tracing/public/mojom/perfetto_service.mojom
+++ b/services/tracing/public/mojom/perfetto_service.mojom
@@ -146,6 +146,7 @@
 
 struct DataSource {
   DataSourceConfig config;
+  array<string> producer_name_filter;
 };
 
 // The configuration provided by a Consumer to the Perfetto service which
@@ -165,12 +166,38 @@
   // Enable Perfetto tracing with the given TracingSession interface for
   // signaling lifespan of the tracing session, and any future callbacks.
   EnableTracing(TracingSession tracing_session, TraceConfig config);
+
+  // Update the trace config for the active tracing session. Currently, only
+  // (additive) updates to the |producer_name_filter| of a data source are
+  // supported.
+  ChangeTraceConfig(TraceConfig config);
+
+  // Stop tracing for the active tracing session. The host will disconnect the
+  // TracingSession once tracing was disabled. Note that tracing may also stop
+  // without an explicit call to DisableTracing(), e.g. when a tracing duration
+  // is specified in the TraceConfig.
+  DisableTracing();
+
   // Tell Perfetto we're ready to receive data, over the given data pipe.
   // The result callback will be called when there's no more data currently
   // available. If the TracingSession is still active after the callback,
   // another call to ReadBuffers() needs to be made to receive any new
   // tracing data.
   ReadBuffers(handle<data_pipe_producer> stream) => ();
+
+  // Disables tracing and converts the collected trace data converted into the
+  // legacy JSON format before returning it via the data pipe. If
+  // |agent_label_filter| is not empty, only data pertaining to the specified
+  // tracing agent label (e.g. "traceEvents") will be returned.
+  //
+  // DEPRECATED: Existing usecases only; new use cases of tracing should use the
+  // streaming proto format via ReadBuffers.
+  DisableTracingAndEmitJson(string agent_label_filter,
+                            handle<data_pipe_producer> stream) => ();
+
+  // Request current trace buffer usage of the active session. Will be returned
+  // as percentage value between 0.0f and 1.0f.
+  RequestBufferUsage() => (bool success, float percent_full);
 };
 
 // Any client connecting to ConsumerHost should implement this
diff --git a/services/tracing/public/mojom/trace_config_mojom_traits.cc b/services/tracing/public/mojom/trace_config_mojom_traits.cc
index 9c95a2eb..649fbe9 100644
--- a/services/tracing/public/mojom/trace_config_mojom_traits.cc
+++ b/services/tracing/public/mojom/trace_config_mojom_traits.cc
@@ -31,6 +31,16 @@
   }
 
   *out->mutable_config() = std::move(config);
+
+  std::vector<std::string> producer_name_filter;
+  if (!data.ReadProducerNameFilter(&producer_name_filter)) {
+    return false;
+  }
+
+  for (auto&& filter : producer_name_filter) {
+    *out->add_producer_name_filter() = std::move(filter);
+  }
+
   return true;
 }
 
diff --git a/services/tracing/public/mojom/trace_config_mojom_traits.h b/services/tracing/public/mojom/trace_config_mojom_traits.h
index 7606ac6..018d1bd 100644
--- a/services/tracing/public/mojom/trace_config_mojom_traits.h
+++ b/services/tracing/public/mojom/trace_config_mojom_traits.h
@@ -40,6 +40,11 @@
     return src.config();
   }
 
+  static const std::vector<std::string>& producer_name_filter(
+      const perfetto::TraceConfig::DataSource& src) {
+    return src.producer_name_filter();
+  }
+
   static bool Read(tracing::mojom::DataSourceDataView data,
                    perfetto::TraceConfig::DataSource* out);
 };
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 2244a16..9b0d13c 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -17716,11 +17716,23 @@
         },
         "test": "net_unittests"
       }
+    ],
+    "scripts": [
+      {
+        "name": "sizes",
+        "script": "sizes.py"
+      }
     ]
   },
   "android-cronet-arm-rel": {
     "additional_compile_targets": [
       "cronet_package"
+    ],
+    "scripts": [
+      {
+        "name": "sizes",
+        "script": "sizes.py"
+      }
     ]
   },
   "android-cronet-arm64-dbg": {
@@ -17734,6 +17746,12 @@
       "cronet_tests_android",
       "cronet_unittests_android",
       "net_unittests"
+    ],
+    "scripts": [
+      {
+        "name": "sizes",
+        "script": "sizes.py"
+      }
     ]
   },
   "android-cronet-arm64-rel": {
@@ -17747,6 +17765,12 @@
       "cronet_tests_android",
       "cronet_unittests_android",
       "net_unittests"
+    ],
+    "scripts": [
+      {
+        "name": "sizes",
+        "script": "sizes.py"
+      }
     ]
   },
   "android-cronet-asan-arm-rel": {
@@ -18683,6 +18707,12 @@
       "cronet_tests_android",
       "cronet_unittests_android",
       "net_unittests"
+    ],
+    "scripts": [
+      {
+        "name": "sizes",
+        "script": "sizes.py"
+      }
     ]
   },
   "android-cronet-x86-rel": {
@@ -18696,6 +18726,12 @@
       "cronet_tests_android",
       "cronet_unittests_android",
       "net_unittests"
+    ],
+    "scripts": [
+      {
+        "name": "sizes",
+        "script": "sizes.py"
+      }
     ]
   },
   "android-incremental-dbg": {
diff --git a/testing/buildbot/chromium.gpu.fyi.json b/testing/buildbot/chromium.gpu.fyi.json
index 213b240..6747b7d 100644
--- a/testing/buildbot/chromium.gpu.fyi.json
+++ b/testing/buildbot/chromium.gpu.fyi.json
@@ -1,2630 +1,6 @@
 {
   "AAAAA1 AUTOGENERATED FILE DO NOT EDIT": {},
   "AAAAA2 See generate_buildbot_json.py to make changes": {},
-  "ANGLE GPU Linux Release (Intel HD 630)": {
-    "gtest_tests": [
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0"
-        ],
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "intel-hd-630-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "shards": 4
-        },
-        "test": "angle_end2end_tests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--no-xvfb"
-        ],
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "intel-hd-630-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "angle_unittests"
-      },
-      {
-        "args": [
-          "--test-launcher-retry-limit=0"
-        ],
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "intel-hd-630-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "angle_white_box_tests"
-      },
-      {
-        "args": [
-          "--enable-gpu",
-          "--test-launcher-bot-mode",
-          "--test-launcher-jobs=1",
-          "--gtest_filter=CastStreamingApiTestWithPixelOutput.EndToEnd*:TabCaptureApiPixelTest.EndToEnd*",
-          "--no-xvfb"
-        ],
-        "name": "tab_capture_end2end_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "intel-hd-630-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
-        "args": [
-          "--enable-gpu",
-          "--test-launcher-bot-mode",
-          "--test-launcher-jobs=1",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/vulkan.content_browsertests.filter",
-          "--enable-features=VizDisplayCompositor,UseSkiaRenderer,UiGpuRasterization",
-          "--use-gl=any",
-          "--enable-oop-rasterization",
-          "--enable-vulkan",
-          "--enable-gpu-rasterization",
-          "--enable-raster-to-sk-image",
-          "--force-gpu-rasterization",
-          "--disable-software-compositing-fallback",
-          "--no-xvfb"
-        ],
-        "name": "vulkan_content_browsertests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "intel-hd-630-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "content_browsertests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "intel-hd-630-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "shards": 4
-        },
-        "test": "dawn_end2end_tests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--use-cmd-decoder=validating"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "intel-hd-630-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "gl_tests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--no-xvfb"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "intel-hd-630-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "gl_unittests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "intel-hd-630-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "gles2_conform_test"
-      },
-      {
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "intel-hd-630-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "swiftshader_unittests"
-      }
-    ],
-    "isolated_scripts": [
-      {
-        "args": [
-          "context_lost",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "context_lost_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "intel-hd-630-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "depth_capture",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "depth_capture_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "intel-hd-630-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "gpu_process",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "gpu_process_launch_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "intel-hd-630-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "hardware_accelerated_feature",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "hardware_accelerated_feature_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "intel-hd-630-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "info_collection",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--expected-vendor-id",
-          "8086",
-          "--expected-device-id",
-          "5912"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "info_collection_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "intel-hd-630-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "maps",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--dont-restore-color-profile-after-test",
-          "--os-type",
-          "linux",
-          "--build-revision",
-          "${got_revision}",
-          "--test-machine-name",
-          "${buildername}"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "maps_pixel_test",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "intel-hd-630-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "pixel",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--dont-restore-color-profile-after-test",
-          "--refimg-cloud-storage-bucket",
-          "chromium-gpu-archive/reference-images",
-          "--os-type",
-          "linux",
-          "--build-revision",
-          "${got_revision}",
-          "--test-machine-name",
-          "${buildername}",
-          "--use-skia-gold"
-        ],
-        "experiment_percentage": 100,
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "pixel_skia_gold_test",
-        "non_precommit_args": [
-          "--upload-refimg-to-cloud-storage"
-        ],
-        "precommit_args": [
-          "--download-refimg-from-cloud-storage",
-          "--review-patch-issue",
-          "${patch_issue}",
-          "--review-patch-set",
-          "${patch_set}",
-          "--buildbucket-build-id",
-          "${buildbucket_build_id}"
-        ],
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "intel-hd-630-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false,
-          "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
-        }
-      },
-      {
-        "args": [
-          "pixel",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--dont-restore-color-profile-after-test",
-          "--refimg-cloud-storage-bucket",
-          "chromium-gpu-archive/reference-images",
-          "--os-type",
-          "linux",
-          "--build-revision",
-          "${got_revision}",
-          "--test-machine-name",
-          "${buildername}"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "pixel_test",
-        "non_precommit_args": [
-          "--upload-refimg-to-cloud-storage"
-        ],
-        "precommit_args": [
-          "--download-refimg-from-cloud-storage"
-        ],
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "intel-hd-630-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "screenshot_sync",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--dont-restore-color-profile-after-test"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "screenshot_sync_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "intel-hd-630-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "trace_test",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "trace_test",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "intel-hd-630-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "webgl_conformance",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-gl=angle --use-angle=gl --use-cmd-decoder=passthrough",
-          "--webgl-conformance-version=2.0.1",
-          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "webgl2_conformance_gl_passthrough_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "intel-hd-630-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false,
-          "shards": 20
-        }
-      },
-      {
-        "args": [
-          "webgl_conformance",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--webgl-conformance-version=2.0.1",
-          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "webgl2_conformance_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "intel-hd-630-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false,
-          "shards": 20
-        }
-      },
-      {
-        "args": [
-          "webgl_conformance",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-gl=angle --use-angle=gl --use-cmd-decoder=passthrough"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "webgl_conformance_gl_passthrough_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "intel-hd-630-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false,
-          "shards": 2
-        }
-      },
-      {
-        "args": [
-          "webgl_conformance",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "webgl_conformance_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "intel-hd-630-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false,
-          "shards": 2
-        }
-      }
-    ]
-  },
-  "ANGLE GPU Linux Release (NVIDIA)": {
-    "gtest_tests": [
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0"
-        ],
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "nvidia-quadro-p400-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "shards": 4
-        },
-        "test": "angle_end2end_tests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--no-xvfb"
-        ],
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "nvidia-quadro-p400-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "angle_unittests"
-      },
-      {
-        "args": [
-          "--test-launcher-retry-limit=0"
-        ],
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "nvidia-quadro-p400-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "angle_white_box_tests"
-      },
-      {
-        "args": [
-          "--enable-gpu",
-          "--test-launcher-bot-mode",
-          "--test-launcher-jobs=1",
-          "--gtest_filter=CastStreamingApiTestWithPixelOutput.EndToEnd*:TabCaptureApiPixelTest.EndToEnd*",
-          "--no-xvfb"
-        ],
-        "name": "tab_capture_end2end_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "nvidia-quadro-p400-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
-        "args": [
-          "--enable-gpu",
-          "--test-launcher-bot-mode",
-          "--test-launcher-jobs=1",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/vulkan.content_browsertests.filter",
-          "--enable-features=VizDisplayCompositor,UseSkiaRenderer,UiGpuRasterization",
-          "--use-gl=any",
-          "--enable-oop-rasterization",
-          "--enable-vulkan",
-          "--enable-gpu-rasterization",
-          "--enable-raster-to-sk-image",
-          "--force-gpu-rasterization",
-          "--disable-software-compositing-fallback",
-          "--no-xvfb"
-        ],
-        "name": "vulkan_content_browsertests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "nvidia-quadro-p400-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "content_browsertests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "nvidia-quadro-p400-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "shards": 4
-        },
-        "test": "dawn_end2end_tests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--use-cmd-decoder=validating"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "nvidia-quadro-p400-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "gl_tests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--no-xvfb"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "nvidia-quadro-p400-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "gl_unittests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "nvidia-quadro-p400-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "gles2_conform_test"
-      },
-      {
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "nvidia-quadro-p400-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "swiftshader_unittests"
-      }
-    ],
-    "isolated_scripts": [
-      {
-        "args": [
-          "--gtest-benchmark-name=angle_perftests",
-          "-v",
-          "--one-frame-only"
-        ],
-        "isolate_name": "angle_perftests",
-        "merge": {
-          "args": [
-            "--smoke-test-mode"
-          ],
-          "script": "//tools/perf/process_perf_results.py"
-        },
-        "name": "angle_perftests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "nvidia-quadro-p400-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        }
-      },
-      {
-        "args": [
-          "context_lost",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "context_lost_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "nvidia-quadro-p400-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "depth_capture",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "depth_capture_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "nvidia-quadro-p400-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "gpu_process",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "gpu_process_launch_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "nvidia-quadro-p400-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "hardware_accelerated_feature",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "hardware_accelerated_feature_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "nvidia-quadro-p400-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "info_collection",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--expected-vendor-id",
-          "10de",
-          "--expected-device-id",
-          "1cb3"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "info_collection_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "nvidia-quadro-p400-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "maps",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--dont-restore-color-profile-after-test",
-          "--os-type",
-          "linux",
-          "--build-revision",
-          "${got_revision}",
-          "--test-machine-name",
-          "${buildername}"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "maps_pixel_test",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "nvidia-quadro-p400-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "pixel",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--dont-restore-color-profile-after-test",
-          "--refimg-cloud-storage-bucket",
-          "chromium-gpu-archive/reference-images",
-          "--os-type",
-          "linux",
-          "--build-revision",
-          "${got_revision}",
-          "--test-machine-name",
-          "${buildername}",
-          "--use-skia-gold"
-        ],
-        "experiment_percentage": 100,
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "pixel_skia_gold_test",
-        "non_precommit_args": [
-          "--upload-refimg-to-cloud-storage"
-        ],
-        "precommit_args": [
-          "--download-refimg-from-cloud-storage",
-          "--review-patch-issue",
-          "${patch_issue}",
-          "--review-patch-set",
-          "${patch_set}",
-          "--buildbucket-build-id",
-          "${buildbucket_build_id}"
-        ],
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "nvidia-quadro-p400-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false,
-          "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
-        }
-      },
-      {
-        "args": [
-          "pixel",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--dont-restore-color-profile-after-test",
-          "--refimg-cloud-storage-bucket",
-          "chromium-gpu-archive/reference-images",
-          "--os-type",
-          "linux",
-          "--build-revision",
-          "${got_revision}",
-          "--test-machine-name",
-          "${buildername}"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "pixel_test",
-        "non_precommit_args": [
-          "--upload-refimg-to-cloud-storage"
-        ],
-        "precommit_args": [
-          "--download-refimg-from-cloud-storage"
-        ],
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "nvidia-quadro-p400-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "screenshot_sync",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--dont-restore-color-profile-after-test"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "screenshot_sync_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "nvidia-quadro-p400-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "trace_test",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "trace_test",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "nvidia-quadro-p400-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "webgl_conformance",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-gl=angle --use-angle=gl --use-cmd-decoder=passthrough",
-          "--webgl-conformance-version=2.0.1",
-          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "webgl2_conformance_gl_passthrough_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "nvidia-quadro-p400-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false,
-          "shards": 20
-        }
-      },
-      {
-        "args": [
-          "webgl_conformance",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--webgl-conformance-version=2.0.1",
-          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "webgl2_conformance_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "nvidia-quadro-p400-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false,
-          "shards": 20
-        }
-      },
-      {
-        "args": [
-          "webgl_conformance",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-gl=angle --use-angle=gl --use-cmd-decoder=passthrough"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "webgl_conformance_gl_passthrough_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "nvidia-quadro-p400-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false,
-          "shards": 2
-        }
-      },
-      {
-        "args": [
-          "webgl_conformance",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "webgl_conformance_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "nvidia-quadro-p400-ubuntu-stable",
-              "os": "Ubuntu",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false,
-          "shards": 2
-        }
-      }
-    ]
-  },
-  "ANGLE GPU Mac Release (Intel)": {
-    "gtest_tests": [
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0"
-        ],
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
-            }
-          ],
-          "shards": 4
-        },
-        "test": "angle_end2end_tests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0"
-        ],
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
-            }
-          ]
-        },
-        "test": "angle_unittests"
-      },
-      {
-        "args": [
-          "--enable-gpu",
-          "--test-launcher-bot-mode",
-          "--test-launcher-jobs=1",
-          "--gtest_filter=CastStreamingApiTestWithPixelOutput.EndToEnd*:TabCaptureApiPixelTest.EndToEnd*"
-        ],
-        "name": "tab_capture_end2end_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
-            }
-          ],
-          "shards": 4
-        },
-        "test": "dawn_end2end_tests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--use-cmd-decoder=validating"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
-            }
-          ]
-        },
-        "test": "gl_tests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
-            }
-          ]
-        },
-        "test": "gl_unittests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
-            }
-          ]
-        },
-        "test": "gles2_conform_test"
-      },
-      {
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
-            }
-          ]
-        },
-        "test": "gpu_unittests"
-      },
-      {
-        "args": [
-          "--gtest_filter=*Detection*",
-          "--use-gpu-in-tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
-            }
-          ]
-        },
-        "test": "services_unittests"
-      },
-      {
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
-            }
-          ]
-        },
-        "test": "swiftshader_unittests"
-      }
-    ],
-    "isolated_scripts": [
-      {
-        "args": [
-          "context_lost",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "context_lost_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "depth_capture",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "depth_capture_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "gpu_process",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "gpu_process_launch_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "hardware_accelerated_feature",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "hardware_accelerated_feature_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "info_collection",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--expected-vendor-id",
-          "8086",
-          "--expected-device-id",
-          "0a2e"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "info_collection_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "maps",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--dont-restore-color-profile-after-test",
-          "--os-type",
-          "mac",
-          "--build-revision",
-          "${got_revision}",
-          "--test-machine-name",
-          "${buildername}"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "maps_pixel_test",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "pixel",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--dont-restore-color-profile-after-test",
-          "--refimg-cloud-storage-bucket",
-          "chromium-gpu-archive/reference-images",
-          "--os-type",
-          "mac",
-          "--build-revision",
-          "${got_revision}",
-          "--test-machine-name",
-          "${buildername}"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "pixel_test",
-        "non_precommit_args": [
-          "--upload-refimg-to-cloud-storage"
-        ],
-        "precommit_args": [
-          "--download-refimg-from-cloud-storage"
-        ],
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "screenshot_sync",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--dont-restore-color-profile-after-test"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "screenshot_sync_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "trace_test",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "trace_test",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "webgl_conformance",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--webgl-conformance-version=2.0.1",
-          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "webgl2_conformance_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
-            }
-          ],
-          "idempotent": false,
-          "shards": 20
-        }
-      },
-      {
-        "args": [
-          "webgl_conformance",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "webgl_conformance_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
-            }
-          ],
-          "idempotent": false,
-          "shards": 2
-        }
-      }
-    ]
-  },
-  "ANGLE GPU Mac Retina Release (AMD)": {
-    "gtest_tests": [
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0"
-        ],
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "shards": 4
-        },
-        "test": "angle_end2end_tests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0"
-        ],
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "angle_unittests"
-      },
-      {
-        "args": [
-          "--enable-gpu",
-          "--test-launcher-bot-mode",
-          "--test-launcher-jobs=1",
-          "--gtest_filter=CastStreamingApiTestWithPixelOutput.EndToEnd*:TabCaptureApiPixelTest.EndToEnd*"
-        ],
-        "name": "tab_capture_end2end_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "shards": 4
-        },
-        "test": "dawn_end2end_tests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--use-cmd-decoder=validating"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "gl_tests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "gl_unittests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "gles2_conform_test"
-      },
-      {
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "gpu_unittests"
-      },
-      {
-        "args": [
-          "--gtest_filter=*Detection*",
-          "--use-gpu-in-tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "services_unittests"
-      },
-      {
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "swiftshader_unittests"
-      }
-    ],
-    "isolated_scripts": [
-      {
-        "args": [
-          "context_lost",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "context_lost_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "depth_capture",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "depth_capture_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "gpu_process",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "gpu_process_launch_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "hardware_accelerated_feature",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "hardware_accelerated_feature_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "info_collection",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--expected-vendor-id",
-          "1002",
-          "--expected-device-id",
-          "6821"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "info_collection_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "maps",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--dont-restore-color-profile-after-test",
-          "--os-type",
-          "mac",
-          "--build-revision",
-          "${got_revision}",
-          "--test-machine-name",
-          "${buildername}"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "maps_pixel_test",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "pixel",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--dont-restore-color-profile-after-test",
-          "--refimg-cloud-storage-bucket",
-          "chromium-gpu-archive/reference-images",
-          "--os-type",
-          "mac",
-          "--build-revision",
-          "${got_revision}",
-          "--test-machine-name",
-          "${buildername}"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "pixel_test",
-        "non_precommit_args": [
-          "--upload-refimg-to-cloud-storage"
-        ],
-        "precommit_args": [
-          "--download-refimg-from-cloud-storage"
-        ],
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "screenshot_sync",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--dont-restore-color-profile-after-test"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "screenshot_sync_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "trace_test",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "trace_test",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "webgl_conformance",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--webgl-conformance-version=2.0.1",
-          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "webgl2_conformance_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false,
-          "shards": 20
-        }
-      },
-      {
-        "args": [
-          "webgl_conformance",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "webgl_conformance_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false,
-          "shards": 2
-        }
-      }
-    ]
-  },
-  "ANGLE GPU Mac Retina Release (NVIDIA)": {
-    "gtest_tests": [
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0"
-        ],
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:0fe9",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "shards": 4
-        },
-        "test": "angle_end2end_tests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0"
-        ],
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:0fe9",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "angle_unittests"
-      },
-      {
-        "args": [
-          "--enable-gpu",
-          "--test-launcher-bot-mode",
-          "--test-launcher-jobs=1",
-          "--gtest_filter=CastStreamingApiTestWithPixelOutput.EndToEnd*:TabCaptureApiPixelTest.EndToEnd*"
-        ],
-        "name": "tab_capture_end2end_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:0fe9",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:0fe9",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "shards": 4
-        },
-        "test": "dawn_end2end_tests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--use-cmd-decoder=validating"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:0fe9",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "gl_tests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:0fe9",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "gl_unittests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:0fe9",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "gles2_conform_test"
-      },
-      {
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:0fe9",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "gpu_unittests"
-      },
-      {
-        "args": [
-          "--gtest_filter=*Detection*",
-          "--use-gpu-in-tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:0fe9",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "services_unittests"
-      },
-      {
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:0fe9",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ]
-        },
-        "test": "swiftshader_unittests"
-      }
-    ],
-    "isolated_scripts": [
-      {
-        "args": [
-          "context_lost",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "context_lost_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:0fe9",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "depth_capture",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "depth_capture_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:0fe9",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "gpu_process",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "gpu_process_launch_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:0fe9",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "hardware_accelerated_feature",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "hardware_accelerated_feature_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:0fe9",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "info_collection",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--expected-vendor-id",
-          "10de",
-          "--expected-device-id",
-          "0fe9"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "info_collection_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:0fe9",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "maps",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--dont-restore-color-profile-after-test",
-          "--os-type",
-          "mac",
-          "--build-revision",
-          "${got_revision}",
-          "--test-machine-name",
-          "${buildername}"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "maps_pixel_test",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:0fe9",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "pixel",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--dont-restore-color-profile-after-test",
-          "--refimg-cloud-storage-bucket",
-          "chromium-gpu-archive/reference-images",
-          "--os-type",
-          "mac",
-          "--build-revision",
-          "${got_revision}",
-          "--test-machine-name",
-          "${buildername}"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "pixel_test",
-        "non_precommit_args": [
-          "--upload-refimg-to-cloud-storage"
-        ],
-        "precommit_args": [
-          "--download-refimg-from-cloud-storage"
-        ],
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:0fe9",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "screenshot_sync",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--dont-restore-color-profile-after-test"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "screenshot_sync_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:0fe9",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "trace_test",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "trace_test",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:0fe9",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "webgl_conformance",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--webgl-conformance-version=2.0.1",
-          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "webgl2_conformance_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:0fe9",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false,
-          "shards": 20
-        }
-      },
-      {
-        "args": [
-          "webgl_conformance",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "name": "webgl_conformance_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:0fe9",
-              "hidpi": "1",
-              "os": "Mac-10.13.6",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "idempotent": false,
-          "shards": 2
-        }
-      }
-    ]
-  },
   "ANGLE GPU Win10 Release (Intel HD 630)": {
     "gtest_tests": [
       {
diff --git a/testing/buildbot/generate_buildbot_json.py b/testing/buildbot/generate_buildbot_json.py
index 0167c83..3f8549bb 100755
--- a/testing/buildbot/generate_buildbot_json.py
+++ b/testing/buildbot/generate_buildbot_json.py
@@ -920,11 +920,6 @@
     # are defined only to be mirrored into trybots, and don't actually
     # exist on any of the waterfalls or consoles.
     return [
-      'ANGLE GPU Linux Release (Intel HD 630)',
-      'ANGLE GPU Linux Release (NVIDIA)',
-      'ANGLE GPU Mac Release (Intel)',
-      'ANGLE GPU Mac Retina Release (AMD)',
-      'ANGLE GPU Mac Retina Release (NVIDIA)',
       'ANGLE GPU Win10 Release (Intel HD 630)',
       'ANGLE GPU Win10 Release (NVIDIA)',
       'Dawn GPU Linux Release (Intel HD 630)',
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index e6e2ec3..35e7987 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -354,6 +354,7 @@
         ],
         'test_suites': {
           'gtest_tests': 'cronet_gtests',
+          'scripts': 'cronet_scripts',
         },
         'swarming': {
           'dimension_sets': [
@@ -370,6 +371,9 @@
         'additional_compile_targets': [
           'cronet_package',
         ],
+        'test_suites': {
+          'scripts': 'cronet_scripts',
+        },
       },
       'android-cronet-arm64-dbg': {
         'additional_compile_targets': [
@@ -383,6 +387,9 @@
           'cronet_unittests_android',
           'net_unittests',
         ],
+        'test_suites': {
+          'scripts': 'cronet_scripts',
+        },
       },
       'android-cronet-arm64-rel': {
         'additional_compile_targets': [
@@ -396,6 +403,9 @@
           'cronet_unittests_android',
           'net_unittests',
         ],
+        'test_suites': {
+          'scripts': 'cronet_scripts',
+        },
       },
       'android-cronet-asan-arm-rel': {
         'additional_compile_targets': [
@@ -459,6 +469,9 @@
           'cronet_unittests_android',
           'net_unittests',
         ],
+        'test_suites': {
+          'scripts': 'cronet_scripts',
+        },
       },
       'android-cronet-x86-rel': {
         'additional_compile_targets': [
@@ -472,6 +485,9 @@
           'cronet_unittests_android',
           'net_unittests',
         ],
+        'test_suites': {
+          'scripts': 'cronet_scripts',
+        },
       },
       'android-incremental-dbg': {
         'test_suites': {
@@ -2054,67 +2070,6 @@
     'name': 'chromium.gpu.fyi',
     'machines': {
       # BEGIN Fake builder used as mirror targets for ANGLE's GPU tryservers
-      'ANGLE GPU Linux Release (Intel HD 630)': {
-        'os_type': 'linux',
-        'browser_config': 'release',
-        'mixins': [
-          'linux_intel_hd_630',
-        ],
-        'test_suites': {
-          # TODO(jmadill): Use custom test lists. crbug.com/822310
-          'gtest_tests': 'gpu_fyi_linux_release_gtests',
-          'gpu_telemetry_tests': 'gpu_fyi_linux_intel_and_nvidia_release_telemetry_tests',
-        },
-      },
-      'ANGLE GPU Linux Release (NVIDIA)': {
-        'os_type': 'linux',
-        'browser_config': 'release',
-        'mixins': [
-          'linux_nvidia_quadro_p400',
-        ],
-        'test_suites': {
-          # TODO(jmadill): Use custom test lists. crbug.com/822310
-          'gtest_tests': 'gpu_fyi_linux_release_gtests',
-          'isolated_scripts': 'gpu_angle_perftests',
-          'gpu_telemetry_tests': 'gpu_fyi_linux_intel_and_nvidia_release_telemetry_tests',
-        },
-      },
-      'ANGLE GPU Mac Release (Intel)': {
-        'os_type': 'mac',
-        'browser_config': 'release',
-        'mixins': [
-          'mac_mini_intel_gpu',
-        ],
-        'test_suites': {
-          # TODO(jmadill): Use custom test lists. crbug.com/822310
-          'gtest_tests': 'gpu_fyi_mac_release_gtests',
-          'gpu_telemetry_tests': 'gpu_fyi_mac_release_telemetry_tests',
-        },
-      },
-      'ANGLE GPU Mac Retina Release (AMD)': {
-        'os_type': 'mac',
-        'browser_config': 'release',
-        'mixins': [
-          'mac_retina_amd_gpu',
-        ],
-        'test_suites': {
-          # TODO(jmadill): Use custom test lists. crbug.com/822310
-          'gtest_tests': 'gpu_fyi_mac_release_gtests',
-          'gpu_telemetry_tests': 'gpu_fyi_mac_release_telemetry_tests',
-        },
-      },
-      'ANGLE GPU Mac Retina Release (NVIDIA)': {
-        'os_type': 'mac',
-        'browser_config': 'release',
-        'mixins': [
-          'mac_retina_nvidia_gpu',
-        ],
-        'test_suites': {
-          # TODO(jmadill): Use custom test lists. crbug.com/822310
-          'gtest_tests': 'gpu_fyi_mac_release_gtests',
-          'gpu_telemetry_tests': 'gpu_fyi_mac_release_telemetry_tests',
-        },
-      },
       'ANGLE GPU Win10 Release (Intel HD 630)': {
         'os_type': 'win',
         'browser_config': 'release',
diff --git a/testing/libfuzzer/fuzzers/BUILD.gn b/testing/libfuzzer/fuzzers/BUILD.gn
index 88e5804e4..71447aef 100644
--- a/testing/libfuzzer/fuzzers/BUILD.gn
+++ b/testing/libfuzzer/fuzzers/BUILD.gn
@@ -401,7 +401,7 @@
     "$target_gen_dir/javascript_parser.proto",
   ]
   proto_deps = [ ":gen_javascript_parser_proto" ]
-  proto_out_dir = target_gen_dir
+  proto_out_dir = ""
 }
 
 fuzzer_test("javascript_parser_proto_fuzzer") {
diff --git a/testing/libfuzzer/fuzzers/javascript_parser_proto_fuzzer.cc b/testing/libfuzzer/fuzzers/javascript_parser_proto_fuzzer.cc
index 56f39a0..e38f88e 100644
--- a/testing/libfuzzer/fuzzers/javascript_parser_proto_fuzzer.cc
+++ b/testing/libfuzzer/fuzzers/javascript_parser_proto_fuzzer.cc
@@ -6,7 +6,7 @@
 
 #include <iostream>
 
-#include "testing/libfuzzer/fuzzers/javascript_parser.pb.h"  // from out/gen
+#include "javascript_parser.pb.h"  // from out/gen
 #include "testing/libfuzzer/fuzzers/javascript_parser_proto_to_string.h"
 #include "third_party/libprotobuf-mutator/src/src/libfuzzer/libfuzzer_macro.h"
 
diff --git a/testing/libfuzzer/fuzzers/javascript_parser_proto_to_string.h b/testing/libfuzzer/fuzzers/javascript_parser_proto_to_string.h
index d9e5a4c..285ed1a 100644
--- a/testing/libfuzzer/fuzzers/javascript_parser_proto_to_string.h
+++ b/testing/libfuzzer/fuzzers/javascript_parser_proto_to_string.h
@@ -5,7 +5,7 @@
 #ifndef TESTING_LIBFUZZER_FUZZERS_JAVASCRIPT_PARSER_PROTO_TO_STRING_H
 #define TESTING_LIBFUZZER_FUZZERS_JAVASCRIPT_PARSER_PROTO_TO_STRING_H
 
-#include "testing/libfuzzer/fuzzers/javascript_parser.pb.h"  // from out/gen
+#include "javascript_parser.pb.h"  // from out/gen
 
 #include <string>
 
diff --git a/testing/libfuzzer/proto/BUILD.gn b/testing/libfuzzer/proto/BUILD.gn
index 40d3a9a..47747e3 100644
--- a/testing/libfuzzer/proto/BUILD.gn
+++ b/testing/libfuzzer/proto/BUILD.gn
@@ -8,6 +8,10 @@
   sources = [
     "json.proto",
   ]
+
+  # This way json.pb.h header goes into "$root_gen_dir" directory precisely,
+  # otherwise it goes into "$root_gen_dir" + "/testing/libfuzzer/proto/".
+  proto_out_dir = ""
 }
 
 source_set("json_proto_converter") {
@@ -15,7 +19,6 @@
     "json_proto_converter.cc",
     "json_proto_converter.h",
   ]
-  include_dirs = [ target_gen_dir ]
   deps = [
     ":json_proto",
   ]
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index d28ea53..f2cfeb1 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -1577,40 +1577,6 @@
             ]
         }
     ],
-    "DataReductionProxyServerExperiments": [
-        {
-            "platforms": [
-                "android"
-            ],
-            "experiments": [
-                {
-                    "name": "OnDeviceSafeBrowsingFieldTrialEnabled",
-                    "params": {
-                        "exp": "disable_server_safebrowsing"
-                    }
-                }
-            ]
-        }
-    ],
-    "DataSaverSiteBreakdownUsingPageLoadMetrics": [
-        {
-            "platforms": [
-                "android",
-                "windows",
-                "mac",
-                "chromeos",
-                "linux"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "DataSaverSiteBreakdownUsingPageLoadMetrics"
-                    ]
-                }
-            ]
-        }
-    ],
     "DecoupleTranslateLanguage": [
         {
             "platforms": [
@@ -2583,6 +2549,21 @@
             ]
         }
     ],
+    "LazyLoad": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "LazyFrameLoading"
+                    ]
+                }
+            ]
+        }
+    ],
     "LegacySymantecPKI": [
         {
             "platforms": [
@@ -3044,28 +3025,6 @@
             ]
         }
     ],
-    "NetworkQualityEstimator": [
-        {
-            "platforms": [
-                "android",
-                "windows",
-                "mac",
-                "chromeos",
-                "linux"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled9",
-                    "params": {
-                        "cap_ect_based_on_signal_strength": "true"
-                    },
-                    "enable_features": [
-                        "NetworkQualityEstimator"
-                    ]
-                }
-            ]
-        }
-    ],
     "NewExtensionUpdaterService": [
         {
             "platforms": [
diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD.gn
index 67567fe7c..7cb97e9 100644
--- a/third_party/android_deps/BUILD.gn
+++ b/third_party/android_deps/BUILD.gn
@@ -599,7 +599,7 @@
 
 # This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
 android_aar_prebuilt("com_google_ar_core_java") {
-  aar_path = "libs/com_google_ar_core/core-1.6.0.aar"
+  aar_path = "libs/com_google_ar_core/core-1.8.0.aar"
   info_path = "libs/com_google_ar_core/com_google_ar_core.info"
   extract_native_libraries = true
   split_compat_class_names = [ "com/google/ar/core/InstallActivity" ]
diff --git a/third_party/android_deps/libs/com_google_ar_core/README.chromium b/third_party/android_deps/libs/com_google_ar_core/README.chromium
index 19c09d0..253f7971 100644
--- a/third_party/android_deps/libs/com_google_ar_core/README.chromium
+++ b/third_party/android_deps/libs/com_google_ar_core/README.chromium
@@ -1,7 +1,7 @@
 Name: 
 Short Name: core
 URL: https://github.com/google-ar/arcore-android-sdk
-Version: 1.6.0
+Version: 1.8.0
 License: Apache 2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/third_party/android_deps/libs/com_google_ar_core/cipd.yaml b/third_party/android_deps/libs/com_google_ar_core/cipd.yaml
index 2a888d1..f7f0f44 100644
--- a/third_party/android_deps/libs/com_google_ar_core/cipd.yaml
+++ b/third_party/android_deps/libs/com_google_ar_core/cipd.yaml
@@ -3,8 +3,8 @@
 # found in the LICENSE file.
 
 # To create CIPD package run the following command.
-# cipd create --pkg-def cipd.yaml -tag version:1.6.0-cr0
+# cipd create --pkg-def cipd.yaml -tag version:1.8.0-cr0
 package: chromium/third_party/android_deps/libs/com_google_ar_core
 description: ""
 data:
-- file: core-1.6.0.aar
+- file: core-1.8.0.aar
diff --git a/third_party/android_deps/libs/com_google_ar_core/com_google_ar_core.info b/third_party/android_deps/libs/com_google_ar_core/com_google_ar_core.info
index c5073f5..88ab94a 100644
--- a/third_party/android_deps/libs/com_google_ar_core/com_google_ar_core.info
+++ b/third_party/android_deps/libs/com_google_ar_core/com_google_ar_core.info
@@ -8,7 +8,7 @@
 has_proguard_flags = true
 has_r_text_file = true
 is_manifest_empty = false
-native_libraries = [ "jni/arm64-v8a/libarcore_sdk_c.so", "jni/arm64-v8a/libarcore_sdk_jni.so", "jni/armeabi-v7a/libarcore_sdk_c.so", "jni/armeabi-v7a/libarcore_sdk_jni.so", "jni/x86/libarcore_sdk_c.so", "jni/x86/libarcore_sdk_jni.so" ]
+native_libraries = [ "jni/arm64-v8a/libarcore_sdk_c.so", "jni/arm64-v8a/libarcore_sdk_jni.so", "jni/armeabi-v7a/libarcore_sdk_c.so", "jni/armeabi-v7a/libarcore_sdk_jni.so", "jni/x86/libarcore_sdk_c.so", "jni/x86/libarcore_sdk_jni.so", "jni/x86_64/libarcore_sdk_c.so", "jni/x86_64/libarcore_sdk_jni.so" ]
 resources = [ "res/layout/__arcore_education.xml", "res/raw/keep.xml", "res/values-af/values.xml", "res/values-am/values.xml", "res/values-ar-rEG/values.xml", "res/values-ar-rSA/values.xml", "res/values-ar-rXB/values.xml", "res/values-az/values.xml", "res/values-b+es+419/values.xml", "res/values-b+sr+Latn/values.xml", "res/values-be/values.xml", "res/values-bg/values.xml", "res/values-bn/values.xml", "res/values-bs/values.xml", "res/values-ca/values.xml", "res/values-cs/values.xml", "res/values-da/values.xml", "res/values-de-rAT/values.xml", "res/values-de-rCH/values.xml", "res/values-de/values.xml", "res/values-el/values.xml", "res/values-en-rAU/values.xml", "res/values-en-rCA/values.xml", "res/values-en-rGB/values.xml", "res/values-en-rIE/values.xml", "res/values-en-rSG/values.xml", "res/values-en-rXA/values.xml", "res/values-en-rXC/values.xml", "res/values-en-rZA/values.xml", "res/values-es-rAR/values.xml", "res/values-es-rBO/values.xml", "res/values-es-rCL/values.xml", "res/values-es-rCO/values.xml", "res/values-es-rCR/values.xml", "res/values-es-rDO/values.xml", "res/values-es-rEC/values.xml", "res/values-es-rGT/values.xml", "res/values-es-rHN/values.xml", "res/values-es-rMX/values.xml", "res/values-es-rNI/values.xml", "res/values-es-rPA/values.xml", "res/values-es-rPE/values.xml", "res/values-es-rPR/values.xml", "res/values-es-rPY/values.xml", "res/values-es-rSV/values.xml", "res/values-es-rUS/values.xml", "res/values-es-rUY/values.xml", "res/values-es-rVE/values.xml", "res/values-es/values.xml", "res/values-et/values.xml", "res/values-eu/values.xml", "res/values-fa/values.xml", "res/values-fi/values.xml", "res/values-fil/values.xml", "res/values-fr-rCA/values.xml", "res/values-fr-rCH/values.xml", "res/values-fr/values.xml", "res/values-gl/values.xml", "res/values-gsw/values.xml", "res/values-gu/values.xml", "res/values-he/values.xml", "res/values-hi/values.xml", "res/values-hr/values.xml", "res/values-hu/values.xml", "res/values-hy/values.xml", "res/values-id/values.xml", "res/values-in/values.xml", "res/values-is/values.xml", "res/values-it/values.xml", "res/values-iw/values.xml", "res/values-ja/values.xml", "res/values-ka/values.xml", "res/values-kk/values.xml", "res/values-km/values.xml", "res/values-kn/values.xml", "res/values-ko/values.xml", "res/values-ky/values.xml", "res/values-lo/values.xml", "res/values-lt/values.xml", "res/values-lv/values.xml", "res/values-mk/values.xml", "res/values-ml/values.xml", "res/values-mn/values.xml", "res/values-mo/values.xml", "res/values-ms/values.xml", "res/values-my/values.xml", "res/values-nb/values.xml", "res/values-ne/values.xml", "res/values-nl/values.xml", "res/values-no/values.xml", "res/values-pa/values.xml", "res/values-pl/values.xml", "res/values-pt-rBR/values.xml", "res/values-pt-rPT/values.xml", "res/values-pt/values.xml", "res/values-ro/values.xml", "res/values-ru/values.xml", "res/values-si/values.xml", "res/values-sk/values.xml", "res/values-sl/values.xml", "res/values-sq/values.xml", "res/values-sr/values.xml", "res/values-sv/values.xml", "res/values-sw/values.xml", "res/values-ta/values.xml", "res/values-te/values.xml", "res/values-th/values.xml", "res/values-tl/values.xml", "res/values-tr/values.xml", "res/values-uk/values.xml", "res/values-ur/values.xml", "res/values-uz/values.xml", "res/values-vi/values.xml", "res/values-zh-rCN/values.xml", "res/values-zh-rHK/values.xml", "res/values-zh-rTW/values.xml", "res/values-zh/values.xml", "res/values-zu/values.xml", "res/values/values.xml" ]
 subjar_tuples = [  ]
 subjars = [  ]
diff --git a/third_party/blink/public/BUILD.gn b/third_party/blink/public/BUILD.gn
index b85cbda..c9b5544a 100644
--- a/third_party/blink/public/BUILD.gn
+++ b/third_party/blink/public/BUILD.gn
@@ -396,6 +396,7 @@
     "web/modules/mediastream/media_stream_video_source.h",
     "web/modules/mediastream/media_stream_video_track.h",
     "web/modules/mediastream/video_track_adapter.h",
+    "web/modules/mediastream/video_track_adapter_settings.h",
     "web/modules/mediastream/web_media_stream_utils.h",
     "web/modules/service_worker/web_service_worker_context_client.h",
     "web/modules/service_worker/web_service_worker_context_proxy.h",
diff --git a/third_party/blink/public/platform/web_encrypted_media_request.h b/third_party/blink/public/platform/web_encrypted_media_request.h
index d50f362d..270567e 100644
--- a/third_party/blink/public/platform/web_encrypted_media_request.h
+++ b/third_party/blink/public/platform/web_encrypted_media_request.h
@@ -6,13 +6,15 @@
 #define THIRD_PARTY_BLINK_PUBLIC_PLATFORM_WEB_ENCRYPTED_MEDIA_REQUEST_H_
 
 #include "third_party/blink/public/platform/web_common.h"
+#include "third_party/blink/public/platform/web_content_decryption_module_access.h"
 #include "third_party/blink/public/platform/web_private_ptr.h"
 #include "third_party/blink/public/platform/web_string.h"
 
+#include <memory>
+
 namespace blink {
 
 class EncryptedMediaRequest;
-class WebContentDecryptionModuleAccess;
 struct WebMediaKeySystemConfiguration;
 class WebSecurityOrigin;
 template <typename T>
@@ -31,7 +33,7 @@
   BLINK_PLATFORM_EXPORT WebSecurityOrigin GetSecurityOrigin() const;
 
   BLINK_PLATFORM_EXPORT void RequestSucceeded(
-      WebContentDecryptionModuleAccess*);
+      std::unique_ptr<WebContentDecryptionModuleAccess>);
   BLINK_PLATFORM_EXPORT void RequestNotSupported(
       const WebString& error_message);
 
diff --git a/third_party/blink/public/platform/web_layer_tree_view.h b/third_party/blink/public/platform/web_layer_tree_view.h
index 22e0fa9..93b851b 100644
--- a/third_party/blink/public/platform/web_layer_tree_view.h
+++ b/third_party/blink/public/platform/web_layer_tree_view.h
@@ -41,10 +41,6 @@
 #include "third_party/skia/include/core/SkImage.h"
 #include "third_party/skia/include/core/SkRefCnt.h"
 
-namespace cc {
-class PaintImage;
-}
-
 namespace blink {
 
 class WebLayerTreeView {
@@ -139,14 +135,6 @@
   virtual void NotifySwapTime(ReportTimeCallback callback) {}
 
   virtual void RequestBeginMainFrameNotExpected(bool new_state) {}
-
-  virtual void RequestDecode(const cc::PaintImage& image,
-                             base::OnceCallback<void(bool)> callback) {}
-
-  // Runs |callback| after a new frame has been submitted to the display
-  // compositor, and the display-compositor has displayed it on screen. Forces a
-  // redraw so that a new frame is submitted.
-  virtual void RequestPresentationCallback(base::OnceClosure callback) {}
 };
 
 }  // namespace blink
diff --git a/third_party/blink/public/web/modules/mediastream/media_stream_constraints_util.h b/third_party/blink/public/web/modules/mediastream/media_stream_constraints_util.h
index b6df0a1..7020b82 100644
--- a/third_party/blink/public/web/modules/mediastream/media_stream_constraints_util.h
+++ b/third_party/blink/public/web/modules/mediastream/media_stream_constraints_util.h
@@ -16,7 +16,7 @@
 #include "third_party/blink/public/platform/web_media_constraints.h"
 #include "third_party/blink/public/platform/web_media_stream_source.h"
 #include "third_party/blink/public/web/modules/mediastream/media_stream_constraints_util_sets.h"
-#include "third_party/blink/public/web/modules/mediastream/video_track_adapter.h"
+#include "third_party/blink/public/web/modules/mediastream/video_track_adapter_settings.h"
 
 namespace blink {
 
diff --git a/third_party/blink/public/web/modules/mediastream/video_track_adapter.h b/third_party/blink/public/web/modules/mediastream/video_track_adapter.h
index 57c33d6..088ea41 100644
--- a/third_party/blink/public/web/modules/mediastream/video_track_adapter.h
+++ b/third_party/blink/public/web/modules/mediastream/video_track_adapter.h
@@ -11,6 +11,7 @@
 
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/optional.h"
 #include "base/single_thread_task_runner.h"
 #include "base/time/time.h"
 #include "media/base/video_frame.h"
@@ -21,51 +22,7 @@
 
 namespace blink {
 
-class BLINK_EXPORT VideoTrackAdapterSettings {
- public:
-  // Creates a VideoTrackAdapterSettings with no target resolution or frame rate
-  // and without any constraints on the resolution.
-  VideoTrackAdapterSettings();
-  // Creates a VideoTrackAdapterSettings with a given target resolution and
-  // and frame rate, and without any constraints on the resolution.
-  VideoTrackAdapterSettings(const gfx::Size& target_size,
-                            double max_frame_rate);
-  // Creates a VideoTrackAdapterSettings with the specified resolution, frame
-  // rate and resolution constraints. If |target_size| is null, it means that
-  // no video processing is desired.
-  VideoTrackAdapterSettings(base::Optional<gfx::Size> target_size,
-                            double min_aspect_ratio,
-                            double max_aspect_ratio,
-                            double max_frame_rate);
-  VideoTrackAdapterSettings(const VideoTrackAdapterSettings& other);
-  VideoTrackAdapterSettings& operator=(const VideoTrackAdapterSettings& other);
-  bool operator==(const VideoTrackAdapterSettings& other) const;
-
-  const base::Optional<gfx::Size>& target_size() const { return target_size_; }
-  int target_width() const {
-    DCHECK(target_size_);
-    return target_size_->width();
-  }
-  int target_height() const {
-    DCHECK(target_size_);
-    return target_size_->height();
-  }
-  double min_aspect_ratio() const { return min_aspect_ratio_; }
-  double max_aspect_ratio() const { return max_aspect_ratio_; }
-  double max_frame_rate() const { return max_frame_rate_; }
-  void set_max_frame_rate(double max_frame_rate) {
-    max_frame_rate_ = max_frame_rate;
-  }
-
- private:
-  base::Optional<gfx::Size> target_size_;
-  double min_aspect_ratio_;
-  double max_aspect_ratio_;
-  // A |max_frame_rate| of zero is used to signal that no frame-rate
-  // adjustment is necessary.
-  // TODO(guidou): Change this to base::Optional. https://crbug.com/734528
-  double max_frame_rate_;
-};
+class VideoTrackAdapterSettings;
 
 // VideoTrackAdapter is a helper class used by MediaStreamVideoSource used for
 // adapting the video resolution from a source implementation to the resolution
@@ -121,8 +78,10 @@
   void SetSourceFrameSize(const gfx::Size& source_frame_size);
 
   // Exported for testing.
-  // Returns true if |desired_size| is updated successfully, false otherwise.
-  // |desired_size| is not updated |settings| has rescaling disabled and
+  //
+  // Calculates the desired size of a VideoTrack instance, and returns true if
+  // |desired_size| is updated successfully, false otherwise.
+  // |desired_size| is not updated if |settings| has rescaling disabled and
   // |input_size| is invalid.
   static bool CalculateDesiredSize(bool is_rotated,
                                    const gfx::Size& input_size,
diff --git a/third_party/blink/public/web/modules/mediastream/video_track_adapter_settings.h b/third_party/blink/public/web/modules/mediastream/video_track_adapter_settings.h
new file mode 100644
index 0000000..84cc2f63
--- /dev/null
+++ b/third_party/blink/public/web/modules/mediastream/video_track_adapter_settings.h
@@ -0,0 +1,77 @@
+// Copyright 2019 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 THIRD_PARTY_BLINK_PUBLIC_WEB_MODULES_MEDIASTREAM_VIDEO_TRACK_ADAPTER_SETTINGS_H_
+#define THIRD_PARTY_BLINK_PUBLIC_WEB_MODULES_MEDIASTREAM_VIDEO_TRACK_ADAPTER_SETTINGS_H_
+
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "third_party/blink/public/platform/web_common.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace blink {
+
+class BLINK_EXPORT VideoTrackAdapterSettings {
+ public:
+  // Creates a VideoTrackAdapterSettings with no target resolution or frame rate
+  // and without any constraints on the resolution.
+  VideoTrackAdapterSettings();
+  // Creates a VideoTrackAdapterSettings with a given target resolution and
+  // and frame rate, and without any constraints on the resolution.
+  VideoTrackAdapterSettings(const gfx::Size& target_size,
+                            double max_frame_rate);
+  // Creates a VideoTrackAdapterSettings with the specified resolution, frame
+  // rate and resolution constraints. If |target_size| is null, it means that
+  // no video processing is desired.
+  VideoTrackAdapterSettings(base::Optional<gfx::Size> target_size,
+                            double min_aspect_ratio,
+                            double max_aspect_ratio,
+                            double max_frame_rate);
+  VideoTrackAdapterSettings(const VideoTrackAdapterSettings& other);
+  VideoTrackAdapterSettings& operator=(const VideoTrackAdapterSettings& other);
+  bool operator==(const VideoTrackAdapterSettings& other) const;
+
+  const base::Optional<gfx::Size>& target_size() const { return target_size_; }
+  int target_width() const {
+    DCHECK(target_size_);
+    return target_size_->width();
+  }
+  int target_height() const {
+    DCHECK(target_size_);
+    return target_size_->height();
+  }
+  double min_aspect_ratio() const { return min_aspect_ratio_; }
+  double max_aspect_ratio() const { return max_aspect_ratio_; }
+  double max_frame_rate() const { return max_frame_rate_; }
+  void set_max_frame_rate(double max_frame_rate) {
+    max_frame_rate_ = max_frame_rate;
+  }
+
+ private:
+  base::Optional<gfx::Size> target_size_;
+  double min_aspect_ratio_;
+  double max_aspect_ratio_;
+  // A |max_frame_rate| of zero is used to signal that no frame-rate
+  // adjustment is necessary.
+  // TODO(guidou): Change this to base::Optional. https://crbug.com/734528
+  double max_frame_rate_;
+};
+
+// Exported for testing.
+//
+// Calculates the desired size of a VideoTrack instance, and returns true if
+// |desired_size| is updated successfully, false otherwise.
+// |desired_size| is not updated if |settings| has rescaling disabled and
+// |input_size| is invalid.
+BLINK_EXPORT bool CalculateDesiredVideoTrackSize(
+    bool is_rotated,
+    const gfx::Size& input_size,
+    const VideoTrackAdapterSettings& settings,
+    gfx::Size* desired_size);
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_PUBLIC_WEB_MODULES_MEDIASTREAM_VIDEO_TRACK_ADAPTER_SETTINGS_H_
diff --git a/third_party/blink/public/web/web_widget.h b/third_party/blink/public/web/web_widget.h
index 84299699..d4de9b5 100644
--- a/third_party/blink/public/web/web_widget.h
+++ b/third_party/blink/public/web/web_widget.h
@@ -168,12 +168,6 @@
   // transormations (e.g. pinch-zoom, dev tools emulation, etc.).
   virtual void PaintContent(cc::PaintCanvas*, const WebRect& view_port) {}
 
-  // Runs |callback| after a new frame has been submitted to the display
-  // compositor, and the display-compositor has displayed it on screen. Forces a
-  // redraw so that a new frame is submitted.
-  virtual void RequestPresentationCallbackForTesting(
-      base::OnceClosure callback) {}
-
   // Called to inform the WebWidget of a change in theme.
   // Implementors that cache rendered copies of widgets need to re-render
   // on receiving this message
diff --git a/third_party/blink/public/web/web_widget_client.h b/third_party/blink/public/web/web_widget_client.h
index 2a2e5cb..362ed035 100644
--- a/third_party/blink/public/web/web_widget_client.h
+++ b/third_party/blink/public/web/web_widget_client.h
@@ -50,6 +50,7 @@
 class SkBitmap;
 
 namespace cc {
+class PaintImage;
 struct ViewportLayers;
 }
 
@@ -249,6 +250,12 @@
                                        bool use_anchor,
                                        float new_page_scale,
                                        double duration_sec) {}
+
+  // Requests an image decode and will have the |callback| run asynchronously
+  // when it completes. Forces a new main frame to occur that will trigger
+  // pushing the decode through the compositor.
+  virtual void RequestDecode(const cc::PaintImage& image,
+                             base::OnceCallback<void(bool)> callback) {}
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/bindings/modules/v8/BUILD.gn b/third_party/blink/renderer/bindings/modules/v8/BUILD.gn
index 0745a1c..c82043e 100644
--- a/third_party/blink/renderer/bindings/modules/v8/BUILD.gn
+++ b/third_party/blink/renderer/bindings/modules/v8/BUILD.gn
@@ -50,9 +50,8 @@
 }
 
 generate_origin_trial_features("bindings_modules_origin_trial_features") {
-  sources =
-      modules_idl_files + modules_global_constructors_generated_idl_files +
-      modules_dependency_idl_files
+  sources = modules_idl_files + modules_generated_dependency_idl_files +
+            modules_dependency_idl_files
   component = "modules"
   output_dir = bindings_modules_output_dir + "/v8"
   deps = [
diff --git a/third_party/blink/renderer/bindings/modules/v8/generated.gni b/third_party/blink/renderer/bindings/modules/v8/generated.gni
index 7569f710..7c37c6d2 100644
--- a/third_party/blink/renderer/bindings/modules/v8/generated.gni
+++ b/third_party/blink/renderer/bindings/modules/v8/generated.gni
@@ -133,6 +133,8 @@
   "$bindings_modules_v8_output_dir/v8_rtc_session_description_callback.h",
   "$bindings_modules_v8_output_dir/v8_rtc_stats_callback.cc",
   "$bindings_modules_v8_output_dir/v8_rtc_stats_callback.h",
+  "$bindings_modules_v8_output_dir/v8_state_callback.cc",
+  "$bindings_modules_v8_output_dir/v8_state_callback.h",
   "$bindings_modules_v8_output_dir/v8_storage_error_callback.cc",
   "$bindings_modules_v8_output_dir/v8_storage_error_callback.h",
   "$bindings_modules_v8_output_dir/v8_storage_quota_callback.cc",
diff --git a/third_party/blink/renderer/bindings/scripts/interface_dependency_resolver.py b/third_party/blink/renderer/bindings/scripts/interface_dependency_resolver.py
index ad5f3c2..318f9e6b 100644
--- a/third_party/blink/renderer/bindings/scripts/interface_dependency_resolver.py
+++ b/third_party/blink/renderer/bindings/scripts/interface_dependency_resolver.py
@@ -221,8 +221,7 @@
             # However,
             # - An interface defined in core cannot include an interface mixin
             #   defined in modules.
-            if (not dependency_interface.is_mixin and
-                    'NoInterfaceObject' not in dependency_interface.extended_attributes):
+            if not dependency_interface.is_mixin:
                 raise Exception('The interface:%s cannot implement '
                                 'the non-mixin interface: %s.' % (
                                     target_interface.name,
diff --git a/third_party/blink/renderer/bindings/scripts/scripts.gni b/third_party/blink/renderer/bindings/scripts/scripts.gni
index ec05dfe..7d31660 100644
--- a/third_party/blink/renderer/bindings/scripts/scripts.gni
+++ b/third_party/blink/renderer/bindings/scripts/scripts.gni
@@ -485,8 +485,10 @@
     # should be interfaces_info_core (w/o modules). This is the same issue as
     # above in the idl_compiler template.
     # http://crbug.com/358074
-    deps = invoker.deps +
-           [ "//third_party/blink/renderer/bindings/modules:interfaces_info" ]
+    deps = invoker.deps + [
+             "//third_party/blink/renderer/bindings/modules:interfaces_info",
+             "//third_party/blink/renderer/bindings/modules:modules_core_global_constructors_idls",
+           ]
   }
 }
 
diff --git a/third_party/blink/renderer/core/css/font_face_source.idl b/third_party/blink/renderer/core/css/font_face_source.idl
index c7263bf..50490ea 100644
--- a/third_party/blink/renderer/core/css/font_face_source.idl
+++ b/third_party/blink/renderer/core/css/font_face_source.idl
@@ -5,8 +5,7 @@
 // spec: https://drafts.csswg.org/css-font-loading/#font-face-source
 
 [
-  LegacyTreatAsPartialInterface,
-  NoInterfaceObject
-] interface FontFaceSource {
+  LegacyTreatAsPartialInterface
+] interface mixin FontFaceSource {
   [MeasureAs=DocumentFonts] readonly attribute FontFaceSet fonts;
 };
diff --git a/third_party/blink/renderer/core/dom/accessibility_role.idl b/third_party/blink/renderer/core/dom/accessibility_role.idl
index cda5216e..22454d63 100644
--- a/third_party/blink/renderer/core/dom/accessibility_role.idl
+++ b/third_party/blink/renderer/core/dom/accessibility_role.idl
@@ -5,9 +5,8 @@
 // ARIA reflection
 // https://w3c.github.io/aria/#idl-interface
 [
-    NoInterfaceObject,
     RuntimeEnabled=AccessibilityObjectModel
-] interface AccessibilityRole {
+] interface mixin AccessibilityRole {
   [CEReactions, Reflect] attribute DOMString? role;
 };
 
diff --git a/third_party/blink/renderer/core/dom/aria_attributes.idl b/third_party/blink/renderer/core/dom/aria_attributes.idl
index 3d6d7c8..e4271c3 100644
--- a/third_party/blink/renderer/core/dom/aria_attributes.idl
+++ b/third_party/blink/renderer/core/dom/aria_attributes.idl
@@ -5,9 +5,8 @@
 // ARIA reflection
 // https://w3c.github.io/aria/#idl-interface
 [
-    NoInterfaceObject,
     RuntimeEnabled=AccessibilityObjectModel
-] interface AriaAttributes {
+] interface mixin AriaAttributes {
     [CEReactions, Reflect=aria_activedescendant] attribute DOMString? ariaActiveDescendant;
     [CEReactions, Reflect=aria_atomic] attribute DOMString? ariaAtomic;
     [CEReactions, Reflect=aria_autocomplete] attribute DOMString? ariaAutoComplete;
diff --git a/third_party/blink/renderer/core/dom/child_node.idl b/third_party/blink/renderer/core/dom/child_node.idl
index 1d71889..f455f95 100644
--- a/third_party/blink/renderer/core/dom/child_node.idl
+++ b/third_party/blink/renderer/core/dom/child_node.idl
@@ -23,9 +23,8 @@
 // https://dom.spec.whatwg.org/#interface-childnode
 
 [
-    LegacyTreatAsPartialInterface,
-    NoInterfaceObject // Always used on target of 'implements'
-] interface ChildNode {
+    LegacyTreatAsPartialInterface
+] interface mixin ChildNode {
     [Unscopable, RaisesException, CEReactions, CustomElementCallbacks] void before((Node or DOMString) ... nodes);
     [Unscopable, RaisesException, CEReactions, CustomElementCallbacks] void after((Node or DOMString)... nodes);
     [Unscopable, RaisesException, CEReactions, CustomElementCallbacks] void replaceWith((Node or DOMString)... nodes);
diff --git a/third_party/blink/renderer/core/dom/document_and_element_event_handlers.idl b/third_party/blink/renderer/core/dom/document_and_element_event_handlers.idl
index a42404f..73735f8 100644
--- a/third_party/blink/renderer/core/dom/document_and_element_event_handlers.idl
+++ b/third_party/blink/renderer/core/dom/document_and_element_event_handlers.idl
@@ -5,9 +5,8 @@
 // https://html.spec.whatwg.org/C/#documentandelementeventhandlers
 
 [
-    LegacyTreatAsPartialInterface,
-    NoInterfaceObject // Always used on target of 'implements'
-]  interface DocumentAndElementEventHandlers {
+    LegacyTreatAsPartialInterface
+] interface mixin DocumentAndElementEventHandlers {
   attribute EventHandler oncopy;
   attribute EventHandler oncut;
   attribute EventHandler onpaste;
diff --git a/third_party/blink/renderer/core/dom/document_or_shadow_root.idl b/third_party/blink/renderer/core/dom/document_or_shadow_root.idl
index 69e7f36..f637e1a98 100644
--- a/third_party/blink/renderer/core/dom/document_or_shadow_root.idl
+++ b/third_party/blink/renderer/core/dom/document_or_shadow_root.idl
@@ -5,9 +5,8 @@
 // https://dom.spec.whatwg.org/#mixin-documentorshadowroot
 // https://w3c.github.io/webcomponents/spec/shadow/#extensions-to-the-documentorshadowroot-mixin
 [
-    LegacyTreatAsPartialInterface,
-    NoInterfaceObject
-] interface DocumentOrShadowRoot {
+    LegacyTreatAsPartialInterface
+] interface mixin DocumentOrShadowRoot {
     // Selection API
     // https://w3c.github.io/selection-api/#extensions-to-document-interface
     [Affects=Nothing] Selection? getSelection();
diff --git a/third_party/blink/renderer/core/dom/global_event_handlers.idl b/third_party/blink/renderer/core/dom/global_event_handlers.idl
index 1fd17f5..aa26190 100644
--- a/third_party/blink/renderer/core/dom/global_event_handlers.idl
+++ b/third_party/blink/renderer/core/dom/global_event_handlers.idl
@@ -30,9 +30,8 @@
 // https://html.spec.whatwg.org/C/#globaleventhandlers
 
 [
-    LegacyTreatAsPartialInterface,
-    NoInterfaceObject // Always used on target of 'implements'
-] interface GlobalEventHandlers {
+    LegacyTreatAsPartialInterface
+] interface mixin GlobalEventHandlers {
     attribute EventHandler onabort;
     [RuntimeEnabled=InvisibleDOM] attribute EventHandler onactivateinvisible;
     [RuntimeEnabled=DisplayLocking] attribute EventHandler onbeforeactivate;
diff --git a/third_party/blink/renderer/core/dom/non_document_type_child_node.idl b/third_party/blink/renderer/core/dom/non_document_type_child_node.idl
index d186e00fd..9fd6196 100644
--- a/third_party/blink/renderer/core/dom/non_document_type_child_node.idl
+++ b/third_party/blink/renderer/core/dom/non_document_type_child_node.idl
@@ -5,9 +5,8 @@
 // https://dom.spec.whatwg.org/#interface-nondocumenttypechildnode
 
 [
-    LegacyTreatAsPartialInterface,
-    NoInterfaceObject // Always used on target of 'implements'
-] interface NonDocumentTypeChildNode {
+    LegacyTreatAsPartialInterface
+] interface mixin NonDocumentTypeChildNode {
     [PerWorldBindings] readonly attribute Element? previousElementSibling;
     [PerWorldBindings] readonly attribute Element? nextElementSibling;
 };
diff --git a/third_party/blink/renderer/core/dom/non_element_parent_node.idl b/third_party/blink/renderer/core/dom/non_element_parent_node.idl
index 03511a6..572d925 100644
--- a/third_party/blink/renderer/core/dom/non_element_parent_node.idl
+++ b/third_party/blink/renderer/core/dom/non_element_parent_node.idl
@@ -5,8 +5,7 @@
 // https://dom.spec.whatwg.org/#interface-nonelementparentnode
 
 [
-    LegacyTreatAsPartialInterface,
-    NoInterfaceObject // Always used on target of 'implements'
-] interface NonElementParentNode {
+    LegacyTreatAsPartialInterface
+] interface mixin NonElementParentNode {
     [PerWorldBindings] Element? getElementById(DOMString elementId);
 };
diff --git a/third_party/blink/renderer/core/dom/nonced_element.idl b/third_party/blink/renderer/core/dom/nonced_element.idl
index cf3b6d7..1800aa7c 100644
--- a/third_party/blink/renderer/core/dom/nonced_element.idl
+++ b/third_party/blink/renderer/core/dom/nonced_element.idl
@@ -6,8 +6,6 @@
 //
 // TODO(mkwst): Update the link iff this lands.
 
-[
-    NoInterfaceObject
-] interface NoncedElement {
+interface mixin NoncedElement {
     [CEReactions] attribute DOMString nonce;
 };
diff --git a/third_party/blink/renderer/core/dom/parent_node.idl b/third_party/blink/renderer/core/dom/parent_node.idl
index 546a6305..6c99394 100644
--- a/third_party/blink/renderer/core/dom/parent_node.idl
+++ b/third_party/blink/renderer/core/dom/parent_node.idl
@@ -31,9 +31,8 @@
 // https://dom.spec.whatwg.org/#interface-parentnode
 
 [
-    LegacyTreatAsPartialInterface,
-    NoInterfaceObject // Always used on target of 'implements'
-] interface ParentNode {
+    LegacyTreatAsPartialInterface
+] interface mixin ParentNode {
     [Affects=Nothing, SameObject, PerWorldBindings] readonly attribute HTMLCollection children;
     [Affects=Nothing, PerWorldBindings] readonly attribute Element? firstElementChild;
     [Affects=Nothing, PerWorldBindings] readonly attribute Element? lastElementChild;
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc
index 6baa3d4..a9368fdf 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -1628,11 +1628,6 @@
   }
 }
 
-void WebViewImpl::RequestPresentationCallbackForTesting(
-    base::OnceClosure callback) {
-  layer_tree_view_->RequestPresentationCallback(std::move(callback));
-}
-
 void WebViewImpl::PaintContent(cc::PaintCanvas* canvas, const WebRect& rect) {
   // This should only be used when compositing is not being used for this
   // WebView, and it is painting into the recording of its parent.
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.h b/third_party/blink/renderer/core/exported/web_view_impl.h
index 0243b305d6..e537691c 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.h
+++ b/third_party/blink/renderer/core/exported/web_view_impl.h
@@ -446,8 +446,6 @@
   void RecordEndOfFrameMetrics(base::TimeTicks frame_begin_time) override;
   void UpdateLifecycle(LifecycleUpdate requested_update,
                        LifecycleUpdateReason reason) override;
-  void RequestPresentationCallbackForTesting(
-      base::OnceClosure callback) override;
   void PaintContent(cc::PaintCanvas*, const WebRect&) override;
   void ThemeChanged() override;
   WebInputEventResult HandleInputEvent(const WebCoalescedInputEvent&) override;
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy.cc b/third_party/blink/renderer/core/feature_policy/feature_policy.cc
index bbfcf65..0c8b6fd 100644
--- a/third_party/blink/renderer/core/feature_policy/feature_policy.cc
+++ b/third_party/blink/renderer/core/feature_policy/feature_policy.cc
@@ -45,6 +45,44 @@
   return PolicyValue(false);
 }
 
+PolicyValue ParseValueForType(mojom::PolicyValueType feature_type,
+                              const String& value_string,
+                              bool* ok) {
+  *ok = false;
+  PolicyValue value;
+  switch (feature_type) {
+    case mojom::PolicyValueType::kBool:
+      // recognize true, false
+      if (value_string.LowerASCII() == "true") {
+        value = PolicyValue(true);
+        *ok = true;
+      } else if (value_string.LowerASCII() == "false") {
+        value = PolicyValue(false);
+        *ok = true;
+      }
+      break;
+    case mojom::PolicyValueType::kDecDouble: {
+      if (value_string.LowerASCII() == "inf") {
+        value = PolicyValue::CreateMaxPolicyValue(feature_type);
+        *ok = true;
+      } else {
+        double parsed_value = value_string.ToDouble(ok);
+        if (*ok && parsed_value >= 0.0f) {
+          value = PolicyValue(parsed_value);
+        } else {
+          *ok = false;
+        }
+      }
+      break;
+    }
+    default:
+      NOTREACHED();
+  }
+  if (!*ok)
+    return PolicyValue();
+  return value;
+}
+
 }  // namespace
 
 ParsedFeaturePolicy ParseFeaturePolicyHeader(
@@ -160,43 +198,109 @@
       }
 
       for (wtf_size_t i = 1; i < tokens.size(); i++) {
-        // TODO(loonybear): for each token, parse the policy value from the
-        // new syntax.
         if (!tokens[i].ContainsOnlyASCIIOrEmpty()) {
           messages->push_back("Non-ASCII characters in origin.");
           continue;
         }
-        if (EqualIgnoringASCIICase(tokens[i], "'self'")) {
-          values[self_origin->ToUrlOrigin()] = value;
-        } else if (src_origin && EqualIgnoringASCIICase(tokens[i], "'src'")) {
-          // Only the iframe allow attribute can define |src_origin|.
-          // When parsing feature policy header, 'src' is disallowed and
-          // |src_origin| = nullptr.
-          // If the iframe will have an opaque origin (for example, if it is
-          // sandboxed, or has a data: URL), then 'src' needs to refer to the
-          // opaque origin of the frame, which is not known yet. In this case,
-          // the |opaque_value| on the declaration is set, rather than adding
-          // an origin to the allowlist.
-          if (src_origin->IsOpaque()) {
-            allowlist.opaque_value = value;
-          } else {
-            values[src_origin->ToUrlOrigin()] = value;
+
+        // Break the token into an origin and a value. Either one may be
+        // omitted.
+        PolicyValue value = PolicyValue::CreateMaxPolicyValue(feature_type);
+        String origin_string = tokens[i];
+        String value_string;
+        wtf_size_t param_start = origin_string.find('(');
+        if (param_start != kNotFound) {
+          // There is a value attached to this origin
+          if (!origin_string.EndsWith(')')) {
+            // The declaration is malformed if the value is not the last part of
+            // the string.
+            if (messages)
+              messages->push_back("Unable to parse policy value.");
+            continue;
           }
-        } else if (EqualIgnoringASCIICase(tokens[i], "'none'")) {
+          value_string = origin_string.Substring(
+              param_start + 1, origin_string.length() - param_start - 2);
+          origin_string = origin_string.Substring(0, param_start);
+          bool ok = false;
+          value = ParseValueForType(feature_type, value_string, &ok);
+          if (!ok) {
+            if (messages)
+              messages->push_back("Unable to parse policy value.");
+            continue;
+          }
+        }
+
+        // Determine the target of the declaration. This may be a specific
+        // origin, either explicitly written, or one of the special keywords
+        // 'self' or 'src'. ('src' can only be used in the iframe allow
+        // attribute.)
+        url::Origin target_origin;
+
+        // If the iframe will have an opaque origin (for example, if it is
+        // sandboxed, or has a data: URL), then 'src' needs to refer to the
+        // opaque origin of the frame, which is not known yet. In this case,
+        // the |opaque_value| on the declaration is set, rather than adding
+        // an origin to the allowlist.
+        bool target_is_opaque = false;
+        bool target_is_all = false;
+
+        // 'self' origin is used if either the origin is omitted (and there is
+        // no 'src' origin available) or the origin is exactly 'self'.
+        if ((origin_string.length() == 0 && !src_origin) ||
+            EqualIgnoringASCIICase(origin_string, "'self'")) {
+          target_origin = self_origin->ToUrlOrigin();
+        }
+        // 'src' origin is used if |src_origin| is available and either the
+        // origin is omitted or is a match for 'src'. |src_origin| is only set
+        // when parsing an iframe allow attribute.
+        else if (src_origin &&
+                 (origin_string.length() == 0 ||
+                  EqualIgnoringASCIICase(origin_string, "'src'"))) {
+          if (!src_origin->IsOpaque()) {
+            target_origin = src_origin->ToUrlOrigin();
+          } else {
+            target_is_opaque = true;
+          }
+        } else if (EqualIgnoringASCIICase(origin_string, "'none'")) {
           continue;
-        } else if (tokens[i] == "*") {
+        } else if (origin_string == "*") {
+          target_is_all = true;
+        }
+        // Otherwise, parse the origin string and verify that the result is
+        // valid. Invalid strings will produce an opaque origin, which will
+        // result in an error message.
+        else {
+          scoped_refptr<SecurityOrigin> parsed_origin =
+              SecurityOrigin::CreateFromString(origin_string);
+          if (!parsed_origin->IsOpaque()) {
+            target_origin = parsed_origin->ToUrlOrigin();
+          } else if (messages) {
+            messages->push_back("Unrecognized origin: '" + origin_string +
+                                "'.");
+            continue;
+          }
+        }
+
+        // Assign the value to the target origin(s).
+        if (target_is_all) {
           allowlist.fallback_value = value;
           allowlist.opaque_value = value;
-          break;
+        } else if (target_is_opaque) {
+          allowlist.opaque_value = value;
         } else {
-          scoped_refptr<SecurityOrigin> target_origin =
-              SecurityOrigin::CreateFromString(tokens[i]);
-          if (!target_origin->IsOpaque())
-            values[target_origin->ToUrlOrigin()] = value;
-          else if (messages)
-            messages->push_back("Unrecognized origin: '" + tokens[i] + "'.");
+          DCHECK(!target_origin.opaque());
+          values[target_origin] = value;
         }
       }
+      // Size reduction: remove all items in the allowlist whose value is the
+      // same as the fallback.
+      for (auto it = values.begin(); it != values.end();) {
+        if (it->second == allowlist.fallback_value)
+          it = values.erase(it);
+        else
+          it++;
+      }
+
       allowlist.values = std::move(values);
       allowlists.push_back(allowlist);
     }
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy_test.cc b/third_party/blink/renderer/core/feature_policy/feature_policy_test.cc
index b408d15..be888f3 100644
--- a/third_party/blink/renderer/core/feature_policy/feature_policy_test.cc
+++ b/third_party/blink/renderer/core/feature_policy/feature_policy_test.cc
@@ -48,6 +48,18 @@
     "geolocation 'none' 'none' 'none'",
     "geolocation " ORIGIN_A " *",
     "fullscreen  " ORIGIN_A "; payment 'self'",
+    "fullscreen  " ORIGIN_A "(true)",
+    "fullscreen  " ORIGIN_A "(false)",
+    "fullscreen  " ORIGIN_A "(True)",
+    "fullscreen  " ORIGIN_A "(TRUE)",
+    "oversized-images " ORIGIN_A "(2.0)",
+    "oversized-images " ORIGIN_A "(0.0)",
+    "oversized-images " ORIGIN_A "(4)",
+    "oversized-images " ORIGIN_A "(20000)",
+    "oversized-images " ORIGIN_A "(2e50)",
+    "oversized-images " ORIGIN_A "(inf)",
+    "oversized-images " ORIGIN_A "(Inf)",
+    "oversized-images " ORIGIN_A "(INF)",
     "fullscreen " ORIGIN_A "; payment *, geolocation 'self'"};
 
 const char* const kInvalidPolicies[] = {
@@ -59,7 +71,17 @@
     "geolocation https:/bad,origin",
     "geolocation https://example.com, https://a.com",
     "geolocation *, payment data://badorigin",
-    "geolocation ws://xn--fd\xbcwsw3taaaaaBaa333aBBBBBBJBBJBBBt"};
+    "geolocation ws://xn--fd\xbcwsw3taaaaaBaa333aBBBBBBJBBJBBBt",
+    "fullscreen(true)",
+    "fullscreen  " ORIGIN_A "(notabool)",
+    "fullscreen " ORIGIN_A "(2.0)",
+    "oversized-images " ORIGIN_A "(true)",
+    "oversized-images " ORIGIN_A "(Something else)",
+    "oversized-images " ORIGIN_A "(1",
+    "oversized-images " ORIGIN_A "(-1)",
+    "oversized-images " ORIGIN_A "(1.2.3)",
+    "oversized-images " ORIGIN_A "(1.a.3)",
+    "fullscreen  " ORIGIN_A "()"};
 
 }  // namespace
 
@@ -89,8 +111,12 @@
 
   const PolicyValue min_value = PolicyValue(false);
   const PolicyValue max_value = PolicyValue(true);
-  const PolicyValue min_double_value =
+  const PolicyValue sample_double_value =
+      PolicyValue(1.5, mojom::PolicyValueType::kDecDouble);
+  const PolicyValue default_double_value =
       PolicyValue(2.0, mojom::PolicyValueType::kDecDouble);
+  const PolicyValue min_double_value =
+      PolicyValue::CreateMinPolicyValue(mojom::PolicyValueType::kDecDouble);
   const PolicyValue max_double_value =
       PolicyValue::CreateMaxPolicyValue(mojom::PolicyValueType::kDecDouble);
 };
@@ -101,7 +127,7 @@
     messages.clear();
     ParseFeaturePolicy(policy_string, origin_a_.get(), origin_b_.get(),
                        &messages, test_feature_name_map);
-    EXPECT_EQ(0UL, messages.size());
+    EXPECT_EQ(0UL, messages.size()) << "Should parse " << policy_string;
   }
 }
 
@@ -111,7 +137,7 @@
     messages.clear();
     ParseFeaturePolicy(policy_string, origin_a_.get(), origin_b_.get(),
                        &messages, test_feature_name_map);
-    EXPECT_LT(0UL, messages.size());
+    EXPECT_LT(0UL, messages.size()) << "Should fail to parse " << policy_string;
   }
 }
 
@@ -303,6 +329,308 @@
       expected_url_origin_b_));
 }
 
+TEST_F(FeaturePolicyParserTest, BooleanPolicyParametersParsedCorrectly) {
+  Vector<String> messages;
+  ParsedFeaturePolicy parsed_policy;
+
+  // Test no origin specified, in a container policy context.
+  // (true)
+  parsed_policy =
+      ParseFeaturePolicy("fullscreen (true)", origin_a_.get(), origin_b_.get(),
+                         &messages, test_feature_name_map);
+  EXPECT_EQ(1UL, parsed_policy.size());
+  EXPECT_EQ(mojom::FeaturePolicyFeature::kFullscreen, parsed_policy[0].feature);
+  EXPECT_EQ(min_value, parsed_policy[0].fallback_value);
+  EXPECT_EQ(min_value, parsed_policy[0].opaque_value);
+  EXPECT_EQ(1UL, parsed_policy[0].values.size());
+  EXPECT_TRUE(parsed_policy[0].values.begin()->first.IsSameOriginWith(
+      expected_url_origin_b_));
+  EXPECT_EQ(max_value, parsed_policy[0].values.begin()->second);
+
+  // Test no origin specified, in a header context.
+  // (true)
+  parsed_policy = ParseFeaturePolicy("fullscreen (true)", origin_a_.get(),
+                                     nullptr, &messages, test_feature_name_map);
+  EXPECT_EQ(1UL, parsed_policy.size());
+  EXPECT_EQ(mojom::FeaturePolicyFeature::kFullscreen, parsed_policy[0].feature);
+  EXPECT_EQ(min_value, parsed_policy[0].fallback_value);
+  EXPECT_EQ(min_value, parsed_policy[0].opaque_value);
+  EXPECT_EQ(1UL, parsed_policy[0].values.size());
+  EXPECT_TRUE(parsed_policy[0].values.begin()->first.IsSameOriginWith(
+      expected_url_origin_a_));
+  EXPECT_EQ(max_value, parsed_policy[0].values.begin()->second);
+
+  // Test no origin specified, in a sandboxed container policy context.
+  // (true)
+  scoped_refptr<SecurityOrigin> opaque_origin =
+      SecurityOrigin::CreateUniqueOpaque();
+  parsed_policy =
+      ParseFeaturePolicy("fullscreen (true)", origin_a_.get(), opaque_origin,
+                         &messages, test_feature_name_map);
+  EXPECT_EQ(1UL, parsed_policy.size());
+  EXPECT_EQ(mojom::FeaturePolicyFeature::kFullscreen, parsed_policy[0].feature);
+  EXPECT_EQ(min_value, parsed_policy[0].fallback_value);
+  EXPECT_EQ(max_value, parsed_policy[0].opaque_value);
+  EXPECT_EQ(0UL, parsed_policy[0].values.size());
+
+  // 'self'(true)
+  parsed_policy =
+      ParseFeaturePolicy("fullscreen 'self'(true)", origin_a_.get(),
+                         origin_b_.get(), &messages, test_feature_name_map);
+  EXPECT_EQ(1UL, parsed_policy.size());
+
+  EXPECT_EQ(mojom::FeaturePolicyFeature::kFullscreen, parsed_policy[0].feature);
+  EXPECT_EQ(min_value, parsed_policy[0].fallback_value);
+  EXPECT_EQ(min_value, parsed_policy[0].opaque_value);
+  EXPECT_EQ(1UL, parsed_policy[0].values.size());
+  EXPECT_TRUE(parsed_policy[0].values.begin()->first.IsSameOriginWith(
+      expected_url_origin_a_));
+  EXPECT_EQ(max_value, parsed_policy[0].values.begin()->second);
+
+  // *(false)
+  parsed_policy =
+      ParseFeaturePolicy("fullscreen *(false)", origin_a_.get(),
+                         origin_b_.get(), &messages, test_feature_name_map);
+  EXPECT_EQ(1UL, parsed_policy.size());
+
+  EXPECT_EQ(mojom::FeaturePolicyFeature::kFullscreen, parsed_policy[0].feature);
+  EXPECT_EQ(min_value, parsed_policy[0].fallback_value);
+  EXPECT_EQ(min_value, parsed_policy[0].opaque_value);
+  EXPECT_EQ(0UL, parsed_policy[0].values.size());
+
+  // *(true)
+  parsed_policy =
+      ParseFeaturePolicy("fullscreen *(true)", origin_a_.get(), origin_b_.get(),
+                         &messages, test_feature_name_map);
+  EXPECT_EQ(1UL, parsed_policy.size());
+
+  EXPECT_EQ(mojom::FeaturePolicyFeature::kFullscreen, parsed_policy[0].feature);
+  EXPECT_EQ(max_value, parsed_policy[0].fallback_value);
+  EXPECT_EQ(max_value, parsed_policy[0].opaque_value);
+  EXPECT_EQ(0UL, parsed_policy[0].values.size());
+}
+
+TEST_F(FeaturePolicyParserTest, DoublePolicyParametersParsedCorrectly) {
+  Vector<String> messages;
+  ParsedFeaturePolicy parsed_policy;
+
+  // 'self'(inf)
+  parsed_policy =
+      ParseFeaturePolicy("oversized-images 'self'(inf)", origin_a_.get(),
+                         origin_b_.get(), &messages, test_feature_name_map);
+  EXPECT_EQ(1UL, parsed_policy.size());
+
+  EXPECT_EQ(mojom::FeaturePolicyFeature::kOversizedImages,
+            parsed_policy[0].feature);
+  EXPECT_EQ(default_double_value, parsed_policy[0].fallback_value);
+  EXPECT_EQ(default_double_value, parsed_policy[0].opaque_value);
+  EXPECT_EQ(1UL, parsed_policy[0].values.size());
+  EXPECT_TRUE(parsed_policy[0].values.begin()->first.IsSameOriginWith(
+      expected_url_origin_a_));
+  EXPECT_EQ(max_double_value, parsed_policy[0].values.begin()->second);
+
+  // 'self'(1.5)
+  parsed_policy =
+      ParseFeaturePolicy("oversized-images 'self'(1.5)", origin_a_.get(),
+                         origin_b_.get(), &messages, test_feature_name_map);
+  EXPECT_EQ(1UL, parsed_policy.size());
+
+  EXPECT_EQ(mojom::FeaturePolicyFeature::kOversizedImages,
+            parsed_policy[0].feature);
+  EXPECT_EQ(default_double_value, parsed_policy[0].fallback_value);
+  EXPECT_EQ(default_double_value, parsed_policy[0].opaque_value);
+  EXPECT_EQ(1UL, parsed_policy[0].values.size());
+  EXPECT_TRUE(parsed_policy[0].values.begin()->first.IsSameOriginWith(
+      expected_url_origin_a_));
+  EXPECT_EQ(sample_double_value, parsed_policy[0].values.begin()->second);
+
+  // *(inf)
+  parsed_policy =
+      ParseFeaturePolicy("oversized-images *(inf)", origin_a_.get(),
+                         origin_b_.get(), &messages, test_feature_name_map);
+  EXPECT_EQ(1UL, parsed_policy.size());
+
+  EXPECT_EQ(mojom::FeaturePolicyFeature::kOversizedImages,
+            parsed_policy[0].feature);
+  EXPECT_EQ(max_double_value, parsed_policy[0].fallback_value);
+  EXPECT_EQ(max_double_value, parsed_policy[0].opaque_value);
+  EXPECT_EQ(0UL, parsed_policy[0].values.size());
+
+  // *(0)
+  parsed_policy =
+      ParseFeaturePolicy("oversized-images *(0)", origin_a_.get(),
+                         origin_b_.get(), &messages, test_feature_name_map);
+  EXPECT_EQ(1UL, parsed_policy.size());
+
+  EXPECT_EQ(mojom::FeaturePolicyFeature::kOversizedImages,
+            parsed_policy[0].feature);
+  EXPECT_EQ(min_double_value, parsed_policy[0].fallback_value);
+  EXPECT_EQ(min_double_value, parsed_policy[0].opaque_value);
+  EXPECT_EQ(0UL, parsed_policy[0].values.size());
+
+  // *(1.5)
+  parsed_policy =
+      ParseFeaturePolicy("oversized-images *(1.5)", origin_a_.get(),
+                         origin_b_.get(), &messages, test_feature_name_map);
+  EXPECT_EQ(1UL, parsed_policy.size());
+
+  EXPECT_EQ(mojom::FeaturePolicyFeature::kOversizedImages,
+            parsed_policy[0].feature);
+  EXPECT_EQ(sample_double_value, parsed_policy[0].fallback_value);
+  EXPECT_EQ(sample_double_value, parsed_policy[0].opaque_value);
+  EXPECT_EQ(0UL, parsed_policy[0].values.size());
+
+  // 'self'(1.5) 'src'(inf)
+  // Fallbacks should be default values.
+  parsed_policy = ParseFeaturePolicy("oversized-images 'self'(1.5) 'src'(inf)",
+                                     origin_a_.get(), origin_b_.get(),
+                                     &messages, test_feature_name_map);
+  EXPECT_EQ(1UL, parsed_policy.size());
+
+  EXPECT_EQ(mojom::FeaturePolicyFeature::kOversizedImages,
+            parsed_policy[0].feature);
+  EXPECT_EQ(default_double_value, parsed_policy[0].fallback_value);
+  EXPECT_EQ(default_double_value, parsed_policy[0].opaque_value);
+  EXPECT_EQ(2UL, parsed_policy[0].values.size());
+  auto origin_and_value = parsed_policy[0].values.begin();
+  EXPECT_TRUE(origin_and_value->first.IsSameOriginWith(expected_url_origin_a_));
+  EXPECT_EQ(sample_double_value, origin_and_value->second);
+  origin_and_value++;
+  EXPECT_TRUE(origin_and_value->first.IsSameOriginWith(expected_url_origin_b_));
+  EXPECT_EQ(max_double_value, origin_and_value->second);
+
+  // *(1.5) 'src'(inf)
+  // Fallbacks should be 1.5
+  parsed_policy =
+      ParseFeaturePolicy("oversized-images *(1.5) 'src'(inf)", origin_a_.get(),
+                         origin_b_.get(), &messages, test_feature_name_map);
+  EXPECT_EQ(1UL, parsed_policy.size());
+
+  EXPECT_EQ(mojom::FeaturePolicyFeature::kOversizedImages,
+            parsed_policy[0].feature);
+  EXPECT_EQ(sample_double_value, parsed_policy[0].fallback_value);
+  EXPECT_EQ(sample_double_value, parsed_policy[0].opaque_value);
+  EXPECT_EQ(1UL, parsed_policy[0].values.size());
+  origin_and_value = parsed_policy[0].values.begin();
+  EXPECT_TRUE(origin_and_value->first.IsSameOriginWith(expected_url_origin_b_));
+  EXPECT_EQ(max_double_value, origin_and_value->second);
+
+  // Test policy: 'self'(1.5) https://example.org(inf)
+  // Fallbacks should be default value.
+  parsed_policy = ParseFeaturePolicy(
+      "oversized-images 'self'(1.5) " ORIGIN_C "(inf)", origin_a_.get(),
+      origin_b_.get(), &messages, test_feature_name_map);
+  EXPECT_EQ(1UL, parsed_policy.size());
+
+  EXPECT_EQ(mojom::FeaturePolicyFeature::kOversizedImages,
+            parsed_policy[0].feature);
+  EXPECT_EQ(default_double_value, parsed_policy[0].fallback_value);
+  EXPECT_EQ(default_double_value, parsed_policy[0].opaque_value);
+  EXPECT_EQ(2UL, parsed_policy[0].values.size());
+  origin_and_value = parsed_policy[0].values.begin();
+  EXPECT_TRUE(origin_and_value->first.IsSameOriginWith(expected_url_origin_a_));
+  EXPECT_EQ(sample_double_value, origin_and_value->second);
+  origin_and_value++;
+  EXPECT_TRUE(origin_and_value->first.IsSameOriginWith(expected_url_origin_c_));
+  EXPECT_EQ(max_double_value, origin_and_value->second);
+
+  // Test policy: 'self'(1.5) https://example.org(inf) *(0)
+  // Fallbacks should be 0.
+  parsed_policy = ParseFeaturePolicy(
+      "oversized-images 'self'(1.5) " ORIGIN_C "(inf) *(0)", origin_a_.get(),
+      origin_b_.get(), &messages, test_feature_name_map);
+  EXPECT_EQ(1UL, parsed_policy.size());
+
+  EXPECT_EQ(mojom::FeaturePolicyFeature::kOversizedImages,
+            parsed_policy[0].feature);
+  EXPECT_EQ(min_double_value, parsed_policy[0].fallback_value);
+  EXPECT_EQ(min_double_value, parsed_policy[0].opaque_value);
+  EXPECT_EQ(2UL, parsed_policy[0].values.size());
+  origin_and_value = parsed_policy[0].values.begin();
+  EXPECT_TRUE(origin_and_value->first.IsSameOriginWith(expected_url_origin_a_));
+  EXPECT_EQ(sample_double_value, origin_and_value->second);
+  origin_and_value++;
+  EXPECT_TRUE(origin_and_value->first.IsSameOriginWith(expected_url_origin_c_));
+  EXPECT_EQ(max_double_value, origin_and_value->second);
+}
+
+TEST_F(FeaturePolicyParserTest, RedundantBooleanItemsRemoved) {
+  Vector<String> messages;
+  ParsedFeaturePolicy parsed_policy;
+
+  // 'self'(true) *(true)
+  parsed_policy =
+      ParseFeaturePolicy("fullscreen 'self'(true) *(true)", origin_a_.get(),
+                         origin_b_.get(), &messages, test_feature_name_map);
+  EXPECT_EQ(1UL, parsed_policy.size());
+
+  EXPECT_EQ(mojom::FeaturePolicyFeature::kFullscreen, parsed_policy[0].feature);
+  EXPECT_EQ(max_value, parsed_policy[0].fallback_value);
+  EXPECT_EQ(max_value, parsed_policy[0].opaque_value);
+  EXPECT_EQ(0UL, parsed_policy[0].values.size());
+
+  // 'self'(false)
+  parsed_policy =
+      ParseFeaturePolicy("fullscreen 'self'(false)", origin_a_.get(),
+                         origin_b_.get(), &messages, test_feature_name_map);
+  EXPECT_EQ(1UL, parsed_policy.size());
+
+  EXPECT_EQ(mojom::FeaturePolicyFeature::kFullscreen, parsed_policy[0].feature);
+  EXPECT_EQ(min_value, parsed_policy[0].fallback_value);
+  EXPECT_EQ(min_value, parsed_policy[0].opaque_value);
+  EXPECT_EQ(0UL, parsed_policy[0].values.size());
+
+  // (true)
+  parsed_policy =
+      ParseFeaturePolicy("fullscreen (false)", origin_a_.get(), origin_b_.get(),
+                         &messages, test_feature_name_map);
+  EXPECT_EQ(1UL, parsed_policy.size());
+  EXPECT_EQ(mojom::FeaturePolicyFeature::kFullscreen, parsed_policy[0].feature);
+  EXPECT_EQ(min_value, parsed_policy[0].fallback_value);
+  EXPECT_EQ(min_value, parsed_policy[0].opaque_value);
+  EXPECT_EQ(0UL, parsed_policy[0].values.size());
+}
+
+TEST_F(FeaturePolicyParserTest, RedundantDoubleItemsRemoved) {
+  Vector<String> messages;
+  ParsedFeaturePolicy parsed_policy;
+
+  // 'self'(1.5) *(1.5)
+  parsed_policy =
+      ParseFeaturePolicy("oversized-images 'self'(1.5) *(1.5)", origin_a_.get(),
+                         origin_b_.get(), &messages, test_feature_name_map);
+  EXPECT_EQ(1UL, parsed_policy.size());
+
+  EXPECT_EQ(mojom::FeaturePolicyFeature::kOversizedImages,
+            parsed_policy[0].feature);
+  EXPECT_EQ(sample_double_value, parsed_policy[0].fallback_value);
+  EXPECT_EQ(sample_double_value, parsed_policy[0].opaque_value);
+  EXPECT_EQ(0UL, parsed_policy[0].values.size());
+
+  // 'self'(inf)
+  parsed_policy =
+      ParseFeaturePolicy("oversized-images 'self'(2.0)", origin_a_.get(),
+                         origin_b_.get(), &messages, test_feature_name_map);
+  EXPECT_EQ(1UL, parsed_policy.size());
+
+  EXPECT_EQ(mojom::FeaturePolicyFeature::kOversizedImages,
+            parsed_policy[0].feature);
+  EXPECT_EQ(default_double_value, parsed_policy[0].fallback_value);
+  EXPECT_EQ(default_double_value, parsed_policy[0].opaque_value);
+  EXPECT_EQ(0UL, parsed_policy[0].values.size());
+
+  // (inf)
+  parsed_policy =
+      ParseFeaturePolicy("oversized-images (2.0)", origin_a_.get(),
+                         origin_b_.get(), &messages, test_feature_name_map);
+  EXPECT_EQ(1UL, parsed_policy.size());
+  EXPECT_EQ(mojom::FeaturePolicyFeature::kOversizedImages,
+            parsed_policy[0].feature);
+  EXPECT_EQ(default_double_value, parsed_policy[0].fallback_value);
+  EXPECT_EQ(default_double_value, parsed_policy[0].opaque_value);
+  EXPECT_EQ(0UL, parsed_policy[0].values.size());
+}
+
 // Test histogram counting the use of feature policies in header.
 TEST_F(FeaturePolicyParserTest, HeaderHistogram) {
   const char* histogram_name = "Blink.UseCounter.FeaturePolicy.Header";
@@ -418,7 +746,7 @@
 
   EXPECT_EQ(mojom::FeaturePolicyFeature::kOversizedImages,
             parsed_policy[0].feature);
-  EXPECT_GE(min_double_value, parsed_policy[0].fallback_value);
+  EXPECT_GE(default_double_value, parsed_policy[0].fallback_value);
   EXPECT_LE(max_double_value, parsed_policy[0].opaque_value);
   EXPECT_EQ(1UL, parsed_policy[0].values.size());
   EXPECT_LE(max_double_value, parsed_policy[0].values.begin()->second);
diff --git a/third_party/blink/renderer/core/fetch/body.idl b/third_party/blink/renderer/core/fetch/body.idl
index c9aa946..79a8952 100644
--- a/third_party/blink/renderer/core/fetch/body.idl
+++ b/third_party/blink/renderer/core/fetch/body.idl
@@ -5,9 +5,8 @@
 // https://fetch.spec.whatwg.org/#body
 
 [
-    ActiveScriptWrappable,
-    NoInterfaceObject
-] interface Body {
+    ActiveScriptWrappable
+] interface mixin Body {
     [RaisesException] readonly attribute boolean bodyUsed;
     [CallWith=ScriptState, NewObject, RaisesException] Promise<ArrayBuffer> arrayBuffer();
     [CallWith=ScriptState, NewObject, RaisesException] Promise<Blob> blob();
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index a6f7152..f0e2a6d 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -4405,4 +4405,28 @@
   }
 }
 
+#if DCHECK_IS_ON()
+LocalFrameView::DisallowLayoutInvalidationScope::
+    DisallowLayoutInvalidationScope(LocalFrameView* view)
+    : local_frame_view_(view) {
+  local_frame_view_->allows_layout_invalidation_after_layout_clean_ = false;
+  local_frame_view_->ForAllChildLocalFrameViews([](LocalFrameView& frame_view) {
+    if (!frame_view.ShouldThrottleRendering())
+      frame_view.CheckDoesNotNeedLayout();
+    frame_view.allows_layout_invalidation_after_layout_clean_ = false;
+  });
+}
+
+LocalFrameView::DisallowLayoutInvalidationScope::
+    ~DisallowLayoutInvalidationScope() {
+  local_frame_view_->allows_layout_invalidation_after_layout_clean_ = true;
+  local_frame_view_->ForAllChildLocalFrameViews([](LocalFrameView& frame_view) {
+    if (!frame_view.ShouldThrottleRendering())
+      frame_view.CheckDoesNotNeedLayout();
+    frame_view.allows_layout_invalidation_after_layout_clean_ = true;
+  });
+}
+
+#endif
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.h b/third_party/blink/renderer/core/frame/local_frame_view.h
index 82b27669..fa26378 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.h
+++ b/third_party/blink/renderer/core/frame/local_frame_view.h
@@ -734,25 +734,8 @@
     STACK_ALLOCATED();
 
    public:
-    DisallowLayoutInvalidationScope(LocalFrameView* view)
-        : local_frame_view_(view) {
-      local_frame_view_->allows_layout_invalidation_after_layout_clean_ = false;
-      local_frame_view_->ForAllChildLocalFrameViews(
-          [](LocalFrameView& frame_view) {
-            if (!frame_view.ShouldThrottleRendering())
-              frame_view.CheckDoesNotNeedLayout();
-            frame_view.allows_layout_invalidation_after_layout_clean_ = false;
-          });
-    }
-    ~DisallowLayoutInvalidationScope() {
-      local_frame_view_->allows_layout_invalidation_after_layout_clean_ = true;
-      local_frame_view_->ForAllChildLocalFrameViews(
-          [](LocalFrameView& frame_view) {
-            if (!frame_view.ShouldThrottleRendering())
-              frame_view.CheckDoesNotNeedLayout();
-            frame_view.allows_layout_invalidation_after_layout_clean_ = true;
-          });
-    }
+    explicit DisallowLayoutInvalidationScope(LocalFrameView* view);
+    ~DisallowLayoutInvalidationScope();
 
    private:
     UntracedMember<LocalFrameView> local_frame_view_;
diff --git a/third_party/blink/renderer/core/frame/navigator_automation_information.idl b/third_party/blink/renderer/core/frame/navigator_automation_information.idl
index e14af26..263575ea 100644
--- a/third_party/blink/renderer/core/frame/navigator_automation_information.idl
+++ b/third_party/blink/renderer/core/frame/navigator_automation_information.idl
@@ -2,12 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// https://w3c.github.io/webdriver/webdriver-spec.html#interface
+// https://w3c.github.io/webdriver/#interface
 
 [
-    NoInterfaceObject, // Always used on target of 'implements'
-    Exposed=(Window),
+    Exposed=Window,
     RuntimeEnabled=AutomationControlled
-] interface NavigatorAutomationInformation {
+] interface mixin NavigatorAutomationInformation {
     readonly attribute boolean webdriver;
 };
diff --git a/third_party/blink/renderer/core/frame/navigator_concurrent_hardware.idl b/third_party/blink/renderer/core/frame/navigator_concurrent_hardware.idl
index 8976b5f..f10248c8 100644
--- a/third_party/blink/renderer/core/frame/navigator_concurrent_hardware.idl
+++ b/third_party/blink/renderer/core/frame/navigator_concurrent_hardware.idl
@@ -5,8 +5,7 @@
 // https://html.spec.whatwg.org/C/#navigator.hardwareconcurrency
 
 [
-    NoInterfaceObject,
-    Exposed=(Window,Worker)
-] interface NavigatorConcurrentHardware {
+    Exposed=(Window, Worker)
+] interface mixin NavigatorConcurrentHardware {
     [HighEntropy, MeasureAs=NavigatorHardwareConcurrency] readonly attribute unsigned long long hardwareConcurrency;
 };
diff --git a/third_party/blink/renderer/core/frame/navigator_cookies.idl b/third_party/blink/renderer/core/frame/navigator_cookies.idl
index 12b9b92..3909adb 100644
--- a/third_party/blink/renderer/core/frame/navigator_cookies.idl
+++ b/third_party/blink/renderer/core/frame/navigator_cookies.idl
@@ -4,7 +4,6 @@
 
 // https://html.spec.whatwg.org/C/#cookies
 
-[NoInterfaceObject]
-interface NavigatorCookies {
+interface mixin NavigatorCookies {
   readonly attribute boolean cookieEnabled;
 };
diff --git a/third_party/blink/renderer/core/frame/navigator_device_memory.idl b/third_party/blink/renderer/core/frame/navigator_device_memory.idl
index 03e558c8..7a8e26b 100644
--- a/third_party/blink/renderer/core/frame/navigator_device_memory.idl
+++ b/third_party/blink/renderer/core/frame/navigator_device_memory.idl
@@ -5,9 +5,8 @@
 // https://github.com/w3c/device-memory#the-web-exposed-api
 
 [
-    NoInterfaceObject,
-    Exposed=(Window,Worker)
-] interface NavigatorDeviceMemory {
+    Exposed=(Window, Worker)
+] interface mixin NavigatorDeviceMemory {
     [HighEntropy,MeasureAs=NavigatorDeviceMemory,RuntimeEnabled=NavigatorDeviceMemory,SecureContext]
     readonly attribute float deviceMemory;
 };
diff --git a/third_party/blink/renderer/core/frame/navigator_id.idl b/third_party/blink/renderer/core/frame/navigator_id.idl
index a402a6968..803f5e2 100644
--- a/third_party/blink/renderer/core/frame/navigator_id.idl
+++ b/third_party/blink/renderer/core/frame/navigator_id.idl
@@ -31,9 +31,8 @@
 // https://html.spec.whatwg.org/C/#client-identification
 
 [
-    NoInterfaceObject, // Always used on target of 'implements'
     Exposed=(Window,Worker)
-] interface NavigatorID {
+] interface mixin NavigatorID {
     readonly attribute DOMString appCodeName; // constant "Mozilla"
     readonly attribute DOMString appName; // constant "Netscape"
     [HighEntropy, MeasureAs=NavigatorAppVersion] readonly attribute DOMString appVersion;
diff --git a/third_party/blink/renderer/core/frame/navigator_language.idl b/third_party/blink/renderer/core/frame/navigator_language.idl
index 7a72f53..cba24ed0 100644
--- a/third_party/blink/renderer/core/frame/navigator_language.idl
+++ b/third_party/blink/renderer/core/frame/navigator_language.idl
@@ -5,9 +5,8 @@
 // https://html.spec.whatwg.org/C/#language-preferences
 
 [
-    NoInterfaceObject,
-    Exposed=(Window,Worker)
-] interface NavigatorLanguage {
+    Exposed=(Window, Worker)
+] interface mixin NavigatorLanguage {
     [HighEntropy, MeasureAs=NavigatorLanguage] readonly attribute DOMString language;
     [CachedAttribute=IsLanguagesDirty, HighEntropy, MeasureAs=NavigatorLanguages] readonly attribute FrozenArray<DOMString> languages;
 };
diff --git a/third_party/blink/renderer/core/frame/navigator_on_line.idl b/third_party/blink/renderer/core/frame/navigator_on_line.idl
index 16dda054..4fcfb13 100644
--- a/third_party/blink/renderer/core/frame/navigator_on_line.idl
+++ b/third_party/blink/renderer/core/frame/navigator_on_line.idl
@@ -31,8 +31,7 @@
 // https://html.spec.whatwg.org/C/#navigator.online
 
 [
-    NoInterfaceObject, // Always used on target of 'implements'
     Exposed=(Window,Worker)
-] interface NavigatorOnLine {
+] interface mixin NavigatorOnLine {
     readonly attribute boolean onLine;
 };
diff --git a/third_party/blink/renderer/core/frame/navigator_user_agent.idl b/third_party/blink/renderer/core/frame/navigator_user_agent.idl
index b9aa924..a7bbe07 100644
--- a/third_party/blink/renderer/core/frame/navigator_user_agent.idl
+++ b/third_party/blink/renderer/core/frame/navigator_user_agent.idl
@@ -3,10 +3,10 @@
 // found in the LICENSE file.
 
 // https://github.com/WICG/ua-client-hints
+
 [
-    NoInterfaceObject, // Always used on target of 'implements'
     RuntimeEnabled=UserAgentClientHint,
     Exposed=Window
-] interface NavigatorUserAgent {
+] interface mixin NavigatorUserAgent {
   [SecureContext, CallWith=ScriptState] Promise<UserAgent> getUserAgent();
 };
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_base.cc b/third_party/blink/renderer/core/frame/web_frame_widget_base.cc
index d165254..8bc8bc1 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_base.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_base.cc
@@ -342,11 +342,10 @@
     base::OnceCallback<void(bool)> callback) {
   // If we have a LayerTreeView, propagate the request, otherwise fail it since
   // otherwise it would remain in a unresolved and unrejected state.
-  if (WebLayerTreeView* layer_tree_view = GetLayerTreeView()) {
-    layer_tree_view->RequestDecode(image, std::move(callback));
-  } else {
+  // TODO(danakj): This should be based on |does_composite| instead.
+  if (!GetLayerTreeView())
     std::move(callback).Run(false);
-  }
+  Client()->RequestDecode(image, std::move(callback));
 }
 
 void WebFrameWidgetBase::Trace(blink::Visitor* visitor) {
diff --git a/third_party/blink/renderer/core/frame/window_event_handlers.idl b/third_party/blink/renderer/core/frame/window_event_handlers.idl
index 595cf78..dba180e 100644
--- a/third_party/blink/renderer/core/frame/window_event_handlers.idl
+++ b/third_party/blink/renderer/core/frame/window_event_handlers.idl
@@ -30,9 +30,8 @@
 // https://html.spec.whatwg.org/C/#windoweventhandlers
 
 [
-    LegacyTreatAsPartialInterface,
-    NoInterfaceObject // Always used on target of 'implements'
-] interface WindowEventHandlers {
+    LegacyTreatAsPartialInterface
+] interface mixin WindowEventHandlers {
     attribute EventHandler onafterprint;
     attribute EventHandler onbeforeprint;
     // FIXME: onbeforeunload should be an OnBeforeUnloadEventHandler.
diff --git a/third_party/blink/renderer/core/frame/window_or_worker_global_scope.idl b/third_party/blink/renderer/core/frame/window_or_worker_global_scope.idl
index 219aab54..9acd0e6 100644
--- a/third_party/blink/renderer/core/frame/window_or_worker_global_scope.idl
+++ b/third_party/blink/renderer/core/frame/window_or_worker_global_scope.idl
@@ -40,9 +40,8 @@
 
 [
     LegacyTreatAsPartialInterface,
-    NoInterfaceObject, // Always used on target of 'implements'
     Exposed=(Window,Worker)
-] interface WindowOrWorkerGlobalScope {
+] interface mixin WindowOrWorkerGlobalScope {
     // base64 utility methods
     [RaisesException] DOMString btoa(DOMString btoa);
     [RaisesException] DOMString atob(DOMString atob);
diff --git a/third_party/blink/renderer/core/html/html_hyperlink_element_utils.idl b/third_party/blink/renderer/core/html/html_hyperlink_element_utils.idl
index ff9057d..e46519a 100644
--- a/third_party/blink/renderer/core/html/html_hyperlink_element_utils.idl
+++ b/third_party/blink/renderer/core/html/html_hyperlink_element_utils.idl
@@ -4,9 +4,7 @@
 
 // https://html.spec.whatwg.org/C/#htmlhyperlinkelementutils
 
-[
-    NoInterfaceObject // Always used on target of 'implements'
-] interface HTMLHyperlinkElementUtils {
+interface mixin HTMLHyperlinkElementUtils {
 
     [CEReactions, RaisesException=Setter] stringifier attribute URLString href;
     readonly attribute USVString origin;
diff --git a/third_party/blink/renderer/core/inspector/browser_protocol.pdl b/third_party/blink/renderer/core/inspector/browser_protocol.pdl
index 1d0800e..e7b2297 100644
--- a/third_party/blink/renderer/core/inspector/browser_protocol.pdl
+++ b/third_party/blink/renderer/core/inspector/browser_protocol.pdl
@@ -6405,6 +6405,13 @@
       # Configuration for memory dump triggers. Used only when "memory-infra" category is enabled.
       optional MemoryDumpConfig memoryDumpConfig
 
+  # Data format of a trace. Can be either the legacy JSON format or the
+  # protocol buffer format. Note that the JSON format will be deprecated soon.
+  type StreamFormat extends string
+    enum
+      json
+      proto
+
   # Compression type to use for traces returned via streams.
   type StreamCompression extends string
     enum
@@ -6448,6 +6455,9 @@
       optional enum transferMode
         ReportEvents
         ReturnAsStream
+      # Trace data format to use. This only applies when using `ReturnAsStream`
+      # transfer mode (defaults to `json`).
+      optional StreamFormat streamFormat
       # Compression format to use. This only applies when using `ReturnAsStream`
       # transfer mode (defaults to `none`)
       optional StreamCompression streamCompression
@@ -6476,6 +6486,8 @@
     parameters
       # A handle of the stream that holds resulting trace data.
       optional IO.StreamHandle stream
+      # Trace data format of returned stream.
+      optional StreamFormat traceFormat
       # Compression format of returned stream.
       optional StreamCompression streamCompression
 
diff --git a/third_party/blink/renderer/core/layout/layout_box_model_object.cc b/third_party/blink/renderer/core/layout/layout_box_model_object.cc
index 0909aaa..be5c65d 100644
--- a/third_party/blink/renderer/core/layout/layout_box_model_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_box_model_object.cc
@@ -437,30 +437,30 @@
     }
   }
 
-  if (old_style && HasLayer() && !Layer()->NeedsRepaint()) {
-    if (old_style->BackfaceVisibility() != StyleRef().BackfaceVisibility()) {
-      // We need to repaint the layer to update the backface visibility value of
-      // the paint chunk.
+  if (old_style &&
+      old_style->BackfaceVisibility() != StyleRef().BackfaceVisibility()) {
+    SetNeedsPaintPropertyUpdate();
+  }
+
+  if (old_style && HasLayer() && !Layer()->NeedsRepaint() &&
+      diff.TransformChanged() &&
+      (RuntimeEnabledFeatures::CompositeAfterPaintEnabled() ||
+       !Layer()->HasStyleDeterminedDirectCompositingReasons())) {
+    // PaintLayerPainter::PaintLayerWithAdjustedRoot skips painting of a layer
+    // whose transform is not invertible, so we need to repaint the layer when
+    // invertible status changes.
+    TransformationMatrix old_transform;
+    TransformationMatrix new_transform;
+    old_style->ApplyTransform(
+        old_transform, LayoutSize(), ComputedStyle::kExcludeTransformOrigin,
+        ComputedStyle::kExcludeMotionPath,
+        ComputedStyle::kIncludeIndependentTransformProperties);
+    StyleRef().ApplyTransform(
+        new_transform, LayoutSize(), ComputedStyle::kExcludeTransformOrigin,
+        ComputedStyle::kExcludeMotionPath,
+        ComputedStyle::kIncludeIndependentTransformProperties);
+    if (old_transform.IsInvertible() != new_transform.IsInvertible())
       Layer()->SetNeedsRepaint();
-    } else if (diff.TransformChanged() &&
-               (RuntimeEnabledFeatures::CompositeAfterPaintEnabled() ||
-                !Layer()->HasStyleDeterminedDirectCompositingReasons())) {
-      // PaintLayerPainter::PaintLayerWithAdjustedRoot skips painting of a layer
-      // whose transform is not invertible, so we need to repaint the layer when
-      // invertible status changes.
-      TransformationMatrix old_transform;
-      TransformationMatrix new_transform;
-      old_style->ApplyTransform(
-          old_transform, LayoutSize(), ComputedStyle::kExcludeTransformOrigin,
-          ComputedStyle::kExcludeMotionPath,
-          ComputedStyle::kIncludeIndependentTransformProperties);
-      StyleRef().ApplyTransform(
-          new_transform, LayoutSize(), ComputedStyle::kExcludeTransformOrigin,
-          ComputedStyle::kExcludeMotionPath,
-          ComputedStyle::kIncludeIndependentTransformProperties);
-      if (old_transform.IsInvertible() != new_transform.IsInvertible())
-        Layer()->SetNeedsRepaint();
-    }
   }
 }
 
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index 00d5808..12561842 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -3539,7 +3539,6 @@
 
 void LayoutObject::ForceLayout() {
   SetSelfNeedsLayoutForAvailableSpace(true);
-  MarkContainerNeedsCollectInlines();
   UpdateLayout();
 }
 
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc
index e2c22b7..1503b10 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc
@@ -538,6 +538,23 @@
   TEST_ITEM_TYPE_OFFSET((*items[4]), kText, 6u, 8u);
 }
 
+TEST_F(NGInlineNodeTest, NeedsCollectInlinesOnForceLayout) {
+  SetBodyInnerHTML(R"HTML(
+    <div id="container">
+      <span id="target">
+        <span id="child" style="position: absolute">X</span>
+      </span>
+    </div>
+  )HTML");
+
+  LayoutObject* container = GetLayoutObjectByElementId("container");
+  LayoutObject* target = GetLayoutObjectByElementId("target");
+  LayoutObject* child = GetLayoutObjectByElementId("child");
+  child->ForceLayout();
+  EXPECT_FALSE(container->NeedsCollectInlines());
+  EXPECT_FALSE(target->NeedsCollectInlines());
+}
+
 TEST_F(NGInlineNodeTest, InvalidateAddSpan) {
   SetupHtml("t", "<div id=t>before</div>");
   EXPECT_FALSE(layout_block_flow_->NeedsCollectInlines());
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
index 9ae716b7..aa408850 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
@@ -432,22 +432,24 @@
     const NGOutOfFlowPositionedDescendant& descendant,
     const LayoutBox* only_layout,
     NGLogicalOffset* offset) {
+  NGBlockNode node = descendant.node;
+
   // "NGOutOfFlowLayoutPart container is ContainingBlock" invariant cannot
-  // be enforced for tables. Tables are special, in that ContainingBlock is
+  // be enforced for tables. Tables are special, in that the ContainingBlock is
   // TABLE, but constraint space is generated by TBODY/TR/. This happens
   // because TBODY/TR are not LayoutBlocks, but LayoutBoxModelObjects.
   DCHECK((container_builder_->GetLayoutObject() ==
-          descendant.node.GetLayoutBox()->ContainingBlock()) ||
-         descendant.node.GetLayoutBox()->ContainingBlock()->IsTable());
+          node.GetLayoutBox()->ContainingBlock()) ||
+         node.GetLayoutBox()->ContainingBlock()->IsTable());
 
   const ContainingBlockInfo& container_info =
       GetContainingBlockInfo(descendant);
-  const ComputedStyle& descendant_style = descendant.node.Style();
+  const ComputedStyle& descendant_style = node.Style();
 
   WritingMode container_writing_mode(container_info.style->GetWritingMode());
   WritingMode descendant_writing_mode(descendant_style.GetWritingMode());
 
-  // Adjust the static_position (which is currently relative to the default
+  // Adjust the |static_position| (which is currently relative to the default
   // container's border-box). ng_absolute_utils expects the static position to
   // be relative to the container's padding-box.
   NGStaticPosition static_position(descendant.static_position);
@@ -465,19 +467,19 @@
           .ToConstraintSpace();
 
   NGBoxStrut border_padding =
-      ComputeBorders(descendant_constraint_space, descendant.node) +
+      ComputeBorders(descendant_constraint_space, node) +
       ComputePadding(descendant_constraint_space, descendant_style);
 
-  // The block_estimate is in the descendant's writing mode.
+  // The |block_estimate| is wrt. the descendant's writing mode.
   base::Optional<LayoutUnit> block_estimate;
   base::Optional<MinMaxSize> min_max_size;
-
   scoped_refptr<const NGLayoutResult> layout_result = nullptr;
 
-  NGBlockNode node = descendant.node;
+  bool is_replaced = node.IsReplaced();
+  bool should_be_considered_as_replaced = node.ShouldBeConsideredAsReplaced();
+
   if (AbsoluteNeedsChildInlineSize(descendant_style) ||
-      NeedMinMaxSize(descendant_style) ||
-      descendant.node.ShouldBeConsideredAsReplaced()) {
+      NeedMinMaxSize(descendant_style) || should_be_considered_as_replaced) {
     // This is a new formatting context, so whatever happened on the outside
     // doesn't concern us.
     MinMaxSizeInput input(container_content_size.block_size);
@@ -486,10 +488,10 @@
   }
 
   base::Optional<NGLogicalSize> replaced_size;
-  if (descendant.node.IsReplaced()) {
-    replaced_size = ComputeReplacedSize(
-        descendant.node, descendant_constraint_space, min_max_size);
-  } else if (descendant.node.ShouldBeConsideredAsReplaced()) {
+  if (is_replaced) {
+    replaced_size =
+        ComputeReplacedSize(node, descendant_constraint_space, min_max_size);
+  } else if (should_be_considered_as_replaced) {
     replaced_size = NGLogicalSize{
         min_max_size->ShrinkToFit(
             descendant_constraint_space.AvailableSize().inline_size),
@@ -501,15 +503,14 @@
           static_position, min_max_size, replaced_size, container_writing_mode,
           container_info.style->Direction());
 
-  // ShouldBeConsideredAsReplaced sets inline size.
-  // It does not set block size. This is a compatiblity quirk.
-  if (!descendant.node.IsReplaced() &&
-      descendant.node.ShouldBeConsideredAsReplaced())
+  // |should_be_considered_as_replaced| sets the inline-size.
+  // It does not set the block-size. This is a compatibility quirk.
+  if (!is_replaced && should_be_considered_as_replaced)
     replaced_size.reset();
 
   if (AbsoluteNeedsChildBlockSize(descendant_style)) {
-    layout_result = GenerateFragment(descendant.node, container_info,
-                                     block_estimate, node_position);
+    layout_result =
+        GenerateFragment(node, container_info, block_estimate, node_position);
 
     DCHECK(layout_result->PhysicalFragment());
     NGFragment fragment(descendant_writing_mode,
@@ -523,12 +524,12 @@
       static_position, block_estimate, replaced_size, container_writing_mode,
       container_info.style->Direction(), &node_position);
 
-  // Skip this step if we produced a fragment when estimating the block size.
+  // Skip this step if we produced a fragment when estimating the block-size.
   if (!layout_result) {
     block_estimate =
         node_position.size.ConvertToLogical(descendant_writing_mode).block_size;
-    layout_result = GenerateFragment(descendant.node, container_info,
-                                     block_estimate, node_position);
+    layout_result =
+        GenerateFragment(node, container_info, block_estimate, node_position);
   }
   if (node.GetLayoutBox()->IsLayoutNGObject()) {
     ToLayoutBlock(node.GetLayoutBox())
@@ -538,7 +539,7 @@
   NGBoxStrut inset = node_position.inset.ConvertToLogical(
       container_writing_mode, default_containing_block_.style->Direction());
 
-  // inset is relative to the container's padding-box. Convert this to being
+  // |inset| is relative to the container's padding-box. Convert this to being
   // relative to the default container's border-box.
   offset->inline_offset =
       inset.inline_start + container_info.container_offset.inline_offset;
@@ -546,8 +547,7 @@
       inset.block_start + container_info.container_offset.block_offset;
 
   base::Optional<LayoutUnit> y = ComputeAbsoluteDialogYPosition(
-      *descendant.node.GetLayoutBox(),
-      layout_result->PhysicalFragment()->Size().height);
+      *node.GetLayoutBox(), layout_result->PhysicalFragment()->Size().height);
   if (y.has_value()) {
     if (IsHorizontalWritingMode(container_writing_mode))
       offset->block_offset = *y;
@@ -560,7 +560,7 @@
   // can only be computed by a block that is an ancestor of all fragments
   // generated by css container. That block is parent of anonymous containing
   // block.
-  // That is why instead of OOF being placed by its anononymous container,
+  // That is why instead of OOF being placed by its anonymous container,
   // they get placed by anonymous container's parent.
   // This is different from all other OOF blocks, and requires special
   // handling in several places in the OOF code.
@@ -593,7 +593,7 @@
   if (only_layout)
     return layout_result;
 
-  const LayoutObject* container = descendant.node.GetLayoutBox()->Container();
+  const LayoutObject* container = node.GetLayoutBox()->Container();
   if (container->IsAnonymousBlock()) {
     NGLogicalOffset container_offset =
         container_builder_->GetChildOffset(container);
diff --git a/third_party/blink/renderer/core/loader/progress_tracker.cc b/third_party/blink/renderer/core/loader/progress_tracker.cc
index 98ff4d6..23998db 100644
--- a/third_party/blink/renderer/core/loader/progress_tracker.cc
+++ b/third_party/blink/renderer/core/loader/progress_tracker.cc
@@ -54,15 +54,9 @@
 
 struct ProgressItem {
   USING_FAST_MALLOC(ProgressItem);
-
  public:
-  explicit ProgressItem(int64_t length)
-      : bytes_received(0), estimated_length(length) {}
-
-  int64_t bytes_received;
-  int64_t estimated_length;
-
-  DISALLOW_COPY_AND_ASSIGN(ProgressItem);
+  int64_t bytes_received = 0;
+  int64_t estimated_length = 0;
 };
 
 ProgressTracker::ProgressTracker(LocalFrame* frame)
@@ -96,6 +90,8 @@
   last_notified_progress_time_ = 0;
   finished_parsing_ = false;
   did_first_contentful_paint_ = false;
+  bytes_received_ = 0;
+  estimated_bytes_for_pending_requests_ = 0;
 }
 
 LocalFrameClient* ProgressTracker::GetLocalFrameClient() const {
@@ -144,31 +140,34 @@
     return;
   if (HaveParsedAndPainted() || priority < ResourceLoadPriority::kHigh)
     return;
-  progress_items_.Set(identifier, std::make_unique<ProgressItem>(
-                                      kProgressItemDefaultEstimatedLength));
+  ProgressItem new_item;
+  UpdateProgressItem(new_item, 0, kProgressItemDefaultEstimatedLength);
+  progress_items_.Set(identifier, new_item);
 }
 
 void ProgressTracker::IncrementProgress(uint64_t identifier,
                                         const ResourceResponse& response) {
-  ProgressItem* item = progress_items_.at(identifier);
-  if (!item)
+  auto item = progress_items_.find(identifier);
+  if (item == progress_items_.end())
     return;
 
   int64_t estimated_length = response.ExpectedContentLength();
   if (estimated_length < 0)
     estimated_length = kProgressItemDefaultEstimatedLength;
-  item->bytes_received = 0;
-  item->estimated_length = estimated_length;
+  UpdateProgressItem(item->value, 0, estimated_length);
 }
 
 void ProgressTracker::IncrementProgress(uint64_t identifier, uint64_t length) {
-  ProgressItem* item = progress_items_.at(identifier);
-  if (!item)
+  auto item = progress_items_.find(identifier);
+  if (item == progress_items_.end())
     return;
 
-  item->bytes_received += length;
-  if (item->bytes_received > item->estimated_length)
-    item->estimated_length = item->bytes_received * 2;
+  ProgressItem& progress_item = item->value;
+  int64_t bytes_received = progress_item.bytes_received + length;
+  int64_t estimated_length = bytes_received > progress_item.estimated_length
+                                 ? bytes_received * 2
+                                 : progress_item.estimated_length;
+  UpdateProgressItem(progress_item, bytes_received, estimated_length);
   MaybeSendProgress();
 }
 
@@ -176,6 +175,19 @@
   return finished_parsing_ && did_first_contentful_paint_;
 }
 
+void ProgressTracker::UpdateProgressItem(ProgressItem& item,
+                                         int64_t bytes_received,
+                                         int64_t estimated_length) {
+  bytes_received_ += (bytes_received - item.bytes_received);
+  estimated_bytes_for_pending_requests_ +=
+      (estimated_length - item.estimated_length);
+  DCHECK_GE(bytes_received_, 0);
+  DCHECK_GE(estimated_bytes_for_pending_requests_, bytes_received_);
+
+  item.bytes_received = bytes_received;
+  item.estimated_length = estimated_length;
+}
+
 void ProgressTracker::MaybeSendProgress() {
   if (!frame_->IsLoading())
     return;
@@ -186,27 +198,17 @@
   if (did_first_contentful_paint_)
     progress_value_ += 0.1;
 
-  int64_t bytes_received = 0;
-  int64_t estimated_bytes_for_pending_requests = 0;
-  for (const auto& progress_item : progress_items_) {
-    bytes_received += progress_item.value->bytes_received;
-    estimated_bytes_for_pending_requests +=
-        progress_item.value->estimated_length;
-  }
-  DCHECK_GE(estimated_bytes_for_pending_requests, 0);
-  DCHECK_GE(estimated_bytes_for_pending_requests, bytes_received);
-
   if (HaveParsedAndPainted() &&
-      estimated_bytes_for_pending_requests == bytes_received) {
+      estimated_bytes_for_pending_requests_ == bytes_received_) {
     SendFinalProgress();
     return;
   }
 
   double percent_of_bytes_received =
-      !estimated_bytes_for_pending_requests
+      !estimated_bytes_for_pending_requests_
           ? 1.0
-          : (double)bytes_received /
-                (double)estimated_bytes_for_pending_requests;
+          : (double)bytes_received_ /
+                (double)estimated_bytes_for_pending_requests_;
   progress_value_ += percent_of_bytes_received / 2;
 
   DCHECK_GE(progress_value_, kInitialProgressValue);
@@ -230,11 +232,13 @@
 }
 
 void ProgressTracker::CompleteProgress(uint64_t identifier) {
-  ProgressItem* item = progress_items_.at(identifier);
-  if (!item)
+  auto item = progress_items_.find(identifier);
+  if (item == progress_items_.end())
     return;
 
-  item->estimated_length = item->bytes_received;
+  ProgressItem& progress_item = item->value;
+  UpdateProgressItem(item->value, progress_item.bytes_received,
+                     progress_item.bytes_received);
   MaybeSendProgress();
 }
 
diff --git a/third_party/blink/renderer/core/loader/progress_tracker.h b/third_party/blink/renderer/core/loader/progress_tracker.h
index b81db1c..0e4ba58f 100644
--- a/third_party/blink/renderer/core/loader/progress_tracker.h
+++ b/third_party/blink/renderer/core/loader/progress_tracker.h
@@ -71,6 +71,10 @@
  private:
   LocalFrameClient* GetLocalFrameClient() const;
 
+  void UpdateProgressItem(ProgressItem& item,
+                          int64_t bytes_received,
+                          int64_t estimated_length);
+
   void MaybeSendProgress();
   void SendFinalProgress();
   void Reset();
@@ -84,7 +88,10 @@
   bool did_first_contentful_paint_;
   double progress_value_;
 
-  HashMap<uint64_t, std::unique_ptr<ProgressItem>> progress_items_;
+  int64_t bytes_received_ = 0;
+  int64_t estimated_bytes_for_pending_requests_ = 0;
+
+  HashMap<uint64_t, ProgressItem> progress_items_;
 
   DISALLOW_COPY_AND_ASSIGN(ProgressTracker);
 };
diff --git a/third_party/blink/renderer/core/loader/resource/image_resource_content.cc b/third_party/blink/renderer/core/loader/resource/image_resource_content.cc
index 8d2cbcdb..004eb75 100644
--- a/third_party/blink/renderer/core/loader/resource/image_resource_content.cc
+++ b/third_party/blink/renderer/core/loader/resource/image_resource_content.cc
@@ -499,15 +499,18 @@
 // unoptimized image feature policy on |context|.
 bool ImageResourceContent::IsAcceptableCompressionRatio(
     const SecurityContext& context) {
+  if (!image_)
+    return true;
+
   uint64_t pixels = IntrinsicSize(kDoNotRespectImageOrientation).Area();
   if (!pixels)
     return true;
-  DCHECK(image_);
+
   double resource_length =
       static_cast<double>(GetResponse().ExpectedContentLength());
-  if (resource_length <= 0 && GetImage() && GetImage()->Data()) {
+  if (resource_length <= 0 && image_->Data()) {
     // WPT and LayoutTests server returns -1 or 0 for the content length.
-    resource_length = static_cast<double>(GetImage()->Data()->size());
+    resource_length = static_cast<double>(image_->Data()->size());
   }
 
   // Calculate the image's compression ratio (in bytes per pixel) with both 1k
@@ -516,12 +519,19 @@
   double compression_ratio_1k = (resource_length - 1024) / pixels;
   double compression_ratio_10k = (resource_length - 10240) / pixels;
 
-  // Note that this approach may not always correctly identify the image (for
-  // example, due to a misconfigured web server). This approach SHOULD work in
-  // all usual cases, but content sniffing could be used in the future to more
-  // confidently identify the image type.
-  // TODO(crbug.com/943203): Implement content sniffing.
-  AtomicString mime_type = GetResponse().HttpContentType();
+  // Attempt to sniff the image content to determine the true MIME type of the
+  // image, and fall back on the provided MIME type if this is not
+  // possible.
+  //
+  // Note that if the type cannot be sniffed AND the provided type is incorrect
+  // (for example, due to a misconfigured web server), then it is possible that
+  // either the wrong policy (or no policy) will be enforced. However, this case
+  // should be exceedingly rare.
+  String mime_type =
+      image_->Data() &&
+      ImageDecoder::HasSufficientDataToSniffImageType(*image_->Data().get())
+          ? ImageDecoder::SniffImageType(image_->Data())
+          : GetResponse().HttpContentType();
   if (MIMETypeRegistry::IsLossyImageMIMEType(mime_type)) {
     // Enforce the lossy image policy.
     return context.IsFeatureEnabled(
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc b/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc
index 57d2ce3..6f284898 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc
@@ -1688,4 +1688,13 @@
   EXPECT_TRUE(paint_artifact_compositor->NeedsUpdate());
 }
 
+TEST_P(PaintPropertyTreeUpdateTest, BackfaceVisibilityInvalidatesProperties) {
+  SetBodyInnerHTML("<span id='span'>a</span>");
+
+  auto* span = GetDocument().getElementById("span");
+  span->setAttribute(html_names::kStyleAttr, "backface-visibility: hidden;");
+  GetDocument().View()->UpdateLifecycleToLayoutClean();
+  EXPECT_TRUE(span->GetLayoutObject()->NeedsPaintPropertyUpdate());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/theme_painter_default.cc b/third_party/blink/renderer/core/paint/theme_painter_default.cc
index 02c262ed..d4f4e51b 100644
--- a/third_party/blink/renderer/core/paint/theme_painter_default.cc
+++ b/third_party/blink/renderer/core/paint/theme_painter_default.cc
@@ -214,15 +214,6 @@
   if (style.HasBorderRadius() || style.HasBackgroundImage())
     return true;
 
-  // Don't use the theme painter if dark mode is enabled. It has a separate
-  // graphics pipeline that doesn't go through GraphicsContext and so does not
-  // currently know how to handle Dark Mode, causing elements to be rendered
-  // incorrectly (e.g. https://crbug.com/937872).
-  // TODO(gilmanmh): Implement a more permanent solution that allows use of
-  // native dark themes.
-  if (paint_info.context.dark_mode_settings().mode != DarkMode::kOff)
-    return true;
-
   ControlPart part = style.Appearance();
 
   WebThemeEngine::ExtraParams extra_params;
@@ -475,7 +466,8 @@
   // pixel off-center, it will be one pixel closer to the bottom of the field.
   // This tends to look better with the text.
   LayoutRect cancel_button_rect(
-      cancel_button_object.OffsetFromAncestor(&input_layout_box).Width(),
+      cancel_button_object.OffsetFromAncestor(&input_layout_box)
+          .Width(),
       input_content_box.Y() +
           (input_content_box.Height() - cancel_button_size + 1) / 2,
       cancel_button_size, cancel_button_size);
diff --git a/third_party/blink/renderer/core/svg/svg_filter_primitive_standard_attributes.idl b/third_party/blink/renderer/core/svg/svg_filter_primitive_standard_attributes.idl
index 08d9194..f6852b7 100644
--- a/third_party/blink/renderer/core/svg/svg_filter_primitive_standard_attributes.idl
+++ b/third_party/blink/renderer/core/svg/svg_filter_primitive_standard_attributes.idl
@@ -26,9 +26,7 @@
 
 // https://drafts.fxtf.org/filter-effects/#InterfaceSVGFilterPrimitiveStandardAttributes
 
-[
-    NoInterfaceObject // Always used on target of 'implements'
-] interface SVGFilterPrimitiveStandardAttributes {
+interface mixin SVGFilterPrimitiveStandardAttributes {
     [MeasureAs=SVG1DOMFilter] readonly attribute SVGAnimatedLength x;
     [MeasureAs=SVG1DOMFilter] readonly attribute SVGAnimatedLength y;
     [MeasureAs=SVG1DOMFilter] readonly attribute SVGAnimatedLength width;
diff --git a/third_party/blink/renderer/core/svg/svg_fit_to_view_box.idl b/third_party/blink/renderer/core/svg/svg_fit_to_view_box.idl
index b8af7cf..4f78218 100644
--- a/third_party/blink/renderer/core/svg/svg_fit_to_view_box.idl
+++ b/third_party/blink/renderer/core/svg/svg_fit_to_view_box.idl
@@ -26,9 +26,7 @@
 
 // https://svgwg.org/svg2-draft/types.html#InterfaceSVGFitToViewBox
 
-[
-    NoInterfaceObject // Always used on target of 'implements'
-] interface SVGFitToViewBox {
+interface mixin SVGFitToViewBox {
     [MeasureAs=SVG1DOMFitToViewBox] readonly attribute SVGAnimatedRect viewBox;
     [MeasureAs=SVG1DOMFitToViewBox] readonly attribute SVGAnimatedPreserveAspectRatio preserveAspectRatio;
 };
diff --git a/third_party/blink/renderer/core/svg/svg_tests.idl b/third_party/blink/renderer/core/svg/svg_tests.idl
index 665ac6f5a1..edd7450 100644
--- a/third_party/blink/renderer/core/svg/svg_tests.idl
+++ b/third_party/blink/renderer/core/svg/svg_tests.idl
@@ -26,9 +26,7 @@
 
 // https://svgwg.org/svg2-draft/types.html#InterfaceSVGTests
 
-[
-    NoInterfaceObject // Always used on target of 'implements'
-] interface SVGTests {
+interface mixin SVGTests {
     [MeasureAs=SVG1DOMSVGTests] readonly attribute SVGStringList requiredExtensions;
     [MeasureAs=SVG1DOMSVGTests] readonly attribute SVGStringList systemLanguage;
 };
diff --git a/third_party/blink/renderer/core/svg/svg_uri_reference.idl b/third_party/blink/renderer/core/svg/svg_uri_reference.idl
index 6f6cee2b..71d563f6 100644
--- a/third_party/blink/renderer/core/svg/svg_uri_reference.idl
+++ b/third_party/blink/renderer/core/svg/svg_uri_reference.idl
@@ -26,8 +26,6 @@
 
 // https://svgwg.org/svg2-draft/types.html#InterfaceSVGURIReference
 
-[
-    NoInterfaceObject // Always used on target of 'implements'
-] interface SVGURIReference {
+interface mixin SVGURIReference {
     [MeasureAs=SVG1DOMUriReference] readonly attribute SVGAnimatedString href;
 };
diff --git a/third_party/blink/renderer/core/svg/svg_zoom_and_pan.idl b/third_party/blink/renderer/core/svg/svg_zoom_and_pan.idl
index 20e6ce5..d156aac 100644
--- a/third_party/blink/renderer/core/svg/svg_zoom_and_pan.idl
+++ b/third_party/blink/renderer/core/svg/svg_zoom_and_pan.idl
@@ -26,9 +26,7 @@
 
 // https://svgwg.org/svg2-draft/types.html#InterfaceSVGZoomAndPan
 
-[
-    NoInterfaceObject // Always used on target of 'implements'
-] interface SVGZoomAndPan {
+interface mixin SVGZoomAndPan {
 
     // Zoom and Pan Types
     const unsigned short SVG_ZOOMANDPAN_UNKNOWN = 0;
diff --git a/third_party/blink/renderer/core/workers/abstract_worker.idl b/third_party/blink/renderer/core/workers/abstract_worker.idl
index 99091a7..8c3e449 100644
--- a/third_party/blink/renderer/core/workers/abstract_worker.idl
+++ b/third_party/blink/renderer/core/workers/abstract_worker.idl
@@ -33,8 +33,7 @@
 
 [
     LegacyTreatAsPartialInterface,
-    NoInterfaceObject, // Always used on target of 'implements'
     Exposed=(Window,Worker)
-] interface AbstractWorker {
+] interface mixin AbstractWorker {
     attribute EventHandler onerror;
 };
diff --git a/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope.cc b/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope.cc
index 30b5b91..3411fca 100644
--- a/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope.cc
+++ b/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope.cc
@@ -4,21 +4,26 @@
 
 #include "third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope.h"
 
+#include "base/optional.h"
+#include "third_party/blink/renderer/bindings/core/v8/generated_code_helper.h"
 #include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_function.h"
 #include "third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_animate_callback.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_animator_constructor.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_state_callback.h"
 #include "third_party/blink/renderer/core/workers/global_scope_creation_params.h"
 #include "third_party/blink/renderer/core/workers/worker_thread.h"
 #include "third_party/blink/renderer/modules/animationworklet/animation_worklet_proxy_client.h"
 #include "third_party/blink/renderer/modules/animationworklet/worklet_animation_options.h"
 #include "third_party/blink/renderer/platform/bindings/callback_method_retriever.h"
+#include "third_party/blink/renderer/platform/bindings/exception_code.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/bindings/v8_binding_macros.h"
 #include "third_party/blink/renderer/platform/bindings/v8_object_constructor.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 #include "third_party/blink/renderer/platform/wtf/time.h"
+#include "v8/include/v8.h"
 
 namespace blink {
 
@@ -63,10 +68,12 @@
 Animator* AnimationWorkletGlobalScope::CreateAnimatorFor(
     int animation_id,
     const String& name,
-    WorkletAnimationOptions* options,
+    WorkletAnimationOptions options,
+    scoped_refptr<SerializedScriptValue> serialized_state,
     int num_effects) {
   DCHECK(!animators_.at(animation_id));
-  Animator* animator = CreateInstance(name, options, num_effects);
+  Animator* animator =
+      CreateInstance(name, options, serialized_state, num_effects);
   if (!animator)
     return nullptr;
   animators_.Set(animation_id, animator);
@@ -90,11 +97,14 @@
     const String name =
         String::FromUTF8(animation.name.data(), animation.name.size());
 
+    WorkletAnimationOptions options(nullptr);
     // Down casting to blink type to access the serialized value.
-    WorkletAnimationOptions* options =
-        static_cast<WorkletAnimationOptions*>(animation.options.get());
-
-    CreateAnimatorFor(id, name, options, animation.num_effects);
+    if (animation.options) {
+      options =
+          *(static_cast<WorkletAnimationOptions*>(animation.options.get()));
+    }
+    CreateAnimatorFor(id, name, options, nullptr /* serialized_state */,
+                      animation.num_effects);
   }
 }
 
@@ -190,11 +200,16 @@
   if (exception_state.HadException())
     return;
   V8AnimateCallback* animate = V8AnimateCallback::Create(v8_animate);
+
   v8::Local<v8::Value> v8_state =
       retriever.GetMethodOrUndefined("state", exception_state);
-  V8Function* state = v8_state->IsFunction()
-                          ? V8Function::Create(v8_state.As<v8::Function>())
-                          : nullptr;
+  if (exception_state.HadException())
+    return;
+
+  V8StateCallback* state =
+      v8_state->IsFunction()
+          ? V8StateCallback::Create(v8_state.As<v8::Function>())
+          : nullptr;
 
   AnimatorDefinition* definition =
       MakeGarbageCollected<AnimatorDefinition>(animator_ctor, animate, state);
@@ -213,34 +228,98 @@
 
 Animator* AnimationWorkletGlobalScope::CreateInstance(
     const String& name,
-    WorkletAnimationOptions* options,
+    WorkletAnimationOptions options,
+    scoped_refptr<SerializedScriptValue> serialized_state,
     int num_effects) {
   DCHECK(IsContextThread());
   AnimatorDefinition* definition = animator_definitions_.at(name);
   if (!definition)
     return nullptr;
 
-  v8::Isolate* isolate = ScriptController()->GetScriptState()->GetIsolate();
+  ScriptState* script_state = ScriptController()->GetScriptState();
+  ScriptState::Scope scope(script_state);
+  v8::Isolate* isolate = script_state->GetIsolate();
+
+  v8::TryCatch try_catch(isolate);
+  try_catch.SetVerbose(true);
+
   v8::Local<v8::Value> v8_options =
-      options && options->GetData() ? options->GetData()->Deserialize(isolate)
-                                    : v8::Undefined(isolate).As<v8::Value>();
-  ScriptValue options_value(ScriptController()->GetScriptState(), v8_options);
+      options.GetData() ? options.GetData()->Deserialize(isolate)
+                        : v8::Undefined(isolate).As<v8::Value>();
+  v8::Local<v8::Value> v8_state = serialized_state
+                                      ? serialized_state->Deserialize(isolate)
+                                      : v8::Undefined(isolate).As<v8::Value>();
+  ScriptValue options_value(script_state, v8_options);
+  ScriptValue state_value(script_state, v8_state);
 
   ScriptValue instance;
   if (!definition->ConstructorFunction()
-           ->Construct(options_value)
+           ->Construct(options_value, state_value)
            .To(&instance)) {
     return nullptr;
   }
 
   return MakeGarbageCollected<Animator>(isolate, definition, instance.V8Value(),
-                                        num_effects);
+                                        name, std::move(options), num_effects);
 }
 
 bool AnimationWorkletGlobalScope::IsAnimatorStateful(int animation_id) {
   return animators_.at(animation_id)->IsStateful();
 }
 
+// Implementation of "Migrating an Animator Instance":
+// https://drafts.css-houdini.org/css-animationworklet/#migrating-animator
+// Note that per specification if the state function does not exist, the
+// migration process should be aborted. However the following implementation
+// is used for both the stateful and stateless animators. For the latter ones
+// the migration (including name, options etc.) should be completed regardless
+// the state function.
+void AnimationWorkletGlobalScope::MigrateAnimatorsTo(
+    AnimationWorkletGlobalScope* target_global_scope) {
+  DCHECK_NE(this, target_global_scope);
+
+  ScriptState* script_state = ScriptController()->GetScriptState();
+  ScriptState::Scope scope(script_state);
+  v8::Isolate* isolate = script_state->GetIsolate();
+
+  for (const auto& animator_map : animators_) {
+    int animation_id = animator_map.key;
+    Animator* animator = animator_map.value;
+    scoped_refptr<SerializedScriptValue> serialized_state;
+    if (animator->IsStateful()) {
+      ExceptionState exception_state(script_state->GetIsolate(),
+                                     ExceptionState::kExecutionContext,
+                                     "Animator", "state");
+      // If an animator state function throws or the state is not
+      // serializable, the animator will be removed from the global scope.
+      // TODO(yigu): We should post an error message to console in case of
+      // exceptions.
+      v8::Local<v8::Value> state = animator->State(isolate, exception_state);
+      if (exception_state.HadException()) {
+        exception_state.ClearException();
+        continue;
+      }
+
+      // Do not skip migrating the stateful animator if its state is
+      // undefined.
+      if (!state->IsNullOrUndefined()) {
+        serialized_state = SerializedScriptValue::Serialize(
+            isolate, state, SerializedScriptValue::SerializeOptions(),
+            exception_state);
+        if (exception_state.HadException()) {
+          exception_state.ClearException();
+          continue;
+        }
+      }
+    }
+
+    target_global_scope->CreateAnimatorFor(
+        animation_id, animator->name(), animator->options(), serialized_state,
+        animator->num_effects());
+  }
+  animators_.clear();
+}
+
 AnimatorDefinition* AnimationWorkletGlobalScope::FindDefinitionForTest(
     const String& name) {
   return animator_definitions_.at(name);
diff --git a/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope.h b/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope.h
index f27dcaea..88a1731d3 100644
--- a/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope.h
+++ b/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope.h
@@ -55,17 +55,25 @@
 
   AnimatorDefinition* FindDefinitionForTest(const String& name);
   bool IsAnimatorStateful(int animation_id);
+  void MigrateAnimatorsTo(AnimationWorkletGlobalScope*);
+  Animator* GetAnimator(int animation_id) {
+    return animators_.at(animation_id);
+  }
   unsigned GetAnimatorsSizeForTest() { return animators_.size(); }
 
  private:
   void RegisterWithProxyClientIfNeeded();
-  Animator* CreateInstance(const String& name,
-                           WorkletAnimationOptions* options,
-                           int num_effects);
-  Animator* CreateAnimatorFor(int animation_id,
-                              const String& name,
-                              WorkletAnimationOptions* options,
-                              int num_effects);
+  Animator* CreateInstance(
+      const String& name,
+      WorkletAnimationOptions options,
+      scoped_refptr<SerializedScriptValue> serialized_state,
+      int num_effects);
+  Animator* CreateAnimatorFor(
+      int animation_id,
+      const String& name,
+      WorkletAnimationOptions options,
+      scoped_refptr<SerializedScriptValue> serialized_state,
+      int num_effects);
   typedef HeapHashMap<String, Member<AnimatorDefinition>> DefinitionMap;
   DefinitionMap animator_definitions_;
 
diff --git a/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope.idl b/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope.idl
index b03e72e..08be78f2 100644
--- a/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope.idl
+++ b/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope.idl
@@ -14,6 +14,8 @@
 
 // Blink-specific types
 // https://wicg.github.io/animation-worklet/#create-a-new-animator-instance
-callback AnimatorConstructor = any (any options);
+callback AnimatorConstructor = any (any options, any state);
 // https://wicg.github.io/animation-worklet/#run-animators
 callback AnimateCallback = void (double currentTime, (EffectProxy or WorkletGroupEffectProxy) effect);
+// https://drafts.css-houdini.org/css-animationworklet/#stateful-animator-desc
+callback StateCallback = any ();
diff --git a/third_party/blink/renderer/modules/animationworklet/animation_worklet_proxy_client.cc b/third_party/blink/renderer/modules/animationworklet/animation_worklet_proxy_client.cc
index c26fe1b..5c07675 100644
--- a/third_party/blink/renderer/modules/animationworklet/animation_worklet_proxy_client.cc
+++ b/third_party/blink/renderer/modules/animationworklet/animation_worklet_proxy_client.cc
@@ -21,7 +21,6 @@
 namespace {
 
 static const wtf_size_t kMaxMutateCountToSwitch = 10u;
-static const wtf_size_t kStatefulGlobalScopeIndex = 0u;
 
 }  // end namespace
 
@@ -43,7 +42,7 @@
     : worklet_id_(worklet_id),
       state_(RunState::kUninitialized),
       next_global_scope_switch_countdown_(0),
-      current_stateless_global_scope_index_(0) {
+      current_global_scope_index_(0) {
   DCHECK(IsMainThread());
 
   // The dispatchers are weak pointers that may come from another thread. It's
@@ -164,24 +163,14 @@
       << worklet_id_;
 #endif
 
-  // Create or destroy instances of animators on each global scope.
-  for (auto global_scope : global_scopes_) {
-    global_scope->UpdateAnimatorsList(*input);
-  }
+  AnimationWorkletGlobalScope* global_scope =
+      SelectGlobalScopeAndUpdateAnimatorsIfNecessary();
+  DCHECK(global_scope);
+  // Create or destroy instances of animators on current global scope.
+  global_scope->UpdateAnimatorsList(*input);
 
-  AnimationWorkletGlobalScope* stateful_global_scope =
-      SelectStatefulGlobalScope();
-  DCHECK(stateful_global_scope);
-  stateful_global_scope->UpdateAnimators(
-      *input, output.get(),
-      [](Animator* animator) { return animator->IsStateful(); });
-
-  AnimationWorkletGlobalScope* stateless_global_scope =
-      SelectStatelessGlobalScope();
-  DCHECK(stateless_global_scope);
-  stateless_global_scope->UpdateAnimators(
-      *input, output.get(),
-      [](Animator* animator) { return !animator->IsStateful(); });
+  global_scope->UpdateAnimators(*input, output.get(),
+                                [](Animator* animator) { return true; });
 
   UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
       "Animation.AnimationWorklet.MutateDuration", timer.Elapsed(),
@@ -192,21 +181,19 @@
 }
 
 AnimationWorkletGlobalScope*
-AnimationWorkletProxyClient::SelectStatelessGlobalScope() {
+AnimationWorkletProxyClient::SelectGlobalScopeAndUpdateAnimatorsIfNecessary() {
   if (--next_global_scope_switch_countdown_ < 0) {
-    current_stateless_global_scope_index_ =
-        (++current_stateless_global_scope_index_ % global_scopes_.size());
+    int last_global_scope_index = current_global_scope_index_;
+    current_global_scope_index_ =
+        (++current_global_scope_index_ % global_scopes_.size());
+    global_scopes_[last_global_scope_index]->MigrateAnimatorsTo(
+        global_scopes_[current_global_scope_index_]);
     // Introduce an element of randomness in the switching interval to make
     // stateful dependences easier to spot.
     next_global_scope_switch_countdown_ =
         base::RandInt(0, kMaxMutateCountToSwitch - 1);
   }
-  return global_scopes_[current_stateless_global_scope_index_];
-}
-
-AnimationWorkletGlobalScope*
-AnimationWorkletProxyClient::SelectStatefulGlobalScope() {
-  return global_scopes_[kStatefulGlobalScopeIndex];
+  return global_scopes_[current_global_scope_index_];
 }
 
 void AnimationWorkletProxyClient::AddGlobalScopeForTesting(
diff --git a/third_party/blink/renderer/modules/animationworklet/animation_worklet_proxy_client.h b/third_party/blink/renderer/modules/animationworklet/animation_worklet_proxy_client.h
index 5e7f9af..74770067 100644
--- a/third_party/blink/renderer/modules/animationworklet/animation_worklet_proxy_client.h
+++ b/third_party/blink/renderer/modules/animationworklet/animation_worklet_proxy_client.h
@@ -62,22 +62,17 @@
   static AnimationWorkletProxyClient* From(WorkerClients*);
 
  private:
+  friend class AnimationWorkletProxyClientTest;
   FRIEND_TEST_ALL_PREFIXES(AnimationWorkletProxyClientTest,
                            AnimationWorkletProxyClientConstruction);
   FRIEND_TEST_ALL_PREFIXES(AnimationWorkletProxyClientTest,
                            RegisteredAnimatorNameShouldSyncOnce);
-  FRIEND_TEST_ALL_PREFIXES(AnimationWorkletProxyClientTest, SelectGlobalScope);
 
-  // Separate global scope selectors are used instead of overriding
-  // Worklet::SelectGlobalScope since two different selection mechanisms are
-  // required in order to support statefulness and enforce statelessness
-  // depending on the animators.
-  // The stateless global scope periodically switches in order to enforce
-  // stateless behavior. Prior state is lost on each switch to global scope.
-  AnimationWorkletGlobalScope* SelectStatelessGlobalScope();
-  // The stateful global scope remains fixed to preserve state between mutate
-  // calls.
-  AnimationWorkletGlobalScope* SelectStatefulGlobalScope();
+  // The global scope periodically switches in order to enforce stateless
+  // behavior. For stateless animators, prior state is lost on each switch to
+  // global scope. For stateful animators, prior state is transferred to the new
+  // global scope.
+  AnimationWorkletGlobalScope* SelectGlobalScopeAndUpdateAnimatorsIfNecessary();
 
   const int worklet_id_;
 
@@ -98,7 +93,7 @@
   enum RunState { kUninitialized, kWorking, kDisposed } state_;
 
   int next_global_scope_switch_countdown_;
-  wtf_size_t current_stateless_global_scope_index_;
+  wtf_size_t current_global_scope_index_;
 };
 
 void MODULES_EXPORT
diff --git a/third_party/blink/renderer/modules/animationworklet/animation_worklet_proxy_client_test.cc b/third_party/blink/renderer/modules/animationworklet/animation_worklet_proxy_client_test.cc
index 5637841..82ac234 100644
--- a/third_party/blink/renderer/modules/animationworklet/animation_worklet_proxy_client_test.cc
+++ b/third_party/blink/renderer/modules/animationworklet/animation_worklet_proxy_client_test.cc
@@ -13,6 +13,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_source_code.h"
 #include "third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h"
+#include "third_party/blink/renderer/core/inspector/console_message.h"
 #include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
 #include "third_party/blink/renderer/core/workers/worker_reporting_proxy.h"
 #include "third_party/blink/renderer/modules/worklet/worklet_thread_test_common.h"
@@ -62,38 +63,101 @@
   }
 
   using TestCallback =
-      void (AnimationWorkletProxyClientTest::*)(WorkerThread*,
-                                                AnimationWorkletProxyClient*,
+      void (AnimationWorkletProxyClientTest::*)(AnimationWorkletProxyClient*,
                                                 base::WaitableEvent*);
-  void RunTestOnWorkletThread(TestCallback callback) {
-    std::unique_ptr<WorkerThread> worklet_thread =
+
+  void RunMultipleGlobalScopeTestsOnWorklet(TestCallback callback) {
+    // Global scopes must be created on worker threads.
+    std::unique_ptr<WorkerThread> first_worklet =
+        CreateThreadAndProvideAnimationWorkletProxyClient(
+            &GetDocument(), reporting_proxy_.get(), proxy_client_);
+    std::unique_ptr<WorkerThread> second_worklet =
         CreateThreadAndProvideAnimationWorkletProxyClient(
             &GetDocument(), reporting_proxy_.get(), proxy_client_);
 
+    ASSERT_NE(first_worklet, second_worklet);
+
+    // Register global scopes with proxy client. This step must be performed on
+    // the worker threads.
     base::WaitableEvent waitable_event;
     PostCrossThreadTask(
-        *worklet_thread->GetTaskRunner(TaskType::kInternalTest), FROM_HERE,
+        *first_worklet->GetTaskRunner(TaskType::kInternalTest), FROM_HERE,
+        CrossThreadBind(
+            &AnimationWorkletProxyClientTest::AddGlobalScopeForTesting,
+            CrossThreadUnretained(this),
+            CrossThreadUnretained(first_worklet.get()),
+            CrossThreadPersistent<AnimationWorkletProxyClient>(proxy_client_),
+            CrossThreadUnretained(&waitable_event)));
+    waitable_event.Wait();
+
+    waitable_event.Reset();
+    PostCrossThreadTask(
+        *second_worklet->GetTaskRunner(TaskType::kInternalTest), FROM_HERE,
+        CrossThreadBind(
+            &AnimationWorkletProxyClientTest::AddGlobalScopeForTesting,
+            CrossThreadUnretained(this),
+            CrossThreadUnretained(second_worklet.get()),
+            CrossThreadPersistent<AnimationWorkletProxyClient>(proxy_client_),
+            CrossThreadUnretained(&waitable_event)));
+    waitable_event.Wait();
+
+    PostCrossThreadTask(
+        *first_worklet->GetTaskRunner(TaskType::kInternalTest), FROM_HERE,
         CrossThreadBind(
             callback, CrossThreadUnretained(this),
-            CrossThreadUnretained(worklet_thread.get()),
             CrossThreadPersistent<AnimationWorkletProxyClient>(proxy_client_),
             CrossThreadUnretained(&waitable_event)));
     waitable_event.Wait();
     waitable_event.Reset();
 
-    worklet_thread->Terminate();
-    worklet_thread->WaitForShutdownForTesting();
+    first_worklet->Terminate();
+    first_worklet->WaitForShutdownForTesting();
+    second_worklet->Terminate();
+    second_worklet->WaitForShutdownForTesting();
   }
 
-  void RunMutateCorrectAnimatorOnWorklet(
-      WorkerThread* thread,
+  void RunSelectGlobalScopeOnWorklet(AnimationWorkletProxyClient* proxy_client,
+                                     base::WaitableEvent* waitable_event) {
+    AnimationWorkletGlobalScope* first_global_scope =
+        proxy_client->global_scopes_[0];
+    AnimationWorkletGlobalScope* second_global_scope =
+        proxy_client->global_scopes_[1];
+
+    // Initialize switch countdown to 1, to force a switch in the stateless
+    // global scope on the second call.
+    proxy_client->next_global_scope_switch_countdown_ = 1;
+    EXPECT_EQ(proxy_client->SelectGlobalScopeAndUpdateAnimatorsIfNecessary(),
+              first_global_scope);
+    EXPECT_EQ(proxy_client->SelectGlobalScopeAndUpdateAnimatorsIfNecessary(),
+              second_global_scope);
+
+    // Increase countdown and verify that the switchover adjusts as expected.
+    proxy_client->next_global_scope_switch_countdown_ = 3;
+    EXPECT_EQ(proxy_client->SelectGlobalScopeAndUpdateAnimatorsIfNecessary(),
+              second_global_scope);
+    EXPECT_EQ(proxy_client->SelectGlobalScopeAndUpdateAnimatorsIfNecessary(),
+              second_global_scope);
+    EXPECT_EQ(proxy_client->SelectGlobalScopeAndUpdateAnimatorsIfNecessary(),
+              second_global_scope);
+    EXPECT_EQ(proxy_client->SelectGlobalScopeAndUpdateAnimatorsIfNecessary(),
+              first_global_scope);
+
+    waitable_event->Signal();
+  }
+
+  void RunMigrateAnimatorsBetweenGlobalScopesOnWorklet(
       AnimationWorkletProxyClient* proxy_client,
       base::WaitableEvent* waitable_event) {
+    AnimationWorkletGlobalScope* first_global_scope =
+        proxy_client->global_scopes_[0];
+    AnimationWorkletGlobalScope* second_global_scope =
+        proxy_client->global_scopes_[1];
+
     String source_code =
         R"JS(
           class Stateful {
             animate () {}
-            state () {}
+            state () { return { foo: 'bar'}; }
           }
 
           class Stateless {
@@ -104,16 +168,15 @@
           registerAnimator('stateless_animator', Stateless);
       )JS";
 
-    auto* global_scope = To<AnimationWorkletGlobalScope>(thread->GlobalScope());
-    ASSERT_TRUE(global_scope->ScriptController()->Evaluate(
+    ASSERT_TRUE(first_global_scope->ScriptController()->Evaluate(
+        ScriptSourceCode(source_code), SanitizeScriptErrors::kDoNotSanitize));
+    ASSERT_TRUE(second_global_scope->ScriptController()->Evaluate(
         ScriptSourceCode(source_code), SanitizeScriptErrors::kDoNotSanitize));
 
     std::unique_ptr<AnimationWorkletInput> state =
         std::make_unique<AnimationWorkletInput>();
     cc::WorkletAnimationId first_animation_id = {1, 1};
     cc::WorkletAnimationId second_animation_id = {1, 2};
-    cc::WorkletAnimationId third_animation_id = {1, 3};
-    cc::WorkletAnimationId forth_animation_id = {1, 4};
     state->added_and_updated_animations.emplace_back(
         first_animation_id,    // animation id
         "stateless_animator",  // name associated with the animation
@@ -123,26 +186,17 @@
     );
     state->added_and_updated_animations.emplace_back(
         second_animation_id, "stateful_animator", 5000, nullptr, 1);
-    state->added_and_updated_animations.emplace_back(
-        third_animation_id, "stateful_animator", 5000, nullptr, 1);
-    state->added_and_updated_animations.emplace_back(
-        forth_animation_id, "stateless_animator", 5000, nullptr, 1);
 
-    std::unique_ptr<AnimationWorkletOutput> output =
-        proxy_client->Mutate(std::move(state));
-    EXPECT_EQ(4u, output->animations.size());
+    // Initialize switch countdown to 1, to force a switch on the second call.
+    proxy_client->next_global_scope_switch_countdown_ = 1;
 
-    auto has_id = [&](WorkletAnimationId id) {
-      auto found =
-          std::find_if(output->animations.begin(), output->animations.end(),
-                       [&](auto& it) { return it.worklet_animation_id == id; });
-      return found != output->animations.end();
-    };
+    proxy_client->Mutate(std::move(state));
+    EXPECT_EQ(first_global_scope->GetAnimatorsSizeForTest(), 2u);
+    EXPECT_EQ(second_global_scope->GetAnimatorsSizeForTest(), 0u);
 
-    EXPECT_TRUE(has_id(first_animation_id));
-    EXPECT_TRUE(has_id(second_animation_id));
-    EXPECT_TRUE(has_id(third_animation_id));
-    EXPECT_TRUE(has_id(forth_animation_id));
+    proxy_client->SelectGlobalScopeAndUpdateAnimatorsIfNecessary();
+    EXPECT_EQ(second_global_scope->GetAnimatorsSizeForTest(), 2u);
+    EXPECT_EQ(first_global_scope->GetAnimatorsSizeForTest(), 0u);
 
     waitable_event->Signal();
   }
@@ -194,81 +248,14 @@
 }
 
 TEST_F(AnimationWorkletProxyClientTest, SelectGlobalScope) {
-  // Global scopes must be created on worker threads.
-  std::unique_ptr<WorkerThread> first_worklet =
-      CreateThreadAndProvideAnimationWorkletProxyClient(
-          &GetDocument(), reporting_proxy_.get(), proxy_client_);
-  std::unique_ptr<WorkerThread> second_worklet =
-      CreateThreadAndProvideAnimationWorkletProxyClient(
-          &GetDocument(), reporting_proxy_.get(), proxy_client_);
-
-  ASSERT_NE(first_worklet, second_worklet);
-
-  // Register global scopes with proxy client. This step must be performed on
-  // the worker threads.
-  base::WaitableEvent waitable_event;
-  PostCrossThreadTask(
-      *first_worklet->GetTaskRunner(TaskType::kInternalTest), FROM_HERE,
-      CrossThreadBind(
-          &AnimationWorkletProxyClientTest::AddGlobalScopeForTesting,
-          CrossThreadUnretained(this),
-          CrossThreadUnretained(first_worklet.get()),
-          CrossThreadPersistent<AnimationWorkletProxyClient>(proxy_client_),
-          CrossThreadUnretained(&waitable_event)));
-  waitable_event.Wait();
-
-  waitable_event.Reset();
-  PostCrossThreadTask(
-      *second_worklet->GetTaskRunner(TaskType::kInternalTest), FROM_HERE,
-      CrossThreadBind(
-          &AnimationWorkletProxyClientTest::AddGlobalScopeForTesting,
-          CrossThreadUnretained(this),
-          CrossThreadUnretained(second_worklet.get()),
-          CrossThreadPersistent<AnimationWorkletProxyClient>(proxy_client_),
-          CrossThreadUnretained(&waitable_event)));
-  waitable_event.Wait();
-
-  AnimationWorkletGlobalScope* stateful_global_scope =
-      proxy_client_->global_scopes_[0];
-  AnimationWorkletGlobalScope* first_stateless_global_scope =
-      proxy_client_->global_scopes_[0];
-  AnimationWorkletGlobalScope* second_stateless_global_scope =
-      proxy_client_->global_scopes_[1];
-
-  // Initialize switch countdown to 1, to force a switch in the stateless
-  // global scope on the second call.
-  proxy_client_->next_global_scope_switch_countdown_ = 1;
-  EXPECT_EQ(proxy_client_->SelectStatefulGlobalScope(), stateful_global_scope);
-  EXPECT_EQ(proxy_client_->SelectStatelessGlobalScope(),
-            first_stateless_global_scope);
-  EXPECT_EQ(proxy_client_->SelectStatefulGlobalScope(), stateful_global_scope);
-  EXPECT_EQ(proxy_client_->SelectStatelessGlobalScope(),
-            second_stateless_global_scope);
-
-  // Increase countdown and verify that the switchover adjusts as expected.
-  proxy_client_->next_global_scope_switch_countdown_ = 3;
-  EXPECT_EQ(proxy_client_->SelectStatefulGlobalScope(), stateful_global_scope);
-  EXPECT_EQ(proxy_client_->SelectStatelessGlobalScope(),
-            second_stateless_global_scope);
-  EXPECT_EQ(proxy_client_->SelectStatefulGlobalScope(), stateful_global_scope);
-  EXPECT_EQ(proxy_client_->SelectStatelessGlobalScope(),
-            second_stateless_global_scope);
-  EXPECT_EQ(proxy_client_->SelectStatefulGlobalScope(), stateful_global_scope);
-  EXPECT_EQ(proxy_client_->SelectStatelessGlobalScope(),
-            second_stateless_global_scope);
-  EXPECT_EQ(proxy_client_->SelectStatefulGlobalScope(), stateful_global_scope);
-  EXPECT_EQ(proxy_client_->SelectStatelessGlobalScope(),
-            first_stateless_global_scope);
-
-  first_worklet->Terminate();
-  first_worklet->WaitForShutdownForTesting();
-  second_worklet->Terminate();
-  second_worklet->WaitForShutdownForTesting();
+  RunMultipleGlobalScopeTestsOnWorklet(
+      &AnimationWorkletProxyClientTest::RunSelectGlobalScopeOnWorklet);
 }
 
-TEST_F(AnimationWorkletProxyClientTest, MutateCorrectAnimator) {
-  RunTestOnWorkletThread(
-      &AnimationWorkletProxyClientTest::RunMutateCorrectAnimatorOnWorklet);
+TEST_F(AnimationWorkletProxyClientTest, MigrateAnimatorsBetweenGlobalScopes) {
+  RunMultipleGlobalScopeTestsOnWorklet(
+      &AnimationWorkletProxyClientTest::
+          RunMigrateAnimatorsBetweenGlobalScopesOnWorklet);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/animationworklet/animator.cc b/third_party/blink/renderer/modules/animationworklet/animator.cc
index c79ff47..f7f37b49 100644
--- a/third_party/blink/renderer/modules/animationworklet/animator.cc
+++ b/third_party/blink/renderer/modules/animationworklet/animator.cc
@@ -7,16 +7,23 @@
 #include "base/stl_util.h"
 #include "third_party/blink/renderer/bindings/modules/v8/effect_proxy_or_worklet_group_effect_proxy.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_animate_callback.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_state_callback.h"
 #include "third_party/blink/renderer/modules/animationworklet/animator_definition.h"
+#include "third_party/blink/renderer/platform/bindings/exception_state.h"
+#include "v8/include/v8.h"
 
 namespace blink {
 
 Animator::Animator(v8::Isolate* isolate,
                    AnimatorDefinition* definition,
                    v8::Local<v8::Value> instance,
+                   const String& name,
+                   WorkletAnimationOptions options,
                    int num_effects)
     : definition_(definition),
       instance_(isolate, instance),
+      name_(name),
+      options_(options),
       group_effect_(
           MakeGarbageCollected<WorkletGroupEffectProxy>(num_effects)) {
   DCHECK_GE(num_effects, 1);
@@ -70,4 +77,21 @@
   return definition_->IsStateful();
 }
 
+v8::Local<v8::Value> Animator::State(v8::Isolate* isolate,
+                                     ExceptionState& exception_state) {
+  if (!IsStateful())
+    return v8::Undefined(isolate);
+
+  v8::Local<v8::Value> instance = instance_.NewLocal(isolate);
+  DCHECK(!IsUndefinedOrNull(instance));
+
+  v8::TryCatch try_catch(isolate);
+  v8::Maybe<ScriptValue> state = definition_->StateFunction()->Invoke(instance);
+  if (try_catch.HasCaught()) {
+    exception_state.RethrowV8Exception(try_catch.Exception());
+    return v8::Undefined(isolate);
+  }
+  return state.ToChecked().V8Value();
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/animationworklet/animator.h b/third_party/blink/renderer/modules/animationworklet/animator.h
index ca354fb0..f1ab17ab 100644
--- a/third_party/blink/renderer/modules/animationworklet/animator.h
+++ b/third_party/blink/renderer/modules/animationworklet/animator.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_ANIMATIONWORKLET_ANIMATOR_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_ANIMATIONWORKLET_ANIMATOR_H_
 
+#include "third_party/blink/renderer/modules/animationworklet/worklet_animation_options.h"
 #include "third_party/blink/renderer/modules/animationworklet/worklet_group_effect_proxy.h"
 #include "third_party/blink/renderer/platform/bindings/name_client.h"
 #include "third_party/blink/renderer/platform/bindings/trace_wrapper_v8_reference.h"
@@ -26,6 +27,8 @@
   Animator(v8::Isolate*,
            AnimatorDefinition*,
            v8::Local<v8::Value> instance,
+           const String& name,
+           WorkletAnimationOptions options,
            int num_effects);
   ~Animator();
   void Trace(blink::Visitor*);
@@ -37,15 +40,27 @@
   bool Animate(v8::Isolate* isolate,
                double current_time,
                AnimationWorkletDispatcherOutput::AnimationState* output);
+  v8::Local<v8::Value> State(v8::Isolate*, ExceptionState&);
   std::vector<base::Optional<TimeDelta>> GetLocalTimes() const;
   bool IsStateful() const;
 
+  const String& name() const { return name_; }
+  WorkletAnimationOptions options() { return options_; }
+  int num_effects() const { return group_effect_->getChildren().size(); }
+
  private:
   // This object keeps the definition object, and animator instance alive.
   // It participates in wrapper tracing as it holds onto V8 wrappers.
   Member<AnimatorDefinition> definition_;
   TraceWrapperV8Reference<v8::Value> instance_;
 
+  // The 'name' and 'options' of the animator need to be stored to be
+  // migrated to the new animator upon switching global scopes. For other
+  // properties, 'animation id' and 'number of effects' can be, and 'state'
+  // should be queried on the fly.
+  String name_;
+  WorkletAnimationOptions options_;
+
   Member<WorkletGroupEffectProxy> group_effect_;
 };
 
diff --git a/third_party/blink/renderer/modules/animationworklet/animator_definition.cc b/third_party/blink/renderer/modules/animationworklet/animator_definition.cc
index 0eaf9c8..a19cd4d 100644
--- a/third_party/blink/renderer/modules/animationworklet/animator_definition.cc
+++ b/third_party/blink/renderer/modules/animationworklet/animator_definition.cc
@@ -7,12 +7,13 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_function.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_animate_callback.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_animator_constructor.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_state_callback.h"
 
 namespace blink {
 
 AnimatorDefinition::AnimatorDefinition(V8AnimatorConstructor* constructor,
                                        V8AnimateCallback* animate,
-                                       V8Function* state)
+                                       V8StateCallback* state)
     : constructor_(constructor), animate_(animate), state_(state) {
   DCHECK(constructor_);
   DCHECK(animate_);
diff --git a/third_party/blink/renderer/modules/animationworklet/animator_definition.h b/third_party/blink/renderer/modules/animationworklet/animator_definition.h
index 3d1647a..85658f2 100644
--- a/third_party/blink/renderer/modules/animationworklet/animator_definition.h
+++ b/third_party/blink/renderer/modules/animationworklet/animator_definition.h
@@ -13,7 +13,7 @@
 
 class V8AnimateCallback;
 class V8AnimatorConstructor;
-class V8Function;
+class V8StateCallback;
 
 // Represents a valid registered Javascript animator.  In particular it owns two
 // |v8::Function|s that are the "constructor" and "animate" functions of the
@@ -26,7 +26,7 @@
  public:
   explicit AnimatorDefinition(V8AnimatorConstructor* constructor,
                               V8AnimateCallback* animate,
-                              V8Function* state);
+                              V8StateCallback* state);
   ~AnimatorDefinition();
   virtual void Trace(blink::Visitor* visitor);
   const char* NameInHeapSnapshot() const override {
@@ -35,6 +35,7 @@
 
   V8AnimatorConstructor* ConstructorFunction() const { return constructor_; }
   V8AnimateCallback* AnimateFunction() const { return animate_; }
+  V8StateCallback* StateFunction() const { return state_; }
   bool IsStateful() const { return state_; }
 
  private:
@@ -42,7 +43,7 @@
   // alive. It participates in wrapper tracing as it holds onto V8 wrappers.
   Member<V8AnimatorConstructor> constructor_;
   Member<V8AnimateCallback> animate_;
-  Member<V8Function> state_;
+  Member<V8StateCallback> state_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/animationworklet/worklet_animation_options.h b/third_party/blink/renderer/modules/animationworklet/worklet_animation_options.h
index 4868ad2b..e11a3d2 100644
--- a/third_party/blink/renderer/modules/animationworklet/worklet_animation_options.h
+++ b/third_party/blink/renderer/modules/animationworklet/worklet_animation_options.h
@@ -5,17 +5,18 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_ANIMATIONWORKLET_WORKLET_ANIMATION_OPTIONS_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_ANIMATIONWORKLET_WORKLET_ANIMATION_OPTIONS_H_
 
+#include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
 #include "third_party/blink/renderer/platform/animation/compositor_animation.h"
 
 namespace blink {
 
-class SerializedScriptValue;
-
 class MODULES_EXPORT WorkletAnimationOptions final
     : public cc::AnimationOptions {
  public:
   explicit WorkletAnimationOptions(scoped_refptr<SerializedScriptValue>);
+  WorkletAnimationOptions(const WorkletAnimationOptions&) = default;
+  WorkletAnimationOptions& operator=(const WorkletAnimationOptions&) = default;
   std::unique_ptr<cc::AnimationOptions> Clone() const override;
 
   scoped_refptr<SerializedScriptValue> GetData() { return data_; }
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_path.idl b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_path.idl
index 29bfbaf..81a279a 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_path.idl
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_path.idl
@@ -4,9 +4,7 @@
 
 // https://html.spec.whatwg.org/C/#canvaspath
 
-[
-    NoInterfaceObject // Always used on target of 'implements'
-] interface CanvasPath {
+interface mixin CanvasPath {
     // shared path API methods
     void closePath();
     void moveTo(unrestricted float x, unrestricted float y);
diff --git a/third_party/blink/renderer/modules/credentialmanager/credential_user_data.idl b/third_party/blink/renderer/modules/credentialmanager/credential_user_data.idl
index 970140e..c2082ee 100644
--- a/third_party/blink/renderer/modules/credentialmanager/credential_user_data.idl
+++ b/third_party/blink/renderer/modules/credentialmanager/credential_user_data.idl
@@ -4,8 +4,7 @@
 
 // https://w3c.github.io/webappsec-credential-management/#credentialuserdata
 
-[NoInterfaceObject]
-interface CredentialUserData {
+interface mixin CredentialUserData {
     readonly attribute USVString name;
     readonly attribute USVString iconURL;
 };
diff --git a/third_party/blink/renderer/modules/encryptedmedia/navigator_request_media_key_system_access.cc b/third_party/blink/renderer/modules/encryptedmedia/navigator_request_media_key_system_access.cc
index cf8b72a..ee5079d 100644
--- a/third_party/blink/renderer/modules/encryptedmedia/navigator_request_media_key_system_access.cc
+++ b/third_party/blink/renderer/modules/encryptedmedia/navigator_request_media_key_system_access.cc
@@ -127,7 +127,8 @@
     return supported_configurations_;
   }
   const SecurityOrigin* GetSecurityOrigin() const override;
-  void RequestSucceeded(WebContentDecryptionModuleAccess*) override;
+  void RequestSucceeded(
+      std::unique_ptr<WebContentDecryptionModuleAccess>) override;
   void RequestNotSupported(const WebString& error_message) override;
 
   ScriptPromise Promise() { return resolver_->Promise(); }
@@ -216,14 +217,14 @@
 }
 
 void MediaKeySystemAccessInitializer::RequestSucceeded(
-    WebContentDecryptionModuleAccess* access) {
+    std::unique_ptr<WebContentDecryptionModuleAccess> access) {
   DVLOG(3) << __func__;
 
   if (!IsExecutionContextValid())
     return;
 
   resolver_->Resolve(
-      MakeGarbageCollected<MediaKeySystemAccess>(base::WrapUnique(access)));
+      MakeGarbageCollected<MediaKeySystemAccess>(std::move(access)));
   resolver_.Clear();
 }
 
diff --git a/third_party/blink/renderer/modules/mediastream/BUILD.gn b/third_party/blink/renderer/modules/mediastream/BUILD.gn
index 0ca5ecb..3ee2db8 100644
--- a/third_party/blink/renderer/modules/mediastream/BUILD.gn
+++ b/third_party/blink/renderer/modules/mediastream/BUILD.gn
@@ -48,6 +48,7 @@
     "user_media_request.cc",
     "user_media_request.h",
     "video_track_adapter.cc",
+    "video_track_adapter_settings.cc",
     "web_media_stream_utils.cc",
   ]
 }
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source_test.cc b/third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source_test.cc
index 7b5c0965..9665996 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source_test.cc
@@ -16,7 +16,7 @@
 #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
 #include "third_party/blink/public/web/modules/mediastream/media_stream_video_sink.h"
 #include "third_party/blink/public/web/modules/mediastream/media_stream_video_track.h"
-#include "third_party/blink/public/web/modules/mediastream/video_track_adapter.h"
+#include "third_party/blink/public/web/modules/mediastream/video_track_adapter_settings.h"
 #include "third_party/blink/public/web/web_heap.h"
 #include "third_party/blink/renderer/modules/mediastream/mock_mojo_media_stream_dispatcher_host.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread.h"
diff --git a/third_party/blink/renderer/modules/mediastream/video_track_adapter.cc b/third_party/blink/renderer/modules/mediastream/video_track_adapter.cc
index 40b9252..83f8f2cb 100644
--- a/third_party/blink/renderer/modules/mediastream/video_track_adapter.cc
+++ b/third_party/blink/renderer/modules/mediastream/video_track_adapter.cc
@@ -24,6 +24,7 @@
 #include "media/base/limits.h"
 #include "media/base/video_util.h"
 #include "third_party/blink/public/platform/platform.h"
+#include "third_party/blink/public/web/modules/mediastream/video_track_adapter_settings.h"
 #include "third_party/blink/renderer/platform/wtf/thread_safe_ref_counted.h"
 
 namespace blink {
@@ -472,49 +473,6 @@
                                   base::BindOnce(frame_dropped_cb_, reason));
 }
 
-VideoTrackAdapterSettings::VideoTrackAdapterSettings()
-    : VideoTrackAdapterSettings(base::nullopt,
-                                0.0,
-                                std::numeric_limits<double>::max(),
-                                0.0) {}
-
-VideoTrackAdapterSettings::VideoTrackAdapterSettings(
-    const gfx::Size& target_size,
-    double max_frame_rate)
-    : VideoTrackAdapterSettings(target_size, 0.0, HUGE_VAL, max_frame_rate) {}
-
-VideoTrackAdapterSettings::VideoTrackAdapterSettings(
-    base::Optional<gfx::Size> target_size,
-    double min_aspect_ratio,
-    double max_aspect_ratio,
-    double max_frame_rate)
-    : target_size_(std::move(target_size)),
-      min_aspect_ratio_(min_aspect_ratio),
-      max_aspect_ratio_(max_aspect_ratio),
-      max_frame_rate_(max_frame_rate) {
-  DCHECK(!target_size_ ||
-         (target_size_->width() >= 0 && target_size_->height() >= 0));
-  DCHECK(!std::isnan(min_aspect_ratio_));
-  DCHECK_GE(min_aspect_ratio_, 0.0);
-  DCHECK(!std::isnan(max_aspect_ratio_));
-  DCHECK_GE(max_aspect_ratio_, min_aspect_ratio_);
-  DCHECK(!std::isnan(max_frame_rate_));
-  DCHECK_GE(max_frame_rate_, 0.0);
-}
-
-VideoTrackAdapterSettings::VideoTrackAdapterSettings(
-    const VideoTrackAdapterSettings& other) = default;
-VideoTrackAdapterSettings& VideoTrackAdapterSettings::operator=(
-    const VideoTrackAdapterSettings& other) = default;
-
-bool VideoTrackAdapterSettings::operator==(
-    const VideoTrackAdapterSettings& other) const {
-  return target_size_ == other.target_size_ &&
-         min_aspect_ratio_ == other.min_aspect_ratio_ &&
-         max_aspect_ratio_ == other.max_aspect_ratio_ &&
-         max_frame_rate_ == other.max_frame_rate_;
-}
-
 VideoTrackAdapter::VideoTrackAdapter(
     scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
     base::RepeatingCallback<void(media::VideoCaptureFrameDropReason)>
@@ -617,7 +575,6 @@
                                 this, source_frame_size));
 }
 
-// static
 bool VideoTrackAdapter::CalculateDesiredSize(
     bool is_rotated,
     const gfx::Size& original_input_size,
diff --git a/third_party/blink/renderer/modules/mediastream/video_track_adapter_settings.cc b/third_party/blink/renderer/modules/mediastream/video_track_adapter_settings.cc
new file mode 100644
index 0000000..b23e06d
--- /dev/null
+++ b/third_party/blink/renderer/modules/mediastream/video_track_adapter_settings.cc
@@ -0,0 +1,56 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/public/web/modules/mediastream/video_track_adapter_settings.h"
+
+#include <limits>
+#include <memory>
+#include <utility>
+
+namespace blink {
+
+VideoTrackAdapterSettings::VideoTrackAdapterSettings()
+    : VideoTrackAdapterSettings(base::nullopt,
+                                0.0,
+                                std::numeric_limits<double>::max(),
+                                0.0) {}
+
+VideoTrackAdapterSettings::VideoTrackAdapterSettings(
+    const gfx::Size& target_size,
+    double max_frame_rate)
+    : VideoTrackAdapterSettings(target_size, 0.0, HUGE_VAL, max_frame_rate) {}
+
+VideoTrackAdapterSettings::VideoTrackAdapterSettings(
+    base::Optional<gfx::Size> target_size,
+    double min_aspect_ratio,
+    double max_aspect_ratio,
+    double max_frame_rate)
+    : target_size_(std::move(target_size)),
+      min_aspect_ratio_(min_aspect_ratio),
+      max_aspect_ratio_(max_aspect_ratio),
+      max_frame_rate_(max_frame_rate) {
+  DCHECK(!target_size_ ||
+         (target_size_->width() >= 0 && target_size_->height() >= 0));
+  DCHECK(!std::isnan(min_aspect_ratio_));
+  DCHECK_GE(min_aspect_ratio_, 0.0);
+  DCHECK(!std::isnan(max_aspect_ratio_));
+  DCHECK_GE(max_aspect_ratio_, min_aspect_ratio_);
+  DCHECK(!std::isnan(max_frame_rate_));
+  DCHECK_GE(max_frame_rate_, 0.0);
+}
+
+VideoTrackAdapterSettings::VideoTrackAdapterSettings(
+    const VideoTrackAdapterSettings& other) = default;
+VideoTrackAdapterSettings& VideoTrackAdapterSettings::operator=(
+    const VideoTrackAdapterSettings& other) = default;
+
+bool VideoTrackAdapterSettings::operator==(
+    const VideoTrackAdapterSettings& other) const {
+  return target_size_ == other.target_size_ &&
+         min_aspect_ratio_ == other.min_aspect_ratio_ &&
+         max_aspect_ratio_ == other.max_aspect_ratio_ &&
+         max_frame_rate_ == other.max_frame_rate_;
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/webgl/webgl2_compute_rendering_context_base.idl b/third_party/blink/renderer/modules/webgl/webgl2_compute_rendering_context_base.idl
index 9cd92a0f..fedeb6c7 100644
--- a/third_party/blink/renderer/modules/webgl/webgl2_compute_rendering_context_base.idl
+++ b/third_party/blink/renderer/modules/webgl/webgl2_compute_rendering_context_base.idl
@@ -2,9 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-[
-    NoInterfaceObject
-] interface WebGL2ComputeRenderingContextBase {
+interface mixin WebGL2ComputeRenderingContextBase {
     // TODO(jiajia.qin@intel.com): add more enums and apis
     const GLenum COMPUTE_SHADER                         = 0x91B9;
     const GLenum UNIFORM                                = 0x92E1;
diff --git a/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.idl b/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.idl
index beb3f21a..060d0be 100644
--- a/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.idl
+++ b/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.idl
@@ -7,9 +7,7 @@
 typedef long long GLint64;
 typedef unsigned long long GLuint64;
 
-[
-    NoInterfaceObject
-] interface WebGL2RenderingContextBase {
+interface mixin WebGL2RenderingContextBase {
     const GLenum READ_BUFFER                                   = 0x0C02;
     const GLenum UNPACK_ROW_LENGTH                             = 0x0CF2;
     const GLenum UNPACK_SKIP_ROWS                              = 0x0CF3;
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.idl b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.idl
index d3a2f114..b825f7e 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.idl
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.idl
@@ -40,12 +40,7 @@
 typedef unrestricted float GLfloat;
 typedef unrestricted float GLclampf;
 
-[
-    // FIXME: [DoNotCheckConstants] should be applied to members and not need to
-    // be put on implementing interface
-    // DoNotCheckConstants, // need to put on implementing interface
-    NoInterfaceObject // Always used on target of 'implements'
-] interface WebGLRenderingContextBase {
+interface mixin WebGLRenderingContextBase {
 
     [ImplementedAs=getHTMLOrOffscreenCanvas] readonly attribute (HTMLCanvasElement or OffscreenCanvas) canvas;
 
diff --git a/third_party/blink/renderer/modules/xr/xr.cc b/third_party/blink/renderer/modules/xr/xr.cc
index f693347c..f8ecd32 100644
--- a/third_party/blink/renderer/modules/xr/xr.cc
+++ b/third_party/blink/renderer/modules/xr/xr.cc
@@ -505,10 +505,11 @@
     XRSession::SessionMode mode,
     XRSession::EnvironmentBlendMode blend_mode,
     device::mojom::blink::XRSessionClientRequest client_request,
-    device::mojom::blink::VRDisplayInfoPtr display_info) {
+    device::mojom::blink::VRDisplayInfoPtr display_info,
+    bool sensorless_session) {
   XRSession* session = MakeGarbageCollected<XRSession>(
       this, client_request ? std::move(client_request) : nullptr, mode,
-      sessionModeToString(mode), blend_mode);
+      sessionModeToString(mode), blend_mode, sensorless_session);
   if (display_info)
     session->SetXRDisplayInfo(std::move(display_info));
   sessions_.insert(session);
@@ -519,8 +520,8 @@
   // TODO(https://crbug.com/944936): The blend mode could be "additive".
   XRSession::EnvironmentBlendMode blend_mode = XRSession::kBlendModeOpaque;
   return CreateSession(XRSession::kModeInline, blend_mode,
-                       nullptr /* client request */,
-                       nullptr /* display_info */);
+                       nullptr /* client request */, nullptr /* display_info */,
+                       true /* sensorless_session */);
 }
 
 void XR::Dispose() {
diff --git a/third_party/blink/renderer/modules/xr/xr.h b/third_party/blink/renderer/modules/xr/xr.h
index f50d4890d..80cb19d 100644
--- a/third_party/blink/renderer/modules/xr/xr.h
+++ b/third_party/blink/renderer/modules/xr/xr.h
@@ -111,7 +111,8 @@
       XRSession::SessionMode mode,
       XRSession::EnvironmentBlendMode blend_mode,
       device::mojom::blink::XRSessionClientRequest client_request,
-      device::mojom::blink::VRDisplayInfoPtr display_info);
+      device::mojom::blink::VRDisplayInfoPtr display_info,
+      bool sensorless_session = false);
   XRSession* CreateSensorlessInlineSession();
 
   void Dispose();
diff --git a/third_party/blink/renderer/modules/xr/xr_session.cc b/third_party/blink/renderer/modules/xr/xr_session.cc
index 815cd29..e96c3a2e 100644
--- a/third_party/blink/renderer/modules/xr/xr_session.cc
+++ b/third_party/blink/renderer/modules/xr/xr_session.cc
@@ -112,7 +112,8 @@
     device::mojom::blink::XRSessionClientRequest client_request,
     XRSession::SessionMode mode,
     const String& mode_string,
-    EnvironmentBlendMode environment_blend_mode)
+    EnvironmentBlendMode environment_blend_mode,
+    bool sensorless_session)
     : xr_(xr),
       mode_(mode),
       mode_string_(mode_string),
@@ -121,7 +122,8 @@
       client_binding_(this, std::move(client_request)),
       callback_collection_(
           MakeGarbageCollected<XRFrameRequestCallbackCollection>(
-              xr_->GetExecutionContext())) {
+              xr_->GetExecutionContext())),
+      sensorless_session_(sensorless_session) {
   render_state_ = MakeGarbageCollected<XRRenderState>();
   viewer_space_ = MakeGarbageCollected<XRViewerSpace>(this);
   blurred_ = !HasAppropriateFocus();
@@ -235,6 +237,12 @@
                                            kSessionEnded));
   }
 
+  if (sensorless_session_ && options->type() != "identity") {
+    return ScriptPromise::RejectWithDOMException(
+        script_state, DOMException::Create(DOMExceptionCode::kNotSupportedError,
+                                           kReferenceSpaceNotSupported));
+  }
+
   XRReferenceSpace* reference_space = nullptr;
   if (options->type() == "identity") {
     reference_space = MakeGarbageCollected<XRReferenceSpace>(this);
diff --git a/third_party/blink/renderer/modules/xr/xr_session.h b/third_party/blink/renderer/modules/xr/xr_session.h
index 18c6811..d8c25720 100644
--- a/third_party/blink/renderer/modules/xr/xr_session.h
+++ b/third_party/blink/renderer/modules/xr/xr_session.h
@@ -65,7 +65,8 @@
             device::mojom::blink::XRSessionClientRequest client_request,
             SessionMode mode,
             const String& mode_string,
-            EnvironmentBlendMode environment_blend_mode);
+            EnvironmentBlendMode environment_blend_mode,
+            bool sensorless_session);
   ~XRSession() override = default;
 
   XR* xr() const { return xr_; }
@@ -252,6 +253,10 @@
   // Dimensions of the output canvas.
   int output_width_ = 1;
   int output_height_ = 1;
+
+  // Indicates that this is a sensorless session which should only support the
+  // identity reference space.
+  bool sensorless_session_ = false;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/encrypted_media_request.h b/third_party/blink/renderer/platform/encrypted_media_request.h
index 92e73fb..143f47c 100644
--- a/third_party/blink/renderer/platform/encrypted_media_request.h
+++ b/third_party/blink/renderer/platform/encrypted_media_request.h
@@ -5,6 +5,8 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_ENCRYPTED_MEDIA_REQUEST_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_ENCRYPTED_MEDIA_REQUEST_H_
 
+#include <memory>
+
 #include "third_party/blink/renderer/platform/heap/handle.h"
 
 namespace blink {
@@ -27,7 +29,8 @@
 
   virtual const SecurityOrigin* GetSecurityOrigin() const = 0;
 
-  virtual void RequestSucceeded(WebContentDecryptionModuleAccess*) = 0;
+  virtual void RequestSucceeded(
+      std::unique_ptr<WebContentDecryptionModuleAccess>) = 0;
   virtual void RequestNotSupported(const WebString& error_message) = 0;
 
   virtual void Trace(blink::Visitor* visitor) {}
diff --git a/third_party/blink/renderer/platform/exported/web_encrypted_media_request.cc b/third_party/blink/renderer/platform/exported/web_encrypted_media_request.cc
index 470722c..e0a066e 100644
--- a/third_party/blink/renderer/platform/exported/web_encrypted_media_request.cc
+++ b/third_party/blink/renderer/platform/exported/web_encrypted_media_request.cc
@@ -40,8 +40,8 @@
 }
 
 void WebEncryptedMediaRequest::RequestSucceeded(
-    WebContentDecryptionModuleAccess* access) {
-  private_->RequestSucceeded(access);
+    std::unique_ptr<WebContentDecryptionModuleAccess> access) {
+  private_->RequestSucceeded(std::move(access));
 }
 
 void WebEncryptedMediaRequest::RequestNotSupported(
diff --git a/third_party/blink/renderer/platform/image-decoders/image_decoder.cc b/third_party/blink/renderer/platform/image-decoders/image_decoder.cc
index 6133e01..cd3399c 100644
--- a/third_party/blink/renderer/platform/image-decoders/image_decoder.cc
+++ b/third_party/blink/renderer/platform/image-decoders/image_decoder.cc
@@ -138,6 +138,31 @@
   return data.size() >= kLongestSignatureLength;
 }
 
+// static
+String ImageDecoder::SniffImageType(scoped_refptr<SharedBuffer> image_data) {
+  // Access the first kLongestSignatureLength chars to sniff the signature.
+  // (note: FastSharedBufferReader only makes a copy if the bytes are segmented)
+  char buffer[kLongestSignatureLength];
+  const FastSharedBufferReader fast_reader(
+      SegmentReader::CreateFromSharedBuffer(std::move(image_data)));
+  const char* contents =
+      fast_reader.GetConsecutiveData(0, kLongestSignatureLength, buffer);
+
+  if (MatchesJPEGSignature(contents))
+    return "image/jpeg";
+  if (MatchesPNGSignature(contents))
+    return "image/png";
+  if (MatchesGIFSignature(contents))
+    return "image/gif";
+  if (MatchesWebPSignature(contents))
+    return "image/webp";
+  if (MatchesICOSignature(contents) || MatchesCURSignature(contents))
+    return "image/x-icon";
+  if (MatchesBMPSignature(contents))
+    return "image/bmp";
+  return String();
+}
+
 size_t ImageDecoder::FrameCount() {
   const size_t old_size = frame_buffer_cache_.size();
   const size_t new_size = DecodeFrameCount();
diff --git a/third_party/blink/renderer/platform/image-decoders/image_decoder.h b/third_party/blink/renderer/platform/image-decoders/image_decoder.h
index bbb221c8..56488d7a 100644
--- a/third_party/blink/renderer/platform/image-decoders/image_decoder.h
+++ b/third_party/blink/renderer/platform/image-decoders/image_decoder.h
@@ -168,6 +168,9 @@
   // failure is due to insufficient or bad data.
   static bool HasSufficientDataToSniffImageType(const SharedBuffer&);
 
+  // Looks at the image data to determine and return the image MIME type.
+  static String SniffImageType(scoped_refptr<SharedBuffer> image_data);
+
   void SetData(scoped_refptr<SegmentReader> data, bool all_data_received) {
     if (failed_)
       return;
diff --git a/third_party/blink/renderer/platform/weborigin/known_ports.cc b/third_party/blink/renderer/platform/weborigin/known_ports.cc
index 609b35a..5cf7ba7 100644
--- a/third_party/blink/renderer/platform/weborigin/known_ports.cc
+++ b/third_party/blink/renderer/platform/weborigin/known_ports.cc
@@ -79,7 +79,7 @@
   if (!effective_port)
     effective_port = DefaultPortForProtocol(protocol);
   StringUTF8Adaptor utf8(protocol);
-  return net::IsPortAllowedForScheme(effective_port, utf8.AsStdString());
+  return net::IsPortAllowedForScheme(effective_port, utf8.AsStringPiece());
 }
 
 }  // namespace blink
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 6d231b94..dc2a98d9 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -6063,6 +6063,7 @@
 crbug.com/937416 http/tests/devtools/resource-tree/resource-tree-frame-navigate.js [ Pass Failure ]
 crbug.com/937416 virtual/nobinary-for-devtools/http/tests/devtools/resource-tree/resource-tree-frame-navigate.js [ Pass Failure ]
 crbug.com/937546 [ Win7 ] http/tests/security/xss-DENIED-cross-origin-stack-overflow.html [ Pass Timeout ]
+crbug.com/937546 [ Win7 ] virtual/outofblink-cors/http/tests/security/xss-DENIED-cross-origin-stack-overflow.html [ Pass Timeout ]
 # Sheriff 2019-03-02
 crbug.com/937639 [ Linux Debug ] external/wpt/html/browsers/browsing-the-web/read-media/pageload-image-in-popup.html [ Pass Failure ]
 
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 552c4c70..62792d4 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -482,13 +482,8 @@
   },
   {
     "prefix": "dark-mode",
-    "base": "paint/dark-mode/native-theme-off",
-    "args": ["--blink-settings=darkMode=3"]
-  },
-  {
-    "prefix": "dark-mode",
-    "base": "paint/dark-mode/native-theme-on",
-    "args": ["--blink-settings=darkMode=3"]
+    "base": "paint/dark-mode/svg-invert-all",
+    "args": ["--blink-settings=darkMode=3,darkModeImagePolicy=0"]
   },
   {
     "prefix": "outofblink-cors",
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
index e07cff4..ba86f88 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
@@ -159811,6 +159811,11 @@
      {}
     ]
    ],
+   "dom/events/EventTarget-dispatchEvent-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "dom/events/event-global-extra.window-expected.txt": [
     [
      {}
@@ -199896,11 +199901,6 @@
      {}
     ]
    ],
-   "webrtc/RTCPeerConnection-onnegotiationneeded-expected.txt": [
-    [
-     {}
-    ]
-   ],
    "webrtc/RTCPeerConnection-setDescription-transceiver-expected.txt": [
     [
      {}
@@ -407757,12 +407757,16 @@
    "b0e7614e625b3de018eb76c90148c7710b6c807f",
    "testharness"
   ],
+  "dom/events/EventTarget-dispatchEvent-expected.txt": [
+   "625adecb9f4c0c5700e99243c5be7fa6bbabb71a",
+   "support"
+  ],
   "dom/events/EventTarget-dispatchEvent-returnvalue.html": [
    "c4466e0d6cdada90be82a95a208c1ee025cfb96a",
    "testharness"
   ],
   "dom/events/EventTarget-dispatchEvent.html": [
-   "1a8bf3de915d5cb8c608ecdef69511d4dbffe113",
+   "8a0d7353bc62ba8330069fef4c71a0e33321b0aa",
    "testharness"
   ],
   "dom/events/EventTarget-removeEventListener.html": [
@@ -430042,11 +430046,11 @@
    "testharness"
   ],
   "html/semantics/forms/textfieldselection/selection-start-end-extra-expected.txt": [
-   "b2174bf856c36ffa08037200b5ee884d6fc5f60d",
+   "953645a241941b237fd639992f26e9db8ee9dc43",
    "support"
   ],
   "html/semantics/forms/textfieldselection/selection-start-end-extra.html": [
-   "af51354035c5cae33d35cbff03e2eed5d5359f85",
+   "e76f5f6ea70c2ba769fe6a75e9aa1c95d98e2760",
    "testharness"
   ],
   "html/semantics/forms/textfieldselection/selection-start-end.html": [
@@ -475758,7 +475762,7 @@
    "support"
   ],
   "url/resources/urltestdata.json": [
-   "26b8ea2e0bc9a166deef8af1a0df87e0a7e0fda4",
+   "bf4e2a7833d17fab604eb634051e627887fe936a",
    "support"
   ],
   "url/toascii.window-expected.txt": [
@@ -480425,12 +480429,8 @@
    "2fd33ca541148ca6a7be5f5624e303795c7c458d",
    "testharness"
   ],
-  "webrtc/RTCPeerConnection-onnegotiationneeded-expected.txt": [
-   "62c3abf9a08a1fcbc0c5454a5b417ea4c85623e3",
-   "support"
-  ],
   "webrtc/RTCPeerConnection-onnegotiationneeded.html": [
-   "f7bf8bd3e3bdc24c63a92da22eaae77b95f0d4fd",
+   "336b100de058200ab49400fac9ae00f8524b9da9",
    "testharness"
   ],
   "webrtc/RTCPeerConnection-onsignalingstatechanged.https.html": [
diff --git a/third_party/blink/web_tests/external/wpt/animation-worklet/stateful-animator.https.html b/third_party/blink/web_tests/external/wpt/animation-worklet/stateful-animator.https.html
new file mode 100644
index 0000000..c4b6301
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/animation-worklet/stateful-animator.https.html
@@ -0,0 +1,162 @@
+<!DOCTYPE html>
+<title>Basic use of stateful animator</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-animationworklet/">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/web-animations/testcommon.js"></script>
+<script src="common.js"></script>
+
+<div id="target"></div>
+
+<script id="stateful_animator_basic" type="text/worklet">
+  registerAnimator("stateful_animator_basic", class {
+    constructor(options, state = { test_local_time: 0 }) {
+      this.test_local_time = state.test_local_time;
+    }
+    animate(currentTime, effect) {
+      effect.localTime = this.test_local_time++;
+    }
+    state() {
+      return {
+        test_local_time: this.test_local_time
+      };
+    }
+  });
+</script>
+
+<script id="stateless_animator_basic" type="text/worklet">
+  registerAnimator("stateless_animator_basic", class {
+    constructor(options, state = { test_local_time: 0 }) {
+      this.test_local_time = state.test_local_time;
+    }
+    animate(currentTime, effect) {
+      effect.localTime = this.test_local_time++;
+    }
+    // Unless a valid state function is provided, the animator is considered
+    // stateless. e.g. animator with incorrect state function name.
+    State() {
+      return {
+        test_local_time: this.test_local_time
+      };
+    }
+  });
+</script>
+
+<script id="state_function_returns_empty" type="text/worklet">
+  registerAnimator("state_function_returns_empty", class {
+    constructor(options, state = { test_local_time: 0 }) {
+      this.test_local_time = state.test_local_time;
+    }
+    animate(currentTime, effect) {
+      effect.localTime = this.test_local_time++;
+    }
+    state() {}
+  });
+</script>
+
+<script id="state_function_returns_not_serializable" type="text/worklet">
+  registerAnimator("state_function_returns_not_serializable", class {
+    constructor(options) {
+      this.test_local_time = 0;
+    }
+    animate(currentTime, effect) {
+      effect.localTime = this.test_local_time++;
+    }
+    state() {
+      return new Error('foo');
+    }
+  });
+</script>
+
+<script>
+  async function localTimeDoesNotUpdate(animation) {
+    // The local time stops increasing after the animator instance being dropped.
+    // e.g. 0, 1, 2, .., n, n, n, n, .. where n is the frame that the global
+    // scope switches at.
+    let last_local_time = animation.effect.getComputedTiming().localTime;
+    let frame_count = 0;
+    const FRAMES_WITHOUT_CHANGE = 10;
+    do {
+      await new Promise(window.requestAnimationFrame);
+      let current_local_time = animation.effect.getComputedTiming().localTime;
+      if (last_local_time == current_local_time)
+        ++frame_count;
+      else
+        frame_count = 0;
+      last_local_time = current_local_time;
+    } while (frame_count < FRAMES_WITHOUT_CHANGE);
+  }
+
+  async function localTimeResetsToZero(animation) {
+    // The local time is reset upon global scope switching. e.g.
+    // 0, 1, 2, .., 0, 1, 2, .., 0, 1, 2, .., 0, 1, 2, ...
+    let reset_count = 0;
+    const LOCAL_TIME_RESET_CHECK = 3;
+    do {
+      await new Promise(window.requestAnimationFrame);
+      if (0 == animation.effect.getComputedTiming().localTime)
+        ++reset_count;
+    } while (reset_count < LOCAL_TIME_RESET_CHECK);
+  }
+
+  promise_test(async t => {
+    await runInAnimationWorklet(document.getElementById('stateful_animator_basic').textContent);
+    const target = document.getElementById('target');
+    const effect = new KeyframeEffect(target, [{ opacity: 0 }], { duration: 1000 });
+    const animation = new WorkletAnimation('stateful_animator_basic', effect);
+    animation.play();
+
+    // effect.localTime should be correctly increased upon global scope
+    // switches for stateful animators.
+    const EXPECTED_FRAMES_TO_A_SCOPE_SWITCH = 15;
+    await waitForAnimationFrameWithCondition(_ => {
+      return animation.effect.getComputedTiming().localTime ==
+          EXPECTED_FRAMES_TO_A_SCOPE_SWITCH;
+    });
+
+    animation.cancel();
+  }, "Stateful animator can use its state to update the animation. Pass if test does not timeout");
+
+  promise_test(async t => {
+    await runInAnimationWorklet(document.getElementById('stateless_animator_basic').textContent);
+    const target = document.getElementById('target');
+    const effect = new KeyframeEffect(target, [{ opacity: 0 }], { duration: 1000 });
+    const animation = new WorkletAnimation('stateless_animator_basic', effect);
+    animation.play();
+
+    // The local time should be reset to 0 upon global scope switching for
+    // stateless animators.
+    await localTimeResetsToZero(animation);
+
+    animation.cancel();
+  }, "Stateless animator gets reecreated with 'undefined' state.");
+
+  promise_test(async t => {
+    await runInAnimationWorklet(document.getElementById('state_function_returns_empty').textContent);
+    const target = document.getElementById('target');
+    const effect = new KeyframeEffect(target, [{ opacity: 0 }], { duration: 1000 });
+    const animation = new WorkletAnimation('state_function_returns_empty', effect);
+    animation.play();
+
+    // The local time should be reset to 0 upon global scope switching for
+    // stateless animators.
+    await localTimeResetsToZero(animation);
+
+    animation.cancel();
+  }, "Stateful animator gets recreated with 'undefined' state if state function returns undefined.");
+
+  promise_test(async t => {
+    await runInAnimationWorklet(document.getElementById('state_function_returns_not_serializable').textContent);
+    const target = document.getElementById('target');
+    const effect = new KeyframeEffect(target, [{ opacity: 0 }], { duration: 1000, iteration: Infinity });
+    const animation = new WorkletAnimation('state_function_returns_not_serializable', effect);
+    animation.play();
+
+    // The local time of an animation increases until the registered animator
+    // gets removed.
+    await localTimeDoesNotUpdate(animation);
+
+    animation.cancel();
+  }, "Stateful Animator instance gets dropped (does not get migrated) if state function is not serializable.");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/dom/events/EventTarget-dispatchEvent-expected.txt b/third_party/blink/web_tests/external/wpt/dom/events/EventTarget-dispatchEvent-expected.txt
new file mode 100644
index 0000000..625adecb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/dom/events/EventTarget-dispatchEvent-expected.txt
@@ -0,0 +1,29 @@
+This is a testharness.js-based test.
+PASS Calling dispatchEvent(null).
+PASS If the event's initialized flag is not set, an InvalidStateError must be thrown (BeforeUnloadEvent).
+PASS If the event's initialized flag is not set, an InvalidStateError must be thrown (CompositionEvent).
+PASS If the event's initialized flag is not set, an InvalidStateError must be thrown (CustomEvent).
+PASS If the event's initialized flag is not set, an InvalidStateError must be thrown (DeviceMotionEvent).
+PASS If the event's initialized flag is not set, an InvalidStateError must be thrown (DeviceOrientationEvent).
+PASS If the event's initialized flag is not set, an InvalidStateError must be thrown (DragEvent).
+PASS If the event's initialized flag is not set, an InvalidStateError must be thrown (Event).
+PASS If the event's initialized flag is not set, an InvalidStateError must be thrown (Events).
+PASS If the event's initialized flag is not set, an InvalidStateError must be thrown (FocusEvent).
+PASS If the event's initialized flag is not set, an InvalidStateError must be thrown (HashChangeEvent).
+PASS If the event's initialized flag is not set, an InvalidStateError must be thrown (HTMLEvents).
+PASS If the event's initialized flag is not set, an InvalidStateError must be thrown (KeyboardEvent).
+PASS If the event's initialized flag is not set, an InvalidStateError must be thrown (MessageEvent).
+PASS If the event's initialized flag is not set, an InvalidStateError must be thrown (MouseEvent).
+PASS If the event's initialized flag is not set, an InvalidStateError must be thrown (MouseEvents).
+PASS If the event's initialized flag is not set, an InvalidStateError must be thrown (StorageEvent).
+PASS If the event's initialized flag is not set, an InvalidStateError must be thrown (SVGEvents).
+PASS If the event's initialized flag is not set, an InvalidStateError must be thrown (TextEvent).
+PASS If the event's initialized flag is not set, an InvalidStateError must be thrown (TouchEvent).
+PASS If the event's initialized flag is not set, an InvalidStateError must be thrown (UIEvent).
+PASS If the event's initialized flag is not set, an InvalidStateError must be thrown (UIEvents).
+PASS If the event's dispatch flag is set, an InvalidStateError must be thrown.
+PASS Exceptions from event listeners must not be propagated.
+PASS Event listeners added during dispatch should be called
+FAIL Capturing event listeners should be called before non-capturing ones assert_array_equals: property 1, expected 3 but got 2
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/dom/events/EventTarget-dispatchEvent.html b/third_party/blink/web_tests/external/wpt/dom/events/EventTarget-dispatchEvent.html
index 1a8bf3d..8a0d735 100644
--- a/third_party/blink/web_tests/external/wpt/dom/events/EventTarget-dispatchEvent.html
+++ b/third_party/blink/web_tests/external/wpt/dom/events/EventTarget-dispatchEvent.html
@@ -98,7 +98,7 @@
     results.push(3)
   }), true)
   b.dispatchEvent(new Event("x"))
-  assert_array_equals(results, [1, 2, 3])
+  assert_array_equals(results, [1, 3, 2])
   this.done()
-}, "Event listeners should be called in order of addition")
+}, "Capturing event listeners should be called before non-capturing ones")
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/feature-policy/parameters/feature-parameters-inf.html b/third_party/blink/web_tests/external/wpt/feature-policy/parameters/feature-parameters-inf.html
new file mode 100644
index 0000000..db21427
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/feature-policy/parameters/feature-parameters-inf.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test oversized-images policy with threshold 'inf'</title>
+</head>
+<body>
+  <!-- The sample image has an intrinsic image size of 200x200px -->
+  <img src="resources/sample-1.png" width="200" height="200">
+  <img src="resources/sample-1.png" width="100" height="200">
+  <img src="resources/sample-1.png" width="50" height="200">
+  <br>
+  <img src="resources/sample-1.png" width="200" height="100">
+  <img src="resources/sample-1.png" width="100" height="100">
+  <img src="resources/sample-1.png" width="50" height="100">
+  <br>
+  <img src="resources/sample-1.png" width="200" height="50">
+  <img src="resources/sample-1.png" width="100" height="50">
+  <img src="resources/sample-1.png" width="50" height="50">
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/feature-policy/parameters/feature-parameters-inf.html.headers b/third_party/blink/web_tests/external/wpt/feature-policy/parameters/feature-parameters-inf.html.headers
new file mode 100644
index 0000000..1ec0034
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/feature-policy/parameters/feature-parameters-inf.html.headers
@@ -0,0 +1 @@
+Feature-Policy: oversized-images (inf)
diff --git a/third_party/blink/web_tests/external/wpt/feature-policy/parameters/feature-parameters-with-frames.html b/third_party/blink/web_tests/external/wpt/feature-policy/parameters/feature-parameters-with-frames.html
new file mode 100644
index 0000000..de0a3ab1e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/feature-policy/parameters/feature-parameters-with-frames.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script src="/feature-policy/resources/featurepolicy.js"></script>
+  <title>Test oversized-images policy with threshold 1.5</title>
+</head>
+<body>
+  <iframe scrolling="no" name="a" style="overflow:hidden" width="380" height="220"></iframe>
+  <iframe scrolling="no" name="b" style="overflow:hidden" width="380" height="220"></iframe>
+  <iframe scrolling="no" name="c" style="overflow:hidden" width="380" height="220"></iframe>
+  <iframe scrolling="no" name="d" style="overflow:hidden" width="380" height="220"></iframe>
+  <iframe scrolling="no" name="e" style="overflow:hidden" width="380" height="220"></iframe>
+
+  <script>
+    const frame_to_test_map = {};
+    window.addEventListener('message', ev => {
+      if (ev.data.type == "finished") {
+        if (frame_to_test_map.hasOwnProperty(ev.data.name)) {
+          frame_to_test_map[ev.data.name].done();
+        }
+      }
+    });
+    const config = {
+      a: {threshold: 0.0, blocked: 3},
+      b: {threshold: 1.0, blocked: 2},
+      c: {threshold: 2.5, blocked: 1},
+      d: {threshold: 4.0, blocked: 0},
+      e: {threshold: "inf", blocked: 0}
+    };
+    const iframes = document.querySelectorAll('iframe');
+    const total_iframes = iframes.length;
+    iframes.forEach(iframe => {
+      const frame_config = config[iframe.name]
+      async_test(t => {
+        frame_to_test_map[iframe.name] = t;
+        iframe.src = "resources/feature-parameters-frame.html?name="+iframe.name+"&n="+frame_config.blocked+"&pipe=header(Feature-Policy,oversized-images%20("+frame_config.threshold+"\\);)";
+      }, "Test frame with threshold " + frame_config.threshold + " should block " + frame_config.blocked + " images.");
+    });
+  </script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/feature-policy/parameters/feature-parameters.html b/third_party/blink/web_tests/external/wpt/feature-policy/parameters/feature-parameters.html
new file mode 100644
index 0000000..9830f93e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/feature-policy/parameters/feature-parameters.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script src="/feature-policy/resources/featurepolicy.js"></script>
+  <title>Test oversized-images policy with threshold 1.5</title>
+</head>
+<body>
+  <!-- The sample image has an intrinsic image size of 200x200px -->
+  <img src="resources/sample-1.png" width="200" height="200">
+  <img src="resources/sample-1.png" width="100" height="200">
+  <img src="resources/sample-1.png" width="50" height="200">
+  <br>
+  <img src="resources/sample-1.png" width="200" height="100">
+  <img src="resources/sample-1.png" width="100" height="100">
+  <img src="resources/sample-1.png" width="50" height="100">
+  <br>
+  <img src="resources/sample-1.png" width="200" height="50">
+  <img src="resources/sample-1.png" width="100" height="50">
+  <img src="resources/sample-1.png" width="50" height="50">
+
+  <script>
+    expect_reports(8, "oversized-images", "8 images should be blocked by policy");
+  </script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/feature-policy/parameters/feature-parameters.html.headers b/third_party/blink/web_tests/external/wpt/feature-policy/parameters/feature-parameters.html.headers
new file mode 100644
index 0000000..b4fa805
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/feature-policy/parameters/feature-parameters.html.headers
@@ -0,0 +1 @@
+Feature-Policy: oversized-images (1.5)
diff --git a/third_party/blink/web_tests/external/wpt/feature-policy/parameters/resources/feature-parameters-frame.html b/third_party/blink/web_tests/external/wpt/feature-policy/parameters/resources/feature-parameters-frame.html
new file mode 100644
index 0000000..4f01f85
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/feature-policy/parameters/resources/feature-parameters-frame.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script src="/feature-policy/resources/featurepolicy.js"></script>
+  <title>Test oversized-images policy in subframe</title>
+</head>
+<body>
+  <!-- The sample image has an intrinsic image size of 200x200px -->
+  <img width="200" height="200">
+  <img width="100" height="200">
+  <img width="50" height="200">
+
+  <script>
+    const policy_name = "oversized-images";
+    const params = new URLSearchParams(document.location.search);
+    const frame_name = params.get('name');
+    const expected_report_count = +params.get('n');
+    var num_received_reports = 0;
+
+    const images = document.querySelectorAll('img');
+    const total_images = images.length;
+    var images_loaded = 0;
+
+    const notifyIfDone = () => {
+        if (num_received_reports >= expected_report_count &&
+            images_loaded == total_images) {
+            parent.postMessage({
+                "type": "finished",
+                "name": frame_name
+            },"*");
+        }
+    };
+
+    images.forEach(image => {
+        image.addEventListener('load', () => { images_loaded++; notifyIfDone(); });
+        image.src = "sample-1.png";
+    });
+
+    new ReportingObserver((reports, observer) => {
+        const relevant_reports = reports.filter(r => (r.body.featureId === policy_name));
+        num_received_reports += relevant_reports.length;
+        notifyIfDone();
+    }, {types: ['feature-policy-violation'], buffered: true}).observe();
+  </script>
+
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/feature-policy/parameters/resources/sample-1.png b/third_party/blink/web_tests/external/wpt/feature-policy/parameters/resources/sample-1.png
new file mode 100644
index 0000000..92901925
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/feature-policy/parameters/resources/sample-1.png
Binary files differ
diff --git a/third_party/blink/web_tests/external/wpt/feature-policy/resources/featurepolicy.js b/third_party/blink/web_tests/external/wpt/feature-policy/resources/featurepolicy.js
index e2577f3..744c4c6 100644
--- a/third_party/blink/web_tests/external/wpt/feature-policy/resources/featurepolicy.js
+++ b/third_party/blink/web_tests/external/wpt/feature-policy/resources/featurepolicy.js
@@ -433,3 +433,16 @@
     assert_false(frame_policy.allowedFeatures().includes(feature));
   }
 }
+
+function expect_reports(report_count, policy_name, description) {
+  async_test(t => {
+    var num_received_reports = 0;
+    new ReportingObserver(t.step_func((reports, observer) => {
+        const relevant_reports = reports.filter(r => (r.body.featureId === policy_name));
+        num_received_reports += relevant_reports.length;
+        if (num_received_reports >= report_count) {
+            t.done();
+        }
+   }), {types: ['feature-policy-violation'], buffered: true}).observe();
+  }, description);
+}
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/textfieldselection/selection-start-end-extra-expected.txt b/third_party/blink/web_tests/external/wpt/html/semantics/forms/textfieldselection/selection-start-end-extra-expected.txt
index b2174bf..953645a 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/forms/textfieldselection/selection-start-end-extra-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/textfieldselection/selection-start-end-extra-expected.txt
@@ -4,10 +4,11 @@
 PASS Setting textContent in a textarea should move selection{Start,End} to the end
 PASS Adding children to a textarea should move selection{Start,End} to the end
 PASS Removing children from a textarea should update selection{Start,End}
-FAIL Setting the same value (with different newlines) in a textarea should NOT update selection{Start,End} assert_equals: expected 3 but got 14
+PASS Setting the same value (with different newlines) in a textarea should NOT update selection{Start,End}
+FAIL Removing child nodes in non-dirty textarea should make selection{Start,End} 0 assert_equals: selectionStart after appendChild expected 0 but got 6
 PASS Setting value to a shorter string than defaultValue should correct the cursor position
 PASS Shortening value by turning the input type into 'url' should correct selection{Start,End}
-FAIL Shortening value by turning the input type into 'color' and back to 'text' should correct selection{Start,End} assert_equals: expected 7 but got 9
+FAIL Shortening value by turning the input type into 'color' and back to 'text' should correct selection{Start,End} assert_equals: expected 0 but got 9
 PASS Resetting a value to a shorter string than defaultValue should correct the cursor position
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/textfieldselection/selection-start-end-extra.html b/third_party/blink/web_tests/external/wpt/html/semantics/forms/textfieldselection/selection-start-end-extra.html
index af51354..e76f5f6 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/forms/textfieldselection/selection-start-end-extra.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/textfieldselection/selection-start-end-extra.html
@@ -69,13 +69,39 @@
     el.selectionStart = 3;
     el.selectionEnd = 5;
 
-    el.textContent = "abcdef\r\nwhatevs";
+    el.firstChild.data = "abcdef\r\nwhatevs";
     assert_equals(el.selectionStart, 3);
     assert_equals(el.selectionEnd, 5);
   }, "Setting the same value (with different newlines) in a textarea should NOT update selection{Start,End}");
 
   test(function() {
     var el = document.createElement("textarea");
+    el.textContent = "foobar";
+    el.selectionStart = 3;
+    el.selectionEnd = 5;
+    el.firstChild.remove();
+    assert_equals(el.selectionStart, 0, 'selectionStart after node removal');
+    assert_equals(el.selectionEnd, 0, 'selectionEnd after node removal');
+    el.appendChild(document.createTextNode("foobar"));
+    assert_equals(el.selectionStart, 0, 'selectionStart after appendChild');
+    assert_equals(el.selectionEnd, 0, 'selectionEnd after appendChild');
+
+    el.selectionStart = 3;
+    el.selectionEnd = 5;
+    el.textContent = "foobar2"; // This removes the child node first.
+    assert_equals(el.selectionStart, 0, 'selectionStart after textContent setter');
+    assert_equals(el.selectionEnd, 0, 'selectionEnd after textContent setter');
+
+    el.selectionStart = 3;
+    el.selectionEnd = 5;
+    el.defaultValue = "foobar"; // Same as textContent setter.
+    assert_equals(el.selectionStart, 0, 'selectionStart after defaultValue setter');
+    assert_equals(el.selectionEnd, 0, 'selectionEnd after defaultValue setter');
+
+  }, "Removing child nodes in non-dirty textarea should make selection{Start,End} 0");
+
+  test(function() {
+    var el = document.createElement("textarea");
     el.defaultValue = "123";
     assert_equals(el.value.length, 3);
     assert_equals(el.selectionStart, 3);
@@ -105,8 +131,12 @@
     assert_equals(el.selectionEnd, 9);
     el.type = "color";
     el.type = "text";
-    assert_equals(el.selectionStart, 7);
-    assert_equals(el.selectionEnd, 7);
+    // https://html.spec.whatwg.org/C/input.html#the-input-element:attr-input-type-15
+    // 9. If previouslySelectable is false and nowSelectable is true, set the
+    // element's text entry cursor position to the beginning of the text
+    // control, ...
+    assert_equals(el.selectionStart, 0);
+    assert_equals(el.selectionEnd, 0);
   }, "Shortening value by turning the input type into 'color' and back to 'text' should correct selection{Start,End}");
 
   test(function() {
diff --git a/third_party/blink/web_tests/flag-specific/disable-perfetto/inspector-protocol/timeline/tracing-proto-format-expected.txt b/third_party/blink/web_tests/flag-specific/disable-perfetto/inspector-protocol/timeline/tracing-proto-format-expected.txt
new file mode 100644
index 0000000..e1a7e53
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/disable-perfetto/inspector-protocol/timeline/tracing-proto-format-expected.txt
@@ -0,0 +1,3 @@
+Tests that tracing with proto format outputs something resembling protos.
+Start failed: Proto format is only supported with the perfetto backend.
+
diff --git a/third_party/blink/web_tests/http/tests/resources/slow-notify-done.php b/third_party/blink/web_tests/http/tests/resources/slow-notify-done.php
index 08d7d4a..1df013ef 100644
--- a/third_party/blink/web_tests/http/tests/resources/slow-notify-done.php
+++ b/third_party/blink/web_tests/http/tests/resources/slow-notify-done.php
@@ -1,5 +1,5 @@
 <?php
-usleep(1000000);
+usleep(100000);
 ?>
 DONE!
 <script>
diff --git a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index 5a77993..65977c53 100644
--- a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -1257,6 +1257,9 @@
     getter readable
     getter writable
     method constructor
+interface TrustedTypePolicyFactory
+    attribute @@toStringTag
+    method constructor
 interface URL
     attribute @@toStringTag
     getter hash
diff --git a/third_party/blink/web_tests/inspector-protocol/timeline/tracing-proto-format-expected.txt b/third_party/blink/web_tests/inspector-protocol/timeline/tracing-proto-format-expected.txt
new file mode 100644
index 0000000..22ca095
--- /dev/null
+++ b/third_party/blink/web_tests/inspector-protocol/timeline/tracing-proto-format-expected.txt
@@ -0,0 +1,4 @@
+Tests that tracing with proto format outputs something resembling protos.
+Tracing complete
+First byte: 10
+
diff --git a/third_party/blink/web_tests/inspector-protocol/timeline/tracing-proto-format.js b/third_party/blink/web_tests/inspector-protocol/timeline/tracing-proto-format.js
new file mode 100644
index 0000000..ac2e8990
--- /dev/null
+++ b/third_party/blink/web_tests/inspector-protocol/timeline/tracing-proto-format.js
@@ -0,0 +1,21 @@
+(async function(testRunner) {
+  const {page, session, dp} = await testRunner.startBlank(
+      `Tests that tracing with proto format outputs something resembling protos.`);
+
+  const TracingHelper = await testRunner.loadScript('../resources/tracing-test.js');
+  const tracingHelper = new TracingHelper(testRunner, session);
+
+  const startResponse = await dp.Tracing.start(
+      {transferMode: 'ReturnAsStream', streamFormat: 'proto'});
+  if (startResponse.error) {
+    testRunner.log('Start failed: ' + startResponse.error.message);
+    testRunner.completeTest();
+    return;
+  }
+
+  const stream = await tracingHelper.stopTracingAndReturnStream();
+  const data = await tracingHelper.retrieveStream(stream, null, null);
+  // First byte should be TracePacket field ID preamble (byte value 10).
+  testRunner.log('First byte: ' + data.charCodeAt(0));
+  testRunner.completeTest();
+})
diff --git a/third_party/blink/web_tests/paint/dark-mode/native-theme-off/text-input-elements.html b/third_party/blink/web_tests/paint/dark-mode/native-theme-off/text-input-elements.html
deleted file mode 100644
index 846b0912f..0000000
--- a/third_party/blink/web_tests/paint/dark-mode/native-theme-off/text-input-elements.html
+++ /dev/null
@@ -1 +0,0 @@
-<input style="border-width: thick"><textarea style="border-width: thick"></textarea>
diff --git a/third_party/blink/web_tests/paint/dark-mode/native-theme-on/text-input-elements.html b/third_party/blink/web_tests/paint/dark-mode/native-theme-on/text-input-elements.html
deleted file mode 100644
index f2e3d4c..0000000
--- a/third_party/blink/web_tests/paint/dark-mode/native-theme-on/text-input-elements.html
+++ /dev/null
@@ -1 +0,0 @@
-<input><textarea></textarea>
diff --git a/third_party/blink/web_tests/paint/dark-mode/svg-invert-all/resources/circle.svg b/third_party/blink/web_tests/paint/dark-mode/svg-invert-all/resources/circle.svg
new file mode 100644
index 0000000..7dcc595
--- /dev/null
+++ b/third_party/blink/web_tests/paint/dark-mode/svg-invert-all/resources/circle.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 100 100">
+  <circle cx="50" cy="50" r="40" stroke="black" stroke-width="2" fill="white" />
+</svg>
diff --git a/third_party/blink/web_tests/paint/dark-mode/svg-invert-all/svg-as-image.html b/third_party/blink/web_tests/paint/dark-mode/svg-invert-all/svg-as-image.html
new file mode 100644
index 0000000..21c0f06
--- /dev/null
+++ b/third_party/blink/web_tests/paint/dark-mode/svg-invert-all/svg-as-image.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<img src="resources/circle.svg" />
diff --git a/third_party/blink/web_tests/paint/dark-mode/svg-invert-all/svg-in-html.html b/third_party/blink/web_tests/paint/dark-mode/svg-invert-all/svg-in-html.html
new file mode 100644
index 0000000..c51e435
--- /dev/null
+++ b/third_party/blink/web_tests/paint/dark-mode/svg-invert-all/svg-in-html.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 100 100">
+  <circle cx="50" cy="50" r="40" stroke="black" stroke-width="2" fill="white" />
+</svg>
diff --git a/third_party/blink/web_tests/platform/linux/paint/dark-mode/native-theme-off/text-input-elements-expected.png b/third_party/blink/web_tests/platform/linux/paint/dark-mode/native-theme-off/text-input-elements-expected.png
deleted file mode 100644
index 9adc149..0000000
--- a/third_party/blink/web_tests/platform/linux/paint/dark-mode/native-theme-off/text-input-elements-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/paint/dark-mode/native-theme-on/text-input-elements-expected.png b/third_party/blink/web_tests/platform/linux/paint/dark-mode/native-theme-on/text-input-elements-expected.png
deleted file mode 100644
index 8315438..0000000
--- a/third_party/blink/web_tests/platform/linux/paint/dark-mode/native-theme-on/text-input-elements-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/dark-mode/paint/dark-mode/native-theme-off/text-input-elements-expected.png b/third_party/blink/web_tests/platform/linux/virtual/dark-mode/paint/dark-mode/native-theme-off/text-input-elements-expected.png
deleted file mode 100644
index 0e3e22f0..0000000
--- a/third_party/blink/web_tests/platform/linux/virtual/dark-mode/paint/dark-mode/native-theme-off/text-input-elements-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/dark-mode/paint/dark-mode/native-theme-on/text-input-elements-expected.png b/third_party/blink/web_tests/platform/linux/virtual/dark-mode/paint/dark-mode/native-theme-on/text-input-elements-expected.png
deleted file mode 100644
index 310a6f2..0000000
--- a/third_party/blink/web_tests/platform/linux/virtual/dark-mode/paint/dark-mode/native-theme-on/text-input-elements-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/paint/dark-mode/native-theme-off/text-input-elements-expected.png b/third_party/blink/web_tests/platform/mac/paint/dark-mode/native-theme-off/text-input-elements-expected.png
deleted file mode 100644
index 0d5bd5a..0000000
--- a/third_party/blink/web_tests/platform/mac/paint/dark-mode/native-theme-off/text-input-elements-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/paint/dark-mode/native-theme-on/text-input-elements-expected.png b/third_party/blink/web_tests/platform/mac/paint/dark-mode/native-theme-on/text-input-elements-expected.png
deleted file mode 100644
index b71efac..0000000
--- a/third_party/blink/web_tests/platform/mac/paint/dark-mode/native-theme-on/text-input-elements-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/paint/dark-mode/svg-invert-all/svg-as-image-expected.png b/third_party/blink/web_tests/platform/mac/paint/dark-mode/svg-invert-all/svg-as-image-expected.png
new file mode 100644
index 0000000..b64980e
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/paint/dark-mode/svg-invert-all/svg-as-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/paint/dark-mode/svg-invert-all/svg-in-html-expected.png b/third_party/blink/web_tests/platform/mac/paint/dark-mode/svg-invert-all/svg-in-html-expected.png
new file mode 100644
index 0000000..b64980e
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/paint/dark-mode/svg-invert-all/svg-in-html-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/dark-mode/paint/dark-mode/native-theme-off/text-input-elements-expected.png b/third_party/blink/web_tests/platform/mac/virtual/dark-mode/paint/dark-mode/native-theme-off/text-input-elements-expected.png
deleted file mode 100644
index 8dd35947..0000000
--- a/third_party/blink/web_tests/platform/mac/virtual/dark-mode/paint/dark-mode/native-theme-off/text-input-elements-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/dark-mode/paint/dark-mode/native-theme-on/text-input-elements-expected.png b/third_party/blink/web_tests/platform/mac/virtual/dark-mode/paint/dark-mode/native-theme-on/text-input-elements-expected.png
deleted file mode 100644
index 80b2276..0000000
--- a/third_party/blink/web_tests/platform/mac/virtual/dark-mode/paint/dark-mode/native-theme-on/text-input-elements-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/dark-mode/paint/dark-mode/svg-invert-all/svg-as-image-expected.png b/third_party/blink/web_tests/platform/mac/virtual/dark-mode/paint/dark-mode/svg-invert-all/svg-as-image-expected.png
new file mode 100644
index 0000000..fe6af93
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/dark-mode/paint/dark-mode/svg-invert-all/svg-as-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/dark-mode/paint/dark-mode/svg-invert-all/svg-in-html-expected.png b/third_party/blink/web_tests/platform/mac/virtual/dark-mode/paint/dark-mode/svg-invert-all/svg-in-html-expected.png
new file mode 100644
index 0000000..93ae99c0
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/dark-mode/paint/dark-mode/svg-invert-all/svg-in-html-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/paint/dark-mode/native-theme-off/text-input-elements-expected.png b/third_party/blink/web_tests/platform/win/paint/dark-mode/native-theme-off/text-input-elements-expected.png
deleted file mode 100644
index 67a425c..0000000
--- a/third_party/blink/web_tests/platform/win/paint/dark-mode/native-theme-off/text-input-elements-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/paint/dark-mode/native-theme-on/text-input-elements-expected.png b/third_party/blink/web_tests/platform/win/paint/dark-mode/native-theme-on/text-input-elements-expected.png
deleted file mode 100644
index c52b4fd..0000000
--- a/third_party/blink/web_tests/platform/win/paint/dark-mode/native-theme-on/text-input-elements-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/paint/dark-mode/svg-invert-all/svg-as-image-expected.png b/third_party/blink/web_tests/platform/win/paint/dark-mode/svg-invert-all/svg-as-image-expected.png
new file mode 100644
index 0000000..89592fe
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/paint/dark-mode/svg-invert-all/svg-as-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/paint/dark-mode/svg-invert-all/svg-in-html-expected.png b/third_party/blink/web_tests/platform/win/paint/dark-mode/svg-invert-all/svg-in-html-expected.png
new file mode 100644
index 0000000..89592fe
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/paint/dark-mode/svg-invert-all/svg-in-html-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/dark-mode/paint/dark-mode/native-theme-off/text-input-elements-expected.png b/third_party/blink/web_tests/platform/win/virtual/dark-mode/paint/dark-mode/native-theme-off/text-input-elements-expected.png
deleted file mode 100644
index 5b4aadf..0000000
--- a/third_party/blink/web_tests/platform/win/virtual/dark-mode/paint/dark-mode/native-theme-off/text-input-elements-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/dark-mode/paint/dark-mode/native-theme-on/text-input-elements-expected.png b/third_party/blink/web_tests/platform/win/virtual/dark-mode/paint/dark-mode/native-theme-on/text-input-elements-expected.png
deleted file mode 100644
index 8257781..0000000
--- a/third_party/blink/web_tests/platform/win/virtual/dark-mode/paint/dark-mode/native-theme-on/text-input-elements-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/dark-mode/paint/dark-mode/svg-invert-all/svg-as-image-expected.png b/third_party/blink/web_tests/platform/win/virtual/dark-mode/paint/dark-mode/svg-invert-all/svg-as-image-expected.png
new file mode 100644
index 0000000..c4a10d1f
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/dark-mode/paint/dark-mode/svg-invert-all/svg-as-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/dark-mode/paint/dark-mode/svg-invert-all/svg-in-html-expected.png b/third_party/blink/web_tests/platform/win/virtual/dark-mode/paint/dark-mode/svg-invert-all/svg-in-html-expected.png
new file mode 100644
index 0000000..839dc5d1
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/dark-mode/paint/dark-mode/svg-invert-all/svg-in-html-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/native-theme-on/README.txt b/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/native-theme-on/README.txt
deleted file mode 100644
index cb49f2e..0000000
--- a/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/native-theme-on/README.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-# This suite runs the tests in LayoutTests/paint/dark-mode
-# with --blink-settings="darkMode=3"
-# See the virtual_test_suites() method in tools/blinkpy/web_tests/port/base.py.
diff --git a/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/native-theme-off/README.txt b/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/svg-invert-all/README.txt
similarity index 70%
rename from third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/native-theme-off/README.txt
rename to third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/svg-invert-all/README.txt
index cb49f2e..a656a64d 100644
--- a/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/native-theme-off/README.txt
+++ b/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/svg-invert-all/README.txt
@@ -1,3 +1,3 @@
 # This suite runs the tests in LayoutTests/paint/dark-mode
-# with --blink-settings="darkMode=3"
+# with --blink-settings="darkMode=3,darkModeImagePolicy=0"
 # See the virtual_test_suites() method in tools/blinkpy/web_tests/port/base.py.
diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/BUILD.gn b/third_party/cacheinvalidation/src/google/cacheinvalidation/BUILD.gn
index fc92fb5..3bbb844 100644
--- a/third_party/cacheinvalidation/src/google/cacheinvalidation/BUILD.gn
+++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/BUILD.gn
@@ -23,6 +23,5 @@
     ]
   }
 
-  proto_out_dir = "$target_gen_dir/google/cacheinvalidation"
-  public_include_dirs = [ target_gen_dir ]
+  proto_out_dir = "google/cacheinvalidation"
 }
diff --git a/third_party/crc32c/BUILD.gn b/third_party/crc32c/BUILD.gn
index 963a3c08..db9f98d 100644
--- a/third_party/crc32c/BUILD.gn
+++ b/third_party/crc32c/BUILD.gn
@@ -129,6 +129,8 @@
         "-Xclang",
         "+crypto",
       ]
+    } else {
+      cflags = [ "-march=armv8-a+crc+crypto" ]
     }
   }
 
diff --git a/third_party/node/OWNERS b/third_party/node/OWNERS
index 79ae090..ca467ff7f 100644
--- a/third_party/node/OWNERS
+++ b/third_party/node/OWNERS
@@ -1,3 +1,2 @@
 dbeam@chromium.org
 dpapad@chromium.org
-scottchen@chromium.org
diff --git a/third_party/polymer/OWNERS b/third_party/polymer/OWNERS
index cc0c2c7..0e971c4f 100644
--- a/third_party/polymer/OWNERS
+++ b/third_party/polymer/OWNERS
@@ -1,4 +1,3 @@
 dbeam@chromium.org
 dpapad@chromium.org
 michaelpg@chromium.org
-scottchen@chromium.org
diff --git a/third_party/protobuf/proto_library.gni b/third_party/protobuf/proto_library.gni
index 9798fd68..5c4ffa01 100644
--- a/third_party/protobuf/proto_library.gni
+++ b/third_party/protobuf/proto_library.gni
@@ -16,8 +16,8 @@
 #
 #   proto_out_dir (optional)
 #       Specifies the path suffix that output files are generated under.
-#       If a relative path is provided, it be appended to |root_gen_dir|.
-#       For python stubs it will be appended to |root_build_dir|/pyproto.
+#       This path will be appended to |root_gen_dir|, but for python stubs
+#       it will be appended to |root_build_dir|/pyproto.
 #
 #   generate_python (optional, default true)
 #       Generate Python protobuf stubs.
@@ -98,9 +98,6 @@
 #       A list of config labels that will be removed from the configs apllying
 #       to the source set.
 #
-#   public_include_dirs (optional)
-#       Add a public config with given include_dirs.
-#
 # Example:
 #  proto_library("mylib") {
 #    sources = [
@@ -110,11 +107,6 @@
 
 import("//build/config/sanitizers/sanitizers.gni")
 
-declare_args() {
-  # TODO(agrieve): Remove arg after all offenders have been fixed.
-  allow_proto_library_outside_of_target_gen_dir = true
-}
-
 template("proto_library") {
   assert(defined(invoker.sources), "Need sources for proto_library")
   proto_sources = invoker.sources
@@ -196,65 +188,22 @@
 
   if (defined(invoker.proto_out_dir)) {
     proto_out_dir = invoker.proto_out_dir
-
-    # TODO(agrieve): Remove special case once perfetto is updated.
-    # Note: Other perfetto paths already pass this check because
-    # get_path_info normalizes "a//b" -> "a/b".
-    proto_out_dir_is_abs =
-        proto_out_dir != "//third_party/perfetto/" &&
-        get_path_info(proto_out_dir, "abspath") == proto_out_dir
+  } else {
+    # Absolute path to the directory of current BUILD.gn file excluding "//".
+    proto_out_dir = rebase_path(".", "//")
+    if (proto_in_dir != ".") {
+      proto_out_dir += "/$proto_in_dir"
+    }
   }
 
   # We need both absolute path to use in GN statements and a relative one
   # to pass to external script.
   if (generate_cc || generate_with_plugin) {
-    if (defined(proto_out_dir)) {
-      if (!proto_out_dir_is_abs) {
-        proto_out_dir = "$root_gen_dir/$proto_out_dir"
-      }
-
-      if (!allow_proto_library_outside_of_target_gen_dir) {
-        # Use sources filter to test the prefix of proto_out_dir.
-        set_sources_assignment_filter([
-                                        target_gen_dir,
-                                        "$target_gen_dir/*",
-                                      ])
-        sources = [
-          proto_out_dir,
-        ]
-        set_sources_assignment_filter([])
-        if (sources != []) {
-          # TODO(agrieve): Change this to an assert.
-          print(
-              "proto_out_dir must be subdirectory of \$target_gen_dir (https://crbug.com/944928)")
-          print("    target_gen_dir=$target_gen_dir")
-          print("    proto_out_dir=$proto_out_dir")
-          sources = []
-        }
-      }
-
-      cc_out_dir = proto_out_dir
-    } else {
-      cc_out_dir = target_gen_dir
-      if (rebase_path(".", "//") != ".") {
-        cc_out_dir += "/$proto_in_dir"
-      }
-    }
+    cc_out_dir = "$root_gen_dir/" + proto_out_dir
     rel_cc_out_dir = rebase_path(cc_out_dir, root_build_dir)
   }
   if (generate_python) {
-    if (defined(proto_out_dir) && proto_out_dir_is_abs) {
-      py_out_dir = proto_out_dir
-    } else {
-      if (!defined(proto_out_dir)) {
-        # Absolute path to the directory of current BUILD.gn file excluding "//".
-        proto_out_dir = rebase_path(".", "//")
-        if (proto_in_dir != ".") {
-          proto_out_dir += "/$proto_in_dir"
-        }
-      }
-      py_out_dir = "$root_out_dir/pyproto/" + proto_out_dir
-    }
+    py_out_dir = "$root_out_dir/pyproto/" + proto_out_dir
     rel_py_out_dir = rebase_path(py_out_dir, root_build_dir)
   }
 
@@ -283,7 +232,7 @@
     }
   }
 
-  action_name = "${target_name}__gen"
+  action_name = "${target_name}_gen"
   source_set_name = target_name
 
   # Generate protobuf stubs.
@@ -395,21 +344,15 @@
   # use relative paths starting at |cc_out_dir|.
   # However there is no necessity to add an additional directory, if all protos
   # are located in the same directory which is in the search path by default.
-  if (has_nested_dirs || defined(invoker.import_dirs) ||
-      defined(invoker.public_include_dirs)) {
-    config_name = "${target_name}__config"
-    config(config_name) {
-      include_dirs = []
-      if (has_nested_dirs) {
-        include_dirs += [ cc_out_dir ]
-      }
-      if (defined(invoker.import_dirs)) {
-        foreach(path, invoker.import_dirs) {
-          include_dirs += [ "$root_gen_dir/" + rebase_path(path, "//") ]
-        }
-      }
-      if (defined(invoker.public_include_dirs)) {
-        include_dirs += invoker.public_include_dirs
+  config_name = "${target_name}_config"
+  config(config_name) {
+    include_dirs = []
+    if (has_nested_dirs) {
+      include_dirs += [ cc_out_dir ]
+    }
+    if (defined(invoker.import_dirs)) {
+      foreach(path, invoker.import_dirs) {
+        include_dirs += [ "$root_gen_dir/" + rebase_path(path, "//") ]
       }
     }
   }
@@ -444,7 +387,7 @@
 
     if (generate_cc || generate_with_plugin) {
       # Not necessary if all protos are located in the same directory.
-      if (defined(config_name)) {
+      if (has_nested_dirs || defined(invoker.import_dirs)) {
         # It's not enough to set |include_dirs| for target since public imports
         # expose corresponding includes to header files as well.
         public_configs += [ ":$config_name" ]
diff --git a/third_party/unrar/README.chromium b/third_party/unrar/README.chromium
index 8ef4d71..7782eec 100644
--- a/third_party/unrar/README.chromium
+++ b/third_party/unrar/README.chromium
@@ -33,3 +33,6 @@
 - Put all symbols into a namespace third_party_unrar to avoid polluting global
   namespace (add_namespaces.patch)
 - Remove unnecessary semicolons from header files (semicolons.patch)
+- Fix a bug with NOVOLUME implementation (https://crbug.com/949787). This
+  should be temporary, until the fix can be pulled from upstream.
+  (fix_novolume.patch)
diff --git a/third_party/unrar/patches/fix_novolume.patch b/third_party/unrar/patches/fix_novolume.patch
new file mode 100644
index 0000000..0074cc5
--- /dev/null
+++ b/third_party/unrar/patches/fix_novolume.patch
@@ -0,0 +1,24 @@
+diff --git a/third_party/unrar/src/extract.cpp b/third_party/unrar/src/extract.cpp
+index d33306d82dc8..209047e130aa 100644
+--- a/third_party/unrar/src/extract.cpp
++++ b/third_party/unrar/src/extract.cpp
+@@ -270,15 +270,17 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat)
+     {
+       if (Arc.EndArcHead.NextVolume)
+       {
+-#ifndef NOVOLUME
++#ifdef NOOVLUME
++        return false;
++#else
+         if (!MergeArchive(Arc,&DataIO,false,Command))
+         {
+           ErrHandler.SetErrorCode(RARX_WARNING);
+           return false;
+         }
+-#endif
+         Arc.Seek(Arc.CurBlockPos,SEEK_SET);
+         return true;
++#endif
+       }
+       else
+         return false;
diff --git a/third_party/unrar/src/extract.cpp b/third_party/unrar/src/extract.cpp
index d33306d8..209047e13 100644
--- a/third_party/unrar/src/extract.cpp
+++ b/third_party/unrar/src/extract.cpp
@@ -270,15 +270,17 @@
     {
       if (Arc.EndArcHead.NextVolume)
       {
-#ifndef NOVOLUME
+#ifdef NOOVLUME
+        return false;
+#else
         if (!MergeArchive(Arc,&DataIO,false,Command))
         {
           ErrHandler.SetErrorCode(RARX_WARNING);
           return false;
         }
-#endif
         Arc.Seek(Arc.CurBlockPos,SEEK_SET);
         return true;
+#endif
       }
       else
         return false;
diff --git a/third_party/webxr_test_pages/webxr-samples/360-photos.html b/third_party/webxr_test_pages/webxr-samples/360-photos.html
index 9f8487f..2d933bf 100644
--- a/third_party/webxr_test_pages/webxr-samples/360-photos.html
+++ b/third_party/webxr_test_pages/webxr-samples/360-photos.html
@@ -145,6 +145,16 @@
         // solely on the device orientation. (As an added bonus this mode may
         // be more power efficient on some hardware!)
         session.requestReferenceSpace({ type: 'stationary', subtype: 'position-disabled' }).then((refSpace) => {
+          return refSpace;
+        }, (e) => {
+          if (!session.mode.startsWith('immersive')) {
+            // If we're in inline mode, our underlying platform may not support
+            // the stationary reference space, but an identity space is guaranteed.
+            return session.requestReferenceSpace({ type: 'identity' });
+          } else {
+            throw e;
+          }
+        }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
             xrImmersiveRefSpace = refSpace;
           } else {
diff --git a/third_party/webxr_test_pages/webxr-samples/fallback-rendering.html b/third_party/webxr_test_pages/webxr-samples/fallback-rendering.html
index 0f349ee44..8e396ae 100644
--- a/third_party/webxr_test_pages/webxr-samples/fallback-rendering.html
+++ b/third_party/webxr_test_pages/webxr-samples/fallback-rendering.html
@@ -163,6 +163,16 @@
         });
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'eye-level' }).then((refSpace) => {
+          return refSpace;
+        }, (e) => {
+          if (!session.mode.startsWith('immersive')) {
+            // If we're in inline mode, our underlying platform may not support
+            // the stationary reference space, but an identity space is guaranteed.
+            return session.requestReferenceSpace({ type: 'identity' });
+          } else {
+            throw e;
+          }
+        }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
             xrImmersiveRefSpace = refSpace;
           } else {
@@ -225,7 +235,7 @@
         // only when we resize for efficency.
         gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
 
-        // We're drawing with our own projection and view matrix now, and we 
+        // We're drawing with our own projection and view matrix now, and we
         // don't have a list of view to loop through, but otherwise all of the
         // WebGL drawing logic is exactly the same.
         scene.draw(projectionMatrix, viewMatrix);
diff --git a/third_party/webxr_test_pages/webxr-samples/input-selection.html b/third_party/webxr_test_pages/webxr-samples/input-selection.html
index 136b9dc9..0facbb28 100644
--- a/third_party/webxr_test_pages/webxr-samples/input-selection.html
+++ b/third_party/webxr_test_pages/webxr-samples/input-selection.html
@@ -201,6 +201,16 @@
         });
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'floor-level' }).then((refSpace) => {
+          return refSpace;
+        }, (e) => {
+          if (!session.mode.startsWith('immersive')) {
+            // If we're in inline mode, our underlying platform may not support
+            // the stationary reference space, but an identity space is guaranteed.
+            return session.requestReferenceSpace({ type: 'identity' });
+          } else {
+            throw e;
+          }
+        }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
             xrImmersiveRefSpace = refSpace;
           } else {
diff --git a/third_party/webxr_test_pages/webxr-samples/input-tracking.html b/third_party/webxr_test_pages/webxr-samples/input-tracking.html
index efedc1d4..f27ad2f 100644
--- a/third_party/webxr_test_pages/webxr-samples/input-tracking.html
+++ b/third_party/webxr_test_pages/webxr-samples/input-tracking.html
@@ -144,6 +144,16 @@
         });
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'floor-level' }).then((refSpace) => {
+          return refSpace;
+        }, (e) => {
+          if (!session.mode.startsWith('immersive')) {
+            // If we're in inline mode, our underlying platform may not support
+            // the stationary reference space, but an identity space is guaranteed.
+            return session.requestReferenceSpace({ type: 'identity' });
+          } else {
+            throw e;
+          }
+        }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
             xrImmersiveRefSpace = refSpace;
           } else {
diff --git a/third_party/webxr_test_pages/webxr-samples/magic-window.html b/third_party/webxr_test_pages/webxr-samples/magic-window.html
index 29a32fa..e743307 100644
--- a/third_party/webxr_test_pages/webxr-samples/magic-window.html
+++ b/third_party/webxr_test_pages/webxr-samples/magic-window.html
@@ -137,6 +137,16 @@
         });
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'eye-level' }).then((refSpace) => {
+          return refSpace;
+        }, (e) => {
+          if (!session.mode.startsWith('immersive')) {
+            // If we're in inline mode, our underlying platform may not support
+            // the stationary reference space, but an identity space is guaranteed.
+            return session.requestReferenceSpace({ type: 'identity' });
+          } else {
+            throw e;
+          }
+        }).then((refSpace) => {
           // Since we're dealing with multple sessions now we need to track
           // which XRReferenceSpace is associated with which XRSession.
           if (session.mode.startsWith('immersive')) {
diff --git a/third_party/webxr_test_pages/webxr-samples/positional-audio.html b/third_party/webxr_test_pages/webxr-samples/positional-audio.html
index b284250..2b94f87 100644
--- a/third_party/webxr_test_pages/webxr-samples/positional-audio.html
+++ b/third_party/webxr_test_pages/webxr-samples/positional-audio.html
@@ -158,7 +158,7 @@
 
       function createBufferSource(source, buffer, analyser) {
         // Create a buffer source. This will need to be recreated every time
-        // we wish to start the audio, see 
+        // we wish to start the audio, see
         // https://developer.mozilla.org/en-US/docs/Web/API/AudioBufferSourceNode
         let bufferSource = audioContext.createBufferSource();
         bufferSource.loop = true;
@@ -362,6 +362,16 @@
         });
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'floor-level' }).then((refSpace) => {
+          return refSpace;
+        }, (e) => {
+          if (!session.mode.startsWith('immersive')) {
+            // If we're in inline mode, our underlying platform may not support
+            // the stationary reference space, but an identity space is guaranteed.
+            return session.requestReferenceSpace({ type: 'identity' });
+          } else {
+            throw e;
+          }
+        }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
             xrImmersiveRefSpace = refSpace;
           } else {
diff --git a/third_party/webxr_test_pages/webxr-samples/reduced-bind-rendering.html b/third_party/webxr_test_pages/webxr-samples/reduced-bind-rendering.html
index b71847f..5510d66 100644
--- a/third_party/webxr_test_pages/webxr-samples/reduced-bind-rendering.html
+++ b/third_party/webxr_test_pages/webxr-samples/reduced-bind-rendering.html
@@ -140,6 +140,16 @@
         });
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'eye-level' }).then((refSpace) => {
+          return refSpace;
+        }, (e) => {
+          if (!session.mode.startsWith('immersive')) {
+            // If we're in inline mode, our underlying platform may not support
+            // the stationary reference space, but an identity space is guaranteed.
+            return session.requestReferenceSpace({ type: 'identity' });
+          } else {
+            throw e;
+          }
+        }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
             xrImmersiveRefSpace = refSpace;
           } else {
@@ -210,7 +220,7 @@
 
           // Draw(views):
           //   for each object in scene:
-          //     bindProgram();  
+          //     bindProgram();
           //     bindUniforms();
           //     bindBuffers();
           //     bindTextures();
diff --git a/third_party/webxr_test_pages/webxr-samples/room-scale.html b/third_party/webxr_test_pages/webxr-samples/room-scale.html
index 05bb828..70abfbd 100644
--- a/third_party/webxr_test_pages/webxr-samples/room-scale.html
+++ b/third_party/webxr_test_pages/webxr-samples/room-scale.html
@@ -143,6 +143,16 @@
         // emulated stage, where the view is translated up by a static height so
         // that the scene still renders in approximately the right place.
         session.requestReferenceSpace({ type: 'stationary', subtype: 'floor-level' }).then((refSpace) => {
+          return refSpace;
+        }, (e) => {
+          if (!session.mode.startsWith('immersive')) {
+            // If we're in inline mode, our underlying platform may not support
+            // the stationary reference space, but an identity space is guaranteed.
+            return session.requestReferenceSpace({ type: 'identity' });
+          } else {
+            throw e;
+          }
+        }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
             xrImmersiveRefSpace = refSpace;
             let boundsRenderer = new BoundsRenderer();
diff --git a/third_party/webxr_test_pages/webxr-samples/spectator-mode.html b/third_party/webxr_test_pages/webxr-samples/spectator-mode.html
index d72e646..26e31bad 100644
--- a/third_party/webxr_test_pages/webxr-samples/spectator-mode.html
+++ b/third_party/webxr_test_pages/webxr-samples/spectator-mode.html
@@ -172,6 +172,16 @@
         session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'floor-level' }).then((refSpace) => {
+          return refSpace;
+        }, (e) => {
+          if (!session.mode.startsWith('immersive')) {
+            // If we're in inline mode, our underlying platform may not support
+            // the stationary reference space, but an identity space is guaranteed.
+            return session.requestReferenceSpace({ type: 'identity' });
+          } else {
+            throw e;
+          }
+        }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
             xrImmersiveRefSpace = refSpace;
           } else {
diff --git a/third_party/webxr_test_pages/webxr-samples/stereo-video.html b/third_party/webxr_test_pages/webxr-samples/stereo-video.html
index 76843a3..041c7dc 100644
--- a/third_party/webxr_test_pages/webxr-samples/stereo-video.html
+++ b/third_party/webxr_test_pages/webxr-samples/stereo-video.html
@@ -86,7 +86,7 @@
         video: video,
         displayMode: 'stereoTopBottom'
       });
-      
+
       // When the video is clicked we'll pause it if it's playing.
       videoNode.onSelect(() => {
         if (!video.paused) {
@@ -198,6 +198,16 @@
         // to the center chair, as if they're sitting in it, rather than
         // somewhere in the room relative to the floor.
         session.requestReferenceSpace({ type: 'stationary', subtype: 'eye-level' }).then((refSpace) => {
+          return refSpace;
+        }, (e) => {
+          if (!session.mode.startsWith('immersive')) {
+            // If we're in inline mode, our underlying platform may not support
+            // the stationary reference space, but an identity space is guaranteed.
+            return session.requestReferenceSpace({ type: 'identity' });
+          } else {
+            throw e;
+          }
+        }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
             xrImmersiveRefSpace = refSpace;
           } else {
diff --git a/third_party/webxr_test_pages/webxr-samples/tests/cube-sea.html b/third_party/webxr_test_pages/webxr-samples/tests/cube-sea.html
index f4188e9a..7c4d264 100644
--- a/third_party/webxr_test_pages/webxr-samples/tests/cube-sea.html
+++ b/third_party/webxr_test_pages/webxr-samples/tests/cube-sea.html
@@ -324,7 +324,7 @@
           framebufferScaleFactor: appSettings.framebufferScale,
           antialias: appSettings.antialias});
         session.updateRenderState({ baseLayer: webglLayer });
-        
+
         if (session.mode.startsWith('immersive') && appSettings.mirrorCanvas) {
           let outputCanvas = document.createElement('canvas');
           document.body.appendChild(outputCanvas);
@@ -332,6 +332,16 @@
         }
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'eye-level' }).then((refSpace) => {
+          return refSpace;
+        }, (e) => {
+          if (!session.mode.startsWith('immersive')) {
+            // If we're in inline mode, our underlying platform may not support
+            // the stationary reference space, but an identity space is guaranteed.
+            return session.requestReferenceSpace({ type: 'identity' });
+          } else {
+            throw e;
+          }
+        }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
             xrImmersiveRefSpace = refSpace;
           } else {
diff --git a/third_party/webxr_test_pages/webxr-samples/tests/offscreen-canvas.html b/third_party/webxr_test_pages/webxr-samples/tests/offscreen-canvas.html
index 28a239e0..04c7db9 100644
--- a/third_party/webxr_test_pages/webxr-samples/tests/offscreen-canvas.html
+++ b/third_party/webxr_test_pages/webxr-samples/tests/offscreen-canvas.html
@@ -128,6 +128,16 @@
         });
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'eye-level' }).then((refSpace) => {
+          return refSpace;
+        }, (e) => {
+          if (!session.mode.startsWith('immersive')) {
+            // If we're in inline mode, our underlying platform may not support
+            // the stationary reference space, but an identity space is guaranteed.
+            return session.requestReferenceSpace({ type: 'identity' });
+          } else {
+            throw e;
+          }
+        }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
             xrImmersiveRefSpace = refSpace;
           } else {
diff --git a/third_party/webxr_test_pages/webxr-samples/tests/permission-request.html b/third_party/webxr_test_pages/webxr-samples/tests/permission-request.html
index 8dbd653a..9c6149a 100644
--- a/third_party/webxr_test_pages/webxr-samples/tests/permission-request.html
+++ b/third_party/webxr_test_pages/webxr-samples/tests/permission-request.html
@@ -224,6 +224,16 @@
         });
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'floor-level' }).then((refSpace) => {
+          return refSpace;
+        }, (e) => {
+          if (!session.mode.startsWith('immersive')) {
+            // If we're in inline mode, our underlying platform may not support
+            // the stationary reference space, but an identity space is guaranteed.
+            return session.requestReferenceSpace({ type: 'identity' });
+          } else {
+            throw e;
+          }
+        }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
             xrImmersiveRefSpace = refSpace;
           } else {
diff --git a/third_party/webxr_test_pages/webxr-samples/tests/pointer-painter.html b/third_party/webxr_test_pages/webxr-samples/tests/pointer-painter.html
index 15160b95..45b0891d 100644
--- a/third_party/webxr_test_pages/webxr-samples/tests/pointer-painter.html
+++ b/third_party/webxr_test_pages/webxr-samples/tests/pointer-painter.html
@@ -139,6 +139,16 @@
         });
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'floor-level' }).then((refSpace) => {
+          return refSpace;
+        }, (e) => {
+          if (!session.mode.startsWith('immersive')) {
+            // If we're in inline mode, our underlying platform may not support
+            // the stationary reference space, but an identity space is guaranteed.
+            return session.requestReferenceSpace({ type: 'identity' });
+          } else {
+            throw e;
+          }
+        }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
             xrImmersiveRefSpace = refSpace;
           } else {
diff --git a/third_party/webxr_test_pages/webxr-samples/tests/sponza.html b/third_party/webxr_test_pages/webxr-samples/tests/sponza.html
index d97e9b5..5dbee32 100644
--- a/third_party/webxr_test_pages/webxr-samples/tests/sponza.html
+++ b/third_party/webxr_test_pages/webxr-samples/tests/sponza.html
@@ -152,6 +152,16 @@
         });
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'floor-level' }).then((refSpace) => {
+          return refSpace;
+        }, (e) => {
+          if (!session.mode.startsWith('immersive')) {
+            // If we're in inline mode, our underlying platform may not support
+            // the stationary reference space, but an identity space is guaranteed.
+            return session.requestReferenceSpace({ type: 'identity' });
+          } else {
+            throw e;
+          }
+        }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
             xrImmersiveRefSpace = refSpace;
           } else {
@@ -163,7 +173,7 @@
       }
 
       function onSelect(ev) {
-        
+
       }
 
       function onEndSession(session) {
diff --git a/third_party/webxr_test_pages/webxr-samples/viewport-scaling.html b/third_party/webxr_test_pages/webxr-samples/viewport-scaling.html
index 47732d5..f34145b 100644
--- a/third_party/webxr_test_pages/webxr-samples/viewport-scaling.html
+++ b/third_party/webxr_test_pages/webxr-samples/viewport-scaling.html
@@ -164,6 +164,16 @@
         });
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'eye-level' }).then((refSpace) => {
+          return refSpace;
+        }, (e) => {
+          if (!session.mode.startsWith('immersive')) {
+            // If we're in inline mode, our underlying platform may not support
+            // the stationary reference space, but an identity space is guaranteed.
+            return session.requestReferenceSpace({ type: 'identity' });
+          } else {
+            throw e;
+          }
+        }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
             xrImmersiveRefSpace = refSpace;
           } else {
diff --git a/tools/android/roll/android_deps/build.gradle b/tools/android/roll/android_deps/build.gradle
index a3a4734..141aa0d 100644
--- a/tools/android/roll/android_deps/build.gradle
+++ b/tools/android/roll/android_deps/build.gradle
@@ -78,7 +78,7 @@
     compile "com.google.protobuf:protobuf-lite:3.0.1"
 
     // ARCore - needed for WebXR implementation on Android
-    compile "com.google.ar:core:1.6.0"
+    compile "com.google.ar:core:1.8.0"
 
     // Androidx test libraries
     testCompile "androidx.test:core:1.0.0"
diff --git a/tools/binary_size/diagnose_bloat.py b/tools/binary_size/diagnose_bloat.py
index 4e8af31..ae850c1 100755
--- a/tools/binary_size/diagnose_bloat.py
+++ b/tools/binary_size/diagnose_bloat.py
@@ -498,6 +498,34 @@
     logging.info('See detailed diff results here: %s',
                  os.path.relpath(diff_path))
 
+  def GenerateHtmlReport(self, before_id, after_id):
+    """Generate HTML report given two build archives."""
+    before = self.build_archives[before_id]
+    after = self.build_archives[after_id]
+    diff_path = self._DiffDir(before, after)
+    if not self._CanDiff(before, after):
+      logging.info(
+          'Skipping HTML report for %s due to missing build archives.',
+          diff_path)
+      return
+
+    supersize_path = os.path.join(_BINARY_SIZE_DIR, 'supersize')
+
+    report_path = os.path.join(diff_path, 'diff.ndjson')
+
+    supersize_cmd = [supersize_path, 'html_report', '--diff-with',
+      before.archived_size_path,
+      after.archived_size_path,
+      report_path]
+
+    logging.info('Creating HTML report')
+
+    _RunCmd(supersize_cmd)
+
+    logging.info('View using a local server via: %s start_server %s',
+      os.path.relpath(supersize_path),
+      os.path.relpath(report_path))
+
   def Summarize(self):
     path = os.path.join(self.archive_dir, 'last_diff_summary.txt')
     if self._summary_stats:
@@ -1000,6 +1028,7 @@
                                     subrepo, args.include_slow_options,
                                     args.unstripped)
     consecutive_failures = 0
+    i = 0
     for i, archive in enumerate(diff_mngr.build_archives):
       if archive.Exists():
         step = 'download' if build.IsCloud() else 'build'
@@ -1029,6 +1058,7 @@
       if i != 0:
         diff_mngr.MaybeDiff(i - 1, i)
 
+    diff_mngr.GenerateHtmlReport(0, i)
     diff_mngr.Summarize()
 
 
diff --git a/tools/chrome_proxy/webdriver/bypass.py b/tools/chrome_proxy/webdriver/bypass.py
index fd691f8..fea8ae1 100644
--- a/tools/chrome_proxy/webdriver/bypass.py
+++ b/tools/chrome_proxy/webdriver/bypass.py
@@ -21,7 +21,7 @@
       self.assertEqual(2, len(responses))
       for response in responses:
         if response.url == "http://check.googlezip.net/image.png":
-          self.assertHasChromeProxyViaHeader(response)
+          self.assertHasProxyHeaders(response)
         else:
           self.assertNotHasChromeProxyViaHeader(response)
 
@@ -49,7 +49,7 @@
       responses = t.GetHTTPResponses()
       self.assertEqual(2, len(responses))
       for response in responses:
-        self.assertHasChromeProxyViaHeader(response)
+        self.assertHasProxyHeaders(response)
 
       # Load HTTPS page and check that Data Saver is not used.
       t.LoadURL('https://check.googlezip.net/test.html')
@@ -75,7 +75,7 @@
       for response in test_driver.GetHTTPResponses():
         # The origin header implies that |response| is a CORS request.
         if ('origin' not in response.request_headers):
-          self.assertHasChromeProxyViaHeader(response)
+          self.assertHasProxyHeaders(response)
           same_origin_requests = same_origin_requests + 1
         else:
           self.assertNotHasChromeProxyViaHeader(response)
@@ -102,7 +102,7 @@
       responses = test_driver.GetHTTPResponses()
       self.assertNotEqual(0, len(responses))
       for response in responses:
-        self.assertHasChromeProxyViaHeader(response)
+        self.assertHasProxyHeaders(response)
 
   # Verify that Chrome does not bypass the proxy when a response gets a missing
   # via header.
@@ -199,7 +199,7 @@
       responses = test_driver.GetHTTPResponses()
       self.assertNotEqual(0, len(responses))
       for response in responses:
-        self.assertHasChromeProxyViaHeader(response)
+        self.assertHasProxyHeaders(response)
 
       # Verify that loading the exp directive test page with
       # "exp=client_test_bypass" triggers a bypass.
@@ -218,7 +218,7 @@
       responses = test_driver.GetHTTPResponses()
       self.assertNotEqual(0, len(responses))
       for response in responses:
-        self.assertHasChromeProxyViaHeader(response)
+        self.assertHasProxyHeaders(response)
 
   # Data Saver uses a HTTPS proxy by default, if that fails it will fall back to
   # a HTTP proxy.
@@ -245,7 +245,7 @@
       responses = test_driver.GetHTTPResponses()
       self.assertNotEqual(0, len(responses))
       for response in responses:
-        self.assertHasChromeProxyViaHeader(response)
+        self.assertHasProxyHeaders(response)
         if 'chrome-proxy' in response.request_headers:
             chrome_proxy_header = response.request_headers['chrome-proxy']
             chrome_proxy_directives = chrome_proxy_header.split(',')
diff --git a/tools/chrome_proxy/webdriver/client_config.py b/tools/chrome_proxy/webdriver/client_config.py
index f87ed26..048c67e3 100644
--- a/tools/chrome_proxy/webdriver/client_config.py
+++ b/tools/chrome_proxy/webdriver/client_config.py
@@ -23,7 +23,7 @@
       self.assertEqual(2, len(responses))
       for response in responses:
         # Verify that the proxy server honored the session ID.
-        self.assertHasChromeProxyViaHeader(response)
+        self.assertHasProxyHeaders(response)
         self.assertEqual(200, response.status)
 
   # Ensure Chrome uses a direct connection when no valid client config is given.
diff --git a/tools/chrome_proxy/webdriver/common.py b/tools/chrome_proxy/webdriver/common.py
index 738e2c5..7531935 100644
--- a/tools/chrome_proxy/webdriver/common.py
+++ b/tools/chrome_proxy/webdriver/common.py
@@ -76,6 +76,10 @@
   parser.add_argument('--via_header_value',
     default='1.1 Chrome-Compression-Proxy', help='What the via should match to '
     'be considered valid')
+  parser.add_argument('--aceh_header_value',
+    default='Chrome-Proxy,Chrome-Proxy-Content-Transform,Via', help='Comma '
+    'separated list of values that should be in the '
+    'Access-Control-Expose-Headers header.')
   parser.add_argument('--android', help='If given, attempts to run the test on '
     'Android via adb. Ignores usage of --chrome_exec', action='store_true')
   parser.add_argument('--android_package',
@@ -770,20 +774,41 @@
   unittest.TestCase class.
   """
 
-  def assertHasChromeProxyViaHeader(self, http_response):
-    """Asserts that the Via header in the given HTTPResponse matches the
-    expected value as given on the command line.
+  def assertHasProxyHeaders(self, http_response):
+    """Asserts that Proxy headers are present contain the expected values
+    as provided on the command line.
+
+    The Proxy adds two headers: Via, and Access-Control-Expose-Headers.
+    This checks that both headers contain the values enumerated in both
+    --via_header_value and --aceh_header_value. Both headers are additive,
+    so there may be more values in the headers than expected.
 
     Args:
       http_response: The HTTPResponse object to check.
     """
+    # Via header check
     self.assertIn('via', http_response.response_headers)
-    expected_via_header = ParseFlags().via_header_value
-    actual_via_headers = http_response.response_headers['via'].split(',')
+    expected_via_header = ParseFlags().via_header_value.lower()
+    actual_via_headers = set(
+      [h.lower() for h in http_response.response_headers['via'].split(',')])
     self.assertIn(expected_via_header, actual_via_headers, "Via header not in "
       "response headers! Expected: %s, Actual: %s" %
       (expected_via_header, actual_via_headers))
 
+    # Access-Control-Expose-Headers header check.
+    if ParseFlags().aceh_header_value:
+      aceh = 'access-control-expose-headers'
+      self.assertIn(aceh,  http_response.response_headers)
+      expected_aceh_header = set(
+          [h.lower() for h in ParseFlags().aceh_header_value.split(',')])
+      actual_aceh_header = set(
+          [h.lower() for h in http_response.response_headers[aceh].split(',')])
+      diff = expected_aceh_header - actual_aceh_header
+      self.assertTrue(len(diff) == 0, "Access-Control-Expose-Headers missing "
+        "values (%s)! Expected: [%s], Actual: [%s]" %
+        (",".join(diff), ",".join(expected_aceh_header),
+         ",".join(actual_aceh_header)))
+
   def assertNotHasChromeProxyViaHeader(self, http_response):
     """Asserts that the Via header in the given HTTPResponse does not match the
     expected value as given on the command line.
@@ -857,7 +882,7 @@
     """
 
     if (expected_lo_fi) :
-      self.assertHasChromeProxyViaHeader(http_response)
+      self.assertHasProxyHeaders(http_response)
       content_length = http_response.response_headers['content-length']
       cpat_request = http_response.request_headers[
                        'chrome-proxy-accept-transform']
@@ -891,7 +916,7 @@
       Whether the response was a Lite Page.
     """
 
-    self.assertHasChromeProxyViaHeader(http_response)
+    self.assertHasProxyHeaders(http_response)
     if ('chrome-proxy-content-transform' not in http_response.response_headers):
       return False;
     cpct_response = http_response.response_headers[
diff --git a/tools/chrome_proxy/webdriver/compression_regression.py b/tools/chrome_proxy/webdriver/compression_regression.py
index e397ec2..6b7b99e0 100644
--- a/tools/chrome_proxy/webdriver/compression_regression.py
+++ b/tools/chrome_proxy/webdriver/compression_regression.py
@@ -148,7 +148,7 @@
       compression = {}
       for response in t.GetHTTPResponses():
         # Check that the response was proxied.
-        self.assertHasChromeProxyViaHeader(response)
+        self.assertHasProxyHeaders(response)
         # Compute compression metrics.
         cl = response.response_headers['content-length']
         ofcl = getChromeProxyOFCL(response)
diff --git a/tools/chrome_proxy/webdriver/data_use.py b/tools/chrome_proxy/webdriver/data_use.py
index 7426c2b..0a004d53 100644
--- a/tools/chrome_proxy/webdriver/data_use.py
+++ b/tools/chrome_proxy/webdriver/data_use.py
@@ -26,7 +26,7 @@
       t.LoadURL('http://check.googlezip.net/test.html')
       responses = t.GetHTTPResponses()
       for response in responses:
-        self.assertHasChromeProxyViaHeader(response)
+        self.assertHasProxyHeaders(response)
 
       # Load the extension page and verify the host appears in data use list.
       t.LoadURL('chrome-extension://pfmgfdlgomnbgkofeojodiodmgpgmkac/'
diff --git a/tools/chrome_proxy/webdriver/fallback.py b/tools/chrome_proxy/webdriver/fallback.py
index a28c325..240ff5b 100644
--- a/tools/chrome_proxy/webdriver/fallback.py
+++ b/tools/chrome_proxy/webdriver/fallback.py
@@ -37,7 +37,7 @@
       responses = test_driver.GetHTTPResponses()
       self.assertNotEqual(0, len(responses))
       for response in responses:
-          self.assertHasChromeProxyViaHeader(response)
+          self.assertHasProxyHeaders(response)
           self.assertEqual(u'http/2+quic/43', response.protocol)
 
   # Verify that when Chrome receives a non-4xx response through a Data Reduction
@@ -61,7 +61,7 @@
       responses = test_driver.GetHTTPResponses()
       self.assertNotEqual(0, len(responses))
       for response in responses:
-        self.assertHasChromeProxyViaHeader(response)
+        self.assertHasProxyHeaders(response)
         self.assertEqual(u'http/1.1', response.protocol)
 
       # Check that the BypassTypePrimary histogram has a single entry in the
diff --git a/tools/chrome_proxy/webdriver/html5.py b/tools/chrome_proxy/webdriver/html5.py
index 036b019..b08edc5 100644
--- a/tools/chrome_proxy/webdriver/html5.py
+++ b/tools/chrome_proxy/webdriver/html5.py
@@ -22,7 +22,7 @@
         # Site has a lot on it, just check the main page.
         if (response.url == 'http://html5test.com/'
             or response.url == 'http://html5test.com/index.html'):
-          self.assertHasChromeProxyViaHeader(response)
+          self.assertHasProxyHeaders(response)
           checked_main_page = True
       if not checked_main_page:
         self.fail("Did not check any page!")
diff --git a/tools/chrome_proxy/webdriver/lite_page.py b/tools/chrome_proxy/webdriver/lite_page.py
index 3c84a4bb..c9bc029 100644
--- a/tools/chrome_proxy/webdriver/lite_page.py
+++ b/tools/chrome_proxy/webdriver/lite_page.py
@@ -194,7 +194,7 @@
             in response.response_headers['content-type']):
           continue
         # Make sure non-video requests are proxied.
-        self.assertHasChromeProxyViaHeader(response)
+        self.assertHasProxyHeaders(response)
         # Make sure there are no 4XX or 5xx status codes.
         self.assertLess(response.status, 400)
 
diff --git a/tools/chrome_proxy/webdriver/lofi.py b/tools/chrome_proxy/webdriver/lofi.py
index 03d825d..2f12c5a 100644
--- a/tools/chrome_proxy/webdriver/lofi.py
+++ b/tools/chrome_proxy/webdriver/lofi.py
@@ -300,7 +300,7 @@
         if not response.request_headers:
           continue
         responses = responses + 1
-        self.assertHasChromeProxyViaHeader(response)
+        self.assertHasProxyHeaders(response)
         self.checkLoFiResponse(response, False)
 
       # Verify that responses were seen.
@@ -388,7 +388,7 @@
         if not response.request_headers:
           continue
         responses = responses + 1
-        self.assertHasChromeProxyViaHeader(response)
+        self.assertHasProxyHeaders(response)
         self.checkLoFiResponse(response, False)
 
       # Verify that responses were seen.
@@ -493,7 +493,7 @@
       image_response_count = 0
       for response in test_driver.GetHTTPResponses():
         if response.url.endswith('.png'):
-          self.assertHasChromeProxyViaHeader(response)
+          self.assertHasProxyHeaders(response)
           self.assertIn('range', response.request_headers)
           self.assertIn('content-range', response.response_headers)
           self.assertTrue(response.response_headers['content-range'].startswith(
diff --git a/tools/chrome_proxy/webdriver/quic.py b/tools/chrome_proxy/webdriver/quic.py
index bd57e11..53d4528e 100644
--- a/tools/chrome_proxy/webdriver/quic.py
+++ b/tools/chrome_proxy/webdriver/quic.py
@@ -31,7 +31,7 @@
       responses = t.GetHTTPResponses()
       self.assertEqual(2, len(responses))
       for response in responses:
-        self.assertHasChromeProxyViaHeader(response)
+        self.assertHasProxyHeaders(response)
 
   # Ensure Chrome uses QUIC DataSaver proxy when QUIC is enabled. This test
   # may fail if QUIC is disabled on the server side.
@@ -52,7 +52,7 @@
       responses = t.GetHTTPResponses()
       self.assertEqual(2, len(responses))
       for response in responses:
-        self.assertHasChromeProxyViaHeader(response)
+        self.assertHasProxyHeaders(response)
 
       # Verify that histogram DataReductionProxy.Quic.ProxyStatus has at least 1
       # sample. This sample must be in bucket 0 (QUIC_PROXY_STATUS_AVAILABLE).
@@ -60,14 +60,5 @@
       self.assertLessEqual(1, proxy_status['count'])
       self.assertEqual(0, proxy_status['sum'])
 
-      # Navigate to one more page to ensure that established QUIC connection
-      # is used for the next request. Give 3 seconds extra headroom for the QUIC
-      # connection to be established.
-      time.sleep(3)
-      t.LoadURL('http://check.googlezip.net/test.html')
-      proxy_usage = t.GetHistogram('Net.QuicAlternativeProxy.Usage')
-      # Bucket ALTERNATIVE_PROXY_USAGE_NO_RACE should have at least onesample.
-      self.assertLessEqual(1, proxy_usage['buckets'][0]['count'])
-
 if __name__ == '__main__':
   IntegrationTest.RunAllTests()
diff --git a/tools/chrome_proxy/webdriver/reenable_after_bypass.py b/tools/chrome_proxy/webdriver/reenable_after_bypass.py
index 4fcea97a..3d0ade8 100644
--- a/tools/chrome_proxy/webdriver/reenable_after_bypass.py
+++ b/tools/chrome_proxy/webdriver/reenable_after_bypass.py
@@ -46,7 +46,7 @@
       responses = test_driver.GetHTTPResponses()
       self.assertNotEqual(0, len(responses))
       for response in responses:
-        self.assertHasChromeProxyViaHeader(response)
+        self.assertHasProxyHeaders(response)
 
   # Verify that when the Data Reduction Proxy responds with the "block=0"
   # directive, Chrome bypasses all proxies for the next 1-5 minutes.
@@ -78,7 +78,7 @@
       responses = test_driver.GetHTTPResponses()
       self.assertNotEqual(0, len(responses))
       for response in responses:
-        self.assertHasChromeProxyViaHeader(response)
+        self.assertHasProxyHeaders(response)
 
 
 if __name__ == '__main__':
diff --git a/tools/chrome_proxy/webdriver/safebrowsing.py b/tools/chrome_proxy/webdriver/safebrowsing.py
index 6e7e371..b0f3f5e 100644
--- a/tools/chrome_proxy/webdriver/safebrowsing.py
+++ b/tools/chrome_proxy/webdriver/safebrowsing.py
@@ -54,7 +54,7 @@
       responses = t.GetHTTPResponses()
       self.assertEqual(1, len(responses))
       for response in responses:
-        self.assertHasChromeProxyViaHeader(response)
+        self.assertHasProxyHeaders(response)
 
 if __name__ == '__main__':
   IntegrationTest.RunAllTests()
diff --git a/tools/chrome_proxy/webdriver/smoke.py b/tools/chrome_proxy/webdriver/smoke.py
index 54a38eb..2f27d17b 100644
--- a/tools/chrome_proxy/webdriver/smoke.py
+++ b/tools/chrome_proxy/webdriver/smoke.py
@@ -93,7 +93,7 @@
       self.assertNotEqual(0, len(responses))
       num_chrome_proxy_request_headers = 0
       for response in responses:
-        self.assertHasChromeProxyViaHeader(response)
+        self.assertHasProxyHeaders(response)
         if ('chrome-proxy' in response.request_headers):
           num_chrome_proxy_request_headers += 1
       t.SleepUntilHistogramHasEntry('PageLoad.Clients.DataReductionProxy.'
@@ -161,7 +161,7 @@
         for response in responses:
           if not response.request_headers:
             continue
-          self.assertHasChromeProxyViaHeader(response)
+          self.assertHasProxyHeaders(response)
           self.assertEqual(200, response.status)
           chrome_proxy_header = response.request_headers['chrome-proxy']
           chrome_proxy_directives = chrome_proxy_header.split(',')
@@ -194,7 +194,7 @@
       responses = t.GetHTTPResponses()
       self.assertNotEqual(0, len(responses))
       for response in responses:
-        self.assertHasChromeProxyViaHeader(response)
+        self.assertHasProxyHeaders(response)
 
 if __name__ == '__main__':
   IntegrationTest.RunAllTests()
diff --git a/tools/chrome_proxy/webdriver/video.py b/tools/chrome_proxy/webdriver/video.py
index cdab171..58237ea10 100644
--- a/tools/chrome_proxy/webdriver/video.py
+++ b/tools/chrome_proxy/webdriver/video.py
@@ -31,7 +31,7 @@
       responses = t.GetHTTPResponses()
       self.assertEquals(2, len(responses))
       for response in responses:
-        self.assertHasChromeProxyViaHeader(response)
+        self.assertHasProxyHeaders(response)
 
   # Videos fetched via an XHR request should not be proxied.
   def testNoCompressionOnXHR(self):
@@ -52,7 +52,7 @@
           self.assertNotHasChromeProxyViaHeader(response)
           saw_video_response = True
         else:
-          self.assertHasChromeProxyViaHeader(response)
+          self.assertHasProxyHeaders(response)
       self.assertTrue(saw_video_response, 'No video request seen in test!')
 
   @ChromeVersionEqualOrAfterM(64)
@@ -70,7 +70,7 @@
       )
       saw_range_response = False
       for response in t.GetHTTPResponses():
-        self.assertHasChromeProxyViaHeader(response)
+        self.assertHasProxyHeaders(response)
         if response.response_headers['status']=='206':
           saw_range_response = True
           content_range = response.response_headers['content-range']
@@ -105,7 +105,7 @@
       responses = t.GetHTTPResponses()
       saw_range_response = False
       for response in responses:
-        self.assertHasChromeProxyViaHeader(response)
+        self.assertHasProxyHeaders(response)
         if response.response_headers['status']=='206':
           saw_range_response = True
           content_range = response.response_headers['content-range']
@@ -131,7 +131,7 @@
           'http://check.googlezip.net/cacheable/video/buck_bunny_tiny.html')
       # Check request was proxied and we got a compressed video back.
       for response in t.GetHTTPResponses():
-        self.assertHasChromeProxyViaHeader(response)
+        self.assertHasProxyHeaders(response)
         if ('content-type' in response.response_headers
             and 'video' in response.response_headers['content-type']):
           self.assertEqual('video/webm',
@@ -193,7 +193,7 @@
       video_etag = None
       num_partial_requests = 0
       for response in t.GetHTTPResponses():
-        self.assertHasChromeProxyViaHeader(response)
+        self.assertHasProxyHeaders(response)
         rh = response.response_headers
         if ('content-type' in rh and 'video' in rh['content-type']):
           self.assertTrue('etag' in rh),
@@ -254,7 +254,7 @@
           if ('content-type' in resp.response_headers
               and resp.response_headers['content-type'] == 'video/webm'):
             loaded_compressed_video = True
-            self.assertHasChromeProxyViaHeader(resp)
+            self.assertHasProxyHeaders(resp)
           else:
             # Take a breath before requesting again.
             time.sleep(1)
@@ -292,7 +292,7 @@
         'window.playerState == YT.PlayerState.PLAYING', 30)
       for response in t.GetHTTPResponses():
         if not response.url.startswith('https'):
-          self.assertHasChromeProxyViaHeader(response)
+          self.assertHasProxyHeaders(response)
 
 if __name__ == '__main__':
   IntegrationTest.RunAllTests()
diff --git a/tools/gritsettings/resource_ids b/tools/gritsettings/resource_ids
index dc660db..47b4836 100644
--- a/tools/gritsettings/resource_ids
+++ b/tools/gritsettings/resource_ids
@@ -83,8 +83,10 @@
     "includes": [11000],
     "structures": [12000],
   },
+  "chrome/browser/resources/chromeos/cellular_setup/cellular_setup_resources.grd": {
+    "structures": [12040],
+  },
   "chrome/browser/resources/chromeos/multidevice_setup/multidevice_setup_resources.grd": {
-    "includes": [12040],
     "structures": [12045],
   },
   "chrome/browser/resources/component_extension_resources.grd": {
diff --git a/tools/json_schema_compiler/idl_schema.py b/tools/json_schema_compiler/idl_schema.py
index f9f73442..cfa9e81 100755
--- a/tools/json_schema_compiler/idl_schema.py
+++ b/tools/json_schema_compiler/idl_schema.py
@@ -362,6 +362,8 @@
     for node in self.node.GetChildren():
       if node.cls == 'EnumItem':
         enum_value = {'name': node.GetName()}
+        if node.GetProperty('nodoc'):
+          enum_value['nodoc'] = True
         for child in node.GetChildren():
           if child.cls == 'Comment':
             enum_value['description'] = ProcessComment(child.GetName())[0]
diff --git a/tools/json_schema_compiler/idl_schema_test.py b/tools/json_schema_compiler/idl_schema_test.py
index 8d4e8b9..b9d1435a 100755
--- a/tools/json_schema_compiler/idl_schema_test.py
+++ b/tools/json_schema_compiler/idl_schema_test.py
@@ -150,6 +150,25 @@
     self.assertTrue(enum_with_nodoc is not None)
     self.assertTrue(enum_with_nodoc['nodoc'])
 
+  def testNoDocOnEnumValue(self):
+    schema = self.idl_basics
+    expected = {
+        'enum': [{
+            'name': 'name1'
+        }, {
+            'name': 'name2',
+            'nodoc': True,
+            'description': 'comment2'
+        }, {
+            'name': 'name3',
+            'description': 'comment3'
+        }],
+        'type': 'string',
+        'id': 'EnumTypeWithNoDocValue',
+        'description': ''
+    }
+    self.assertEquals(expected, getType(schema, expected['id']))
+
   def testInternalNamespace(self):
     idl_basics  = self.idl_basics
     self.assertEquals('idl_basics', idl_basics['namespace'])
diff --git a/tools/json_schema_compiler/test/idl_basics.idl b/tools/json_schema_compiler/test/idl_basics.idl
index c4fb5aa..b0ca3fa 100644
--- a/tools/json_schema_compiler/test/idl_basics.idl
+++ b/tools/json_schema_compiler/test/idl_basics.idl
@@ -17,6 +17,14 @@
     name2
   };
 
+  enum EnumTypeWithNoDocValue {
+    name1,
+    // comment2
+    [nodoc] name2,
+    // comment3
+    name3
+  };
+
   dictionary MyType1 {
     // This comment tests "double-quotes".
     [legalValues=(1,2)] long x;
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 496c60d..67880387 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -669,12 +669,10 @@
       'android_angle_deqp_rel_ng': 'deqp_android_release_trybot_arm64',
       'android_angle_vk32_deqp_rel_ng': 'deqp_android_vulkan_release_trybot',
       'android_angle_vk64_deqp_rel_ng': 'deqp_android_vulkan_release_trybot_arm64',
-      'linux-angle-rel': 'gpu_fyi_tests_release_trybot',
       'linux_angle_ozone_rel_ng': 'gpu_fyi_tests_ozone_linux_system_gbm_libdrm_release_trybot',
       'linux_angle_dbg_ng': 'gpu_fyi_tests_debug_trybot',
       'linux_angle_deqp_rel_ng': 'deqp_release_trybot',
       'linux_angle_rel_ng': 'gpu_fyi_tests_release_trybot',
-      'mac-angle-rel': 'gpu_fyi_tests_release_trybot',
       'mac_angle_dbg_ng': 'gpu_fyi_tests_debug_trybot',
       'mac_angle_rel_ng': 'gpu_fyi_tests_release_trybot',
       'win-angle-rel': 'gpu_fyi_tests_release_trybot_x86',
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 8796f44..f94590d 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -11870,6 +11870,9 @@
 </action>
 
 <action name="MobileNTPSwitchToBookmarks">
+  <obsolete>
+    Removed with deprecation of Bookmark suggestions category.
+  </obsolete>
   <owner>ianwen@chromium.org</owner>
   <description>
     Action indicating the user has clicked the boomkark button on NTP to swith
@@ -11878,6 +11881,9 @@
 </action>
 
 <action name="MobileNTPSwitchToDownloadManager">
+  <obsolete>
+    Removed with deprecation of Download suggestions category.
+  </obsolete>
   <owner>bauerb@chromium.org</owner>
   <description>
     Android: User clicked on the &quot;More&quot; card at the end of the section
@@ -19980,6 +19986,9 @@
 </action>
 
 <action name="Suggestions.Category.ViewAll">
+  <obsolete>
+    Removed with deprecation of Download suggestions category.
+  </obsolete>
   <owner>finkm@chromium.org</owner>
   <owner>dgn@chromium.org</owner>
   <description>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 83e353f..2a6fc27 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -8902,7 +8902,7 @@
 <enum name="ContentSuggestionsCategory">
   <int value="0" label="Experimental"/>
   <int value="1" label="Recent Tabs (obsolete)"/>
-  <int value="2" label="Downloads"/>
+  <int value="2" label="Downloads (obsolete)"/>
   <int value="3" label="Bookmarks (obsolete)"/>
   <int value="4" label="Physical Web Pages (obsolete)"/>
   <int value="5" label="Foreign Tabs (obsolete)"/>
@@ -33849,6 +33849,7 @@
   <int value="782167080" label="enable-new-qp-input-view"/>
   <int value="783270752" label="AndroidHistoryManager:enabled"/>
   <int value="787385958" label="RegionalLocalesAsDisplayUI:enabled"/>
+  <int value="799680074" label="ContextualSearchTranslationModel:enabled"/>
   <int value="803282885" label="PreferHtmlOverPlugins:disabled"/>
   <int value="806334184" label="AndroidSpellChecker:enabled"/>
   <int value="807447752" label="ChromeMemex:disabled"/>
@@ -33935,6 +33936,7 @@
   <int value="955340765" label="ChromeHomeOptOutSnackbar:enabled"/>
   <int value="963457392" label="ChromeHomeModernLayout:disabled"/>
   <int value="963671232" label="DrawOcclusion:disabled"/>
+  <int value="964613807" label="ContextualSearchTranslationModel:disabled"/>
   <int value="966415988" label="SyncPseudoUSSPasswords:enabled"/>
   <int value="969340095" label="EnableDspHotword:disabled"/>
   <int value="972228058" label="SyncUSSSessions:disabled"/>
@@ -36632,6 +36634,17 @@
   <int value="1" label="Remote"/>
 </enum>
 
+<enum name="MediaSessionAction">
+  <int value="0" label="Play"/>
+  <int value="1" label="Pause"/>
+  <int value="2" label="Previous Track"/>
+  <int value="3" label="Next Track"/>
+  <int value="4" label="Seek Backward"/>
+  <int value="5" label="Seek Forward"/>
+  <int value="6" label="Skip Ad"/>
+  <int value="7" label="Stop"/>
+</enum>
+
 <enum name="MediaSessionActionSource">
   <int value="0" label="Media Notification"/>
   <int value="1" label="MediaSession (Android)"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 55bea1c..6a23f5ba 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -51969,6 +51969,15 @@
   </summary>
 </histogram>
 
+<histogram name="Media.Notification.UserAction" enum="MediaSessionAction"
+    expires_after="2019-12-31">
+  <owner>beccahughes@chromium.org</owner>
+  <owner>media-dev@chromium.org</owner>
+  <summary>
+    The action (e.g. pause) that a user clicked on a media notification.
+  </summary>
+</histogram>
+
 <histogram name="Media.OutputStreamDuration" units="ms">
   <owner>maxmorin@chromium.org</owner>
   <summary>
@@ -106901,7 +106910,10 @@
 </histogram>
 
 <histogram name="Search.ContextualSearchShouldTranslate"
-    enum="ContextualSearchShouldTranslate">
+    enum="ContextualSearchShouldTranslate" expires_after="2019-04-05">
+  <obsolete>
+    Removed in M75 because the data is no longer needed.
+  </obsolete>
   <owner>donnd@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <summary>
@@ -107087,7 +107099,6 @@
 <histogram name="Search.ContextualSearchTranslateCondition" enum="Boolean"
     expires_after="M77">
   <owner>donnd@chromium.org</owner>
-  <owner>mahmoudi@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <summary>
     For each contextual search that is triggered by tap, records whether the
diff --git a/tools/perf/core/upload_results_to_perf_dashboard.py b/tools/perf/core/upload_results_to_perf_dashboard.py
index 818505e..884dfab 100755
--- a/tools/perf/core/upload_results_to_perf_dashboard.py
+++ b/tools/perf/core/upload_results_to_perf_dashboard.py
@@ -77,10 +77,7 @@
   is_reference_build = 'reference' in options.name
   stripped_test_name = options.name.replace('.reference', '')
 
-  # 30 MB max_bytes was chosen to be large enough that we rarely have to break
-  # up perf data into chunks since it is slow (crbug.com/947035) but small
-  # enough that we don't cause crbug.com/909961.
-  max_bytes = 30 << 20
+  max_bytes = 1 << 20
   output_dir = tempfile.mkdtemp()
 
   try:
diff --git a/tools/traffic_annotation/auditor/BUILD.gn b/tools/traffic_annotation/auditor/BUILD.gn
index 7931a890..5f01c32 100644
--- a/tools/traffic_annotation/auditor/BUILD.gn
+++ b/tools/traffic_annotation/auditor/BUILD.gn
@@ -9,6 +9,8 @@
 assert(is_win || is_linux)
 
 proto_library("chrome_settings_full_runtime") {
+  proto_out_dir = "/tools/traffic_annotation"
+
   cc_include = "components/policy/proto/policy_proto_export.h"
 
   sources = [
diff --git a/ui/accessibility/BUILD.gn b/ui/accessibility/BUILD.gn
index 8ce6a2e..a8b692a 100644
--- a/ui/accessibility/BUILD.gn
+++ b/ui/accessibility/BUILD.gn
@@ -132,12 +132,15 @@
 
   defines = [ "ACCESSIBILITY_IMPLEMENTATION" ]
 
+  deps = [
+    "//third_party/cld_3/src/src:cld_3",
+  ]
+
   public_deps = [
     ":ax_constants_mojo",
     ":ax_enums_mojo",
     "//base",
     "//base:i18n",
-    "//third_party/cld_3/src/src:cld_3",
     "//ui/base",
     "//ui/display",
     "//ui/gfx",
diff --git a/ui/accessibility/accessibility_switches.cc b/ui/accessibility/accessibility_switches.cc
index 07371fb..e96dd18 100644
--- a/ui/accessibility/accessibility_switches.cc
+++ b/ui/accessibility/accessibility_switches.cc
@@ -18,6 +18,11 @@
 const char kEnableExperimentalAccessibilityAutoclick[] =
     "enable-experimental-accessibility-autoclick";
 
+// Enables support for visually debugging the accessibility labels
+// feature, which provides images descriptions for screen reader users.
+const char kEnableExperimentalAccessibilityLabelsDebugging[] =
+    "enable-experimental-accessibility-labels-debugging";
+
 // Enables language detection on in-page text content which is then exposed to
 // accessibility technology such as screen readers.
 const char kEnableExperimentalAccessibilityLanguageDetection[] =
diff --git a/ui/accessibility/accessibility_switches.h b/ui/accessibility/accessibility_switches.h
index 8579f94..159a0ad 100644
--- a/ui/accessibility/accessibility_switches.h
+++ b/ui/accessibility/accessibility_switches.h
@@ -13,6 +13,7 @@
 
 AX_EXPORT extern const char kEnableExperimentalAccessibilityFeatures[];
 AX_EXPORT extern const char kEnableExperimentalAccessibilityAutoclick[];
+AX_EXPORT extern const char kEnableExperimentalAccessibilityLabelsDebugging[];
 AX_EXPORT extern const char kEnableExperimentalAccessibilityLanguageDetection[];
 AX_EXPORT extern const char kEnableExperimentalAccessibilitySwitchAccess[];
 AX_EXPORT extern const char
diff --git a/ui/accessibility/ax_node_position_unittest.cc b/ui/accessibility/ax_node_position_unittest.cc
index 4861c5a..31cd999 100644
--- a/ui/accessibility/ax_node_position_unittest.cc
+++ b/ui/accessibility/ax_node_position_unittest.cc
@@ -1440,38 +1440,42 @@
       ax::mojom::TextAffinity::kDownstream);
   ASSERT_NE(nullptr, text_position);
   ASSERT_TRUE(text_position->IsTextPosition());
+
   test_position = text_position->CreateNextCharacterPosition(
       AXBoundaryBehavior::StopAtAnchorBoundary);
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
-  EXPECT_EQ(5, test_position->text_offset());
-  test_position = text_position->CreateNextCharacterPosition(
-      AXBoundaryBehavior::CrossBoundary);
-  EXPECT_NE(nullptr, test_position);
-  EXPECT_TRUE(test_position->IsTextPosition());
-  EXPECT_EQ(line_break_.id, test_position->anchor_id());
-  EXPECT_EQ(0, test_position->text_offset());
-
+  EXPECT_EQ(6, test_position->text_offset());
   test_position = test_position->CreateNextCharacterPosition(
       AXBoundaryBehavior::StopAtAnchorBoundary);
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
-  EXPECT_EQ(line_break_.id, test_position->anchor_id());
-  EXPECT_EQ(0, test_position->text_offset());
-  test_position = test_position->CreateNextCharacterPosition(
+  EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
+  EXPECT_EQ(6, test_position->text_offset());
+
+  test_position = text_position->CreateNextCharacterPosition(
       AXBoundaryBehavior::CrossBoundary);
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
-  EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
-  EXPECT_EQ(0, test_position->text_offset());
+  EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
+  EXPECT_EQ(6, test_position->text_offset());
 
   test_position = test_position->CreateNextCharacterPosition(
       AXBoundaryBehavior::CrossBoundary);
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
+  EXPECT_EQ(line_break_.id, test_position->anchor_id());
+  EXPECT_EQ(0, test_position->text_offset());
+
+  // The line break doesn't reach its max length (1) to distinguish between the
+  // "before" and "after" positions
+  test_position = test_position->CreateNextCharacterPosition(
+      AXBoundaryBehavior::CrossBoundary);
+  EXPECT_NE(nullptr, test_position);
+  EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
-  EXPECT_EQ(1, test_position->text_offset());
+  EXPECT_EQ(0, test_position->text_offset());
 
   text_position = AXNodePosition::CreateTextPosition(
       tree_.data().tree_id, check_box_.id, 0 /* text_offset */,
@@ -1504,6 +1508,20 @@
   EXPECT_EQ(1, test_position->text_offset());
   // Affinity should have been reset to downstream.
   EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
+
+  text_position = AXNodePosition::CreateTextPosition(
+      tree_.data().tree_id, text_field_.id, 12 /* text_offset */,
+      ax::mojom::TextAffinity::kUpstream);
+  ASSERT_NE(nullptr, text_position);
+  ASSERT_TRUE(text_position->IsTextPosition());
+  test_position = text_position->CreateNextCharacterPosition(
+      AXBoundaryBehavior::CrossBoundary);
+  EXPECT_NE(nullptr, test_position);
+  EXPECT_TRUE(test_position->IsTextPosition());
+  EXPECT_EQ(text_field_.id, test_position->anchor_id());
+  EXPECT_EQ(13, test_position->text_offset());
+  // Affinity should have been reset to downstream.
+  EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
 }
 
 TEST_F(AXPositionTest, CreatePreviousCharacterPosition) {
diff --git a/ui/accessibility/ax_position.h b/ui/accessibility/ax_position.h
index 5df467d..1a207a14 100644
--- a/ui/accessibility/ax_position.h
+++ b/ui/accessibility/ax_position.h
@@ -634,15 +634,18 @@
     if (text_position->IsNullPosition())
       return text_position;
 
+    const int max_position = text_position->MaxTextOffset();
+
     // Note that |BoundaryBehavior::StopIfAlreadyAtBoundary| doesn't make
     // sense for character boundaries.
     DCHECK_NE(boundary_behavior, AXBoundaryBehavior::StopIfAlreadyAtBoundary);
     if (boundary_behavior == AXBoundaryBehavior::StopAtAnchorBoundary &&
-        (text_position->text_offset_ + 1) >= text_position->MaxTextOffset()) {
+        (text_position->text_offset_) >= max_position) {
       return Clone();
     }
 
-    if ((text_position->text_offset_ + 1) < text_position->MaxTextOffset()) {
+    if (!text_position->AtEndOfLine() &&
+        (text_position->text_offset_ + 1) <= max_position) {
       text_position->text_offset_ += 1;
       // Even if our affinity was upstream, moving to the next character should
       // inevitably reset it to downstream.
diff --git a/ui/display/manager/display_configurator.cc b/ui/display/manager/display_configurator.cc
index c81b1e8..6dce07f 100644
--- a/ui/display/manager/display_configurator.cc
+++ b/ui/display/manager/display_configurator.cc
@@ -573,7 +573,6 @@
       has_pending_power_state_(false),
       pending_power_flags_(kSetDisplayPowerNoFlags),
       force_configure_(false),
-      next_display_protection_client_id_(1),
       display_externally_controlled_(false),
       display_control_changing_(false),
       displays_suspended_(false),
@@ -593,9 +592,9 @@
     query_protection_callbacks_.pop();
   }
 
-  while (!set_protection_callbacks_.empty()) {
-    std::move(set_protection_callbacks_.front()).Run(false);
-    set_protection_callbacks_.pop();
+  while (!apply_protection_callbacks_.empty()) {
+    std::move(apply_protection_callbacks_.front()).Run(false);
+    apply_protection_callbacks_.pop();
   }
 }
 
@@ -638,7 +637,7 @@
     std::unique_ptr<NativeDisplayDelegate> display_delegate,
     bool is_panel_fitting_enabled) {
   is_panel_fitting_enabled_ = is_panel_fitting_enabled;
-  if (!configure_display_ || display_externally_controlled_)
+  if (configurator_disabled())
     return;
 
   // If the delegate is already initialized don't update it (For example, tests
@@ -740,7 +739,7 @@
 }
 
 void DisplayConfigurator::ForceInitialConfigure() {
-  if (!configure_display_ || display_externally_controlled_)
+  if (configurator_disabled())
     return;
 
   DCHECK(native_display_delegate_);
@@ -759,25 +758,29 @@
   configuration_task_->Run();
 }
 
-uint64_t DisplayConfigurator::RegisterContentProtectionClient() {
-  if (!configure_display_ || display_externally_controlled_)
-    return INVALID_CLIENT_ID;
+DisplayConfigurator::ContentProtectionClientId
+DisplayConfigurator::RegisterContentProtectionClient() {
+  if (configurator_disabled())
+    return base::nullopt;
 
-  return next_display_protection_client_id_++;
+  return next_content_protection_client_id_++;
 }
 
 void DisplayConfigurator::UnregisterContentProtectionClient(
-    uint64_t client_id) {
-  client_protection_requests_.erase(client_id);
+    ContentProtectionClientId client_id) {
+  if (!client_id)
+    return;
+
+  content_protection_requests_.erase(*client_id);
 
   ContentProtections protections;
-  for (const auto& requests_pair : client_protection_requests_) {
+  for (const auto& requests_pair : content_protection_requests_) {
     for (const auto& protections_pair : requests_pair.second) {
       protections[protections_pair.first] |= protections_pair.second;
     }
   }
 
-  set_protection_callbacks_.push(base::DoNothing());
+  apply_protection_callbacks_.push(base::DoNothing());
   ApplyContentProtectionTask* task = new ApplyContentProtectionTask(
       layout_manager_.get(), native_display_delegate_.get(), protections,
       base::Bind(&DisplayConfigurator::OnContentProtectionClientUnregistered,
@@ -793,38 +796,41 @@
   DCHECK(!content_protection_tasks_.empty());
   content_protection_tasks_.pop();
 
-  DCHECK(!set_protection_callbacks_.empty());
-  SetProtectionCallback callback = std::move(set_protection_callbacks_.front());
-  set_protection_callbacks_.pop();
+  DCHECK(!apply_protection_callbacks_.empty());
+  ApplyContentProtectionCallback callback =
+      std::move(apply_protection_callbacks_.front());
+  apply_protection_callbacks_.pop();
 
   if (!content_protection_tasks_.empty())
     content_protection_tasks_.front().Run();
 }
 
-void DisplayConfigurator::QueryContentProtectionStatus(
-    uint64_t client_id,
+void DisplayConfigurator::QueryContentProtection(
+    ContentProtectionClientId client_id,
     int64_t display_id,
-    QueryProtectionCallback callback) {
+    QueryContentProtectionCallback callback) {
+  if (!client_id || configurator_disabled()) {
+    std::move(callback).Run(/*success=*/false, DISPLAY_CONNECTION_TYPE_NONE,
+                            CONTENT_PROTECTION_METHOD_NONE);
+    return;
+  }
+
   // Exclude virtual displays so that protected content will not be recaptured
   // through the cast stream.
   for (const DisplaySnapshot* display : cached_displays_) {
     if (display->display_id() == display_id &&
         !IsPhysicalDisplayType(display->type())) {
-      std::move(callback).Run(false, 0, 0);
+      std::move(callback).Run(/*success=*/false, DISPLAY_CONNECTION_TYPE_NONE,
+                              CONTENT_PROTECTION_METHOD_NONE);
       return;
     }
   }
 
-  if (!configure_display_ || display_externally_controlled_) {
-    std::move(callback).Run(false, 0, 0);
-    return;
-  }
-
   query_protection_callbacks_.push(std::move(callback));
   QueryContentProtectionTask* task = new QueryContentProtectionTask(
       layout_manager_.get(), native_display_delegate_.get(), display_id,
       base::Bind(&DisplayConfigurator::OnContentProtectionQueried,
-                 weak_ptr_factory_.GetWeakPtr(), client_id, display_id));
+                 weak_ptr_factory_.GetWeakPtr(), *client_id, display_id));
   content_protection_tasks_.push(
       base::Bind(&QueryContentProtectionTask::Run, base::Owned(task)));
   if (content_protection_tasks_.size() == 1)
@@ -834,93 +840,99 @@
 void DisplayConfigurator::OnContentProtectionQueried(
     uint64_t client_id,
     int64_t display_id,
-    QueryContentProtectionTask::Response task_response) {
-  bool success = task_response.success;
-  uint32_t link_mask = task_response.link_mask;
-  uint32_t protection_mask = 0;
+    QueryContentProtectionTask::Response response) {
+  bool success = response.success;
+  uint32_t connection_mask = response.link_mask;
+  uint32_t protection_mask = response.enabled & ~response.unfulfilled;
+  uint32_t client_mask = CONTENT_PROTECTION_METHOD_NONE;
 
   // Don't reveal protections requested by other clients.
-  ProtectionRequests::iterator it = client_protection_requests_.find(client_id);
-  if (success && it != client_protection_requests_.end()) {
-    uint32_t requested_mask = 0;
-    if (it->second.find(display_id) != it->second.end())
-      requested_mask = it->second[display_id];
-    protection_mask =
-        task_response.enabled & ~task_response.unfulfilled & requested_mask;
+  auto it = content_protection_requests_.find(client_id);
+  if (success && it != content_protection_requests_.end()) {
+    const ContentProtections& protections = it->second;
+
+    auto it = protections.find(display_id);
+    if (it != protections.end())
+      client_mask = it->second;
   }
 
+  protection_mask &= client_mask;
+
   DCHECK(!content_protection_tasks_.empty());
   content_protection_tasks_.pop();
 
   DCHECK(!query_protection_callbacks_.empty());
-  QueryProtectionCallback callback =
+  QueryContentProtectionCallback callback =
       std::move(query_protection_callbacks_.front());
   query_protection_callbacks_.pop();
-  std::move(callback).Run(success, link_mask, protection_mask);
+  std::move(callback).Run(success, connection_mask, protection_mask);
 
   if (!content_protection_tasks_.empty())
     content_protection_tasks_.front().Run();
 }
 
-void DisplayConfigurator::SetContentProtection(uint64_t client_id,
-                                               int64_t display_id,
-                                               uint32_t desired_method_mask,
-                                               SetProtectionCallback callback) {
-  if (!configure_display_ || display_externally_controlled_) {
-    std::move(callback).Run(false);
+void DisplayConfigurator::ApplyContentProtection(
+    ContentProtectionClientId client_id,
+    int64_t display_id,
+    uint32_t protection_mask,
+    ApplyContentProtectionCallback callback) {
+  if (!client_id || configurator_disabled()) {
+    std::move(callback).Run(/*success=*/false);
     return;
   }
 
   ContentProtections protections;
-  for (const auto& requests_pair : client_protection_requests_) {
+  for (const auto& requests_pair : content_protection_requests_) {
     for (const auto& protections_pair : requests_pair.second) {
-      if (requests_pair.first == client_id &&
+      if (requests_pair.first == *client_id &&
           protections_pair.first == display_id)
         continue;
 
       protections[protections_pair.first] |= protections_pair.second;
     }
   }
-  protections[display_id] |= desired_method_mask;
+  protections[display_id] |= protection_mask;
 
-  set_protection_callbacks_.push(std::move(callback));
+  apply_protection_callbacks_.push(std::move(callback));
   ApplyContentProtectionTask* task = new ApplyContentProtectionTask(
       layout_manager_.get(), native_display_delegate_.get(), protections,
-      base::Bind(&DisplayConfigurator::OnSetContentProtectionCompleted,
-                 weak_ptr_factory_.GetWeakPtr(), client_id, display_id,
-                 desired_method_mask));
+      base::Bind(&DisplayConfigurator::OnContentProtectionApplied,
+                 weak_ptr_factory_.GetWeakPtr(), *client_id, display_id,
+                 protection_mask));
   content_protection_tasks_.push(
       base::Bind(&ApplyContentProtectionTask::Run, base::Owned(task)));
   if (content_protection_tasks_.size() == 1)
     content_protection_tasks_.front().Run();
 }
 
-void DisplayConfigurator::OnSetContentProtectionCompleted(
-    uint64_t client_id,
-    int64_t display_id,
-    uint32_t desired_method_mask,
-    bool success) {
+void DisplayConfigurator::OnContentProtectionApplied(uint64_t client_id,
+                                                     int64_t display_id,
+                                                     uint32_t protection_mask,
+                                                     bool success) {
   DCHECK(!content_protection_tasks_.empty());
   content_protection_tasks_.pop();
 
-  DCHECK(!set_protection_callbacks_.empty());
-  SetProtectionCallback callback = std::move(set_protection_callbacks_.front());
-  set_protection_callbacks_.pop();
+  DCHECK(!apply_protection_callbacks_.empty());
+  ApplyContentProtectionCallback callback =
+      std::move(apply_protection_callbacks_.front());
+  apply_protection_callbacks_.pop();
 
   if (!success) {
     std::move(callback).Run(false);
     return;
   }
 
-  if (desired_method_mask == CONTENT_PROTECTION_METHOD_NONE) {
-    if (client_protection_requests_.find(client_id) !=
-        client_protection_requests_.end()) {
-      client_protection_requests_[client_id].erase(display_id);
-      if (client_protection_requests_[client_id].size() == 0)
-        client_protection_requests_.erase(client_id);
+  if (protection_mask == CONTENT_PROTECTION_METHOD_NONE) {
+    auto it = content_protection_requests_.find(client_id);
+    if (it != content_protection_requests_.end()) {
+      ContentProtections& protections = it->second;
+      protections.erase(display_id);
+
+      if (protections.empty())
+        content_protection_requests_.erase(client_id);
     }
   } else {
-    client_protection_requests_[client_id][display_id] = desired_method_mask;
+    content_protection_requests_[client_id][display_id] = protection_mask;
   }
 
   std::move(callback).Run(true);
@@ -997,7 +1009,7 @@
     chromeos::DisplayPowerState power_state,
     int flags,
     const ConfigurationCallback& callback) {
-  if (!configure_display_ || display_externally_controlled_) {
+  if (configurator_disabled()) {
     callback.Run(false);
     return;
   }
@@ -1012,7 +1024,7 @@
 }
 
 void DisplayConfigurator::SetDisplayMode(MultipleDisplayState new_state) {
-  if (!configure_display_ || display_externally_controlled_)
+  if (configurator_disabled())
     return;
 
   VLOG(1) << "SetDisplayMode: state="
@@ -1064,7 +1076,7 @@
 
 void DisplayConfigurator::SuspendDisplays(
     const ConfigurationCallback& callback) {
-  if (!configure_display_ || display_externally_controlled_) {
+  if (configurator_disabled()) {
     callback.Run(false);
     return;
   }
@@ -1084,7 +1096,7 @@
 }
 
 void DisplayConfigurator::ResumeDisplays() {
-  if (!configure_display_ || display_externally_controlled_)
+  if (configurator_disabled())
     return;
 
   displays_suspended_ = false;
@@ -1114,7 +1126,7 @@
 }
 
 void DisplayConfigurator::ConfigureDisplays() {
-  if (!configure_display_ || display_externally_controlled_)
+  if (configurator_disabled())
     return;
 
   force_configure_ = true;
diff --git a/ui/display/manager/display_configurator.h b/ui/display/manager/display_configurator.h
index 1ceb678..f7be296 100644
--- a/ui/display/manager/display_configurator.h
+++ b/ui/display/manager/display_configurator.h
@@ -7,11 +7,11 @@
 
 #include <stdint.h>
 
-#include <map>
 #include <memory>
 #include <string>
 #include <vector>
 
+#include "base/containers/flat_map.h"
 #include "base/containers/queue.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
@@ -48,28 +48,20 @@
 class DISPLAY_MANAGER_EXPORT DisplayConfigurator
     : public NativeDisplayObserver {
  public:
-  enum : uint64_t {
-    INVALID_CLIENT_ID = 0,
-  };
-
   using ConfigurationCallback = base::Callback<void(bool /* success */)>;
 
-  using SetProtectionCallback = base::OnceCallback<void(bool /* success */)>;
+  // |connection_mask| is a DisplayConnectionType bitmask, and |protection_mask|
+  // is a ContentProtectionMethod bitmask.
+  using QueryContentProtectionCallback = base::OnceCallback<
+      void(bool success, uint32_t connection_mask, uint32_t protection_mask)>;
+  using ApplyContentProtectionCallback = base::OnceCallback<void(bool success)>;
 
-  // link_mask: The type of connected display links, which is a bitmask of
-  // DisplayConnectionType values.
-  // protection_mask: The desired protection methods, which is a bitmask of the
-  // ContentProtectionMethod values.
-  using QueryProtectionCallback =
-      base::OnceCallback<void(bool /* success */,
-                              uint32_t /* link_mask */,
-                              uint32_t /* protection_mask */)>;
-  using DisplayControlCallback = base::OnceCallback<void(bool /* success */)>;
+  using DisplayControlCallback = base::OnceCallback<void(bool success)>;
 
   using DisplayStateList = std::vector<DisplaySnapshot*>;
 
-  // Mapping a display_id to a protection request bitmask.
-  using ContentProtections = std::map<int64_t, uint32_t>;
+  using ContentProtections =
+      base::flat_map<int64_t /* display_id */, uint32_t /* protection_mask */>;
 
   class Observer {
    public:
@@ -267,27 +259,19 @@
   // suspended.
   void ResumeDisplays();
 
-  // Registers a client for display protection and requests a client id. Returns
-  // 0 if requesting failed.
-  uint64_t RegisterContentProtectionClient();
+  using ContentProtectionClientId = base::Optional<uint64_t>;
 
-  // Unregisters the client.
-  void UnregisterContentProtectionClient(uint64_t client_id);
+  ContentProtectionClientId RegisterContentProtectionClient();
+  void UnregisterContentProtectionClient(ContentProtectionClientId client_id);
 
-  // Queries link status and protection status. |callback| is used to respond
-  // to the query.
-  void QueryContentProtectionStatus(uint64_t client_id,
-                                    int64_t display_id,
-                                    QueryProtectionCallback callback);
-
-  // Requests the desired protection methods.
-  // |protection_mask| is the desired protection methods, which is a bitmask
-  // of the ContentProtectionMethod values.
-  // Returns true when the protection request has been made.
-  void SetContentProtection(uint64_t client_id,
-                            int64_t display_id,
-                            uint32_t protection_mask,
-                            SetProtectionCallback callback);
+  void QueryContentProtection(ContentProtectionClientId client_id,
+                              int64_t display_id,
+                              QueryContentProtectionCallback callback);
+  // |protection_mask| is a ContentProtectionMethod bitmask.
+  void ApplyContentProtection(ContentProtectionClientId client_id,
+                              int64_t display_id,
+                              uint32_t protection_mask,
+                              ApplyContentProtectionCallback callback);
 
   // Returns true if there is at least one display on.
   bool IsDisplayOn() const;
@@ -321,8 +305,9 @@
 
   class DisplayLayoutManagerImpl;
 
-  // Mapping a client to its protection request.
-  using ProtectionRequests = std::map<uint64_t, ContentProtections>;
+  bool configurator_disabled() const {
+    return !configure_display_ || display_externally_controlled_;
+  }
 
   // Updates |pending_*| members and applies the passed-in state. |callback| is
   // invoked (perhaps synchronously) on completion.
@@ -345,9 +330,6 @@
   MultipleDisplayState ChooseDisplayState(
       chromeos::DisplayPowerState power_state) const;
 
-  // Applies display protections according to requests.
-  bool ApplyProtections(const ContentProtections& requests);
-
   // If |configuration_task_| isn't initialized, initializes it and starts the
   // configuration task.
   void RunPendingConfiguration();
@@ -382,10 +364,10 @@
       uint64_t client_id,
       int64_t display_id,
       QueryContentProtectionTask::Response response);
-  void OnSetContentProtectionCompleted(uint64_t client_id,
-                                       int64_t display_id,
-                                       uint32_t desired_method_mask,
-                                       bool success);
+  void OnContentProtectionApplied(uint64_t client_id,
+                                  int64_t display_id,
+                                  uint32_t protection_mask,
+                                  bool success);
   void OnContentProtectionClientUnregistered(bool success);
 
   // Callbacks used to signal when the native platform has released/taken
@@ -446,8 +428,8 @@
   std::vector<ConfigurationCallback> in_progress_configuration_callbacks_;
 
   base::queue<base::Closure> content_protection_tasks_;
-  base::queue<QueryProtectionCallback> query_protection_callbacks_;
-  base::queue<SetProtectionCallback> set_protection_callbacks_;
+  base::queue<QueryContentProtectionCallback> query_protection_callbacks_;
+  base::queue<ApplyContentProtectionCallback> apply_protection_callbacks_;
 
   // True if the caller wants to force the display configuration process.
   bool force_configure_;
@@ -462,11 +444,10 @@
   // display configuration events when they are reported in short time spans.
   base::OneShotTimer configure_timer_;
 
-  // Id for next display protection client.
-  uint64_t next_display_protection_client_id_;
+  uint64_t next_content_protection_client_id_ = 0;
 
-  // Display protection requests of each client.
-  ProtectionRequests client_protection_requests_;
+  // Content protections requested by each client.
+  base::flat_map<uint64_t, ContentProtections> content_protection_requests_;
 
   // Display controlled by an external entity.
   bool display_externally_controlled_;
diff --git a/ui/display/manager/display_configurator_unittest.cc b/ui/display/manager/display_configurator_unittest.cc
index d1018a88..3fb738a 100644
--- a/ui/display/manager/display_configurator_unittest.cc
+++ b/ui/display/manager/display_configurator_unittest.cc
@@ -203,20 +203,8 @@
 
 class DisplayConfiguratorTest : public testing::Test {
  public:
-  DisplayConfiguratorTest()
-      : small_mode_(gfx::Size(1366, 768), false, 60.0f),
-        big_mode_(gfx::Size(2560, 1600), false, 60.0f),
-        observer_(&configurator_),
-        test_api_(&configurator_),
-        config_waiter_(&test_api_),
-        set_content_protection_status_(false),
-        set_content_protection_call_count_(0),
-        query_content_protection_response_success_(false),
-        query_content_protection_response_link_mask_(0),
-        query_content_protection_response_protection_mask_(0),
-        query_content_protection_call_count_(0),
-        display_control_result_(CALLBACK_NOT_CALLED) {}
-  ~DisplayConfiguratorTest() override {}
+  DisplayConfiguratorTest() = default;
+  ~DisplayConfiguratorTest() override = default;
 
   void SetUp() override {
     log_.reset(new ActionLogger());
@@ -260,27 +248,27 @@
     UpdateOutputs(2, false);
   }
 
-  void OnDisplayControlUpdated(bool status) {
-    display_control_result_ = (status ? CALLBACK_SUCCESS : CALLBACK_FAILURE);
+  void OnDisplayControlUpdated(bool success) {
+    display_control_result_ = success ? CALLBACK_SUCCESS : CALLBACK_FAILURE;
   }
 
-  void SetContentProtectionCallback(bool status) {
-    set_content_protection_status_ = status;
-    set_content_protection_call_count_++;
+  void ApplyContentProtectionCallback(bool success) {
+    apply_content_protection_success_ = success;
+    apply_content_protection_call_count_++;
   }
 
   void QueryContentProtectionCallback(bool success,
-                                      uint32_t link_mask,
+                                      uint32_t connection_mask,
                                       uint32_t protection_mask) {
-    query_content_protection_response_success_ = success;
-    query_content_protection_response_link_mask_ = link_mask;
-    query_content_protection_response_protection_mask_ = protection_mask;
+    query_content_protection_success_ = success;
+    query_content_protection_connection_mask_ = connection_mask;
+    query_content_protection_protection_mask_ = protection_mask;
     query_content_protection_call_count_++;
   }
 
   // Predefined modes that can be used by outputs.
-  const DisplayMode small_mode_;
-  const DisplayMode big_mode_;
+  const DisplayMode small_mode_{gfx::Size(1366, 768), false, 60.0f};
+  const DisplayMode big_mode_{gfx::Size(2560, 1600), false, 60.0f};
 
  protected:
   // Configures |native_display_delegate_| to return the first |num_outputs|
@@ -331,21 +319,22 @@
   TestStateController state_controller_;
   TestMirroringController mirroring_controller_;
   DisplayConfigurator configurator_;
-  TestObserver observer_;
+  TestObserver observer_{&configurator_};
   std::unique_ptr<ActionLogger> log_;
   TestNativeDisplayDelegate* native_display_delegate_;  // not owned
-  DisplayConfigurator::TestApi test_api_;
-  ConfigurationWaiter config_waiter_;
-  bool set_content_protection_status_;
-  int set_content_protection_call_count_;
-  bool query_content_protection_response_success_;
-  uint32_t query_content_protection_response_link_mask_;
-  uint32_t query_content_protection_response_protection_mask_;
-  int query_content_protection_call_count_;
+  DisplayConfigurator::TestApi test_api_{&configurator_};
+  ConfigurationWaiter config_waiter_{&test_api_};
+
+  bool apply_content_protection_success_ = false;
+  int apply_content_protection_call_count_ = 0;
+  bool query_content_protection_success_ = false;
+  int query_content_protection_call_count_ = 0;
+  uint32_t query_content_protection_connection_mask_ = 0;
+  uint32_t query_content_protection_protection_mask_ = 0;
 
   std::unique_ptr<DisplaySnapshot> outputs_[3];
 
-  CallbackResult display_control_result_;
+  CallbackResult display_control_result_ = CALLBACK_NOT_CALLED;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(DisplayConfiguratorTest);
@@ -942,60 +931,60 @@
   configurator_.ForceInitialConfigure();
   EXPECT_NE(kNoActions, log_->GetActionsAndClear());
 
-  uint64_t id = configurator_.RegisterContentProtectionClient();
-  EXPECT_NE(0u, id);
+  auto id = configurator_.RegisterContentProtectionClient();
+  EXPECT_TRUE(id);
 
   // One output.
   UpdateOutputs(1, true);
   EXPECT_NE(kNoActions, log_->GetActionsAndClear());
-  configurator_.QueryContentProtectionStatus(
+  configurator_.QueryContentProtection(
       id, outputs_[0]->display_id(),
-      base::Bind(&DisplayConfiguratorTest::QueryContentProtectionCallback,
-                 base::Unretained(this)));
+      base::BindOnce(&DisplayConfiguratorTest::QueryContentProtectionCallback,
+                     base::Unretained(this)));
   EXPECT_EQ(1, query_content_protection_call_count_);
-  EXPECT_TRUE(query_content_protection_response_success_);
+  EXPECT_TRUE(query_content_protection_success_);
   EXPECT_EQ(static_cast<uint32_t>(DISPLAY_CONNECTION_TYPE_INTERNAL),
-            query_content_protection_response_link_mask_);
+            query_content_protection_connection_mask_);
   EXPECT_EQ(static_cast<uint32_t>(CONTENT_PROTECTION_METHOD_NONE),
-            query_content_protection_response_protection_mask_);
+            query_content_protection_protection_mask_);
   EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
 
   // Two outputs.
   UpdateOutputs(2, true);
   EXPECT_NE(kNoActions, log_->GetActionsAndClear());
-  configurator_.QueryContentProtectionStatus(
+  configurator_.QueryContentProtection(
       id, outputs_[1]->display_id(),
-      base::Bind(&DisplayConfiguratorTest::QueryContentProtectionCallback,
-                 base::Unretained(this)));
+      base::BindOnce(&DisplayConfiguratorTest::QueryContentProtectionCallback,
+                     base::Unretained(this)));
   EXPECT_EQ(2, query_content_protection_call_count_);
-  EXPECT_TRUE(query_content_protection_response_success_);
+  EXPECT_TRUE(query_content_protection_success_);
   EXPECT_EQ(static_cast<uint32_t>(DISPLAY_CONNECTION_TYPE_HDMI),
-            query_content_protection_response_link_mask_);
+            query_content_protection_connection_mask_);
   EXPECT_EQ(static_cast<uint32_t>(CONTENT_PROTECTION_METHOD_NONE),
-            query_content_protection_response_protection_mask_);
+            query_content_protection_protection_mask_);
   EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
 
-  configurator_.SetContentProtection(
+  configurator_.ApplyContentProtection(
       id, outputs_[1]->display_id(), CONTENT_PROTECTION_METHOD_HDCP,
-      base::Bind(&DisplayConfiguratorTest::SetContentProtectionCallback,
-                 base::Unretained(this)));
-  EXPECT_EQ(1, set_content_protection_call_count_);
-  EXPECT_TRUE(set_content_protection_status_);
+      base::BindOnce(&DisplayConfiguratorTest::ApplyContentProtectionCallback,
+                     base::Unretained(this)));
+  EXPECT_EQ(1, apply_content_protection_call_count_);
+  EXPECT_TRUE(apply_content_protection_success_);
   EXPECT_EQ(GetSetHDCPStateAction(*outputs_[1], HDCP_STATE_DESIRED),
             log_->GetActionsAndClear());
 
   // Enable protection.
   native_display_delegate_->set_hdcp_state(HDCP_STATE_ENABLED);
-  configurator_.QueryContentProtectionStatus(
+  configurator_.QueryContentProtection(
       id, outputs_[1]->display_id(),
-      base::Bind(&DisplayConfiguratorTest::QueryContentProtectionCallback,
-                 base::Unretained(this)));
+      base::BindOnce(&DisplayConfiguratorTest::QueryContentProtectionCallback,
+                     base::Unretained(this)));
   EXPECT_EQ(3, query_content_protection_call_count_);
-  EXPECT_TRUE(query_content_protection_response_success_);
+  EXPECT_TRUE(query_content_protection_success_);
   EXPECT_EQ(static_cast<uint32_t>(DISPLAY_CONNECTION_TYPE_HDMI),
-            query_content_protection_response_link_mask_);
+            query_content_protection_connection_mask_);
   EXPECT_EQ(static_cast<uint32_t>(CONTENT_PROTECTION_METHOD_HDCP),
-            query_content_protection_response_protection_mask_);
+            query_content_protection_protection_mask_);
   EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
 
   // Protections should be disabled after unregister.
@@ -1090,8 +1079,8 @@
 }
 
 TEST_F(DisplayConfiguratorTest, ContentProtectionTwoClients) {
-  uint64_t client1 = configurator_.RegisterContentProtectionClient();
-  uint64_t client2 = configurator_.RegisterContentProtectionClient();
+  auto client1 = configurator_.RegisterContentProtectionClient();
+  auto client2 = configurator_.RegisterContentProtectionClient();
   EXPECT_NE(client1, client2);
 
   Init(false);
@@ -1100,60 +1089,60 @@
   EXPECT_NE(kNoActions, log_->GetActionsAndClear());
 
   // Clients never know state enableness for methods that they didn't request.
-  configurator_.SetContentProtection(
+  configurator_.ApplyContentProtection(
       client1, outputs_[1]->display_id(), CONTENT_PROTECTION_METHOD_HDCP,
-      base::Bind(&DisplayConfiguratorTest::SetContentProtectionCallback,
-                 base::Unretained(this)));
-  EXPECT_EQ(1, set_content_protection_call_count_);
-  EXPECT_TRUE(set_content_protection_status_);
+      base::BindOnce(&DisplayConfiguratorTest::ApplyContentProtectionCallback,
+                     base::Unretained(this)));
+  EXPECT_EQ(1, apply_content_protection_call_count_);
+  EXPECT_TRUE(apply_content_protection_success_);
   EXPECT_EQ(GetSetHDCPStateAction(*outputs_[1], HDCP_STATE_DESIRED).c_str(),
             log_->GetActionsAndClear());
   native_display_delegate_->set_hdcp_state(HDCP_STATE_ENABLED);
 
-  configurator_.QueryContentProtectionStatus(
+  configurator_.QueryContentProtection(
       client1, outputs_[1]->display_id(),
-      base::Bind(&DisplayConfiguratorTest::QueryContentProtectionCallback,
-                 base::Unretained(this)));
+      base::BindOnce(&DisplayConfiguratorTest::QueryContentProtectionCallback,
+                     base::Unretained(this)));
   EXPECT_EQ(1, query_content_protection_call_count_);
-  EXPECT_TRUE(query_content_protection_response_success_);
+  EXPECT_TRUE(query_content_protection_success_);
   EXPECT_EQ(static_cast<uint32_t>(DISPLAY_CONNECTION_TYPE_HDMI),
-            query_content_protection_response_link_mask_);
+            query_content_protection_connection_mask_);
   EXPECT_EQ(CONTENT_PROTECTION_METHOD_HDCP,
-            query_content_protection_response_protection_mask_);
+            query_content_protection_protection_mask_);
 
-  configurator_.QueryContentProtectionStatus(
+  configurator_.QueryContentProtection(
       client2, outputs_[1]->display_id(),
-      base::Bind(&DisplayConfiguratorTest::QueryContentProtectionCallback,
-                 base::Unretained(this)));
+      base::BindOnce(&DisplayConfiguratorTest::QueryContentProtectionCallback,
+                     base::Unretained(this)));
   EXPECT_EQ(2, query_content_protection_call_count_);
-  EXPECT_TRUE(query_content_protection_response_success_);
+  EXPECT_TRUE(query_content_protection_success_);
   EXPECT_EQ(static_cast<uint32_t>(DISPLAY_CONNECTION_TYPE_HDMI),
-            query_content_protection_response_link_mask_);
+            query_content_protection_connection_mask_);
   EXPECT_EQ(CONTENT_PROTECTION_METHOD_NONE,
-            query_content_protection_response_protection_mask_);
+            query_content_protection_protection_mask_);
 
   // Protections will be disabled only if no more clients request them.
-  configurator_.SetContentProtection(
+  configurator_.ApplyContentProtection(
       client2, outputs_[1]->display_id(), CONTENT_PROTECTION_METHOD_NONE,
-      base::Bind(&DisplayConfiguratorTest::SetContentProtectionCallback,
-                 base::Unretained(this)));
-  EXPECT_EQ(2, set_content_protection_call_count_);
-  EXPECT_TRUE(set_content_protection_status_);
+      base::BindOnce(&DisplayConfiguratorTest::ApplyContentProtectionCallback,
+                     base::Unretained(this)));
+  EXPECT_EQ(2, apply_content_protection_call_count_);
+  EXPECT_TRUE(apply_content_protection_success_);
   EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
 
-  configurator_.SetContentProtection(
+  configurator_.ApplyContentProtection(
       client1, outputs_[1]->display_id(), CONTENT_PROTECTION_METHOD_NONE,
-      base::Bind(&DisplayConfiguratorTest::SetContentProtectionCallback,
-                 base::Unretained(this)));
-  EXPECT_EQ(3, set_content_protection_call_count_);
-  EXPECT_TRUE(set_content_protection_status_);
+      base::BindOnce(&DisplayConfiguratorTest::ApplyContentProtectionCallback,
+                     base::Unretained(this)));
+  EXPECT_EQ(3, apply_content_protection_call_count_);
+  EXPECT_TRUE(apply_content_protection_success_);
   EXPECT_EQ(GetSetHDCPStateAction(*outputs_[1], HDCP_STATE_UNDESIRED).c_str(),
             log_->GetActionsAndClear());
 }
 
 TEST_F(DisplayConfiguratorTest, ContentProtectionTwoClientsEnable) {
-  uint64_t client1 = configurator_.RegisterContentProtectionClient();
-  uint64_t client2 = configurator_.RegisterContentProtectionClient();
+  auto client1 = configurator_.RegisterContentProtectionClient();
+  auto client2 = configurator_.RegisterContentProtectionClient();
   EXPECT_NE(client1, client2);
 
   Init(false);
@@ -1162,36 +1151,36 @@
   log_->GetActionsAndClear();
 
   // Only enable once if HDCP is enabling.
-  configurator_.SetContentProtection(
+  configurator_.ApplyContentProtection(
       client1, outputs_[1]->display_id(), CONTENT_PROTECTION_METHOD_HDCP,
-      base::Bind(&DisplayConfiguratorTest::SetContentProtectionCallback,
-                 base::Unretained(this)));
-  EXPECT_EQ(1, set_content_protection_call_count_);
-  EXPECT_TRUE(set_content_protection_status_);
+      base::BindOnce(&DisplayConfiguratorTest::ApplyContentProtectionCallback,
+                     base::Unretained(this)));
+  EXPECT_EQ(1, apply_content_protection_call_count_);
+  EXPECT_TRUE(apply_content_protection_success_);
   native_display_delegate_->set_hdcp_state(HDCP_STATE_DESIRED);
-  configurator_.SetContentProtection(
+  configurator_.ApplyContentProtection(
       client2, outputs_[1]->display_id(), CONTENT_PROTECTION_METHOD_HDCP,
-      base::Bind(&DisplayConfiguratorTest::SetContentProtectionCallback,
-                 base::Unretained(this)));
-  EXPECT_EQ(2, set_content_protection_call_count_);
-  EXPECT_TRUE(set_content_protection_status_);
+      base::BindOnce(&DisplayConfiguratorTest::ApplyContentProtectionCallback,
+                     base::Unretained(this)));
+  EXPECT_EQ(2, apply_content_protection_call_count_);
+  EXPECT_TRUE(apply_content_protection_success_);
   EXPECT_EQ(GetSetHDCPStateAction(*outputs_[1], HDCP_STATE_DESIRED).c_str(),
             log_->GetActionsAndClear());
   native_display_delegate_->set_hdcp_state(HDCP_STATE_ENABLED);
 
   // Don't enable again if HDCP is already active.
-  configurator_.SetContentProtection(
+  configurator_.ApplyContentProtection(
       client1, outputs_[1]->display_id(), CONTENT_PROTECTION_METHOD_HDCP,
-      base::Bind(&DisplayConfiguratorTest::SetContentProtectionCallback,
-                 base::Unretained(this)));
-  EXPECT_EQ(3, set_content_protection_call_count_);
-  EXPECT_TRUE(set_content_protection_status_);
-  configurator_.SetContentProtection(
+      base::BindOnce(&DisplayConfiguratorTest::ApplyContentProtectionCallback,
+                     base::Unretained(this)));
+  EXPECT_EQ(3, apply_content_protection_call_count_);
+  EXPECT_TRUE(apply_content_protection_success_);
+  configurator_.ApplyContentProtection(
       client2, outputs_[1]->display_id(), CONTENT_PROTECTION_METHOD_HDCP,
-      base::Bind(&DisplayConfiguratorTest::SetContentProtectionCallback,
-                 base::Unretained(this)));
-  EXPECT_EQ(4, set_content_protection_call_count_);
-  EXPECT_TRUE(set_content_protection_status_);
+      base::BindOnce(&DisplayConfiguratorTest::ApplyContentProtectionCallback,
+                     base::Unretained(this)));
+  EXPECT_EQ(4, apply_content_protection_call_count_);
+  EXPECT_TRUE(apply_content_protection_success_);
   EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
 }
 
diff --git a/ui/file_manager/file_manager/foreground/js/ui/banners.js b/ui/file_manager/file_manager/foreground/js/ui/banners.js
index 5b369986..eeaa760 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/banners.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/banners.js
@@ -3,91 +3,6 @@
 // found in the LICENSE file.
 
 /**
- * Responsible for showing following banners in the file list.
- *  - WelcomeBanner
- *  - AuthFailBanner
- * @param {DirectoryModel} directoryModel The model.
- * @param {!VolumeManager} volumeManager The manager.
- * @param {Document} document HTML document.
- * @param {boolean} showWelcome True if the welcome banner can be shown.
- * @constructor
- * @extends {cr.EventTarget}
- */
-function Banners(directoryModel, volumeManager, document, showWelcome) {
-  this.directoryModel_ = directoryModel;
-  this.volumeManager_ = volumeManager;
-  this.document_ = assert(document);
-  this.showWelcome_ = showWelcome;
-  this.driveEnabled_ = false;
-
-  this.privateOnDirectoryChangedBound_ =
-      this.privateOnDirectoryChanged_.bind(this);
-
-  const handler = this.checkSpaceAndMaybeShowWelcomeBanner_.bind(this);
-  this.directoryModel_.addEventListener('scan-completed', handler);
-  this.directoryModel_.addEventListener('rescan-completed', handler);
-  this.directoryModel_.addEventListener(
-      'directory-changed', this.onDirectoryChanged_.bind(this));
-
-  this.unmountedPanel_ = this.document_.querySelector('#unmounted-panel');
-  this.volumeManager_.volumeInfoList.addEventListener(
-      'splice', this.onVolumeInfoListSplice_.bind(this));
-  this.volumeManager_.addEventListener(
-      'drive-connection-changed', this.onDriveConnectionChanged_.bind(this));
-
-  chrome.storage.onChanged.addListener(this.onStorageChange_.bind(this));
-  this.welcomeHeaderCounter_ = WELCOME_HEADER_COUNTER_LIMIT;
-  this.warningDismissedCounter_ = 0;
-  this.downloadsWarningDismissedTime_ = 0;
-
-  this.ready_ = new Promise((resolve, reject) => {
-    chrome.storage.local.get(
-        [
-          WELCOME_HEADER_COUNTER_KEY, DRIVE_WARNING_DISMISSED_KEY,
-          DOWNLOADS_WARNING_DISMISSED_KEY
-        ],
-        values => {
-          if (chrome.runtime.lastError) {
-            reject(
-                'Failed to load banner data from chrome.storage: ' +
-                chrome.runtime.lastError.message);
-            return;
-          }
-          this.welcomeHeaderCounter_ =
-              parseInt(values[WELCOME_HEADER_COUNTER_KEY], 10) || 0;
-          this.warningDismissedCounter_ =
-              parseInt(values[DRIVE_WARNING_DISMISSED_KEY], 10) || 0;
-          this.downloadsWarningDismissedTime_ =
-              parseInt(values[DOWNLOADS_WARNING_DISMISSED_KEY], 10) || 0;
-
-          // If it's in test, override the counter to show the header by
-          // force.
-          if (chrome.test) {
-            this.welcomeHeaderCounter_ = 0;
-            this.warningDismissedCounter_ = 0;
-          }
-          resolve();
-        });
-  });
-
-  // Authentication failed banner.
-  this.authFailedBanner_ =
-      this.document_.querySelector('#drive-auth-failed-warning');
-  const authFailedText = this.authFailedBanner_.querySelector('.drive-text');
-  authFailedText.innerHTML = util.htmlUnescape(str('DRIVE_NOT_REACHED'));
-  authFailedText.querySelector('a').addEventListener('click', e => {
-    chrome.fileManagerPrivate.logoutUserForReauthentication();
-    e.preventDefault();
-  });
-  this.maybeShowAuthFailBanner_();
-}
-
-/**
- * Banners extends cr.EventTarget.
- */
-Banners.prototype.__proto__ = cr.EventTarget.prototype;
-
-/**
  * Key in localStorage to keep number of times the Drive Welcome
  * banner has shown.
  */
@@ -131,579 +46,671 @@
 const DOWNLOADS_SPACE_WARNING_DISMISS_DURATION = 36 * 60 * 60 * 1000;
 
 /**
- * @param {number} value How many times the Drive Welcome header banner
- * has shown.
- * @private
+ * Responsible for showing following banners in the file list.
+ *  - WelcomeBanner
+ *  - AuthFailBanner
  */
-Banners.prototype.setWelcomeHeaderCounter_ = value => {
-  const values = {};
-  values[WELCOME_HEADER_COUNTER_KEY] = value;
-  chrome.storage.local.set(values);
-};
+class Banners extends cr.EventTarget {
+  /**
+   * @param {DirectoryModel} directoryModel The model.
+   * @param {!VolumeManager} volumeManager The manager.
+   * @param {Document} document HTML document.
+   * @param {boolean} showWelcome True if the welcome banner can be shown.
+   */
+  constructor(directoryModel, volumeManager, document, showWelcome) {
+    super();
 
-/**
- * @param {number} value How many times the low space warning has dismissed.
- * @private
- */
-Banners.prototype.setWarningDismissedCounter_ = value => {
-  const values = {};
-  values[DRIVE_WARNING_DISMISSED_KEY] = value;
-  chrome.storage.local.set(values);
-};
+    this.directoryModel_ = directoryModel;
+    this.volumeManager_ = volumeManager;
+    this.document_ = assert(document);
+    this.showWelcome_ = showWelcome;
+    this.driveEnabled_ = false;
 
-/**
- * chrome.storage.onChanged event handler.
- * @param {Object<Object>} changes Changes values.
- * @param {string} areaName "local" or "sync".
- * @private
- */
-Banners.prototype.onStorageChange_ = function(changes, areaName) {
-  if (areaName == 'local' && WELCOME_HEADER_COUNTER_KEY in changes) {
-    this.welcomeHeaderCounter_ = changes[WELCOME_HEADER_COUNTER_KEY].newValue;
-  }
-  if (areaName == 'local' && DRIVE_WARNING_DISMISSED_KEY in changes) {
-    this.warningDismissedCounter_ =
-        changes[DRIVE_WARNING_DISMISSED_KEY].newValue;
-  }
-  if (areaName == 'local' && DOWNLOADS_WARNING_DISMISSED_KEY in changes) {
-    this.downloadsWarningDismissedTime_ =
-        changes[DOWNLOADS_WARNING_DISMISSED_KEY].newValue;
-  }
-};
+    /** @private {boolean} */
+    this.previousDirWasOnDrive_ = false;
 
-/**
- * Invoked when the drive connection status is change in the volume manager.
- * @private
- */
-Banners.prototype.onDriveConnectionChanged_ = function() {
-  this.maybeShowAuthFailBanner_();
-};
+    this.privateOnDirectoryChangedBound_ =
+        this.privateOnDirectoryChanged_.bind(this);
 
-/**
- * @param {string} type 'none'|'page'|'header'.
- * @param {string} messageId Resource ID of the message.
- * @private
- */
-Banners.prototype.prepareAndShowWelcomeBanner_ = function(type, messageId) {
-  if (!this.showWelcome_) {
-    return;
-  }
+    const handler = this.checkSpaceAndMaybeShowWelcomeBanner_.bind(this);
+    this.directoryModel_.addEventListener('scan-completed', handler);
+    this.directoryModel_.addEventListener('rescan-completed', handler);
+    this.directoryModel_.addEventListener(
+        'directory-changed', this.onDirectoryChanged_.bind(this));
 
-  this.showWelcomeBanner_(type);
+    this.unmountedPanel_ = this.document_.querySelector('#unmounted-panel');
+    this.volumeManager_.volumeInfoList.addEventListener(
+        'splice', this.onVolumeInfoListSplice_.bind(this));
+    this.volumeManager_.addEventListener(
+        'drive-connection-changed', this.onDriveConnectionChanged_.bind(this));
 
-  const container =
-      queryRequiredElement('.drive-welcome.' + type, this.document_);
-  if (container.firstElementChild) {
-    return;
-  }  // Do not re-create.
+    chrome.storage.onChanged.addListener(this.onStorageChange_.bind(this));
+    this.welcomeHeaderCounter_ = WELCOME_HEADER_COUNTER_LIMIT;
+    this.warningDismissedCounter_ = 0;
+    this.downloadsWarningDismissedTime_ = 0;
 
-  if (!this.document_.querySelector('link[drive-welcome-style]')) {
-    const style = this.document_.createElement('link');
-    style.rel = 'stylesheet';
-    style.href = constants.DRIVE_WELCOME_CSS;
-    style.setAttribute('drive-welcome-style', '');
-    this.document_.head.appendChild(style);
-  }
+    this.ready_ = new Promise((resolve, reject) => {
+      chrome.storage.local.get(
+          [
+            WELCOME_HEADER_COUNTER_KEY, DRIVE_WARNING_DISMISSED_KEY,
+            DOWNLOADS_WARNING_DISMISSED_KEY
+          ],
+          values => {
+            if (chrome.runtime.lastError) {
+              reject(
+                  'Failed to load banner data from chrome.storage: ' +
+                  chrome.runtime.lastError.message);
+              return;
+            }
+            this.welcomeHeaderCounter_ =
+                parseInt(values[WELCOME_HEADER_COUNTER_KEY], 10) || 0;
+            this.warningDismissedCounter_ =
+                parseInt(values[DRIVE_WARNING_DISMISSED_KEY], 10) || 0;
+            this.downloadsWarningDismissedTime_ =
+                parseInt(values[DOWNLOADS_WARNING_DISMISSED_KEY], 10) || 0;
 
-  const wrapper = util.createChild(container, 'drive-welcome-wrapper');
-  util.createChild(wrapper, 'drive-welcome-icon');
+            // If it's in test, override the counter to show the header by
+            // force.
+            if (chrome.test) {
+              this.welcomeHeaderCounter_ = 0;
+              this.warningDismissedCounter_ = 0;
+            }
+            resolve();
+          });
+    });
 
-  if (type === 'header') {
-    util.createChild(wrapper, 'banner-cloud-bg');
-    util.createChild(wrapper, 'banner-people');
-  }
-
-  const close = util.createChild(wrapper, 'banner-close');
-  close.addEventListener('click', this.closeWelcomeBanner_.bind(this));
-
-  const message = util.createChild(wrapper, 'drive-welcome-message');
-
-  const title = util.createChild(message, 'drive-welcome-title');
-
-  const text = util.createChild(message, 'drive-welcome-text');
-  text.innerHTML = str(messageId);
-
-  const links = util.createChild(message, 'drive-welcome-links');
-
-  title.textContent = str('DRIVE_WELCOME_TITLE');
-  const more = util.createChild(links, 'plain-link', 'a');
-  more.textContent = str('DRIVE_LEARN_MORE');
-  more.href = str('GOOGLE_DRIVE_OVERVIEW_URL');
-  more.tabIndex = 21;  // See: go/filesapp-tabindex.
-  more.id = 'drive-welcome-link';
-  more.target = '_blank';
-
-  const dismiss = util.createChild(links, 'plain-link');
-  dismiss.classList.add('drive-welcome-dismiss');
-  dismiss.textContent = str('DRIVE_WELCOME_DISMISS');
-  dismiss.addEventListener('click', this.closeWelcomeBanner_.bind(this));
-
-  this.previousDirWasOnDrive_ = false;
-};
-
-/**
- * Show or hide the "Low Google Drive space" warning.
- * @param {boolean} show True if the box need to be shown.
- * @param {Object=} opt_sizeStats Size statistics. Should be defined when
- *     showing the warning.
- * @private
- */
-Banners.prototype.showLowDriveSpaceWarning_ = function(show, opt_sizeStats) {
-  const box = this.document_.querySelector('#volume-space-warning');
-
-  // Avoid showing two banners.
-  // TODO(kaznacheev): Unify the low space warning and the promo header.
-  if (show) {
-    this.cleanupWelcomeBanner_();
-  }
-
-  if (box.hidden == !show) {
-    return;
-  }
-
-  if (this.warningDismissedCounter_) {
-    if (opt_sizeStats &&
-        // Quota had not changed
-        this.warningDismissedCounter_ == opt_sizeStats.totalSize &&
-        opt_sizeStats.remainingSize / opt_sizeStats.totalSize < 0.15) {
-      // Since the last dismissal decision the quota has not changed AND
-      // the user did not free up significant space. Obey the dismissal.
-      show = false;
-    } else {
-      // Forget the dismissal. Warning will be shown again.
-      this.setWarningDismissedCounter_(0);
-    }
-  }
-
-  box.textContent = '';
-  if (show && opt_sizeStats) {
-    const icon = this.document_.createElement('div');
-    icon.className = 'drive-icon';
-    box.appendChild(icon);
-
-    const text = this.document_.createElement('div');
-    text.className = 'drive-text';
-    text.textContent = strf(
-        'DRIVE_SPACE_AVAILABLE_LONG',
-        util.bytesToString(opt_sizeStats.remainingSize));
-    box.appendChild(text);
-
-    const link = this.document_.createElement('a');
-    link.href = str('GOOGLE_DRIVE_BUY_STORAGE_URL');
-    link.target = '_blank';
-    const button = this.document_.createElement('button');
-    button.className = 'imitate-paper-button';
-    button.textContent = str('DRIVE_BUY_MORE_SPACE_LINK');
-    link.appendChild(button);
-    box.appendChild(link);
-
-    const close = this.document_.createElement('div');
-    close.className = 'banner-close';
-    box.appendChild(close);
-    close.addEventListener('click', ((total) => {
-                                      const values = {};
-                                      values[DRIVE_WARNING_DISMISSED_KEY] =
-                                          total;
-                                      chrome.storage.local.set(values);
-                                      box.hidden = true;
-                                      this.requestRelayout_(100);
-                                    }).bind(null, opt_sizeStats.totalSize));
-  }
-
-  if (box.hidden != !show) {
-    box.hidden = !show;
-    this.requestRelayout_(100);
-  }
-};
-/**
- * Closes the Drive Welcome banner.
- * @private
- */
-Banners.prototype.closeWelcomeBanner_ = function() {
-  this.cleanupWelcomeBanner_();
-  // Stop showing the welcome banner.
-  this.setWelcomeHeaderCounter_(WELCOME_HEADER_COUNTER_LIMIT);
-};
-
-/**
- * Shows or hides the welcome banner for drive.
- * @private
- */
-Banners.prototype.checkSpaceAndMaybeShowWelcomeBanner_ = function() {
-  this.ready_.then(() => {
-    if (!this.isOnCurrentProfileDrive()) {
-      // We are not on the drive file system. Do not show (close) the welcome
-      // banner.
-      this.cleanupWelcomeBanner_();
-      this.previousDirWasOnDrive_ = false;
-      return;
-    }
-
-    const driveVolume = this.volumeManager_.getCurrentProfileVolumeInfo(
-        VolumeManagerCommon.VolumeType.DRIVE);
-    if (this.welcomeHeaderCounter_ >= WELCOME_HEADER_COUNTER_LIMIT ||
-        !driveVolume || driveVolume.error) {
-      // The banner is already shown enough times or the drive FS is not
-      // mounted. So, do nothing here.
-      return;
-    }
-
-    this.maybeShowWelcomeBanner_();
-  });
-};
-
-/**
- * Decides which banner should be shown, and show it. This method is designed
- * to be called only from checkSpaceAndMaybeShowWelcomeBanner_.
- * @private
- */
-Banners.prototype.maybeShowWelcomeBanner_ = function() {
-  this.ready_.then(() => {
-    if (this.directoryModel_.getFileList().length == 0 &&
-        this.welcomeHeaderCounter_ == 0) {
-      // Only show the full page banner if the header banner was never shown.
-      // Do not increment the counter.
-      // The timeout below is required because sometimes another
-      // 'rescan-completed' event arrives shortly with non-empty file list.
-      setTimeout(() => {
-        if (this.isOnCurrentProfileDrive() && this.welcomeHeaderCounter_ == 0) {
-          this.prepareAndShowWelcomeBanner_('page', 'DRIVE_WELCOME_TEXT_LONG');
-        }
-      }, 2000);
-    } else {
-      // We do not want to increment the counter when the user navigates
-      // between different directories on Drive, but we increment the counter
-      // once anyway to prevent the full page banner from showing.
-      if (!this.previousDirWasOnDrive_ || this.welcomeHeaderCounter_ == 0) {
-        this.setWelcomeHeaderCounter_(this.welcomeHeaderCounter_ + 1);
-        this.prepareAndShowWelcomeBanner_('header', 'DRIVE_WELCOME_TEXT_SHORT');
-      }
-    }
-    this.previousDirWasOnDrive_ = true;
-  });
-};
-
-/**
- * @return {boolean} True if current directory is on Drive root of current
- * profile.
- */
-Banners.prototype.isOnCurrentProfileDrive = function() {
-  const entry = this.directoryModel_.getCurrentDirEntry();
-  if (!entry || util.isFakeEntry(entry)) {
-    return false;
-  }
-  const locationInfo = this.volumeManager_.getLocationInfo(entry);
-  if (!locationInfo) {
-    return false;
-  }
-  return locationInfo.rootType === VolumeManagerCommon.RootType.DRIVE &&
-      locationInfo.volumeInfo.profile.isCurrentProfile;
-};
-
-/**
- * Shows the Drive Welcome banner.
- * @param {string} type 'page'|'head'|'none'.
- * @private
- */
-Banners.prototype.showWelcomeBanner_ = function(type) {
-  const container = this.document_.querySelector('.dialog-container');
-  if (container.getAttribute('drive-welcome') != type) {
-    container.setAttribute('drive-welcome', type);
-    this.requestRelayout_(200);  // Resize only after the animation is done.
-  }
-};
-
-/**
- * Update the UI when the current directory changes.
- *
- * @param {Event} event The directory-changed event.
- * @private
- */
-Banners.prototype.onDirectoryChanged_ = function(event) {
-  const rootVolume = this.volumeManager_.getVolumeInfo(event.newDirEntry);
-  if (!rootVolume) {
-    return;
-  }
-  const previousRootVolume = event.previousDirEntry ?
-      this.volumeManager_.getVolumeInfo(event.previousDirEntry) :
-      null;
-
-  // Show (or hide) the low space warning.
-  this.maybeShowLowSpaceWarning_(rootVolume);
-
-  // Add or remove listener to show low space warning, if necessary.
-  const isLowSpaceWarningTarget = this.isLowSpaceWarningTarget_(rootVolume);
-  if (isLowSpaceWarningTarget !==
-      this.isLowSpaceWarningTarget_(previousRootVolume)) {
-    if (isLowSpaceWarningTarget) {
-      chrome.fileManagerPrivate.onDirectoryChanged.addListener(
-          this.privateOnDirectoryChangedBound_);
-    } else {
-      chrome.fileManagerPrivate.onDirectoryChanged.removeListener(
-          this.privateOnDirectoryChangedBound_);
-    }
-  }
-
-  if (!this.isOnCurrentProfileDrive()) {
-    this.cleanupWelcomeBanner_();
-    this.authFailedBanner_.hidden = true;
-  }
-
-  this.updateDriveUnmountedPanel_();
-  if (this.isOnCurrentProfileDrive()) {
-    this.unmountedPanel_.classList.remove('retry-enabled');
-    this.maybeShowAuthFailBanner_();
-  }
-};
-
-/**
- * @param {VolumeInfo} volumeInfo Volume info to be checked.
- * @return {boolean} true if the file system specified by |root| is a target
- *     to show low space warning. Otherwise false.
- * @private
- */
-Banners.prototype.isLowSpaceWarningTarget_ = volumeInfo => {
-  if (!volumeInfo) {
-    return false;
-  }
-  return volumeInfo.profile.isCurrentProfile &&
-      (volumeInfo.volumeType === VolumeManagerCommon.VolumeType.DOWNLOADS ||
-       volumeInfo.volumeType === VolumeManagerCommon.VolumeType.DRIVE);
-};
-
-/**
- * Callback which is invoked when the file system has been changed.
- * @param {Object} event chrome.fileManagerPrivate.onDirectoryChanged event.
- * @private
- */
-Banners.prototype.privateOnDirectoryChanged_ = function(event) {
-  const currentDirEntry = this.directoryModel_.getCurrentDirEntry();
-  if (!currentDirEntry) {
-    return;
-  }
-  const currentVolume = this.volumeManager_.getVolumeInfo(currentDirEntry);
-  if (!currentVolume) {
-    return;
-  }
-  const eventVolume = this.volumeManager_.getVolumeInfo(event.entry);
-  if (currentVolume === eventVolume) {
-    // The file system we are currently on is changed.
-    // So, check the free space.
-    this.maybeShowLowSpaceWarning_(currentVolume);
-  }
-};
-
-/**
- * Shows or hides the low space warning.
- * @param {VolumeInfo} volume Type of volume, which we are interested in.
- * @private
- */
-Banners.prototype.maybeShowLowSpaceWarning_ = function(volume) {
-  // TODO(kaznacheev): Unify the two low space warning.
-  switch (volume.volumeType) {
-    case VolumeManagerCommon.VolumeType.DOWNLOADS:
-      this.showLowDriveSpaceWarning_(false);
-      break;
-    case VolumeManagerCommon.VolumeType.DRIVE:
-      this.showLowDownloadsSpaceWarning_(false);
-      break;
-    default:
-      // If the current file system is neither the DOWNLOAD nor the DRIVE,
-      // just hide the warning.
-      this.showLowDownloadsSpaceWarning_(false);
-      this.showLowDriveSpaceWarning_(false);
-      return;
-  }
-
-  // If not mounted correctly, then do not continue.
-  if (!volume.fileSystem) {
-    return;
-  }
-
-  chrome.fileManagerPrivate.getSizeStats(volume.volumeId, sizeStats => {
-    const currentVolume = this.volumeManager_.getVolumeInfo(
-        assert(this.directoryModel_.getCurrentDirEntry()));
-    if (volume !== currentVolume) {
-      // This happens when the current directory is moved during requesting
-      // the file system size. Just ignore it.
-      return;
-    }
-    // sizeStats is undefined, if some error occurs.
-    if (!sizeStats || sizeStats.totalSize == 0) {
-      return;
-    }
-
-    if (volume.volumeType === VolumeManagerCommon.VolumeType.DOWNLOADS) {
-      // Show the warning banner when the available space is less than 1GB.
-      this.showLowDownloadsSpaceWarning_(
-          sizeStats.remainingSize < DOWNLOADS_SPACE_WARNING_THRESHOLD_SIZE);
-    } else {
-      // Show the warning banner when the available space ls less than 10%.
-      const remainingRatio = sizeStats.remainingSize / sizeStats.totalSize;
-      this.showLowDriveSpaceWarning_(
-          remainingRatio < DRIVE_SPACE_WARNING_THRESHOLD_RATIO, sizeStats);
-    }
-  });
-};
-
-/**
- * removes the Drive Welcome banner.
- * @private
- */
-Banners.prototype.cleanupWelcomeBanner_ = function() {
-  this.showWelcomeBanner_('none');
-};
-
-/**
- * Notifies the file manager what layout must be recalculated.
- * @param {number} delay In milliseconds.
- * @private
- */
-Banners.prototype.requestRelayout_ = function(delay) {
-  const self = this;
-  setTimeout(() => {
-    cr.dispatchSimpleEvent(self, 'relayout');
-  }, delay);
-};
-
-/**
- * Show or hide the "Low disk space" warning.
- * @param {boolean} show True if the box need to be shown.
- * @private
- */
-Banners.prototype.showLowDownloadsSpaceWarning_ = function(show) {
-  const box = this.document_.querySelector('.downloads-warning');
-
-  if (box.hidden == !show) {
-    return;
-  }
-
-  if (this.downloadsWarningDismissedTime_) {
-    if (Date.now() - this.downloadsWarningDismissedTime_ <
-        DOWNLOADS_SPACE_WARNING_DISMISS_DURATION) {
-      show = false;
-    }
-  }
-
-  box.textContent = '';
-  if (show) {
-    const icon = this.document_.createElement('div');
-    icon.className = 'warning-icon';
-    const message = this.document_.createElement('div');
-    message.className = 'warning-message';
-    message.innerHTML = util.htmlUnescape(str('DOWNLOADS_DIRECTORY_WARNING'));
-    box.appendChild(icon);
-    box.appendChild(message);
-    box.querySelector('a').addEventListener('click', e => {
-      util.visitURL(str('DOWNLOADS_LOW_SPACE_WARNING_HELP_URL'));
+    // Authentication failed banner.
+    this.authFailedBanner_ =
+        this.document_.querySelector('#drive-auth-failed-warning');
+    const authFailedText = this.authFailedBanner_.querySelector('.drive-text');
+    authFailedText.innerHTML = util.htmlUnescape(str('DRIVE_NOT_REACHED'));
+    authFailedText.querySelector('a').addEventListener('click', e => {
+      chrome.fileManagerPrivate.logoutUserForReauthentication();
       e.preventDefault();
     });
-
-    const close = this.document_.createElement('div');
-    close.className = 'banner-close';
-    close.tabIndex = 0;
-    box.appendChild(close);
-    close.addEventListener('click', () => {
-      const values = {};
-      values[DOWNLOADS_WARNING_DISMISSED_KEY] = Date.now();
-      chrome.storage.local.set(values);
-      box.hidden = true;
-      // We explicitly mark the banner-close element as hidden as due to the
-      // use of position absolute in it's layout it does not get hidden by
-      // hiding it's parent.
-      close.hidden = true;
-      this.requestRelayout_(100);
-    });
-  }
-
-  box.hidden = !show;
-  this.requestRelayout_(100);
-};
-
-/**
- * Creates contents for the DRIVE unmounted panel.
- * @private
- */
-Banners.prototype.ensureDriveUnmountedPanelInitialized_ = function() {
-  const panel = this.unmountedPanel_;
-  if (panel.firstElementChild) {
-    return;
+    this.maybeShowAuthFailBanner_();
   }
 
   /**
-   * Creates an element using given parameters.
-   * @param {!Element} parent Parent element of the new element.
-   * @param {string} tag Tag of the new element.
-   * @param {string} className Class name of the new element.
-   * @param {string=} opt_textContent Text content of the new element.
-   * @return {!Element} The newly created element.
+   * @param {number} value How many times the Drive Welcome header banner
+   * has shown.
+   * @private
    */
-  const create = (parent, tag, className, opt_textContent) => {
-    const div = panel.ownerDocument.createElement(tag);
-    div.className = className;
-    div.textContent = opt_textContent || '';
-    parent.appendChild(div);
-    return div;
-  };
-
-  create(panel, 'div', 'error', str('DRIVE_CANNOT_REACH'));
-
-  const learnMore =
-      create(panel, 'a', 'learn-more plain-link', str('DRIVE_LEARN_MORE'));
-  learnMore.href = str('GOOGLE_DRIVE_ERROR_HELP_URL');
-  learnMore.target = '_blank';
-};
-
-/**
- * Called when volume info list is updated.
- * @param {Event} event Splice event data on volume info list.
- * @private
- */
-Banners.prototype.onVolumeInfoListSplice_ = function(event) {
-  const isDriveVolume = volumeInfo => {
-    return volumeInfo.volumeType === VolumeManagerCommon.VolumeType.DRIVE;
-  };
-  if (event.removed.some(isDriveVolume) || event.added.some(isDriveVolume)) {
-    this.updateDriveUnmountedPanel_();
+  setWelcomeHeaderCounter_(value) {
+    const values = {};
+    values[WELCOME_HEADER_COUNTER_KEY] = value;
+    chrome.storage.local.set(values);
   }
-};
 
-/**
- * Shows the panel when current directory is DRIVE and it's unmounted.
- * Hides it otherwise. The panel shows an error message if it failed.
- * @private
- */
-Banners.prototype.updateDriveUnmountedPanel_ = function() {
-  const node = this.document_.body;
-  if (this.isOnCurrentProfileDrive()) {
-    const driveVolume = this.volumeManager_.getCurrentProfileVolumeInfo(
-        VolumeManagerCommon.VolumeType.DRIVE);
-    if (driveVolume) {
-      if (driveVolume.error) {
-        this.ensureDriveUnmountedPanelInitialized_();
-        this.unmountedPanel_.classList.add('retry-enabled');
-        node.setAttribute('drive', 'error');
+  /**
+   * @param {number} value How many times the low space warning has dismissed.
+   * @private
+   */
+  setWarningDismissedCounter_(value) {
+    const values = {};
+    values[DRIVE_WARNING_DISMISSED_KEY] = value;
+    chrome.storage.local.set(values);
+  }
+
+  /**
+   * chrome.storage.onChanged event handler.
+   * @param {Object<Object>} changes Changes values.
+   * @param {string} areaName "local" or "sync".
+   * @private
+   */
+  onStorageChange_(changes, areaName) {
+    if (areaName == 'local' && WELCOME_HEADER_COUNTER_KEY in changes) {
+      this.welcomeHeaderCounter_ = changes[WELCOME_HEADER_COUNTER_KEY].newValue;
+    }
+    if (areaName == 'local' && DRIVE_WARNING_DISMISSED_KEY in changes) {
+      this.warningDismissedCounter_ =
+          changes[DRIVE_WARNING_DISMISSED_KEY].newValue;
+    }
+    if (areaName == 'local' && DOWNLOADS_WARNING_DISMISSED_KEY in changes) {
+      this.downloadsWarningDismissedTime_ =
+          changes[DOWNLOADS_WARNING_DISMISSED_KEY].newValue;
+    }
+  }
+
+  /**
+   * Invoked when the drive connection status is change in the volume manager.
+   * @private
+   */
+  onDriveConnectionChanged_() {
+    this.maybeShowAuthFailBanner_();
+  }
+
+  /**
+   * @param {string} type 'none'|'page'|'header'.
+   * @param {string} messageId Resource ID of the message.
+   * @private
+   */
+  prepareAndShowWelcomeBanner_(type, messageId) {
+    if (!this.showWelcome_) {
+      return;
+    }
+
+    this.showWelcomeBanner_(type);
+
+    const container =
+        queryRequiredElement('.drive-welcome.' + type, this.document_);
+    if (container.firstElementChild) {
+      return;
+    }  // Do not re-create.
+
+    if (!this.document_.querySelector('link[drive-welcome-style]')) {
+      const style = this.document_.createElement('link');
+      style.rel = 'stylesheet';
+      style.href = constants.DRIVE_WELCOME_CSS;
+      style.setAttribute('drive-welcome-style', '');
+      this.document_.head.appendChild(style);
+    }
+
+    const wrapper = util.createChild(container, 'drive-welcome-wrapper');
+    util.createChild(wrapper, 'drive-welcome-icon');
+
+    if (type === 'header') {
+      util.createChild(wrapper, 'banner-cloud-bg');
+      util.createChild(wrapper, 'banner-people');
+    }
+
+    const close = util.createChild(wrapper, 'banner-close');
+    close.addEventListener('click', this.closeWelcomeBanner_.bind(this));
+
+    const message = util.createChild(wrapper, 'drive-welcome-message');
+
+    const title = util.createChild(message, 'drive-welcome-title');
+
+    const text = util.createChild(message, 'drive-welcome-text');
+    text.innerHTML = str(messageId);
+
+    const links = util.createChild(message, 'drive-welcome-links');
+
+    title.textContent = str('DRIVE_WELCOME_TITLE');
+    const more = util.createChild(links, 'plain-link', 'a');
+    more.textContent = str('DRIVE_LEARN_MORE');
+    more.href = str('GOOGLE_DRIVE_OVERVIEW_URL');
+    more.tabIndex = 21;  // See: go/filesapp-tabindex.
+    more.id = 'drive-welcome-link';
+    more.target = '_blank';
+
+    const dismiss = util.createChild(links, 'plain-link');
+    dismiss.classList.add('drive-welcome-dismiss');
+    dismiss.textContent = str('DRIVE_WELCOME_DISMISS');
+    dismiss.addEventListener('click', this.closeWelcomeBanner_.bind(this));
+
+    this.previousDirWasOnDrive_ = false;
+  }
+
+  /**
+   * Show or hide the "Low Google Drive space" warning.
+   * @param {boolean} show True if the box need to be shown.
+   * @param {Object=} opt_sizeStats Size statistics. Should be defined when
+   *     showing the warning.
+   * @private
+   */
+  showLowDriveSpaceWarning_(show, opt_sizeStats) {
+    const box = this.document_.querySelector('#volume-space-warning');
+
+    // Avoid showing two banners.
+    // TODO(kaznacheev): Unify the low space warning and the promo header.
+    if (show) {
+      this.cleanupWelcomeBanner_();
+    }
+
+    if (box.hidden == !show) {
+      return;
+    }
+
+    if (this.warningDismissedCounter_) {
+      if (opt_sizeStats &&
+          // Quota had not changed
+          this.warningDismissedCounter_ == opt_sizeStats.totalSize &&
+          opt_sizeStats.remainingSize / opt_sizeStats.totalSize < 0.15) {
+        // Since the last dismissal decision the quota has not changed AND
+        // the user did not free up significant space. Obey the dismissal.
+        show = false;
       } else {
-        node.setAttribute('drive', 'mounted');
+        // Forget the dismissal. Warning will be shown again.
+        this.setWarningDismissedCounter_(0);
+      }
+    }
+
+    box.textContent = '';
+    if (show && opt_sizeStats) {
+      const icon = this.document_.createElement('div');
+      icon.className = 'drive-icon';
+      box.appendChild(icon);
+
+      const text = this.document_.createElement('div');
+      text.className = 'drive-text';
+      text.textContent = strf(
+          'DRIVE_SPACE_AVAILABLE_LONG',
+          util.bytesToString(opt_sizeStats.remainingSize));
+      box.appendChild(text);
+
+      const link = this.document_.createElement('a');
+      link.href = str('GOOGLE_DRIVE_BUY_STORAGE_URL');
+      link.target = '_blank';
+      const button = this.document_.createElement('button');
+      button.className = 'imitate-paper-button';
+      button.textContent = str('DRIVE_BUY_MORE_SPACE_LINK');
+      link.appendChild(button);
+      box.appendChild(link);
+
+      const close = this.document_.createElement('div');
+      close.className = 'banner-close';
+      box.appendChild(close);
+      close.addEventListener('click', ((total) => {
+                                        const values = {};
+                                        values[DRIVE_WARNING_DISMISSED_KEY] =
+                                            total;
+                                        chrome.storage.local.set(values);
+                                        box.hidden = true;
+                                        this.requestRelayout_(100);
+                                      }).bind(null, opt_sizeStats.totalSize));
+    }
+
+    if (box.hidden != !show) {
+      box.hidden = !show;
+      this.requestRelayout_(100);
+    }
+  }
+
+  /**
+   * Closes the Drive Welcome banner.
+   * @private
+   */
+  closeWelcomeBanner_() {
+    this.cleanupWelcomeBanner_();
+    // Stop showing the welcome banner.
+    this.setWelcomeHeaderCounter_(WELCOME_HEADER_COUNTER_LIMIT);
+  }
+
+  /**
+   * Shows or hides the welcome banner for drive.
+   * @private
+   */
+  checkSpaceAndMaybeShowWelcomeBanner_() {
+    this.ready_.then(() => {
+      if (!this.isOnCurrentProfileDrive()) {
+        // We are not on the drive file system. Do not show (close) the welcome
+        // banner.
+        this.cleanupWelcomeBanner_();
+        this.previousDirWasOnDrive_ = false;
+        return;
+      }
+
+      const driveVolume = this.volumeManager_.getCurrentProfileVolumeInfo(
+          VolumeManagerCommon.VolumeType.DRIVE);
+      if (this.welcomeHeaderCounter_ >= WELCOME_HEADER_COUNTER_LIMIT ||
+          !driveVolume || driveVolume.error) {
+        // The banner is already shown enough times or the drive FS is not
+        // mounted. So, do nothing here.
+        return;
+      }
+
+      this.maybeShowWelcomeBanner_();
+    });
+  }
+
+  /**
+   * Decides which banner should be shown, and show it. This method is designed
+   * to be called only from checkSpaceAndMaybeShowWelcomeBanner_.
+   * @private
+   */
+  maybeShowWelcomeBanner_() {
+    this.ready_.then(() => {
+      if (this.directoryModel_.getFileList().length == 0 &&
+          this.welcomeHeaderCounter_ == 0) {
+        // Only show the full page banner if the header banner was never shown.
+        // Do not increment the counter.
+        // The timeout below is required because sometimes another
+        // 'rescan-completed' event arrives shortly with non-empty file list.
+        setTimeout(() => {
+          if (this.isOnCurrentProfileDrive() &&
+              this.welcomeHeaderCounter_ == 0) {
+            this.prepareAndShowWelcomeBanner_(
+                'page', 'DRIVE_WELCOME_TEXT_LONG');
+          }
+        }, 2000);
+      } else {
+        // We do not want to increment the counter when the user navigates
+        // between different directories on Drive, but we increment the counter
+        // once anyway to prevent the full page banner from showing.
+        if (!this.previousDirWasOnDrive_ || this.welcomeHeaderCounter_ == 0) {
+          this.setWelcomeHeaderCounter_(this.welcomeHeaderCounter_ + 1);
+          this.prepareAndShowWelcomeBanner_(
+              'header', 'DRIVE_WELCOME_TEXT_SHORT');
+        }
+      }
+      this.previousDirWasOnDrive_ = true;
+    });
+  }
+
+  /**
+   * @return {boolean} True if current directory is on Drive root of current
+   * profile.
+   */
+  isOnCurrentProfileDrive() {
+    const entry = this.directoryModel_.getCurrentDirEntry();
+    if (!entry || util.isFakeEntry(entry)) {
+      return false;
+    }
+    const locationInfo = this.volumeManager_.getLocationInfo(entry);
+    if (!locationInfo) {
+      return false;
+    }
+    return locationInfo.rootType === VolumeManagerCommon.RootType.DRIVE &&
+        locationInfo.volumeInfo.profile.isCurrentProfile;
+  }
+
+  /**
+   * Shows the Drive Welcome banner.
+   * @param {string} type 'page'|'head'|'none'.
+   * @private
+   */
+  showWelcomeBanner_(type) {
+    const container = this.document_.querySelector('.dialog-container');
+    if (container.getAttribute('drive-welcome') != type) {
+      container.setAttribute('drive-welcome', type);
+      this.requestRelayout_(200);  // Resize only after the animation is done.
+    }
+  }
+
+  /**
+   * Update the UI when the current directory changes.
+   *
+   * @param {Event} event The directory-changed event.
+   * @private
+   */
+  onDirectoryChanged_(event) {
+    const rootVolume = this.volumeManager_.getVolumeInfo(event.newDirEntry);
+    if (!rootVolume) {
+      return;
+    }
+    const previousRootVolume = event.previousDirEntry ?
+        this.volumeManager_.getVolumeInfo(event.previousDirEntry) :
+        null;
+
+    // Show (or hide) the low space warning.
+    this.maybeShowLowSpaceWarning_(rootVolume);
+
+    // Add or remove listener to show low space warning, if necessary.
+    const isLowSpaceWarningTarget = this.isLowSpaceWarningTarget_(rootVolume);
+    if (isLowSpaceWarningTarget !==
+        this.isLowSpaceWarningTarget_(previousRootVolume)) {
+      if (isLowSpaceWarningTarget) {
+        chrome.fileManagerPrivate.onDirectoryChanged.addListener(
+            this.privateOnDirectoryChangedBound_);
+      } else {
+        chrome.fileManagerPrivate.onDirectoryChanged.removeListener(
+            this.privateOnDirectoryChangedBound_);
+      }
+    }
+
+    if (!this.isOnCurrentProfileDrive()) {
+      this.cleanupWelcomeBanner_();
+      this.authFailedBanner_.hidden = true;
+    }
+
+    this.updateDriveUnmountedPanel_();
+    if (this.isOnCurrentProfileDrive()) {
+      this.unmountedPanel_.classList.remove('retry-enabled');
+      this.maybeShowAuthFailBanner_();
+    }
+  }
+
+  /**
+   * @param {VolumeInfo} volumeInfo Volume info to be checked.
+   * @return {boolean} true if the file system specified by |root| is a target
+   *     to show low space warning. Otherwise false.
+   * @private
+   */
+  isLowSpaceWarningTarget_(volumeInfo) {
+    if (!volumeInfo) {
+      return false;
+    }
+    return volumeInfo.profile.isCurrentProfile &&
+        (volumeInfo.volumeType === VolumeManagerCommon.VolumeType.DOWNLOADS ||
+         volumeInfo.volumeType === VolumeManagerCommon.VolumeType.DRIVE);
+  }
+
+  /**
+   * Callback which is invoked when the file system has been changed.
+   * @param {Object} event chrome.fileManagerPrivate.onDirectoryChanged event.
+   * @private
+   */
+  privateOnDirectoryChanged_(event) {
+    const currentDirEntry = this.directoryModel_.getCurrentDirEntry();
+    if (!currentDirEntry) {
+      return;
+    }
+    const currentVolume = this.volumeManager_.getVolumeInfo(currentDirEntry);
+    if (!currentVolume) {
+      return;
+    }
+    const eventVolume = this.volumeManager_.getVolumeInfo(event.entry);
+    if (currentVolume === eventVolume) {
+      // The file system we are currently on is changed.
+      // So, check the free space.
+      this.maybeShowLowSpaceWarning_(currentVolume);
+    }
+  }
+
+  /**
+   * Shows or hides the low space warning.
+   * @param {VolumeInfo} volume Type of volume, which we are interested in.
+   * @private
+   */
+  maybeShowLowSpaceWarning_(volume) {
+    // TODO(kaznacheev): Unify the two low space warning.
+    switch (volume.volumeType) {
+      case VolumeManagerCommon.VolumeType.DOWNLOADS:
+        this.showLowDriveSpaceWarning_(false);
+        break;
+      case VolumeManagerCommon.VolumeType.DRIVE:
+        this.showLowDownloadsSpaceWarning_(false);
+        break;
+      default:
+        // If the current file system is neither the DOWNLOAD nor the DRIVE,
+        // just hide the warning.
+        this.showLowDownloadsSpaceWarning_(false);
+        this.showLowDriveSpaceWarning_(false);
+        return;
+    }
+
+    // If not mounted correctly, then do not continue.
+    if (!volume.fileSystem) {
+      return;
+    }
+
+    chrome.fileManagerPrivate.getSizeStats(volume.volumeId, sizeStats => {
+      const currentVolume = this.volumeManager_.getVolumeInfo(
+          assert(this.directoryModel_.getCurrentDirEntry()));
+      if (volume !== currentVolume) {
+        // This happens when the current directory is moved during requesting
+        // the file system size. Just ignore it.
+        return;
+      }
+      // sizeStats is undefined, if some error occurs.
+      if (!sizeStats || sizeStats.totalSize == 0) {
+        return;
+      }
+
+      if (volume.volumeType === VolumeManagerCommon.VolumeType.DOWNLOADS) {
+        // Show the warning banner when the available space is less than 1GB.
+        this.showLowDownloadsSpaceWarning_(
+            sizeStats.remainingSize < DOWNLOADS_SPACE_WARNING_THRESHOLD_SIZE);
+      } else {
+        // Show the warning banner when the available space ls less than 10%.
+        const remainingRatio = sizeStats.remainingSize / sizeStats.totalSize;
+        this.showLowDriveSpaceWarning_(
+            remainingRatio < DRIVE_SPACE_WARNING_THRESHOLD_RATIO, sizeStats);
+      }
+    });
+  }
+
+  /**
+   * removes the Drive Welcome banner.
+   * @private
+   */
+  cleanupWelcomeBanner_() {
+    this.showWelcomeBanner_('none');
+  }
+
+  /**
+   * Notifies the file manager what layout must be recalculated.
+   * @param {number} delay In milliseconds.
+   * @private
+   */
+  requestRelayout_(delay) {
+    const self = this;
+    setTimeout(() => {
+      cr.dispatchSimpleEvent(self, 'relayout');
+    }, delay);
+  }
+
+  /**
+   * Show or hide the "Low disk space" warning.
+   * @param {boolean} show True if the box need to be shown.
+   * @private
+   */
+  showLowDownloadsSpaceWarning_(show) {
+    const box = this.document_.querySelector('.downloads-warning');
+
+    if (box.hidden == !show) {
+      return;
+    }
+
+    if (this.downloadsWarningDismissedTime_) {
+      if (Date.now() - this.downloadsWarningDismissedTime_ <
+          DOWNLOADS_SPACE_WARNING_DISMISS_DURATION) {
+        show = false;
+      }
+    }
+
+    box.textContent = '';
+    if (show) {
+      const icon = this.document_.createElement('div');
+      icon.className = 'warning-icon';
+      const message = this.document_.createElement('div');
+      message.className = 'warning-message';
+      message.innerHTML = util.htmlUnescape(str('DOWNLOADS_DIRECTORY_WARNING'));
+      box.appendChild(icon);
+      box.appendChild(message);
+      box.querySelector('a').addEventListener('click', e => {
+        util.visitURL(str('DOWNLOADS_LOW_SPACE_WARNING_HELP_URL'));
+        e.preventDefault();
+      });
+
+      const close = this.document_.createElement('div');
+      close.className = 'banner-close';
+      close.tabIndex = 0;
+      box.appendChild(close);
+      close.addEventListener('click', () => {
+        const values = {};
+        values[DOWNLOADS_WARNING_DISMISSED_KEY] = Date.now();
+        chrome.storage.local.set(values);
+        box.hidden = true;
+        // We explicitly mark the banner-close element as hidden as due to the
+        // use of position absolute in it's layout it does not get hidden by
+        // hiding it's parent.
+        close.hidden = true;
+        this.requestRelayout_(100);
+      });
+    }
+
+    box.hidden = !show;
+    this.requestRelayout_(100);
+  }
+
+  /**
+   * Creates contents for the DRIVE unmounted panel.
+   * @private
+   */
+  ensureDriveUnmountedPanelInitialized_() {
+    const panel = this.unmountedPanel_;
+    if (panel.firstElementChild) {
+      return;
+    }
+
+    /**
+     * Creates an element using given parameters.
+     * @param {!Element} parent Parent element of the new element.
+     * @param {string} tag Tag of the new element.
+     * @param {string} className Class name of the new element.
+     * @param {string=} opt_textContent Text content of the new element.
+     * @return {!Element} The newly created element.
+     */
+    const create = (parent, tag, className, opt_textContent) => {
+      const div = panel.ownerDocument.createElement(tag);
+      div.className = className;
+      div.textContent = opt_textContent || '';
+      parent.appendChild(div);
+      return div;
+    };
+
+    create(panel, 'div', 'error', str('DRIVE_CANNOT_REACH'));
+
+    const learnMore =
+        create(panel, 'a', 'learn-more plain-link', str('DRIVE_LEARN_MORE'));
+    learnMore.href = str('GOOGLE_DRIVE_ERROR_HELP_URL');
+    learnMore.target = '_blank';
+  }
+
+  /**
+   * Called when volume info list is updated.
+   * @param {Event} event Splice event data on volume info list.
+   * @private
+   */
+  onVolumeInfoListSplice_(event) {
+    const isDriveVolume = volumeInfo => {
+      return volumeInfo.volumeType === VolumeManagerCommon.VolumeType.DRIVE;
+    };
+    if (event.removed.some(isDriveVolume) || event.added.some(isDriveVolume)) {
+      this.updateDriveUnmountedPanel_();
+    }
+  }
+
+  /**
+   * Shows the panel when current directory is DRIVE and it's unmounted.
+   * Hides it otherwise. The panel shows an error message if it failed.
+   * @private
+   */
+  updateDriveUnmountedPanel_() {
+    const node = this.document_.body;
+    if (this.isOnCurrentProfileDrive()) {
+      const driveVolume = this.volumeManager_.getCurrentProfileVolumeInfo(
+          VolumeManagerCommon.VolumeType.DRIVE);
+      if (driveVolume) {
+        if (driveVolume.error) {
+          this.ensureDriveUnmountedPanelInitialized_();
+          this.unmountedPanel_.classList.add('retry-enabled');
+          node.setAttribute('drive', 'error');
+        } else {
+          node.setAttribute('drive', 'mounted');
+        }
+      } else {
+        this.unmountedPanel_.classList.remove('retry-enabled');
+        node.setAttribute('drive', 'unmounted');
       }
     } else {
-      this.unmountedPanel_.classList.remove('retry-enabled');
-      node.setAttribute('drive', 'unmounted');
+      node.removeAttribute('drive');
     }
-  } else {
-    node.removeAttribute('drive');
   }
-};
 
-/**
- * Updates the visibility of Drive Connection Warning banner, retrieving the
- * current connection information.
- * @private
- */
-Banners.prototype.maybeShowAuthFailBanner_ = function() {
-  const connection = this.volumeManager_.getDriveConnectionState();
-  const showDriveNotReachedMessage = this.isOnCurrentProfileDrive() &&
-      connection.type == VolumeManagerCommon.DriveConnectionType.OFFLINE &&
-      connection.reason == VolumeManagerCommon.DriveConnectionReason.NOT_READY;
-  this.authFailedBanner_.hidden = !showDriveNotReachedMessage;
-};
+  /**
+   * Updates the visibility of Drive Connection Warning banner, retrieving the
+   * current connection information.
+   * @private
+   */
+  maybeShowAuthFailBanner_() {
+    const connection = this.volumeManager_.getDriveConnectionState();
+    const showDriveNotReachedMessage = this.isOnCurrentProfileDrive() &&
+        connection.type == VolumeManagerCommon.DriveConnectionType.OFFLINE &&
+        connection.reason ==
+            VolumeManagerCommon.DriveConnectionReason.NOT_READY;
+    this.authFailedBanner_.hidden = !showDriveNotReachedMessage;
+  }
+}
diff --git a/ui/file_manager/file_manager/foreground/js/ui/location_line.js b/ui/file_manager/file_manager/foreground/js/ui/location_line.js
index ac11e59..15fc8c9 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/location_line.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/location_line.js
@@ -4,408 +4,413 @@
 
 /**
  * Location line.
- *
- * @extends {cr.EventTarget}
- * @param {!Element} breadcrumbs Container element for breadcrumbs.
- * @param {!VolumeManager} volumeManager Volume manager.
- * @constructor
  */
-function LocationLine(breadcrumbs, volumeManager) {
-  this.breadcrumbs_ = breadcrumbs;
-  this.volumeManager_ = volumeManager;
-  this.entry_ = null;
-  this.components_ = [];
-}
+class LocationLine extends cr.EventTarget {
+  /**
+   * @param {!Element} breadcrumbs Container element for breadcrumbs.
+   * @param {!VolumeManager} volumeManager Volume manager.
+   */
+  constructor(breadcrumbs, volumeManager) {
+    super();
 
-/**
- * Extends cr.EventTarget.
- */
-LocationLine.prototype.__proto__ = cr.EventTarget.prototype;
-
-/**
- * Shows breadcrumbs. This operation is done without IO.
- *
- * @param {!Entry|!FakeEntry} entry Target entry or fake entry.
- */
-LocationLine.prototype.show = function(entry) {
-  if (entry === this.entry_) {
-    return;
+    this.breadcrumbs_ = breadcrumbs;
+    this.volumeManager_ = volumeManager;
+    this.entry_ = null;
+    this.components_ = [];
   }
 
-  this.update_(this.getComponents_(entry));
-};
-
-/**
- * Returns current path components built by the current directory entry.
- * @return {!Array<!LocationLine.PathComponent>} Current path components.
- */
-LocationLine.prototype.getCurrentPathComponents = function() {
-  return this.components_;
-};
-
-/**
- * Replace the root directory name at the end of a url.
- * The input, |url| is a displayRoot URL of a Drive volume like
- * filesystem:chrome-extension://....foo.com-hash/root
- * The output is like:
- * filesystem:chrome-extension://....foo.com-hash/other
- *
- * @param {string} url which points to a volume display root
- * @param {string} newRoot new root directory name
- * @return {string} new URL with the new root directory name
- * @private
- */
-LocationLine.prototype.replaceRootName_ = (url, newRoot) => {
-  return url.slice(0, url.length - '/root'.length) + newRoot;
-};
-
-/**
- * Get components for the path of entry.
- * @param {!Entry|!FilesAppEntry} entry An entry.
- * @return {!Array<!LocationLine.PathComponent>} Components.
- * @private
- */
-LocationLine.prototype.getComponents_ = function(entry) {
-  const components = [];
-  const locationInfo = this.volumeManager_.getLocationInfo(entry);
-
-  if (!locationInfo) {
-    return components;
-  }
-
-  if (util.isFakeEntry(entry)) {
-    components.push(new LocationLine.PathComponent(
-        util.getEntryLabel(locationInfo, entry), entry.toURL(),
-        /** @type {!FakeEntry} */ (entry)));
-    return components;
-  }
-
-  // Add volume component.
-  let displayRootUrl = locationInfo.volumeInfo.displayRoot.toURL();
-  let displayRootFullPath = locationInfo.volumeInfo.displayRoot.fullPath;
-
-  const prefixEntry = locationInfo.volumeInfo.prefixEntry;
-  if (prefixEntry) {
-    components.push(new LocationLine.PathComponent(
-        prefixEntry.name, prefixEntry.toURL(), prefixEntry));
-  }
-  if (locationInfo.rootType === VolumeManagerCommon.RootType.DRIVE_OTHER) {
-    // When target path is a shared directory, volume should be shared with me.
-    const match = entry.fullPath.match(/\/\.files-by-id\/\d+\//);
-    if (match) {
-      displayRootFullPath = match[0];
-    } else {
-      displayRootFullPath = '/other';
-    }
-    displayRootUrl = this.replaceRootName_(displayRootUrl, displayRootFullPath);
-    const sharedWithMeFakeEntry =
-        locationInfo.volumeInfo
-            .fakeEntries[VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME];
-    components.push(new LocationLine.PathComponent(
-        str('DRIVE_SHARED_WITH_ME_COLLECTION_LABEL'),
-        sharedWithMeFakeEntry.toURL(), sharedWithMeFakeEntry));
-  } else if (
-      locationInfo.rootType === VolumeManagerCommon.RootType.SHARED_DRIVE) {
-    displayRootUrl = this.replaceRootName_(
-        displayRootUrl, VolumeManagerCommon.SHARED_DRIVES_DIRECTORY_PATH);
-    components.push(new LocationLine.PathComponent(
-        util.getRootTypeLabel(locationInfo), displayRootUrl));
-  } else if (locationInfo.rootType === VolumeManagerCommon.RootType.COMPUTER) {
-    displayRootUrl = this.replaceRootName_(
-        displayRootUrl, VolumeManagerCommon.COMPUTERS_DIRECTORY_PATH);
-    components.push(new LocationLine.PathComponent(
-        util.getRootTypeLabel(locationInfo), displayRootUrl));
-  } else {
-    components.push(new LocationLine.PathComponent(
-        util.getRootTypeLabel(locationInfo), displayRootUrl));
-  }
-
-  // Get relative path to display root (e.g. /root/foo/bar -> foo/bar).
-  let relativePath = entry.fullPath.slice(displayRootFullPath.length);
-  if (entry.fullPath.startsWith(
-          VolumeManagerCommon.SHARED_DRIVES_DIRECTORY_PATH)) {
-    relativePath = entry.fullPath.slice(
-        VolumeManagerCommon.SHARED_DRIVES_DIRECTORY_PATH.length);
-  } else if (entry.fullPath.startsWith(
-                 VolumeManagerCommon.COMPUTERS_DIRECTORY_PATH)) {
-    relativePath = entry.fullPath.slice(
-        VolumeManagerCommon.COMPUTERS_DIRECTORY_PATH.length);
-  }
-  if (relativePath.indexOf('/') === 0) {
-    relativePath = relativePath.slice(1);
-  }
-  if (relativePath.length === 0) {
-    return components;
-  }
-
-  // currentUrl should be without trailing slash.
-  let currentUrl = /^.+\/$/.test(displayRootUrl) ?
-      displayRootUrl.slice(0, displayRootUrl.length - 1) :
-      displayRootUrl;
-
-  // Add directory components to the target path.
-  const paths = relativePath.split('/');
-  for (let i = 0; i < paths.length; i++) {
-    currentUrl += '/' + encodeURIComponent(paths[i]);
-    components.push(new LocationLine.PathComponent(paths[i], currentUrl));
-  }
-
-  return components;
-};
-
-/**
- * Updates the breadcrumb display.
- * @param {!Array<!LocationLine.PathComponent>} components Components to the
- *     target path.
- * @private
- */
-LocationLine.prototype.update_ = function(components) {
-  this.components_ = components;
-
-  // Make the new breadcrumbs temporarily.
-  const newBreadcrumbs = document.createElement('div');
-  for (let i = 0; i < components.length; i++) {
-    // Add a component.
-    const component = components[i];
-    const button = document.createElement('button');
-    button.id = 'breadcrumb-path-' + i;
-    button.classList.add(
-        'breadcrumb-path', 'entry-name', 'imitate-paper-button');
-    const nameElement = document.createElement('div');
-    nameElement.classList.add('name');
-    nameElement.textContent = component.name;
-    button.appendChild(nameElement);
-    button.addEventListener('click', this.onClick_.bind(this, i));
-    newBreadcrumbs.appendChild(button);
-
-    const ripple = document.createElement('paper-ripple');
-    ripple.classList.add('recenteringTouch');
-    ripple.setAttribute('fit', '');
-    button.appendChild(ripple);
-
-    // If this is the last component, break here.
-    if (i === components.length - 1) {
-      break;
-    }
-
-    // Add a separator.
-    const separator = document.createElement('span');
-    separator.classList.add('separator');
-    newBreadcrumbs.appendChild(separator);
-  }
-
-  // Replace the shown breadcrumbs with the new one, keeping the DOMs for common
-  // prefix of the path.
-  // 1. Forward the references to the path element while in the common prefix.
-  let childOriginal = this.breadcrumbs_.firstChild;
-  let childNew = newBreadcrumbs.firstChild;
-  let cnt = 0;
-  while (childOriginal && childNew &&
-         childOriginal.textContent === childNew.textContent) {
-    childOriginal = childOriginal.nextSibling;
-    childNew = childNew.nextSibling;
-    cnt++;
-  }
-  // 2. Remove all elements in original breadcrumbs which are not in the common
-  // prefix.
-  while (childOriginal) {
-    const childToRemove = childOriginal;
-    childOriginal = childOriginal.nextSibling;
-    this.breadcrumbs_.removeChild(childToRemove);
-  }
-  // 3. Append new elements after the common prefix.
-  while (childNew) {
-    const childToAppend = childNew;
-    childNew = childNew.nextSibling;
-    this.breadcrumbs_.appendChild(childToAppend);
-  }
-  // 4. Reset the tab index and class 'breadcrumb-last'.
-  for (let el = this.breadcrumbs_.firstChild; el; el = el.nextSibling) {
-    if (el.classList.contains('breadcrumb-path')) {
-      const isLast = !el.nextSibling;
-      el.tabIndex = isLast ? -1 : 9;
-      el.classList.toggle('breadcrumb-last', isLast);
-    }
-  }
-
-  this.breadcrumbs_.hidden = false;
-  this.truncate();
-};
-
-/**
- * Updates breadcrumbs widths in order to truncate it properly.
- */
-LocationLine.prototype.truncate = function() {
-  if (!this.breadcrumbs_.firstChild) {
-    return;
-  }
-
-  // Assume style.width == clientWidth (items have no margins).
-
-  for (let item = this.breadcrumbs_.firstChild; item; item = item.nextSibling) {
-    item.removeAttribute('style');
-    item.removeAttribute('collapsed');
-    item.removeAttribute('hidden');
-  }
-
-  const containerWidth = this.breadcrumbs_.getBoundingClientRect().width;
-
-  let pathWidth = 0;
-  let currentWidth = 0;
-  let lastSeparator;
-  for (let item = this.breadcrumbs_.firstChild; item; item = item.nextSibling) {
-    if (item.className == 'separator') {
-      pathWidth += currentWidth;
-      currentWidth = item.getBoundingClientRect().width;
-      lastSeparator = item;
-    } else {
-      currentWidth += item.getBoundingClientRect().width;
-    }
-  }
-  if (pathWidth + currentWidth <= containerWidth) {
-    return;
-  }
-  if (!lastSeparator) {
-    this.breadcrumbs_.lastChild.style.width =
-        Math.min(currentWidth, containerWidth) + 'px';
-    return;
-  }
-  const lastCrumbSeparatorWidth = lastSeparator.getBoundingClientRect().width;
-  // Current directory name may occupy up to 70% of space or even more if the
-  // path is short.
-  let maxPathWidth =
-      Math.max(Math.round(containerWidth * 0.3), containerWidth - currentWidth);
-  maxPathWidth = Math.min(pathWidth, maxPathWidth);
-
-  const parentCrumb = lastSeparator.previousSibling;
-
-  // Pre-calculate the minimum width for crumbs.
-  parentCrumb.setAttribute('collapsed', '');
-  const minCrumbWidth = parentCrumb.getBoundingClientRect().width;
-  parentCrumb.removeAttribute('collapsed');
-
-  let collapsedWidth = 0;
-  if (parentCrumb &&
-      pathWidth - parentCrumb.getBoundingClientRect().width + minCrumbWidth >
-          maxPathWidth) {
-    // At least one crumb is hidden completely (or almost completely).
-    // Show sign of hidden crumbs like this:
-    // root > some di... > ... > current directory.
-    parentCrumb.setAttribute('collapsed', '');
-    collapsedWidth =
-        Math.min(maxPathWidth, parentCrumb.getBoundingClientRect().width);
-    maxPathWidth -= collapsedWidth;
-    if (parentCrumb.getBoundingClientRect().width != collapsedWidth) {
-      parentCrumb.style.width = collapsedWidth + 'px';
-    }
-
-    lastSeparator = parentCrumb.previousSibling;
-    if (!lastSeparator) {
+  /**
+   * Shows breadcrumbs. This operation is done without IO.
+   *
+   * @param {!Entry|!FakeEntry} entry Target entry or fake entry.
+   */
+  show(entry) {
+    if (entry === this.entry_) {
       return;
     }
-    collapsedWidth += lastSeparator.clientWidth;
-    maxPathWidth = Math.max(0, maxPathWidth - lastSeparator.clientWidth);
+
+    this.update_(this.getComponents_(entry));
   }
 
-  pathWidth = 0;
-  for (let item = this.breadcrumbs_.firstChild; item != lastSeparator;
-       item = item.nextSibling) {
-    // TODO(serya): Mixing access item.clientWidth and modifying style and
-    // attributes could cause multiple layout reflows.
-    if (pathWidth === maxPathWidth) {
-      item.setAttribute('hidden', '');
-    } else {
-      if (item.classList.contains('separator')) {
-        // If the current separator and the following crumb don't fit in the
-        // breadcrumbs area, hide remaining separators and crumbs.
-        if (pathWidth + item.getBoundingClientRect().width + minCrumbWidth >
-            maxPathWidth) {
-          item.setAttribute('hidden', '');
-          maxPathWidth = pathWidth;
-        } else {
-          pathWidth += item.getBoundingClientRect().width;
-        }
+  /**
+   * Returns current path components built by the current directory entry.
+   * @return {!Array<!LocationLine.PathComponent>} Current path components.
+   */
+  getCurrentPathComponents() {
+    return this.components_;
+  }
+
+  /**
+   * Replace the root directory name at the end of a url.
+   * The input, |url| is a displayRoot URL of a Drive volume like
+   * filesystem:chrome-extension://....foo.com-hash/root
+   * The output is like:
+   * filesystem:chrome-extension://....foo.com-hash/other
+   *
+   * @param {string} url which points to a volume display root
+   * @param {string} newRoot new root directory name
+   * @return {string} new URL with the new root directory name
+   * @private
+   */
+  replaceRootName_(url, newRoot) {
+    return url.slice(0, url.length - '/root'.length) + newRoot;
+  }
+
+  /**
+   * Get components for the path of entry.
+   * @param {!Entry|!FilesAppEntry} entry An entry.
+   * @return {!Array<!LocationLine.PathComponent>} Components.
+   * @private
+   */
+  getComponents_(entry) {
+    const components = [];
+    const locationInfo = this.volumeManager_.getLocationInfo(entry);
+
+    if (!locationInfo) {
+      return components;
+    }
+
+    if (util.isFakeEntry(entry)) {
+      components.push(new LocationLine.PathComponent(
+          util.getEntryLabel(locationInfo, entry), entry.toURL(),
+          /** @type {!FakeEntry} */ (entry)));
+      return components;
+    }
+
+    // Add volume component.
+    let displayRootUrl = locationInfo.volumeInfo.displayRoot.toURL();
+    let displayRootFullPath = locationInfo.volumeInfo.displayRoot.fullPath;
+
+    const prefixEntry = locationInfo.volumeInfo.prefixEntry;
+    if (prefixEntry) {
+      components.push(new LocationLine.PathComponent(
+          prefixEntry.name, prefixEntry.toURL(), prefixEntry));
+    }
+    if (locationInfo.rootType === VolumeManagerCommon.RootType.DRIVE_OTHER) {
+      // When target path is a shared directory, volume should be shared with
+      // me.
+      const match = entry.fullPath.match(/\/\.files-by-id\/\d+\//);
+      if (match) {
+        displayRootFullPath = match[0];
       } else {
-        // If the current crumb doesn't fully fit in the breadcrumbs area,
-        // shorten the crumb and hide remaining separators and crums.
-        if (pathWidth + item.getBoundingClientRect().width > maxPathWidth) {
-          item.style.width = (maxPathWidth - pathWidth) + 'px';
-          pathWidth = maxPathWidth;
+        displayRootFullPath = '/other';
+      }
+      displayRootUrl =
+          this.replaceRootName_(displayRootUrl, displayRootFullPath);
+      const sharedWithMeFakeEntry =
+          locationInfo.volumeInfo
+              .fakeEntries[VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME];
+      components.push(new LocationLine.PathComponent(
+          str('DRIVE_SHARED_WITH_ME_COLLECTION_LABEL'),
+          sharedWithMeFakeEntry.toURL(), sharedWithMeFakeEntry));
+    } else if (
+        locationInfo.rootType === VolumeManagerCommon.RootType.SHARED_DRIVE) {
+      displayRootUrl = this.replaceRootName_(
+          displayRootUrl, VolumeManagerCommon.SHARED_DRIVES_DIRECTORY_PATH);
+      components.push(new LocationLine.PathComponent(
+          util.getRootTypeLabel(locationInfo), displayRootUrl));
+    } else if (
+        locationInfo.rootType === VolumeManagerCommon.RootType.COMPUTER) {
+      displayRootUrl = this.replaceRootName_(
+          displayRootUrl, VolumeManagerCommon.COMPUTERS_DIRECTORY_PATH);
+      components.push(new LocationLine.PathComponent(
+          util.getRootTypeLabel(locationInfo), displayRootUrl));
+    } else {
+      components.push(new LocationLine.PathComponent(
+          util.getRootTypeLabel(locationInfo), displayRootUrl));
+    }
+
+    // Get relative path to display root (e.g. /root/foo/bar -> foo/bar).
+    let relativePath = entry.fullPath.slice(displayRootFullPath.length);
+    if (entry.fullPath.startsWith(
+            VolumeManagerCommon.SHARED_DRIVES_DIRECTORY_PATH)) {
+      relativePath = entry.fullPath.slice(
+          VolumeManagerCommon.SHARED_DRIVES_DIRECTORY_PATH.length);
+    } else if (entry.fullPath.startsWith(
+                   VolumeManagerCommon.COMPUTERS_DIRECTORY_PATH)) {
+      relativePath = entry.fullPath.slice(
+          VolumeManagerCommon.COMPUTERS_DIRECTORY_PATH.length);
+    }
+    if (relativePath.indexOf('/') === 0) {
+      relativePath = relativePath.slice(1);
+    }
+    if (relativePath.length === 0) {
+      return components;
+    }
+
+    // currentUrl should be without trailing slash.
+    let currentUrl = /^.+\/$/.test(displayRootUrl) ?
+        displayRootUrl.slice(0, displayRootUrl.length - 1) :
+        displayRootUrl;
+
+    // Add directory components to the target path.
+    const paths = relativePath.split('/');
+    for (let i = 0; i < paths.length; i++) {
+      currentUrl += '/' + encodeURIComponent(paths[i]);
+      components.push(new LocationLine.PathComponent(paths[i], currentUrl));
+    }
+
+    return components;
+  }
+
+  /**
+   * Updates the breadcrumb display.
+   * @param {!Array<!LocationLine.PathComponent>} components Components to the
+   *     target path.
+   * @private
+   */
+  update_(components) {
+    this.components_ = components;
+
+    // Make the new breadcrumbs temporarily.
+    const newBreadcrumbs = document.createElement('div');
+    for (let i = 0; i < components.length; i++) {
+      // Add a component.
+      const component = components[i];
+      const button = document.createElement('button');
+      button.id = 'breadcrumb-path-' + i;
+      button.classList.add(
+          'breadcrumb-path', 'entry-name', 'imitate-paper-button');
+      const nameElement = document.createElement('div');
+      nameElement.classList.add('name');
+      nameElement.textContent = component.name;
+      button.appendChild(nameElement);
+      button.addEventListener('click', this.onClick_.bind(this, i));
+      newBreadcrumbs.appendChild(button);
+
+      const ripple = document.createElement('paper-ripple');
+      ripple.classList.add('recenteringTouch');
+      ripple.setAttribute('fit', '');
+      button.appendChild(ripple);
+
+      // If this is the last component, break here.
+      if (i === components.length - 1) {
+        break;
+      }
+
+      // Add a separator.
+      const separator = document.createElement('span');
+      separator.classList.add('separator');
+      newBreadcrumbs.appendChild(separator);
+    }
+
+    // Replace the shown breadcrumbs with the new one, keeping the DOMs for
+    // common prefix of the path.
+    // 1. Forward the references to the path element while in the common prefix.
+    let childOriginal = this.breadcrumbs_.firstChild;
+    let childNew = newBreadcrumbs.firstChild;
+    let cnt = 0;
+    while (childOriginal && childNew &&
+           childOriginal.textContent === childNew.textContent) {
+      childOriginal = childOriginal.nextSibling;
+      childNew = childNew.nextSibling;
+      cnt++;
+    }
+    // 2. Remove all elements in original breadcrumbs which are not in the
+    // common prefix.
+    while (childOriginal) {
+      const childToRemove = childOriginal;
+      childOriginal = childOriginal.nextSibling;
+      this.breadcrumbs_.removeChild(childToRemove);
+    }
+    // 3. Append new elements after the common prefix.
+    while (childNew) {
+      const childToAppend = childNew;
+      childNew = childNew.nextSibling;
+      this.breadcrumbs_.appendChild(childToAppend);
+    }
+    // 4. Reset the tab index and class 'breadcrumb-last'.
+    for (let el = this.breadcrumbs_.firstChild; el; el = el.nextSibling) {
+      if (el.classList.contains('breadcrumb-path')) {
+        const isLast = !el.nextSibling;
+        el.tabIndex = isLast ? -1 : 9;
+        el.classList.toggle('breadcrumb-last', isLast);
+      }
+    }
+
+    this.breadcrumbs_.hidden = false;
+    this.truncate();
+  }
+
+  /**
+   * Updates breadcrumbs widths in order to truncate it properly.
+   */
+  truncate() {
+    if (!this.breadcrumbs_.firstChild) {
+      return;
+    }
+
+    // Assume style.width == clientWidth (items have no margins).
+
+    for (let item = this.breadcrumbs_.firstChild; item;
+         item = item.nextSibling) {
+      item.removeAttribute('style');
+      item.removeAttribute('collapsed');
+      item.removeAttribute('hidden');
+    }
+
+    const containerWidth = this.breadcrumbs_.getBoundingClientRect().width;
+
+    let pathWidth = 0;
+    let currentWidth = 0;
+    let lastSeparator;
+    for (let item = this.breadcrumbs_.firstChild; item;
+         item = item.nextSibling) {
+      if (item.className == 'separator') {
+        pathWidth += currentWidth;
+        currentWidth = item.getBoundingClientRect().width;
+        lastSeparator = item;
+      } else {
+        currentWidth += item.getBoundingClientRect().width;
+      }
+    }
+    if (pathWidth + currentWidth <= containerWidth) {
+      return;
+    }
+    if (!lastSeparator) {
+      this.breadcrumbs_.lastChild.style.width =
+          Math.min(currentWidth, containerWidth) + 'px';
+      return;
+    }
+    const lastCrumbSeparatorWidth = lastSeparator.getBoundingClientRect().width;
+    // Current directory name may occupy up to 70% of space or even more if the
+    // path is short.
+    let maxPathWidth = Math.max(
+        Math.round(containerWidth * 0.3), containerWidth - currentWidth);
+    maxPathWidth = Math.min(pathWidth, maxPathWidth);
+
+    const parentCrumb = lastSeparator.previousSibling;
+
+    // Pre-calculate the minimum width for crumbs.
+    parentCrumb.setAttribute('collapsed', '');
+    const minCrumbWidth = parentCrumb.getBoundingClientRect().width;
+    parentCrumb.removeAttribute('collapsed');
+
+    let collapsedWidth = 0;
+    if (parentCrumb &&
+        pathWidth - parentCrumb.getBoundingClientRect().width + minCrumbWidth >
+            maxPathWidth) {
+      // At least one crumb is hidden completely (or almost completely).
+      // Show sign of hidden crumbs like this:
+      // root > some di... > ... > current directory.
+      parentCrumb.setAttribute('collapsed', '');
+      collapsedWidth =
+          Math.min(maxPathWidth, parentCrumb.getBoundingClientRect().width);
+      maxPathWidth -= collapsedWidth;
+      if (parentCrumb.getBoundingClientRect().width != collapsedWidth) {
+        parentCrumb.style.width = collapsedWidth + 'px';
+      }
+
+      lastSeparator = parentCrumb.previousSibling;
+      if (!lastSeparator) {
+        return;
+      }
+      collapsedWidth += lastSeparator.clientWidth;
+      maxPathWidth = Math.max(0, maxPathWidth - lastSeparator.clientWidth);
+    }
+
+    pathWidth = 0;
+    for (let item = this.breadcrumbs_.firstChild; item != lastSeparator;
+         item = item.nextSibling) {
+      // TODO(serya): Mixing access item.clientWidth and modifying style and
+      // attributes could cause multiple layout reflows.
+      if (pathWidth === maxPathWidth) {
+        item.setAttribute('hidden', '');
+      } else {
+        if (item.classList.contains('separator')) {
+          // If the current separator and the following crumb don't fit in the
+          // breadcrumbs area, hide remaining separators and crumbs.
+          if (pathWidth + item.getBoundingClientRect().width + minCrumbWidth >
+              maxPathWidth) {
+            item.setAttribute('hidden', '');
+            maxPathWidth = pathWidth;
+          } else {
+            pathWidth += item.getBoundingClientRect().width;
+          }
         } else {
-          pathWidth += item.getBoundingClientRect().width;
+          // If the current crumb doesn't fully fit in the breadcrumbs area,
+          // shorten the crumb and hide remaining separators and crums.
+          if (pathWidth + item.getBoundingClientRect().width > maxPathWidth) {
+            item.style.width = (maxPathWidth - pathWidth) + 'px';
+            pathWidth = maxPathWidth;
+          } else {
+            pathWidth += item.getBoundingClientRect().width;
+          }
         }
       }
     }
+
+    currentWidth =
+        Math.min(currentWidth, containerWidth - pathWidth - collapsedWidth);
+    this.breadcrumbs_.lastChild.style.width =
+        (currentWidth - lastCrumbSeparatorWidth) + 'px';
   }
 
-  currentWidth =
-      Math.min(currentWidth, containerWidth - pathWidth - collapsedWidth);
-  this.breadcrumbs_.lastChild.style.width =
-      (currentWidth - lastCrumbSeparatorWidth) + 'px';
-};
-
-/**
- * Hide breadcrumbs div.
- */
-LocationLine.prototype.hide = function() {
-  this.breadcrumbs_.hidden = true;
-};
-
-/**
- * Execute an element.
- * @param {number} index The index of clicked path component.
- * @param {!Event} event The MouseEvent object.
- * @private
- */
-LocationLine.prototype.onClick_ = function(index, event) {
-  if (index >= this.components_.length - 1) {
-    return;
+  /**
+   * Hide breadcrumbs div.
+   */
+  hide() {
+    this.breadcrumbs_.hidden = true;
   }
 
-  // Remove 'focused' state from the clicked button.
-  let button = event.target;
-  while (button && !button.classList.contains('breadcrumb-path')) {
-    button = button.parentElement;
-  }
-  if (button) {
-    button.blur();
-  }
+  /**
+   * Execute an element.
+   * @param {number} index The index of clicked path component.
+   * @param {!Event} event The MouseEvent object.
+   * @private
+   */
+  onClick_(index, event) {
+    if (index >= this.components_.length - 1) {
+      return;
+    }
 
-  const pathComponent = this.components_[index];
-  pathComponent.resolveEntry().then(entry => {
-    const pathClickEvent = new Event('pathclick');
-    pathClickEvent.entry = entry;
-    this.dispatchEvent(pathClickEvent);
-  });
-  metrics.recordUserAction('ClickBreadcrumbs');
-};
+    // Remove 'focused' state from the clicked button.
+    let button = event.target;
+    while (button && !button.classList.contains('breadcrumb-path')) {
+      button = button.parentElement;
+    }
+    if (button) {
+      button.blur();
+    }
+
+    const pathComponent = this.components_[index];
+    pathComponent.resolveEntry().then(entry => {
+      const pathClickEvent = new Event('pathclick');
+      pathClickEvent.entry = entry;
+      this.dispatchEvent(pathClickEvent);
+    });
+    metrics.recordUserAction('ClickBreadcrumbs');
+  }
+}
 
 /**
  * Path component.
- * @param {string} name Name.
- * @param {string} url Url.
- * @param {FilesAppEntry=} opt_fakeEntry Fake entry should be set when
- *     this component represents fake entry.
- * @constructor
- * @struct
  */
-LocationLine.PathComponent = function(name, url, opt_fakeEntry) {
-  this.name = name;
-  this.url_ = url;
-  this.fakeEntry_ = opt_fakeEntry || null;
-};
+LocationLine.PathComponent = class {
+  /**
+   * @param {string} name Name.
+   * @param {string} url Url.
+   * @param {FilesAppEntry=} opt_fakeEntry Fake entry should be set when
+   *     this component represents fake entry.
+   */
+  constructor(name, url, opt_fakeEntry) {
+    this.name = name;
+    this.url_ = url;
+    this.fakeEntry_ = opt_fakeEntry || null;
+  }
 
-/**
- * Resolve an entry of the component.
- * @return {!Promise<!Entry|!FilesAppEntry>} A promise which is
- *     resolved with an entry.
- */
-LocationLine.PathComponent.prototype.resolveEntry = function() {
-  if (this.fakeEntry_) {
-    return /** @type {!Promise<!Entry|!FilesAppEntry>} */ (
-        Promise.resolve(this.fakeEntry_));
-  } else {
-    return new Promise(
-        window.webkitResolveLocalFileSystemURL.bind(null, this.url_));
+  /**
+   * Resolve an entry of the component.
+   * @return {!Promise<!Entry|!FilesAppEntry>} A promise which is
+   *     resolved with an entry.
+   */
+  resolveEntry() {
+    if (this.fakeEntry_) {
+      return /** @type {!Promise<!Entry|!FilesAppEntry>} */ (
+          Promise.resolve(this.fakeEntry_));
+    } else {
+      return new Promise(
+          window.webkitResolveLocalFileSystemURL.bind(null, this.url_));
+    }
   }
 };
diff --git a/ui/latency/BUILD.gn b/ui/latency/BUILD.gn
index 6afee94..b40ad83 100644
--- a/ui/latency/BUILD.gn
+++ b/ui/latency/BUILD.gn
@@ -7,6 +7,8 @@
 
 jumbo_source_set("latency") {
   sources = [
+    "average_lag_tracker.cc",
+    "average_lag_tracker.h",
     "fixed_point.cc",
     "fixed_point.h",
     "frame_metrics.cc",
@@ -49,6 +51,7 @@
 
 test("latency_unittests") {
   sources = [
+    "average_lag_tracker_unittest.cc",
     "fixed_point_unittest.cc",
     "frame_metrics_test_common.cc",
     "frame_metrics_test_common.h",
diff --git a/ui/latency/average_lag_tracker.cc b/ui/latency/average_lag_tracker.cc
new file mode 100644
index 0000000..e1419ba
--- /dev/null
+++ b/ui/latency/average_lag_tracker.cc
@@ -0,0 +1,159 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/latency/average_lag_tracker.h"
+
+#include "base/metrics/histogram_functions.h"
+
+namespace ui {
+
+AverageLagTracker::AverageLagTracker() = default;
+
+AverageLagTracker::~AverageLagTracker() = default;
+
+void AverageLagTracker::AddLatencyInFrame(
+    const ui::LatencyInfo& latency,
+    base::TimeTicks gpu_swap_begin_timestamp,
+    const std::string& scroll_name) {
+  base::TimeTicks event_timestamp;
+  bool found_component = latency.FindLatency(
+      ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_LAST_EVENT_COMPONENT,
+      &event_timestamp);
+  DCHECK(found_component);
+  // Skip if no event timestamp.
+  if (!found_component)
+    return;
+
+  if (scroll_name == "ScrollBegin") {
+    // Flush all unfinished frames.
+    while (!frame_lag_infos_.empty()) {
+      base::TimeTicks last_time =
+          std::max(last_event_timestamp_, last_finished_frame_time_);
+      frame_lag_infos_.front().lag_area +=
+          std::abs(last_event_accumulated_delta_ -
+                   frame_lag_infos_.front().rendered_accumulated_delta) *
+          (frame_lag_infos_.front().frame_time - last_time).InMillisecondsF();
+      // Record UMA when it's the last item in queue.
+      CalculateAndReportAverageLagUma(frame_lag_infos_.size() == 1);
+    }
+    // |accumulated_lag_| should be cleared/reset.
+    DCHECK(accumulated_lag_ == 0);
+
+    // Create ScrollBegin report, with report time equals to gpu swap time.
+    LagAreaInFrame first_frame(gpu_swap_begin_timestamp);
+    frame_lag_infos_.push_back(first_frame);
+
+    // Reset fields.
+    last_reported_time_ = event_timestamp;
+    last_finished_frame_time_ = event_timestamp;
+    last_event_accumulated_delta_ = 0;
+    last_rendered_accumulated_delta_ = 0;
+    is_begin_ = true;
+  } else if (scroll_name == "ScrollUpdate" &&
+             !last_event_timestamp_.is_null()) {
+    DCHECK((event_timestamp - last_event_timestamp_).InMilliseconds() >= 0);
+    // Pop all frames where frame_time <= event_timestamp.
+    while (!frame_lag_infos_.empty() &&
+           frame_lag_infos_.front().frame_time <= event_timestamp) {
+      base::TimeTicks front_time =
+          std::max(last_event_timestamp_, last_finished_frame_time_);
+      base::TimeTicks back_time = frame_lag_infos_.front().frame_time;
+      frame_lag_infos_.front().lag_area +=
+          LagBetween(front_time, back_time, latency, event_timestamp);
+
+      CalculateAndReportAverageLagUma();
+    }
+
+    // Initialize a new LagAreaInFrame when current_frame_time > frame_time.
+    if (frame_lag_infos_.empty() ||
+        gpu_swap_begin_timestamp > frame_lag_infos_.back().frame_time) {
+      LagAreaInFrame new_frame(gpu_swap_begin_timestamp,
+                               last_rendered_accumulated_delta_);
+      frame_lag_infos_.push_back(new_frame);
+    }
+
+    // last_frame_time <= event_timestamp < frame_time
+    if (!frame_lag_infos_.empty()) {
+      // The front element in queue (if any) must satisfy frame_time >
+      // event_timestamp, otherwise it would be popped in the while loop.
+      DCHECK(last_finished_frame_time_ <= event_timestamp &&
+             event_timestamp <= frame_lag_infos_.front().frame_time);
+      base::TimeTicks front_time =
+          std::max(last_finished_frame_time_, last_event_timestamp_);
+      base::TimeTicks back_time = event_timestamp;
+
+      frame_lag_infos_.front().lag_area +=
+          LagBetween(front_time, back_time, latency, event_timestamp);
+    }
+  }
+
+  last_event_timestamp_ = event_timestamp;
+  last_event_accumulated_delta_ += latency.scroll_update_delta();
+  last_rendered_accumulated_delta_ += latency.scroll_update_delta();
+}
+
+float AverageLagTracker::LagBetween(base::TimeTicks front_time,
+                                    base::TimeTicks back_time,
+                                    const LatencyInfo& latency,
+                                    base::TimeTicks event_timestamp) {
+  // In some tests, we use const event time. return 0 to avoid divided by 0.
+  if (event_timestamp == last_event_timestamp_)
+    return 0;
+
+  float front_delta =
+      (last_event_accumulated_delta_ +
+       (latency.scroll_update_delta() *
+        ((front_time - last_event_timestamp_).InMillisecondsF() /
+         (event_timestamp - last_event_timestamp_).InMillisecondsF()))) -
+      frame_lag_infos_.front().rendered_accumulated_delta;
+
+  float back_delta =
+      (last_event_accumulated_delta_ +
+       latency.scroll_update_delta() *
+
+           ((back_time - last_event_timestamp_).InMillisecondsF() /
+            (event_timestamp - last_event_timestamp_).InMillisecondsF())) -
+      frame_lag_infos_.front().rendered_accumulated_delta;
+
+  // Calculate the trapezoid area.
+  if (front_delta * back_delta >= 0) {
+    return 0.5f * std::abs(front_delta + back_delta) *
+           (back_time - front_time).InMillisecondsF();
+  }
+
+  // Corner case that rendered_accumulated_delta is in between of front_pos
+  // and back_pos.
+  return 0.5f *
+         std::abs((front_delta * front_delta + back_delta * back_delta) /
+                  (back_delta - front_delta)) *
+         (back_time - front_time).InMillisecondsF();
+}
+
+void AverageLagTracker::CalculateAndReportAverageLagUma(bool send_anyway) {
+  DCHECK(!frame_lag_infos_.empty());
+  const LagAreaInFrame& frame_lag = frame_lag_infos_.front();
+
+  DCHECK(frame_lag.lag_area >= 0.f);
+  accumulated_lag_ += frame_lag.lag_area;
+
+  // |send_anyway| is true when we are flush all remaining frames on next
+  // |ScrollBegin|. Otherwise record UMA when it's ScrollBegin, or when
+  // reaching the 1 second gap.
+  if (send_anyway || is_begin_ ||
+      (frame_lag.frame_time - last_reported_time_).InSecondsF() >= 1.0f) {
+    std::string scroll_name = is_begin_ ? "ScrollBegin" : "ScrollUpdate";
+    base::UmaHistogramCounts1000(
+        "Event.Latency." + scroll_name + ".Touch.AverageLag",
+        accumulated_lag_ /
+            (frame_lag.frame_time - last_reported_time_).InMillisecondsF());
+    accumulated_lag_ = 0;
+    last_reported_time_ = frame_lag.frame_time;
+    is_begin_ = false;
+  }
+
+  last_finished_frame_time_ = frame_lag.frame_time;
+  frame_lag_infos_.pop_front();
+}
+
+}  // namespace ui
diff --git a/ui/latency/average_lag_tracker.h b/ui/latency/average_lag_tracker.h
new file mode 100644
index 0000000..bcde09d7
--- /dev/null
+++ b/ui/latency/average_lag_tracker.h
@@ -0,0 +1,78 @@
+// Copyright 2019 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 UI_LATENCY_AVERAGE_LAG_TRACKER_H_
+#define UI_LATENCY_AVERAGE_LAG_TRACKER_H_
+
+#include <deque>
+
+#include "base/macros.h"
+#include "ui/latency/latency_info.h"
+
+namespace ui {
+
+// A class for reporting AverageLag metrics. See
+// https://docs.google.com/document/d/1e8NuzPblIv2B9bz01oSj40rmlse7_PHq5oFS3lqz6N4/
+class AverageLagTracker {
+ public:
+  AverageLagTracker();
+  ~AverageLagTracker();
+  void AddLatencyInFrame(const LatencyInfo& latency,
+                         base::TimeTicks gpu_swap_begin_timestamp,
+                         const std::string& scroll_name);
+
+ private:
+  typedef struct LagAreaInFrame {
+    LagAreaInFrame(base::TimeTicks time, float rendered_pos = 0)
+        : frame_time(time),
+          rendered_accumulated_delta(rendered_pos),
+          lag_area(0) {}
+    base::TimeTicks frame_time;
+    float rendered_accumulated_delta;
+    float lag_area;
+  } LagAreaInFrame;
+
+  // Calculate lag in 1 seconds intervals and report UMA.
+  void CalculateAndReportAverageLagUma(bool send_anyway = false);
+
+  // Helper function to calculate lag area between |front_time| to
+  // |back_time|.
+  float LagBetween(base::TimeTicks front_time,
+                   base::TimeTicks back_time,
+                   const LatencyInfo& latency,
+                   base::TimeTicks event_time);
+
+  std::deque<LagAreaInFrame> frame_lag_infos_;
+
+  // Last scroll event's timestamp in the sequence, reset on ScrollBegin.
+  base::TimeTicks last_event_timestamp_;
+  // Timestamp of the last frame popped from |frame_lag_infos_| queue.
+  base::TimeTicks last_finished_frame_time_;
+
+  // Accumulated scroll delta for actual scroll update events. Cumulated from
+  // latency.scroll_update_delta(). Reset on ScrollBegin.
+  float last_event_accumulated_delta_ = 0;
+  // Accumulated scroll delta got rendered on gpu swap. Cumulated from
+  // latency.predicted_scroll_update_delta(). It always has same value as
+  // |last_event_accumulated_delta_| when scroll prediction is disabled.
+  float last_rendered_accumulated_delta_ = 0;
+
+  // This keeps track of the last report_time when we report to UMA, so we can
+  // calculate the report's duration by current - last. Reset on ScrollBegin.
+  base::TimeTicks last_reported_time_;
+
+  // True if the first element of |frame_lag_infos_| is for ScrollBegin.
+  // For ScrollBegin, we don't wait for the 1 second interval but record the
+  // UMA once the frame is finished.
+  bool is_begin_ = false;
+
+  // Accumulated lag area in the 1 second intervals.
+  float accumulated_lag_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(AverageLagTracker);
+};
+
+}  // namespace ui
+
+#endif  // UI_LATENCY_AVERAGE_LAG_TRACKER_H_
diff --git a/ui/latency/average_lag_tracker_unittest.cc b/ui/latency/average_lag_tracker_unittest.cc
new file mode 100644
index 0000000..c4c34f1
--- /dev/null
+++ b/ui/latency/average_lag_tracker_unittest.cc
@@ -0,0 +1,248 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/latency/average_lag_tracker.h"
+
+#include "base/test/metrics/histogram_tester.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::Bucket;
+using testing::ElementsAre;
+
+namespace ui {
+namespace {
+
+class AverageLagTrackerTest : public testing::Test {
+ public:
+  AverageLagTrackerTest() { ResetHistograms(); }
+
+  void ResetHistograms() {
+    histogram_tester_.reset(new base::HistogramTester());
+  }
+
+  const base::HistogramTester& histogram_tester() { return *histogram_tester_; }
+
+  void SetUp() override {
+    average_lag_tracker_ = std::make_unique<AverageLagTracker>();
+  }
+
+  void SyntheticTouchScrollBeginLatencyInfo(base::TimeTicks event_time,
+                                            base::TimeTicks frame_time,
+                                            float delta) {
+    ui::LatencyInfo touch_latency(ui::SourceEventType::TOUCH);
+    touch_latency.set_scroll_update_delta(delta);
+    touch_latency.AddLatencyNumberWithTimestamp(
+        ui::INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL_COMPONENT,
+        event_time, 1);
+    touch_latency.AddLatencyNumberWithTimestamp(
+        ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_LAST_EVENT_COMPONENT, event_time,
+        1);
+    average_lag_tracker_->AddLatencyInFrame(touch_latency, frame_time,
+                                            "ScrollBegin");
+  }
+
+  void SyntheticTouchScrollUpdateLatencyInfo(base::TimeTicks event_time,
+                                             base::TimeTicks frame_time,
+                                             float delta) {
+    ui::LatencyInfo touch_latency(ui::SourceEventType::TOUCH);
+    touch_latency.set_scroll_update_delta(delta);
+    touch_latency.AddLatencyNumberWithTimestamp(
+        ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT, event_time,
+        1);
+    touch_latency.AddLatencyNumberWithTimestamp(
+        ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_LAST_EVENT_COMPONENT, event_time,
+        1);
+    average_lag_tracker_->AddLatencyInFrame(touch_latency, frame_time,
+                                            "ScrollUpdate");
+  }
+
+ protected:
+  std::unique_ptr<AverageLagTracker> average_lag_tracker_;
+
+  std::unique_ptr<base::HistogramTester> histogram_tester_;
+};
+
+base::TimeTicks MillisecondsToTimeTicks(float t_ms) {
+  return base::TimeTicks() + base::TimeDelta::FromMilliseconds(t_ms);
+}
+
+// Simulate a simple situation that events at every 10ms and start at t=15ms,
+// frame swaps at every 10ms too and start at t=20ms and test we record one
+// UMA for ScrollUpdate in one second.
+TEST_F(AverageLagTrackerTest, OneSecondInterval) {
+  base::TimeTicks event_time =
+      base::TimeTicks() + base::TimeDelta::FromMilliseconds(5);
+  base::TimeTicks frame_time =
+      base::TimeTicks() + base::TimeDelta::FromMilliseconds(10);
+  float scroll_delta = 10;
+
+  // ScrollBegin
+  event_time += base::TimeDelta::FromMilliseconds(10);  // 15ms
+  frame_time += base::TimeDelta::FromMilliseconds(10);  // 20ms
+  SyntheticTouchScrollBeginLatencyInfo(event_time, frame_time, scroll_delta);
+
+  // Send 101 ScrollUpdate events to verify that there is 1 AverageLag record
+  // per 1 second.
+  const int kUpdates = 101;
+  for (int i = 0; i < kUpdates; i++) {
+    event_time += base::TimeDelta::FromMilliseconds(10);
+    frame_time += base::TimeDelta::FromMilliseconds(10);
+    // First 50 has positive delta, others negetive delta.
+    const int sign = (i < kUpdates / 2) ? 1 : -1;
+    SyntheticTouchScrollUpdateLatencyInfo(event_time, frame_time,
+                                          sign * scroll_delta);
+  }
+
+  // ScrollBegin report_time is at 20ms, so the next ScrollUpdate report_time is
+  // at 1020ms. The last event_time that finish this report should be later than
+  // 1020ms.
+  EXPECT_EQ(event_time,
+            base::TimeTicks() + base::TimeDelta::FromMilliseconds(1025));
+  EXPECT_EQ(frame_time,
+            base::TimeTicks() + base::TimeDelta::FromMilliseconds(1030));
+
+  // ScrollBegin AverageLag are the area between the event original component
+  // (time=15ms, delta=10px) to the frame swap time (time=20ms, expect finger
+  // position at delta=15px). The AverageLag scaled to 1 second is
+  // (0.5*(10px+15px)*5ms)/5ms = 12.5px.
+  EXPECT_THAT(histogram_tester().GetAllSamples(
+                  "Event.Latency.ScrollBegin.Touch.AverageLag"),
+              ElementsAre(Bucket(12, 1)));
+  // This ScrollUpdate AverageLag are calculated as the finger uniformly scroll
+  // 10px each frame. For scroll up/down frame, the Lag at the last frame swap
+  // is 5px, and Lag at this frame swap is 15px. For the one changing direction,
+  // the Lag is from 5 to 10 and down to 5 again. So total LagArea is 99 * 100,
+  // plus 75. the AverageLag in 1 second is 9.975px.
+  EXPECT_THAT(histogram_tester().GetAllSamples(
+                  "Event.Latency.ScrollUpdate.Touch.AverageLag"),
+              ElementsAre(Bucket(9, 1)));
+  ResetHistograms();
+
+  // Send another ScrollBegin to end the unfinished ScrollUpdate report.
+  event_time += base::TimeDelta::FromMilliseconds(10);
+  frame_time += base::TimeDelta::FromMilliseconds(10);
+  SyntheticTouchScrollBeginLatencyInfo(event_time, frame_time, scroll_delta);
+
+  // The last ScrollUpdate's lag is 8.75px and truncated to 8.
+  EXPECT_THAT(histogram_tester().GetAllSamples(
+                  "Event.Latency.ScrollUpdate.Touch.AverageLag"),
+              ElementsAre(Bucket(8, 1)));
+}
+
+// Test the case that event's frame swap time is later than next event's
+// creation time. (i.e, event at t=10ms will be dispatch at t=30ms, while next
+// event is at t=20ms).
+TEST_F(AverageLagTrackerTest, LargerLatency) {
+  base::TimeTicks event_time = MillisecondsToTimeTicks(10);
+  base::TimeTicks frame_time =
+      event_time + base::TimeDelta::FromMilliseconds(20);
+  float scroll_delta = 10;
+
+  SyntheticTouchScrollBeginLatencyInfo(event_time, frame_time, scroll_delta);
+
+  // Send 2 ScrollUpdate. The second one will record AverageLag.ScrollBegin as
+  // it's event_time is larger or equal to ScrollBegin's frame_time.
+  for (int i = 0; i < 2; i++) {
+    event_time += base::TimeDelta::FromMilliseconds(10);
+    frame_time = event_time + base::TimeDelta::FromMilliseconds(20);
+    SyntheticTouchScrollUpdateLatencyInfo(event_time, frame_time, scroll_delta);
+  }
+
+  // ScrollBegin AveragLag are from t=10ms to t=30ms, with absolute scroll
+  // position from 10 to 30. The AverageLag should be:
+  // (0.5*(10px + 30px)*20ms/20ms) = 20px.
+  EXPECT_THAT(histogram_tester().GetAllSamples(
+                  "Event.Latency.ScrollBegin.Touch.AverageLag"),
+              ElementsAre(Bucket(20, 1)));
+
+  // Another ScrollBegin to flush unfinished frames.
+  // event_time doesn't matter here because the previous frames' lag are
+  // compute from their frame_time.
+  event_time = MillisecondsToTimeTicks(1000);
+  frame_time = MillisecondsToTimeTicks(1000);
+  SyntheticTouchScrollBeginLatencyInfo(event_time, frame_time, scroll_delta);
+  // The to unfinished frames' lag are (finger_positon-rendered_position)*time,
+  // AverageLag is ((30px-10px)*10ms+(30px-20px)*10ms)/20ms = 15px.
+  EXPECT_THAT(histogram_tester().GetAllSamples(
+                  "Event.Latency.ScrollUpdate.Touch.AverageLag"),
+              ElementsAre(Bucket(14, 1)));
+}
+
+// Test that multiple latency being flush in the same frame swap.
+TEST_F(AverageLagTrackerTest, TwoLatencyInfoInSameFrame) {
+  // ScrollBegin
+  base::TimeTicks event_time = MillisecondsToTimeTicks(10);
+  base::TimeTicks frame_time = MillisecondsToTimeTicks(20);
+  SyntheticTouchScrollBeginLatencyInfo(event_time, frame_time,
+                                       -10 /* scroll_delta */);
+
+  // ScrollUpdate with event_time >= ScrollBegin frame_time will generate
+  // a histogram for AverageLag.ScrollBegin.
+  event_time = MillisecondsToTimeTicks(20);
+  frame_time = MillisecondsToTimeTicks(30);
+  SyntheticTouchScrollUpdateLatencyInfo(event_time, frame_time,
+                                        -10 /* scroll_delta */);
+
+  // Absolute position from -10 to -20. The AverageLag should be:
+  // (0.5*(10px + 20px)*10ms/10ms) = 15px.
+  EXPECT_THAT(histogram_tester().GetAllSamples(
+                  "Event.Latency.ScrollBegin.Touch.AverageLag"),
+              ElementsAre(Bucket(14, 1)));
+
+  event_time = MillisecondsToTimeTicks(25);
+  frame_time = MillisecondsToTimeTicks(30);
+  SyntheticTouchScrollUpdateLatencyInfo(event_time, frame_time,
+                                        5 /* scroll_delta */);
+
+  // Another ScrollBegin to flush unfinished frames.
+  event_time = MillisecondsToTimeTicks(1000);
+  frame_time = MillisecondsToTimeTicks(1000);
+  SyntheticTouchScrollBeginLatencyInfo(event_time, frame_time, 0);
+
+  // The ScrollUpdates are at t=20ms, finger_pos=-20px, rendered_pos=-10px,
+  // at t=25ms, finger_pos=-15px, rendered_pos=-10px;
+  // To t=30ms both events get flush.
+  // AverageLag is (0.5*(10px+5px)*5ms + 5px*5ms)/10ms = 6.25px
+  EXPECT_THAT(histogram_tester().GetAllSamples(
+                  "Event.Latency.ScrollUpdate.Touch.AverageLag"),
+              ElementsAre(Bucket(6, 1)));
+}
+
+// Test the case that switching direction causes lag at current frame
+// time and previous frame time are in different direction.
+TEST_F(AverageLagTrackerTest, ChangeDirectionInFrame) {
+  // ScrollBegin
+  base::TimeTicks event_time = MillisecondsToTimeTicks(10);
+  base::TimeTicks frame_time = MillisecondsToTimeTicks(20);
+  SyntheticTouchScrollBeginLatencyInfo(event_time, frame_time,
+                                       10 /* scroll_delta */);
+
+  // At t=20, lag = 10px.
+  event_time = MillisecondsToTimeTicks(20);
+  frame_time = MillisecondsToTimeTicks(30);
+  SyntheticTouchScrollUpdateLatencyInfo(event_time, frame_time,
+                                        10 /* scroll_delta */);
+
+  // At t=30, lag = -10px.
+  event_time = MillisecondsToTimeTicks(30);
+  frame_time = MillisecondsToTimeTicks(40);
+  SyntheticTouchScrollUpdateLatencyInfo(event_time, frame_time,
+                                        -20 /* scroll_delta */);
+
+  // Another ScrollBegin to flush unfinished frames.
+  event_time = MillisecondsToTimeTicks(1000);
+  frame_time = MillisecondsToTimeTicks(1000);
+  SyntheticTouchScrollBeginLatencyInfo(event_time, frame_time, 0);
+
+  // From t=20 to t=30, lag_area=2*(0.5*10px*5ms)=50px*ms.
+  // From t=30 to t=40, lag_area=20px*10ms=200px*ms
+  // AverageLag = (50+200)/20 = 12.5px.
+  EXPECT_THAT(histogram_tester().GetAllSamples(
+                  "Event.Latency.ScrollUpdate.Touch.AverageLag"),
+              ElementsAre(Bucket(12, 1)));
+}
+
+}  // namespace
+}  // namespace ui
diff --git a/ui/latency/latency_tracker.cc b/ui/latency/latency_tracker.cc
index d98a8e0..4c851c1 100644
--- a/ui/latency/latency_tracker.cc
+++ b/ui/latency/latency_tracker.cc
@@ -260,7 +260,8 @@
          (IsInertialScroll(latency) && scroll_name == "ScrollInertial"));
 
   if (!IsInertialScroll(latency) && input_modality == "Touch")
-    CalculateAverageLag(latency, gpu_swap_begin_timestamp, scroll_name);
+    average_lag_tracker_.AddLatencyInFrame(latency, gpu_swap_begin_timestamp,
+                                           scroll_name);
 
   base::TimeTicks rendering_scheduled_timestamp;
   bool rendering_scheduled_on_main = latency.FindLatency(
@@ -343,133 +344,6 @@
       gpu_swap_begin_timestamp, gpu_swap_end_timestamp);
 }
 
-void LatencyTracker::CalculateAverageLag(
-    const ui::LatencyInfo& latency,
-    base::TimeTicks gpu_swap_begin_timestamp,
-    const std::string& scroll_name) {
-  base::TimeTicks event_timestamp;
-  bool found_component = latency.FindLatency(
-      ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_LAST_EVENT_COMPONENT,
-      &event_timestamp);
-  DCHECK_AND_RETURN_ON_FAIL(found_component);
-
-  if (scroll_name == "ScrollBegin") {
-    // Clear both lag_reports.
-    ReportAverageLagUma(std::move(pending_finished_lag_report_));
-    if (current_lag_report_)
-      current_lag_report_->report_time = last_frame_time_;
-    ReportAverageLagUma(std::move(current_lag_report_));
-
-    // Create ScrollBegin report, with report time equals to gpu swap time.
-    LagData new_report(scroll_name);
-    pending_finished_lag_report_ = std::make_unique<LagData>(scroll_name);
-    pending_finished_lag_report_->report_time = gpu_swap_begin_timestamp;
-    // For ScrollBegin, we don't have the previous time to calculate the
-    // interpolated area, so the lag is the area between the current event
-    // creation time and gpu swap begin time.
-    pending_finished_lag_report_->lag =
-        (gpu_swap_begin_timestamp - event_timestamp).InMillisecondsF() *
-        std::abs(latency.scroll_update_delta());
-    // The next report time should be a least 1 second away from current report
-    // time.
-    next_report_time_ = pending_finished_lag_report_->report_time +
-                        base::TimeDelta::FromSeconds(1);
-    // Reset last_reported_time to event time.
-    last_reported_time_ = event_timestamp;
-  } else if (scroll_name == "ScrollUpdate" &&
-             !last_event_timestamp_.is_null()) {
-    DCHECK((event_timestamp - last_event_timestamp_).InMilliseconds() >= 0);
-
-    // |pending_finger_move_lag| is the interpolated area between last event to
-    // current event. We assume the finger moved at a constant velocity between
-    // the past two events, so the lag in this duration is calculated by the
-    // average delta(current delta/2).
-    float pending_finger_move_lag =
-        (event_timestamp - last_event_timestamp_).InMillisecondsF() *
-        std::abs(latency.scroll_update_delta() / 2);
-
-    // |event_dispatch_lag| is the area between the current event creation time
-    // (i.e. last coalesced event of current event creation time) and gpu swap
-    // begin time of this event.
-    float event_dispatch_lag =
-        (gpu_swap_begin_timestamp - event_timestamp).InMillisecondsF() *
-        std::abs(latency.scroll_update_delta());
-
-    if (pending_finished_lag_report_) {
-      if (event_timestamp >= pending_finished_lag_report_->report_time) {
-        DCHECK_GE(pending_finished_lag_report_->report_time,
-                  last_event_timestamp_);
-        // This event is created after this report's report time, so part of
-        // the |pending_finger_move_lag| should be counted in this report, the
-        // rest should be count in the following report. The area of first part
-        // is calculated by similar triangle area.
-        float ratio =
-            (pending_finished_lag_report_->report_time - last_event_timestamp_)
-                .InMillisecondsF() /
-            (event_timestamp - last_event_timestamp_).InMillisecondsF();
-        pending_finished_lag_report_->lag +=
-            pending_finger_move_lag * ratio * ratio;
-        pending_finger_move_lag *= 1 - ratio * ratio;
-        ReportAverageLagUma(std::move(pending_finished_lag_report_));
-      } else {  // event_timestamp < pending_finished_lag_report_->report_time
-        DCHECK_LE(pending_finished_lag_report_->report_time,
-                  gpu_swap_begin_timestamp);
-        // This event is created before this report's report_time, so
-        // |pending_finger_move_lag|, and also part of |event_dispatch_lag| that
-        // before |report_time| should be counted in this report.
-        float lag_after_report_time =
-            (gpu_swap_begin_timestamp -
-             pending_finished_lag_report_->report_time)
-                .InMillisecondsF() *
-            std::abs(latency.scroll_update_delta());
-        pending_finished_lag_report_->lag += pending_finger_move_lag +
-                                             event_dispatch_lag -
-                                             lag_after_report_time;
-        pending_finger_move_lag = 0;
-        event_dispatch_lag = lag_after_report_time;
-      }
-    }
-
-    // Remaining pending lag should be counted in the |current_lag_report_|.
-    if (pending_finger_move_lag + event_dispatch_lag != 0) {
-      if (!current_lag_report_)
-        current_lag_report_ = std::make_unique<LagData>(scroll_name);
-
-      current_lag_report_->lag += pending_finger_move_lag + event_dispatch_lag;
-
-      // When the |pending_finished_lag_report_| is finished, and the current
-      // gpu_swap_time is larger than the |next_report_time_|, it means the we
-      // reach the 1 second gap, and we can filled in the timestamp and move it
-      // to |pending_finished_lag_report_|. We use the
-      // current|gpu_swap_begin_timestamp| as the report_time, so it can be
-      // align with gpu swaps.
-      if (!pending_finished_lag_report_ &&
-          gpu_swap_begin_timestamp >= next_report_time_) {
-        current_lag_report_->report_time = gpu_swap_begin_timestamp;
-        // The next report time is 1 second away from this report time.
-        next_report_time_ =
-            gpu_swap_begin_timestamp + base::TimeDelta::FromSeconds(1);
-        pending_finished_lag_report_ = std::move(current_lag_report_);
-      }
-    }
-  }
-  last_event_timestamp_ = event_timestamp;
-  last_frame_time_ = gpu_swap_begin_timestamp;
-}
-
-void LatencyTracker::ReportAverageLagUma(std::unique_ptr<LagData> report) {
-  if (report) {
-    DCHECK(!report->report_time.is_null());
-    DCHECK(report->lag >= 0.f);
-    base::UmaHistogramCounts1000(
-        "Event.Latency." + report->scroll_name + ".Touch.AverageLag",
-        report->lag /
-            (report->report_time - last_reported_time_).InMillisecondsF());
-
-    last_reported_time_ = report->report_time;
-  }
-}
-
 // static
 void LatencyTracker::SetLatencyInfoProcessorForTesting(
     const LatencyInfoProcessor& processor) {
diff --git a/ui/latency/latency_tracker.h b/ui/latency/latency_tracker.h
index d7ac971..02ecf72 100644
--- a/ui/latency/latency_tracker.h
+++ b/ui/latency/latency_tracker.h
@@ -5,8 +5,8 @@
 #ifndef UI_LATENCY_LATENCY_TRACKER_H_
 #define UI_LATENCY_LATENCY_TRACKER_H_
 
-#include <deque>
 #include "base/macros.h"
+#include "ui/latency/average_lag_tracker.h"
 #include "ui/latency/latency_info.h"
 
 namespace ui {
@@ -52,41 +52,7 @@
       base::TimeTicks gpu_swap_end_timestamp,
       const LatencyInfo& latency);
 
-  void CalculateAverageLag(const ui::LatencyInfo& latency,
-                           base::TimeTicks gpu_swap_begin_timestamp,
-                           const std::string& scroll_name);
-
-  // Used for reporting AverageLag metrics.
-  typedef struct LagData {
-    LagData(const std::string& name)
-        : report_time(base::TimeTicks()), lag(0), scroll_name(name) {}
-    // Lag report's report_time, align with |gpu_swap_begin_time|. It should has
-    // one second gap between previous report. We do not set the report_time
-    // before the 1 second gap is reached.
-    base::TimeTicks report_time;
-    float lag;
-    const std::string scroll_name;
-  } LagData;
-
-  void ReportAverageLagUma(std::unique_ptr<LagData> report);
-
-  // Last scroll event's timestamp in the sequence, reset on ScrollBegin.
-  base::TimeTicks last_event_timestamp_;
-  // next_report_time is always 1 second after the newest report's report_time.
-  base::TimeTicks next_report_time_;
-  // This keeps track the last report_time when we report to UMA, so we can
-  // calculate the report's duration by current - last. Reset on ScrollBegin.
-  base::TimeTicks last_reported_time_;
-  // Keeps track of last gpu_swap time, so we can end the previous unfinished
-  // report on the new ScrollBegin.
-  base::TimeTicks last_frame_time_;
-  // Lag report that already filled in the report_time, and it will be finished
-  // and report once we have an event whose timestamp is later then the
-  // report_time.
-  std::unique_ptr<LagData> pending_finished_lag_report_;
-  // The current unfinished lag report, which doesn't reach the 1 second length
-  // yet. It's report_time is null and invalid now.
-  std::unique_ptr<LagData> current_lag_report_;
+  AverageLagTracker average_lag_tracker_;
 
   DISALLOW_COPY_AND_ASSIGN(LatencyTracker);
 };
diff --git a/ui/webui/PLATFORM_OWNERS b/ui/webui/PLATFORM_OWNERS
index 1a5610ef..b60b4c4 100644
--- a/ui/webui/PLATFORM_OWNERS
+++ b/ui/webui/PLATFORM_OWNERS
@@ -7,7 +7,6 @@
 hcarmona@chromium.org
 michaelpg@chromium.org
 rbpotter@chromium.org
-scottchen@chromium.org
 stevenjb@chromium.org
 tommycli@chromium.org
 xiyuan@chromium.org
diff --git a/ui/webui/resources/cr_elements/OWNERS b/ui/webui/resources/cr_elements/OWNERS
index 5fd1662..057813143 100644
--- a/ui/webui/resources/cr_elements/OWNERS
+++ b/ui/webui/resources/cr_elements/OWNERS
@@ -1,3 +1,2 @@
 michaelpg@chromium.org
-scottchen@chromium.org
 stevenjb@chromium.org