diff --git a/AUTHORS b/AUTHORS
index a8d90ac..152cb09 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -76,6 +76,7 @@
 anatoly techtonik <techtonik@gmail.com>
 Ancil George <ancilgeorge@samsung.com>
 Andra Paraschiv <andra.paraschiv@intel.com>
+Andreas Papacharalampous <andreas@apap04.com>
 Andrei Borza <andrei.borza@gmail.com>
 Andrei Parvu <andrei.prv@gmail.com>
 Andrei Parvu <parvu@adobe.com>
diff --git a/DEPS b/DEPS
index dc3615d..534a1bc4 100644
--- a/DEPS
+++ b/DEPS
@@ -175,11 +175,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '6ec5688413840c42afaa72b3a38302e1716dce86',
+  'skia_revision': 'b3f96cadd7058b25cd00642c69d7a544a3566528',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '5e04fbf91e5dad673d29a119904c1b068cdd7996',
+  'v8_revision': '76649a7cd9701e25728fd36c96ef1203053b1e4d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -187,11 +187,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'bf6b8d4a16ae13243fee8718e98c8658f4fab218',
+  'angle_revision': 'f2bee3043a5e55189e775b07548779d2dd4061f9',
   # 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': 'cafff78f665b8a591d1f7d2d2cb00b64c8c24312',
+  'swiftshader_revision': '02e15b249b12fe4e1ca5e303a5a0de0668950536',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -238,7 +238,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': 'a8bbccaabeff0b1a8fd0c4eaf9f32c5128776976',
+  'catapult_revision': '438ea30dcc291b1305568a44bdf6e9922895d789',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -246,7 +246,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': 'd25ae4048565a67dbc63bb91f6b00470f3039f58',
+  'devtools_frontend_revision': '60a0e9defc41cc50316a04d743a262314be1026c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -298,15 +298,15 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'shaderc_revision': '3d915b2802667f44a359463f1f420ac33576001b',
+  'shaderc_revision': '9472e3eec08587a1c6616fd64d0d38553e9efd04',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '0847cb4637366d15efdb09c57d9b53ea720587fb',
+  'dawn_revision': '4b1be08ec993dd8a858229abdfd33cb2ebb6a292',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'quiche_revision': '7379c493206bd0fb646cbaaa86b03617bb2fbbd9',
+  'quiche_revision': '2542cb728f1119cfbabd91105cc5a3cc44e784a3',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ios_webkit
   # and whatever else without interference from each other.
@@ -877,7 +877,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '17aaea21d40dc178638a5e295541fbd8757811c1',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '0dd5482c5254e0457062aad7b93954918ced54fd',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1211,7 +1211,7 @@
     Var('chromium_git') + '/external/github.com/cisco/openh264' + '@' + '6f26bce0b1c4e8ce0e13332f7c0083788def5fdf',
 
   'src/third_party/openscreen/src':
-    Var('chromium_git') + '/openscreen' + '@' + 'b172d257d938ca5bed0a2d62eb5420bcbe0672e9',
+    Var('chromium_git') + '/openscreen' + '@' + 'fbd8fd2ccff9d1f51b0dec379d18ab7f11bcd4bf',
 
   'src/third_party/openxr/src': {
     'url': Var('chromium_git') + '/external/github.com/KhronosGroup/OpenXR-SDK' + '@' + '9e97b73e7dd2bfc07745489d728f6a36665c648f',
@@ -1228,7 +1228,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '658779b63e6bb8170e7e9e064e86485124fa63c5',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'edcb5301a064b9a2901d257816b521266608ddf9',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1523,7 +1523,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@6dca61aa30a9ffba2998e97031897d723bd28302',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@8c4f7654a023db9ebbe2a0c8c96db721e359b759',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/browser/gfx/aw_draw_fn_impl.cc b/android_webview/browser/gfx/aw_draw_fn_impl.cc
index c0a1bcbb..beb6789d 100644
--- a/android_webview/browser/gfx/aw_draw_fn_impl.cc
+++ b/android_webview/browser/gfx/aw_draw_fn_impl.cc
@@ -16,6 +16,7 @@
 #include "base/trace_event/trace_event.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
+#include "gpu/command_buffer/service/skia_utils.h"
 #include "gpu/ipc/common/android/android_image_reader_utils.h"
 #include "gpu/vulkan/vulkan_fence_helper.h"
 #include "gpu/vulkan/vulkan_function_pointers.h"
@@ -478,14 +479,7 @@
       return;
     }
 
-    // Create backend texture from the VkImage.
-    GrVkAlloc alloc(vulkan_image->device_memory(), 0 /* offset */,
-                    vulkan_image->device_size(), 0 /* flags */);
-    pending_draw->image_info = GrVkImageInfo(
-        vulkan_image->image(), alloc, vulkan_image->image_tiling(),
-        VK_IMAGE_LAYOUT_UNDEFINED, vulkan_image->format(), 1 /* levelCount */,
-        VK_QUEUE_FAMILY_EXTERNAL);
-
+    pending_draw->image_info = gpu::CreateGrVkImageInfo(vulkan_image.get());
     pending_draw->vulkan_image = std::move(vulkan_image);
   }
 
diff --git a/android_webview/browser/safe_browsing/aw_url_checker_delegate_impl.cc b/android_webview/browser/safe_browsing/aw_url_checker_delegate_impl.cc
index 2c6cd06..92a2676 100644
--- a/android_webview/browser/safe_browsing/aw_url_checker_delegate_impl.cc
+++ b/android_webview/browser/safe_browsing/aw_url_checker_delegate_impl.cc
@@ -90,6 +90,13 @@
                      ui_manager_, resource, std::move(request)));
 }
 
+void AwUrlCheckerDelegateImpl::
+    StartObservingInteractionsForDelayedBlockingPageHelper(
+        const security_interstitials::UnsafeResource& resource,
+        bool is_main_frame) {
+  NOTREACHED() << "Delayed warnings not implemented for WebView";
+}
+
 bool AwUrlCheckerDelegateImpl::IsUrlWhitelisted(const GURL& url) {
   return whitelist_manager_->IsURLWhitelisted(url);
 }
diff --git a/android_webview/browser/safe_browsing/aw_url_checker_delegate_impl.h b/android_webview/browser/safe_browsing/aw_url_checker_delegate_impl.h
index 5098f14..8ac4b5d 100644
--- a/android_webview/browser/safe_browsing/aw_url_checker_delegate_impl.h
+++ b/android_webview/browser/safe_browsing/aw_url_checker_delegate_impl.h
@@ -45,6 +45,9 @@
       const net::HttpRequestHeaders& headers,
       bool is_main_frame,
       bool has_user_gesture) override;
+  void StartObservingInteractionsForDelayedBlockingPageHelper(
+      const security_interstitials::UnsafeResource& resource,
+      bool is_main_frame) override;
   bool IsUrlWhitelisted(const GURL& url) override;
   bool ShouldSkipRequestCheck(const GURL& original_url,
                               int frame_tree_node_id,
diff --git a/android_webview/java/src/org/chromium/android_webview/FullScreenView.java b/android_webview/java/src/org/chromium/android_webview/FullScreenView.java
index bb4c24a..372b916 100644
--- a/android_webview/java/src/org/chromium/android_webview/FullScreenView.java
+++ b/android_webview/java/src/org/chromium/android_webview/FullScreenView.java
@@ -33,11 +33,7 @@
             int initialWidth, int initialHeight) {
         super(context);
         setRight(initialWidth);
-        // Setting to the exact same dimensions avoids a layout later in some apps.
-        // This apparently causes some unexpected behavior such as not receiving key events
-        // Arbitrarily set the height to 5 pixels less to force a layout while also minimizing
-        // changes to viewport that can affect the graphics pipeline.
-        setBottom(Math.max(0, initialHeight - 5));
+        setBottom(initialHeight);
         setAwViewMethods(awViewMethods);
         mAwContents = awContents;
         mInternalAccessAdapter = new InternalAccessAdapter();
diff --git a/ash/keyboard/ui/keyboard_ui_controller_unittest.cc b/ash/keyboard/ui/keyboard_ui_controller_unittest.cc
index 7506021..6c714dc4 100644
--- a/ash/keyboard/ui/keyboard_ui_controller_unittest.cc
+++ b/ash/keyboard/ui/keyboard_ui_controller_unittest.cc
@@ -150,8 +150,8 @@
     layout_delegate_ =
         std::make_unique<TestKeyboardLayoutDelegate>(root_window());
 
-    aura::client::SetScreenPositionClient(root_window(),
-                                          &screen_position_client_);
+    screen_position_client_ =
+        std::make_unique<wm::DefaultScreenPositionClient>(root_window());
 
     // Force enable the virtual keyboard.
     controller_.Initialize(
@@ -164,6 +164,7 @@
   void TearDown() override {
     SetTouchKeyboardEnabled(false);
     controller_.RemoveObserver(this);
+    screen_position_client_.reset();
     focus_controller_.reset();
     aura::test::AuraTestBase::TearDown();
   }
@@ -268,7 +269,7 @@
   std::unique_ptr<KeyboardLayoutDelegate> layout_delegate_;
   std::unique_ptr<ui::TextInputClient> test_text_input_client_;
   bool keyboard_disabled_ = false;
-  wm::DefaultScreenPositionClient screen_position_client_;
+  std::unique_ptr<wm::DefaultScreenPositionClient> screen_position_client_;
   ui::ScopedTestInputMethodFactory scoped_test_input_method_factory_;
   DISALLOW_COPY_AND_ASSIGN(KeyboardUIControllerTest);
 };
diff --git a/ash/public/cpp/ash_features.cc b/ash/public/cpp/ash_features.cc
index 2be4aae..9e58270 100644
--- a/ash/public/cpp/ash_features.cc
+++ b/ash/public/cpp/ash_features.cc
@@ -116,6 +116,9 @@
 const base::Feature kHideShelfControlsInTabletMode{
     "HideShelfControlsInTabletMode", base::FEATURE_ENABLED_BY_DEFAULT};
 
+const base::Feature kSystemTrayMicGainSetting{
+    "SystemTrayMicGainSetting", base::FEATURE_DISABLED_BY_DEFAULT};
+
 bool IsAllowAmbientEQEnabled() {
   return base::FeatureList::IsEnabled(kAllowAmbientEQ);
 }
@@ -245,6 +248,10 @@
   return base::FeatureList::IsEnabled(kCornerShortcuts);
 }
 
+bool IsSystemTrayMicGainSettingEnabled() {
+  return base::FeatureList::IsEnabled(kSystemTrayMicGainSetting);
+}
+
 namespace {
 
 // The boolean flag indicating if "WebUITabStrip" feature is enabled in Chrome.
diff --git a/ash/public/cpp/ash_features.h b/ash/public/cpp/ash_features.h
index cff486f1..ba63869 100644
--- a/ash/public/cpp/ash_features.h
+++ b/ash/public/cpp/ash_features.h
@@ -150,6 +150,10 @@
 // preferences, or policy).
 ASH_PUBLIC_EXPORT extern const base::Feature kHideShelfControlsInTabletMode;
 
+// Enables sliders for setting mic gain levels in the more audio settings
+// section in the system tray.
+ASH_PUBLIC_EXPORT extern const base::Feature kSystemTrayMicGainSetting;
+
 ASH_PUBLIC_EXPORT bool IsAllowAmbientEQEnabled();
 
 ASH_PUBLIC_EXPORT bool IsAltTabLimitedToActiveDesk();
@@ -204,6 +208,8 @@
 
 ASH_PUBLIC_EXPORT bool IsCornerShortcutsEnabled();
 
+ASH_PUBLIC_EXPORT bool IsSystemTrayMicGainSettingEnabled();
+
 // These two functions are supposed to be temporary functions to set or get
 // whether "WebUITabStrip" feature is enabled from Chrome.
 ASH_PUBLIC_EXPORT void SetWebUITabStripEnabled(bool enabled);
diff --git a/ash/public/cpp/shelf_ui_info.h b/ash/public/cpp/shelf_ui_info.h
index 6f4cb49..9cf752b 100644
--- a/ash/public/cpp/shelf_ui_info.h
+++ b/ash/public/cpp/shelf_ui_info.h
@@ -41,6 +41,9 @@
 
   // Screen bounds of visible shelf icons.
   std::vector<gfx::Rect> icons_bounds_in_screen;
+
+  // Indicates whether shelf widget is animating;
+  bool is_shelf_widget_animating = false;
 };
 
 struct ASH_PUBLIC_EXPORT ShelfState {
diff --git a/ash/shelf/shelf_test_api.cc b/ash/shelf/shelf_test_api.cc
index e781530..253e850 100644
--- a/ash/shelf/shelf_test_api.cc
+++ b/ash/shelf/shelf_test_api.cc
@@ -76,6 +76,8 @@
   info.is_animating = scrollable_shelf_view->during_scroll_animation_;
   info.is_overflow = (scrollable_shelf_view->layout_strategy_ !=
                       ScrollableShelfView::kNotShowArrowButtons);
+  info.is_shelf_widget_animating =
+      GetShelfWidget()->GetLayer()->GetAnimator()->is_animating();
 
   const ShelfView* const shelf_view = scrollable_shelf_view->shelf_view_;
   for (int i = shelf_view->first_visible_index();
@@ -103,7 +105,7 @@
   info.hotseat_state = hotseat_widget->state();
 
   const gfx::Rect shelf_widget_bounds =
-      GetShelf()->shelf_widget()->GetTargetBounds();
+      GetShelf()->shelf_widget()->GetWindowBoundsInScreen();
   info.swipe_up.swipe_start_location = shelf_widget_bounds.CenterPoint();
 
   // The swipe distance is small enough to avoid the window drag from shelf.
diff --git a/ash/system/message_center/unified_message_center_bubble.cc b/ash/system/message_center/unified_message_center_bubble.cc
index c946873..8cc5a2cd 100644
--- a/ash/system/message_center/unified_message_center_bubble.cc
+++ b/ash/system/message_center/unified_message_center_bubble.cc
@@ -190,6 +190,10 @@
   return tray_->FocusQuickSettings(reverse);
 }
 
+void UnifiedMessageCenterBubble::ActivateQuickSettingsBubble() {
+  tray_->ActivateBubble();
+}
+
 void UnifiedMessageCenterBubble::FocusFirstNotification() {
   // Move focus to first notification from notification bar if it is visible.
   if (message_center_view_->IsNotificationBarVisible())
diff --git a/ash/system/message_center/unified_message_center_bubble.h b/ash/system/message_center/unified_message_center_bubble.h
index a7b44581..fc7e9ed 100644
--- a/ash/system/message_center/unified_message_center_bubble.h
+++ b/ash/system/message_center/unified_message_center_bubble.h
@@ -54,6 +54,10 @@
   // Relinquish focus and transfer it to the quick settings widget.
   bool FocusOut(bool reverse);
 
+  // Activate quick settings bubble. Used when the message center is going
+  // invisible.
+  void ActivateQuickSettingsBubble();
+
   // Move focus to the first notification.
   void FocusFirstNotification();
 
diff --git a/ash/system/message_center/unified_message_center_view.cc b/ash/system/message_center/unified_message_center_view.cc
index 348cdaf..42aae64 100644
--- a/ash/system/message_center/unified_message_center_view.cc
+++ b/ash/system/message_center/unified_message_center_view.cc
@@ -411,7 +411,7 @@
     // Transfer focus to quick settings when going invisible.
     auto* widget = GetWidget();
     if (widget && widget->IsActive())
-      FocusOut(false);
+      message_center_bubble_->ActivateQuickSettingsBubble();
   }
 }
 
diff --git a/ash/system/unified/unified_system_tray_controller_unittest.cc b/ash/system/unified/unified_system_tray_controller_unittest.cc
index c2adba1..900762e 100644
--- a/ash/system/unified/unified_system_tray_controller_unittest.cc
+++ b/ash/system/unified/unified_system_tray_controller_unittest.cc
@@ -13,7 +13,7 @@
 #include "ash/system/unified/unified_system_tray_view.h"
 #include "ash/test/ash_test_base.h"
 #include "chromeos/dbus/shill/shill_clients.h"
-#include "chromeos/network/network_handler.h"
+#include "chromeos/services/network_config/public/cpp/cros_network_config_test_helper.h"
 #include "components/prefs/testing_pref_service.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
 #include "ui/gfx/animation/slide_animation.h"
@@ -39,12 +39,9 @@
 
   // testing::Test:
   void SetUp() override {
-    chromeos::shill_clients::InitializeFakes();
-    // Initializing NetworkHandler before ash is more like production.
-    chromeos::NetworkHandler::Initialize();
+    network_config_helper_ = std::make_unique<
+        chromeos::network_config::CrosNetworkConfigTestHelper>();
     AshTestBase::SetUp();
-    chromeos::NetworkHandler::Get()->InitializePrefServices(&profile_prefs_,
-                                                            &local_state_);
     // Networking stubs may have asynchronous initialization.
     base::RunLoop().RunUntilIdle();
 
@@ -61,11 +58,7 @@
     controller_.reset();
     model_.reset();
 
-    // This roughly matches production shutdown order.
-    chromeos::NetworkHandler::Get()->ShutdownPrefServices();
     AshTestBase::TearDown();
-    chromeos::NetworkHandler::Shutdown();
-    chromeos::shill_clients::Shutdown();
   }
 
   // views::ViewObserver:
@@ -99,13 +92,12 @@
   UnifiedSystemTrayView* view() { return view_.get(); }
 
  private:
+  std::unique_ptr<chromeos::network_config::CrosNetworkConfigTestHelper>
+      network_config_helper_;
   std::unique_ptr<UnifiedSystemTrayModel> model_;
   std::unique_ptr<UnifiedSystemTrayController> controller_;
   std::unique_ptr<UnifiedSystemTrayView> view_;
 
-  TestingPrefServiceSimple profile_prefs_;
-  TestingPrefServiceSimple local_state_;
-
   int preferred_size_changed_count_ = 0;
 
   DISALLOW_COPY_AND_ASSIGN(UnifiedSystemTrayControllerTest);
diff --git a/ash/test/ash_test_helper.cc b/ash/test/ash_test_helper.cc
index 39713e0..2bd6ae4 100644
--- a/ash/test/ash_test_helper.cc
+++ b/ash/test/ash_test_helper.cc
@@ -70,16 +70,55 @@
 AshTestHelper::InitParams::InitParams(InitParams&&) = default;
 AshTestHelper::InitParams::~InitParams() = default;
 
-AshTestHelper::AshTestHelper()
-    : command_line_(std::make_unique<base::test::ScopedCommandLine>()) {}
+AshTestHelper::AshTestHelper() = default;
 
 AshTestHelper::~AshTestHelper() {
-  // Ensure the next test starts with a null display::Screen. Done here because
-  // some tests use Screen after TearDown().
+  // Ensure the next test starts with a null display::Screen.  This must be done
+  // here instead of in TearDown() since some tests test access to the Screen
+  // after the shell shuts down (which they use TearDown() to trigger).
   ScreenAsh::DeleteScreenForShutdown();
 }
 
 void AshTestHelper::SetUp(InitParams init_params) {
+  // Aura-general setup -------------------------------------------------------
+
+  wm_state_ = std::make_unique<::wm::WMState>();
+
+  if (init_params.config_type != kShell) {
+    ui::test::EnableTestConfigForPlatformWindows();
+    ui::InitializeInputMethodForTesting();
+  }
+
+  ui::test::EventGeneratorDelegate::SetFactoryFunction(
+      base::BindRepeating(&aura::test::EventGeneratorDelegateAura::Create));
+
+  if (init_params.config_type == kUnitTest) {
+    zero_duration_mode_.reset(new ui::ScopedAnimationDurationScaleMode(
+        ui::ScopedAnimationDurationScaleMode::ZERO_DURATION));
+  }
+
+  if (!init_params.context_factory) {
+    context_factories_ = std::make_unique<ui::TestContextFactories>(false);
+    init_params.context_factory = context_factories_->GetContextFactory();
+  }
+
+  // Reset aura::Env to eliminate test dependency (https://crbug.com/586514).
+  aura::test::EnvTestHelper env_helper(aura::Env::GetInstance());
+  env_helper.ResetEnvForTesting();
+  env_helper.SetInputStateLookup(std::unique_ptr<aura::InputStateLookup>());
+
+  // Ash-specific setup -------------------------------------------------------
+
+  command_line_ = std::make_unique<base::test::ScopedCommandLine>();
+  statistics_provider_ =
+      std::make_unique<chromeos::system::ScopedFakeStatisticsProvider>();
+  prefs_provider_ = std::make_unique<TestPrefServiceProvider>();
+  notifier_settings_controller_ =
+      std::make_unique<TestNotifierSettingsController>();
+  assistant_service_ = std::make_unique<TestAssistantService>();
+  system_tray_client_ = std::make_unique<TestSystemTrayClient>();
+  photo_controller_ = std::make_unique<TestPhotoController>();
+
   // TODO(jamescook): Can we do this without changing command line?
   // Use the origin (1,1) so that it doesn't over
   // lap with the native mouse cursor.
@@ -91,48 +130,11 @@
         ::switches::kHostWindowBounds, "10+10-800x600");
   }
 
-  // Pre shell creation config init.
-  switch (init_params.config_type) {
-    case kUnitTest:
-      // Default for unit tests but not for perf tests.
-      zero_duration_mode_.reset(new ui::ScopedAnimationDurationScaleMode(
-          ui::ScopedAnimationDurationScaleMode::ZERO_DURATION));
-      TabletModeController::SetUseScreenshotForTest(false);
-      FALLTHROUGH;
-    case kPerfTest:
-      // Default for both unit and perf tests.
-      ui::test::EnableTestConfigForPlatformWindows();
-      display::ResetDisplayIdForTest();
-      ui::InitializeInputMethodForTesting();
-      break;
-    case kShell:
-      break;
-  }
+  if (init_params.config_type == kUnitTest)
+    TabletModeController::SetUseScreenshotForTest(false);
 
-  statistics_provider_ =
-      std::make_unique<chromeos::system::ScopedFakeStatisticsProvider>();
-
-  ui::test::EventGeneratorDelegate::SetFactoryFunction(
-      base::BindRepeating(&aura::test::EventGeneratorDelegateAura::Create));
-
-  wm_state_ = std::make_unique<::wm::WMState>();
-  // Only create a ViewsDelegate if the test didn't create one already.
-  if (!views::ViewsDelegate::GetInstance())
-    test_views_delegate_ = std::make_unique<AshTestViewsDelegate>();
-
-  if (!bluez::BluezDBusManager::IsInitialized()) {
-    bluez::BluezDBusManager::InitializeFake();
-    bluez_dbus_manager_initialized_ = true;
-  }
-
-  if (!chromeos::PowerManagerClient::Get())
-    chromeos::PowerManagerClient::InitializeFake();
-
-  if (!chromeos::PowerPolicyController::IsInitialized()) {
-    chromeos::PowerPolicyController::Initialize(
-        chromeos::PowerManagerClient::Get());
-    power_policy_controller_initialized_ = true;
-  }
+  if (init_params.config_type != kShell)
+    display::ResetDisplayIdForTest();
 
   chromeos::CrasAudioClient::InitializeFake();
   // Create CrasAudioHandler for testing since g_browser_process is not
@@ -143,104 +145,93 @@
   // last cursor visibility state, etc.
   ::wm::CursorManager::ResetCursorVisibilityStateForTest();
 
+  if (!bluez::BluezDBusManager::IsInitialized()) {
+    bluez::BluezDBusManager::InitializeFake();
+    bluez_dbus_manager_initialized_ = true;
+  }
+  if (!chromeos::PowerManagerClient::Get())
+    chromeos::PowerManagerClient::InitializeFake();
+  if (!chromeos::PowerPolicyController::IsInitialized()) {
+    chromeos::PowerPolicyController::Initialize(
+        chromeos::PowerManagerClient::Get());
+    power_policy_controller_initialized_ = true;
+  }
+  if (!NewWindowDelegate::GetInstance())
+    new_window_delegate_ = std::make_unique<TestNewWindowDelegate>();
+  if (!views::ViewsDelegate::GetInstance())
+    test_views_delegate_ = std::make_unique<AshTestViewsDelegate>();
+
   ShellInitParams shell_init_params;
   shell_init_params.delegate = std::move(init_params.delegate);
   if (!shell_init_params.delegate)
     shell_init_params.delegate = std::make_unique<TestShellDelegate>();
   shell_init_params.context_factory = init_params.context_factory;
-  if (!shell_init_params.context_factory) {
-    context_factories_ = std::make_unique<ui::TestContextFactories>(false);
-    shell_init_params.context_factory = context_factories_->GetContextFactory();
-  }
   shell_init_params.local_state = init_params.local_state;
   shell_init_params.keyboard_ui_factory =
       std::make_unique<TestKeyboardUIFactory>();
   Shell::CreateInstance(std::move(shell_init_params));
-
-  // Reset aura::Env to eliminate test dependency (https://crbug.com/586514).
-  aura::test::EnvTestHelper env_helper(aura::Env::GetInstance());
-  env_helper.ResetEnvForTesting();
-
-  env_helper.SetInputStateLookup(std::unique_ptr<aura::InputStateLookup>());
-
   Shell* shell = Shell::Get();
 
   // Cursor is visible by default in tests.
-  // CursorManager is null on MASH.
-  if (shell->cursor_manager())
-    shell->cursor_manager()->ShowCursor();
+  shell->cursor_manager()->ShowCursor();
 
-  prefs_provider_ = std::make_unique<TestPrefServiceProvider>();
-  session_controller_client_.reset(new TestSessionControllerClient(
-      shell->session_controller(), prefs_provider_.get()));
-  session_controller_client_->InitializeAndSetClient();
-
-  notifier_settings_controller_ =
-      std::make_unique<TestNotifierSettingsController>();
-
-  assistant_service_ = std::make_unique<TestAssistantService>();
   shell->assistant_controller()->SetAssistant(
       assistant_service_->CreateRemoteAndBind());
 
-  system_tray_client_ = std::make_unique<TestSystemTrayClient>();
   shell->system_tray_model()->SetClient(system_tray_client_.get());
 
-  photo_controller_ = std::make_unique<TestPhotoController>();
-
+  session_controller_client_.reset(new TestSessionControllerClient(
+      shell->session_controller(), prefs_provider_.get()));
+  session_controller_client_->InitializeAndSetClient();
   if (init_params.start_session)
     session_controller_client_->CreatePredefinedUserSessions(1);
 
+  // Requires the AppListController the Shell creates.
   app_list_test_helper_ = std::make_unique<AppListTestHelper>();
 
-  if (!NewWindowDelegate::GetInstance())
-    new_window_delegate_ = std::make_unique<TestNewWindowDelegate>();
-
-  // Post shell creation config init.
-  switch (init_params.config_type) {
-    case kUnitTest:
-      // Tests that change the display configuration generally don't care about
-      // the notifications and the popup UI can interfere with things like
-      // cursors.
-      shell->screen_layout_observer()->set_show_notifications_for_testing(
-          false);
-
-      // Disable display change animations in unit tests.
-      DisplayConfigurationControllerTestApi(
-          shell->display_configuration_controller())
-          .SetDisplayAnimator(false);
-
-      // Remove the app dragging animations delay for testing purposes.
-      shell->overview_controller()->set_delayed_animation_task_delay_for_test(
-          base::TimeDelta());
-      // Tests expect empty wallpaper.
-      shell->wallpaper_controller()->CreateEmptyWallpaperForTesting();
-
-      FALLTHROUGH;
-    case kPerfTest:
-      // Don't change the display size due to host size resize.
-      display::test::DisplayManagerTestApi(shell->display_manager())
-          .DisableChangeDisplayUponHostResize();
-
-      // Create the test keyboard controller observer to respond to
-      // OnLoadKeyboardContentsRequested().
-      test_keyboard_controller_observer_ =
-          std::make_unique<TestKeyboardControllerObserver>(
-              shell->keyboard_controller());
-      break;
-    case kShell:
-      shell->wallpaper_controller()->ShowDefaultWallpaperForTesting();
-      break;
+  if (init_params.config_type == kShell) {
+    shell->wallpaper_controller()->ShowDefaultWallpaperForTesting();
+    return;
   }
+
+  // Don't change the display size due to host size resize.
+  display::test::DisplayManagerTestApi(shell->display_manager())
+      .DisableChangeDisplayUponHostResize();
+
+  // Create the test keyboard controller observer to respond to
+  // OnLoadKeyboardContentsRequested().
+  test_keyboard_controller_observer_ =
+      std::make_unique<TestKeyboardControllerObserver>(
+          shell->keyboard_controller());
+
+  if (init_params.config_type != kUnitTest)
+    return;
+
+  // Tests that change the display configuration generally don't care about the
+  // notifications and the popup UI can interfere with things like cursors.
+  shell->screen_layout_observer()->set_show_notifications_for_testing(false);
+
+  // Disable display change animations in unit tests.
+  DisplayConfigurationControllerTestApi(
+      shell->display_configuration_controller())
+      .SetDisplayAnimator(false);
+
+  // Remove the app dragging animations delay for testing purposes.
+  shell->overview_controller()->set_delayed_animation_task_delay_for_test(
+      base::TimeDelta());
+
+  // Tests expect empty wallpaper.
+  shell->wallpaper_controller()->CreateEmptyWallpaperForTesting();
 }
 
 void AshTestHelper::TearDown() {
+  // Ash-specific teardown ----------------------------------------------------
+
+  // The AppListTestHelper holds a pointer to the AppListController the Shell
+  // owns, so shut the test helper down first.
   app_list_test_helper_.reset();
 
   Shell::DeleteInstance();
-  new_window_delegate_.reset();
-
-  // Needs to be reset after Shell::Get()->keyboard_controller() is deleted.
-  test_keyboard_controller_observer_.reset();
 
   // Suspend the tear down until all resources are returned via
   // CompositorFrameSinkClient::ReclaimResources()
@@ -249,6 +240,8 @@
   chromeos::CrasAudioHandler::Shutdown();
   chromeos::CrasAudioClient::Shutdown();
 
+  // The PowerPolicyController holds a pointer to the PowerManagementClient, so
+  // shut the controller down first.
   if (power_policy_controller_initialized_) {
     chromeos::PowerPolicyController::Shutdown();
     power_policy_controller_initialized_ = false;
@@ -256,13 +249,29 @@
 
   chromeos::PowerManagerClient::Shutdown();
 
+  TabletModeController::SetUseScreenshotForTest(true);
+
+  // Destroy all owned objects to prevent tests from depending on their state
+  // after this returns.
+  test_keyboard_controller_observer_.reset();
+  test_views_delegate_.reset();
+  new_window_delegate_.reset();
   if (bluez_dbus_manager_initialized_) {
     device::BluetoothAdapterFactory::Shutdown();
     bluez::BluezDBusManager::Shutdown();
     bluez_dbus_manager_initialized_ = false;
   }
+  photo_controller_.reset();
+  system_tray_client_.reset();
+  assistant_service_.reset();
+  notifier_settings_controller_.reset();
+  prefs_provider_.reset();
+  statistics_provider_.reset();
+  command_line_.reset();
 
-  context_factories_.reset();
+  // Aura-general teardown ----------------------------------------------------
+
+  ui::ShutdownInputMethodForTesting();
 
   // Context factory referenced by Env is now destroyed. Reset Env's members in
   // case some other test tries to use it. This matters if someone else created
@@ -270,24 +279,12 @@
   if (aura::Env::HasInstance())
     aura::Env::GetInstance()->set_context_factory(nullptr);
 
-  ui::ShutdownInputMethodForTesting();
-  zero_duration_mode_.reset();
-
-  test_views_delegate_.reset();
-  wm_state_.reset();
-
-  command_line_.reset();
-
-  display::Display::ResetForceDeviceScaleFactorForTesting();
-
-  CHECK(!::wm::CaptureController::Get());
-
   ui::test::EventGeneratorDelegate::SetFactoryFunction(
       ui::test::EventGeneratorDelegate::FactoryFunction());
 
-  statistics_provider_.reset();
-
-  TabletModeController::SetUseScreenshotForTest(true);
+  context_factories_.reset();
+  zero_duration_mode_.reset();
+  wm_state_.reset();
 }
 
 PrefService* AshTestHelper::GetLocalStatePrefService() {
diff --git a/ash/test/ash_test_helper.h b/ash/test/ash_test_helper.h
index 95524f1..f0fed39 100644
--- a/ash/test/ash_test_helper.h
+++ b/ash/test/ash_test_helper.h
@@ -135,32 +135,23 @@
   void reset_commandline() { command_line_.reset(); }
 
  private:
+  std::unique_ptr<::wm::WMState> wm_state_;
+  std::unique_ptr<ui::ScopedAnimationDurationScaleMode> zero_duration_mode_;
+  std::unique_ptr<ui::TestContextFactories> context_factories_;
+  std::unique_ptr<base::test::ScopedCommandLine> command_line_;
   std::unique_ptr<chromeos::system::ScopedFakeStatisticsProvider>
       statistics_provider_;
-
-  std::unique_ptr<ui::ScopedAnimationDurationScaleMode> zero_duration_mode_;
-
-  std::unique_ptr<::wm::WMState> wm_state_;
-  std::unique_ptr<AshTestViewsDelegate> test_views_delegate_;
-
-  // Flags for whether various services were initialized here.
+  std::unique_ptr<TestPrefServiceProvider> prefs_provider_;
+  std::unique_ptr<TestNotifierSettingsController> notifier_settings_controller_;
+  std::unique_ptr<TestAssistantService> assistant_service_;
+  std::unique_ptr<TestSystemTrayClient> system_tray_client_;
+  std::unique_ptr<TestPhotoController> photo_controller_;
+  std::unique_ptr<AppListTestHelper> app_list_test_helper_;
   bool bluez_dbus_manager_initialized_ = false;
   bool power_policy_controller_initialized_ = false;
-
-  std::unique_ptr<TestSessionControllerClient> session_controller_client_;
-  std::unique_ptr<TestNotifierSettingsController> notifier_settings_controller_;
-  std::unique_ptr<TestSystemTrayClient> system_tray_client_;
-  std::unique_ptr<TestPrefServiceProvider> prefs_provider_;
-  std::unique_ptr<TestAssistantService> assistant_service_;
-  std::unique_ptr<ui::TestContextFactories> context_factories_;
-  std::unique_ptr<TestPhotoController> photo_controller_;
-
-  std::unique_ptr<base::test::ScopedCommandLine> command_line_;
-
-  std::unique_ptr<AppListTestHelper> app_list_test_helper_;
-
   std::unique_ptr<TestNewWindowDelegate> new_window_delegate_;
-
+  std::unique_ptr<AshTestViewsDelegate> test_views_delegate_;
+  std::unique_ptr<TestSessionControllerClient> session_controller_client_;
   std::unique_ptr<TestKeyboardControllerObserver>
       test_keyboard_controller_observer_;
 
diff --git a/ash/wm/gestures/back_gesture/back_gesture_event_handler.cc b/ash/wm/gestures/back_gesture/back_gesture_event_handler.cc
index 0d48e0f..5b8198d 100644
--- a/ash/wm/gestures/back_gesture/back_gesture_event_handler.cc
+++ b/ash/wm/gestures/back_gesture/back_gesture_event_handler.cc
@@ -198,12 +198,19 @@
       /*is_source_touch_event_set_non_blocking=*/false);
 
   // Get the event target from TouchEvent since target of the GestureEvent
-  // from GetAndResetPendingGestures is nullptr.
+  // from GetAndResetPendingGestures is nullptr. The coordinate conversion is
+  // done outside the loop as the previous gesture events in a sequence may
+  // invalidate the target, for example given a sequence of
+  // {ET_GESTURE_SCROLL_END, ET_GESTURE_END} on a non-resizable window, the
+  // first gesture will trigger a minimize event which will delete the backdrop,
+  // which was the target. See http://crbug.com/1064618.
   aura::Window* target = static_cast<aura::Window*>(event->target());
+  gfx::Point screen_location = event->location();
+  ::wm::ConvertPointToScreen(target, &screen_location);
   const std::vector<std::unique_ptr<ui::GestureEvent>> gestures =
       gesture_provider_.GetAndResetPendingGestures();
   for (const auto& gesture : gestures) {
-    if (MaybeHandleBackGesture(gesture.get(), target))
+    if (MaybeHandleBackGesture(gesture.get(), screen_location))
       event->StopPropagation();
   }
 }
@@ -214,15 +221,13 @@
   // handled at OnTouchEvent() by calling MaybeHandleBackGesture().
 }
 
-bool BackGestureEventHandler::MaybeHandleBackGesture(ui::GestureEvent* event,
-                                                     aura::Window* target) {
+bool BackGestureEventHandler::MaybeHandleBackGesture(
+    ui::GestureEvent* event,
+    const gfx::Point& screen_location) {
   DCHECK(features::IsSwipingFromLeftEdgeToGoBackEnabled());
-  DCHECK(target);
-  gfx::Point screen_location = event->location();
-  ::wm::ConvertPointToScreen(target, &screen_location);
   switch (event->type()) {
     case ui::ET_GESTURE_TAP_DOWN:
-      going_back_started_ = CanStartGoingBack(target, screen_location);
+      going_back_started_ = CanStartGoingBack(screen_location);
       if (!going_back_started_)
         break;
       back_gesture_affordance_ = std::make_unique<BackGestureAffordance>(
@@ -334,7 +339,6 @@
 }
 
 bool BackGestureEventHandler::CanStartGoingBack(
-    aura::Window* target,
     const gfx::Point& screen_location) {
   DCHECK(features::IsSwipingFromLeftEdgeToGoBackEnabled());
 
@@ -363,7 +367,7 @@
   if (!top_window && !shell->overview_controller()->InOverviewSession())
     return false;
 
-  for (aura::Window* window = target; window; window = window->parent()) {
+  for (aura::Window* window = top_window; window; window = window->parent()) {
     SkRegion* gesture_exclusion =
         window->GetProperty(kSystemGestureExclusionKey);
     if (gesture_exclusion) {
@@ -377,7 +381,7 @@
   }
 
   gfx::Rect hit_bounds_in_screen(display::Screen::GetScreen()
-                                     ->GetDisplayNearestWindow(target)
+                                     ->GetDisplayNearestWindow(top_window)
                                      .work_area());
   hit_bounds_in_screen.set_width(kStartGoingBackLeftEdgeInset);
   if (hit_bounds_in_screen.Contains(screen_location))
diff --git a/ash/wm/gestures/back_gesture/back_gesture_event_handler.h b/ash/wm/gestures/back_gesture/back_gesture_event_handler.h
index 9157632..b8551b2 100644
--- a/ash/wm/gestures/back_gesture/back_gesture_event_handler.h
+++ b/ash/wm/gestures/back_gesture/back_gesture_event_handler.h
@@ -10,10 +10,6 @@
 #include "ui/events/event_handler.h"
 #include "ui/events/gestures/gesture_provider_aura.h"
 
-namespace aura {
-class Window;
-}  // namespace aura
-
 namespace ash {
 
 class BackGestureAffordance;
@@ -51,14 +47,14 @@
 
  private:
   // Returns true if |event| was handled as a go-back gesture. |event| is
-  // generated by |gesture_provider_| from touch event, its target will be
-  // nullptr. Gets |target| from corresponding touch event instead.
-  bool MaybeHandleBackGesture(ui::GestureEvent* event, aura::Window* target);
+  // generated by |gesture_provider_| from touch event, |screen_location| is
+  // location of the event in screen coordinates.
+  bool MaybeHandleBackGesture(ui::GestureEvent* event,
+                              const gfx::Point& screen_location);
 
   // True if we can start swiping from left edge of the display or splitview
   // divider to go back.
-  bool CanStartGoingBack(aura::Window* window,
-                         const gfx::Point& screen_location);
+  bool CanStartGoingBack(const gfx::Point& screen_location);
 
   void SendBackEvent(const gfx::Point& screen_location);
 
diff --git a/ash/wm/gestures/back_gesture/back_gesture_event_handler_unittest.cc b/ash/wm/gestures/back_gesture/back_gesture_event_handler_unittest.cc
index 8e0c7bd..04aad14 100644
--- a/ash/wm/gestures/back_gesture/back_gesture_event_handler_unittest.cc
+++ b/ash/wm/gestures/back_gesture/back_gesture_event_handler_unittest.cc
@@ -29,7 +29,11 @@
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
 #include "ash/wm/wm_event.h"
+#include "ash/wm/workspace/backdrop_controller.h"
+#include "ash/wm/workspace/workspace_layout_manager.h"
+#include "ash/wm/workspace_controller.h"
 #include "base/test/scoped_feature_list.h"
+#include "ui/aura/client/aura_constants.h"
 #include "ui/base/accelerators/accelerator.h"
 #include "ui/base/accelerators/test_accelerator_target.h"
 #include "ui/display/test/display_manager_test_api.h"
@@ -606,4 +610,34 @@
   EXPECT_FALSE(window_state->IsMinimized());
 }
 
+// Tests that swiping on the backdrop to minimize a non-resizable app will not
+// cause a crash. Regression test for http://crbug.com/1064618.
+TEST_F(BackGestureEventHandlerTestCantGoBack, NonResizableApp) {
+  // Make the top window non-resizable and set its bounds so that the backdrop
+  // will take the gesture events.
+  top_window()->SetProperty(aura::client::kResizeBehaviorKey,
+                            aura::client::kResizeBehaviorCanMinimize);
+
+  WindowState* window_state = WindowState::Get(top_window());
+  window_state->Restore();
+  SetBoundsWMEvent bounds_event(gfx::Rect(200, 100, 300, 300));
+  window_state->OnWMEvent(&bounds_event);
+  ASSERT_FALSE(window_state->IsMinimized());
+
+  // Check that the backdrop is visible.
+  WorkspaceController* workspace_controller =
+      GetWorkspaceControllerForContext(top_window());
+  WorkspaceLayoutManager* layout_manager =
+      workspace_controller->layout_manager();
+  BackdropController* backdrop_controller =
+      layout_manager->backdrop_controller();
+  aura::Window* backdrop_window = backdrop_controller->backdrop_window();
+  ASSERT_TRUE(backdrop_window);
+  ASSERT_TRUE(backdrop_window->IsVisible());
+
+  // Generate a back seqeuence. There should be no crash.
+  GenerateBackSequence();
+  EXPECT_TRUE(window_state->IsMinimized());
+}
+
 }  // namespace ash
diff --git a/base/metrics/field_trial_params.h b/base/metrics/field_trial_params.h
index a9bd0c54..7b4bd5b 100644
--- a/base/metrics/field_trial_params.h
+++ b/base/metrics/field_trial_params.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "base/base_export.h"
+#include "base/logging.h"
 
 namespace base {
 
@@ -256,6 +257,16 @@
     return default_value;
   }
 
+  // Returns the param-string for the given enum value.
+  std::string GetName(Enum value) const {
+    for (size_t i = 0; i < option_count; ++i) {
+      if (value == options[i].value)
+        return options[i].name;
+    }
+    NOTREACHED();
+    return "";
+  }
+
   const base::Feature* const feature;
   const char* const name;
   const Enum default_value;
diff --git a/base/metrics/ukm_source_id.cc b/base/metrics/ukm_source_id.cc
index 5b3b65fa..ce8c886 100644
--- a/base/metrics/ukm_source_id.cc
+++ b/base/metrics/ukm_source_id.cc
@@ -13,7 +13,7 @@
 namespace {
 
 const int64_t kLowBitsMask = (INT64_C(1) << 32) - 1;
-const int64_t kNumTypeBits = 3;
+const int64_t kNumTypeBits = static_cast<int64_t>(UkmSourceId::Type::kMaxValue);
 const int64_t kTypeMask = (INT64_C(1) << kNumTypeBits) - 1;
 
 }  // namespace
diff --git a/base/metrics/ukm_source_id.h b/base/metrics/ukm_source_id.h
index e35278b..3672235 100644
--- a/base/metrics/ukm_source_id.h
+++ b/base/metrics/ukm_source_id.h
@@ -18,7 +18,7 @@
  public:
   enum class Type : int64_t {
     // Source ids of this type are created via ukm::AssignNewSourceId, to denote
-    // 'custom' source other than the 3 types below. Source of this type has
+    // 'custom' source other than the 4 types below. Source of this type has
     // additional restrictions with logging, as determined by
     // IsWhitelistedSourceId.
     UKM = 0,
@@ -40,6 +40,11 @@
     // associated events are expected to be recorded within the same report
     // interval; it will not be kept in memory between different reports.
     WEBAPK_ID = 4,
+    // Source ID for service worker based payment handlers. A new source of this
+    // type and associated events are expected to be recorded within the same
+    // report interval; it will not be kept in memory between different reports.
+    PAYMENT_APP_ID = 5,
+    kMaxValue = PAYMENT_APP_ID,
   };
 
   // Default constructor has the invalid value.
diff --git a/base/system/sys_info.h b/base/system/sys_info.h
index 400d0577..057b0dc 100644
--- a/base/system/sys_info.h
+++ b/base/system/sys_info.h
@@ -169,6 +169,9 @@
   // Returns the Android build ID.
   static std::string GetAndroidBuildID();
 
+  // Returns the Android hardware EGL system property.
+  static std::string GetAndroidHardwareEGL();
+
   static int DalvikHeapSizeMB();
   static int DalvikHeapGrowthLimitMB();
 #endif  // defined(OS_ANDROID)
diff --git a/base/system/sys_info_android.cc b/base/system/sys_info_android.cc
index 3993b876..fa4eeacf 100644
--- a/base/system/sys_info_android.cc
+++ b/base/system/sys_info_android.cc
@@ -215,6 +215,12 @@
   return std::string(os_build_id_str);
 }
 
+std::string SysInfo::GetAndroidHardwareEGL() {
+  char os_hardware_egl_str[PROP_VALUE_MAX];
+  __system_property_get("ro.hardware.egl", os_hardware_egl_str);
+  return std::string(os_hardware_egl_str);
+}
+
 int SysInfo::DalvikHeapSizeMB() {
   static int heap_size = GetDalvikHeapSizeMB();
   return heap_size;
diff --git a/base/trace_event/builtin_categories.h b/base/trace_event/builtin_categories.h
index 6f07209..73e9b6a 100644
--- a/base/trace_event/builtin_categories.h
+++ b/base/trace_event/builtin_categories.h
@@ -60,6 +60,7 @@
   X("devtools")                                                          \
   X("devtools.timeline")                                                 \
   X("devtools.timeline.async")                                           \
+  X("disk_cache")                                                        \
   X("download")                                                          \
   X("download_service")                                                  \
   X("drm")                                                               \
diff --git a/base/trace_event/trace_event_memory_overhead.h b/base/trace_event/trace_event_memory_overhead.h
index 69468d4..e10a3ec 100644
--- a/base/trace_event/trace_event_memory_overhead.h
+++ b/base/trace_event/trace_event_memory_overhead.h
@@ -8,6 +8,7 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include <string>
 #include <unordered_map>
 
 #include "base/base_export.h"
diff --git a/build/android/bytecode/BUILD.gn b/build/android/bytecode/BUILD.gn
index 5d651d2..7a9d304 100644
--- a/build/android/bytecode/BUILD.gn
+++ b/build/android/bytecode/BUILD.gn
@@ -8,7 +8,6 @@
 
 java_binary("java_bytecode_rewriter") {
   sources = [
-    "java/org/chromium/bytecode/AssertionEnablerClassAdapter.java",
     "java/org/chromium/bytecode/ByteCodeProcessor.java",
     "java/org/chromium/bytecode/ClassPathValidator.java",
     "java/org/chromium/bytecode/CustomClassLoaderClassWriter.java",
diff --git a/build/android/bytecode/java/org/chromium/bytecode/AssertionEnablerClassAdapter.java b/build/android/bytecode/java/org/chromium/bytecode/AssertionEnablerClassAdapter.java
deleted file mode 100644
index 4b04e18..0000000
--- a/build/android/bytecode/java/org/chromium/bytecode/AssertionEnablerClassAdapter.java
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.bytecode;
-
-import static org.chromium.bytecode.TypeUtils.ASSERTION_ERROR;
-import static org.chromium.bytecode.TypeUtils.BUILD_HOOKS;
-import static org.chromium.bytecode.TypeUtils.VOID;
-
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.Label;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-
-/**
- * An ClassVisitor for replacing Java ASSERT statements with a function by modifying Java bytecode.
- *
- * We do this in two steps, first step is to enable assert.
- * Following bytecode is generated for each class with ASSERT statements:
- * 0: ldc #8 // class CLASSNAME
- * 2: invokevirtual #9 // Method java/lang/Class.desiredAssertionStatus:()Z
- * 5: ifne 12
- * 8: iconst_1
- * 9: goto 13
- * 12: iconst_0
- * 13: putstatic #2 // Field $assertionsDisabled:Z
- * Replaces line #13 to the following:
- * 13: pop
- * Consequently, $assertionsDisabled is assigned the default value FALSE.
- * This is done in the first if statement in overridden visitFieldInsn. We do this per per-assert.
- *
- * Second step is to replace assert statement with a function:
- * The followed instructions are generated by a java assert statement:
- * getstatic     #3     // Field $assertionsDisabled:Z
- * ifne          118    // Jump to instruction as if assertion if not enabled
- * ...
- * ifne          19
- * new           #4     // class java/lang/AssertionError
- * dup
- * ldc           #5     // String (don't have this line if no assert message given)
- * invokespecial #6     // Method java/lang/AssertionError.
- * athrow
- * Replace athrow with:
- * invokestatic  #7     // Method org/chromium/base/JavaExceptionReporter.assertFailureHandler
- * goto          118
- * JavaExceptionReporter.assertFailureHandler is a function that handles the AssertionError,
- * 118 is the instruction to execute as if assertion if not enabled.
- */
-class AssertionEnablerClassAdapter extends ClassVisitor {
-    AssertionEnablerClassAdapter(ClassVisitor visitor) {
-        super(Opcodes.ASM7, visitor);
-    }
-
-    @Override
-    public MethodVisitor visitMethod(final int access, final String name, String desc,
-            String signature, String[] exceptions) {
-        return new RewriteAssertMethodVisitor(
-                Opcodes.ASM7, super.visitMethod(access, name, desc, signature, exceptions));
-    }
-
-    static class RewriteAssertMethodVisitor extends MethodVisitor {
-        static final String ASSERTION_DISABLED_NAME = "$assertionsDisabled";
-        static final String INSERT_INSTRUCTION_NAME = "assertFailureHandler";
-        static final String INSERT_INSTRUCTION_DESC =
-                TypeUtils.getMethodDescriptor(VOID, ASSERTION_ERROR);
-        static final boolean INSERT_INSTRUCTION_ITF = false;
-
-        boolean mStartLoadingAssert;
-        Label mGotoLabel;
-
-        public RewriteAssertMethodVisitor(int api, MethodVisitor mv) {
-            super(api, mv);
-        }
-
-        @Override
-        public void visitFieldInsn(int opcode, String owner, String name, String desc) {
-            if (opcode == Opcodes.PUTSTATIC && name.equals(ASSERTION_DISABLED_NAME)) {
-                super.visitInsn(Opcodes.POP); // enable assert
-            } else if (opcode == Opcodes.GETSTATIC && name.equals(ASSERTION_DISABLED_NAME)) {
-                mStartLoadingAssert = true;
-                super.visitFieldInsn(opcode, owner, name, desc);
-            } else {
-                super.visitFieldInsn(opcode, owner, name, desc);
-            }
-        }
-
-        @Override
-        public void visitJumpInsn(int opcode, Label label) {
-            if (mStartLoadingAssert && opcode == Opcodes.IFNE && mGotoLabel == null) {
-                mGotoLabel = label;
-            }
-            super.visitJumpInsn(opcode, label);
-        }
-
-        @Override
-        public void visitInsn(int opcode) {
-            if (!mStartLoadingAssert || opcode != Opcodes.ATHROW) {
-                super.visitInsn(opcode);
-            } else {
-                super.visitMethodInsn(Opcodes.INVOKESTATIC, BUILD_HOOKS, INSERT_INSTRUCTION_NAME,
-                        INSERT_INSTRUCTION_DESC, INSERT_INSTRUCTION_ITF);
-                super.visitJumpInsn(Opcodes.GOTO, mGotoLabel);
-                mStartLoadingAssert = false;
-                mGotoLabel = null;
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/build/android/bytecode/java/org/chromium/bytecode/ByteCodeProcessor.java b/build/android/bytecode/java/org/chromium/bytecode/ByteCodeProcessor.java
index 2a4d08a..485a42dd 100644
--- a/build/android/bytecode/java/org/chromium/bytecode/ByteCodeProcessor.java
+++ b/build/android/bytecode/java/org/chromium/bytecode/ByteCodeProcessor.java
@@ -46,9 +46,8 @@
  * Java application that takes in an input jar, performs a series of bytecode transformations,
  * and generates an output jar.
  *
- * Two types of transformations are performed:
- * 1) Enabling assertions via {@link AssertionEnablerClassAdapter}
- * 2) Providing support for custom resources via {@link CustomResourcesClassAdapter}
+ * One type of transformation are performed:
+ * 1) Providing support for custom resources via {@link CustomResourcesClassAdapter}
  */
 class ByteCodeProcessor {
     private static final String CLASS_FILE_SUFFIX = ".class";
@@ -56,7 +55,6 @@
     private static final int BUFFER_SIZE = 16384;
     private static boolean sVerbose;
     private static boolean sIsPrebuilt;
-    private static boolean sShouldAssert;
     private static boolean sShouldUseCustomResources;
     private static boolean sShouldUseThreadAnnotations;
     private static boolean sShouldCheckClassPath;
@@ -129,9 +127,6 @@
         if (sShouldUseThreadAnnotations) {
             chain = new ThreadAssertionClassAdapter(chain);
         }
-        if (sShouldAssert) {
-            chain = new AssertionEnablerClassAdapter(chain);
-        }
         if (sShouldUseCustomResources) {
             chain = new CustomResourcesClassAdapter(
                     chain, reader.getClassName(), reader.getSuperName(), sFullClassPathClassLoader);
@@ -252,7 +247,6 @@
         String outputJarPath = args[currIndex++];
         sVerbose = args[currIndex++].equals("--verbose");
         sIsPrebuilt = args[currIndex++].equals("--is-prebuilt");
-        sShouldAssert = args[currIndex++].equals("--enable-assert");
         sShouldUseCustomResources = args[currIndex++].equals("--enable-custom-resources");
         sShouldUseThreadAnnotations = args[currIndex++].equals("--enable-thread-annotations");
         sShouldCheckClassPath = args[currIndex++].equals("--enable-check-class-path");
diff --git a/build/android/gyp/bytecode_processor.py b/build/android/gyp/bytecode_processor.py
index 76775d3..86aa46ec 100755
--- a/build/android/gyp/bytecode_processor.py
+++ b/build/android/gyp/bytecode_processor.py
@@ -33,7 +33,6 @@
   parser.add_argument('-v', '--verbose', action='store_true')
   _AddSwitch(parser, '--is-prebuilt')
   _AddSwitch(parser, '--enable-custom-resources')
-  _AddSwitch(parser, '--enable-assert')
   _AddSwitch(parser, '--enable-thread-annotations')
   _AddSwitch(parser, '--enable-check-class-path')
   args = parser.parse_args(argv)
@@ -55,8 +54,8 @@
 
   cmd = ([
       args.script, args.input_jar, args.output_jar, verbose, args.is_prebuilt,
-      args.enable_assert, args.enable_custom_resources,
-      args.enable_thread_annotations, args.enable_check_class_path,
+      args.enable_custom_resources, args.enable_thread_annotations,
+      args.enable_check_class_path,
       str(len(sdk_jars))
   ] + sdk_jars + [str(len(direct_jars))] + direct_jars + extra_classpath_jars)
   subprocess.check_call(cmd)
diff --git a/build/android/gyp/dex.py b/build/android/gyp/dex.py
index 0285ce3..813b180 100755
--- a/build/android/gyp/dex.py
+++ b/build/android/gyp/dex.py
@@ -82,6 +82,11 @@
             'unobfuscated symbols present in the code. If not present, the jar '
             'is assumed not to be obfuscated.'))
 
+  parser.add_argument(
+      '--force-enable-assertions',
+      action='store_true',
+      help='Forcefully enable javac generated assertion code.')
+
   options = parser.parse_args(args)
 
   if options.dexlayout_profile:
@@ -427,6 +432,9 @@
   if options.min_api:
     dex_cmd += ['--min-api', options.min_api]
 
+  if options.force_enable_assertions:
+    dex_cmd += ['--force-enable-assertions']
+
   md5_check.CallAndWriteDepfileIfStale(
       lambda changes: _OnStaleMd5(changes, options, final_dex_inputs, dex_cmd),
       options,
diff --git a/build/android/gyp/proguard.py b/build/android/gyp/proguard.py
index 5acd831..aabbfeaf 100755
--- a/build/android/gyp/proguard.py
+++ b/build/android/gyp/proguard.py
@@ -161,6 +161,11 @@
       action='store_true',
       help='Disable -checkdiscard directives')
   parser.add_argument('--sourcefile', help='Value for source file attribute')
+  parser.add_argument(
+      '--force-enable-assertions',
+      action='store_true',
+      help='Forcefully enable javac generated assertion code.')
+
 
   options = parser.parse_args(args)
 
@@ -241,6 +246,9 @@
     if options.min_api:
       cmd += ['--min-api', options.min_api]
 
+    if options.force_enable_assertions:
+      cmd += ['--force-enable-assertions']
+
     if options.main_dex_rules_path:
       for main_dex_rule in options.main_dex_rules_path:
         cmd += ['--main-dex-rules', main_dex_rule]
diff --git a/build/android/pylib/utils/gold_utils.py b/build/android/pylib/utils/gold_utils.py
index 1702553e..28b0118 100644
--- a/build/android/pylib/utils/gold_utils.py
+++ b/build/android/pylib/utils/gold_utils.py
@@ -128,6 +128,11 @@
       authentication process. |output| is the stdout + stderr of the
       authentication process.
     """
+    if self._gold_properties.bypass_skia_gold_functionality:
+      logging.warning('Not actually authenticating with Gold due to '
+                      '--bypass-skia-gold-functionality being present.')
+      return 0, ''
+
     auth_cmd = [GOLDCTL_BINARY, 'auth', '--work-dir', self._working_dir]
     if use_luci:
       auth_cmd.append('--luci')
@@ -158,6 +163,11 @@
       comparison process. |output| is the stdout + stderr of the comparison
       process.
     """
+    if self._gold_properties.bypass_skia_gold_functionality:
+      logging.warning('Not actually comparing with Gold due to '
+                      '--bypass-skia-gold-functionality being present.')
+      return 0, ''
+
     compare_cmd = [
         GOLDCTL_BINARY,
         'imgtest',
@@ -243,6 +253,14 @@
       A tuple (return_code, output). |return_code| is the return code of the
       diff process. |output| is the stdout + stderr of the diff process.
     """
+    # Instead of returning that everything is okay and putting in dummy links,
+    # just fail since this should only be called when running locally and
+    # --bypass-skia-gold-functionality is only meant for use on the bots.
+    if self._gold_properties.bypass_skia_gold_functionality:
+      raise RuntimeError(
+          '--bypass-skia-gold-functionality is not supported when running '
+          'tests locally.')
+
     # Output managers only support archived files, not directories, so we have
     # to use a temporary directory and later move the data into the archived
     # files.
@@ -392,6 +410,7 @@
     self._job_id = None
     self._local_pixel_tests = None
     self._no_luci_auth = None
+    self._bypass_skia_gold_functionality = None
 
     # Could in theory be configurable, but hard-coded for now since there's
     # no plan to support anything else.
@@ -435,6 +454,10 @@
   def patchset(self):
     return self._patchset
 
+  @property
+  def bypass_skia_gold_functionality(self):
+    return self._bypass_skia_gold_functionality
+
   def _GetGitRevision(self):
     if not self._git_revision:
       # Automated tests should always pass the revision, so assume we're on
@@ -469,6 +492,9 @@
     if hasattr(args, 'no_luci_auth'):
       self._no_luci_auth = args.no_luci_auth
 
+    if hasattr(args, 'bypass_skia_gold_functionality'):
+      self._bypass_skia_gold_functionality = args.bypass_skia_gold_functionality
+
     # Will be automatically determined later if needed.
     if not hasattr(args, 'git_revision') or not args.git_revision:
       return
diff --git a/build/android/pylib/utils/gold_utils_test.py b/build/android/pylib/utils/gold_utils_test.py
index 924a6302..3882fe7 100755
--- a/build/android/pylib/utils/gold_utils_test.py
+++ b/build/android/pylib/utils/gold_utils_test.py
@@ -25,6 +25,7 @@
     'gerrit_issue',
     'gerrit_patchset',
     'buildbucket_id',
+    'bypass_skia_gold_functionality',
 ])
 
 
@@ -33,9 +34,11 @@
                        git_revision=None,
                        gerrit_issue=None,
                        gerrit_patchset=None,
-                       buildbucket_id=None):
+                       buildbucket_id=None,
+                       bypass_skia_gold_functionality=None):
   return _SkiaGoldArgs(local_pixel_tests, no_luci_auth, git_revision,
-                       gerrit_issue, gerrit_patchset, buildbucket_id)
+                       gerrit_issue, gerrit_patchset, buildbucket_id,
+                       bypass_skia_gold_functionality)
 
 
 def assertArgWith(test, arg_list, arg, value):
@@ -212,18 +215,34 @@
   @mock.patch('devil.utils.cmd_helper.GetCmdStatusOutputAndError')
   def test_commandOutputReturned(self, cmd_mock):
     cmd_mock.return_value = (1, 'Something bad :(', None)
+    args = createSkiaGoldArgs(git_revision='a')
+    sgp = gold_utils.SkiaGoldProperties(args)
     with tempfile_ext.NamedTemporaryDirectory() as working_dir:
-      session = gold_utils.SkiaGoldSession(working_dir, None)
+      session = gold_utils.SkiaGoldSession(working_dir, sgp)
       rc, stdout = session.Authenticate()
     self.assertEqual(cmd_mock.call_count, 1)
     self.assertEqual(rc, 1)
     self.assertEqual(stdout, 'Something bad :(')
 
   @mock.patch('devil.utils.cmd_helper.GetCmdStatusOutputAndError')
+  def test_bypassSkiaGoldFunctionality(self, cmd_mock):
+    cmd_mock.return_value = (None, None, None)
+    args = createSkiaGoldArgs(
+        git_revision='a', bypass_skia_gold_functionality=True)
+    sgp = gold_utils.SkiaGoldProperties(args)
+    with tempfile_ext.NamedTemporaryDirectory() as working_dir:
+      session = gold_utils.SkiaGoldSession(working_dir, sgp)
+      rc, _ = session.Authenticate()
+    self.assertEqual(rc, 0)
+    cmd_mock.assert_not_called()
+
+  @mock.patch('devil.utils.cmd_helper.GetCmdStatusOutputAndError')
   def test_commandWithUseLuciTrue(self, cmd_mock):
     cmd_mock.return_value = (None, None, None)
+    args = createSkiaGoldArgs(git_revision='a')
+    sgp = gold_utils.SkiaGoldProperties(args)
     with tempfile_ext.NamedTemporaryDirectory() as working_dir:
-      session = gold_utils.SkiaGoldSession(working_dir, None)
+      session = gold_utils.SkiaGoldSession(working_dir, sgp)
       session.Authenticate(use_luci=True)
     self.assertIn('--luci', cmd_mock.call_args[0][0])
 
@@ -250,8 +269,10 @@
   @mock.patch('devil.utils.cmd_helper.GetCmdStatusOutputAndError')
   def test_commandCommonArgs(self, cmd_mock):
     cmd_mock.return_value = (None, None, None)
+    args = createSkiaGoldArgs(git_revision='a')
+    sgp = gold_utils.SkiaGoldProperties(args)
     with tempfile_ext.NamedTemporaryDirectory() as working_dir:
-      session = gold_utils.SkiaGoldSession(working_dir, None)
+      session = gold_utils.SkiaGoldSession(working_dir, sgp)
       session.Authenticate()
     call_args = cmd_mock.call_args[0][0]
     self.assertIn('auth', call_args)
@@ -274,6 +295,18 @@
     self.assertEqual(stdout, 'Something bad :(')
 
   @mock.patch('devil.utils.cmd_helper.GetCmdStatusOutputAndError')
+  def test_bypassSkiaGoldFunctionality(self, cmd_mock):
+    cmd_mock.return_value = (None, None, None)
+    args = createSkiaGoldArgs(
+        git_revision='a', bypass_skia_gold_functionality=True)
+    sgp = gold_utils.SkiaGoldProperties(args)
+    with tempfile_ext.NamedTemporaryDirectory() as working_dir:
+      session = gold_utils.SkiaGoldSession(working_dir, sgp)
+      rc, _ = session.Compare(None, None, None, None)
+    self.assertEqual(rc, 0)
+    cmd_mock.assert_not_called()
+
+  @mock.patch('devil.utils.cmd_helper.GetCmdStatusOutputAndError')
   def test_commandWithLocalPixelTestsTrue(self, cmd_mock):
     cmd_mock.return_value = (None, None, None)
     args = createSkiaGoldArgs(git_revision='a', local_pixel_tests=True)
@@ -426,6 +459,17 @@
     self.assertEqual(stdout, 'Something bad :(')
 
   @mock.patch('devil.utils.cmd_helper.GetCmdStatusOutputAndError')
+  def test_bypassSkiaGoldFunctionality(self, cmd_mock):
+    cmd_mock.return_value = (None, None, None)
+    args = createSkiaGoldArgs(
+        git_revision='a', bypass_skia_gold_functionality=True)
+    sgp = gold_utils.SkiaGoldProperties(args)
+    with tempfile_ext.NamedTemporaryDirectory() as working_dir:
+      session = gold_utils.SkiaGoldSession(working_dir, sgp)
+      with self.assertRaises(RuntimeError):
+        session.Diff(None, None, None, None)
+
+  @mock.patch('devil.utils.cmd_helper.GetCmdStatusOutputAndError')
   def test_commandCommonArgs(self, cmd_mock):
     cmd_mock.return_value = (None, None, None)
     args = createSkiaGoldArgs(git_revision='a', local_pixel_tests=False)
@@ -497,6 +541,8 @@
     self.assertEqual(instance._issue, expected.get('gerrit_issue'))
     self.assertEqual(instance._patchset, expected.get('gerrit_patchset'))
     self.assertEqual(instance._job_id, expected.get('buildbucket_id'))
+    self.assertEqual(instance._bypass_skia_gold_functionality,
+                     expected.get('bypass_skia_gold_functionality'))
 
   def test_initializeSkiaGoldAttributes_unsetLocal(self):
     args = createSkiaGoldArgs()
@@ -518,6 +564,11 @@
     sgp = gold_utils.SkiaGoldProperties(args)
     self.verifySkiaGoldProperties(sgp, {'no_luci_auth': True})
 
+  def test_initializeSkiaGoldAttributes_bypassExplicitTrue(self):
+    args = createSkiaGoldArgs(bypass_skia_gold_functionality=True)
+    sgp = gold_utils.SkiaGoldProperties(args)
+    self.verifySkiaGoldProperties(sgp, {'bypass_skia_gold_functionality': True})
+
   def test_initializeSkiaGoldAttributes_explicitGitRevision(self):
     args = createSkiaGoldArgs(git_revision='a')
     sgp = gold_utils.SkiaGoldProperties(args)
diff --git a/build/android/test_runner.py b/build/android/test_runner.py
index 539210f..fdf43e0 100755
--- a/build/android/test_runner.py
+++ b/build/android/test_runner.py
@@ -588,6 +588,13 @@
       help="Don't use the serve account provided by LUCI for authentication "
       'with Skia Gold, instead relying on gsutil to be pre-authenticated. '
       'Meant for testing locally instead of on the bots.')
+  parser.add_argument(
+      '--bypass-skia-gold-functionality',
+      action='store_true',
+      default=False,
+      help='Bypass all interaction with Skia Gold, effectively disabling the '
+      'image comparison portion of any tests that use Gold. Only meant to be '
+      'used in case a Gold outage occurs and cannot be fixed quickly.')
 
 
 def AddJUnitTestOptions(parser):
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index 40332f7..d40ac31 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -1077,6 +1077,13 @@
         inputs += [ _proguard_jar_path ]
       }
 
+      _enable_assert = is_java_debug || dcheck_always_on || report_java_assert
+      if (_enable_assert) {
+        # The default for generating dex file format is
+        # --force-disable-assertions.
+        args += [ "--force-enable-assertions" ]
+      }
+
       if (defined(invoker.args)) {
         args += invoker.args
       }
@@ -1439,6 +1446,13 @@
           "--r8-jar-path",
           rebase_path(_r8_path, root_build_dir),
         ]
+
+        _enable_assert = is_java_debug || dcheck_always_on || report_java_assert
+        if (_enable_assert) {
+          # The default for generating dex file format is
+          # --force-disable-assertions.
+          args += [ "--force-enable-assertions" ]
+        }
       }
     }
   }
@@ -1522,10 +1536,6 @@
     _input_jar_path = invoker.input_jar_path
     _output_jar_path = invoker.output_jar_path
 
-    _enable_assert =
-        defined(invoker.enable_build_hooks) && invoker.enable_build_hooks &&
-        (is_java_debug || dcheck_always_on || report_java_assert)
-
     _enable_custom_resources = defined(invoker.enable_build_hooks_android) &&
                                invoker.enable_build_hooks_android
 
@@ -1537,7 +1547,7 @@
     _skip_jetify = defined(invoker.skip_jetify) && invoker.skip_jetify
 
     _enable_bytecode_rewriter =
-        _enable_assert || _enable_custom_resources || _enable_thread_annotations
+        _enable_custom_resources || _enable_thread_annotations
     _is_prebuilt = defined(invoker.is_prebuilt) && invoker.is_prebuilt
     _enable_bytecode_checks = !defined(invoker.enable_bytecode_checks) ||
                               invoker.enable_bytecode_checks
@@ -1552,7 +1562,6 @@
                ])
     if (defined(invoker.enable_bytecode_rewriter)) {
       not_needed([
-                   "_enable_assert",
                    "_enable_custom_resources",
                    "_enable_thread_annotations",
                  ])
@@ -1644,9 +1653,6 @@
         if (_is_prebuilt) {
           args += [ "--is-prebuilt" ]
         }
-        if (_enable_assert) {
-          args += [ "--enable-assert" ]
-        }
         if (_enable_custom_resources) {
           args += [ "--enable-custom-resources" ]
         }
@@ -3542,7 +3548,6 @@
                                  ])
           is_prebuilt = _is_prebuilt
           supports_android = _supports_android
-          enable_build_hooks = _enable_build_hooks
           enable_build_hooks_android = _enable_build_hooks_android
           build_config = _build_config
           input_jar_path = _unprocessed_jar_path
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index 49d6640..13a9631 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -4076,6 +4076,7 @@
   #       See also extract_native_libraries.
   #   ignore_proguard_configs: Whether to ignore proguard configs.
   #   strip_resources: Whether to ignore android resources found in the .aar.
+  #   custom_package: Java package for generated R.java files.
   #   extract_native_libraries: Whether to extract .so files found in the .aar.
   #       If the file contains .so, either extract_native_libraries or
   #       ignore_native_libraries must be set.
@@ -4205,6 +4206,7 @@
       android_resources(_res_target_name) {
         forward_variables_from(invoker,
                                [
+                                 "custom_package",
                                  "create_srcjar",
                                  "deps",
                                  "testonly",
@@ -4214,8 +4216,8 @@
           deps = []
         }
         deps += [ ":$_unpack_target_name" ]
-        android_manifest_dep = ":$_unpack_target_name"
         if (!_ignore_manifest) {
+          android_manifest_dep = ":$_unpack_target_name"
           android_manifest = "${_output_path}/AndroidManifest.xml"
         }
         sources = []
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 55de6e6..e6a98ecee 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-0.20200326.1.1
\ No newline at end of file
+0.20200326.2.1
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 55de6e6..e6a98ecee 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-0.20200326.1.1
\ No newline at end of file
+0.20200326.2.1
\ No newline at end of file
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogTest.java
index c4876e9..cabdd9e 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogTest.java
@@ -72,6 +72,7 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.ChromeActivity;
@@ -560,6 +561,7 @@
     @Test
     @MediumTest
     @Feature({"RenderTest"})
+    @DisabledTest(message = "crbug.com/1065242")
     public void testRenderDialog_3Tabs_Landscape() throws Exception {
         final ChromeTabbedActivity cta = mActivityTestRule.getActivity();
         prepareTabsWithThumbnail(mActivityTestRule, 3, 0, "about:blank");
diff --git a/chrome/android/java/monochrome_public_bundle__base_bundle_module.AndroidManifest.expected b/chrome/android/java/monochrome_public_bundle__base_bundle_module.AndroidManifest.expected
index 3900b7e..e58e0c33 100644
--- a/chrome/android/java/monochrome_public_bundle__base_bundle_module.AndroidManifest.expected
+++ b/chrome/android/java/monochrome_public_bundle__base_bundle_module.AndroidManifest.expected
@@ -80,7 +80,6 @@
       android:protectionLevel="signature"/>
   <application
       android:allowBackup="false"
-      android:appComponentFactory="androidx.core.app.CoreComponentFactory"
       android:extractNativeLibs="false"
       android:icon="@drawable/ic_launcher"
       android:label="@string/app_name"
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/Layout.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/Layout.java
index a9159300..badc5bfa 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/Layout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/Layout.java
@@ -24,7 +24,6 @@
 import org.chromium.chrome.browser.compositor.scene_layer.SceneOverlayLayer;
 import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
 import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.browser.tab.TabImpl;
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabModelUtils;
@@ -885,16 +884,4 @@
     protected void updateSceneLayer(RectF viewport, RectF contentViewport,
             LayerTitleCache layerTitleCache, TabContentManager tabContentManager,
             ResourceManager resourceManager, ChromeFullscreenManager fullscreenManager) {}
-
-    /**
-     * Gets the full screen manager.
-     * @return The {@link ChromeFullscreenManager} manager, possibly null
-     */
-    public ChromeFullscreenManager getFullscreenManager() {
-        if (mTabModelSelector == null) return null;
-        Tab tab = mTabModelSelector.getCurrentTab();
-        if (tab == null) return null;
-        if (((TabImpl) tab).getActivity() == null) return null;
-        return ((TabImpl) tab).getActivity().getFullscreenManager();
-    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/JourneyLogger.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/JourneyLogger.java
index 49f2d09a..6d1fada 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/JourneyLogger.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/JourneyLogger.java
@@ -211,6 +211,15 @@
         JourneyLoggerJni.get().setTriggerTime(mJourneyLoggerAndroid, JourneyLogger.this);
     }
 
+    /**
+     * Sets the ukm source id of payment app.
+     * @param sourceId A long indicating the ukm source id of the invoked payment app.
+     */
+    public void setPaymentAppUkmSourceId(long sourceId) {
+        JourneyLoggerJni.get().setPaymentAppUkmSourceId(
+                mJourneyLoggerAndroid, JourneyLogger.this, sourceId);
+    }
+
     @NativeMethods
     interface Natives {
         long initJourneyLoggerAndroid(
@@ -241,5 +250,7 @@
         void recordTransactionAmount(long nativeJourneyLoggerAndroid, JourneyLogger caller,
                 String currency, String value, boolean completed);
         void setTriggerTime(long nativeJourneyLoggerAndroid, JourneyLogger caller);
+        void setPaymentAppUkmSourceId(
+                long nativeJourneyLoggerAndroid, JourneyLogger caller, long sourceId);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentApp.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentApp.java
index a644bc89..fd16b971 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentApp.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentApp.java
@@ -351,4 +351,11 @@
     public Set<String> getApplicationIdentifiersThatHideThisApp() {
         return null;
     }
+
+    /**
+     * @return The ukm source id assigned to the payment app.
+     */
+    public long getUkmSourceId() {
+        return 0;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
index b9f5b54..9d354ad 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
@@ -1380,8 +1380,15 @@
         mPaymentHandlerUi = new PaymentHandlerCoordinator();
         ChromeActivity chromeActivity = ChromeActivity.fromWebContents(mWebContents);
         if (chromeActivity == null) return false;
-        return mPaymentHandlerUi.show(chromeActivity, url, mIsIncognito,
+
+        boolean success = mPaymentHandlerUi.show(chromeActivity, url, mIsIncognito,
                 paymentHandlerWebContentsObserver, /*uiObserver=*/this);
+        if (success) {
+            // UKM for payment app origin should get recorded only when the origin of the invoked
+            // payment app is shown to the user.
+            mJourneyLogger.setPaymentAppUkmSourceId(mInvokedPaymentApp.getUkmSourceId());
+        }
+        return success;
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentApp.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentApp.java
index 43ece8c..0edc3b62 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentApp.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentApp.java
@@ -62,6 +62,9 @@
     /** Whether the app is ready for minial UI flow. */
     private boolean mIsReadyForMinimalUI;
 
+    /** UKM source Id generated using the app's origin. */
+    private long mUkmSourceId;
+
     /** The account balance to be used in the minimal UI flow. */
     @Nullable
     private String mAccountBalance;
@@ -147,6 +150,7 @@
         mAppName = name;
         mSwUri = null;
         mUseCache = false;
+        mUkmSourceId = 0;
     }
 
     /**
@@ -193,6 +197,7 @@
         mAppName = name;
         mSwUri = swUri;
         mUseCache = useCache;
+        mUkmSourceId = 0;
     }
 
     /**
@@ -370,4 +375,12 @@
     public Set<String> getApplicationIdentifiersThatHideThisApp() {
         return mPreferredRelatedApplicationIds;
     }
+
+    @Override
+    public long getUkmSourceId() {
+        if (mUkmSourceId == 0) {
+            mUkmSourceId = ServiceWorkerPaymentAppBridge.getSourceIdForPaymentAppFromScope(mScope);
+        }
+        return mUkmSourceId;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentAppBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentAppBridge.java
index 229e09e8..fe255f4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentAppBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentAppBridge.java
@@ -460,6 +460,15 @@
                 webContents, PaymentEventResponseType.PAYMENT_HANDLER_WINDOW_CLOSING);
     }
 
+    /**
+     * Get the ukm source id for the invoked payment app.
+     * @param swScope The scope of the invoked payment app.
+     */
+    public static long getSourceIdForPaymentAppFromScope(URI swScope) {
+        return ServiceWorkerPaymentAppBridgeJni.get().getSourceIdForPaymentAppFromScope(
+                swScope.toString());
+    }
+
     @CalledByNative
     private static String getSupportedMethodFromMethodData(PaymentMethodData data) {
         return data.supportedMethod;
@@ -685,5 +694,6 @@
                 PaymentDetailsModifier[] modifiers, String currency, PaymentHandlerFinder callback,
                 ServiceWorkerPaymentApp app);
         void onClosingPaymentAppWindow(WebContents webContents, int reason);
+        long getSourceIdForPaymentAppFromScope(String swScope);
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarLayoutTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarLayoutTest.java
index da0f564..2e2eadf 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarLayoutTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarLayoutTest.java
@@ -5,10 +5,15 @@
 package org.chromium.chrome.browser.omnibox;
 
 import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
 
+import static org.hamcrest.Matchers.not;
+
 import android.support.test.filters.SmallTest;
 import android.view.View;
 import android.widget.ImageButton;
@@ -164,10 +169,11 @@
     }
 
     private void setUrlBarTextAndFocus(String text) throws ExecutionException {
+        onView(withId(R.id.url_bar)).perform(click());
+
         TestThreadUtils.runOnUiThreadBlocking(new Callable<Void>() {
             @Override
             public Void call() throws InterruptedException {
-                getLocationBar().onUrlFocusChange(true);
                 mActivityTestRule.typeInOmnibox(text, false);
                 return null;
             }
@@ -178,20 +184,22 @@
     @SmallTest
     @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
     public void testNotShowingVoiceSearchButtonIfUrlBarContainsText() throws ExecutionException {
+        // When there is text, the delete button should be visible.
         setUrlBarTextAndFocus("testing");
 
-        Assert.assertEquals(getDeleteButton().getVisibility(), VISIBLE);
-        Assert.assertNotEquals(getMicButton().getVisibility(), VISIBLE);
+        onView(withId(R.id.delete_button)).check(matches(isDisplayed()));
+        onView(withId(R.id.mic_button)).check(matches(not(isDisplayed())));
     }
 
     @Test
     @SmallTest
     @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
     public void testShowingVoiceSearchButtonIfUrlBarIsEmpty() throws ExecutionException {
+        // When there's no text, the mic button should be visible.
         setUrlBarTextAndFocus("");
 
-        Assert.assertNotEquals(getDeleteButton().getVisibility(), VISIBLE);
-        Assert.assertEquals(getMicButton().getVisibility(), VISIBLE);
+        onView(withId(R.id.mic_button)).check(matches(isDisplayed()));
+        onView(withId(R.id.delete_button)).check(matches(not(isDisplayed())));
     }
 
     @Test
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 43221a9..1b28de45 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3106,6 +3106,8 @@
       "apps/app_service/app_service_proxy.h",
       "apps/app_service/app_service_proxy_factory.cc",
       "apps/app_service/app_service_proxy_factory.h",
+      "apps/app_service/browser_app_launcher.cc",
+      "apps/app_service/browser_app_launcher.h",
       "apps/app_service/dip_px_util.cc",
       "apps/app_service/dip_px_util.h",
       "apps/app_service/launch_utils.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 321a91a..2a901b0 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -4017,6 +4017,13 @@
      kOsAll,
      FEATURE_VALUE_TYPE(features::kEnableAmbientAuthenticationInGuestSession)},
 
+    {"enable-send-tab-to-self-omnibox-sending-animation",
+     flag_descriptions::kSendTabToSelfOmniboxSendingAnimationName,
+     flag_descriptions::kSendTabToSelfOmniboxSendingAnimationDescription,
+     kOsDesktop,
+     FEATURE_VALUE_TYPE(
+         send_tab_to_self::kSendTabToSelfOmniboxSendingAnimation)},
+
     {"enable-send-tab-to-self-when-signed-in",
      flag_descriptions::kSendTabToSelfWhenSignedInName,
      flag_descriptions::kSendTabToSelfWhenSignedInDescription, kOsAll,
diff --git a/chrome/browser/apps/app_service/app_service_proxy.cc b/chrome/browser/apps/app_service/app_service_proxy.cc
index 77f913d..28cc51a3 100644
--- a/chrome/browser/apps/app_service/app_service_proxy.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy.cc
@@ -119,6 +119,8 @@
     return;
   }
 
+  browser_app_launcher_ = std::make_unique<apps::BrowserAppLauncher>(profile_);
+
   app_service_impl_ =
       std::make_unique<apps::AppServiceImpl>(profile_->GetPrefs());
   app_service_impl_->BindReceiver(app_service_.BindNewPipeAndPassReceiver());
@@ -174,6 +176,10 @@
 }
 #endif
 
+BrowserAppLauncher& AppServiceProxy::BrowserAppLauncher() {
+  return *browser_app_launcher_;
+}
+
 apps::PreferredApps& AppServiceProxy::PreferredApps() {
   return preferred_apps_;
 }
diff --git a/chrome/browser/apps/app_service/app_service_proxy.h b/chrome/browser/apps/app_service/app_service_proxy.h
index 7124481..1ebadfd0 100644
--- a/chrome/browser/apps/app_service/app_service_proxy.h
+++ b/chrome/browser/apps/app_service/app_service_proxy.h
@@ -13,6 +13,7 @@
 #include "base/containers/unique_ptr_adapters.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
+#include "chrome/browser/apps/app_service/browser_app_launcher.h"
 #include "chrome/services/app_service/public/cpp/app_registry_cache.h"
 #include "chrome/services/app_service/public/cpp/icon_cache.h"
 #include "chrome/services/app_service/public/cpp/icon_coalescer.h"
@@ -73,12 +74,15 @@
 
   mojo::Remote<apps::mojom::AppService>& AppService();
   apps::AppRegistryCache& AppRegistryCache();
-  apps::PreferredApps& PreferredApps();
 
 #if defined(OS_CHROMEOS)
   apps::InstanceRegistry& InstanceRegistry();
 #endif
 
+  BrowserAppLauncher& BrowserAppLauncher();
+
+  apps::PreferredApps& PreferredApps();
+
   // apps::IconLoader overrides.
   apps::mojom::IconKeyPtr GetIconKey(const std::string& app_id) override;
   std::unique_ptr<IconLoader::Releaser> LoadIconFromIconKey(
@@ -364,6 +368,11 @@
 
   Profile* profile_;
 
+  // TODO(crbug.com/1061843): Remove BrowserAppLauncher and merge the interfaces
+  // to AppServiceProxy when publishers(ExtensionApps and WebApps) can run on
+  // Chrome.
+  std::unique_ptr<apps::BrowserAppLauncher> browser_app_launcher_;
+
   using UninstallDialogs = std::set<std::unique_ptr<apps::UninstallDialog>,
                                     base::UniquePtrComparator>;
   UninstallDialogs uninstall_dialogs_;
diff --git a/chrome/browser/apps/app_service/browser_app_launcher.cc b/chrome/browser/apps/app_service/browser_app_launcher.cc
new file mode 100644
index 0000000..0f1edf1b
--- /dev/null
+++ b/chrome/browser/apps/app_service/browser_app_launcher.cc
@@ -0,0 +1,59 @@
+// Copyright 2020 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/apps/app_service/browser_app_launcher.h"
+
+#include "base/command_line.h"
+#include "base/feature_list.h"
+#include "base/files/file_path.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/extensions/application_launch.h"
+#include "chrome/browser/ui/web_applications/web_app_launch_manager.h"
+#include "chrome/common/chrome_features.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/extension.h"
+
+namespace apps {
+
+BrowserAppLauncher::BrowserAppLauncher(Profile* profile) : profile_(profile) {
+  if (base::FeatureList::IsEnabled(features::kDesktopPWAsWithoutExtensions) ||
+      base::FeatureList::IsEnabled(features::kDesktopPWAsUnifiedLaunch)) {
+    web_app_launch_manager_ =
+        std::make_unique<web_app::WebAppLaunchManager>(profile);
+  }
+}
+
+BrowserAppLauncher::~BrowserAppLauncher() = default;
+
+void BrowserAppLauncher::LaunchAppWithCallback(
+    const std::string& app_id,
+    const base::FilePath& current_directory,
+    base::OnceCallback<void(Browser* browser,
+                            apps::mojom::LaunchContainer container)> callback) {
+  // TODO(crbug.com/1061843): Remove command_line from AppLaunchParams, and get
+  // command line from the current process in GetLaunchFilesFromCommandLine
+  auto& command_line = *base::CommandLine::ForCurrentProcess();
+
+  // old-style app shortcuts
+  if (app_id.empty()) {
+    ::LaunchAppWithCallback(profile_, app_id, command_line, current_directory,
+                            std::move(callback));
+    return;
+  }
+
+  const extensions::Extension* extension =
+      extensions::ExtensionRegistry::Get(profile_)->GetInstalledExtension(
+          app_id);
+  if ((!extension || extension->from_bookmark()) && web_app_launch_manager_) {
+    web_app_launch_manager_->LaunchApplication(
+        app_id, command_line, current_directory, std::move(callback));
+    return;
+  }
+
+  ::LaunchAppWithCallback(profile_, app_id, command_line, current_directory,
+                          std::move(callback));
+}
+
+}  // namespace apps
diff --git a/chrome/browser/apps/app_service/browser_app_launcher.h b/chrome/browser/apps/app_service/browser_app_launcher.h
new file mode 100644
index 0000000..3812e8dd
--- /dev/null
+++ b/chrome/browser/apps/app_service/browser_app_launcher.h
@@ -0,0 +1,60 @@
+// Copyright 2020 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_APPS_APP_SERVICE_BROWSER_APP_LAUNCHER_H_
+#define CHROME_BROWSER_APPS_APP_SERVICE_BROWSER_APP_LAUNCHER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "components/services/app_service/public/mojom/types.mojom.h"
+
+class Browser;
+class Profile;
+
+namespace base {
+class FilePath;
+}  // namespace base
+
+namespace web_app {
+class WebAppLaunchManager;
+}  // namespace web_app
+
+namespace apps {
+
+// BrowserAppLauncher receives app launch requests and forwards them to
+// extensions or WebAppLaunchManager, based on the app type.
+//
+// TODO(crbug.com/1061843): Remove BrowserAppLauncher and merge the interfaces
+// to AppServiceProxy when publishers(ExtensionApps and WebApps) can run on
+// Chrome.
+class BrowserAppLauncher {
+ public:
+  explicit BrowserAppLauncher(Profile* profile);
+  ~BrowserAppLauncher();
+
+  BrowserAppLauncher(const BrowserAppLauncher&) = delete;
+  BrowserAppLauncher& operator=(const BrowserAppLauncher&) = delete;
+
+  // Attempt to open |app_id| in a new window or tab. Open an empty browser
+  // window if unsuccessful. The user's preferred launch container for the app
+  // (standalone window or browser tab) is used. |callback| will be called with
+  // the container type used to open the app, kLaunchContainerNone if an empty
+  // browser window was opened.
+  void LaunchAppWithCallback(
+      const std::string& app_id,
+      const base::FilePath& current_directory,
+      base::OnceCallback<void(Browser* browser,
+                              apps::mojom::LaunchContainer container)>
+          callback);
+
+ private:
+  Profile* const profile_;
+  std::unique_ptr<web_app::WebAppLaunchManager> web_app_launch_manager_;
+};
+
+}  // namespace apps
+
+#endif  // CHROME_BROWSER_APPS_APP_SERVICE_BROWSER_APP_LAUNCHER_H_
diff --git a/chrome/browser/apps/app_service/web_apps.cc b/chrome/browser/apps/app_service/web_apps.cc
index f7e779d8..b41bc9f 100644
--- a/chrome/browser/apps/app_service/web_apps.cc
+++ b/chrome/browser/apps/app_service/web_apps.cc
@@ -595,6 +595,7 @@
   app->name = web_app->name();
   app->short_name = web_app->name();
   app->description = web_app->description();
+  app->additional_search_terms = web_app->additional_search_terms();
 
   bool paused = paused_apps_.IsPaused(web_app->app_id());
   app->icon_key =
diff --git a/chrome/browser/apps/launch_service/extension_app_launch_manager.cc b/chrome/browser/apps/launch_service/extension_app_launch_manager.cc
index 42a50ff9..d3023f89 100644
--- a/chrome/browser/apps/launch_service/extension_app_launch_manager.cc
+++ b/chrome/browser/apps/launch_service/extension_app_launch_manager.cc
@@ -43,26 +43,4 @@
   return ::OpenApplication(profile(), params);
 }
 
-void ExtensionAppLaunchManager::LaunchApplication(
-    const std::string& app_id,
-    const base::CommandLine& command_line,
-    const base::FilePath& current_directory,
-    base::OnceCallback<void(Browser* browser,
-                            apps::mojom::LaunchContainer container)> callback) {
-  apps::mojom::LaunchContainer container;
-  if (OpenExtensionApplicationWindow(profile(), app_id, command_line,
-                                     current_directory)) {
-    RecordBookmarkLaunch(profile(), app_id);
-    container = apps::mojom::LaunchContainer::kLaunchContainerWindow;
-  } else if (OpenExtensionApplicationTab(profile(), app_id)) {
-    container = apps::mojom::LaunchContainer::kLaunchContainerTab;
-  } else {
-    // Open an empty browser window as the app_id is invalid.
-    CreateBrowserWithNewTabPage(profile());
-    container = apps::mojom::LaunchContainer::kLaunchContainerNone;
-  }
-  std::move(callback).Run(BrowserList::GetInstance()->GetLastActive(),
-                          container);
-}
-
 }  // namespace apps
diff --git a/chrome/browser/apps/launch_service/extension_app_launch_manager.h b/chrome/browser/apps/launch_service/extension_app_launch_manager.h
index fab74e4..75758f2d8 100644
--- a/chrome/browser/apps/launch_service/extension_app_launch_manager.h
+++ b/chrome/browser/apps/launch_service/extension_app_launch_manager.h
@@ -22,14 +22,6 @@
   // apps::LaunchManager:
   content::WebContents* OpenApplication(const AppLaunchParams& params) override;
 
-  void LaunchApplication(
-      const std::string& app_id,
-      const base::CommandLine& command_line,
-      const base::FilePath& current_directory,
-      base::OnceCallback<void(Browser* browser,
-                              apps::mojom::LaunchContainer container)> callback)
-      override;
-
  private:
   DISALLOW_COPY_AND_ASSIGN(ExtensionAppLaunchManager);
 };
diff --git a/chrome/browser/apps/launch_service/launch_manager.h b/chrome/browser/apps/launch_service/launch_manager.h
index 8ca4a123..2ca2699 100644
--- a/chrome/browser/apps/launch_service/launch_manager.h
+++ b/chrome/browser/apps/launch_service/launch_manager.h
@@ -6,20 +6,12 @@
 #define CHROME_BROWSER_APPS_LAUNCH_SERVICE_LAUNCH_MANAGER_H_
 
 #include <string>
-#include <vector>
 
-#include "base/callback.h"
 #include "base/macros.h"
 #include "components/services/app_service/public/mojom/types.mojom.h"
 
-class Browser;
 class Profile;
 
-namespace base {
-class CommandLine;
-class FilePath;
-}  // namespace base
-
 namespace content {
 class WebContents;
 }
@@ -37,19 +29,6 @@
   virtual content::WebContents* OpenApplication(
       const AppLaunchParams& params) = 0;
 
-  // Attempt to open |app_id| in a new window or tab. Open an empty browser
-  // window if unsuccessful. The user's preferred launch container for the app
-  // (standalone window or browser tab) is used. |callback| will be called with
-  // the container type used to open the app, kLaunchContainerNone if an empty
-  // browser window was opened.
-  virtual void LaunchApplication(
-      const std::string& app_id,
-      const base::CommandLine& command_line,
-      const base::FilePath& current_directory,
-      base::OnceCallback<void(Browser* browser,
-                              apps::mojom::LaunchContainer container)>
-          callback) = 0;
-
  protected:
   explicit LaunchManager(Profile*);
   Profile* profile() { return profile_; }
diff --git a/chrome/browser/apps/launch_service/launch_service.cc b/chrome/browser/apps/launch_service/launch_service.cc
index cbbd0e8..6e4727e 100644
--- a/chrome/browser/apps/launch_service/launch_service.cc
+++ b/chrome/browser/apps/launch_service/launch_service.cc
@@ -10,7 +10,6 @@
 #include "chrome/browser/apps/launch_service/launch_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/web_applications/web_app_launch_manager.h"
-#include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/common/chrome_features.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/common/extension.h"
@@ -48,16 +47,6 @@
   return GetLaunchManagerForApp(params.app_id).OpenApplication(params);
 }
 
-void LaunchService::LaunchApplication(
-    const std::string& app_id,
-    const base::CommandLine& command_line,
-    const base::FilePath& current_directory,
-    base::OnceCallback<void(Browser* browser,
-                            apps::mojom::LaunchContainer container)> callback) {
-  GetLaunchManagerForApp(app_id).LaunchApplication(
-      app_id, command_line, current_directory, std::move(callback));
-}
-
 LaunchManager& LaunchService::GetLaunchManagerForApp(
     const std::string& app_id) {
   // --app old-style app shortcuts
diff --git a/chrome/browser/apps/launch_service/launch_service.h b/chrome/browser/apps/launch_service/launch_service.h
index 50183fa..a779117a5 100644
--- a/chrome/browser/apps/launch_service/launch_service.h
+++ b/chrome/browser/apps/launch_service/launch_service.h
@@ -8,19 +8,12 @@
 #include <memory>
 #include <string>
 
-#include "base/callback.h"
 #include "base/macros.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/services/app_service/public/mojom/types.mojom.h"
 
-class Browser;
 class Profile;
 
-namespace base {
-class CommandLine;
-class FilePath;
-}  // namespace base
-
 namespace content {
 class WebContents;
 }
@@ -47,19 +40,6 @@
   // Open the application in a way specified by |params|.
   content::WebContents* OpenApplication(const AppLaunchParams& params);
 
-  // Attempt to open |app_id| in a new window or tab. Open an empty browser
-  // window if unsuccessful. The user's preferred launch container for the app
-  // (standalone window or browser tab) is used. |callback| will be called with
-  // the container type used to open the app, kLaunchContainerNone if an empty
-  // browser window was opened.
-  void LaunchApplication(
-      const std::string& app_id,
-      const base::CommandLine& command_line,
-      const base::FilePath& current_directory,
-      base::OnceCallback<void(Browser* browser,
-                              apps::mojom::LaunchContainer container)>
-          callback);
-
  private:
   LaunchManager& GetLaunchManagerForApp(const std::string& app_id);
 
diff --git a/chrome/browser/browser_keyevents_browsertest.cc b/chrome/browser/browser_keyevents_browsertest.cc
index b31f168..7f22a582 100644
--- a/chrome/browser/browser_keyevents_browsertest.cc
+++ b/chrome/browser/browser_keyevents_browsertest.cc
@@ -294,13 +294,7 @@
   }
 };
 
-#if defined(OS_MACOSX)
-// http://crbug.com/81451
-#define MAYBE_NormalKeyEvents DISABLED_NormalKeyEvents
-#else
-#define MAYBE_NormalKeyEvents NormalKeyEvents
-#endif
-IN_PROC_BROWSER_TEST_F(BrowserKeyEventsTest, MAYBE_NormalKeyEvents) {
+IN_PROC_BROWSER_TEST_F(BrowserKeyEventsTest, NormalKeyEvents) {
   static const KeyEventTestData kTestNoInput[] = {
     // a
     { ui::VKEY_A, false, false, false, false,
@@ -701,9 +695,6 @@
 
 #if defined(OS_MACOSX)
 IN_PROC_BROWSER_TEST_F(BrowserKeyEventsTest, EditorKeyBindings) {
-  // TODO(kbr): re-enable: http://crbug.com/222296
-  return;
-
   static const KeyEventTestData kTestCtrlA = {
     ui::VKEY_A, true, false, false, false,
     false, false, false, false, 4,
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 507294c6..5d29da10 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -111,6 +111,7 @@
     "//chromeos/components/quick_answers/public/cpp:prefs",
     "//chromeos/components/smbfs",
     "//chromeos/components/smbfs/mojom",
+    "//chromeos/components/sync_wifi",
     "//chromeos/components/tether",
     "//chromeos/constants",
     "//chromeos/cryptohome",
@@ -1272,6 +1273,10 @@
     "input_method/input_method_syncer.h",
     "input_method/native_input_method_engine.cc",
     "input_method/native_input_method_engine.h",
+    "input_method/personal_info_suggester.cc",
+    "input_method/personal_info_suggester.h",
+    "input_method/suggester.h",
+    "input_method/suggestion_enums.h",
     "input_method/suggestion_window_controller.cc",
     "input_method/suggestion_window_controller.h",
     "input_method/suggestion_window_controller_impl.cc",
diff --git a/chrome/browser/chromeos/accessibility/speech_monitor.cc b/chrome/browser/chromeos/accessibility/speech_monitor.cc
index ac0ce3b..0d84ff4 100644
--- a/chrome/browser/chromeos/accessibility/speech_monitor.cc
+++ b/chrome/browser/chromeos/accessibility/speech_monitor.cc
@@ -214,6 +214,21 @@
        "ExpectNextSpeechIsNot(\"" + text + "\") " + location.ToString()});
 }
 
+void SpeechMonitor::ExpectNextSpeechIsNotPattern(
+    const std::string& pattern,
+    const base::Location& location) {
+  CHECK(!replay_loop_runner_.get());
+  replay_queue_.push_back({[this, pattern]() {
+                             if (utterance_queue_.empty())
+                               return false;
+
+                             return !base::MatchPattern(
+                                 utterance_queue_.front().text, pattern);
+                           },
+                           "ExpectNextSpeechIsNotPattern(\"" + pattern +
+                               "\") " + location.ToString()});
+}
+
 void SpeechMonitor::Call(std::function<void()> func,
                          const base::Location& location) {
   CHECK(!replay_loop_runner_.get());
@@ -230,9 +245,19 @@
 }
 
 void SpeechMonitor::MaybeContinueReplay() {
+  // This method can be called prior to Replay() being called.
+  if (!replay_called_)
+    return;
+
   auto it = replay_queue_.begin();
   while (it != replay_queue_.end()) {
     if (it->first()) {
+      // Careful here; the above callback may have triggered more speech which
+      // causes |MaybeContinueReplay| to be called recursively. We have to
+      // ensure to check |replay_queue_| here.
+      if (replay_queue_.empty())
+        break;
+
       replayed_queue_.push_back(it->second);
       it = replay_queue_.erase(it);
     } else {
@@ -240,7 +265,7 @@
     }
   }
 
-  if (replay_queue_.size() > 0) {
+  if (!replay_queue_.empty()) {
     base::PostDelayedTask(
         FROM_HERE, {content::BrowserThread::UI},
         base::BindOnce(&SpeechMonitor::MaybePrintExpectations,
diff --git a/chrome/browser/chromeos/accessibility/speech_monitor.h b/chrome/browser/chromeos/accessibility/speech_monitor.h
index 84537857..eb319a6 100644
--- a/chrome/browser/chromeos/accessibility/speech_monitor.h
+++ b/chrome/browser/chromeos/accessibility/speech_monitor.h
@@ -69,6 +69,8 @@
                            const base::Location& location = FROM_HERE);
   void ExpectNextSpeechIsNot(const std::string& text,
                              const base::Location& location = FROM_HERE);
+  void ExpectNextSpeechIsNotPattern(const std::string& pattern,
+                                    const base::Location& location = FROM_HERE);
 
   // Adds a call to be included in replay.
   void Call(std::function<void()> func,
diff --git a/chrome/browser/chromeos/accessibility/spoken_feedback_app_list_browsertest.cc b/chrome/browser/chromeos/accessibility/spoken_feedback_app_list_browsertest.cc
index 0ff8db8..055e0effa 100644
--- a/chrome/browser/chromeos/accessibility/spoken_feedback_app_list_browsertest.cc
+++ b/chrome/browser/chromeos/accessibility/spoken_feedback_app_list_browsertest.cc
@@ -140,34 +140,32 @@
 
   EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF));
 
-  while (sm_.GetNextUtterance() != "Press Search plus Space to activate") {
-  }
+  sm_.ExpectSpeech("Shelf");
 
   // Press space on the launcher button in shelf, this opens peeking launcher.
-  SendKeyPressWithSearch(ui::VKEY_SPACE);
-  while (sm_.GetNextUtterance() != "Launcher, partial view") {
-  }
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_SPACE); });
+  sm_.ExpectSpeech("Launcher, partial view");
 
   // Send a key press to enable keyboard traversal
-  SendKeyPressWithSearchAndShift(ui::VKEY_TAB);
+  sm_.Call([this]() { SendKeyPressWithSearchAndShift(ui::VKEY_TAB); });
 
   // Move focus to expand all apps button.
-  SendKeyPressWithSearchAndShift(ui::VKEY_TAB);
-  while (sm_.GetNextUtterance() != "Press Search plus Space to activate") {
-  }
+  sm_.Call([this]() { SendKeyPressWithSearchAndShift(ui::VKEY_TAB); });
+  sm_.ExpectSpeech("Expand to all apps");
 
   // Press space on expand arrow to go to fullscreen launcher.
-  SendKeyPressWithSearch(ui::VKEY_SPACE);
-  while (sm_.GetNextUtterance() != "Launcher, all apps") {
-  }
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_SPACE); });
+  sm_.ExpectSpeech("Launcher, all apps");
 
   // Make sure the first traversal left is not the expand arrow button.
-  SendKeyPressWithSearch(ui::VKEY_LEFT);
-  EXPECT_NE("Expand to all apps", sm_.GetNextUtterance());
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_LEFT); });
+  sm_.ExpectNextSpeechIsNot("Expand to all apps");
 
   // Make sure the second traversal left is not the expand arrow button.
-  SendKeyPressWithSearch(ui::VKEY_LEFT);
-  EXPECT_NE("Expand to all apps", sm_.GetNextUtterance());
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_LEFT); });
+  sm_.ExpectNextSpeechIsNot("Expand to all apps");
+
+  sm_.Replay();
 }
 
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackAppListTest,
@@ -256,61 +254,49 @@
   sm_.Replay();
 }
 
-// TODO(newcomer): reimplement this test once the AppListFocus changes are
-// complete (http://crbug.com/784942).
-IN_PROC_BROWSER_TEST_P(SpokenFeedbackAppListTest,
-                       DISABLED_NavigateAppLauncher) {
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackAppListTest, NavigateAppLauncher) {
   EnableChromeVox();
 
+  // Add one app to the applist.
+  PopulateApps(1);
+
   EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF));
 
   // Wait for it to say "Launcher", "Button", "Shelf", "Tool bar".
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (base::MatchPattern(utterance, "Launcher"))
-      break;
-  }
-  EXPECT_EQ("Button", sm_.GetNextUtterance());
-  EXPECT_EQ("Shelf", sm_.GetNextUtterance());
-  EXPECT_EQ("Tool bar", sm_.GetNextUtterance());
+  sm_.ExpectSpeechPattern("Launcher");
+  sm_.ExpectSpeech("Button");
+  sm_.ExpectSpeech("Shelf");
+  sm_.ExpectSpeech("Tool bar");
 
   // Click on the launcher, it brings up the app list UI.
-  SendKeyPress(ui::VKEY_SPACE);
-  while ("Search or type URL" != sm_.GetNextUtterance()) {
-  }
-  while ("Edit text" != sm_.GetNextUtterance()) {
-  }
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_SPACE); });
+  sm_.ExpectSpeech(
+      "Search your device, apps, and web. Use the arrow keys to navigate your "
+      "apps.");
+  sm_.ExpectSpeech("Edit text");
 
   // Close it and open it again.
-  SendKeyPress(ui::VKEY_ESCAPE);
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (base::MatchPattern(utterance, "*window*"))
-      break;
-  }
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_ESCAPE); });
+  sm_.ExpectSpeechPattern("*window*");
 
-  EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF));
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (base::MatchPattern(utterance, "Button"))
-      break;
-  }
-  SendKeyPress(ui::VKEY_SPACE);
+  sm_.Call(
+      [this]() { EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF)); });
+  sm_.ExpectSpeechPattern("Launcher");
+  sm_.ExpectSpeech("Button");
 
-  // Now type a space into the text field and wait until we hear "space".
-  // This makes the test more robust as it allows us to skip over other
-  // speech along the way.
-  SendKeyPress(ui::VKEY_SPACE);
-  while (true) {
-    if ("space" == sm_.GetNextUtterance())
-      break;
-  }
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_SPACE); });
+  sm_.ExpectSpeech(
+      "Search your device, apps, and web. Use the arrow keys to navigate your "
+      "apps.");
 
-  // Now press the down arrow and we should be focused on an app button
+  // Now press the right arrow and we should be focused on an app button
   // in a dialog.
-  SendKeyPress(ui::VKEY_DOWN);
-  while ("Button" != sm_.GetNextUtterance()) {
-  }
+  // THis doesn't work though (to be done below).
+
+  // TODO(newcomer): reimplement this test once the AppListFocus changes are
+  // complete (http://crbug.com/784942).
+
+  sm_.Replay();
 }
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.cc b/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.cc
index bad2403..201bab8 100644
--- a/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.cc
+++ b/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.cc
@@ -89,6 +89,11 @@
       nullptr, key, true, false, false, false)));
 }
 
+void LoggedInSpokenFeedbackTest::SendKeyPressWithShift(ui::KeyboardCode key) {
+  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
+      nullptr, key, false, true, false, false)));
+}
+
 void LoggedInSpokenFeedbackTest::SendKeyPressWithSearchAndShift(
     ui::KeyboardCode key) {
   ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
@@ -112,6 +117,12 @@
       nullptr, key, true, true, false, true)));
 }
 
+void LoggedInSpokenFeedbackTest::SendStickyKeyCommand() {
+  // To avoid flakes in sending keys, execute the command directly in js.
+  RunJavaScriptInChromeVoxBackgroundPage(
+      "CommandHandler.onCommand('toggleStickyMode');");
+}
+
 void LoggedInSpokenFeedbackTest::SendMouseMoveTo(const gfx::Point& location) {
   ASSERT_NO_FATAL_FAILURE(
       ASSERT_TRUE(ui_controls::SendMouseMove(location.x(), location.y())));
@@ -123,7 +134,7 @@
       extensions::ProcessManager::Get(browser()->profile())
           ->GetBackgroundHostForExtension(
               extension_misc::kChromeVoxExtensionId);
-  CHECK(content::ExecuteScript(host->host_contents(), script));
+  content::ExecuteScriptAsync(host->host_contents(), script);
 }
 
 void LoggedInSpokenFeedbackTest::SimulateTouchScreenInChromeVox() {
@@ -174,85 +185,75 @@
   }
 }
 
-// This test is very flakey with ChromeVox Next since we generate a lot more
-// utterances for text fields.
-// TODO(dtseng): Fix properly.
-IN_PROC_BROWSER_TEST_F(LoggedInSpokenFeedbackTest, DISABLED_AddBookmark) {
+IN_PROC_BROWSER_TEST_F(LoggedInSpokenFeedbackTest, AddBookmark) {
   EnableChromeVox();
   chrome::ExecuteCommand(browser(), IDC_SHOW_BOOKMARK_BAR);
 
   // Create a bookmark with title "foo".
   chrome::ExecuteCommand(browser(), IDC_BOOKMARK_THIS_TAB);
-  EXPECT_EQ("Bookmark added! dialog Bookmark name about:blank Edit text",
-            sm_.GetNextUtterance());
-  EXPECT_EQ("about:blank", sm_.GetNextUtterance());
 
-  SendKeyPress(ui::VKEY_F);
-  EXPECT_EQ("f", sm_.GetNextUtterance());
-  SendKeyPress(ui::VKEY_O);
-  EXPECT_EQ("o", sm_.GetNextUtterance());
-  SendKeyPress(ui::VKEY_O);
-  EXPECT_EQ("o", sm_.GetNextUtterance());
+  sm_.ExpectSpeech("Bookmark name");
+  sm_.ExpectSpeech("about:blank");
+  sm_.ExpectSpeech("selected");
+  sm_.ExpectSpeech("Edit text");
+  sm_.ExpectSpeech("Bookmark added");
+  sm_.ExpectSpeech("Dialog");
+  sm_.ExpectSpeech("Bookmark added, window");
 
-  SendKeyPress(ui::VKEY_TAB);
-  EXPECT_EQ("Bookmark folder combo Box Bookmarks bar", sm_.GetNextUtterance());
+  sm_.Call([this]() {
+    SendKeyPress(ui::VKEY_F);
+    SendKeyPress(ui::VKEY_O);
+    SendKeyPress(ui::VKEY_O);
+  });
+  sm_.ExpectSpeech("F");
+  sm_.ExpectSpeech("O");
+  sm_.ExpectSpeech("O");
 
-  SendKeyPress(ui::VKEY_RETURN);
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_TAB); });
+  sm_.ExpectSpeech("Bookmark folder");
+  sm_.ExpectSpeech("Bookmarks bar");
+  sm_.ExpectSpeech("Button");
+  sm_.ExpectSpeech("has pop up");
 
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "*oolbar*"));
-  // Wait for active window change to be announced to avoid interference from
-  // that below.
-  while (sm_.GetNextUtterance() != "window about blank tab") {
-    // Do nothing.
-  }
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_TAB); });
+  sm_.ExpectSpeech("More…");
+
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_TAB); });
+  sm_.ExpectSpeech("Remove");
+
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_TAB); });
+  sm_.ExpectSpeech("Done");
+
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_RETURN); });
+  // Focus goes back to window.
+  sm_.ExpectSpeechPattern("about:blank*");
 
   // Focus bookmarks bar and listen for "foo".
-  chrome::ExecuteCommand(browser(), IDC_FOCUS_BOOKMARKS);
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    VLOG(0) << "Got utterance: " << utterance;
-    if (utterance == "Bookmarks,")
-      break;
-  }
-  EXPECT_EQ("foo,", sm_.GetNextUtterance());
-  EXPECT_EQ("button", sm_.GetNextUtterance());
+  sm_.Call(
+      [this]() { chrome::ExecuteCommand(browser(), IDC_FOCUS_BOOKMARKS); });
+  sm_.ExpectSpeech("foo");
+  sm_.ExpectSpeech("Button");
+  sm_.ExpectSpeech("Bookmarks");
+  sm_.ExpectSpeech("Tool bar");
+  sm_.Replay();
 }
 
-IN_PROC_BROWSER_TEST_F(LoggedInSpokenFeedbackTest,
-                       DISABLED_NavigateNotificationCenter) {
+IN_PROC_BROWSER_TEST_F(LoggedInSpokenFeedbackTest, NavigateNotificationCenter) {
   EnableChromeVox();
 
   EXPECT_TRUE(PerformAcceleratorAction(ash::TOGGLE_MESSAGE_CENTER_BUBBLE));
+  sm_.ExpectSpeech(
+      "Quick Settings, Press search plus left to access the notification "
+      "center., window");
 
-  // Tab to request the initial focus.
-  SendKeyPress(ui::VKEY_TAB);
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_LEFT); });
+  // If you are hitting this in the course of changing the UI, please fix. This
+  // item needs a label.
+  sm_.ExpectSpeech("List item");
 
-  // Wait for it to say "Notification Center, window".
-  while ("Notification Center, window" != sm_.GetNextUtterance()) {
-  }
+  // Furthermore, navigation is generally broken using Search+Left.
 
-  // Tab until we get to the Do Not Disturb button.
-  SendKeyPress(ui::VKEY_TAB);
-  do {
-    std::string ut = sm_.GetNextUtterance();
-
-    if (ut == "Do not disturb")
-      break;
-    else if (ut == "Button")
-      SendKeyPress(ui::VKEY_TAB);
-  } while (true);
-  EXPECT_EQ("Button", sm_.GetNextUtterance());
-  EXPECT_EQ("Not pressed", sm_.GetNextUtterance());
-
-  SendKeyPress(ui::VKEY_SPACE);
-  EXPECT_EQ("Do not disturb", sm_.GetNextUtterance());
-  EXPECT_EQ("Button", sm_.GetNextUtterance());
-  EXPECT_EQ("Pressed", sm_.GetNextUtterance());
-
-  SendKeyPress(ui::VKEY_SPACE);
-  EXPECT_EQ("Do not disturb", sm_.GetNextUtterance());
-  EXPECT_EQ("Button", sm_.GetNextUtterance());
-  EXPECT_EQ("Not pressed", sm_.GetNextUtterance());
+  sm_.Replay();
 }
 
 //
@@ -285,9 +286,7 @@
                          ::testing::Values(kTestAsNormalUser,
                                            kTestAsGuestUser));
 
-// TODO(tommi): Flakily hitting HasOneRef DCHECK in
-// AudioOutputResampler::Shutdown, see crbug.com/630031.
-IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, DISABLED_EnableSpokenFeedback) {
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, EnableSpokenFeedback) {
   EnableChromeVox();
 }
 
@@ -299,21 +298,37 @@
   EXPECT_EQ("Button", sm_.GetNextUtterance());
 }
 
-IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, DISABLED_TypeInOmnibox) {
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, TypeInOmnibox) {
   EnableChromeVox();
 
-  // Location bar has focus by default so just start typing.
-  SendKeyPress(ui::VKEY_X);
-  EXPECT_EQ("x", sm_.GetNextUtterance());
+  ui_test_utils::NavigateToURL(
+      browser(), GURL("data:text/html;charset=utf-8,<p>unused</p>"));
 
-  SendKeyPress(ui::VKEY_Y);
-  EXPECT_EQ("y", sm_.GetNextUtterance());
+  sm_.Call([this]() { SendKeyPressWithControl(ui::VKEY_L); });
+  sm_.ExpectSpeech("Address and search bar");
 
-  SendKeyPress(ui::VKEY_Z);
-  EXPECT_EQ("z", sm_.GetNextUtterance());
+  sm_.Call([this]() {
+    // Select all the text.
+    SendKeyPressWithControl(ui::VKEY_A);
 
-  SendKeyPress(ui::VKEY_BACK);
-  EXPECT_EQ("z", sm_.GetNextUtterance());
+    // Type x, y, and z.
+    SendKeyPress(ui::VKEY_X);
+    SendKeyPress(ui::VKEY_Y);
+    SendKeyPress(ui::VKEY_Z);
+  });
+  sm_.ExpectSpeech("X");
+  sm_.ExpectSpeech("Y");
+  sm_.ExpectSpeech("Z");
+
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_BACK); });
+  sm_.ExpectSpeech("Z");
+
+  // Auto completions.
+  sm_.ExpectSpeech("xy search");
+  sm_.ExpectSpeech("List item");
+  sm_.ExpectSpeech("1 of 1");
+
+  sm_.Replay();
 }
 
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, FocusShelf) {
@@ -339,9 +354,7 @@
 
 // Verifies that pressing right arrow button with search button should move
 // focus to the next ShelfItem instead of the last one
-// (see https://crbug.com/947683).
-// This test is flaky, see http://crbug.com/997628
-IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, DISABLED_ShelfIconFocusForward) {
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, ShelfIconFocusForward) {
   const std::string title("MockApp");
   ChromeLauncherController* controller = ChromeLauncherController::instance();
 
@@ -353,46 +366,42 @@
       ash::ShelfID("FakeApp"), controller->shelf_model()->item_count(),
       base::ASCIIToUTF16(title));
 
-  // Wait for the change on ShelfModel to reach ash.
-  base::RunLoop().RunUntilIdle();
-
   // Focus on the shelf.
-  EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF));
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (base::MatchPattern(utterance, "Launcher"))
-      break;
-  }
-
-  ASSERT_EQ("Button", sm_.GetNextUtterance());
-  ASSERT_EQ("Shelf", sm_.GetNextUtterance());
-  ASSERT_EQ("Tool bar", sm_.GetNextUtterance());
-  ASSERT_EQ(", window", sm_.GetNextUtterance());
-  ASSERT_EQ("Press Search plus Space to activate", sm_.GetNextUtterance());
+  sm_.Call([this]() { PerformAcceleratorAction(ash::FOCUS_SHELF); });
+  sm_.ExpectSpeech("Launcher");
+  sm_.ExpectSpeech("Button");
+  sm_.ExpectSpeech("Shelf");
+  sm_.ExpectSpeech("Tool bar");
 
   // Verifies that pressing right key with search key should move the focus of
   // ShelfItem correctly.
-  SendKeyPressWithSearch(ui::VKEY_RIGHT);
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "*"));
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "Button"));
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "*"));
-  SendKeyPressWithSearch(ui::VKEY_RIGHT);
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), title));
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "Button"));
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "*"));
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_RIGHT); });
+  // Chromium or Google Chrome button here (not being tested).
+  sm_.ExpectSpeech("Button");
+  sm_.ExpectSpeech("Shelf");
+  sm_.ExpectSpeech("Tool bar");
+
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_RIGHT); });
+  sm_.ExpectSpeech("MockApp");
+  sm_.ExpectSpeech("Button");
+
+  sm_.Replay();
 }
 
 // Verifies that speaking text under mouse works for Shelf button and voice
 // announcements should not be stacked when mouse goes over many Shelf buttons
-// (see https://crbug.com/958120 and https://crbug.com/921182).
-// TODO(crbug.com/921182): Fix test correctness/reliability and re-enable.
-IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest,
-                       DISABLED_SpeakingTextUnderMouseForShelfItem) {
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, SpeakingTextUnderMouseForShelfItem) {
   // Add the ShelfItem to the ShelfModel after enabling the ChromeVox. Because
   // when an extension is enabled, the ShelfItems which are not recorded as
   // pinned apps in user preference will be removed.
   EnableChromeVox();
 
+  ui_test_utils::NavigateToURL(
+      browser(),
+      GURL("data:text/html;charset=utf-8,<button autofocus>Click me</button>"));
+
+  sm_.ExpectSpeech("Click me");
+
   // Add three Shelf buttons. Wait for the change on ShelfModel to reach ash.
   ChromeLauncherController* controller = ChromeLauncherController::instance();
   const int base_index = controller->shelf_model()->item_count();
@@ -405,7 +414,6 @@
     controller->CreateAppShortcutLauncherItem(
         ash::ShelfID(app_id), base_index + i, base::ASCIIToUTF16(app_title));
   }
-  base::RunLoop().RunUntilIdle();
 
   // Enable the function of speaking text under mouse.
   ash::EventRewriterController::Get()->SetSendMouseEventsToDelegate(true);
@@ -413,45 +421,26 @@
   // Focus on the Shelf because voice text for focusing on Shelf is fixed. Wait
   // until voice announcements are finished.
   EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF));
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (base::MatchPattern(utterance, "Launcher"))
-      break;
-  }
-  ASSERT_EQ("Button", sm_.GetNextUtterance());
-  ASSERT_EQ("Shelf", sm_.GetNextUtterance());
-  ASSERT_EQ("Tool bar", sm_.GetNextUtterance());
-  ASSERT_EQ(", window", sm_.GetNextUtterance());
-  ASSERT_EQ("Press Search plus Space to activate", sm_.GetNextUtterance());
+  sm_.ExpectSpeechPattern("Launcher");
 
   // Hover mouse on the Shelf button. Verifies that text under mouse is spoken.
-  ash::ShelfView* shelf_view =
-      ash::Shelf::ForWindow(ash::Shell::Get()->GetPrimaryRootWindow())
-          ->shelf_widget()
-          ->shelf_view_for_testing();
-  const int first_app_index =
-      shelf_view->model()->GetItemIndexForType(ash::TYPE_PINNED_APP);
-  SendMouseMoveTo(shelf_view->view_model()
-                      ->view_at(first_app_index)
-                      ->GetBoundsInScreen()
-                      .CenterPoint());
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "MockApp0"));
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "Button"));
+  sm_.Call([this]() {
+    ash::ShelfView* shelf_view =
+        ash::Shelf::ForWindow(ash::Shell::Get()->GetPrimaryRootWindow())
+            ->shelf_widget()
+            ->shelf_view_for_testing();
+    const int first_app_index =
+        shelf_view->model()->GetItemIndexForType(ash::TYPE_PINNED_APP);
+    SendMouseMoveTo(shelf_view->view_model()
+                        ->view_at(first_app_index)
+                        ->GetBoundsInScreen()
+                        .CenterPoint());
+  });
 
-  // Move mouse to the third Shelf button through the second one. Verifies that
-  // only the last Shelf button is announced by ChromeVox.
-  const int second_app_index = first_app_index + 1;
-  SendMouseMoveTo(shelf_view->view_model()
-                      ->view_at(second_app_index)
-                      ->GetBoundsInScreen()
-                      .CenterPoint());
-  const int third_app_index = first_app_index + 2;
-  SendMouseMoveTo(shelf_view->view_model()
-                      ->view_at(third_app_index)
-                      ->GetBoundsInScreen()
-                      .CenterPoint());
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "MockApp2"));
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "Button"));
+  sm_.ExpectSpeechPattern("MockApp*");
+  sm_.ExpectSpeech("Button");
+
+  sm_.Replay();
 }
 
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, OpenStatusTray) {
@@ -470,235 +459,159 @@
 
 // Fails on ASAN. See http://crbug.com/776308 . (Note MAYBE_ doesn't work well
 // with parameterized tests).
-#if !defined(ADDRESS_SANITIZER) && !defined(OS_CHROMEOS)
+#if !defined(ADDRESS_SANITIZER)
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, NavigateSystemTray) {
   EnableChromeVox();
 
-  EXPECT_TRUE(PerformAcceleratorAction(ash::TOGGLE_SYSTEM_TRAY_BUBBLE));
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (base::MatchPattern(utterance, "Status tray,"))
-      break;
-  }
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (base::MatchPattern(utterance, "*window"))
-      break;
-  }
+  sm_.Call(
+      [this]() { (PerformAcceleratorAction(ash::TOGGLE_SYSTEM_TRAY_BUBBLE)); });
+  sm_.ExpectSpeechPattern(
+      "Quick Settings, Press search plus left to access the notification "
+      "center., window");
 
-  SendKeyPress(ui::VKEY_TAB);
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (base::MatchPattern(utterance, "Button"))
-      break;
-  }
-
-  // Next element.
-  SendKeyPressWithSearch(ui::VKEY_RIGHT);
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "*"));
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "Button"));
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_TAB); });
+  sm_.ExpectSpeech(GetParam() == kTestAsGuestUser ? "Exit guest" : "Sign out");
+  sm_.ExpectSpeech("Button");
 
   // Next button.
-  SendKeyPressWithSearch(ui::VKEY_B);
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "*"));
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "Button"));
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_B); });
+  sm_.ExpectSpeech("Shut down");
+  sm_.ExpectSpeech("Button");
 
-  // Navigate to Bluetooth sub-menu and open it.
-  while (true) {
-    SendKeyPress(ui::VKEY_TAB);
-    std::string content = sm_.GetNextUtterance();
-    std::string role = sm_.GetNextUtterance();
-    if (base::MatchPattern(content, "*Bluetooth*") &&
-        base::MatchPattern(role, "Button"))
-      break;
-  }
-  SendKeyPress(ui::VKEY_RETURN);
-
-  // Navigate to return to previous menu button and press it.
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (base::MatchPattern(utterance, "Previous menu"))
-      break;
-    SendKeyPress(ui::VKEY_TAB);
-  }
-  SendKeyPress(ui::VKEY_RETURN);
-
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (base::MatchPattern(utterance, "Bluetooth*"))
-      break;
-  }
+  sm_.Replay();
 }
-#endif  // !defined(ADDRESS_SANITIZER) && !defined(OS_CHROMEOS)
+#endif  // !defined(ADDRESS_SANITIZER)
 
-// See http://crbug.com/443608
+// TODO: these brightness announcements are actually not made.
+// https://crbug.com/1064788
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, DISABLED_ScreenBrightness) {
   EnableChromeVox();
 
-  EXPECT_TRUE(PerformAcceleratorAction(ash::BRIGHTNESS_UP));
-  EXPECT_TRUE(
-      base::MatchPattern(sm_.GetNextUtterance(), "Brightness * percent"));
+  sm_.Call([this]() { (PerformAcceleratorAction(ash::BRIGHTNESS_UP)); });
+  sm_.ExpectSpeechPattern("Brightness * percent");
 
-  EXPECT_TRUE(PerformAcceleratorAction(ash::BRIGHTNESS_DOWN));
-  EXPECT_TRUE(
-      base::MatchPattern(sm_.GetNextUtterance(), "Brightness * percent"));
+  sm_.Call([this]() { (PerformAcceleratorAction(ash::BRIGHTNESS_DOWN)); });
+  sm_.ExpectSpeechPattern("Brightness * percent");
+
+  sm_.Replay();
 }
 
-IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, DISABLED_VolumeSlider) {
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, VolumeSlider) {
   EnableChromeVox();
 
-  // Volume slider does not fire valueChanged event on first key press because
-  // it has no widget.
-  EXPECT_TRUE(PerformAcceleratorAction(ash::VOLUME_UP));
-  EXPECT_TRUE(PerformAcceleratorAction(ash::VOLUME_UP));
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "* percent*"));
+  sm_.Call([this]() {
+    // Volume slider does not fire valueChanged event on first key press because
+    // it has no widget.
+    PerformAcceleratorAction(ash::VOLUME_UP);
+    PerformAcceleratorAction(ash::VOLUME_UP);
+  });
+  sm_.ExpectSpeechPattern("* percent*");
+  sm_.Replay();
 }
 
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, OverviewMode) {
   EnableChromeVox();
+  ui_test_utils::NavigateToURL(
+      browser(),
+      GURL("data:text/html;charset=utf-8,<button autofocus>Click me</button>"));
 
-  EXPECT_TRUE(PerformAcceleratorAction(ash::TOGGLE_OVERVIEW));
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (utterance == "Entered window overview mode. Press tab to navigate.")
-      break;
-  }
+  sm_.ExpectSpeech("Click me");
 
-  // On Chrome OS accessibility title for tabbed browser windows contains app
-  // name ("Chrome" or "Chromium") in overview mode.
-  while (true) {
-    // Tabbing may select a desk item in the overview desks bar, so we tab
-    // repeatedly until the window is selected.
-    SendKeyPress(ui::VKEY_TAB);
-    std::string utterance = sm_.GetNextUtterance();
-    if (base::MatchPattern(utterance, "Chrom*about:blank, window"))
-      break;
-  }
+  sm_.Call([this]() { (PerformAcceleratorAction(ash::TOGGLE_OVERVIEW)); });
+
+  sm_.ExpectSpeech("Entered window overview mode. Press tab to navigate.");
+
+  sm_.Call([this]() { SendKeyPressWithShift(ui::VKEY_TAB); });
+  sm_.ExpectSpeechPattern(
+      "Chrom* - data:text slash html;charset equal utf-8, less than button "
+      "autofocus greater than Click me less than slash button greater than , "
+      "window");
+
+  sm_.Replay();
 }
 
-#if defined(MEMORY_SANITIZER) || defined(OS_CHROMEOS)
-// Fails under MemorySanitizer: http://crbug.com/472125
-// Test is flaky under ChromeOS: http://crbug.com/897249
-#define MAYBE_ChromeVoxShiftSearch DISABLED_ChromeVoxShiftSearch
-#else
-#define MAYBE_ChromeVoxShiftSearch ChromeVoxShiftSearch
-#endif
-IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, MAYBE_ChromeVoxShiftSearch) {
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, ChromeVoxFindInPage) {
   EnableChromeVox();
 
   ui_test_utils::NavigateToURL(
       browser(),
       GURL("data:text/html;charset=utf-8,<button autofocus>Click me</button>"));
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (utterance == "Click me")
-      break;
-  }
+
+  sm_.ExpectSpeech("Click me");
 
   // Press Search+/ to enter ChromeVox's "find in page".
   SendKeyPressWithSearch(ui::VKEY_OEM_2);
-
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (utterance == "Find in page")
-      break;
-  }
+  sm_.ExpectSpeech("Find in page");
+  sm_.Replay();
 }
 
-#if defined(MEMORY_SANITIZER) || defined(OS_CHROMEOS)
-// Fails under MemorySanitizer: http://crbug.com/472125
-// TODO(crbug.com/721475): Flaky on CrOS.
-#define MAYBE_ChromeVoxNavigateAndSelect DISABLED_ChromeVoxNavigateAndSelect
-#else
-#define MAYBE_ChromeVoxNavigateAndSelect ChromeVoxNavigateAndSelect
-#endif
-IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, MAYBE_ChromeVoxNavigateAndSelect) {
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, ChromeVoxNavigateAndSelect) {
   EnableChromeVox();
 
   ui_test_utils::NavigateToURL(browser(),
                                GURL("data:text/html;charset=utf-8,"
                                     "<h1>Title</h1>"
                                     "<button autofocus>Click me</button>"));
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (utterance == "Click me")
-      break;
-  }
-  EXPECT_EQ("Button", sm_.GetNextUtterance());
+
+  sm_.ExpectSpeech("Click me");
 
   // Press Search+Left to navigate to the previous item.
-  SendKeyPressWithSearch(ui::VKEY_LEFT);
-  EXPECT_EQ("Title", sm_.GetNextUtterance());
-  EXPECT_EQ("Heading 1", sm_.GetNextUtterance());
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_LEFT); });
+  sm_.ExpectSpeech("Title");
+  sm_.ExpectSpeech("Heading 1");
 
   // Press Search+S to select the text.
-  SendKeyPressWithSearch(ui::VKEY_S);
-  EXPECT_EQ("Title", sm_.GetNextUtterance());
-  EXPECT_EQ("selected", sm_.GetNextUtterance());
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_S); });
+  sm_.ExpectSpeech("Title");
+  sm_.ExpectSpeech("selected");
 
   // Press again to end the selection.
-  SendKeyPressWithSearch(ui::VKEY_S);
-  EXPECT_EQ("End selection", sm_.GetNextUtterance());
-  EXPECT_EQ("Title", sm_.GetNextUtterance());
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_S); });
+  sm_.ExpectSpeech("End selection");
+
+  sm_.Replay();
 }
 
-IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, DISABLED_ChromeVoxNextStickyMode) {
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, ChromeVoxStickyMode) {
   EnableChromeVox();
 
   ui_test_utils::NavigateToURL(
       browser(),
       GURL("data:text/html;charset=utf-8,<button autofocus>Click me</button>"));
-  while ("Button" != sm_.GetNextUtterance()) {
-  }
+
+  sm_.ExpectSpeech("Click me");
 
   // Press the sticky-key sequence: Search Search.
-  SendKeyPress(ui::VKEY_LWIN);
+  sm_.Call([this]() { SendStickyKeyCommand(); });
 
-  // Sticky key has a minimum 100 ms check to prevent key repeat from toggling
-  // it.
-  base::PostDelayedTask(
-      FROM_HERE, {content::BrowserThread::UI},
-      base::BindOnce(&LoggedInSpokenFeedbackTest::SendKeyPress,
-                     base::Unretained(this), ui::VKEY_LWIN),
-      base::TimeDelta::FromMilliseconds(200));
+  sm_.ExpectSpeech("Sticky mode enabled");
 
-  EXPECT_EQ("Sticky mode enabled", sm_.GetNextUtterance());
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_H); });
+  sm_.ExpectSpeech("No next heading");
 
-  SendKeyPress(ui::VKEY_H);
-  while ("No next heading" != sm_.GetNextUtterance()) {
-  }
+  sm_.Call([this]() { SendStickyKeyCommand(); });
+  sm_.ExpectSpeech("Sticky mode disabled");
 
-  SendKeyPress(ui::VKEY_LWIN);
-
-  // Sticky key has a minimum 100 ms check to prevent key repeat from toggling
-  // it.
-  base::PostDelayedTask(
-      FROM_HERE, {content::BrowserThread::UI},
-      base::BindOnce(&LoggedInSpokenFeedbackTest::SendKeyPress,
-                     base::Unretained(this), ui::VKEY_LWIN),
-      base::TimeDelta::FromMilliseconds(200));
-
-  while ("Sticky mode disabled" != sm_.GetNextUtterance()) {
-  }
+  sm_.Replay();
 }
 
-// Flaky on Linux ChromiumOS MSan Tests. https://crbug.com/752427
-IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, DISABLED_TouchExploreStatusTray) {
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, TouchExploreStatusTray) {
   EnableChromeVox();
   SimulateTouchScreenInChromeVox();
 
   // Send an accessibility hover event on the system tray, which is
   // what we get when you tap it on a touch screen when ChromeVox is on.
-  ash::TrayBackgroundView* tray = ash::Shell::Get()
-                                      ->GetPrimaryRootWindowController()
-                                      ->GetStatusAreaWidget()
-                                      ->unified_system_tray();
-  tray->NotifyAccessibilityEvent(ax::mojom::Event::kHover, true);
+  sm_.Call([]() {
+    ash::TrayBackgroundView* tray = ash::Shell::Get()
+                                        ->GetPrimaryRootWindowController()
+                                        ->GetStatusAreaWidget()
+                                        ->unified_system_tray();
+    tray->NotifyAccessibilityEvent(ax::mojom::Event::kHover, true);
+  });
+  sm_.ExpectSpeechPattern("Status tray, time* Battery at* percent*");
+  sm_.ExpectSpeech("Button");
 
-  EXPECT_EQ("Status tray,", sm_.GetNextUtterance());
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "time*,"));
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "Battery*"));
-  EXPECT_EQ("Button", sm_.GetNextUtterance());
+  sm_.Replay();
 }
 
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, ChromeVoxNextTabRecovery) {
@@ -792,66 +705,22 @@
   DISALLOW_COPY_AND_ASSIGN(OobeSpokenFeedbackTest);
 };
 
-// Test is flaky: http://crbug.com/346797
-IN_PROC_BROWSER_TEST_F(OobeSpokenFeedbackTest, DISABLED_SpokenFeedbackInOobe) {
+IN_PROC_BROWSER_TEST_F(OobeSpokenFeedbackTest, SpokenFeedbackInOobe) {
   ui_controls::EnableUIControls();
   ASSERT_FALSE(AccessibilityManager::Get()->IsSpokenFeedbackEnabled());
-
-  LoginDisplayHost* login_display_host = LoginDisplayHost::default_host();
-  WebUILoginView* web_ui_login_view = login_display_host->GetWebUILoginView();
-  views::Widget* widget = web_ui_login_view->GetWidget();
-  gfx::NativeWindow window = widget->GetNativeWindow();
-
-  // We expect to be in the language select dropdown for this test to work,
-  // so make sure that's the case.
-  test::OobeJS().ExecuteAsync("$('language-select').focus()");
   AccessibilityManager::Get()->EnableSpokenFeedback(true);
-  ASSERT_TRUE(sm_.SkipChromeVoxEnabledMessage());
-  // There's no guarantee that ChromeVox speaks anything when injected after
-  // the page loads, which is by design.  Tab forward and then backward
-  // to make sure we get the right feedback from the language and keyboard
-  // selection fields.
-  ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
-      window, ui::VKEY_TAB, false, false, false, false));
 
-  while (sm_.GetNextUtterance() != "Select your keyboard:") {
-  }
-  EXPECT_EQ("U S", sm_.GetNextUtterance());
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "Combo box * of *"));
-  ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
-      window, ui::VKEY_TAB, false, true /*shift*/, false, false));
-  while (sm_.GetNextUtterance() != "Select your language:") {
-  }
-  EXPECT_EQ("English ( United States)", sm_.GetNextUtterance());
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "Combo box * of *"));
-}
+  // The Let's go button gets initial focus.
+  sm_.ExpectSpeech("Let's go");
 
-// This test is flaky (https://crbug.com/1013551).
-IN_PROC_BROWSER_TEST_F(OobeSpokenFeedbackTest,
-                       DISABLED_ChromeVoxPanelTabsMenuEmpty) {
-  // The ChromeVox panel should not populate the tabs menu if we are in the
-  // OOBE.
-  ASSERT_FALSE(AccessibilityManager::Get()->IsSpokenFeedbackEnabled());
-  AccessibilityManager::Get()->EnableSpokenFeedback(true);
-  // Included to reduce flakiness.
-  while (sm_.GetNextUtterance() != "Press Search plus Space to activate") {
-  }
-  // Press [search + .] to open ChromeVox Panel
-  ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
-      nullptr, ui::VKEY_OEM_PERIOD, false, false, false, true));
-  while (sm_.GetNextUtterance() != "ChromeVox Panel") {
-  }
-  // Go to tabs menu and verify that it has no items.
-  ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
-      nullptr, ui::VKEY_RIGHT, false, false, false, false));
-  while (sm_.GetNextUtterance() != "Speech") {
-  }
-  ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
-      nullptr, ui::VKEY_RIGHT, false, false, false, false));
-  while (sm_.GetNextUtterance() != "Tabs") {
-  }
-  EXPECT_EQ("Menu", sm_.GetNextUtterance());
-  EXPECT_EQ("No items", sm_.GetNextUtterance());
+  sm_.Call([]() {
+    ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
+        nullptr, ui::VKEY_TAB, false, false, false, false));
+  });
+  sm_.ExpectSpeech("Shut down");
+  sm_.ExpectSpeech("Button");
+
+  sm_.Replay();
 }
 
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest,
@@ -917,17 +786,13 @@
   sm_.Replay();
 }
 
-#if defined(OS_CHROMEOS)
-// Flaky on ChromeOS: http://crbug.com/1064947
-#define MAYBE_ResetTtsSettings DISABLED_ResetTtsSettings
-#else
-#define MAYBE_ResetTtsSettings ResetTtsSettings
-#endif
-IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, MAYBE_ResetTtsSettings) {
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, ResetTtsSettings) {
   EnableChromeVox();
   ui_test_utils::NavigateToURL(
       browser(), GURL("data:text/html,<button autofocus>Click me</button>"));
 
+  sm_.ExpectSpeech("Click me");
+
   // Reset Tts settings using hotkey and assert speech output.
   sm_.Call(
       [this]() { SendKeyPressWithSearchAndControlAndShift(ui::VKEY_OEM_5); });
@@ -951,4 +816,58 @@
   sm_.Replay();
 }
 
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, SmartStickyMode) {
+  EnableChromeVox();
+  ui_test_utils::NavigateToURL(browser(),
+                               GURL("data:text/html,<p>start</p><input "
+                                    "autofocus type='text'><p>end</p>"));
+
+  // The input is autofocused.
+  sm_.ExpectSpeech("Edit text");
+
+  // First, navigate with sticky mode on.
+  sm_.Call([this]() { SendStickyKeyCommand(); });
+  sm_.ExpectSpeech("Sticky mode enabled");
+
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_RIGHT); });
+  sm_.ExpectNextSpeechIsNotPattern("Sticky mode *abled");
+  sm_.ExpectSpeech("end");
+
+  // Jump to beginning.
+  sm_.Call([this]() { SendKeyPressWithSearchAndControl(ui::VKEY_LEFT); });
+  sm_.ExpectNextSpeechIsNotPattern("Sticky mode *abled");
+  sm_.ExpectSpeech("start");
+
+  // The nextEditText command is explicitly excluded from toggling.
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_E); });
+  sm_.ExpectNextSpeechIsNotPattern("Sticky mode *abled");
+  sm_.ExpectSpeech("Edit text");
+
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_RIGHT); });
+  sm_.ExpectNextSpeechIsNotPattern("Sticky mode *abled");
+  sm_.ExpectSpeech("end");
+
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_LEFT); });
+  sm_.ExpectSpeech("Sticky mode disabled");
+  sm_.ExpectSpeech("Edit text");
+
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_LEFT); });
+  sm_.ExpectSpeech("Sticky mode enabled");
+  sm_.ExpectSpeech("start");
+
+  // Now, navigate with sticky mode off.
+  sm_.Call([this]() { SendStickyKeyCommand(); });
+  sm_.ExpectSpeech("Sticky mode disabled");
+
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_RIGHT); });
+  sm_.ExpectNextSpeechIsNotPattern("Sticky mode *abled");
+  sm_.ExpectSpeech("Edit text");
+
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_RIGHT); });
+  sm_.ExpectNextSpeechIsNotPattern("Sticky mode *abled");
+  sm_.ExpectSpeech("end");
+
+  sm_.Replay();
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.h b/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.h
index b20d15e..c06f344 100644
--- a/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.h
+++ b/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.h
@@ -29,11 +29,14 @@
   // Simulate key press event.
   void SendKeyPress(ui::KeyboardCode key);
   void SendKeyPressWithControl(ui::KeyboardCode key);
+  void SendKeyPressWithShift(ui::KeyboardCode key);
   void SendKeyPressWithSearchAndShift(ui::KeyboardCode key);
   void SendKeyPressWithSearch(ui::KeyboardCode key);
   void SendKeyPressWithSearchAndControl(ui::KeyboardCode key);
   void SendKeyPressWithSearchAndControlAndShift(ui::KeyboardCode key);
 
+  void SendStickyKeyCommand();
+
   void SendMouseMoveTo(const gfx::Point& location);
 
   void RunJavaScriptInChromeVoxBackgroundPage(const std::string& script);
diff --git a/chrome/browser/chromeos/crostini/crostini_terminal.cc b/chrome/browser/chromeos/crostini/crostini_terminal.cc
index d6bcc28..272c3b2f 100644
--- a/chrome/browser/chromeos/crostini/crostini_terminal.cc
+++ b/chrome/browser/chromeos/crostini/crostini_terminal.cc
@@ -33,6 +33,10 @@
 namespace {
 constexpr char kSettingPrefix[] = "/hterm/profiles/default/";
 const size_t kSettingPrefixSize = base::size(kSettingPrefix) - 1;
+
+constexpr char kSettingBackgroundColor[] =
+    "/hterm/profiles/default/background-color";
+constexpr char kDefaultBackgroundColor[] = "#101010";
 }  // namespace
 
 namespace crostini {
@@ -236,4 +240,11 @@
   }
 }
 
+std::string GetTerminalSettingBackgroundColor(Profile* profile) {
+  const base::DictionaryValue* value = profile->GetPrefs()->GetDictionary(
+      crostini::prefs::kCrostiniTerminalSettings);
+  const std::string* result = value->FindStringKey(kSettingBackgroundColor);
+  return result ? *result : kDefaultBackgroundColor;
+}
+
 }  // namespace crostini
diff --git a/chrome/browser/chromeos/crostini/crostini_terminal.h b/chrome/browser/chromeos/crostini/crostini_terminal.h
index 6f8eec30..a91e815 100644
--- a/chrome/browser/chromeos/crostini/crostini_terminal.h
+++ b/chrome/browser/chromeos/crostini/crostini_terminal.h
@@ -130,6 +130,8 @@
 // Record which terminal settings have been changed by users.
 void RecordTerminalSettingsChangesUMAs(Profile* profile);
 
+// Returns terminal setting 'background-color'.
+std::string GetTerminalSettingBackgroundColor(Profile* profile);
 }  // namespace crostini
 
 #endif  // CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_TERMINAL_H_
diff --git a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
index e5343a9..f6334d8c 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
@@ -4152,6 +4152,8 @@
     scrollable_shelf_ui_info.is_overflow = fetched_info.is_overflow;
     scrollable_shelf_ui_info.icons_bounds_in_screen =
         ToBoundsDictionaryList(fetched_info.icons_bounds_in_screen);
+    scrollable_shelf_ui_info.is_shelf_widget_animating =
+        fetched_info.is_shelf_widget_animating;
 
     if (state.scroll_distance) {
       scrollable_shelf_ui_info.target_main_axis_offset =
diff --git a/chrome/browser/chromeos/input_method/assistive_suggester.cc b/chrome/browser/chromeos/input_method/assistive_suggester.cc
index d1da6f8..d1b049a 100644
--- a/chrome/browser/chromeos/input_method/assistive_suggester.cc
+++ b/chrome/browser/chromeos/input_method/assistive_suggester.cc
@@ -4,14 +4,7 @@
 
 #include "chrome/browser/chromeos/input_method/assistive_suggester.h"
 
-#include "base/feature_list.h"
 #include "base/metrics/histogram_functions.h"
-#include "base/strings/utf_string_conversions.h"
-#include "chrome/browser/autofill/personal_data_manager_factory.h"
-#include "chrome/browser/profiles/profile_manager.h"
-#include "chromeos/constants/chromeos_features.h"
-#include "components/autofill/core/browser/data_model/autofill_profile.h"
-#include "components/autofill/core/browser/personal_data_manager.h"
 
 using input_method::InputMethodEngineBase;
 
@@ -21,10 +14,6 @@
 
 const char kMaxTextBeforeCursorLength = 50;
 const char kKeydown[] = "keydown";
-const char kAssistEmailPrefix[] = "my email is ";
-const char kAssistNamePrefix[] = "my name is ";
-const char kAssistAddressPrefix[] = "my address is ";
-const char kAssistPhoneNumberPrefix[] = "my phone number is ";
 
 void RecordAssistiveCoverage(AssistiveType type) {
   base::UmaHistogramEnumeration("InputMethod.Assistive.Coverage", type);
@@ -34,42 +23,20 @@
   base::UmaHistogramEnumeration("InputMethod.Assistive.Success", type);
 }
 
-AssistiveType ProposeAssistiveAction(const base::string16& text) {
-  AssistiveType action = AssistiveType::kGenericAction;
-  if (base::EndsWith(text, base::UTF8ToUTF16(kAssistEmailPrefix),
-                     base::CompareCase::INSENSITIVE_ASCII)) {
-    action = AssistiveType::kPersonalEmail;
-  }
-  if (base::EndsWith(text, base::UTF8ToUTF16(kAssistNamePrefix),
-                     base::CompareCase::INSENSITIVE_ASCII)) {
-    action = AssistiveType::kPersonalName;
-  }
-  if (base::EndsWith(text, base::UTF8ToUTF16(kAssistAddressPrefix),
-                     base::CompareCase::INSENSITIVE_ASCII)) {
-    action = AssistiveType::kPersonalAddress;
-  }
-  if (base::EndsWith(text, base::UTF8ToUTF16(kAssistPhoneNumberPrefix),
-                     base::CompareCase::INSENSITIVE_ASCII)) {
-    action = AssistiveType::kPersonalPhoneNumber;
-  }
-  return action;
-}
-
 }  // namespace
 
 AssistiveSuggester::AssistiveSuggester(InputMethodEngine* engine,
                                        Profile* profile)
-    : engine_(engine),
-      profile_(profile),
-      personal_data_manager_(
-          autofill::PersonalDataManagerFactory::GetForProfile(profile)) {}
+    : personal_info_suggester_(new PersonalInfoSuggester(engine, profile)) {}
 
 void AssistiveSuggester::OnFocus(int context_id) {
   context_id_ = context_id;
+  personal_info_suggester_->OnFocus(context_id_);
 }
 
 void AssistiveSuggester::OnBlur() {
   context_id_ = -1;
+  personal_info_suggester_->OnBlur();
 }
 
 bool AssistiveSuggester::OnKeyEvent(
@@ -83,18 +50,21 @@
   // surrounding text change, which is triggered by a keydown event. As a
   // result, the next key event after suggesting would be a keyup event of the
   // same key, and that event is meaningless to us.
-  if (suggestion_shown_ && event.type == kKeydown) {
-    suggestion_shown_ = false;
-    if (event.key == "Tab" || event.key == "Right") {
-      std::string error;
-      engine_->AcceptSuggestion(context_id_, &error);
-      RecordAssistiveSuccess(proposed_action_type_);
-      return true;
+  if (IsSuggestionShown() && event.type == kKeydown) {
+    SuggestionStatus status = current_suggester_->HandleKeyEvent(event);
+    switch (status) {
+      case SuggestionStatus::kAccept:
+        RecordAssistiveSuccess(current_suggester_->GetProposeActionType());
+        current_suggester_ = nullptr;
+        return true;
+      case SuggestionStatus::kDismiss:
+        current_suggester_ = nullptr;
+        suggestion_dismissed_ = true;
+        return false;
+      default:
+        break;
     }
-    DismissSuggestion();
-    suggestion_dismissed_ = true;
   }
-
   return false;
 }
 
@@ -125,12 +95,11 @@
   if (context_id_ == -1)
     return false;
 
-  if (suggestion_shown_) {
-    suggestion_shown_ = false;
+  if (IsSuggestionShown()) {
     DismissSuggestion();
   }
   Suggest(text, cursor_pos, anchor_pos);
-  return suggestion_shown_;
+  return IsSuggestionShown();
 }
 
 void AssistiveSuggester::Suggest(const base::string16& text,
@@ -146,67 +115,22 @@
     int start_pos = std::max(0, cursor_pos - kMaxTextBeforeCursorLength);
     base::string16 text_before_cursor =
         text.substr(start_pos, cursor_pos - start_pos);
-    base::string16 suggestion_text =
-        GetPersonalInfoSuggestion(text_before_cursor);
-    if (!suggestion_text.empty()) {
-      ShowSuggestion(suggestion_text);
-      suggestion_shown_ = true;
+    if (personal_info_suggester_->Suggest(text_before_cursor)) {
+      current_suggester_ = personal_info_suggester_;
+    } else {
+      current_suggester_ = nullptr;
     }
   }
 }
 
-base::string16 AssistiveSuggester::GetPersonalInfoSuggestion(
-    const base::string16& text) {
-  AssistiveType action = ProposeAssistiveAction(text);
-  if (action == AssistiveType::kGenericAction)
-    return base::EmptyString16();
-
-  proposed_action_type_ = action;
-
-  if (action == AssistiveType::kPersonalEmail)
-    return base::UTF8ToUTF16(profile_->GetProfileUserName());
-
-  auto autofill_profiles = personal_data_manager_->GetProfilesToSuggest();
-  if (autofill_profiles.empty())
-    return base::EmptyString16();
-
-  // Currently, we are just picking the first candidate, will improve the
-  // strategy in the future.
-  auto* data = autofill_profiles[0];
-  base::string16 suggestion;
-  switch (action) {
-    case AssistiveType::kPersonalName:
-      suggestion = data->GetRawInfo(autofill::ServerFieldType::NAME_FULL);
-      break;
-    case AssistiveType::kPersonalAddress:
-      suggestion = data->GetRawInfo(
-          autofill::ServerFieldType::ADDRESS_HOME_STREET_ADDRESS);
-      break;
-    case AssistiveType::kPersonalPhoneNumber:
-      suggestion =
-          data->GetRawInfo(autofill::ServerFieldType::PHONE_HOME_WHOLE_NUMBER);
-      break;
-    default:
-      NOTREACHED();
-      break;
-  }
-  return suggestion;
-}
-
-void AssistiveSuggester::ShowSuggestion(const base::string16& text) {
-  std::string error;
-  engine_->SetSuggestion(context_id_, text, &error);
-  if (!error.empty()) {
-    LOG(ERROR) << "Fail to show suggestion. " << error;
-  }
-}
-
 void AssistiveSuggester::DismissSuggestion() {
-  std::string error;
-  engine_->DismissSuggestion(context_id_, &error);
-  if (!error.empty()) {
-    LOG(ERROR) << "Failed to dismiss suggestion. " << error;
-  }
+  if (current_suggester_)
+    current_suggester_->DismissSuggestion();
+  current_suggester_ = nullptr;
+}
+
+bool AssistiveSuggester::IsSuggestionShown() {
+  return current_suggester_ != nullptr;
 }
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/input_method/assistive_suggester.h b/chrome/browser/chromeos/input_method/assistive_suggester.h
index 6963b9c..521bcbd 100644
--- a/chrome/browser/chromeos/input_method/assistive_suggester.h
+++ b/chrome/browser/chromeos/input_method/assistive_suggester.h
@@ -8,26 +8,13 @@
 #include <string>
 
 #include "chrome/browser/chromeos/input_method/input_method_engine.h"
+#include "chrome/browser/chromeos/input_method/personal_info_suggester.h"
+#include "chrome/browser/chromeos/input_method/suggester.h"
+#include "chrome/browser/chromeos/input_method/suggestion_enums.h"
 #include "chrome/browser/ui/input_method/input_method_engine_base.h"
 
-namespace autofill {
-class PersonalDataManager;
-}  // namespace autofill
-
-class Profile;
-
 namespace chromeos {
 
-// Must match with IMEAssistiveAction in enums.xml
-enum class AssistiveType {
-  kGenericAction = 0,
-  kPersonalEmail = 1,
-  kPersonalAddress = 2,
-  kPersonalPhoneNumber = 3,
-  kPersonalName = 4,
-  kMaxValue = kPersonalName,
-};
-
 // An agent to suggest assistive information when the user types, and adopt or
 // dismiss the suggestion according to the user action.
 class AssistiveSuggester {
@@ -59,37 +46,26 @@
       const ::input_method::InputMethodEngineBase::KeyboardEvent& event);
 
  private:
-  // Get the suggestion according to |text_before_cursor|.
-  base::string16 GetPersonalInfoSuggestion(
-      const base::string16& text_before_cursor);
-
   // Check if any suggestion text should be displayed according to the
   // surrounding text information.
   void Suggest(const base::string16& text, int cursor_pos, int anchor_pos);
 
-  void ShowSuggestion(const base::string16& text);
   void DismissSuggestion();
 
-  InputMethodEngine* const engine_;
+  // Check if suggestion is being shown.
+  bool IsSuggestionShown();
+
+  PersonalInfoSuggester* const personal_info_suggester_;
 
   // ID of the focused text field, 0 if none is focused.
   int context_id_ = -1;
 
-  // User's Chrome user profile.
-  Profile* const profile_;
-
-  // Personal data manager provided by autofill service.
-  autofill::PersonalDataManager* const personal_data_manager_;
-
-  // If we are showing a suggestion right now.
-  bool suggestion_shown_ = false;
-
-  // Assistive type of the last proposed assistive action.
-  AssistiveType proposed_action_type_ = AssistiveType::kGenericAction;
-
   // If the suggestion is dismissed by the user, this is necessary so that we
   // will not reshow the suggestion immediately after the user dismisses it.
   bool suggestion_dismissed_ = false;
+
+  // The current suggester in use, nullptr means no suggestion is shown.
+  Suggester* current_suggester_ = nullptr;
 };
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/input_method/personal_info_suggester.cc b/chrome/browser/chromeos/input_method/personal_info_suggester.cc
new file mode 100644
index 0000000..eab2c98a
--- /dev/null
+++ b/chrome/browser/chromeos/input_method/personal_info_suggester.cc
@@ -0,0 +1,147 @@
+// 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/input_method/personal_info_suggester.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/autofill/personal_data_manager_factory.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "components/autofill/core/browser/data_model/autofill_profile.h"
+#include "components/autofill/core/browser/personal_data_manager.h"
+
+using input_method::InputMethodEngineBase;
+
+namespace chromeos {
+
+namespace {
+
+const char kAssistEmailPrefix[] = "my email is ";
+const char kAssistNamePrefix[] = "my name is ";
+const char kAssistAddressPrefix[] = "my address is ";
+const char kAssistPhoneNumberPrefix[] = "my phone number is ";
+
+}  // namespace
+
+AssistiveType ProposeAssistiveAction(const base::string16& text) {
+  AssistiveType action = AssistiveType::kGenericAction;
+  if (base::EndsWith(text, base::UTF8ToUTF16(kAssistEmailPrefix),
+                     base::CompareCase::INSENSITIVE_ASCII)) {
+    action = AssistiveType::kPersonalEmail;
+  }
+  if (base::EndsWith(text, base::UTF8ToUTF16(kAssistNamePrefix),
+                     base::CompareCase::INSENSITIVE_ASCII)) {
+    action = AssistiveType::kPersonalName;
+  }
+  if (base::EndsWith(text, base::UTF8ToUTF16(kAssistAddressPrefix),
+                     base::CompareCase::INSENSITIVE_ASCII)) {
+    action = AssistiveType::kPersonalAddress;
+  }
+  if (base::EndsWith(text, base::UTF8ToUTF16(kAssistPhoneNumberPrefix),
+                     base::CompareCase::INSENSITIVE_ASCII)) {
+    action = AssistiveType::kPersonalPhoneNumber;
+  }
+  return action;
+}
+
+PersonalInfoSuggester::PersonalInfoSuggester(InputMethodEngine* engine,
+                                             Profile* profile)
+    : engine_(engine),
+      profile_(profile),
+      personal_data_manager_(
+          autofill::PersonalDataManagerFactory::GetForProfile(profile)) {}
+
+PersonalInfoSuggester::~PersonalInfoSuggester() {}
+
+void PersonalInfoSuggester::OnFocus(int context_id) {
+  context_id_ = context_id;
+}
+
+void PersonalInfoSuggester::OnBlur() {
+  context_id_ = -1;
+}
+
+SuggestionStatus PersonalInfoSuggester::HandleKeyEvent(
+    const InputMethodEngineBase::KeyboardEvent& event) {
+  if (suggestion_shown_) {
+    suggestion_shown_ = false;
+    if (event.key == "Tab" || event.key == "Right") {
+      std::string error;
+      engine_->AcceptSuggestion(context_id_, &error);
+      return SuggestionStatus::kAccept;
+    }
+    DismissSuggestion();
+    return SuggestionStatus::kDismiss;
+  }
+  return SuggestionStatus::kNotHandled;
+}
+
+bool PersonalInfoSuggester::Suggest(const base::string16& text) {
+  base::string16 suggestion_text = GetPersonalInfoSuggestion(text);
+  if (!suggestion_text.empty()) {
+    ShowSuggestion(suggestion_text);
+    suggestion_shown_ = true;
+  }
+  return suggestion_shown_;
+}
+
+base::string16 PersonalInfoSuggester::GetPersonalInfoSuggestion(
+    const base::string16& text) {
+  AssistiveType action = ProposeAssistiveAction(text);
+  proposed_action_type_ = action;
+
+  if (action == AssistiveType::kGenericAction)
+    return base::EmptyString16();
+
+  if (action == AssistiveType::kPersonalEmail)
+    return base::UTF8ToUTF16(profile_->GetProfileUserName());
+
+  auto autofill_profiles = personal_data_manager_->GetProfilesToSuggest();
+  if (autofill_profiles.empty())
+    return base::EmptyString16();
+
+  // Currently, we are just picking the first candidate, will improve the
+  // strategy in the future.
+  auto* data = autofill_profiles[0];
+  base::string16 suggestion;
+  switch (action) {
+    case AssistiveType::kPersonalName:
+      suggestion = data->GetRawInfo(autofill::ServerFieldType::NAME_FULL);
+      break;
+    case AssistiveType::kPersonalAddress:
+      suggestion = data->GetRawInfo(
+          autofill::ServerFieldType::ADDRESS_HOME_STREET_ADDRESS);
+      break;
+    case AssistiveType::kPersonalPhoneNumber:
+      suggestion =
+          data->GetRawInfo(autofill::ServerFieldType::PHONE_HOME_WHOLE_NUMBER);
+      break;
+    default:
+      NOTREACHED();
+      break;
+  }
+  return suggestion;
+}
+
+void PersonalInfoSuggester::ShowSuggestion(const base::string16& text) {
+  std::string error;
+  engine_->SetSuggestion(context_id_, text, &error);
+  if (!error.empty()) {
+    LOG(ERROR) << "Fail to show suggestion. " << error;
+  }
+}
+
+AssistiveType PersonalInfoSuggester::GetProposeActionType() {
+  return proposed_action_type_;
+}
+
+void PersonalInfoSuggester::DismissSuggestion() {
+  std::string error;
+  suggestion_shown_ = false;
+  engine_->DismissSuggestion(context_id_, &error);
+  if (!error.empty()) {
+    LOG(ERROR) << "Failed to dismiss suggestion. " << error;
+  }
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/input_method/personal_info_suggester.h b/chrome/browser/chromeos/input_method/personal_info_suggester.h
new file mode 100644
index 0000000..8f3d261
--- /dev/null
+++ b/chrome/browser/chromeos/input_method/personal_info_suggester.h
@@ -0,0 +1,69 @@
+// 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_CHROMEOS_INPUT_METHOD_PERSONAL_INFO_SUGGESTER_H_
+#define CHROME_BROWSER_CHROMEOS_INPUT_METHOD_PERSONAL_INFO_SUGGESTER_H_
+
+#include <string>
+
+#include "chrome/browser/chromeos/input_method/input_method_engine.h"
+#include "chrome/browser/chromeos/input_method/suggester.h"
+#include "chrome/browser/chromeos/input_method/suggestion_enums.h"
+#include "chrome/browser/ui/input_method/input_method_engine_base.h"
+
+namespace autofill {
+class PersonalDataManager;
+}  // namespace autofill
+
+class Profile;
+
+namespace chromeos {
+
+AssistiveType ProposeAssistiveAction(const base::string16& text);
+
+// An agent to suggest personal information when the user types, and adopt or
+// dismiss the suggestion according to the user action.
+class PersonalInfoSuggester : public Suggester {
+ public:
+  explicit PersonalInfoSuggester(InputMethodEngine* engine, Profile* profile);
+  ~PersonalInfoSuggester() override;
+
+  // Suggester overrides:
+  void OnFocus(int context_id) override;
+  void OnBlur() override;
+  SuggestionStatus HandleKeyEvent(
+      const ::input_method::InputMethodEngineBase::KeyboardEvent& event)
+      override;
+  bool Suggest(const base::string16& text) override;
+  void DismissSuggestion() override;
+  AssistiveType GetProposeActionType() override;
+
+ private:
+  // Get the suggestion according to |text_before_cursor|.
+  base::string16 GetPersonalInfoSuggestion(
+      const base::string16& text_before_cursor);
+
+  void ShowSuggestion(const base::string16& text);
+
+  InputMethodEngine* const engine_;
+
+  // ID of the focused text field, 0 if none is focused.
+  int context_id_ = -1;
+
+  // Assistive type of the last proposed assistive action.
+  AssistiveType proposed_action_type_ = AssistiveType::kGenericAction;
+
+  // User's Chrome user profile.
+  Profile* const profile_;
+
+  // Personal data manager provided by autofill service.
+  autofill::PersonalDataManager* const personal_data_manager_;
+
+  // If we are showing a suggestion right now.
+  bool suggestion_shown_ = false;
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_INPUT_METHOD_PERSONAL_INFO_SUGGESTER_H_
diff --git a/chrome/browser/chromeos/input_method/suggester.h b/chrome/browser/chromeos/input_method/suggester.h
new file mode 100644
index 0000000..ccab16e
--- /dev/null
+++ b/chrome/browser/chromeos/input_method/suggester.h
@@ -0,0 +1,45 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_INPUT_METHOD_SUGGESTER_H_
+#define CHROME_BROWSER_CHROMEOS_INPUT_METHOD_SUGGESTER_H_
+
+#include <string>
+
+#include "chrome/browser/chromeos/input_method/input_method_engine.h"
+#include "chrome/browser/chromeos/input_method/suggestion_enums.h"
+#include "chrome/browser/ui/input_method/input_method_engine_base.h"
+
+namespace chromeos {
+
+// A generic agent to suggest when the user types, and adopt or dismiss the
+// suggestion according to the user action.
+class Suggester {
+ public:
+  virtual ~Suggester() {}
+
+  // Called when a text field gains focus, and suggester starts working.
+  virtual void OnFocus(int context_id) = 0;
+
+  // Called when a text field loses focus, and suggester stops working.
+  virtual void OnBlur() = 0;
+
+  // Called when suggestion is being shown.
+  // Returns SuggestionStatus as suggester handles the event.
+  virtual SuggestionStatus HandleKeyEvent(
+      const ::input_method::InputMethodEngineBase::KeyboardEvent& event) = 0;
+
+  // Check if suggestion should be displayed according to the surrounding text
+  // information.
+  virtual bool Suggest(const base::string16& text) = 0;
+
+  virtual void DismissSuggestion() = 0;
+
+  // Return the propose assistive action type.
+  virtual AssistiveType GetProposeActionType() = 0;
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_INPUT_METHOD_SUGGESTER_H_
diff --git a/chrome/browser/chromeos/input_method/suggestion_enums.h b/chrome/browser/chromeos/input_method/suggestion_enums.h
new file mode 100644
index 0000000..cde2958
--- /dev/null
+++ b/chrome/browser/chromeos/input_method/suggestion_enums.h
@@ -0,0 +1,33 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_INPUT_METHOD_SUGGESTION_ENUMS_H_
+#define CHROME_BROWSER_CHROMEOS_INPUT_METHOD_SUGGESTION_ENUMS_H_
+
+#include <string>
+
+#include "chrome/browser/chromeos/input_method/input_method_engine.h"
+#include "chrome/browser/ui/input_method/input_method_engine_base.h"
+
+namespace chromeos {
+
+// Must match with IMEAssistiveAction in enums.xml
+enum class AssistiveType {
+  kGenericAction = 0,
+  kPersonalEmail = 1,
+  kPersonalAddress = 2,
+  kPersonalPhoneNumber = 3,
+  kPersonalName = 4,
+  kMaxValue = kPersonalName,
+};
+
+enum class SuggestionStatus {
+  kNotHandled = 0,
+  kAccept = 1,
+  kDismiss = 2,
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_INPUT_METHOD_SUGGESTION_ENUMS_H_
diff --git a/chrome/browser/chromeos/net/network_pref_state_observer.cc b/chrome/browser/chromeos/net/network_pref_state_observer.cc
index 232a051..a1fd64d4f 100644
--- a/chrome/browser/chromeos/net/network_pref_state_observer.cc
+++ b/chrome/browser/chromeos/net/network_pref_state_observer.cc
@@ -8,7 +8,10 @@
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/sync/wifi_configuration_sync_service_factory.h"
+#include "chromeos/components/sync_wifi/wifi_configuration_sync_service.h"
 #include "chromeos/network/network_handler.h"
+#include "chromeos/network/network_metadata_store.h"
 #include "content/public/browser/notification_service.h"
 
 namespace chromeos {
@@ -40,6 +43,14 @@
   if (ProfileHelper::IsPrimaryProfile(profile)) {
     InitializeNetworkPrefServices(profile);
     notification_registrar_.RemoveAll();
+
+    auto* wifi_sync_service =
+        WifiConfigurationSyncServiceFactory::GetForProfile(profile,
+                                                           /*create=*/false);
+    if (wifi_sync_service) {
+      wifi_sync_service->SetNetworkMetadataStore(
+          NetworkHandler::Get()->network_metadata_store()->GetWeakPtr());
+    }
   }
 }
 
diff --git a/chrome/browser/chromeos/policy/configuration_policy_handler_chromeos.cc b/chrome/browser/chromeos/policy/configuration_policy_handler_chromeos.cc
index f9ea7db..b88af694 100644
--- a/chrome/browser/chromeos/policy/configuration_policy_handler_chromeos.cc
+++ b/chrome/browser/chromeos/policy/configuration_policy_handler_chromeos.cc
@@ -324,11 +324,11 @@
   return crx_file::id_util::IdIsValid(str);
 }
 
-void PinnedLauncherAppsPolicyHandler::ApplyList(
-    std::unique_ptr<base::ListValue> filtered_list,
-    PrefValueMap* prefs) {
+void PinnedLauncherAppsPolicyHandler::ApplyList(base::Value filtered_list,
+                                                PrefValueMap* prefs) {
+  DCHECK(filtered_list.is_list());
   std::vector<base::Value> pinned_apps_list;
-  for (const base::Value& entry : filtered_list->GetList()) {
+  for (const base::Value& entry : filtered_list.GetList()) {
     base::Value app_dict(base::Value::Type::DICTIONARY);
     app_dict.SetKey(kPinnedAppsPrefAppIDKey, entry.Clone());
     pinned_apps_list.push_back(std::move(app_dict));
diff --git a/chrome/browser/chromeos/policy/configuration_policy_handler_chromeos.h b/chrome/browser/chromeos/policy/configuration_policy_handler_chromeos.h
index ce8d736..936dce6 100644
--- a/chrome/browser/chromeos/policy/configuration_policy_handler_chromeos.h
+++ b/chrome/browser/chromeos/policy/configuration_policy_handler_chromeos.h
@@ -9,15 +9,12 @@
 
 #include "base/compiler_specific.h"
 #include "base/macros.h"
+#include "base/values.h"
 #include "chrome/browser/extensions/policy_handlers.h"
 #include "chromeos/network/network_ui_data.h"
 #include "components/onc/onc_constants.h"
 #include "components/policy/core/browser/configuration_policy_handler.h"
 
-namespace base {
-class Value;
-}
-
 namespace policy {
 
 class Schema;
@@ -95,8 +92,7 @@
 
   // Converts the list of strings |filtered_list| to a list of dictionaries and
   // sets the pref.
-  void ApplyList(std::unique_ptr<base::ListValue> filtered_list,
-                 PrefValueMap* prefs) override;
+  void ApplyList(base::Value filtered_list, PrefValueMap* prefs) override;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(PinnedLauncherAppsPolicyHandler);
diff --git a/chrome/browser/chromeos/policy/system_features_disable_list_policy_handler.cc b/chrome/browser/chromeos/policy/system_features_disable_list_policy_handler.cc
index 961e7c9..dc8efe0 100644
--- a/chrome/browser/chromeos/policy/system_features_disable_list_policy_handler.cc
+++ b/chrome/browser/chromeos/policy/system_features_disable_list_policy_handler.cc
@@ -24,10 +24,11 @@
 }
 
 void SystemFeaturesDisableListPolicyHandler::ApplyList(
-    std::unique_ptr<base::ListValue> filtered_list,
+    base::Value filtered_list,
     PrefValueMap* prefs) {
+  DCHECK(filtered_list.is_list());
   prefs->SetValue(policy_prefs::kSystemFeaturesDisableList,
-                  base::Value::FromUniquePtrValue(std::move(filtered_list)));
+                  std::move(filtered_list));
 }
 
 }  // namespace policy
diff --git a/chrome/browser/chromeos/policy/system_features_disable_list_policy_handler.h b/chrome/browser/chromeos/policy/system_features_disable_list_policy_handler.h
index cf854170..2971cc80 100644
--- a/chrome/browser/chromeos/policy/system_features_disable_list_policy_handler.h
+++ b/chrome/browser/chromeos/policy/system_features_disable_list_policy_handler.h
@@ -7,11 +7,9 @@
 
 #include <memory>
 
+#include "base/values.h"
 #include "components/policy/core/browser/configuration_policy_handler.h"
 
-namespace base {
-class ListValue;
-}
 class PrefValueMap;
 class PrefRegistrySimple;
 
@@ -27,8 +25,7 @@
 
  protected:
   // ListPolicyHandler:
-  void ApplyList(std::unique_ptr<base::ListValue> filtered_list,
-                 PrefValueMap* prefs) override;
+  void ApplyList(base::Value filtered_list, PrefValueMap* prefs) override;
 };
 
 }  // namespace policy
diff --git a/chrome/browser/chromeos/printing/print_servers_provider.cc b/chrome/browser/chromeos/printing/print_servers_provider.cc
index 71ab3e8bc..c972e52ec 100644
--- a/chrome/browser/chromeos/printing/print_servers_provider.cc
+++ b/chrome/browser/chromeos/printing/print_servers_provider.cc
@@ -40,8 +40,9 @@
 
 // Parses |data|, a JSON blob, into a vector of PrintServers.  If |data| cannot
 // be parsed, returns data with empty list of servers.
-// This needs to run on a sequence that may block as it can be very slow.
+// This needs to not run on UI thread as it can be very slow.
 TaskResults ParseData(int task_id, std::unique_ptr<std::string> data) {
+  DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
   TaskResults task_data;
   task_data.task_id = task_id;
 
@@ -50,9 +51,6 @@
     return task_data;
   }
 
-  // This could be really slow.
-  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
-                                                base::BlockingType::MAY_BLOCK);
   base::JSONReader::ValueWithError value_with_error =
       base::JSONReader::ReadAndReturnValueWithError(
           *data, base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS);
diff --git a/chrome/browser/chromeos/web_applications/terminal_source.cc b/chrome/browser/chromeos/web_applications/terminal_source.cc
index b51e3b6f..297d225 100644
--- a/chrome/browser/chromeos/web_applications/terminal_source.cc
+++ b/chrome/browser/chromeos/web_applications/terminal_source.cc
@@ -13,7 +13,7 @@
 #include "base/task/post_task.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
-#include "chrome/browser/chromeos/crostini/crostini_pref_names.h"
+#include "chrome/browser/chromeos/crostini/crostini_terminal.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/common/webui_url_constants.h"
@@ -30,8 +30,6 @@
 constexpr base::FilePath::CharType kDefaultFile[] =
     FILE_PATH_LITERAL("html/crosh.html");
 constexpr char kDefaultMime[] = "text/html";
-constexpr char kDefaultTheme[] = "#101010";
-constexpr char kPrefKeyTheme[] = "/hterm/profiles/default/background-color";
 
 void ReadFile(const std::string& relative_path,
               content::URLDataSource::GotDataCallback callback) {
@@ -105,8 +103,10 @@
     path = kDefaultFile;
 
   // Replace $i8n{themeColor} in *.html.
-  if (base::EndsWith(path, ".html", base::CompareCase::INSENSITIVE_ASCII))
-    replacements_["themeColor"] = GetThemeColorFromPrefs();
+  if (base::EndsWith(path, ".html", base::CompareCase::INSENSITIVE_ASCII)) {
+    replacements_["themeColor"] = net::EscapeForHTML(
+        crostini::GetTerminalSettingBackgroundColor(profile_));
+  }
 
   base::ThreadPool::PostTask(
       FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
@@ -129,10 +129,3 @@
 const ui::TemplateReplacements* TerminalSource::GetReplacements() {
   return &replacements_;
 }
-
-std::string TerminalSource::GetThemeColorFromPrefs() {
-  const base::DictionaryValue* value = profile_->GetPrefs()->GetDictionary(
-      crostini::prefs::kCrostiniTerminalSettings);
-  const std::string* theme = value->FindStringKey(kPrefKeyTheme);
-  return theme ? net::EscapeForHTML(*theme) : kDefaultTheme;
-}
diff --git a/chrome/browser/chromeos/web_applications/terminal_source.h b/chrome/browser/chromeos/web_applications/terminal_source.h
index a3b566a..5722d838 100644
--- a/chrome/browser/chromeos/web_applications/terminal_source.h
+++ b/chrome/browser/chromeos/web_applications/terminal_source.h
@@ -37,10 +37,6 @@
   bool ShouldServeMimeTypeAsContentTypeHeader() override;
   const ui::TemplateReplacements* GetReplacements() override;
 
-  // Get theme color from terminal settings in prefs (with HTML escaping).
-  // Returns default theme '#101010' if no prefs set.
-  std::string GetThemeColorFromPrefs();
-
   Profile* profile_;
   ui::TemplateReplacements replacements_;
 
diff --git a/chrome/browser/extensions/DEPS b/chrome/browser/extensions/DEPS
index d83152c9..c435384 100644
--- a/chrome/browser/extensions/DEPS
+++ b/chrome/browser/extensions/DEPS
@@ -34,4 +34,7 @@
     # network::CrossOriginReadBlocking::Action enum.
     "+services/network/cross_origin_read_blocking.h"
   ],
+  "webstore_private_apitest.cc" : [
+    "+chrome/browser/ui/views/parent_permission_dialog_view.h",
+  ],
 }
diff --git a/chrome/browser/extensions/api/commands/command_service.cc b/chrome/browser/extensions/api/commands/command_service.cc
index 6760176..9968178 100644
--- a/chrome/browser/extensions/api/commands/command_service.cc
+++ b/chrome/browser/extensions/api/commands/command_service.cc
@@ -19,7 +19,6 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/accelerator_utils.h"
 #include "chrome/common/extensions/api/commands/commands_handler.h"
-#include "chrome/common/extensions/manifest_handlers/ui_overrides_handler.h"
 #include "chrome/common/pref_names.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/scoped_user_pref_update.h"
@@ -132,23 +131,6 @@
   return BrowserContextKeyedAPIFactory<CommandService>::Get(context);
 }
 
-// static
-bool CommandService::RemovesBookmarkShortcut(const Extension* extension) {
-  return UIOverrides::RemovesBookmarkShortcut(extension) &&
-      (extension->permissions_data()->HasAPIPermission(
-          APIPermission::kBookmarkManagerPrivate) ||
-       FeatureSwitch::enable_override_bookmarks_ui()->IsEnabled());
-}
-
-// static
-bool CommandService::RemovesBookmarkAllTabsShortcut(
-    const Extension* extension) {
-  return UIOverrides::RemovesBookmarkAllTabsShortcut(extension) &&
-         (extension->permissions_data()->HasAPIPermission(
-              APIPermission::kBookmarkManagerPrivate) ||
-          FeatureSwitch::enable_override_bookmarks_ui()->IsEnabled());
-}
-
 bool CommandService::GetBrowserActionCommand(const std::string& extension_id,
                                              QueryType type,
                                              Command* command,
@@ -419,14 +401,6 @@
   return false;
 }
 
-bool CommandService::RequestsBookmarkShortcutOverride(
-    const Extension* extension) const {
-  return RemovesBookmarkShortcut(extension) &&
-         GetSuggestedExtensionCommand(
-             extension->id(),
-             chrome::GetPrimaryChromeAcceleratorForBookmarkTab(), nullptr);
-}
-
 void CommandService::AddObserver(Observer* observer) {
   observers_.AddObserver(observer);
 }
@@ -572,22 +546,10 @@
       return false;
     return (command.accelerator().key_code() >= ui::VKEY_0 &&
             command.accelerator().key_code() <= ui::VKEY_9);
-  } else {
-    // Not a global command, check if Chrome shortcut and whether
-    // we can override it.
-    if (command.accelerator() ==
-            chrome::GetPrimaryChromeAcceleratorForBookmarkTab() &&
-        CommandService::RemovesBookmarkShortcut(extension)) {
-      // If this check fails it either means we have an API to override a
-      // key that isn't a ChromeAccelerator (and the API can therefore be
-      // deprecated) or the IsChromeAccelerator isn't consistently
-      // returning true for all accelerators.
-      DCHECK(chrome::IsChromeAccelerator(command.accelerator(), profile_));
-      return true;
-    }
-
-    return !chrome::IsChromeAccelerator(command.accelerator(), profile_);
   }
+
+  // Not a global command, check if the command is a Chrome shortcut.
+  return !chrome::IsChromeAccelerator(command.accelerator(), profile_);
 }
 
 void CommandService::UpdateExtensionSuggestedCommandPrefs(
diff --git a/chrome/browser/extensions/api/commands/command_service.h b/chrome/browser/extensions/api/commands/command_service.h
index 73251ed8..0c7f057 100644
--- a/chrome/browser/extensions/api/commands/command_service.h
+++ b/chrome/browser/extensions/api/commands/command_service.h
@@ -94,14 +94,6 @@
   // Convenience method to get the CommandService for a profile.
   static CommandService* Get(content::BrowserContext* context);
 
-  // Returns true if |extension| is permitted to and does remove the bookmark
-  // shortcut key.
-  static bool RemovesBookmarkShortcut(const Extension* extension);
-
-  // Returns true if |extension| is permitted to and does remove the bookmark
-  // all tabs shortcut key.
-  static bool RemovesBookmarkAllTabsShortcut(const Extension* extension);
-
   // Gets the command (if any) for the browser action of an extension given
   // its |extension_id|. The function consults the master list to see if
   // the command is active. Returns false if the extension has no browser
@@ -181,10 +173,6 @@
                                     const ui::Accelerator& accelerator,
                                     Command* command) const;
 
-  // Returns true if |extension| requests to override the bookmark shortcut key
-  // and should be allowed to do so.
-  bool RequestsBookmarkShortcutOverride(const Extension* extension) const;
-
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
 
diff --git a/chrome/browser/extensions/api/messaging/native_messaging_policy_handler.cc b/chrome/browser/extensions/api/messaging/native_messaging_policy_handler.cc
index 4dbc135..e9aa21e 100644
--- a/chrome/browser/extensions/api/messaging/native_messaging_policy_handler.cc
+++ b/chrome/browser/extensions/api/messaging/native_messaging_policy_handler.cc
@@ -28,12 +28,10 @@
   return NativeMessagingHostManifest::IsValidName(str);
 }
 
-void NativeMessagingHostListPolicyHandler::ApplyList(
-    std::unique_ptr<base::ListValue> filtered_list,
-    PrefValueMap* prefs) {
-  DCHECK(filtered_list);
-  prefs->SetValue(pref_path_,
-                  base::Value::FromUniquePtrValue(std::move(filtered_list)));
+void NativeMessagingHostListPolicyHandler::ApplyList(base::Value filtered_list,
+                                                     PrefValueMap* prefs) {
+  DCHECK(filtered_list.is_list());
+  prefs->SetValue(pref_path_, std::move(filtered_list));
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/messaging/native_messaging_policy_handler.h b/chrome/browser/extensions/api/messaging/native_messaging_policy_handler.h
index 456f60a4..b6ab5ce 100644
--- a/chrome/browser/extensions/api/messaging/native_messaging_policy_handler.h
+++ b/chrome/browser/extensions/api/messaging/native_messaging_policy_handler.h
@@ -29,8 +29,7 @@
   bool CheckListEntry(const base::Value& value) override;
 
   // Sets |prefs| at pref_path() to |filtered_list|.
-  void ApplyList(std::unique_ptr<base::ListValue> filtered_list,
-                 PrefValueMap* prefs) override;
+  void ApplyList(base::Value filtered_list, PrefValueMap* prefs) override;
 
  private:
   const char* pref_path_;
diff --git a/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc b/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc
index e1abc29..3349dab 100644
--- a/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc
+++ b/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc
@@ -56,9 +56,11 @@
 #include "url/gurl.h"
 
 #if BUILDFLAG(ENABLE_SUPERVISED_USERS)
+// TODO(https://crbug.com/1060801): Here and elsewhere, possibly switch build
+// flag to #if defined(OS_CHROMEOS)
 #include "chrome/browser/supervised_user/supervised_user_service.h"
 #include "chrome/browser/supervised_user/supervised_user_service_factory.h"
-#endif
+#endif  // BUILDFLAG(ENABLE_SUPERVISED_USERS)
 
 using safe_browsing::SafeBrowsingNavigationObserverManager;
 
@@ -159,6 +161,16 @@
 const char kEphemeralAppLaunchingNotSupported[] =
     "Ephemeral launching of apps is no longer supported.";
 
+#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
+// Note that the following error doesn't mean an incorrect password was entered,
+// nor that the parent permisison request was canceled by the user, but rather
+// that the Parent permission request after credential entry and acceptance
+// failed due to either a network connection error or some unsatisfied invariant
+// that prevented the request from completing.
+const char kWebstoreParentPermissionFailedError[] =
+    "Parent permission request failed";
+#endif  // BUILDFLAG(ENABLE_SUPERVISED_USERS)
+
 // The number of user gestures to trace back for the referrer chain.
 const int kExtensionReferrerUserGestureLimit = 2;
 
@@ -278,17 +290,17 @@
 }
 
 WebstorePrivateBeginInstallWithManifest3Function::
-    WebstorePrivateBeginInstallWithManifest3Function() : chrome_details_(this) {
-}
+    WebstorePrivateBeginInstallWithManifest3Function()
+    : chrome_details_(this) {}
+
+WebstorePrivateBeginInstallWithManifest3Function::
+    ~WebstorePrivateBeginInstallWithManifest3Function() = default;
 
 base::string16 WebstorePrivateBeginInstallWithManifest3Function::
     GetBlockedByPolicyErrorMessageForTesting() const {
   return blocked_by_policy_error_message_;
 }
 
-WebstorePrivateBeginInstallWithManifest3Function::
-    ~WebstorePrivateBeginInstallWithManifest3Function() = default;
-
 ExtensionFunction::ResponseAction
 WebstorePrivateBeginInstallWithManifest3Function::Run() {
   params_ = Params::Create(*args_);
@@ -439,11 +451,18 @@
                 : ExtensionInstallPrompt::EXTENSION_PENDING_REQUEST_PROMPT),
         ExtensionInstallPrompt::GetDefaultShowDialogCallback());
   } else {
+    auto prompt = std::make_unique<ExtensionInstallPrompt::Prompt>(
+        ExtensionInstallPrompt::INSTALL_PROMPT);
+
+#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
+    prompt->set_user_is_child(profile->IsChild());
+#endif  // BUILDFLAG(ENABLE_SUPERVISED_USERS)
+
     install_prompt_->ShowDialog(
         base::BindRepeating(&WebstorePrivateBeginInstallWithManifest3Function::
                                 OnInstallPromptDone,
                             this),
-        dummy_extension_.get(), &icon_,
+        dummy_extension_.get(), &icon_, std::move(prompt),
         ExtensionInstallPrompt::GetDefaultShowDialogCallback());
   }
   // Control flow finishes up in OnInstallPromptDone, OnRequestPromptDone or
@@ -463,11 +482,106 @@
   Release();
 }
 
+#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
+
+void WebstorePrivateBeginInstallWithManifest3Function::OnParentPermissionDone(
+    ParentPermissionDialog::Result result) {
+  switch (result) {
+    case ParentPermissionDialog::Result::kParentPermissionReceived:
+      OnParentPermissionReceived();
+      break;
+    case ParentPermissionDialog::Result::kParentPermissionCanceled:
+      OnParentPermissionCanceled();
+      break;
+    case ParentPermissionDialog::Result::kParentPermissionFailed:
+      OnParentPermissionFailed();
+      break;
+  }
+}
+
+void WebstorePrivateBeginInstallWithManifest3Function::
+    OnParentPermissionReceived() {
+  SupervisedUserService* service =
+      SupervisedUserServiceFactory::GetForProfile(chrome_details_.GetProfile());
+  service->AddExtensionApproval(*dummy_extension_);
+
+  HandleInstallProceed();
+  Release();  // Matches the AddRef in Run().
+}
+
+void WebstorePrivateBeginInstallWithManifest3Function::
+    OnParentPermissionCanceled() {
+  if (test_webstore_installer_delegate) {
+    test_webstore_installer_delegate->OnExtensionInstallFailure(
+        dummy_extension_->id(), kWebstoreParentPermissionFailedError,
+        WebstoreInstaller::FailureReason::FAILURE_REASON_CANCELLED);
+  }
+
+  HandleInstallAbort(true /* user_initiated */);
+  Release();  // Matches the AddRef in Run().
+}
+
+void WebstorePrivateBeginInstallWithManifest3Function::
+    OnParentPermissionFailed() {
+  if (test_webstore_installer_delegate) {
+    test_webstore_installer_delegate->OnExtensionInstallFailure(
+        dummy_extension_->id(), kWebstoreParentPermissionFailedError,
+        WebstoreInstaller::FailureReason::FAILURE_REASON_OTHER);
+  }
+
+  Respond(BuildResponse(api::webstore_private::RESULT_UNKNOWN_ERROR,
+                        kWebstoreParentPermissionFailedError));
+
+  Release();  // Matches the AddRef in Run().
+}
+
+bool WebstorePrivateBeginInstallWithManifest3Function::
+    PromptForParentApproval() {
+  Profile* profile = chrome_details_.GetProfile();
+  DCHECK(profile->IsChild());
+  content::WebContents* web_contents = GetSenderWebContents();
+  if (!web_contents) {
+    // The browser window has gone away.
+    Respond(BuildResponse(api::webstore_private::RESULT_USER_CANCELLED,
+                          kWebstoreUserCancelledError));
+    return false;
+  }
+
+  ParentPermissionDialog::DoneCallback done_callback = base::BindOnce(
+      &WebstorePrivateBeginInstallWithManifest3Function::OnParentPermissionDone,
+      this);
+
+  parent_permission_dialog_ =
+      ParentPermissionDialog::CreateParentPermissionDialogForExtension(
+          profile, web_contents, web_contents->GetTopLevelNativeWindow(),
+          gfx::ImageSkia::CreateFrom1xBitmap(icon_), dummy_extension_.get(),
+          std::move(done_callback));
+  parent_permission_dialog_->ShowDialog();
+
+  return true;
+}
+#endif  // BUILDFLAG(ENABLE_SUPERVISED_USERS)
+
 void WebstorePrivateBeginInstallWithManifest3Function::OnInstallPromptDone(
     ExtensionInstallPrompt::Result result) {
   switch (result) {
     case ExtensionInstallPrompt::Result::ACCEPTED:
     case ExtensionInstallPrompt::Result::ACCEPTED_AND_OPTION_CHECKED: {
+#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
+      // Handle parent permission for child accounts on ChromeOS.
+      Profile* profile = chrome_details_.GetProfile();
+      if (g_browser_process->profile_manager()->IsValidProfile(profile) &&
+          profile->IsChild()) {
+        if (PromptForParentApproval()) {
+          // If are showing parent permission dialog, return instead of
+          // break, so that we don't release the ref below.
+          return;
+        } else {
+          // An error occurred, break so that we release the ref below.
+          break;
+        }
+      }
+#endif  // BUILDFLAG(ENABLE_SUPERVISED_USERS)
       HandleInstallProceed();
       break;
     }
@@ -560,7 +674,8 @@
 
 ExtensionFunction::ResponseValue
 WebstorePrivateBeginInstallWithManifest3Function::BuildResponse(
-    api::webstore_private::Result result, const std::string& error) {
+    api::webstore_private::Result result,
+    const std::string& error) {
   if (result != api::webstore_private::RESULT_SUCCESS)
     return ErrorWithArguments(CreateResults(result), error);
 
diff --git a/chrome/browser/extensions/api/webstore_private/webstore_private_api.h b/chrome/browser/extensions/api/webstore_private/webstore_private_api.h
index 417dd62b..f1651c7 100644
--- a/chrome/browser/extensions/api/webstore_private/webstore_private_api.h
+++ b/chrome/browser/extensions/api/webstore_private/webstore_private_api.h
@@ -15,11 +15,18 @@
 #include "chrome/browser/extensions/extension_install_prompt.h"
 #include "chrome/browser/extensions/webstore_install_helper.h"
 #include "chrome/browser/extensions/webstore_installer.h"
+#include "chrome/common/buildflags.h"
 #include "chrome/common/extensions/api/webstore_private.h"
 #include "chrome/common/extensions/webstore_install_result.h"
 #include "extensions/browser/extension_function.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 
+#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
+// TODO(https://crbug.com/1060801): Here and elsewhere, possibly switch build
+// flag to #if defined(OS_CHROMEOS)
+#include "chrome/browser/ui/supervised_user/parent_permission_dialog.h"
+#endif  // BUILDFLAG(ENABLE_SUPERVISED_USERS)
+
 namespace content {
 class GpuFeatureChecker;
 }
@@ -71,6 +78,15 @@
                               InstallHelperResultCode result,
                               const std::string& error_message) override;
 
+#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
+  void OnParentPermissionDone(ParentPermissionDialog::Result result);
+  void OnParentPermissionReceived();
+  void OnParentPermissionCanceled();
+  void OnParentPermissionFailed();
+  // Returns true if the parental approval prompt was shown, false if there was
+  // an error showing it.
+  bool PromptForParentApproval();
+#endif  // BUILDFLAG(ENABLE_SUPERVISED_USERS)
   void OnInstallPromptDone(ExtensionInstallPrompt::Result result);
   void OnRequestPromptDone(ExtensionInstallPrompt::Result result);
   void OnBlockByPolicyPromptDone();
@@ -110,6 +126,10 @@
 
   base::string16 blocked_by_policy_error_message_;
 
+#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
+  std::unique_ptr<ParentPermissionDialog> parent_permission_dialog_;
+#endif
+
   std::unique_ptr<ExtensionInstallPrompt> install_prompt_;
 };
 
diff --git a/chrome/browser/extensions/api/webstore_private/webstore_private_apitest.cc b/chrome/browser/extensions/api/webstore_private/webstore_private_apitest.cc
index 8e10ae9e..2a9ef3e 100644
--- a/chrome/browser/extensions/api/webstore_private/webstore_private_apitest.cc
+++ b/chrome/browser/extensions/api/webstore_private/webstore_private_apitest.cc
@@ -42,8 +42,21 @@
 #include "ui/gl/gl_switches.h"
 
 #if BUILDFLAG(ENABLE_SUPERVISED_USERS)
+// TODO(https://crbug.com/1060801): Here and elsewhere, possibly switch build
+// flag to #if defined(OS_CHROMEOS)
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/supervised_user/logged_in_user_mixin.h"
 #include "chrome/browser/supervised_user/supervised_user_constants.h"
+#include "chrome/browser/supervised_user/supervised_user_features.h"
+#include "chrome/browser/supervised_user/supervised_user_service.h"
+#include "chrome/browser/supervised_user/supervised_user_service_factory.h"
+#include "chrome/browser/ui/supervised_user/parent_permission_dialog.h"
+#include "chrome/browser/ui/views/parent_permission_dialog_view.h"
+#include "chrome/common/pref_names.h"
+#include "components/account_id/account_id.h"
+#include "components/signin/public/identity_manager/identity_test_environment.h"
+#include "extensions/common/extension_builder.h"
 
 #if defined(OS_CHROMEOS)
 #include "chromeos/constants/chromeos_switches.h"
@@ -79,6 +92,7 @@
     received_failure_ = true;
     id_ = id;
     error_ = error;
+    last_failure_reason_ = reason;
 
     if (waiting_) {
       waiting_ = false;
@@ -94,12 +108,17 @@
     content::RunMessageLoop();
   }
   bool received_success() const { return received_success_; }
+  bool received_failure() const { return received_failure_; }
   const std::string& id() const { return id_; }
+  WebstoreInstaller::FailureReason last_failure_reason() {
+    return last_failure_reason_;
+  }
 
  private:
   bool received_failure_;
   bool received_success_;
   bool waiting_;
+  WebstoreInstaller::FailureReason last_failure_reason_;
   std::string id_;
   std::string error_;
 };
@@ -372,15 +391,21 @@
 }
 
 #if BUILDFLAG(ENABLE_SUPERVISED_USERS)
+static constexpr char kTestChildEmail[] = "test_child_user@google.com";
+static constexpr char kTestChildGaiaId[] = "8u8tuw09sufncmnaos";
+
 class ExtensionWebstorePrivateApiTestChild
     : public ExtensionWebstorePrivateApiTest {
  public:
   ExtensionWebstorePrivateApiTestChild()
       : embedded_test_server_(std::make_unique<net::EmbeddedTestServer>()),
-        logged_in_user_mixin_(&mixin_host_,
-                              chromeos::LoggedInUserMixin::LogInType::kChild,
-                              embedded_test_server_.get(),
-                              this) {
+        logged_in_user_mixin_(
+            &mixin_host_,
+            chromeos::LoggedInUserMixin::LogInType::kChild,
+            embedded_test_server_.get(),
+            this,
+            true /* should_launch_browser */,
+            AccountId::FromUserEmailGaiaId(kTestChildEmail, kTestChildGaiaId)) {
     // Suppress regular user login to enable child user login.
     set_chromeos_user_ = false;
   }
@@ -422,10 +447,39 @@
         browser_main_parts);
   }
 
+  void InitializeFamilyData() {
+    // Set up the child user's custodians (i.e. parents).
+    ASSERT_TRUE(browser());
+    PrefService* prefs = browser()->profile()->GetPrefs();
+    prefs->SetString(prefs::kSupervisedUserCustodianEmail,
+                     "test_parent_0@google.com");
+    prefs->SetString(prefs::kSupervisedUserCustodianObfuscatedGaiaId,
+                     "239029320");
+
+    prefs->SetString(prefs::kSupervisedUserSecondCustodianEmail,
+                     "test_parent_1@google.com");
+    prefs->SetString(prefs::kSupervisedUserSecondCustodianObfuscatedGaiaId,
+                     "85948533");
+
+    // Set up the identity test environment, which provides fake
+    // OAuth refresh tokens.
+    identity_test_env_ = std::make_unique<signin::IdentityTestEnvironment>();
+    identity_test_env_->MakeAccountAvailable(kTestChildEmail);
+    identity_test_env_->SetPrimaryAccount(kTestChildEmail);
+    identity_test_env_->SetRefreshTokenForPrimaryAccount();
+    identity_test_env_->SetAutomaticIssueOfAccessTokens(true);
+  }
+
   void SetUpOnMainThread() override {
     mixin_host_.SetUpOnMainThread();
+    logged_in_user_mixin_.LogInUser(true /* issue_any_scope_token */);
     ExtensionWebstorePrivateApiTest::SetUpOnMainThread();
-    logged_in_user_mixin_.LogInUser(true /*issue_any_scope_token*/);
+
+    InitializeFamilyData();
+    SupervisedUserService* service =
+        SupervisedUserServiceFactory::GetForProfile(profile());
+    service->SetSupervisedUserExtensionsMayRequestPermissionsPrefForTesting(
+        true);
   }
 
   void TearDownOnMainThread() override {
@@ -443,22 +497,143 @@
     ExtensionWebstorePrivateApiTest::TearDown();
   }
 
+  chromeos::LoggedInUserMixin* GetLoggedInUserMixin() {
+    return &logged_in_user_mixin_;
+  }
+
+  void SetNextReAuthStatus(
+      const GaiaAuthConsumer::ReAuthProofTokenStatus next_status) {
+    GetLoggedInUserMixin()
+        ->GetFakeGaiaMixin()
+        ->fake_gaia()
+        ->SetNextReAuthStatus(next_status);
+  }
+
+ protected:
+  std::unique_ptr<signin::IdentityTestEnvironment> identity_test_env_;
+
  private:
   // Replicate what MixinBasedInProcessBrowserTest does since inheriting from
   // that class is inconvenient here.
   InProcessBrowserTestMixinHost mixin_host_;
   // Create another embedded test server to avoid starting the same one twice.
   std::unique_ptr<net::EmbeddedTestServer> embedded_test_server_;
-
   chromeos::LoggedInUserMixin logged_in_user_mixin_;
 };
 
-// Tests that extension installation is blocked for child accounts, and
-// attempting to do so produces a special error code.
-// Note: This will have to be updated when we enable child-initiated installs.
-IN_PROC_BROWSER_TEST_F(ExtensionWebstorePrivateApiTestChild, InstallBlocked) {
-  ASSERT_TRUE(browser());
-  ASSERT_TRUE(RunInstallTest("begin_install_fail_child.html", "extension.crx"));
+class ExtensionWebstorePrivateApiTestChildInstallDisabled
+    : public ExtensionWebstorePrivateApiTestChild {
+ public:
+  ExtensionWebstorePrivateApiTestChildInstallDisabled() {
+    feature_list_.InitWithFeatures(
+        {}, {supervised_users::kSupervisedUserInitiatedExtensionInstall});
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+// Tests that extension installation is blocked for child accounts when
+// the feature flag is disabled.
+IN_PROC_BROWSER_TEST_F(ExtensionWebstorePrivateApiTestChildInstallDisabled,
+                       InstallBlocked) {
+  ASSERT_TRUE(RunInstallTest("install_blocked_child.html", "app.crx"));
+}
+
+static constexpr char kTestAppId[] = "iladmdjkfniedhfhcfoefgojhgaiaccc";
+static constexpr char kTestAppVersion[] = "0.1";
+
+// Test fixture for various cases of installation for child accounts
+// when the feature flag is enabled.
+class ExtensionWebstorePrivateApiTestChildInstallEnabled
+    : public ExtensionWebstorePrivateApiTestChild,
+      public TestParentPermissionDialogViewObserver {
+ public:
+  // The next dialog action to take.
+  enum class NextDialogAction {
+    kCancel,
+    kAccept,
+  };
+
+  ExtensionWebstorePrivateApiTestChildInstallEnabled()
+      : TestParentPermissionDialogViewObserver(this) {
+    feature_list_.InitWithFeatures(
+        {supervised_users::kSupervisedUserInitiatedExtensionInstall}, {});
+  }
+
+  // TestParentPermissionDialogViewObserver override:
+  void OnTestParentPermissionDialogViewCreated(
+      ParentPermissionDialogView* view) override {
+    view->SetRepromptAfterIncorrectCredential(false);
+    view->SetIdentityManagerForTesting(identity_test_env_->identity_manager());
+    // Everything is set up, so take the next action.
+    if (next_dialog_action_) {
+      switch (next_dialog_action_.value()) {
+        case NextDialogAction::kCancel:
+          view->CancelDialog();
+          break;
+        case NextDialogAction::kAccept:
+          view->AcceptDialog();
+          break;
+      }
+    }
+  }
+
+  void set_next_dialog_action(NextDialogAction action) {
+    next_dialog_action_ = action;
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+  base::Optional<NextDialogAction> next_dialog_action_;
+};
+
+// Tests install for a child when parent permission is granted.
+IN_PROC_BROWSER_TEST_F(ExtensionWebstorePrivateApiTestChildInstallEnabled,
+                       ParentPermissionGranted) {
+  WebstoreInstallListener listener;
+  WebstorePrivateApi::SetWebstoreInstallerDelegateForTesting(&listener);
+  set_next_dialog_action(NextDialogAction::kAccept);
+
+  // Tell the Reauth API client to return a success for the next reauth
+  // request.
+  SetNextReAuthStatus(GaiaAuthConsumer::ReAuthProofTokenStatus::kSuccess);
+  ASSERT_TRUE(RunInstallTest("install_child.html", "app.crx"));
+  listener.Wait();
+  ASSERT_TRUE(listener.received_success());
+  ASSERT_EQ(kTestAppId, listener.id());
+
+  scoped_refptr<const Extension> extension =
+      extensions::ExtensionBuilder("test extension")
+          .SetID(kTestAppId)
+          .SetVersion(kTestAppVersion)
+          .Build();
+  SupervisedUserService* service =
+      SupervisedUserServiceFactory::GetForProfile(profile());
+  ASSERT_TRUE(service->IsExtensionAllowed(*extension));
+}
+
+// Tests no install occurs for a child when the parent permission
+// dialog is canceled.
+IN_PROC_BROWSER_TEST_F(ExtensionWebstorePrivateApiTestChildInstallEnabled,
+                       ParentPermissionCanceled) {
+  WebstoreInstallListener listener;
+  set_next_dialog_action(NextDialogAction::kCancel);
+  WebstorePrivateApi::SetWebstoreInstallerDelegateForTesting(&listener);
+  ASSERT_TRUE(RunInstallTest("install_cancel_child.html", "app.crx"));
+  listener.Wait();
+  ASSERT_TRUE(listener.received_failure());
+  ASSERT_EQ(kTestAppId, listener.id());
+  ASSERT_EQ(listener.last_failure_reason(),
+            WebstoreInstaller::FailureReason::FAILURE_REASON_CANCELLED);
+  scoped_refptr<const Extension> extension =
+      extensions::ExtensionBuilder("test extension")
+          .SetID(kTestAppId)
+          .SetVersion(kTestAppVersion)
+          .Build();
+  SupervisedUserService* service =
+      SupervisedUserServiceFactory::GetForProfile(profile());
+  ASSERT_FALSE(service->IsExtensionAllowed(*extension));
 }
 
 #endif  // BUILDFLAG(ENABLE_SUPERVISED_USERS)
diff --git a/chrome/browser/extensions/chrome_ui_overrides_browsertest.cc b/chrome/browser/extensions/chrome_ui_overrides_browsertest.cc
deleted file mode 100644
index 1e9157e1..0000000
--- a/chrome/browser/extensions/chrome_ui_overrides_browsertest.cc
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/command_line.h"
-#include "chrome/app/chrome_command_ids.h"
-#include "chrome/browser/extensions/extension_browsertest.h"
-#include "chrome/browser/ui/browser_commands.h"
-#include "chrome/common/url_constants.h"
-
-using ChromeUIOverridesBrowserTest = extensions::ExtensionBrowserTest;
-
-IN_PROC_BROWSER_TEST_F(ChromeUIOverridesBrowserTest,
-                       BookmarkShortcutOverrides) {
-  // This functionality requires a feature flag.
-  base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
-      "--enable-override-bookmarks-ui", "1");
-
-  ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("bookmarks_ui")));
-  EXPECT_FALSE(chrome::IsCommandEnabled(browser(), IDC_BOOKMARK_THIS_TAB));
-
-  AddTabAtIndex(1,
-                GURL(chrome::kChromeUINewTabURL),
-                ui::PAGE_TRANSITION_TYPED);
-  EXPECT_FALSE(chrome::IsCommandEnabled(browser(), IDC_BOOKMARK_ALL_TABS));
-}
diff --git a/chrome/browser/extensions/corb_and_cors_extension_browsertest.cc b/chrome/browser/extensions/corb_and_cors_extension_browsertest.cc
index ad809cbd..d932fbfa 100644
--- a/chrome/browser/extensions/corb_and_cors_extension_browsertest.cc
+++ b/chrome/browser/extensions/corb_and_cors_extension_browsertest.cc
@@ -206,6 +206,7 @@
               "*://fetch-initiator.com/*",
               "*://127.0.0.1/*",  // Initiator in AppCache tests.
               "*://cross-site.com/*",
+              "*://*.subdomain.com/*",
               "*://other-with-permission.com/*"
               // This list intentionally does NOT include
               // other-without-permission.com.
@@ -243,6 +244,8 @@
   void VerifyPassiveUmaForAllowlistForCors(
       const base::HistogramTester& histograms,
       base::Optional<bool> expected_value) {
+    SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
+
     const char* kUmaName =
         "SiteIsolation.XSD.Browser.AllowedByCorbButNotCors.ContentScript";
     bool expect_uma_presence = expected_value.has_value();
@@ -796,6 +799,40 @@
                                "nosniff.xml - body\n");
 }
 
+// Coverage of *.subdomain.com extension permissions for CORB-eligible fetches
+// (via nosniff.xml).
+IN_PROC_BROWSER_TEST_P(CorbAndCorsExtensionBrowserTest,
+                       FromProgrammaticContentScript_SubdomainPermissions) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  ASSERT_TRUE(InstallExtension());
+
+  // Navigate to a fetch-initiator.com page.
+  GURL page_url = GetTestPageUrl("fetch-initiator.com");
+  ui_test_utils::NavigateToURL(browser(), page_url);
+  ASSERT_EQ(page_url,
+            active_web_contents()->GetMainFrame()->GetLastCommittedURL());
+  ASSERT_EQ(url::Origin::Create(page_url),
+            active_web_contents()->GetMainFrame()->GetLastCommittedOrigin());
+
+  // Verify behavior for fetching URLs covered by extension permissions.
+  GURL kAllowedUrls[] = {
+      embedded_test_server()->GetURL("subdomain.com", "/nosniff.xml"),
+      embedded_test_server()->GetURL("foo.subdomain.com", "/nosniff.xml"),
+  };
+  for (const GURL& allowed_url : kAllowedUrls) {
+    SCOPED_TRACE(::testing::Message() << "allowed_url = " << allowed_url);
+
+    base::HistogramTester histograms;
+    std::string fetch_result =
+        FetchViaContentScript(allowed_url, active_web_contents());
+
+    // Verify whether the fetch worked or not (expectations differ depending on
+    // various factors - see the body of VerifyFetchFromContentScript).
+    VerifyFetchFromContentScript(histograms, fetch_result,
+                                 "nosniff.xml - body\n");
+  }
+}
+
 // Test that verifies the current, baked-in (but not necessarily desirable
 // behavior) where a content script injected by an extension can bypass
 // CORS (and CORB) for any hosts the extension has access to.
@@ -833,7 +870,7 @@
 // Test that verifies CORS-allowed fetches work for targets that are not
 // covered by the extension permissions.
 IN_PROC_BROWSER_TEST_P(CorbAndCorsExtensionBrowserTest,
-                       FromProgrammaticContentScript_NoPermissionToTarget) {
+                       ContentScript_CorsAllowedByServer_NoPermissionToTarget) {
   ASSERT_TRUE(embedded_test_server()->Start());
   ASSERT_TRUE(InstallExtension());
 
@@ -846,18 +883,53 @@
             active_web_contents()->GetMainFrame()->GetLastCommittedOrigin());
 
   // Inject a content script that performs a cross-origin fetch to
-  // cross-site.com.
+  // other-without-permission.com.
   base::HistogramTester histograms;
   GURL cross_site_resource(embedded_test_server()->GetURL(
       "other-without-permission.com", "/cors-ok.txt"));
   std::string fetch_result =
       FetchViaContentScript(cross_site_resource, active_web_contents());
 
-  // Verify whether the fetch worked or not.
+  // Verify that the fetch succeeded (because of the server's
+  // Access-Control-Allow-Origin response header).
   EXPECT_EQ("cors-ok.txt - body\n", fetch_result);
   VerifyFetchFromContentScriptWasAllowedByCorb(histograms);
 }
 
+// Test that verifies that CORS blocks non-CORB-eligible fetches for targets
+// that are not covered by the extension permissions.
+IN_PROC_BROWSER_TEST_P(CorbAndCorsExtensionBrowserTest,
+                       ContentScript_CorsIgnoredByServer_NoPermissionToTarget) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  ASSERT_TRUE(InstallExtension());
+
+  // Navigate to a fetch-initiator.com page.
+  GURL page_url = GetTestPageUrl("fetch-initiator.com");
+  ui_test_utils::NavigateToURL(browser(), page_url);
+  ASSERT_EQ(page_url,
+            active_web_contents()->GetMainFrame()->GetLastCommittedURL());
+  ASSERT_EQ(url::Origin::Create(page_url),
+            active_web_contents()->GetMainFrame()->GetLastCommittedOrigin());
+
+  // Inject a content script that performs a cross-origin fetch to
+  // other-without-permission.com.
+  base::HistogramTester histograms;
+  GURL cross_site_resource(embedded_test_server()->GetURL(
+      "other-without-permission.com", "/save_page/text.txt"));
+  std::string fetch_result =
+      FetchViaContentScript(cross_site_resource, active_web_contents());
+
+  // Verify that the fetch was blocked by CORS (because the extension has no
+  // permission to the target + server didn't reply with
+  // Access-Control-Allow-Origin response header).
+  EXPECT_EQ(kCorsErrorWhenFetching, fetch_result);
+
+  // Verify that the fetch was allowed by CORB (because the response sniffed as
+  // didn't sniff as html/xml/json).
+  VerifyFetchFromContentScriptWasAllowedByCorb(histograms,
+                                               true /* expecting_sniffing */);
+}
+
 // Tests that same-origin fetches (same-origin relative to the webpage the
 // content script is injected into) are allowed.  See also
 // https://crbug.com/918660.
@@ -913,18 +985,10 @@
       embedded_test_server()->GetURL("cross-site.com", "/save_page/text.txt"));
   std::string fetch_result =
       FetchViaContentScript(cross_site_resource, active_web_contents());
-  SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
 
-  if (IsCorbExpectedToBeTurnedOffAltogether()) {
-    // Verify that CORB didn't run.
-    EXPECT_EQ(
-        0u,
-        histograms.GetTotalCountsForPrefix("SiteIsolation.XSD.Browser").size());
-  } else {
-    // Verify that CORB sniffing allowed the response.
-    VerifyFetchFromContentScriptWasAllowedByCorb(histograms,
-                                                 true /* expecting_sniffing */);
-  }
+  // Verify that CORB sniffing allowed the response.
+  VerifyFetchFromContentScriptWasAllowedByCorb(histograms,
+                                               true /* expecting_sniffing */);
 
   if (ShouldAllowlistAlsoApplyToOorCors() &&
       AreContentScriptFetchesExpectedToBeBlocked()) {
@@ -946,6 +1010,61 @@
   VerifyPassiveUmaForAllowlistForCors(histograms, true);
 }
 
+// Coverage of *.subdomain.com extension permissions for non-CORB eligible
+// fetches (via save_page/text.txt).
+IN_PROC_BROWSER_TEST_P(
+    CorbAndCorsExtensionBrowserTest,
+    FromProgrammaticContentScript_AllowedTextResource_SubdomainPermissions) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  ASSERT_TRUE(InstallExtension());
+
+  // Navigate to a fetch-initiator.com page.
+  GURL page_url = GetTestPageUrl("fetch-initiator.com");
+  ui_test_utils::NavigateToURL(browser(), page_url);
+  ASSERT_EQ(page_url,
+            active_web_contents()->GetMainFrame()->GetLastCommittedURL());
+  ASSERT_EQ(url::Origin::Create(page_url),
+            active_web_contents()->GetMainFrame()->GetLastCommittedOrigin());
+
+  // Verify behavior for fetching URLs covered by extension permissions.
+  GURL kAllowedUrls[] = {
+      embedded_test_server()->GetURL("subdomain.com", "/save_page/text.txt"),
+      embedded_test_server()->GetURL("x.subdomain.com", "/save_page/text.txt"),
+  };
+  for (const GURL& allowed_url : kAllowedUrls) {
+    SCOPED_TRACE(::testing::Message() << "allowed_url = " << allowed_url);
+
+    // Inject a content script that performs a cross-origin fetch to
+    // cross-site.com.
+    base::HistogramTester histograms;
+    std::string fetch_result =
+        FetchViaContentScript(allowed_url, active_web_contents());
+
+    // Verify that CORB sniffing allowed the response.
+    VerifyFetchFromContentScriptWasAllowedByCorb(histograms,
+                                                 true /* expecting_sniffing */);
+
+    if (ShouldAllowlistAlsoApplyToOorCors() &&
+        AreContentScriptFetchesExpectedToBeBlocked()) {
+      // Verify that the response body was blocked by CORS.
+      EXPECT_EQ(kCorsErrorWhenFetching, fetch_result);
+    } else {
+      // Verify that the response body was not blocked by either CORB nor CORS.
+      //
+      // StartsWith (rather than equality) is used in the verification step to
+      // account for \n VS \r\n difference on Windows.
+      EXPECT_THAT(fetch_result,
+                  ::testing::StartsWith(
+                      "text-object.txt: ae52dd09-9746-4b7e-86a6-6ada5e2680c2"));
+    }
+
+    // This is the kind of response (i.e., cross-origin fetch of a non-CORB
+    // type) that could be affected by the planned
+    // CorbAllowlistAlsoAppliesToOorCors feature.
+    VerifyPassiveUmaForAllowlistForCors(histograms, true);
+  }
+}
+
 // Test that responses that would have been allowed by CORB after sniffing are
 // included in the AllowedByCorbButNotCors UMA.
 IN_PROC_BROWSER_TEST_P(CorbAndCorsExtensionBrowserTest,
@@ -969,18 +1088,10 @@
       "cross-site.com", "/downloads/image-labeled-as-html.png"));
   std::string fetch_result =
       FetchViaContentScript(cross_site_resource, active_web_contents());
-  SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
 
-  if (IsCorbExpectedToBeTurnedOffAltogether()) {
-    // Verify that CORB didn't run.
-    EXPECT_EQ(
-        0u,
-        histograms.GetTotalCountsForPrefix("SiteIsolation.XSD.Browser").size());
-  } else {
-    // Verify that CORB sniffing allowed the response.
-    VerifyFetchFromContentScriptWasAllowedByCorb(histograms,
-                                                 true /* expecting_sniffing */);
-  }
+  // Verify that CORB sniffing allowed the response.
+  VerifyFetchFromContentScriptWasAllowedByCorb(histograms,
+                                               true /* expecting_sniffing */);
 
   if (ShouldAllowlistAlsoApplyToOorCors() &&
       AreContentScriptFetchesExpectedToBeBlocked()) {
@@ -1725,7 +1836,7 @@
 
   // Verify the request headers (e.g. Origin and Sec-Fetch-Site headers).
   cors_request.WaitForRequest();
-  if (IsExtensionAllowlisted()) {
+  if (IsExtensionAllowlisted() || !ShouldAllowlistAlsoApplyToOorCors()) {
     // Content scripts of allowlisted extensions should be exempted from CORS,
     // based on the websites the extension has permission for, via extension
     // manifest.  Therefore, there should be no "Origin" header.
@@ -1733,9 +1844,6 @@
         cors_request.http_request()->headers,
         testing::Not(testing::Contains(testing::Pair("Origin", testing::_))));
   } else {
-#if 0
-    // TODO(lukasza): https://crbug.com/920638:
-    //
     // Content scripts of non-allowlisted extensions should participate in
     // regular CORS, just as if the request was issued from the webpage that the
     // content script got injected into.  Therefore we should expect the Origin
@@ -1743,7 +1851,6 @@
     EXPECT_THAT(
         cors_request.http_request()->headers,
         testing::Contains(testing::Pair("Origin", page_origin_string.c_str())));
-#endif
   }
 
   // Respond with Access-Control-Allow-Origin that matches the origin of the web
diff --git a/chrome/browser/extensions/cross_origin_xhr_apitest.cc b/chrome/browser/extensions/cross_origin_xhr_apitest.cc
index 5dc3896f..35191b67 100644
--- a/chrome/browser/extensions/cross_origin_xhr_apitest.cc
+++ b/chrome/browser/extensions/cross_origin_xhr_apitest.cc
@@ -2,42 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/extensions/extension_apitest.h"
 #include "net/dns/mock_host_resolver.h"
-#include "services/network/public/cpp/features.h"
 
 const base::FilePath::CharType kFtpDocRoot[] =
     FILE_PATH_LITERAL("chrome/test/data");
 
 class CrossOriginXHR : public extensions::ExtensionApiTest {
  public:
-  CrossOriginXHR() {
-    // TODO(lukasza): https://crbug.com/1061567: Migrate tests related to
-    // content scripts into the CrossOriginReadBlockingExtensionTest suite
-    // (where it is easier to separately tweak test case expectations based on
-    // the enabled features + where a big subset of the allowlisting/corb/etc
-    // test matrix is covered already).
-    //
-    // Affected tests (note that some of the tests do not need to be migrated if
-    // they are already redundant wrt the coverage provided by the
-    // CrossOriginReadBlockingExtensionTest suite):
-    // - CrossOriginXHR.ContentScript
-    //   - allowedOrigin
-    //   - allowedSubdomain
-    //   - noSubdomain
-    scoped_feature_list_.InitAndDisableFeature(
-        network::features::kCorbAllowlistAlsoAppliesToOorCors);
-  }
-
   void SetUpOnMainThread() override {
     extensions::ExtensionApiTest::SetUpOnMainThread();
     host_resolver()->AddRule("*.com", "127.0.0.1");
     ASSERT_TRUE(StartEmbeddedTestServer());
   }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 IN_PROC_BROWSER_TEST_F(CrossOriginXHR, BackgroundPage) {
diff --git a/chrome/browser/extensions/extension_keybinding_apitest.cc b/chrome/browser/extensions/extension_keybinding_apitest.cc
index d0cbdfb1..f333e5c 100644
--- a/chrome/browser/extensions/extension_keybinding_apitest.cc
+++ b/chrome/browser/extensions/extension_keybinding_apitest.cc
@@ -56,8 +56,6 @@
 
 // Name of the command for the "basics" test extension.
 const char kBasicsShortcutCommandName[] = "toggle-feature";
-// Name of the command for the overwrite_bookmark_shortcut test extension.
-const char kOverwriteBookmarkShortcutCommandName[] = "send message";
 
 #if defined(OS_MACOSX)
 const char kBookmarkKeybinding[] = "Command+D";
@@ -535,127 +533,6 @@
   }
 }
 
-// This test validates that an extension can remove the Chrome bookmark shortcut
-// if it has requested to do so.
-IN_PROC_BROWSER_TEST_F(CommandsApiTest, RemoveBookmarkShortcut) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-
-  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
-
-  // This functionality requires a feature flag.
-  base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
-      "--enable-override-bookmarks-ui", "1");
-
-  ASSERT_TRUE(RunExtensionTest("keybinding/remove_bookmark_shortcut"))
-      << message_;
-
-  EXPECT_FALSE(chrome::IsCommandEnabled(browser(), IDC_BOOKMARK_THIS_TAB));
-}
-
-// This test validates that an extension cannot remove the Chrome bookmark
-// shortcut without being given permission with a feature flag.
-IN_PROC_BROWSER_TEST_F(CommandsApiTest,
-                       RemoveBookmarkShortcutWithoutPermission) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-
-  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
-
-  EXPECT_TRUE(RunExtensionTestIgnoreManifestWarnings(
-      "keybinding/remove_bookmark_shortcut"));
-
-  EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_BOOKMARK_THIS_TAB));
-}
-
-// This test validates that an extension that removes the Chrome bookmark
-// shortcut continues to remove the bookmark shortcut with a user-assigned
-// Ctrl+D shortcut (i.e. it does not trigger the overwrite functionality).
-IN_PROC_BROWSER_TEST_F(CommandsApiTest,
-                       RemoveBookmarkShortcutWithUserKeyBinding) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-
-  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
-
-  // This functionality requires a feature flag.
-  base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
-      "--enable-override-bookmarks-ui", "1");
-
-  ASSERT_TRUE(RunExtensionTest("keybinding/remove_bookmark_shortcut"))
-      << message_;
-
-  // Check that the shortcut is removed.
-  CommandService* command_service = CommandService::Get(browser()->profile());
-  const Extension* extension = GetSingleLoadedExtension();
-  // Simulate the user setting a keybinding to Ctrl+D.
-  command_service->UpdateKeybindingPrefs(
-      extension->id(), manifest_values::kBrowserActionCommandEvent,
-      kBookmarkKeybinding);
-
-  // Force the command enable state to be recalculated.
-  browser()->command_controller()->ExtensionStateChanged();
-
-  EXPECT_FALSE(chrome::IsCommandEnabled(browser(), IDC_BOOKMARK_THIS_TAB));
-}
-
-// This test validates that an extension can override the Chrome bookmark
-// shortcut if it has requested to do so.
-IN_PROC_BROWSER_TEST_F(CommandsApiTest, OverwriteBookmarkShortcut) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-
-  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
-
-  // This functionality requires a feature flag.
-  base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
-      "--enable-override-bookmarks-ui", "1");
-
-  ASSERT_TRUE(RunExtensionTest("keybinding/overwrite_bookmark_shortcut"))
-      << message_;
-
-  ui_test_utils::NavigateToURL(
-      browser(), embedded_test_server()->GetURL("/extensions/test_file.txt"));
-
-  // Activate the shortcut (Ctrl+D) to send a test message.
-  ExtensionTestMessageListener test_listener(false);  // Won't reply.
-  ASSERT_TRUE(SendBookmarkKeyPressSync(browser()));
-  EXPECT_TRUE(test_listener.WaitUntilSatisfied());
-  EXPECT_EQ(std::string(kOverwriteBookmarkShortcutCommandName),
-            test_listener.message());
-}
-
-// This test validates that an extension that requests to override the Chrome
-// bookmark shortcut, but does not get the keybinding, does not remove the
-// bookmark UI.
-IN_PROC_BROWSER_TEST_F(CommandsApiTest,
-                       OverwriteBookmarkShortcutWithoutKeybinding) {
-  // This functionality requires a feature flag.
-  base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
-      "--enable-override-bookmarks-ui", "1");
-
-  ASSERT_TRUE(embedded_test_server()->Start());
-
-  EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_BOOKMARK_THIS_TAB));
-
-  ASSERT_TRUE(RunExtensionTest("keybinding/overwrite_bookmark_shortcut"))
-      << message_;
-
-  const Extension* extension = GetSingleLoadedExtension();
-  CommandService* command_service = CommandService::Get(browser()->profile());
-  CommandMap commands;
-  // Verify the expected command is present.
-  EXPECT_TRUE(command_service->GetNamedCommands(
-      extension->id(), CommandService::SUGGESTED, CommandService::ANY_SCOPE,
-      &commands));
-  EXPECT_EQ(1u, commands.count(kOverwriteBookmarkShortcutCommandName));
-
-  // Simulate the user removing the Ctrl+D keybinding from the command.
-  command_service->RemoveKeybindingPrefs(
-      extension->id(), kOverwriteBookmarkShortcutCommandName);
-
-  // Force the command enable state to be recalculated.
-  browser()->command_controller()->ExtensionStateChanged();
-
-  EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_BOOKMARK_THIS_TAB));
-}
-
 // This test validates that an extension override of the Chrome bookmark
 // shortcut does not supersede the same keybinding by web pages.
 IN_PROC_BROWSER_TEST_F(CommandsApiTest,
diff --git a/chrome/browser/extensions/policy_handlers.cc b/chrome/browser/extensions/policy_handlers.cc
index 5b482c0..1dfda7b 100644
--- a/chrome/browser/extensions/policy_handlers.cc
+++ b/chrome/browser/extensions/policy_handlers.cc
@@ -66,12 +66,10 @@
   return crx_file::id_util::IdIsValid(str);
 }
 
-void ExtensionListPolicyHandler::ApplyList(
-    std::unique_ptr<base::ListValue> filtered_list,
-    PrefValueMap* prefs) {
-  DCHECK(filtered_list);
-  prefs->SetValue(pref_path_,
-                  base::Value::FromUniquePtrValue(std::move(filtered_list)));
+void ExtensionListPolicyHandler::ApplyList(base::Value filtered_list,
+                                           PrefValueMap* prefs) {
+  DCHECK(filtered_list.is_list());
+  prefs->SetValue(pref_path_, std::move(filtered_list));
 }
 
 // ExtensionInstallListPolicyHandler implementation ----------------------------
diff --git a/chrome/browser/extensions/policy_handlers.h b/chrome/browser/extensions/policy_handlers.h
index db0e0e2..354311f5c 100644
--- a/chrome/browser/extensions/policy_handlers.h
+++ b/chrome/browser/extensions/policy_handlers.h
@@ -34,8 +34,7 @@
   bool CheckListEntry(const base::Value& value) override;
 
   // Sets |prefs| at pref_path() to |filtered_list|.
-  void ApplyList(std::unique_ptr<base::ListValue> filtered_list,
-                 PrefValueMap* prefs) override;
+  void ApplyList(base::Value filtered_list, PrefValueMap* prefs) override;
 
  private:
   const char* pref_path_;
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 8a6749d..827a8f5 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1876,6 +1876,11 @@
     "expiry_milestone": 76
   },
   {
+    "name": "enable-send-tab-to-self-omnibox-sending-animation",
+    "owners": [ "//components/send_tab_to_self/OWNERS" ],
+    "expiry_milestone": 84
+  },
+  {
     "name": "enable-send-tab-to-self-show-sending-ui",
     "owners": [ "//components/send_tab_to_self/OWNERS" ],
     "expiry_milestone": 77
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index ad84155..85bd05c 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1645,6 +1645,12 @@
     "Allows users to broadcast the tab they send to all of their devices "
     "instead of targetting only one device.";
 
+const char kSendTabToSelfOmniboxSendingAnimationName[] =
+    "Send tab to self omnibox sending animation";
+const char kSendTabToSelfOmniboxSendingAnimationDescription[] =
+    "If enabled, shows Sending... animation in omnibox instead of Desktop OS "
+    "notifications for contextual menu entry points.";
+
 const char kSendTabToSelfWhenSignedInName[] =
     "Send tab to self: enable use when signed-in regardless of sync state";
 const char kSendTabToSelfWhenSignedInDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 31fd57bc7..4a7e82c 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -957,6 +957,9 @@
 extern const char kSendTabToSelfBroadcastName[];
 extern const char kSendTabToSelfBroadcastDescription[];
 
+extern const char kSendTabToSelfOmniboxSendingAnimationName[];
+extern const char kSendTabToSelfOmniboxSendingAnimationDescription[];
+
 extern const char kSendTabToSelfWhenSignedInName[];
 extern const char kSendTabToSelfWhenSignedInDescription[];
 
diff --git a/chrome/browser/media/history/media_history_browsertest.cc b/chrome/browser/media/history/media_history_browsertest.cc
index 5d97074..c0e74b3 100644
--- a/chrome/browser/media/history/media_history_browsertest.cc
+++ b/chrome/browser/media/history/media_history_browsertest.cc
@@ -276,7 +276,7 @@
     ui_test_utils::NavigateToURL(browser, embedded_test_server()->base_url());
 
     // Wait until the session has finished saving.
-    content::RunAllTasksUntilIdle();
+    WaitForDB(GetMediaHistoryService(browser));
   }
 
   const GURL GetTestURL() const {
@@ -301,6 +301,12 @@
         browser->profile()->GetOffTheRecordProfile());
   }
 
+  static void WaitForDB(MediaHistoryKeyedService* service) {
+    base::RunLoop run_loop;
+    service->PostTaskToDBForTest(run_loop.QuitClosure());
+    run_loop.Run();
+  }
+
   Browser* CreateBrowserFromParam() {
     if (GetParam() == TestState::kIncognito) {
       return CreateIncognitoBrowser();
diff --git a/chrome/browser/media/webrtc/OWNERS b/chrome/browser/media/webrtc/OWNERS
index 818f518..104e9a6 100644
--- a/chrome/browser/media/webrtc/OWNERS
+++ b/chrome/browser/media/webrtc/OWNERS
@@ -9,6 +9,7 @@
 
 # For changes related to the tab media indicators.
 per-file media_stream_capture_indicator*=miu@chromium.org
+per-file media_stream_capture_indicator*=mfoltz@chromium.org
 
 # For permissions related code.
 per-file media_stream_device*=raymes@chromium.org
diff --git a/chrome/browser/metrics/metrics_service_user_demographics_browsertest.cc b/chrome/browser/metrics/metrics_service_user_demographics_browsertest.cc
index 96d02dd..fc0c5ac0 100644
--- a/chrome/browser/metrics/metrics_service_user_demographics_browsertest.cc
+++ b/chrome/browser/metrics/metrics_service_user_demographics_browsertest.cc
@@ -13,6 +13,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/optional.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
 #include "chrome/browser/metrics/chrome_metrics_services_manager_client.h"
@@ -115,8 +116,15 @@
 };
 
 // TODO(crbug/1016118): Add the remaining test cases.
+#if defined(OS_ANDROID)
+#define MAYBE_AddSyncedUserBirthYearAndGenderToProtoData \
+  DISABLED_AddSyncedUserBirthYearAndGenderToProtoData
+#else
+#define MAYBE_AddSyncedUserBirthYearAndGenderToProtoData \
+  AddSyncedUserBirthYearAndGenderToProtoData
+#endif
 IN_PROC_BROWSER_TEST_P(MetricsServiceUserDemographicsBrowserTest,
-                       AddSyncedUserBirthYearAndGenderToProtoData) {
+                       MAYBE_AddSyncedUserBirthYearAndGenderToProtoData) {
   test::DemographicsTestParams param = GetParam();
 
   base::HistogramTester histogram;
diff --git a/chrome/browser/metrics/perf/perf_output.cc b/chrome/browser/metrics/perf/perf_output.cc
index f0d46bce..4a57ac1 100644
--- a/chrome/browser/metrics/perf/perf_output.cc
+++ b/chrome/browser/metrics/perf/perf_output.cc
@@ -71,7 +71,7 @@
   // the callback argument. Callback can safely use |result| after |this| is
   // deleted.
   std::move(done_callback_).Run(std::move(result).value_or(std::string()));
-  // The callback may delete us, so it's hammertime: Can't touch |this|.
+  // NOTE: |this| may be deleted at this point!
 }
 
 void PerfOutputCall::OnGetPerfOutput(base::Optional<uint64_t> result) {
@@ -81,6 +81,8 @@
   if (!result.has_value() && perf_data_pipe_reader_.get()) {
     perf_data_pipe_reader_.reset();
     std::move(done_callback_).Run(std::string());
+    // NOTE: |this| may be deleted at this point!
+    return;
   }
 
   // DBus method GetPerfOutputFd returns a generated session ID back to the
diff --git a/chrome/browser/navigation_predictor/navigation_predictor.cc b/chrome/browser/navigation_predictor/navigation_predictor.cc
index 29a9971..c641d58 100644
--- a/chrome/browser/navigation_predictor/navigation_predictor.cc
+++ b/chrome/browser/navigation_predictor/navigation_predictor.cc
@@ -949,7 +949,11 @@
   for (const auto& nav_score : sorted_navigation_scores) {
     top_urls.push_back(nav_score->url);
   }
-  service->OnPredictionUpdated(web_contents(), document_url_, top_urls);
+  service->OnPredictionUpdated(
+      web_contents(), document_url_,
+      NavigationPredictorKeyedService::PredictionSource::
+          kAnchorElementsParsedFromWebPage,
+      top_urls);
 }
 
 void NavigationPredictor::MaybeTakeActionOnLoad(
diff --git a/chrome/browser/navigation_predictor/navigation_predictor_keyed_service.cc b/chrome/browser/navigation_predictor/navigation_predictor_keyed_service.cc
index 72a7e78..3f6dc6e7 100644
--- a/chrome/browser/navigation_predictor/navigation_predictor_keyed_service.cc
+++ b/chrome/browser/navigation_predictor/navigation_predictor_keyed_service.cc
@@ -14,11 +14,27 @@
 
 NavigationPredictorKeyedService::Prediction::Prediction(
     const content::WebContents* web_contents,
-    const GURL& source_document_url,
+    const base::Optional<GURL>& source_document_url,
+    const base::Optional<std::vector<std::string>>& external_app_packages_name,
+    PredictionSource prediction_source,
     const std::vector<GURL>& sorted_predicted_urls)
     : web_contents_(web_contents),
       source_document_url_(source_document_url),
-      sorted_predicted_urls_(sorted_predicted_urls) {}
+      external_app_packages_name_(external_app_packages_name),
+      prediction_source_(prediction_source),
+      sorted_predicted_urls_(sorted_predicted_urls) {
+  switch (prediction_source_) {
+    case PredictionSource::kAnchorElementsParsedFromWebPage:
+      DCHECK(!source_document_url->is_empty());
+      DCHECK(!external_app_packages_name);
+      break;
+    case PredictionSource::kExternalAndroidApp:
+      DCHECK(!web_contents_);
+      DCHECK(!source_document_url);
+      DCHECK(!external_app_packages_name->empty());
+      break;
+  }
+}
 
 NavigationPredictorKeyedService::Prediction::Prediction(
     const NavigationPredictorKeyedService::Prediction& other) = default;
@@ -29,17 +45,29 @@
 
 NavigationPredictorKeyedService::Prediction::~Prediction() = default;
 
-GURL NavigationPredictorKeyedService::Prediction::source_document_url() const {
+const base::Optional<GURL>&
+NavigationPredictorKeyedService::Prediction::source_document_url() const {
+  DCHECK_EQ(PredictionSource::kAnchorElementsParsedFromWebPage,
+            prediction_source_);
   return source_document_url_;
 }
 
-std::vector<GURL>
+const base::Optional<std::vector<std::string>>&
+NavigationPredictorKeyedService::Prediction::external_app_packages_name()
+    const {
+  DCHECK_EQ(PredictionSource::kExternalAndroidApp, prediction_source_);
+  return external_app_packages_name_;
+}
+
+const std::vector<GURL>&
 NavigationPredictorKeyedService::Prediction::sorted_predicted_urls() const {
   return sorted_predicted_urls_;
 }
 
 const content::WebContents*
 NavigationPredictorKeyedService::Prediction::web_contents() const {
+  DCHECK_EQ(PredictionSource::kAnchorElementsParsedFromWebPage,
+            prediction_source_);
   return web_contents_;
 }
 
@@ -62,20 +90,36 @@
 void NavigationPredictorKeyedService::OnPredictionUpdated(
     const content::WebContents* web_contents,
     const GURL& document_url,
+    PredictionSource prediction_source,
     const std::vector<GURL>& sorted_predicted_urls) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  last_prediction_ =
-      Prediction(web_contents, document_url, sorted_predicted_urls);
+
+  // Currently, this method is called only for anchor elements parsed from
+  // webpage.
+  DCHECK_EQ(PredictionSource::kAnchorElementsParsedFromWebPage,
+            prediction_source);
+
+  last_prediction_ = Prediction(web_contents, document_url,
+                                /*external_app_packages_name=*/base::nullopt,
+                                prediction_source, sorted_predicted_urls);
   for (auto& observer : observer_list_) {
     observer.OnPredictionUpdated(last_prediction_);
   }
 }
 
-#ifdef OS_ANDROID
 void NavigationPredictorKeyedService::OnPredictionUpdatedByExternalAndroidApp(
     const std::vector<std::string>& external_app_packages_name,
     const std::vector<GURL>& sorted_predicted_urls) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  if (external_app_packages_name.empty() || sorted_predicted_urls.empty()) {
+    return;
+  }
+  last_prediction_ =
+      Prediction(nullptr, base::nullopt, external_app_packages_name,
+                 PredictionSource::kExternalAndroidApp, sorted_predicted_urls);
+  for (auto& observer : observer_list_) {
+    observer.OnPredictionUpdated(last_prediction_);
+  }
 
   LOCAL_HISTOGRAM_COUNTS_100(
       "NavigationPredictor.ExternalAndroidApp.CountPredictedURLs",
@@ -84,7 +128,6 @@
   // TODO(https://crbug.com/1014210): Notify the predicted URLs to the
   // observers.
 }
-#endif
 
 void NavigationPredictorKeyedService::AddObserver(Observer* observer) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
diff --git a/chrome/browser/navigation_predictor/navigation_predictor_keyed_service.h b/chrome/browser/navigation_predictor/navigation_predictor_keyed_service.h
index 739a5bc..491e8fd 100644
--- a/chrome/browser/navigation_predictor/navigation_predictor_keyed_service.h
+++ b/chrome/browser/navigation_predictor/navigation_predictor_keyed_service.h
@@ -24,17 +24,35 @@
 // the next predicted navigation.
 class NavigationPredictorKeyedService : public KeyedService {
  public:
+  // Indicates how the set of next navigation URLs were predicted.
+  enum class PredictionSource {
+    // Next navigation URLs were predicted by navigation predictor by parsing
+    // the anchor element metrics on a webpage.
+    kAnchorElementsParsedFromWebPage = 0,
+
+    // Next navigation URLs were provided by an external Android app.
+    kExternalAndroidApp = 1
+  };
+
   // Stores the next set of URLs that the user is expected to navigate to.
   class Prediction {
    public:
     Prediction(const content::WebContents* web_contents,
-               const GURL& source_document_url,
+               const base::Optional<GURL>& source_document_url,
+               const base::Optional<std::vector<std::string>>&
+                   external_app_packages_name,
+               PredictionSource prediction_source,
                const std::vector<GURL>& sorted_predicted_urls);
     Prediction(const Prediction& other);
     Prediction& operator=(const Prediction& other);
     ~Prediction();
-    GURL source_document_url() const;
-    std::vector<GURL> sorted_predicted_urls() const;
+    const base::Optional<GURL>& source_document_url() const;
+    const base::Optional<std::vector<std::string>>& external_app_packages_name()
+        const;
+    PredictionSource prediction_source() const { return prediction_source_; }
+    const std::vector<GURL>& sorted_predicted_urls() const;
+
+    // Null if the prediction source is kExternalAndroidApp.
     const content::WebContents* web_contents() const;
 
    private:
@@ -44,7 +62,22 @@
     const content::WebContents* web_contents_;
 
     // Current URL of the document from where the navigtion may happen.
-    GURL source_document_url_;
+    base::Optional<GURL> source_document_url_;
+
+    // If the  |prediction_source_| is kExternalAndroidApp, then
+    // |external_app_packages_name_| is the set of likely external Android apps
+    // that generated the predictions. If the prediction source is
+    // kExternalAndroidApp, then the external Android app that generated the
+    // prediction is guaranteed to be one of the values in
+    // |external_app_packages_name_|.
+    base::Optional<std::vector<std::string>> external_app_packages_name_;
+
+    // |prediction_source_| indicates how the prediction was generated and
+    // affects how the prediction should be consumed. If the
+    // |prediction_source_| is kAnchorElementsParsedFromWebPage, then
+    // |source_document_url_| is the webpage from where the predictions were
+    // generated.
+    PredictionSource prediction_source_;
 
     // Ordered set of URLs that the user is expected to navigate to next. The
     // URLs are in the decreasing order of click probability.
@@ -57,6 +90,9 @@
   // document as well as the ordered list of URLs that the user may navigate to
   // next. OnPredictionUpdated() may be called multiple times for the same
   // source document URL.
+  //
+  // Observers must follow relevant privacy guidelines when consuming the
+  // notifications.
   class Observer {
    public:
     virtual void OnPredictionUpdated(
@@ -76,9 +112,9 @@
   // |document_url| may be invalid. Called by navigation predictor.
   void OnPredictionUpdated(const content::WebContents* web_contents,
                            const GURL& document_url,
+                           PredictionSource prediction_source,
                            const std::vector<GURL>& sorted_predicted_urls);
 
-#ifdef OS_ANDROID
   // Notifies |this| of the next set of URLs that the user is expected to
   // navigate to. The set of URLs are reported by an external Android app.
   // The reporting app is guaranteed to be one of the apps reported in
@@ -87,11 +123,11 @@
   void OnPredictionUpdatedByExternalAndroidApp(
       const std::vector<std::string>& external_app_packages_name,
       const std::vector<GURL>& sorted_predicted_urls);
-#endif
 
   // Adds |observer| as the observer for next predicted navigation. When
   // |observer| is added via AddObserver, it's immediately notified of the last
-  // known prediction.
+  // known prediction. Observers must follow relevant privacy guidelines when
+  // consuming the notifications.
   void AddObserver(Observer* observer);
 
   // Removes |observer| as the observer for next predicted navigation.
diff --git a/chrome/browser/optimization_guide/optimization_guide_hints_manager.cc b/chrome/browser/optimization_guide/optimization_guide_hints_manager.cc
index 64f5cdf5..a5477003 100644
--- a/chrome/browser/optimization_guide/optimization_guide_hints_manager.cc
+++ b/chrome/browser/optimization_guide/optimization_guide_hints_manager.cc
@@ -734,10 +734,19 @@
   if (!prediction.has_value())
     return;
 
-  const GURL& source_document_url = prediction->source_document_url();
+  if (prediction->prediction_source() !=
+      NavigationPredictorKeyedService::PredictionSource::
+          kAnchorElementsParsedFromWebPage) {
+    return;
+  }
+
+  const base::Optional<GURL>& source_document_url =
+      prediction->source_document_url();
+  if (!source_document_url || source_document_url->is_empty())
+    return;
 
   // We only extract next predicted navigations from Google URLs.
-  if (!IsGoogleURL(source_document_url))
+  if (!IsGoogleURL(source_document_url.value()))
     return;
 
   // Extract the target hosts and URLs. Use a flat set to remove duplicates.
diff --git a/chrome/browser/optimization_guide/optimization_guide_hints_manager.h b/chrome/browser/optimization_guide/optimization_guide_hints_manager.h
index 0d525282..1d5160c 100644
--- a/chrome/browser/optimization_guide/optimization_guide_hints_manager.h
+++ b/chrome/browser/optimization_guide/optimization_guide_hints_manager.h
@@ -177,6 +177,9 @@
   FRIEND_TEST_ALL_PREFIXES(
       OptimizationGuideHintsManagerFetchingTest,
       HintsFetched_AtSRP_ECT_SLOW_2G_NonHTTPOrHTTPSHostsRemoved);
+  FRIEND_TEST_ALL_PREFIXES(
+      OptimizationGuideHintsManagerFetchingTest,
+      HintsFetched_ExternalAndroidApp_ECT_SLOW_2G_NonHTTPOrHTTPSHostsRemoved);
 
   // Processes the hints component.
   //
diff --git a/chrome/browser/optimization_guide/optimization_guide_hints_manager_unittest.cc b/chrome/browser/optimization_guide/optimization_guide_hints_manager_unittest.cc
index c89b3cca..21208de 100644
--- a/chrome/browser/optimization_guide/optimization_guide_hints_manager_unittest.cc
+++ b/chrome/browser/optimization_guide/optimization_guide_hints_manager_unittest.cc
@@ -2068,7 +2068,11 @@
   std::vector<GURL> sorted_predicted_urls;
   sorted_predicted_urls.push_back(GURL("https://foo.com/"));
   NavigationPredictorKeyedService::Prediction prediction(
-      nullptr, GURL("https://www.google.com/"), sorted_predicted_urls);
+      nullptr, GURL("https://www.google.com/"),
+      /*external_app_packages_name=*/{},
+      NavigationPredictorKeyedService::PredictionSource::
+          kAnchorElementsParsedFromWebPage,
+      sorted_predicted_urls);
 
   hints_manager()->OnPredictionUpdated(prediction);
   histogram_tester.ExpectUniqueSample(
@@ -2090,7 +2094,11 @@
   std::vector<GURL> sorted_predicted_urls;
   sorted_predicted_urls.push_back(GURL("https://foo.com/"));
   NavigationPredictorKeyedService::Prediction prediction(
-      nullptr, GURL("https://www.google.com/"), sorted_predicted_urls);
+      nullptr, GURL("https://www.google.com/"),
+      /*external_app_packages_name=*/{},
+      NavigationPredictorKeyedService::PredictionSource::
+          kAnchorElementsParsedFromWebPage,
+      sorted_predicted_urls);
 
   hints_manager()->OnPredictionUpdated(prediction);
   histogram_tester.ExpectTotalCount(
@@ -2118,7 +2126,11 @@
   sorted_predicted_urls.push_back(GURL("https://bar.com/"));
 
   NavigationPredictorKeyedService::Prediction prediction(
-      nullptr, GURL("https://www.google.com/"), sorted_predicted_urls);
+      nullptr, GURL("https://www.google.com/"),
+      /*external_app_packages_name=*/{},
+      NavigationPredictorKeyedService::PredictionSource::
+          kAnchorElementsParsedFromWebPage,
+      sorted_predicted_urls);
 
   {
     base::HistogramTester histogram_tester;
@@ -2161,7 +2173,11 @@
   sorted_predicted_urls.push_back(GURL("http://httppage.com/"));
 
   NavigationPredictorKeyedService::Prediction prediction(
-      nullptr, GURL("https://www.google.com/"), sorted_predicted_urls);
+      nullptr, GURL("https://www.google.com/"),
+      /*external_app_packages_name=*/{},
+      NavigationPredictorKeyedService::PredictionSource::
+          kAnchorElementsParsedFromWebPage,
+      sorted_predicted_urls);
 
   hints_manager()->OnPredictionUpdated(prediction);
   // Ensure that we include both web hosts in the request. These would be
@@ -2173,6 +2189,57 @@
       "OptimizationGuide.HintsFetcher.GetHintsRequest.UrlCount", 2, 1);
 }
 
+// Verify that optimization hints are not fetched if the prediction for the next
+// likely navigations are provided by external Android app.
+TEST_F(OptimizationGuideHintsManagerFetchingTest,
+       HintsFetched_ExternalAndroidApp_ECT_SLOW_2G_NonHTTPOrHTTPSHostsRemoved) {
+  base::CommandLine::ForCurrentProcess()->AppendSwitch(
+      optimization_guide::switches::kDisableCheckingUserPermissionsForTesting);
+  hints_manager()->RegisterOptimizationTypes(
+      {optimization_guide::proto::DEFER_ALL_SCRIPT});
+  InitializeWithDefaultConfig("1.0.0.0");
+
+  // Set ECT estimate so fetch is activated.
+  hints_manager()->OnEffectiveConnectionTypeChanged(
+      net::EffectiveConnectionType::EFFECTIVE_CONNECTION_TYPE_SLOW_2G);
+  base::HistogramTester histogram_tester;
+  std::vector<GURL> sorted_predicted_urls;
+  sorted_predicted_urls.push_back(GURL("https://foo.com/page1.html"));
+  sorted_predicted_urls.push_back(GURL("file://non-web-bar.com/"));
+  sorted_predicted_urls.push_back(GURL("http://httppage.com/"));
+
+  std::vector<std::string> external_app_packages_name;
+  external_app_packages_name.push_back("com.example.foo");
+
+  NavigationPredictorKeyedService::Prediction prediction_external_android_app(
+      nullptr, base::nullopt, external_app_packages_name,
+      NavigationPredictorKeyedService::PredictionSource::kExternalAndroidApp,
+      sorted_predicted_urls);
+  hints_manager()->OnPredictionUpdated(prediction_external_android_app);
+  histogram_tester.ExpectTotalCount(
+      "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount", 0);
+  // Ensure that we only include 2 URLs in the request.
+  histogram_tester.ExpectTotalCount(
+      "OptimizationGuide.HintsFetcher.GetHintsRequest.UrlCount", 0);
+
+  // Now fetch again with a prediction from anchor elements. This time
+  // optimization hints should be requested.
+  NavigationPredictorKeyedService::Prediction prediction_anchor_elements(
+      nullptr, GURL("https://www.google.com/"),
+      /*external_app_packages_name=*/{},
+      NavigationPredictorKeyedService::PredictionSource::
+          kAnchorElementsParsedFromWebPage,
+      sorted_predicted_urls);
+  hints_manager()->OnPredictionUpdated(prediction_anchor_elements);
+  // Ensure that we include both web hosts in the request. These would be
+  // foo.com and httppage.com.
+  histogram_tester.ExpectUniqueSample(
+      "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount", 2, 1);
+  // Ensure that we only include 2 URLs in the request.
+  histogram_tester.ExpectUniqueSample(
+      "OptimizationGuide.HintsFetcher.GetHintsRequest.UrlCount", 2, 1);
+}
+
 TEST_F(OptimizationGuideHintsManagerFetchingTest, HintsFetched_AtSRP_ECT_4G) {
   base::CommandLine::ForCurrentProcess()->AppendSwitch(
       optimization_guide::switches::kDisableCheckingUserPermissionsForTesting);
@@ -2187,7 +2254,11 @@
   std::vector<GURL> sorted_predicted_urls;
   sorted_predicted_urls.push_back(GURL("https://foo.com/"));
   NavigationPredictorKeyedService::Prediction prediction(
-      nullptr, GURL("https://www.google.com/"), sorted_predicted_urls);
+      nullptr, GURL("https://www.google.com/"),
+      /*external_app_packages_name=*/{},
+      NavigationPredictorKeyedService::PredictionSource::
+          kAnchorElementsParsedFromWebPage,
+      sorted_predicted_urls);
 
   hints_manager()->OnPredictionUpdated(prediction);
   histogram_tester.ExpectTotalCount(
@@ -2211,7 +2282,11 @@
   std::vector<GURL> sorted_predicted_urls;
   sorted_predicted_urls.push_back(GURL("https://foo.com/"));
   NavigationPredictorKeyedService::Prediction prediction(
-      nullptr, GURL("https://www.not-google.com/"), sorted_predicted_urls);
+      nullptr, GURL("https://www.not-google.com/"),
+      /*external_app_packages_name=*/{},
+      NavigationPredictorKeyedService::PredictionSource::
+          kAnchorElementsParsedFromWebPage,
+      sorted_predicted_urls);
 
   hints_manager()->OnPredictionUpdated(prediction);
   histogram_tester.ExpectTotalCount(
diff --git a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_unittest.cc
index 86c296d6..005140e 100644
--- a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_unittest.cc
@@ -146,7 +146,8 @@
         resources,
         page_load_metrics::mojom::FrameRenderDataUpdatePtr(base::in_place),
         page_load_metrics::mojom::CpuTimingPtr(base::in_place),
-        page_load_metrics::mojom::DeferredResourceCountsPtr(base::in_place));
+        page_load_metrics::mojom::DeferredResourceCountsPtr(base::in_place),
+        page_load_metrics::mojom::InputTimingPtr(base::in_place));
   }
 
   DISALLOW_COPY_AND_ASSIGN(ResourceLoadingCancellingThrottle);
diff --git a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.cc
index 5c73a40..8211158c 100644
--- a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.cc
@@ -207,6 +207,7 @@
   if (!was_hidden_) {
     RecordPageLoadMetrics(base::TimeTicks::Now());
     RecordTimingMetrics(timing);
+    RecordInputTimingMetrics();
   }
   ReportLayoutStability();
   return STOP_OBSERVING;
@@ -217,6 +218,7 @@
   if (!was_hidden_) {
     RecordPageLoadMetrics(base::TimeTicks() /* no app_background_time */);
     RecordTimingMetrics(timing);
+    RecordInputTimingMetrics();
     was_hidden_ = true;
   }
   return CONTINUE_OBSERVING;
@@ -245,6 +247,7 @@
   if (!was_hidden_) {
     RecordPageLoadMetrics(base::TimeTicks() /* no app_background_time */);
     RecordTimingMetrics(timing);
+    RecordInputTimingMetrics();
   }
   ReportLayoutStability();
 }
@@ -388,22 +391,6 @@
     builder.SetInteractiveTiming_LongestInputTimestamp4(
         longest_input_timestamp.InMilliseconds());
   }
-  if (timing.interactive_timing->total_input_delay) {
-    base::TimeDelta total_input_delay =
-        timing.interactive_timing->total_input_delay.value();
-    builder.SetInteractiveTiming_TotalInputDelay(
-        total_input_delay.InMilliseconds());
-  }
-  if (timing.interactive_timing->total_adjusted_input_delay) {
-    base::TimeDelta total_adjusted_input_delay =
-        timing.interactive_timing->total_adjusted_input_delay.value();
-    builder.SetInteractiveTiming_TotalAdjustedInputDelay(
-        total_adjusted_input_delay.InMilliseconds());
-  }
-  if (timing.interactive_timing->num_input_events) {
-    int num_input_events = timing.interactive_timing->num_input_events;
-    builder.SetInteractiveTiming_NumInputEvents(num_input_events);
-  }
   builder.SetCpuTime(total_foreground_cpu_time_.InMilliseconds());
 
   // Use a bucket spacing factor of 1.3 for bytes.
@@ -607,6 +594,22 @@
           GetDelegate().GetMainFrameRenderData().layout_shift_score));
 }
 
+void UkmPageLoadMetricsObserver::RecordInputTimingMetrics() {
+  if (GetDelegate().GetPageInputTiming().num_input_events == 0) {
+    return;
+  }
+  ukm::builders::PageLoad(GetDelegate().GetSourceId())
+      .SetInteractiveTiming_NumInputEvents(
+          GetDelegate().GetPageInputTiming().num_input_events)
+      .SetInteractiveTiming_TotalInputDelay(
+          GetDelegate().GetPageInputTiming().total_input_delay.InMilliseconds())
+      .SetInteractiveTiming_TotalAdjustedInputDelay(
+          GetDelegate()
+              .GetPageInputTiming()
+              .total_adjusted_input_delay.InMilliseconds())
+      .Record(ukm::UkmRecorder::Get());
+}
+
 base::Optional<int64_t>
 UkmPageLoadMetricsObserver::GetRoundedSiteEngagementScore() const {
   if (!browser_context_)
diff --git a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.h b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.h
index 3f34854..116560f 100644
--- a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.h
+++ b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.h
@@ -111,7 +111,9 @@
 
   void ReportLayoutStability();
 
-  // Captures the site engagement score for the commited URL and
+  void RecordInputTimingMetrics();
+
+  // Captures the site engagement score for the committed URL and
   // returns the score rounded to the nearest 10.
   base::Optional<int64_t> GetRoundedSiteEngagementScore() const;
 
diff --git a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer_unittest.cc
index b8c18c3..0f7e13e 100644
--- a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer_unittest.cc
@@ -944,21 +944,16 @@
   }
 }
 
-TEST_F(UkmPageLoadMetricsObserverTest,
-       TotalInputDelayAndTotalAdjustedInputDelay) {
-  page_load_metrics::mojom::PageLoadTiming timing;
-  page_load_metrics::InitPageLoadTimingForTest(&timing);
-  timing.navigation_start = base::Time::FromDoubleT(1);
-  timing.interactive_timing->total_input_delay =
-      base::TimeDelta::FromMilliseconds(500);
-  timing.interactive_timing->total_adjusted_input_delay =
-      base::TimeDelta::FromMilliseconds(250);
-  PopulateRequiredTimingFields(&timing);
-
+TEST_F(UkmPageLoadMetricsObserverTest, InputTiming) {
   NavigateAndCommit(GURL(kTestUrl1));
-  tester()->SimulateTimingUpdate(timing);
 
-  // Simulate closing the tab.
+  page_load_metrics::mojom::InputTiming input_timing;
+  input_timing.num_input_events = 2;
+  input_timing.total_input_delay = base::TimeDelta::FromMilliseconds(100);
+  input_timing.total_adjusted_input_delay =
+      base::TimeDelta::FromMilliseconds(10);
+  tester()->SimulateInputTimingUpdate(input_timing);
+
   DeleteContents();
 
   std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries =
@@ -970,36 +965,12 @@
     tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(),
                                                           GURL(kTestUrl1));
     tester()->test_ukm_recorder().ExpectEntryMetric(
-        kv.second.get(), PageLoad::kInteractiveTiming_TotalInputDelayName, 500);
+        kv.second.get(), PageLoad::kInteractiveTiming_NumInputEventsName, 2);
+    tester()->test_ukm_recorder().ExpectEntryMetric(
+        kv.second.get(), PageLoad::kInteractiveTiming_TotalInputDelayName, 100);
     tester()->test_ukm_recorder().ExpectEntryMetric(
         kv.second.get(),
-        PageLoad::kInteractiveTiming_TotalAdjustedInputDelayName, 250);
-  }
-}
-
-TEST_F(UkmPageLoadMetricsObserverTest, NumInputEvents) {
-  page_load_metrics::mojom::PageLoadTiming timing;
-  page_load_metrics::InitPageLoadTimingForTest(&timing);
-  timing.navigation_start = base::Time::FromDoubleT(1);
-  timing.interactive_timing->num_input_events = 5;
-  PopulateRequiredTimingFields(&timing);
-
-  NavigateAndCommit(GURL(kTestUrl1));
-  tester()->SimulateTimingUpdate(timing);
-
-  // Simulate closing the tab.
-  DeleteContents();
-
-  std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries =
-      tester()->test_ukm_recorder().GetMergedEntriesByName(
-          PageLoad::kEntryName);
-  EXPECT_EQ(1ul, merged_entries.size());
-
-  for (const auto& kv : merged_entries) {
-    tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(),
-                                                          GURL(kTestUrl1));
-    tester()->test_ukm_recorder().ExpectEntryMetric(
-        kv.second.get(), PageLoad::kInteractiveTiming_NumInputEventsName, 5);
+        PageLoad::kInteractiveTiming_TotalAdjustedInputDelayName, 10);
   }
 }
 
diff --git a/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc b/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
index b8f8cd9..ee0eca3 100644
--- a/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
+++ b/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
@@ -2492,9 +2492,8 @@
 
 // Creates a single frame within the main frame and verifies the intersection
 // with the main frame.
-// TODO(crbug/1062006): Re-enable once chromeos-thinlto build is fixed.
 IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
-                       DISABLED_MainFrameDocumentIntersectionSingleFrame) {
+                       MainFrameDocumentIntersectionSingleFrame) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
   auto waiter = CreatePageLoadMetricsTestWaiter();
@@ -2517,9 +2516,8 @@
 
 // Creates a set of nested frames within the main frame and verifies
 // their intersections with the main frame.
-// TODO(crbug/1062006): Re-enable once chromeos-thinlto build is fixed.
 IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
-                       DISABLED_MainFrameDocumentIntersectionSameOrigin) {
+                       MainFrameDocumentIntersectionSameOrigin) {
   EXPECT_TRUE(embedded_test_server()->Start());
 
   auto waiter = CreatePageLoadMetricsTestWaiter();
@@ -2559,9 +2557,8 @@
 
 // Creates a set of nested frames, with a cross origin subframe, within the
 // main frame and verifies their intersections with the main frame.
-// TODO(crbug/1062006): Re-enable once chromeos-thinlto build is fixed.
 IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
-                       DISABLED_MainFrameDocumentIntersectionCrossOrigin) {
+                       MainFrameDocumentIntersectionCrossOrigin) {
   EXPECT_TRUE(embedded_test_server()->Start());
   auto waiter = CreatePageLoadMetricsTestWaiter();
   ui_test_utils::NavigateToURL(
@@ -2602,10 +2599,8 @@
 // Creates a set of nested frames, with a cross origin subframe that is out of
 // view within the main frame and verifies their intersections with the main
 // frame.
-// TODO(crbug/1062006): Re-enable once chromeos-thinlto build is fixed.
-IN_PROC_BROWSER_TEST_F(
-    PageLoadMetricsBrowserTest,
-    DISABLED_MainFrameDocumentIntersectionCrossOriginOutOfView) {
+IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
+                       MainFrameDocumentIntersectionCrossOriginOutOfView) {
   EXPECT_TRUE(embedded_test_server()->Start());
   auto waiter = CreatePageLoadMetricsTestWaiter();
   ui_test_utils::NavigateToURL(
@@ -2644,11 +2639,8 @@
 // view within the main frame and verifies their intersections with the main
 // frame. The out of view frame is then scrolled back into view and the
 // intersection is verified.
-//
-// TODO(crbug/1062006): Re-enable once chromeos-thinlto build is fixed.
-IN_PROC_BROWSER_TEST_F(
-    PageLoadMetricsBrowserTest,
-    DISABLED_MainFrameDocumentIntersectionCrossOriginScrolled) {
+IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
+                       MainFrameDocumentIntersectionCrossOriginScrolled) {
   EXPECT_TRUE(embedded_test_server()->Start());
   auto waiter = CreatePageLoadMetricsTestWaiter();
   ui_test_utils::NavigateToURL(
diff --git a/chrome/browser/payments/android/journey_logger_android.cc b/chrome/browser/payments/android/journey_logger_android.cc
index 4a0336c..67bbfaa 100644
--- a/chrome/browser/payments/android/journey_logger_android.cc
+++ b/chrome/browser/payments/android/journey_logger_android.cc
@@ -157,6 +157,13 @@
   journey_logger_.SetTriggerTime();
 }
 
+void JourneyLoggerAndroid::SetPaymentAppUkmSourceId(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& jcaller,
+    ukm::SourceId source_id) {
+  journey_logger_.SetPaymentAppUkmSourceId(source_id);
+}
+
 static jlong JNI_JourneyLogger_InitJourneyLoggerAndroid(
     JNIEnv* env,
     const JavaParamRef<jobject>& jcaller,
diff --git a/chrome/browser/payments/android/journey_logger_android.h b/chrome/browser/payments/android/journey_logger_android.h
index bffb292..d6e9fa5 100644
--- a/chrome/browser/payments/android/journey_logger_android.h
+++ b/chrome/browser/payments/android/journey_logger_android.h
@@ -80,6 +80,10 @@
       jboolean jcompleted);
   void SetTriggerTime(JNIEnv* env,
                       const base::android::JavaParamRef<jobject>& jcaller);
+  void SetPaymentAppUkmSourceId(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& jcaller,
+      ukm::SourceId source_id);
 
  private:
   JourneyLogger journey_logger_;
diff --git a/chrome/browser/payments/android/service_worker_payment_app_bridge.cc b/chrome/browser/payments/android/service_worker_payment_app_bridge.cc
index 1f19f7c3..cfaae26b 100644
--- a/chrome/browser/payments/android/service_worker_payment_app_bridge.cc
+++ b/chrome/browser/payments/android/service_worker_payment_app_bridge.cc
@@ -25,6 +25,7 @@
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/payment_app_provider.h"
 #include "content/public/browser/web_contents.h"
+#include "services/metrics/public/cpp/ukm_recorder.h"
 #include "third_party/blink/public/mojom/payments/payment_app.mojom.h"
 #include "ui/gfx/android/java_bitmap.h"
 #include "url/gurl.h"
@@ -672,3 +673,16 @@
       web_contents->GetBrowserContext(),
       static_cast<payments::mojom::PaymentEventResponseType>(reason));
 }
+
+static jlong
+JNI_ServiceWorkerPaymentAppBridge_GetSourceIdForPaymentAppFromScope(
+    JNIEnv* env,
+    const JavaParamRef<jstring>& jscope) {
+  // At this point we know that the payment handler window is open for the
+  // payment app associated with this scope. Since this getter is called inside
+  // PaymentApp::getUkmSourceId() function which in turn gets called for the
+  // invoked app inside PaymentRequestImpl::openPaymentHandlerWindowInternal.
+  return content::PaymentAppProvider::GetInstance()
+      ->GetSourceIdForPaymentAppFromScope(
+          GURL(ConvertJavaStringToUTF8(env, jscope)).GetOrigin());
+}
diff --git a/chrome/browser/payments/journey_logger_browsertest.cc b/chrome/browser/payments/journey_logger_browsertest.cc
index f238d78d..2869b31 100644
--- a/chrome/browser/payments/journey_logger_browsertest.cc
+++ b/chrome/browser/payments/journey_logger_browsertest.cc
@@ -24,6 +24,12 @@
 
   void SetUpOnMainThread() override {
     PaymentRequestPlatformBrowserTestBase::SetUpOnMainThread();
+    main_frame_url_ = https_server()->GetURL("/journey_logger_test.html");
+    ASSERT_TRUE(
+        content::NavigateToURL(GetActiveWebContents(), main_frame_url_));
+  }
+
+  void SetUpForGpay() {
     gpay_server_.ServeFilesFromSourceDirectory(
         "components/test/data/payments/google.com/");
     ASSERT_TRUE(gpay_server_.Start());
@@ -33,12 +39,11 @@
     SetDownloaderAndIgnorePortInOriginComparisonForTesting(
         {{method_name, &gpay_server_}});
 
-    main_frame_url_ = https_server()->GetURL("/journey_logger_test.html");
-    ASSERT_TRUE(
-        content::NavigateToURL(GetActiveWebContents(), main_frame_url_));
+    gpay_scope_url_ = gpay_server_.GetURL("google.com", "/");
   }
 
   const GURL& main_frame_url() const { return main_frame_url_; }
+  const GURL& gpay_scope_url() const { return gpay_scope_url_; }
 
   ukm::TestAutoSetUkmRecorder* test_ukm_recorder() {
     return test_ukm_recorder_.get();
@@ -47,6 +52,7 @@
  private:
   net::EmbeddedTestServer gpay_server_;
   GURL main_frame_url_;
+  GURL gpay_scope_url_;
   std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_;
 
   DISALLOW_COPY_AND_ASSIGN(JourneyLoggerTest);
@@ -91,6 +97,7 @@
 
 IN_PROC_BROWSER_TEST_F(JourneyLoggerTest, GooglePaymentApp) {
   base::HistogramTester histogram_tester;
+  SetUpForGpay();
 
   EXPECT_EQ("{\"apiVersion\":1}",
             content::EvalJs(GetActiveWebContents(), "testGPay()"));
@@ -106,6 +113,7 @@
 
 // Make sure the UKM was logged correctly.
 IN_PROC_BROWSER_TEST_F(JourneyLoggerTest, UKMTransactionAmountRecorded) {
+  SetUpForGpay();
   EXPECT_EQ("{\"apiVersion\":1}",
             content::EvalJs(GetActiveWebContents(), "testGPay()"));
 
@@ -128,4 +136,79 @@
   }
 }
 
+IN_PROC_BROWSER_TEST_F(JourneyLoggerTest,
+                       UKMCheckoutEventsRecordedForAppOrigin) {
+  GURL merchant_url = https_server()->GetURL("/payment_handler.html");
+  ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), merchant_url));
+  EXPECT_EQ("success", content::EvalJs(GetActiveWebContents(), "install()"));
+
+  ResetEventWaiterForSingleEvent(TestEvent::kPaymentCompleted);
+  EXPECT_EQ("success", content::EvalJs(GetActiveWebContents(), "launch()"));
+  WaitForObservedEvent();
+
+  // UKM for merchant's website origin.
+  auto entries = test_ukm_recorder()->GetEntriesByName(
+      ukm::builders::PaymentRequest_CheckoutEvents::kEntryName);
+  size_t num_entries = entries.size();
+  EXPECT_EQ(1u, num_entries);
+  test_ukm_recorder()->ExpectEntrySourceHasUrl(entries[0], merchant_url);
+
+  // UKM for payment app's scope.
+  entries = test_ukm_recorder()->GetEntriesByName(
+      ukm::builders::PaymentApp_CheckoutEvents::kEntryName);
+  num_entries = entries.size();
+  EXPECT_EQ(1u, num_entries);
+  test_ukm_recorder()->ExpectEntrySourceHasUrl(entries[0],
+                                               https_server()->GetURL("/"));
+}
+
+IN_PROC_BROWSER_TEST_F(
+    JourneyLoggerTest,
+    UKMCheckoutEventsNotRecordedForAppOriginWhenNoWindowShown) {
+  SetUpForGpay();
+
+  EXPECT_EQ("{\"apiVersion\":1}",
+            content::EvalJs(GetActiveWebContents(), "testGPay()"));
+
+  // UKM for merchant's website origin.
+  auto entries = test_ukm_recorder()->GetEntriesByName(
+      ukm::builders::PaymentRequest_CheckoutEvents::kEntryName);
+  size_t num_entries = entries.size();
+  EXPECT_EQ(1u, num_entries);
+  test_ukm_recorder()->ExpectEntrySourceHasUrl(entries[0], main_frame_url());
+
+  // No UKM for payment app's scope since the app's origin is not shown inside
+  // the PH modal window.
+  entries = test_ukm_recorder()->GetEntriesByName(
+      ukm::builders::PaymentApp_CheckoutEvents::kEntryName);
+  num_entries = entries.size();
+  EXPECT_EQ(0u, num_entries);
+}
+
+IN_PROC_BROWSER_TEST_F(
+    JourneyLoggerTest,
+    UKMCheckoutEventsNotRecordedForAppOriginWhenNoAppInvoked) {
+  CreateAndAddCreditCardForProfile(CreateAndAddAutofillProfile());
+
+  ResetEventWaiterForSingleEvent(TestEvent::kShowAppsReady);
+  EXPECT_TRUE(content::ExecJs(GetActiveWebContents(), "testBasicCard()"));
+  WaitForObservedEvent();
+
+  EXPECT_EQ(true, content::EvalJs(GetActiveWebContents(), "abort()"));
+
+  // UKM for merchant's website origin.
+  auto entries = test_ukm_recorder()->GetEntriesByName(
+      ukm::builders::PaymentRequest_CheckoutEvents::kEntryName);
+  size_t num_entries = entries.size();
+  EXPECT_EQ(1u, num_entries);
+  test_ukm_recorder()->ExpectEntrySourceHasUrl(entries[0], main_frame_url());
+
+  // No UKM for payment app's scope since the request got aborted before
+  // invoking a payment app.
+  entries = test_ukm_recorder()->GetEntriesByName(
+      ukm::builders::PaymentApp_CheckoutEvents::kEntryName);
+  num_entries = entries.size();
+  EXPECT_EQ(0u, num_entries);
+}
+
 }  // namespace payments
diff --git a/chrome/browser/policy/printing_restrictions_policy_handler.cc b/chrome/browser/policy/printing_restrictions_policy_handler.cc
index 7c169fe..439120b 100644
--- a/chrome/browser/policy/printing_restrictions_policy_handler.cc
+++ b/chrome/browser/policy/printing_restrictions_policy_handler.cc
@@ -156,12 +156,10 @@
   return width && height && width->is_int() && height->is_int();
 }
 
-void PrintingAllowedPageSizesPolicyHandler::ApplyList(
-    std::unique_ptr<base::ListValue> filtered_list,
-    PrefValueMap* prefs) {
-  DCHECK(filtered_list);
-  prefs->SetValue(prefs::kPrintingAllowedPageSizes,
-                  base::Value::FromUniquePtrValue(std::move(filtered_list)));
+void PrintingAllowedPageSizesPolicyHandler::ApplyList(base::Value filtered_list,
+                                                      PrefValueMap* prefs) {
+  DCHECK(filtered_list.is_list());
+  prefs->SetValue(prefs::kPrintingAllowedPageSizes, std::move(filtered_list));
 }
 
 PrintingSizeDefaultPolicyHandler::PrintingSizeDefaultPolicyHandler()
diff --git a/chrome/browser/policy/printing_restrictions_policy_handler.h b/chrome/browser/policy/printing_restrictions_policy_handler.h
index 8fd7029b..bb388bd1 100644
--- a/chrome/browser/policy/printing_restrictions_policy_handler.h
+++ b/chrome/browser/policy/printing_restrictions_policy_handler.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "base/containers/flat_map.h"
+#include "base/values.h"
 #include "components/policy/core/browser/configuration_policy_handler.h"
 #include "printing/backend/printing_restrictions.h"
 
@@ -95,8 +96,7 @@
 
   // ListPolicyHandler implementation:
   bool CheckListEntry(const base::Value& value) override;
-  void ApplyList(std::unique_ptr<base::ListValue> filtered_list,
-                 PrefValueMap* prefs) override;
+  void ApplyList(base::Value filtered_list, PrefValueMap* prefs) override;
 };
 
 class PrintingSizeDefaultPolicyHandler : public TypeCheckingPolicyHandler {
diff --git a/chrome/browser/prerender/isolated/isolated_prerender_browsertest.cc b/chrome/browser/prerender/isolated/isolated_prerender_browsertest.cc
index 667f869d..94765c6 100644
--- a/chrome/browser/prerender/isolated/isolated_prerender_browsertest.cc
+++ b/chrome/browser/prerender/isolated/isolated_prerender_browsertest.cc
@@ -185,6 +185,8 @@
     NavigationPredictorKeyedServiceFactory::GetForProfile(browser()->profile())
         ->OnPredictionUpdated(
             browser()->tab_strip_model()->GetActiveWebContents(), doc_url,
+            NavigationPredictorKeyedService::PredictionSource::
+                kAnchorElementsParsedFromWebPage,
             predicted_urls);
   }
 
diff --git a/chrome/browser/prerender/isolated/isolated_prerender_tab_helper.cc b/chrome/browser/prerender/isolated/isolated_prerender_tab_helper.cc
index 5e2f23a..8ec407a 100644
--- a/chrome/browser/prerender/isolated/isolated_prerender_tab_helper.cc
+++ b/chrome/browser/prerender/isolated/isolated_prerender_tab_helper.cc
@@ -300,13 +300,24 @@
     return;
   }
 
+  if (prediction->prediction_source() !=
+      NavigationPredictorKeyedService::PredictionSource::
+          kAnchorElementsParsedFromWebPage) {
+    return;
+  }
+
   if (prediction.value().web_contents() != web_contents()) {
     // We only care about predictions in this tab.
     return;
   }
 
-  if (!google_util::IsGoogleSearchUrl(
-          prediction.value().source_document_url())) {
+  const base::Optional<GURL>& source_document_url =
+      prediction->source_document_url();
+
+  if (!source_document_url || source_document_url->is_empty())
+    return;
+
+  if (!google_util::IsGoogleSearchUrl(source_document_url.value())) {
     return;
   }
 
diff --git a/chrome/browser/prerender/isolated/isolated_prerender_tab_helper_unittest.cc b/chrome/browser/prerender/isolated/isolated_prerender_tab_helper_unittest.cc
index 74631aad..6c64996 100644
--- a/chrome/browser/prerender/isolated/isolated_prerender_tab_helper_unittest.cc
+++ b/chrome/browser/prerender/isolated/isolated_prerender_tab_helper_unittest.cc
@@ -114,7 +114,19 @@
                                 const GURL& doc_url,
                                 const std::vector<GURL>& predicted_urls) {
     NavigationPredictorKeyedServiceFactory::GetForProfile(profile())
-        ->OnPredictionUpdated(web_contents, doc_url, predicted_urls);
+        ->OnPredictionUpdated(
+            web_contents, doc_url,
+            NavigationPredictorKeyedService::PredictionSource::
+                kAnchorElementsParsedFromWebPage,
+            predicted_urls);
+    task_environment()->RunUntilIdle();
+  }
+
+  void MakeExternalAndroidAppNavigationPrediction(
+      const std::vector<GURL>& predicted_urls) {
+    NavigationPredictorKeyedServiceFactory::GetForProfile(profile())
+        ->OnPredictionUpdatedByExternalAndroidApp({"com.example.foo"},
+                                                  predicted_urls);
     task_environment()->RunUntilIdle();
   }
 
@@ -421,6 +433,21 @@
   EXPECT_EQ(RequestCount(), 0);
 }
 
+// Verify that isolated prerender is not triggered if the predictions for next
+// likely navigations are provided by external Android app.
+TEST_F(IsolatedPrerenderTabHelperTest, ExternalAndroidApp) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      features::kPrefetchSRPNavigationPredictions_HTMLOnly);
+
+  GURL doc_url("https://www.google.com/search?q=cats");
+  GURL prediction_url("https://www.cat-food.com/");
+  MakeExternalAndroidAppNavigationPrediction({prediction_url});
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(RequestCount(), 0);
+}
+
 TEST_F(IsolatedPrerenderTabHelperTest, SuccessCase) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
index 72c226e7..8aa05da 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
@@ -21,11 +21,13 @@
 # List of all modules that are included in one or more of the production
 # chromevox scripts.
 chromevox_modules = [
+  "../common/automation_predicate.js",
+  "../common/automation_util.js",
+  "../common/constants.js",
+  "../common/tree_walker.js",
   "background/annotation/node_identifier.js",
   "background/annotation/user_annotation_handler.js",
   "background/automation_object_constructor_installer.js",
-  "../common/automation_predicate.js",
-  "../common/automation_util.js",
   "background/background.js",
   "background/base_automation_handler.js",
   "background/braille_background.js",
@@ -36,7 +38,6 @@
   "background/classic_background.js",
   "background/color.js",
   "background/command_handler.js",
-  "../common/constants.js",
   "background/cursors.js",
   "background/custom_automation_event.js",
   "background/desktop_automation_handler.js",
@@ -51,9 +52,8 @@
   "background/injected_script_loader.js",
   "background/keyboard_handler.js",
   "background/keymaps/key_map.js",
-  "background/locale_output_helper.js",
-  "learn_mode/kbexplorer.js",
   "background/live_regions.js",
+  "background/locale_output_helper.js",
   "background/logging/event_stream_logger.js",
   "background/logging/log.js",
   "background/logging/log_store.js",
@@ -71,8 +71,8 @@
   "background/prefs.js",
   "background/range_automation_handler.js",
   "background/recovery_strategy.js",
+  "background/smart_sticky_mode.js",
   "background/tabs_api_handler.js",
-  "../common/tree_walker.js",
   "braille/bluetooth_braille_display_manager.js",
   "braille/bluetooth_braille_display_ui.js",
   "braille/braille_display_manager.js",
@@ -108,6 +108,7 @@
   "injected/api_implementation.js",
   "injected/loader.js",
   "injected/script_installer.js",
+  "learn_mode/kbexplorer.js",
   "options/options.js",
   "panel/annotations_ui.js",
   "panel/i_search.js",
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/classic_background.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/classic_background.js
index 3ebbe32b..9dd0b8c 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/classic_background.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/classic_background.js
@@ -118,13 +118,12 @@
     if (pref == 'earcons') {
       AbstractEarcons.enabled = !!value;
     } else if (pref == 'sticky' && announce) {
-      if (value) {
-        ChromeVox.tts.speak(
-            Msgs.getMsg('sticky_mode_enabled'), QueueMode.FLUSH);
-      } else {
-        ChromeVox.tts.speak(
-            Msgs.getMsg('sticky_mode_disabled'), QueueMode.FLUSH);
-      }
+      new Output()
+          .withInitialSpeechProperties(AbstractTts.PERSONALITY_ANNOTATION)
+          .withString(
+              value ? Msgs.getMsg('sticky_mode_enabled') :
+                      Msgs.getMsg('sticky_mode_disabled'))
+          .go();
     } else if (pref == 'typingEcho' && announce) {
       let announceStr = '';
       switch (value) {
@@ -144,7 +143,10 @@
           break;
       }
       if (announceStr) {
-        ChromeVox.tts.speak(announceStr, QueueMode.QUEUE);
+        new Output()
+            .withInitialSpeechProperties(AbstractTts.PERSONALITY_ANNOTATION)
+            .withString(announceStr)
+            .go();
       }
     } else if (pref == 'brailleCaptions') {
       BrailleCaptionsBackground.setActive(!!value);
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
index c3e0f26a..4b200e0 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
@@ -14,6 +14,7 @@
 goog.require('LogStore');
 goog.require('Output');
 goog.require('PhoneticData');
+goog.require('SmartStickyMode');
 goog.require('TreeDumper');
 goog.require('ChromeVoxBackground');
 goog.require('ChromeVoxKbHandler');
@@ -34,7 +35,13 @@
 CommandHandler.incognito_ = !!chrome.runtime.getManifest()['incognito'];
 
 /**
- * Handles ChromeVox Next commands.
+ * Handles toggling sticky mode when encountering editables.
+ * @private {!SmartStickyMode}
+ */
+CommandHandler.smartStickyMode_ = new SmartStickyMode();
+
+/**
+ * Handles ChromeVox commands.
  * @param {string} command
  * @return {boolean} True if the command should propagate.
  */
@@ -363,11 +370,13 @@
     case 'nextEditText':
       pred = AutomationPredicate.editText;
       predErrorMsg = 'no_next_edit_text';
+      CommandHandler.smartStickyMode_.startIgnoringRangeChanges();
       break;
     case 'previousEditText':
       dir = Dir.BACKWARD;
       pred = AutomationPredicate.editText;
       predErrorMsg = 'no_previous_edit_text';
+      CommandHandler.smartStickyMode_.startIgnoringRangeChanges();
       break;
     case 'nextFormField':
       pred = AutomationPredicate.formField;
@@ -1089,6 +1098,7 @@
                 .withQueueMode(QueueMode.FLUSH)
                 .go();
           }
+          CommandHandler.onFinishCommand();
           return false;
         }
 
@@ -1123,6 +1133,7 @@
               .withString(Msgs.getMsg(predErrorMsg))
               .withQueueMode(QueueMode.FLUSH)
               .go();
+          CommandHandler.onFinishCommand();
           return false;
         }
       }
@@ -1154,6 +1165,7 @@
               // Jump or if there is a valid current range, then move from it
               // since we have refreshed node data.
               CommandHandler.onCommand(command);
+              CommandHandler.onFinishCommand();
               return;
             }
 
@@ -1183,6 +1195,7 @@
       } else {
         scrollable.scrollBackward(callback);
       }
+      CommandHandler.onFinishCommand();
       return false;
     }
   }
@@ -1192,10 +1205,18 @@
         current, undefined, speechProps, skipSettingSelection);
   }
 
+  CommandHandler.onFinishCommand();
   return false;
 };
 
 /**
+ * Finishes processing of a command.
+ */
+CommandHandler.onFinishCommand = function() {
+  CommandHandler.smartStickyMode_.stopIgnoringRangeChanges();
+};
+
+/**
  * Increase or decrease a speech property and make an announcement.
  * @param {string} propertyName The name of the property to change.
  * @param {boolean} increase If true, increases the property value by one
@@ -1408,4 +1429,5 @@
     }
   });
 };
+
 });  // goog.scope
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output.js
index e71cc22..dbd41bb 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output.js
@@ -1823,10 +1823,6 @@
       return ret;
     }
 
-    if (node.state[StateType.EDITABLE] && ChromeVox.isStickyPrefOn) {
-      ret.push({msgId: 'sticky_mode_enabled'});
-    }
-
     if (node.state[StateType.EDITABLE] && node.state[StateType.FOCUSED] &&
         (node.state[StateType.MULTILINE] ||
          node.state[StateType.RICHLY_EDITABLE])) {
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/prefs.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/prefs.js
index a7c1659..63f6c416 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/prefs.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/prefs.js
@@ -216,11 +216,7 @@
   'menuBrailleCommands': false,
   'numberReadingStyle': 'asWords',
   'position': '{}',
-  'siteSpecificEnhancements': true,
-  'siteSpecificScriptBase':
-      'https://ssl.gstatic.com/accessibility/javascript/ext/',
-  'siteSpecificScriptLoader':
-      'https://ssl.gstatic.com/accessibility/javascript/ext/loader.js',
+  'smartStickyMode': true,
   'speakTextUnderMouse': false,
   'sticky': false,
   'typingEcho': 0,
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode.js
new file mode 100644
index 0000000..148075033
--- /dev/null
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode.js
@@ -0,0 +1,87 @@
+// Copyright 2020 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.
+
+/**
+ * @fileoverview Handles automatic sticky mode toggles. Turns sticky mode off
+ * when the current range is over an editable; restores sticky mode when not on
+ * an editable.
+ */
+
+goog.provide('SmartStickyMode');
+
+goog.require('ChromeVoxState');
+
+/** @implements {ChromeVoxStateObserver} */
+SmartStickyMode = class {
+  constructor() {
+    /** @private {boolean} */
+    this.ignoreRangeChanges_ = false;
+
+    /**
+     * Tracks whether we (and not the user) turned off sticky mode when over an
+     * editable.
+     * @private {boolean}
+     */
+    this.didTurnOffStickyMode_ = false;
+
+    ChromeVoxState.addObserver(this);
+  }
+
+  /** @override */
+  onCurrentRangeChanged(newRange) {
+    if (!newRange || this.ignoreRangeChanges_ ||
+        localStorage['smartStickyMode'] !== 'true') {
+      return;
+    }
+
+    const isRangeEditable =
+        newRange.start.node.state[chrome.automation.StateType.EDITABLE];
+
+    // This toggler should not make any changes when the range isn't editable
+    // and we haven't previously tracked any sticky mode state from the user.
+    if (!isRangeEditable && !this.didTurnOffStickyMode_) {
+      return;
+    }
+
+    if (isRangeEditable) {
+      if (!ChromeVox.isStickyPrefOn) {
+        // Sticky mode was already off; do not track the current sticky state
+        // since we may have set it ourselves.
+        return;
+      }
+
+      if (this.didTurnOffStickyMode_) {
+        // This should not be possible with |ChromeVox.isStickyPrefOn| set to
+        // true.
+        throw 'Unexpected sticky state value encountered.';
+      }
+
+      // Save the sticky state for restoration later.
+      this.didTurnOffStickyMode_ = true;
+      ChromeVoxBackground.setPref(
+          'sticky', false /* value */, true /* announce */);
+    } else if (this.didTurnOffStickyMode_) {
+      // Restore the previous sticky mode state.
+      ChromeVoxBackground.setPref(
+          'sticky', true /* value */, true /* announce */);
+      this.didTurnOffStickyMode_ = false;
+    }
+  }
+
+  /**
+   * When called, ignores all changes in the current range when toggling sticky
+   * mode without user input.
+   */
+  startIgnoringRangeChanges() {
+    this.ignoreRangeChanges_ = true;
+  }
+
+  /**
+   * When called, stops ignoring changes in the current range when toggling
+   * sticky mode without user input.
+   */
+  stopIgnoringRangeChanges() {
+    this.ignoreRangeChanges_ = false;
+  }
+};
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/abstract_tts.js b/chrome/browser/resources/chromeos/accessibility/chromevox/common/abstract_tts.js
index fb18809..68c75d6 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/common/abstract_tts.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/common/abstract_tts.js
@@ -300,7 +300,7 @@
 /**
  * TTS personality for annotations - text spoken by ChromeVox that
  * elaborates on a user interface element but isn't displayed on-screen.
- * @type {Object}
+ * @type {!Object}
  */
 AbstractTts.PERSONALITY_ANNOTATION = {
   'relativePitch': -0.25,
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/options/options.html b/chrome/browser/resources/chromeos/accessibility/chromevox/options/options.html
index cd68d46..1561d4b9 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/options/options.html
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/options/options.html
@@ -100,6 +100,16 @@
              aria-labelledby="announceDownloadsLabel">
   </div>
 
+  <div class="option">
+    <label id="smartStickyModeLabel" class="i18n"
+          msgid="options_smart_sticky_mode">
+      Turn off sticky mode when editing text (Smart Sticky Mode)
+    </label>
+    <input id="smartStickyMode" type="checkbox"
+             class="checkbox pref" name="smartStickyMode"
+             aria-labelledby="smartStickyModeLabel">
+  </div>
+
   <h2 class="i18n description" msgid="options_audio_description"
       id="audioDescription">
     When playing audio
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/options/options_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/options/options_test.js
index f4f955d8..0e7efb2 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/options/options_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/options/options_test.js
@@ -84,3 +84,31 @@
         .replay();
   });
 });
+
+TEST_F('ChromeVoxOptionsTest', 'SmartStickyMode', function() {
+  this.runOnOptionsPage((mockFeedback, evt) => {
+    const smartStickyModeCheckbox = evt.target.find({
+      role: chrome.automation.RoleType.CHECK_BOX,
+      attributes:
+          {name: 'Turn off sticky mode when editing text (Smart Sticky Mode)'}
+    });
+    assertNotNullNorUndefined(smartStickyModeCheckbox);
+    mockFeedback
+        .call(smartStickyModeCheckbox.focus.bind(smartStickyModeCheckbox))
+        .expectSpeech(
+            'Turn off sticky mode when editing text (Smart Sticky Mode)',
+            'Check box', 'Checked')
+        .call(() => {
+          assertEquals('true', localStorage['smartStickyMode']);
+        })
+        .call(smartStickyModeCheckbox.doDefault.bind(smartStickyModeCheckbox))
+        .expectSpeech(
+            'Turn off sticky mode when editing text (Smart Sticky Mode)',
+            'Check box', 'Not checked')
+        .call(() => {
+          assertEquals('false', localStorage['smartStickyMode']);
+        })
+
+        .replay();
+  });
+});
diff --git a/chrome/browser/resources/chromeos/accessibility/strings/chromevox_strings.grdp b/chrome/browser/resources/chromeos/accessibility/strings/chromevox_strings.grdp
index c47fc91..90c6cdf 100644
--- a/chrome/browser/resources/chromeos/accessibility/strings/chromevox_strings.grdp
+++ b/chrome/browser/resources/chromeos/accessibility/strings/chromevox_strings.grdp
@@ -2912,5 +2912,8 @@
   <message desc="Announced to alert the user that ChromeVox has no current focus." name="IDS_CHROMEVOX_NO_FOCUS">
     No current ChromeVox focus. Press Alt+Shift+L to go to the launcher.
   </message>
+  <message desc="Labels the checkbox on the options page that enables or disables Smart Sticky Mode. The label explains how the feature works." name="IDS_CHROMEVOX_OPTIONS_SMART_STICKY_MODE">
+    Turn off sticky mode when editing text (Smart Sticky Mode)
+  </message>
 
 </grit-part>
diff --git a/chrome/browser/resources/settings/chromeos/BUILD.gn b/chrome/browser/resources/settings/chromeos/BUILD.gn
index 70f3bf05..f6fe898 100644
--- a/chrome/browser/resources/settings/chromeos/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/BUILD.gn
@@ -108,6 +108,7 @@
     "os_settings_main:closure_compile",
     "os_settings_menu:closure_compile",
     "os_settings_page:closure_compile",
+    "os_settings_search_box:closure_compile",
     "os_settings_ui:closure_compile",
     "parental_controls_page:closure_compile",
     "personalization_page:closure_compile",
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_search_box/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_settings_search_box/BUILD.gn
new file mode 100644
index 0000000..df914cd
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/os_settings_search_box/BUILD.gn
@@ -0,0 +1,29 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/closure_compiler/compile_js.gni")
+
+js_type_check("closure_compile") {
+  deps = [
+    ":os_search_result_row",
+    ":os_settings_search_box",
+  ]
+}
+
+js_library("os_settings_search_box") {
+  deps = [
+    "..:metrics_recorder",
+    "//third_party/polymer/v1_0/components-chromium/iron-dropdown:iron-dropdown-extracted",
+    "//third_party/polymer/v1_0/components-chromium/iron-list:iron-list-extracted",
+    "//ui/webui/resources/cr_elements/cr_toolbar:cr_toolbar_search_field",
+    "//ui/webui/resources/js:assert",
+  ]
+}
+
+js_library("os_search_result_row") {
+  deps = [
+    "//ui/webui/resources/js/cr/ui:focus_row",
+    "//ui/webui/resources/js/cr/ui:focus_row_behavior",
+  ]
+}
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_search_result_row.html b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_search_result_row.html
new file mode 100644
index 0000000..46c4c94d
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_search_result_row.html
@@ -0,0 +1,41 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
+<link rel="import" href="chrome://resources/html/cr/ui/focus_row_behavior.html">
+<link rel="import" href="../../settings_shared_css.html">
+
+<dom-module id="os-search-result-row">
+  <template>
+    <style include="settings-shared">
+      :host {
+        display: flex;
+        flex-basis: 100%;
+        height: 40px;
+      }
+
+      :host([selected]) .search-result-container {
+        background-color: var(--cros-menu-button-bg-color-active);
+      }
+
+      :host(:not([selected])) .search-result-container:hover {
+        background-color: var(--cros-menu-button-bg-color-hover);
+      }
+
+      .search-result-container {
+        display: flex;
+        flex-basis: 100%;
+      }
+
+      .text {
+        flex-basis: 100%;
+      }
+    </style>
+    <div class="search-result-container" focus-row-container>
+      <!-- TODO(crbug/1056909): Focus right hand icon instead; move
+           focus-row-control to that icon when available-->
+      <div class="text" focus-type="rowWrapper" focus-row-control selectable>
+        [[searchResultText]]
+      </div>
+    </div>
+  </template>
+  <script src="os_search_result_row.js"></script>
+</dom-module>
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_search_result_row.js b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_search_result_row.js
new file mode 100644
index 0000000..da6e8c3
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_search_result_row.js
@@ -0,0 +1,24 @@
+// Copyright 2020 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.
+
+/**
+ * @fileoverview 'os-search-result-row' is the container for one search result.
+ */
+
+Polymer({
+  is: 'os-search-result-row',
+
+  behaviors: [cr.ui.FocusRowBehavior],
+
+  properties: {
+    // Whether the search result row is selected.
+    selected: {
+      type: Boolean,
+      reflectToAttribute: true,
+    },
+
+    // String to be displayed as a result in the UI.
+    searchResultText: String,
+  },
+});
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.html b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.html
new file mode 100644
index 0000000..703b92d
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.html
@@ -0,0 +1,78 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
+<link rel="import" href="chrome://resources/cr_elements/cr_toolbar/cr_toolbar_search_field.html">
+<link rel="import" href="chrome://resources/html/assert.html">
+<link rel="import" href="chrome://resources/html/cr/ui/focus_row.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/iron-dropdown/iron-dropdown.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
+<link rel="import" href="os_search_result_row.html">
+<link rel="import" href="../metrics_recorder.html">
+<link rel="import" href="../../settings_shared_css.html">
+
+<dom-module id="os-settings-search-box">
+  <template>
+    <style include="settings-shared">
+      :host {
+        display: flex;
+        flex-basis: var(--cr-toolbar-field-width, 680px);
+        transition: width 150ms cubic-bezier(0.4, 0, 0.2, 1);
+      }
+
+      /* Only search icon is visible in this mode */
+      :host([narrow]:not([showing-search])) {
+        justify-content: flex-end;
+      }
+
+      :host([narrow][showing-search]) {
+        flex-basis: 100%;
+      }
+
+      :host([narrow]) iron-dropdown {
+        left: 0;
+        right: 0;
+      }
+
+      iron-dropdown [slot='dropdown-content'] {
+        background-color: var(--cr-card-background-color);
+        box-shadow: var(--cr-card-shadow);
+        display: table;
+      }
+
+      :host([narrow]) iron-dropdown [slot='dropdown-content'] {
+        width: 100%;
+      }
+
+      :host(:not([narrow])) iron-dropdown [slot='dropdown-content'] {
+        width: var(--cr-toolbar-field-width, 680px);
+      }
+    </style>
+    <cr-toolbar-search-field id="search" narrow="[[narrow]]"
+        label="$i18n{searchPrompt}" clear-label="$i18n{clearSearch}"
+        showing-search="{{showingSearch}}" spinner-active="[[spinnerActive]]">
+    </cr-toolbar-search-field>
+    <iron-dropdown id="searchResults" opened="[[shouldShowDropdown_]]"
+        no-overlap allow-outside-scroll no-cancel-on-outside-click>
+      <!--  As part of iron-dropdown's behavior, the slot 'dropdown-content' is
+            hidden until iron-dropdown's opened attribute is set true, or when
+            iron-dropdown's open() is called on the JS side. -->
+      <iron-list id="searchResultList" slot="dropdown-content"
+          selection-enabled items="[[searchResults_]]"
+          selected-item="{{selectedItem}}">
+        <!-- TODO(crbug/1056909): Use template dom-if if searchResults_ is
+             empty, show 'No Results' UI -->
+        <template>
+          <os-search-result-row actionable search-result-text="[[item]]"
+              selected="[[isItemSelected_(item, selectedItem)]]"
+              tabindex$="[[getRowTabIndex_(item, selectedItem)]]"
+              iron-list-tab-index$="[[getRowTabIndex_(item, selectedItem)]]"
+              last-focused="{{lastFocused_}}"
+              list-blurred="{{listBlurred_}}"
+              focus-row-index="[[index]]"
+              first$="[[!index]]">
+          </os-search-result-row>
+        </template>
+      </iron-list>
+    </iron-dropdown>
+  </template>
+  <script src="os_settings_search_box.js"></script>
+</dom-module>
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.js b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.js
new file mode 100644
index 0000000..8662679
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.js
@@ -0,0 +1,299 @@
+// Copyright 2020 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.
+
+(function() {
+'use strict';
+
+/**
+ * @fileoverview 'os-settings-search-box' is the container for the search input
+ * and settings search results.
+ */
+
+/**
+ * Fake function to simulate async SettingsSearchHandler Search().
+ * TODO(crbug/1056909): Remove once Settings Search Handler mojo API is ready.
+ * @param {string} query The query used to fetch results.
+ * @return {!Promise<!Array<string>>} A promise that will resolve with an array
+ *     of search results.
+ */
+function fakeSettingsSearchHandlerSearch(query) {
+  /**
+   * @param {number} min The lower bound integer.
+   * @param {number} max The upper bound integer.
+   * @return {number} A random integer between min and max inclusive.
+   */
+  function getRandomInt(min, max) {
+    min = Math.ceil(min);
+    max = Math.floor(max);
+    return Math.floor(Math.random() * (max - min + 1)) + min;
+  }
+
+  const fakeRandomResults = [
+    'bluetooth', 'wifi', 'language', 'people', 'personalization', 'security',
+    'touchpad', 'keyboard', 'passcode'
+  ];
+  fakeRandomResults.sort(() => Math.random() - 0.5);
+  return new Promise(resolve => {
+    setTimeout(() => {
+      resolve(fakeRandomResults.splice(
+          0, getRandomInt(1, fakeRandomResults.length - 1)));
+    }, 0);
+  });
+}
+
+Polymer({
+  is: 'os-settings-search-box',
+
+  properties: {
+    // True when the toolbar is displaying in narrow mode.
+    // TODO(hsuregan): Change narrow to isNarrow here and associated elements.
+    narrow: {
+      type: Boolean,
+      reflectToAttribute: true,
+    },
+
+    // Controls whether the search field is shown.
+    showingSearch: {
+      type: Boolean,
+      value: false,
+      notify: true,
+      reflectToAttribute: true,
+    },
+
+    // Value is proxied through to cr-toolbar-search-field. When true,
+    // the search field will show a processing spinner.
+    spinnerActive: Boolean,
+
+    // The currently selected search result associated with a
+    // <os-search-result-row>. This property is bound to <iron-list>.
+    // Note that when a item is selected, it's associated <os-search-result-row>
+    // is not focused() at the same time unless it is explicitly clicked/tapped.
+    selectedItem: {
+      // TODO(crbug/1056909): Change the type from String to Mojo result type.
+      type: String,
+      notify: true,
+    },
+
+    /**
+     * Passed into <iron-list>, one of which is always the selectedItem.
+     * @private {!Array<string>}
+     */
+    searchResults_: {
+      type: Array,
+      notify: true,
+      observer: 'selectFirstRow_',
+    },
+
+    /** @private */
+    shouldShowDropdown_: {
+      type: Boolean,
+      value: false,
+      computed: 'computeShouldShowDropdown_(searchResults_)',
+    },
+
+    /**
+     * Used by FocusRowBehavior to track the last focused element inside a
+     * <os-search-result-row> with the attribute 'focus-row-control'.
+     * @private {HTMLElement}
+     */
+    lastFocused_: Object,
+
+    /**
+     * Used by FocusRowBehavior to track if the list has been blurred.
+     * @private
+     */
+    listBlurred_: Boolean,
+  },
+
+  /**
+   * Mojo OS Settings Search handler used to fetch search results.
+   * TODO(crbug/1056909): Set in ready() when Mojo bindings are ready or
+   * eliminate altogether along with fake JS file.
+   * @private {*}
+   */
+  searchHandler_: undefined,
+
+  listeners: {
+    'blur': 'onBlur_',
+    'keydown': 'onKeyDown_',
+    'search-changed': 'fetchSearchResults_',
+  },
+
+  /** @private */
+  attached() {
+    const toolbarSearchField = this.$.search;
+    const searchInput = toolbarSearchField.getSearchInput();
+    searchInput.addEventListener(
+        'focus', this.onSearchInputFocused_.bind(this));
+  },
+
+  /**
+   * @param {*} searchHandler
+   */
+  setSearchHandlerForTesting(searchHandler) {
+    this.searchHandler_ = searchHandler;
+  },
+
+  /**
+   * @return {boolean}
+   * @private
+   */
+  computeShouldShowDropdown_() {
+    return this.searchResults_.length !== 0;
+  },
+
+  /** @private */
+  fetchSearchResults_() {
+    const query = this.$.search.getSearchInput().value;
+    if (query === '') {
+      this.searchResults_ = [];
+      return;
+    }
+
+    this.spinnerActive = true;
+
+    if (!this.searchHandler_) {
+      // TODO(crbug/1056909): Remove block when mojo interface is ready.
+      fakeSettingsSearchHandlerSearch(query).then(results => {
+        this.onSearchResultsReceived_(query, results);
+      });
+      return;
+    }
+
+    this.searchHandler_.search(query).then(results => {
+      this.onSearchResultsReceived_(query, results);
+    });
+  },
+
+  /**
+   * Updates search results UI when settings search results are fetched.
+   * @param {string} query The string used to find search results.
+   * @param {!Array<string>} results Array of search results.
+   * @private
+   */
+  onSearchResultsReceived_(query, results) {
+    if (query !== this.$.search.getSearchInput().value) {
+      // Received search results are invalid as the query has since changed.
+      return;
+    }
+
+    this.spinnerActive = false;
+    this.lastFocused_ = null;
+    this.searchResults_ = results;
+    settings.recordSearch();
+  },
+
+  /** @private */
+  onBlur_() {
+    // The user has clicked a region outside the search box or the input has
+    // been blurred; close the dropdown regardless if there are searchResults_.
+    this.$.searchResults.close();
+  },
+
+  /** @private */
+  onSearchInputFocused_() {
+    this.lastFocused_ = null;
+
+    if (this.shouldShowDropdown_) {
+      // Restore previous results instead of re-fetching.
+      this.$.searchResults.open();
+      return;
+    }
+
+    this.fetchSearchResults_();
+  },
+
+  /**
+   * @param {string} item The search result item.
+   * @return {boolean} True if the item is selected.
+   * @private
+   */
+  isItemSelected_(item) {
+    return this.searchResults_.indexOf(item) ===
+        this.searchResults_.indexOf(this.selectedItem);
+  },
+
+  /**
+   * Returns the correct tab index since <iron-list>'s default tabIndex property
+   * does not automatically add selectedItem's <os-search-result-row> to the
+   * default navigation flow, unless the user explicitly clicks on the row.
+   * @param {string} item The search result item.
+   * @return {number} A 0 if the row should be in the navigation flow, or a -1
+   *     if the row should not be in the navigation flow.
+   * @private
+   */
+  getRowTabIndex_(item) {
+    return this.isItemSelected_(item) ? 0 : -1;
+  },
+
+  /** @private */
+  selectFirstRow_() {
+    if (!this.shouldShowDropdown_) {
+      return;
+    }
+
+    this.selectedItem = this.searchResults_[0];
+  },
+
+  /**
+   * @param {string} key The string associated with a key.
+   * @private
+   */
+  selectRowViaKeys_(key) {
+    assert(key === 'ArrowDown' || key === 'ArrowUp' || key === 'Tab');
+
+    assert(!!this.selectedItem, 'There should be a selected item already.');
+
+    // Select the new item.
+    const selectedRowIndex = this.searchResults_.indexOf(this.selectedItem);
+    const numRows = this.searchResults_.length;
+    const delta = key === 'ArrowUp' ? -1 : 1;
+    const indexOfNewRow = (numRows + selectedRowIndex + delta) % numRows;
+    this.selectedItem = this.searchResults_[indexOfNewRow];
+
+    // If a row is focused, ensure it is the selectedResult's row.
+    if (this.lastFocused_) {
+      const rowEls = Array.from(
+          this.$.searchResultList.querySelectorAll('os-search-result-row'));
+      // Calling focus() on a <os-search-result-row> focuses the element within
+      // containing the attribute 'focus-row-control'.
+      rowEls[indexOfNewRow].focus();
+    }
+  },
+
+  /**
+   * Keydown handler to specify how enter-key, arrow-up key, and arrow-down-key
+   * interacts with search results in the dropdown.
+   * @param {!KeyboardEvent} e
+   * @private
+   */
+  onKeyDown_(e) {
+    if (!this.shouldShowDropdown_) {
+      // No action should be taken if there are no search results.
+      return;
+    }
+
+    if (e.key === 'Enter') {
+      // TODO(crbug/1056909): Take action on selected row.
+      return;
+    }
+
+    if (e.key === 'Tab') {
+      if (this.lastFocused_) {
+        // Prevent continuous tabbing from leaving search result
+        e.preventDefault();
+        this.selectRowViaKeys_(e.key);
+      }
+      return;
+    }
+
+    if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
+      // Do not impact the position of <cr-toolbar-search-field>'s caret.
+      e.preventDefault();
+      this.selectRowViaKeys_(e.key);
+      return;
+    }
+  },
+});
+})();
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.html b/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.html
index a346ea4..f25f9e2 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.html
+++ b/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.html
@@ -41,7 +41,9 @@
         @apply --layout-center;
         /* TODO(hsuregan): update for dark mode when needed. */
         min-height: 56px;
-        z-index: 2;
+        /* Needs to be higher than #cr-container-show-top's z-index so that the
+         * new settings search dropdown is not struck through by the shadow. */
+        z-index: 3;
       }
 
       cr-drawer {
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.js b/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.js
index 7f92553..0d7cf8f 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.js
@@ -248,6 +248,12 @@
       this.showDropShadows();
     }
 
+    if (loadTimeData.getBoolean('newOsSettingsSearch')) {
+      // TODO(crbug/1056909): Remove when new os settings search complete. This
+      // block prevents the old settings search code from being executed.
+      return;
+    }
+
     const urlSearchQuery =
         settings.Router.getInstance().getQueryParameters().get('search') || '';
     if (urlSearchQuery == this.lastSearchQuery_) {
@@ -258,7 +264,16 @@
 
     const toolbar = /** @type {!OsToolbarElement} */ (this.$$('os-toolbar'));
     const searchField =
-        /** @type {CrToolbarSearchFieldElement} */ (toolbar.getSearchField());
+        /** @type {?CrToolbarSearchFieldElement} */ (toolbar.getSearchField());
+
+    if (!searchField) {
+      // TODO(crbug/1056909): Remove this and surrounding code when new os
+      // settings search complete. If the search field has not been rendered
+      // yet, do not continue. crbug/1056909 changes the toolbar search field to
+      // an optional value, so the element is not attached to the DOM the first
+      // time this runs when the new OS Settings search flag is not flipped on.
+      return;
+    }
 
     // If the search was initiated by directly entering a search URL, need to
     // sync the URL parameter to the textbox.
@@ -303,10 +318,14 @@
 
   /**
    * Handles the 'search-changed' event fired from the toolbar.
+   * TODO(crbug/1056909): Remove when new settings search complete.
    * @param {!Event} e
    * @private
    */
   onSearchChanged_(e) {
+    if (loadTimeData.getBoolean('newOsSettingsSearch')) {
+      return;
+    }
     const query = e.detail;
     settings.Router.getInstance().navigateTo(
         settings.routes.BASIC,
diff --git a/chrome/browser/resources/settings/chromeos/os_toolbar/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_toolbar/BUILD.gn
index 9c449624..eb6484e5 100644
--- a/chrome/browser/resources/settings/chromeos/os_toolbar/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_toolbar/BUILD.gn
@@ -9,6 +9,8 @@
 }
 
 js_library("os_toolbar") {
-  deps =
-      [ "//ui/webui/resources/cr_elements/cr_toolbar:cr_toolbar_search_field" ]
+  deps = [
+    "../os_settings_search_box",
+    "//ui/webui/resources/cr_elements/cr_toolbar:cr_toolbar_search_field",
+  ]
 }
diff --git a/chrome/browser/resources/settings/chromeos/os_toolbar/os_toolbar.html b/chrome/browser/resources/settings/chromeos/os_toolbar/os_toolbar.html
index 09abb92a..5cf35bb 100644
--- a/chrome/browser/resources/settings/chromeos/os_toolbar/os_toolbar.html
+++ b/chrome/browser/resources/settings/chromeos/os_toolbar/os_toolbar.html
@@ -6,7 +6,9 @@
 <link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html">
 <link rel="import" href="chrome://resources/cr_elements/icons.html">
 <link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
+<link rel="import" href="chrome://resources/html/assert.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-media-query/iron-media-query.html">
+<link rel="import" href="../os_settings_search_box/os_settings_search_box.html">
 
 <dom-module id="os-toolbar">
   <template>
@@ -89,6 +91,10 @@
         flex-basis: var(--settings-main-basis);
       }
 
+      :host([narrow][showing-search_]) #rightContent {
+        display: none;
+      }
+
       :host(:not([narrow])) #rightContent {
         flex: 1 1 0;
         text-align: end;
@@ -108,11 +114,18 @@
     </div>
 
     <div id="centeredContent" hidden$="[[!showSearch]]">
-      <cr-toolbar-search-field id="search" narrow="[[narrow]]"
-          label="$i18n{searchPrompt}" clear-label="$i18n{clearSearch}"
-          spinner-active="[[spinnerActive]]"
-          showing-search="{{showingSearch_}}">
-      </cr-toolbar-search-field>
+      <template is="dom-if" if="[[!newOsSettingsSearch_]]">
+        <cr-toolbar-search-field id="search" narrow="[[narrow]]"
+            label="$i18n{searchPrompt}" clear-label="$i18n{clearSearch}"
+            spinner-active="[[spinnerActive]]"
+            showing-search="{{showingSearch_}}">
+        </cr-toolbar-search-field>
+      </template>
+      <template is="dom-if" if="[[newOsSettingsSearch_]]">
+        <os-settings-search-box id="searchBox" narrow="[[narrow]]"
+            showing-search="{{showingSearch_}}">
+        </os-settings-search-box>
+      </template>
       <iron-media-query query="(max-width: [[narrowThreshold]]px)"
           query-matches="{{narrow}}">
       </iron-media-query>
diff --git a/chrome/browser/resources/settings/chromeos/os_toolbar/os_toolbar.js b/chrome/browser/resources/settings/chromeos/os_toolbar/os_toolbar.js
index 5e0a157a..6ca377a 100644
--- a/chrome/browser/resources/settings/chromeos/os_toolbar/os_toolbar.js
+++ b/chrome/browser/resources/settings/chromeos/os_toolbar/os_toolbar.js
@@ -39,11 +39,25 @@
       type: Boolean,
       reflectToAttribute: true,
     },
+
+    /** @private */
+    newOsSettingsSearch_: {
+      type: Boolean,
+      value() {
+        return loadTimeData.getBoolean('newOsSettingsSearch');
+      },
+      readOnly: true,
+    },
   },
 
-  /** @return {!CrToolbarSearchFieldElement} */
+  /** @return {?CrToolbarSearchFieldElement} */
   getSearchField() {
-    return this.$.search;
+    if (this.newOsSettingsSearch_) {
+      assertNotReached(
+          'New OS Search should not be using OsToolbar.getSearchField()');
+    }
+    return /** @type {?CrToolbarSearchFieldElement} */ (
+        this.$$('cr-toolbar-search-field'));
   },
 
   /** @private */
diff --git a/chrome/browser/resources/settings/global_scroll_target_behavior.js b/chrome/browser/resources/settings/global_scroll_target_behavior.js
index 29ec449..f398c27b 100644
--- a/chrome/browser/resources/settings/global_scroll_target_behavior.js
+++ b/chrome/browser/resources/settings/global_scroll_target_behavior.js
@@ -21,7 +21,7 @@
   let scrollTargetResolver = new PromiseResolver();
 
   /** @polymerBehavior */
-  const GlobalScrollTargetBehaviorImpl = {
+  /* #export */ const GlobalScrollTargetBehaviorImpl = {
     properties: {
       /**
        * Read only property for the scroll target.
diff --git a/chrome/browser/resources/settings/lazy_load.js b/chrome/browser/resources/settings/lazy_load.js
index c798fe1..0f9aabe2 100644
--- a/chrome/browser/resources/settings/lazy_load.js
+++ b/chrome/browser/resources/settings/lazy_load.js
@@ -13,20 +13,20 @@
 // TODO(https://crbug.com/1026426): Uncomment these imports once the pages have
 // been migrated to Polymer 3.
 // import './privacy_page/cookies_page.m.js';
-// import './privacy_page/security_keys_subpage.m.js';
+import './privacy_page/security_keys_subpage.m.js';
 // import './privacy_page/security_page.m.js';
-// import './site_settings/all_sites.m.js';
+import './site_settings/all_sites.m.js';
 import './site_settings/site_data_details_subpage.m.js';
 // import './site_settings_page/site_settings_page.m.js';
 import './site_settings/category_default_setting.m.js';
 import './site_settings/category_setting_exceptions.m.js';
 import './site_settings/chooser_exception_list.m.js';
-// import './site_settings/media_picker.m.js';
-// import './site_settings/pdf_documents.m.js';
-// import './site_settings/protocol_handlers.m.js';
-// import './site_settings/site_data.m.js';
+import './site_settings/media_picker.m.js';
+import './site_settings/pdf_documents.m.js';
+import './site_settings/protocol_handlers.m.js';
+import './site_settings/site_data.m.js';
 import './site_settings/site_details.m.js';
-// import './site_settings/zoom_levels.m.js';
+import './site_settings/zoom_levels.m.js';
 
 // <if expr="not chromeos">
 import './people_page/import_data_dialog.m.js';
@@ -94,6 +94,11 @@
 export {kControlledByLookup} from './site_settings/site_settings_behavior.m.js';
 export {LocalDataBrowserProxyImpl} from './site_settings/local_data_browser_proxy.m.js';
 export {ContentSettingProvider,SiteSettingsPrefsBrowserProxyImpl} from './site_settings/site_settings_prefs_browser_proxy.m.js';
+export {SecurityKeysResetBrowserProxyImpl, SecurityKeysPINBrowserProxyImpl, SecurityKeysCredentialBrowserProxyImpl, SecurityKeysBioEnrollProxyImpl, SampleStatus, Ctap2Status} from './privacy_page/security_keys_browser_proxy.m.js';
+export {ResetDialogPage} from './privacy_page/security_keys_reset_dialog.m.js';
+export {SetPINDialogPage} from './privacy_page/security_keys_set_pin_dialog.m.js';
+export {CredentialManagementDialogPage} from './privacy_page/security_keys_credential_management_dialog.m.js';
+export {BioEnrollDialogPage} from './privacy_page/security_keys_bio_enroll_dialog.m.js';
 export {WebsiteUsageBrowserProxyImpl} from './site_settings/website_usage_browser_proxy.m.js';
 
 // <if expr="not chromeos">
diff --git a/chrome/browser/resources/settings/os_settings_resources.grd b/chrome/browser/resources/settings/os_settings_resources.grd
index 819fad9..b28b758e 100644
--- a/chrome/browser/resources/settings/os_settings_resources.grd
+++ b/chrome/browser/resources/settings/os_settings_resources.grd
@@ -617,6 +617,18 @@
       <structure name="IDR_OS_SETTINGS_LANGUAGES_MANAGE_INPUT_METHODS_PAGE_JS"
                  file="chromeos/os_languages_page/manage_input_methods_page.js"
                  type="chrome_html" />
+      <structure name="IDR_OS_SETTINGS_OS_SEARCH_RESULT_ROW_JS"
+                 file="chromeos/os_settings_search_box/os_search_result_row.js"
+                 type="chrome_html" />
+      <structure name="IDR_OS_SETTINGS_OS_SEARCH_RESULT_ROW_HTML"
+                 file="chromeos/os_settings_search_box/os_search_result_row.html"
+                 type="chrome_html" />
+      <structure name="IDR_OS_SETTINGS_OS_SETTINGS_SEARCH_BOX_JS"
+                 file="chromeos/os_settings_search_box/os_settings_search_box.js"
+                 type="chrome_html" />
+      <structure name="IDR_OS_SETTINGS_OS_SETTINGS_SEARCH_BOX_HTML"
+                 file="chromeos/os_settings_search_box/os_settings_search_box.html"
+                 type="chrome_html" />
       <structure name="IDR_OS_SETTINGS_OS_TOOLBAR_JS"
                  file="chromeos/os_toolbar/os_toolbar.js"
                  type="chrome_html" />
diff --git a/chrome/browser/resources/settings/privacy_page/BUILD.gn b/chrome/browser/resources/settings/privacy_page/BUILD.gn
index 17e24ff..6fb6aa6d 100644
--- a/chrome/browser/resources/settings/privacy_page/BUILD.gn
+++ b/chrome/browser/resources/settings/privacy_page/BUILD.gn
@@ -211,14 +211,14 @@
 
     #":privacy_page.m",
     ":privacy_page_browser_proxy.m",
+    ":security_keys_bio_enroll_dialog.m",
+    ":security_keys_browser_proxy.m",
+    ":security_keys_credential_management_dialog.m",
+    ":security_keys_pin_field.m",
+    ":security_keys_reset_dialog.m",
+    ":security_keys_set_pin_dialog.m",
+    ":security_keys_subpage.m",
 
-    #":security_keys_bio_enroll_dialog.m",
-    #":security_keys_browser_proxy.m",
-    #":security_keys_credential_management_dialog.m",
-    #":security_keys_pin_field.m",
-    #":security_keys_reset_dialog.m",
-    #":security_keys_set_pin_dialog.m",
-    #":security_keys_subpage.m",
     #":security_page.m",
   ]
 }
@@ -264,23 +264,33 @@
 js_library("security_keys_bio_enroll_dialog.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/privacy_page/security_keys_bio_enroll_dialog.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":security_keys_browser_proxy.m",
+    ":security_keys_pin_field.m",
+    "//third_party/polymer/v3_0/components-chromium/iron-a11y-announcer",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/cr_elements/cr_fingerprint:cr_fingerprint_progress_arc.m",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js:i18n_behavior.m",
+    "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
   extra_deps = [ ":security_keys_bio_enroll_dialog_module" ]
 }
 
 js_library("security_keys_browser_proxy.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/privacy_page/security_keys_browser_proxy.m.js" ]
-  deps = [
-    # TODO: Fill those in.
-  ]
+  deps = [ "//ui/webui/resources/js:cr.m" ]
   extra_deps = [ ":modulize" ]
 }
 
 js_library("security_keys_credential_management_dialog.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/privacy_page/security_keys_credential_management_dialog.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":security_keys_browser_proxy.m",
+    ":security_keys_pin_field.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js:i18n_behavior.m",
+    "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
   extra_deps = [ ":security_keys_credential_management_dialog_module" ]
 }
@@ -288,7 +298,9 @@
 js_library("security_keys_pin_field.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/privacy_page/security_keys_pin_field.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    "//third_party/polymer/v3_0/components-chromium/iron-a11y-announcer",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:i18n_behavior.m",
   ]
   extra_deps = [ ":security_keys_pin_field_module" ]
 }
@@ -296,7 +308,9 @@
 js_library("security_keys_reset_dialog.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/privacy_page/security_keys_reset_dialog.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":security_keys_browser_proxy.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:i18n_behavior.m",
   ]
   extra_deps = [ ":security_keys_reset_dialog_module" ]
 }
@@ -304,7 +318,10 @@
 js_library("security_keys_set_pin_dialog.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/privacy_page/security_keys_set_pin_dialog.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":security_keys_browser_proxy.m",
+    "//third_party/polymer/v3_0/components-chromium/iron-a11y-announcer",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:i18n_behavior.m",
   ]
   extra_deps = [ ":security_keys_set_pin_dialog_module" ]
 }
@@ -312,7 +329,9 @@
 js_library("security_keys_subpage.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/privacy_page/security_keys_subpage.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    "..:i18n_setup.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:assert.m",
   ]
   extra_deps = [ ":security_keys_subpage_module" ]
 }
@@ -369,36 +388,61 @@
   js_file = "security_keys_bio_enroll_dialog.js"
   html_file = "security_keys_bio_enroll_dialog.html"
   html_type = "dom-module"
+  namespace_rewrites = settings_namespace_rewrites
+  auto_imports = settings_auto_imports + [
+                   "chrome/browser/resources/settings/privacy_page/security_keys_browser_proxy.html|SecurityKeysBioEnrollProxy,SecurityKeysBioEnrollProxyImpl,Ctap2Status,SampleStatus,Enrollment,EnrollmentResponse,SampleResponse,",
+                   "ui/webui/resources/html/assert.html|assert,assertNotReached",
+                   "ui/webui/resources/html/polymer.html|afterNextRender,html,Polymer",
+                 ]
 }
 
 polymer_modulizer("security_keys_credential_management_dialog") {
   js_file = "security_keys_credential_management_dialog.js"
   html_file = "security_keys_credential_management_dialog.html"
   html_type = "dom-module"
+  namespace_rewrites = settings_namespace_rewrites
+  auto_imports = settings_auto_imports + [
+                   "chrome/browser/resources/settings/privacy_page/security_keys_browser_proxy.html|SecurityKeysCredentialBrowserProxy,SecurityKeysCredentialBrowserProxyImpl,Credential",
+                   "ui/webui/resources/html/assert.html|assert,assertNotReached",
+                 ]
 }
 
 polymer_modulizer("security_keys_pin_field") {
   js_file = "security_keys_pin_field.js"
   html_file = "security_keys_pin_field.html"
   html_type = "dom-module"
+  namespace_rewrites = settings_namespace_rewrites
+  auto_imports =
+      settings_auto_imports +
+      [ "ui/webui/resources/html/polymer.html|afterNextRender,html,Polymer" ]
 }
 
 polymer_modulizer("security_keys_reset_dialog") {
   js_file = "security_keys_reset_dialog.js"
   html_file = "security_keys_reset_dialog.html"
   html_type = "dom-module"
+  namespace_rewrites = settings_namespace_rewrites
+  auto_imports = settings_auto_imports + [ "chrome/browser/resources/settings/privacy_page/security_keys_browser_proxy.html|SecurityKeysResetBrowserProxy,SecurityKeysResetBrowserProxyImpl" ]
 }
 
 polymer_modulizer("security_keys_set_pin_dialog") {
   js_file = "security_keys_set_pin_dialog.js"
   html_file = "security_keys_set_pin_dialog.html"
   html_type = "dom-module"
+  namespace_rewrites = settings_namespace_rewrites
+  auto_imports = settings_auto_imports + [
+                   "chrome/browser/resources/settings/privacy_page/security_keys_browser_proxy.html|SecurityKeysPINBrowserProxy,SecurityKeysPINBrowserProxyImpl",
+                   "ui/webui/resources/html/polymer.html|afterNextRender,html,Polymer",
+                 ]
 }
 
 polymer_modulizer("security_keys_subpage") {
   js_file = "security_keys_subpage.js"
   html_file = "security_keys_subpage.html"
   html_type = "dom-module"
+  namespace_rewrites = settings_namespace_rewrites
+  auto_imports =
+      settings_auto_imports + [ "ui/webui/resources/html/assert.html|assert" ]
 }
 
 polymer_modulizer("security_page") {
diff --git a/chrome/browser/resources/settings/privacy_page/security_keys_bio_enroll_dialog.html b/chrome/browser/resources/settings/privacy_page/security_keys_bio_enroll_dialog.html
index d46583d..76126f0 100644
--- a/chrome/browser/resources/settings/privacy_page/security_keys_bio_enroll_dialog.html
+++ b/chrome/browser/resources/settings/privacy_page/security_keys_bio_enroll_dialog.html
@@ -6,6 +6,7 @@
 <link rel="import" href="chrome://resources/cr_elements/cr_fingerprint/cr_fingerprint_progress_arc.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
 <link rel="import" href="chrome://resources/html/i18n_behavior.html">
+<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-announcer/iron-a11y-announcer.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
diff --git a/chrome/browser/resources/settings/privacy_page/security_keys_bio_enroll_dialog.js b/chrome/browser/resources/settings/privacy_page/security_keys_bio_enroll_dialog.js
index 629a596..691bdfc 100644
--- a/chrome/browser/resources/settings/privacy_page/security_keys_bio_enroll_dialog.js
+++ b/chrome/browser/resources/settings/privacy_page/security_keys_bio_enroll_dialog.js
@@ -10,7 +10,7 @@
 
 cr.define('settings', function() {
   /** @enum {string} */
-  const BioEnrollDialogPage = {
+  /* #export */ const BioEnrollDialogPage = {
     INITIAL: 'initial',
     PIN_PROMPT: 'pinPrompt',
     ENROLLMENTS: 'enrollments',
@@ -69,7 +69,7 @@
       recentEnrollmentName_: String,
     },
 
-    /** @private {?settings.SecurityKeysBioEnrollProxyImpl} */
+    /** @private {?settings.SecurityKeysBioEnrollProxy} */
     browserProxy_: null,
 
     /** @private {number} */
@@ -111,7 +111,8 @@
       // Disable the confirm button to prevent concurrent submissions.
       this.confirmButtonDisabled_ = true;
 
-      this.$.pin.trySubmit(pin => this.browserProxy_.providePIN(pin))
+      /** @type {!SettingsSecurityKeysPinFieldElement} */ (this.$.pin)
+          .trySubmit(pin => this.browserProxy_.providePIN(pin))
           .then(
               () => {
                 // Leave confirm button disabled while enumerating fingerprints.
@@ -186,7 +187,7 @@
       assert(this.dialogPage_ == BioEnrollDialogPage.ENROLLMENTS);
 
       this.maxSamples_ = -1;  // Reset maxSamples_ before enrolling starts.
-      this.$.arc.reset();
+      /** @type {!CrFingerprintProgressArcElement} */ (this.$.arc).reset();
       this.progressArcLabel_ =
           this.i18n('securityKeysBioEnrollmentEnrollingLabel');
 
@@ -221,10 +222,12 @@
         this.maxSamples_ = response.remaining + 1;
       }
 
-      this.$.arc.setProgress(
-          100 * (this.maxSamples_ - response.remaining - 1) / this.maxSamples_,
-          100 * (this.maxSamples_ - response.remaining) / this.maxSamples_,
-          false);
+      /** @type {!CrFingerprintProgressArcElement} */ (this.$.arc)
+          .setProgress(
+              100 * (this.maxSamples_ - response.remaining - 1) /
+                  this.maxSamples_,
+              100 * (this.maxSamples_ - response.remaining) / this.maxSamples_,
+              false);
     },
 
     /**
@@ -243,8 +246,9 @@
       }
 
       this.maxSamples_ = Math.max(this.maxSamples_, 1);
-      this.$.arc.setProgress(
-          100 * (this.maxSamples_ - 1) / this.maxSamples_, 100, true);
+      /** @type {!CrFingerprintProgressArcElement} */ (this.$.arc)
+          .setProgress(
+              100 * (this.maxSamples_ - 1) / this.maxSamples_, 100, true);
 
       assert(response.enrollment);
       this.recentEnrollmentId_ = response.enrollment.id;
diff --git a/chrome/browser/resources/settings/privacy_page/security_keys_browser_proxy.js b/chrome/browser/resources/settings/privacy_page/security_keys_browser_proxy.js
index a7fe480..fffeab5 100644
--- a/chrome/browser/resources/settings/privacy_page/security_keys_browser_proxy.js
+++ b/chrome/browser/resources/settings/privacy_page/security_keys_browser_proxy.js
@@ -2,13 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {addSingletonGetter, sendWithPromise} from 'chrome://resources/js/cr.m.js';
+// clang-format on
+
 cr.define('settings', function() {
   /**
    * Ctap2Status contains a subset of CTAP2 status codes. See
    * device::CtapDeviceResponseCode for the full list.
    * @enum {number}
    */
-  const Ctap2Status = {
+  /* #export */ const Ctap2Status = {
     OK: 0x0,
     ERR_INVALID_OPTION: 0x2C,
     ERR_KEEPALIVE_CANCEL: 0x2D,
@@ -34,7 +38,7 @@
    *            userDisplayName: string}}
    * @see chrome/browser/ui/webui/settings/settings_security_key_handler.cc
    */
-  let Credential;
+  /* #export */ let Credential;
 
   /**
    * SampleStatus is the result for reading an individual sample ("touch")
@@ -42,7 +46,7 @@
    * lastEnrollSampleStatus enum defined in the CTAP spec.
    * @enum {number}
    */
-  const SampleStatus = {
+  /* #export */ const SampleStatus = {
     OK: 0x0,
   };
 
@@ -54,7 +58,7 @@
    *            remaining: number}}
    * @see chrome/browser/ui/webui/settings/settings_security_key_handler.cc
    */
-  let SampleResponse;
+  /* #export */ let SampleResponse;
 
   /**
    * EnrollmentResponse is the final response to an enrollment suboperation,
@@ -63,7 +67,7 @@
    *            enrollment: ?settings.Enrollment}}
    * @see chrome/browser/ui/webui/settings/settings_security_key_handler.cc
    */
-  let EnrollmentResponse;
+  /* #export */ let EnrollmentResponse;
 
   /**
    * Enrollment represents a valid fingerprint template stored on a security
@@ -73,10 +77,10 @@
    *            id: string}}
    * @see chrome/browser/ui/webui/settings/settings_security_key_handler.cc
    */
-  let Enrollment;
+  /* #export */ let Enrollment;
 
   /** @interface */
-  class SecurityKeysPINBrowserProxy {
+  /* #export */ class SecurityKeysPINBrowserProxy {
     /**
      * Starts a PIN set/change operation by flashing all security keys. Resolves
      * with a pair of numbers. The first is one if the process has immediately
@@ -103,7 +107,7 @@
   }
 
   /** @interface */
-  class SecurityKeysCredentialBrowserProxy {
+  /* #export */ class SecurityKeysCredentialBrowserProxy {
     /**
      * Starts a credential management operation.
      *
@@ -147,7 +151,7 @@
   }
 
   /** @interface */
-  class SecurityKeysResetBrowserProxy {
+  /* #export */ class SecurityKeysResetBrowserProxy {
     /**
      * Starts a reset operation by flashing all security keys and sending a
      * reset command to the one that the user activates. Resolves with a CTAP
@@ -167,7 +171,7 @@
   }
 
   /** @interface */
-  class SecurityKeysBioEnrollProxy {
+  /* #export */ class SecurityKeysBioEnrollProxy {
     /**
      * Starts a biometric enrollment operation.
      *
@@ -247,7 +251,7 @@
   }
 
   /** @implements {settings.SecurityKeysPINBrowserProxy} */
-  class SecurityKeysPINBrowserProxyImpl {
+  /* #export */ class SecurityKeysPINBrowserProxyImpl {
     /** @override */
     startSetPIN() {
       return cr.sendWithPromise('securityKeyStartSetPIN');
@@ -265,7 +269,7 @@
   }
 
   /** @implements {settings.SecurityKeysCredentialBrowserProxy} */
-  class SecurityKeysCredentialBrowserProxyImpl {
+  /* #export */ class SecurityKeysCredentialBrowserProxyImpl {
     /** @override */
     startCredentialManagement() {
       return cr.sendWithPromise('securityKeyCredentialManagementStart');
@@ -293,7 +297,7 @@
   }
 
   /** @implements {settings.SecurityKeysResetBrowserProxy} */
-  class SecurityKeysResetBrowserProxyImpl {
+  /* #export */ class SecurityKeysResetBrowserProxyImpl {
     /** @override */
     reset() {
       return cr.sendWithPromise('securityKeyReset');
@@ -311,7 +315,7 @@
   }
 
   /** @implements {settings.SecurityKeysBioEnrollProxy} */
-  class SecurityKeysBioEnrollProxyImpl {
+  /* #export */ class SecurityKeysBioEnrollProxyImpl {
     /** @override */
     startBioEnroll() {
       return cr.sendWithPromise('securityKeyBioEnrollStart');
diff --git a/chrome/browser/resources/settings/privacy_page/security_keys_credential_management_dialog.js b/chrome/browser/resources/settings/privacy_page/security_keys_credential_management_dialog.js
index ddf5532..19eeffdbc 100644
--- a/chrome/browser/resources/settings/privacy_page/security_keys_credential_management_dialog.js
+++ b/chrome/browser/resources/settings/privacy_page/security_keys_credential_management_dialog.js
@@ -9,7 +9,7 @@
 
 cr.define('settings', function() {
   /** @enum {string} */
-  const CredentialManagementDialogPage = {
+  /* #export */ const CredentialManagementDialogPage = {
     INITIAL: 'initial',
     PIN_PROMPT: 'pinPrompt',
     CREDENTIALS: 'credentials',
@@ -100,7 +100,8 @@
       // Disable the confirm button to prevent concurrent submissions.
       this.confirmButtonDisabled_ = true;
 
-      this.$.pin.trySubmit(pin => this.browserProxy_.providePIN(pin))
+      /** @type {!SettingsSecurityKeysPinFieldElement} */ (this.$.pin)
+          .trySubmit(pin => this.browserProxy_.providePIN(pin))
           .then(
               () => {
                 // Leave confirm button disabled while enumerating credentials.
diff --git a/chrome/browser/resources/settings/privacy_page/security_keys_reset_dialog.js b/chrome/browser/resources/settings/privacy_page/security_keys_reset_dialog.js
index 39d8b00..dd55392 100644
--- a/chrome/browser/resources/settings/privacy_page/security_keys_reset_dialog.js
+++ b/chrome/browser/resources/settings/privacy_page/security_keys_reset_dialog.js
@@ -9,7 +9,7 @@
 
 cr.define('settings', function() {
   /** @enum {string} */
-  const ResetDialogPage = {
+  /* #export */ const ResetDialogPage = {
     INITIAL: 'initial',
     NO_RESET: 'noReset',
     RESET_FAILED: 'resetFailed',
diff --git a/chrome/browser/resources/settings/privacy_page/security_keys_set_pin_dialog.js b/chrome/browser/resources/settings/privacy_page/security_keys_set_pin_dialog.js
index 141f45f..f1a6eae 100644
--- a/chrome/browser/resources/settings/privacy_page/security_keys_set_pin_dialog.js
+++ b/chrome/browser/resources/settings/privacy_page/security_keys_set_pin_dialog.js
@@ -9,7 +9,7 @@
 
 cr.define('settings', function() {
   /** @enum {string} */
-  const SetPINDialogPage = {
+  /* #export */ const SetPINDialogPage = {
     INITIAL: 'initial',
     NO_PIN_SUPPORT: 'noPINSupport',
     REINSERT: 'reinsert',
diff --git a/chrome/browser/resources/settings/privacy_page/security_keys_subpage.html b/chrome/browser/resources/settings/privacy_page/security_keys_subpage.html
index 255be05..6a5fd43 100644
--- a/chrome/browser/resources/settings/privacy_page/security_keys_subpage.html
+++ b/chrome/browser/resources/settings/privacy_page/security_keys_subpage.html
@@ -1,8 +1,10 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
 <link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
+<link rel="import" href="chrome://resources/html/assert.html">
 <link rel="import" href="chrome://resources/html/cr.html">
 <link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
+<link rel="import" href="../i18n_setup.html">
 <link rel="import" href="../settings_shared_css.html">
 
 <link rel="import" href="security_keys_credential_management_dialog.html">
diff --git a/chrome/browser/resources/settings/settings.gni b/chrome/browser/resources/settings/settings.gni
index 969f0e3..00351e8 100644
--- a/chrome/browser/resources/settings/settings.gni
+++ b/chrome/browser/resources/settings/settings.gni
@@ -11,11 +11,13 @@
   "settings.AboutPageBrowserProxy|AboutPageBrowserProxy",
   "settings.AccountManagerBrowserProxy|AccountManagerBrowserProxy",
   "settings.Account|Account",
+  "settings.ALL_SITES_DIALOG|ALL_SITES_DIALOG",
   "settings.AllSitesAction2|AllSitesAction2",
   "settings.AndroidInfoBrowserProxy|AndroidInfoBrowserProxy",
   "settings.AndroidSmsInfo|AndroidSmsInfo",
   "settings.AppearanceBrowserProxy|AppearanceBrowserProxy",
   "settings.AutofillManager|AutofillManager",
+  "settings.BioEnrollDialogPage|BioEnrollDialogPage",
   "settings.BlockingRequestManager|BlockingRequestManager",
   "settings.CaptionsBrowserProxy|CaptionsBrowserProxy",
   "settings.ChooserType|ChooserType",
@@ -49,6 +51,7 @@
   "settings.PageStatus|PageStatus",
   "settings.PaymentsManager|PaymentsManager",
   "settings.pageVisibility|pageVisibility",
+  "settings.PINFieldSubmitFunc|PINFieldSubmitFunc",
   "settings.PluralStringProxy|PluralStringProxy",
   "Settings.PrefUtil.prefToString|prefToString",
   "Settings.PrefUtil.stringToPrefValue|stringToPrefValue",
@@ -59,6 +62,7 @@
   "settings.ProfileInfo|ProfileInfo",
   "settings.ProfileShortcutStatus|ProfileShortcutStatus",
   "settings.ResetBrowserProxy|ResetBrowserProxy",
+  "settings.ResetDialogPage|ResetDialogPage",
   "settings.ResolverOption|ResolverOption",
   "settings.Route|Route",
   "settings.routes|routes",
@@ -68,6 +72,18 @@
   "settings.SecureDnsMode|SecureDnsMode",
   "settings.SecureDnsSetting|SecureDnsSetting",
   "settings.SecureDnsUiManagementMode|SecureDnsUiManagementMode",
+  "settings.SecurityKeysBioEnrollProxy|SecurityKeysBioEnrollProxy",
+  "settings.SecurityKeysCredentialBrowserProxy|SecurityKeysCredentialBrowserProxy",
+  "settings.SecurityKeysResetBrowserProxy|SecurityKeysResetBrowserProxy",
+  "settings.Enrollment|Enrollment",
+  "settings.SecurityKeysPINBrowserProxy|SecurityKeysPINBrowserProxy",
+  "settings.EnrollmentResponse|EnrollmentResponse",
+  "settings.SetPINDialogPage|SetPINDialogPage",
+  "settings.CredentialManagementDialogPage|CredentialManagementDialogPage",
+  "settings.SampleStatus|SampleStatus",
+  "settings.SampleResponse|SampleResponse",
+  "settings.Ctap2Status|Ctap2Status",
+  "settings.Credential|Credential",
   "settings.MetricsReporting|MetricsReporting",
   "settings.SITE_EXCEPTION_WILDCARD|SITE_EXCEPTION_WILDCARD",
   "settings.SiteSettingSource|SiteSettingSource",
diff --git a/chrome/browser/resources/settings/settings_resources.grd b/chrome/browser/resources/settings/settings_resources.grd
index a63644b..50f2db3b 100644
--- a/chrome/browser/resources/settings/settings_resources.grd
+++ b/chrome/browser/resources/settings/settings_resources.grd
@@ -336,10 +336,10 @@
       <structure name="IDR_SETTINGS_HISTORY_DELETION_DIALOG_JS"
                  file="clear_browsing_data_dialog/history_deletion_dialog.js"
                  type="chrome_html" />
-      <structure name="IDR_SETTINGS_SECURITY_KEYS_PAGE_HTML"
+      <structure name="IDR_SETTINGS_SECURITY_KEYS_SUBPAGE_HTML"
                  file="privacy_page/security_keys_subpage.html"
                  type="chrome_html" />
-      <structure name="IDR_SETTINGS_SECURITY_KEYS_PAGE_JS"
+      <structure name="IDR_SETTINGS_SECURITY_KEYS_SUBPAGE_JS"
                  file="privacy_page/security_keys_subpage.js"
                  type="chrome_html"/>
       <structure name="IDR_SETTINGS_SECURITY_KEYS_SET_PIN_DIALOG_HTML"
@@ -372,10 +372,10 @@
       <structure name="IDR_SETTINGS_SECURITY_KEYS_BIO_ENROLL_DIALOG_JS"
                  file="privacy_page/security_keys_bio_enroll_dialog.js"
                  type="chrome_html" />
-      <structure name="IDR_SETTINGS_SECURITY_KEYS_DIALOG_BROWSER_PROXY_HTML"
+      <structure name="IDR_SETTINGS_SECURITY_KEYS_BROWSER_PROXY_HTML"
                  file="privacy_page/security_keys_browser_proxy.html"
                  type="chrome_html" />
-      <structure name="IDR_SETTINGS_SECURITY_KEYS_DIALOG_BROWSER_PROXY_JS"
+      <structure name="IDR_SETTINGS_SECURITY_KEYS_BROWSER_PROXY_JS"
                  file="privacy_page/security_keys_browser_proxy.js"
                  type="chrome_html" />
       <structure name="IDR_SETTINGS_CONTROLS_BOOLEAN_CONTROL_BEHAVIOR_HTML"
diff --git a/chrome/browser/resources/settings/settings_resources_v3.grdp b/chrome/browser/resources/settings/settings_resources_v3.grdp
index 5ba6505..0cdeaf1f 100644
--- a/chrome/browser/resources/settings/settings_resources_v3.grdp
+++ b/chrome/browser/resources/settings/settings_resources_v3.grdp
@@ -457,6 +457,34 @@
            use_base_dir="false"
            preprocess="true"
            type="BINDATA" />
+  <include name="IDR_SETTINGS_PRIVACY_PAGE_SECURITY_KEYS_BIO_ENROLL_DIALOG_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/privacy_page/security_keys_bio_enroll_dialog.m.js"
+           use_base_dir="false"
+           type="BINDATA" />
+  <include name="IDR_SETTINGS_PRIVACY_PAGE_SECURITY_KEYS_BROWSER_PROXY_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/privacy_page/security_keys_browser_proxy.m.js"
+           use_base_dir="false"
+           type="BINDATA" />
+  <include name="IDR_SETTINGS_PRIVACY_PAGE_SECURITY_KEYS_CREDENTIAL_MANAGEMENT_DIALOG_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/privacy_page/security_keys_credential_management_dialog.m.js"
+           use_base_dir="false"
+           type="BINDATA" />
+  <include name="IDR_SETTINGS_PRIVACY_PAGE_SECURITY_KEYS_PIN_FIELD_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/privacy_page/security_keys_pin_field.m.js"
+           use_base_dir="false"
+           type="BINDATA" />
+  <include name="IDR_SETTINGS_PRIVACY_PAGE_SECURITY_KEYS_RESET_DIALOG_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/privacy_page/security_keys_reset_dialog.m.js"
+           use_base_dir="false"
+           type="BINDATA" />
+  <include name="IDR_SETTINGS_PRIVACY_PAGE_SECURITY_KEYS_SET_PIN_DIALOG_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/privacy_page/security_keys_set_pin_dialog.m.js"
+           use_base_dir="false"
+           type="BINDATA" />
+  <include name="IDR_SETTINGS_PRIVACY_PAGE_SECURITY_KEYS_SUBPAGE_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/privacy_page/security_keys_subpage.m.js"
+           use_base_dir="false"
+           type="BINDATA" />
   <include name="IDR_SETTINGS_RESET_PAGE_M_JS"
            file="${root_gen_dir}/chrome/browser/resources/settings/reset_page/reset_page.m.js"
            use_base_dir="false"
@@ -579,6 +607,10 @@
            file="${root_gen_dir}/chrome/browser/resources/settings/site_settings/add_site_dialog.m.js"
            use_base_dir="false"
            type="BINDATA" />
+  <include name="IDR_SETTINGS_SITE_SETTINGS_ALL_SITES_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/site_settings/all_sites.m.js"
+           use_base_dir="false"
+           type="BINDATA" />
   <include name="IDR_SETTINGS_SITE_SETTINGS_ALL_SITES_ICONS_M_JS"
            file="${root_gen_dir}/chrome/browser/resources/settings/site_settings/all_sites_icons.m.js"
            use_base_dir="false"
@@ -625,10 +657,31 @@
            file="${root_gen_dir}/chrome/browser/resources/settings/site_settings/local_data_browser_proxy.m.js"
            use_base_dir="false"
            type="BINDATA" />
+  <include name="IDR_SETTINGS_SITE_SETTINGS_MEDIA_PICKER_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/site_settings/media_picker.m.js"
+           use_base_dir="false"
+           type="BINDATA" />
+  <include name="IDR_SETTINGS_SITE_SETTINGS_PDF_DOCUMENTS_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/site_settings/pdf_documents.m.js"
+           use_base_dir="false"
+           type="BINDATA" />
+  <include name="IDR_SETTINGS_SITE_SETTINGS_PROTOCOL_HANDLERS_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/site_settings/protocol_handlers.m.js"
+           use_base_dir="false"
+           type="BINDATA"
+           preprocess="true" />
+  <include name="IDR_SETTINGS_SITE_SETTINGS_SITE_DATA_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/site_settings/site_data.m.js"
+           use_base_dir="false"
+           type="BINDATA" />
   <include name="IDR_SETTINGS_SITE_SETTINGS_SITE_DATA_DETAILS_SUBPAGE_M_JS"
            file="${root_gen_dir}/chrome/browser/resources/settings/site_settings/site_data_details_subpage.m.js"
            use_base_dir="false"
            type="BINDATA" />
+  <include name="IDR_SETTINGS_SITE_SETTINGS_SITE_DATA_ENTRY_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/site_settings/site_data_entry.m.js"
+           use_base_dir="false"
+           type="BINDATA" />
   <include name="IDR_SETTINGS_SITE_SETTINGS_SITE_DETAILS_M_JS"
            file="${root_gen_dir}/chrome/browser/resources/settings/site_settings/site_details.m.js"
            use_base_dir="false"
@@ -666,6 +719,10 @@
            file="${root_gen_dir}/chrome/browser/resources/settings/site_settings/website_usage_browser_proxy.m.js"
            use_base_dir="false"
            type="BINDATA" />
+  <include name="IDR_SETTINGS_SITE_SETTINGS_ZOOM_LEVELS_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/site_settings/zoom_levels.m.js"
+           use_base_dir="false"
+           type="BINDATA" />
   <if expr="not chromeos">
     <include name="IDR_SETTINGS_PRINTING_PAGE_PRINTING_BROWSER_PROXY_M_JS"
              file="${root_gen_dir}/chrome/browser/resources/settings/printing_page/printing_browser_proxy.m.js"
diff --git a/chrome/browser/resources/settings/site_settings/BUILD.gn b/chrome/browser/resources/settings/site_settings/BUILD.gn
index 815c344..e98400d 100644
--- a/chrome/browser/resources/settings/site_settings/BUILD.gn
+++ b/chrome/browser/resources/settings/site_settings/BUILD.gn
@@ -294,13 +294,11 @@
   ]
 }
 
-# TODO(crbug.com/1026426): Fix and enable.
 js_type_check("closure_compile_module") {
   is_polymer3 = true
   deps = [
     ":add_site_dialog.m",
-
-    #    ":all_sites.m",
+    ":all_sites.m",
     ":android_info_browser_proxy.m",
     ":category_default_setting.m",
     ":category_setting_exceptions.m",
@@ -310,14 +308,12 @@
     ":cookie_info.m",
     ":edit_exception_dialog.m",
     ":local_data_browser_proxy.m",
-
-    #    ":media_picker.m",
-    #    ":pdf_documents.m",
-    #    ":protocol_handlers.m",
-    #    ":site_data.m",
+    ":media_picker.m",
+    ":pdf_documents.m",
+    ":protocol_handlers.m",
+    ":site_data.m",
     ":site_data_details_subpage.m",
-
-    #    ":site_data_entry.m",
+    ":site_data_entry.m",
     ":site_details.m",
     ":site_details_permission.m",
     ":site_entry.m",
@@ -326,8 +322,7 @@
     ":site_settings_behavior.m",
     ":site_settings_prefs_browser_proxy.m",
     ":website_usage_browser_proxy.m",
-
-    #    ":zoom_levels.m",
+    ":zoom_levels.m",
   ]
 }
 
@@ -347,8 +342,18 @@
 js_library("all_sites.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/site_settings/all_sites.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":constants.m",
+    ":local_data_browser_proxy.m",
+    ":site_settings_behavior.m",
+    ":site_settings_prefs_browser_proxy.m",
+    "..:global_scroll_target_behavior.m",
+    "..:router.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:i18n_behavior.m",
+    "//ui/webui/resources/js:load_time_data.m",
+    "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
+  externs_list = [ "$externs_path/settings_private.js" ]
   extra_deps = [ ":all_sites_module" ]
 }
 
@@ -446,7 +451,10 @@
 js_library("media_picker.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/site_settings/media_picker.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":site_settings_behavior.m",
+    ":site_settings_prefs_browser_proxy.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
   extra_deps = [ ":media_picker_module" ]
 }
@@ -454,7 +462,7 @@
 js_library("pdf_documents.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/site_settings/pdf_documents.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
   extra_deps = [ ":pdf_documents_module" ]
 }
@@ -462,7 +470,13 @@
 js_library("protocol_handlers.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/site_settings/protocol_handlers.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":site_settings_behavior.m",
+
+    # Must be included on all platforms to satisfy closure compiler.
+    ":android_info_browser_proxy.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/cr_elements/cr_action_menu:cr_action_menu.m",
+    "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
   extra_deps = [ ":protocol_handlers_module" ]
 }
@@ -470,7 +484,20 @@
 js_library("site_data.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/site_settings/site_data.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":cookie_info.m",
+    ":local_data_browser_proxy.m",
+    ":site_data_entry.m",
+    ":site_settings_behavior.m",
+    "..:global_scroll_target_behavior.m",
+    "..:route.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/cr_elements/cr_search_field:cr_search_field.m",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js:cr.m",
+    "//ui/webui/resources/js:i18n_behavior.m",
+    "//ui/webui/resources/js:list_property_update_behavior.m",
+    "//ui/webui/resources/js:web_ui_listener_behavior.m",
+    "//ui/webui/resources/js/cr/ui:focus_without_ink.m",
   ]
   extra_deps = [ ":site_data_module" ]
 }
@@ -492,7 +519,11 @@
 js_library("site_data_entry.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/site_settings/site_data_entry.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":cookie_info.m",
+    ":local_data_browser_proxy.m",
+    "//ui/webui/resources/js:i18n_behavior.m",
+    "//ui/webui/resources/js:icon.m",
+    "//ui/webui/resources/js/cr/ui:focus_row_behavior.m",
   ]
   extra_deps = [ ":site_data_entry_module" ]
 }
@@ -611,7 +642,11 @@
 js_library("zoom_levels.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/site_settings/zoom_levels.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":site_settings_behavior.m",
+    ":site_settings_prefs_browser_proxy.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:list_property_update_behavior.m",
+    "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
   extra_deps = [ ":zoom_levels_module" ]
 }
@@ -658,6 +693,16 @@
   js_file = "all_sites.js"
   html_file = "all_sites.html"
   html_type = "dom-module"
+  auto_imports = settings_auto_imports + [
+                   "chrome/browser/resources/settings/global_scroll_target_behavior.html|GlobalScrollTargetBehavior,GlobalScrollTargetBehaviorImpl",
+                   "chrome/browser/resources/settings/route.html|routes",
+                   "chrome/browser/resources/settings/router.html|Route,Router,RouteObserverBehavior",
+                   "chrome/browser/resources/settings/site_settings/constants.html|ALL_SITES_DIALOG,AllSitesAction2,ContentSetting,ContentSettingsTypes,SortMethod",
+                   "chrome/browser/resources/settings/site_settings/local_data_browser_proxy.html|LocalDataBrowserProxy,LocalDataBrowserProxyImpl",
+                   "chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.html|SiteGroup",
+                   "ui/webui/resources/html/polymer.html|afterNextRender,html,Polymer",
+                 ]
+  namespace_rewrites = settings_namespace_rewrites
 }
 
 polymer_modulizer("all_sites_icons") {
@@ -726,6 +771,8 @@
   js_file = "media_picker.js"
   html_file = "media_picker.html"
   html_type = "dom-module"
+  auto_imports = settings_auto_imports + [ "chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.html|MediaPickerEntry" ]
+  namespace_rewrites = settings_namespace_rewrites
 }
 
 polymer_modulizer("pdf_documents") {
@@ -738,12 +785,22 @@
   js_file = "protocol_handlers.js"
   html_file = "protocol_handlers.html"
   html_type = "dom-module"
+  auto_imports = settings_auto_imports + [ "chrome/browser/resources/settings/site_settings/android_info_browser_proxy.html|AndroidInfoBrowserProxyImpl,AndroidAppsInfo" ]
+  namespace_rewrites = settings_namespace_rewrites
 }
 
 polymer_modulizer("site_data") {
   js_file = "site_data.js"
   html_file = "site_data.html"
   html_type = "dom-module"
+  auto_imports = settings_auto_imports + [
+                   "chrome/browser/resources/settings/global_scroll_target_behavior.html|GlobalScrollTargetBehavior,GlobalScrollTargetBehaviorImpl",
+                   "chrome/browser/resources/settings/route.html|routes",
+                   "chrome/browser/resources/settings/router.html|Route,Router,RouteObserverBehavior",
+                   "chrome/browser/resources/settings/site_settings/local_data_browser_proxy.html|LocalDataBrowserProxy,LocalDataBrowserProxyImpl",
+                   "chrome/browser/resources/settings/site_settings/site_data_entry.html|CookieDataSummaryItem",
+                 ]
+  namespace_rewrites = settings_namespace_rewrites
 }
 
 polymer_modulizer("site_data_details_subpage") {
@@ -763,6 +820,8 @@
   js_file = "site_data_entry.js"
   html_file = "site_data_entry.html"
   html_type = "dom-module"
+  auto_imports = settings_auto_imports + [ "chrome/browser/resources/settings/site_settings/local_data_browser_proxy.html|LocalDataBrowserProxy,LocalDataBrowserProxyImpl" ]
+  namespace_rewrites = settings_namespace_rewrites
 }
 
 polymer_modulizer("site_details") {
@@ -838,6 +897,8 @@
   js_file = "zoom_levels.js"
   html_file = "zoom_levels.html"
   html_type = "dom-module"
+  auto_imports = settings_auto_imports + [ "chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.html|ZoomLevelEntry" ]
+  namespace_rewrites = settings_namespace_rewrites
 }
 
 js_modulizer("modulize") {
diff --git a/chrome/browser/resources/settings/site_settings/all_sites.html b/chrome/browser/resources/settings/site_settings/all_sites.html
index cdc959b..28797440 100644
--- a/chrome/browser/resources/settings/site_settings/all_sites.html
+++ b/chrome/browser/resources/settings/site_settings/all_sites.html
@@ -1,6 +1,8 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
 <link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
+<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
+<link rel="import" href="chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_search_field/cr_search_field.html">
 <link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
 <link rel="import" href="chrome://resources/cr_elements/md_select_css.html">
@@ -9,17 +11,21 @@
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
 <link rel="import" href="../global_scroll_target_behavior.html">
+<link rel="import" href="../i18n_setup.html">
 <link rel="import" href="../route.html">
 <link rel="import" href="../router.html">
 <link rel="import" href="../settings_shared_css.html">
 <link rel="import" href="all_sites_icons.html">
 <link rel="import" href="clear_storage_dialog_css.html">
+<link rel="import" href="constants.html">
+<link rel="import" href="local_data_browser_proxy.html">
 <link rel="import" href="site_entry.html">
 <link rel="import" href="site_settings_behavior.html">
+<link rel="import" href="site_settings_prefs_browser_proxy.html">
 
 <dom-module id="all-sites">
   <template>
-    <style include="settings-shared md-select">
+    <style include="settings-shared md-select clear-storage-dialog-shared">
       #sort {
         align-items: center;
         display: flex;
@@ -160,7 +166,6 @@
     <!-- New version of confirm clear data dialog to show if storage
          pressure UI flag is enabled. -->
     <cr-lazy-render id="confirmClearDataNew">
-      <style include="clear-storage-dialog-shared"></style>
       <template>
         <cr-dialog close-text="$i18n{close}">
           <div slot="title">
@@ -194,7 +199,6 @@
 
     <!-- Confirm clear all data dialog. -->
     <cr-lazy-render id="confirmClearAllData">
-      <style include="clear-storage-dialog-shared"></style>
       <template>
         <cr-dialog close-text="$i18n{close}">
           <div slot="title">
diff --git a/chrome/browser/resources/settings/site_settings/all_sites.js b/chrome/browser/resources/settings/site_settings/all_sites.js
index 99167e4..fc8828a 100644
--- a/chrome/browser/resources/settings/site_settings/all_sites.js
+++ b/chrome/browser/resources/settings/site_settings/all_sites.js
@@ -100,8 +100,10 @@
 
     /**
      * @private {?{
+     *   actionScope: string,
      *   index: number,
      *   item: !SiteGroup,
+     *   origin: string,
      *   path: string,
      *   target: !HTMLElement
      * }}
@@ -414,7 +416,7 @@
    * pane when its menu is opened (it is possible to open off-screen items using
    * keyboard shortcuts).
    * @param {!CustomEvent<{
-   *    index: number, item: !SiteGroup,
+   *    actionScope: string, index: number, item: !SiteGroup, origin: string,
    *    path: string, target: !HTMLElement
    *    }>} e
    * @private
diff --git a/chrome/browser/resources/settings/site_settings/android_info_browser_proxy.js b/chrome/browser/resources/settings/site_settings/android_info_browser_proxy.js
index ea5ccb2..b8ad5fb5 100644
--- a/chrome/browser/resources/settings/site_settings/android_info_browser_proxy.js
+++ b/chrome/browser/resources/settings/site_settings/android_info_browser_proxy.js
@@ -16,7 +16,7 @@
  * }}
  * @see chrome/browser/ui/webui/settings/chromeos/android_apps_handler.cc
  */
-let AndroidAppsInfo;
+/* #export */ let AndroidAppsInfo;
 
 cr.define('settings', function() {
   /**
diff --git a/chrome/browser/resources/settings/site_settings/constants.js b/chrome/browser/resources/settings/site_settings/constants.js
index e02b4d5e..c0d296d2 100644
--- a/chrome/browser/resources/settings/site_settings/constants.js
+++ b/chrome/browser/resources/settings/site_settings/constants.js
@@ -162,7 +162,7 @@
    * used for logging userActions.
    * @enum {string}
    */
-  const ALL_SITES_DIALOG = {
+  /* #export */ const ALL_SITES_DIALOG = {
     CLEAR_DATA: 'ClearData',
     RESET_PERMISSIONS: 'ResetPermissions',
   };
diff --git a/chrome/browser/resources/settings/site_settings/media_picker.html b/chrome/browser/resources/settings/site_settings/media_picker.html
index 92f8c15f..7b28cc4 100644
--- a/chrome/browser/resources/settings/site_settings/media_picker.html
+++ b/chrome/browser/resources/settings/site_settings/media_picker.html
@@ -1,8 +1,11 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
 <link rel="import" href="chrome://resources/cr_elements/md_select_css.html">
+<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
 <link rel="import" href="../settings_shared_css.html">
 <link rel="import" href="../settings_vars_css.html">
+<link rel="import" href="site_settings_behavior.html">
+<link rel="import" href="site_settings_prefs_browser_proxy.html">
 
 <dom-module id="media-picker">
   <template>
diff --git a/chrome/browser/resources/settings/site_settings/protocol_handlers.js b/chrome/browser/resources/settings/site_settings/protocol_handlers.js
index a18960f5..8664248 100644
--- a/chrome/browser/resources/settings/site_settings/protocol_handlers.js
+++ b/chrome/browser/resources/settings/site_settings/protocol_handlers.js
@@ -90,7 +90,7 @@
   // <if expr="chromeos">
   /** @override */
   attached() {
-    cr.addWebUIListener(
+    this.addWebUIListener(
         'android-apps-info-update', this.androidAppsInfoUpdate_.bind(this));
     settings.AndroidInfoBrowserProxyImpl.getInstance().requestAndroidAppsInfo();
   },
diff --git a/chrome/browser/resources/settings/site_settings/site_data.html b/chrome/browser/resources/settings/site_settings/site_data.html
index 3ae3aef..2d9c02e 100644
--- a/chrome/browser/resources/settings/site_settings/site_data.html
+++ b/chrome/browser/resources/settings/site_settings/site_data.html
@@ -13,6 +13,7 @@
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
 <link rel="import" href="../global_scroll_target_behavior.html">
+<link rel="import" href="../i18n_setup.html">
 <link rel="import" href="../route.html">
 <link rel="import" href="../router.html">
 <link rel="import" href="../settings_shared_css.html">
diff --git a/chrome/browser/resources/settings/site_settings/site_data.js b/chrome/browser/resources/settings/site_settings/site_data.js
index 370ff04..66cd119 100644
--- a/chrome/browser/resources/settings/site_settings/site_data.js
+++ b/chrome/browser/resources/settings/site_settings/site_data.js
@@ -9,15 +9,6 @@
 
 /**
  * @typedef {{
- *   site: string,
- *   id: string,
- *   localData: string,
- * }}
- */
-let CookieDataSummaryItem;
-
-/**
- * @typedef {{
  *   id: string,
  *   start: number,
  *   count: number,
diff --git a/chrome/browser/resources/settings/site_settings/site_data_entry.html b/chrome/browser/resources/settings/site_settings/site_data_entry.html
index 5585d9a..9914bb27 100644
--- a/chrome/browser/resources/settings/site_settings/site_data_entry.html
+++ b/chrome/browser/resources/settings/site_settings/site_data_entry.html
@@ -8,6 +8,7 @@
 <link rel="import" href="../settings_shared_css.html">
 <link rel="import" href="../site_favicon.html">
 <link rel="import" href="cookie_info.html">
+<link rel="import" href="local_data_browser_proxy.html">
 
 <dom-module id="site-data-entry">
   <template>
diff --git a/chrome/browser/resources/settings/site_settings/site_data_entry.js b/chrome/browser/resources/settings/site_settings/site_data_entry.js
index 6936608..ae0f5d2d 100644
--- a/chrome/browser/resources/settings/site_settings/site_data_entry.js
+++ b/chrome/browser/resources/settings/site_settings/site_data_entry.js
@@ -7,6 +7,15 @@
  * 'site-data-entry' handles showing the local storage summary for a site.
  */
 
+/**
+ * @typedef {{
+ *   site: string,
+ *   id: string,
+ *   localData: string,
+ * }}
+ */
+/* #export */ let CookieDataSummaryItem;
+
 Polymer({
   is: 'site-data-entry',
 
diff --git a/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.js b/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.js
index 5e722b8..500baa7 100644
--- a/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.js
+++ b/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.js
@@ -151,7 +151,7 @@
  * @typedef {{name: string,
  *            id: string}}
  */
-let MediaPickerEntry;
+/* #export */ let MediaPickerEntry;
 
 /**
  * @typedef {{protocol: string,
@@ -165,7 +165,7 @@
  *            source: string,
  *            zoom: string}}
  */
-let ZoomLevelEntry;
+/* #export */ let ZoomLevelEntry;
 
 cr.define('settings', function() {
   /** @interface */
diff --git a/chrome/browser/safe_browsing/BUILD.gn b/chrome/browser/safe_browsing/BUILD.gn
index 2c05516..8fed398 100644
--- a/chrome/browser/safe_browsing/BUILD.gn
+++ b/chrome/browser/safe_browsing/BUILD.gn
@@ -96,6 +96,8 @@
       "trigger_creator.h",
       "ui_manager.cc",
       "ui_manager.h",
+      "user_interaction_observer.cc",
+      "user_interaction_observer.h",
     ]
     deps += [
       ":url_lookup_service_factory",
diff --git a/chrome/browser/safe_browsing/certificate_reporting_service_unittest.cc b/chrome/browser/safe_browsing/certificate_reporting_service_unittest.cc
index 2b0517be8..00f2964 100644
--- a/chrome/browser/safe_browsing/certificate_reporting_service_unittest.cc
+++ b/chrome/browser/safe_browsing/certificate_reporting_service_unittest.cc
@@ -397,6 +397,7 @@
 
     service_->Shutdown();
     service_.reset(nullptr);
+    safe_browsing::SafeBrowsingService::RegisterFactory(nullptr);
 
     histogram_test_helper_.CheckHistogram();
     event_histogram_tester_.reset();
diff --git a/chrome/browser/safe_browsing/download_protection/deep_scanning_browsertest.cc b/chrome/browser/safe_browsing/download_protection/deep_scanning_browsertest.cc
new file mode 100644
index 0000000..59709d5
--- /dev/null
+++ b/chrome/browser/safe_browsing/download_protection/deep_scanning_browsertest.cc
@@ -0,0 +1,320 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+
+#include "base/base64.h"
+#include "base/path_service.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/safe_browsing/cloud_content_scanning/binary_fcm_service.h"
+#include "chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h"
+#include "chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_factory.h"
+#include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_browsertest_base.h"
+#include "chrome/browser/safe_browsing/dm_token_utils.h"
+#include "chrome/browser/safe_browsing/download_protection/ppapi_download_request.h"
+#include "chrome/browser/safe_browsing/test_safe_browsing_service.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/autofill/core/common/mojom/autofill_types.mojom-shared.h"
+#include "components/download/public/common/download_danger_type.h"
+#include "components/prefs/pref_service.h"
+#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
+#include "components/safe_browsing/core/db/test_database_manager.h"
+#include "components/safe_browsing/core/features.h"
+#include "components/safe_browsing/core/proto/csd.pb.h"
+#include "components/safe_browsing/core/proto/webprotect.pb.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/download_manager.h"
+#include "content/public/test/download_test_observer.h"
+#include "content/public/test/test_utils.h"
+#include "services/network/test/test_utils.h"
+
+namespace safe_browsing {
+
+namespace {
+
+// Extract the metadata proto from the raw request string. Returns true on
+// success.
+bool GetUploadMetadata(const std::string& upload_request,
+                       DeepScanningClientRequest* out_proto) {
+  // The request is of the following format, see multipart_uploader.h for
+  // details:
+  // ---MultipartBoundary---
+  // <Headers for metadata>
+  //
+  // <Base64-encoded metadata>
+  // ---MultipartBoundary---
+  // <Headers for uploaded data>
+  //
+  // <Uploaded data>
+  // ---MultipartBoundary---
+  size_t boundary_end = upload_request.find("\r\n");
+  std::string multipart_boundary = upload_request.substr(0, boundary_end);
+
+  size_t headers_end = upload_request.find("\r\n\r\n");
+  size_t metadata_end =
+      upload_request.find("\r\n" + multipart_boundary, headers_end);
+  std::string encoded_metadata =
+      upload_request.substr(headers_end + 4, metadata_end - headers_end - 4);
+
+  std::string serialized_metadata;
+  base::Base64Decode(encoded_metadata, &serialized_metadata);
+  DeepScanningClientRequest metadata_proto;
+  return out_proto->ParseFromString(serialized_metadata);
+}
+
+}  // namespace
+
+class FakeBinaryFCMService : public BinaryFCMService {
+ public:
+  FakeBinaryFCMService() {}
+
+  void GetInstanceID(GetInstanceIDCallback callback) override {
+    std::move(callback).Run("test_instance_id");
+  }
+
+  void UnregisterInstanceID(const std::string& token,
+                            UnregisterInstanceIDCallback callback) override {
+    // Always successfully unregister.
+    std::move(callback).Run(true);
+  }
+};
+
+// Integration tests for download deep scanning behavior, only mocking network
+// traffic and FCM dependencies.
+class DownloadDeepScanningBrowserTest
+    : public DeepScanningBrowserTestBase,
+      public content::DownloadManager::Observer,
+      public download::DownloadItem::Observer {
+ public:
+  DownloadDeepScanningBrowserTest() {}
+
+  void OnDownloadCreated(content::DownloadManager* manager,
+                         download::DownloadItem* item) override {
+    item->AddObserver(this);
+    download_items_.insert(item);
+  }
+
+  void OnDownloadDestroyed(download::DownloadItem* item) override {
+    download_items_.erase(item);
+  }
+
+ protected:
+  void SetUp() override {
+    test_sb_factory_ = std::make_unique<TestSafeBrowsingServiceFactory>();
+    test_sb_factory_->UseV4LocalDatabaseManager();
+    SafeBrowsingService::RegisterFactory(test_sb_factory_.get());
+
+    InProcessBrowserTest::SetUp();
+  }
+
+  void TearDown() override {
+    InProcessBrowserTest::TearDown();
+
+    SafeBrowsingService::RegisterFactory(nullptr);
+  }
+
+  void WaitForDownloadToFinish() {
+    content::DownloadManager* download_manager =
+        content::BrowserContext::GetDownloadManager(browser()->profile());
+    content::DownloadTestObserverTerminal observer(
+        download_manager, 1,
+        content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_IGNORE);
+    observer.WaitForFinished();
+  }
+
+  void WaitForDeepScanRequest(bool is_advanced_protection) {
+    if (is_advanced_protection)
+      waiting_for_app_ = true;
+    else
+      waiting_for_enterprise_ = true;
+
+    base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
+    waiting_for_upload_closure_ = run_loop.QuitClosure();
+    run_loop.Run();
+
+    waiting_for_app_ = false;
+    waiting_for_enterprise_ = false;
+  }
+
+  void ExpectMetadataResponse(const ClientDownloadResponse& response) {
+    test_sb_factory_->test_safe_browsing_service()
+        ->GetTestUrlLoaderFactory()
+        ->AddResponse(PPAPIDownloadRequest::GetDownloadRequestUrl().spec(),
+                      response.SerializeAsString());
+  }
+
+  void ExpectDeepScanSynchronousResponse(
+      bool is_advanced_protection,
+      const DeepScanningClientResponse& response) {
+    test_sb_factory_->test_safe_browsing_service()
+        ->GetTestUrlLoaderFactory()
+        ->AddResponse(
+            BinaryUploadService::GetUploadUrl(is_advanced_protection).spec(),
+            response.SerializeAsString());
+  }
+
+  base::FilePath GetTestDataDirectory() {
+    base::FilePath test_file_directory;
+    base::PathService::Get(chrome::DIR_TEST_DATA, &test_file_directory);
+    return test_file_directory;
+  }
+
+  FakeBinaryFCMService* binary_fcm_service() { return binary_fcm_service_; }
+
+  TestSafeBrowsingServiceFactory* test_sb_factory() {
+    return test_sb_factory_.get();
+  }
+
+  const DeepScanningClientRequest& last_app_request() {
+    return last_app_request_;
+  }
+
+  const DeepScanningClientRequest& last_enterprise_request() {
+    return last_enterprise_request_;
+  }
+
+  const base::flat_set<download::DownloadItem*>& download_items() {
+    return download_items_;
+  }
+
+  void SetBinaryUploadServiceTestFactory() {
+    BinaryUploadServiceFactory::GetInstance()->SetTestingFactory(
+        browser()->profile(),
+        base::BindRepeating(
+            &DownloadDeepScanningBrowserTest::CreateBinaryUploadService,
+            base::Unretained(this)));
+  }
+
+  void ObserveDownloadManager() {
+    content::DownloadManager* download_manager =
+        content::BrowserContext::GetDownloadManager(browser()->profile());
+    download_manager->AddObserver(this);
+  }
+
+  void SetUrlLoaderInterceptor() {
+    test_sb_factory()->test_safe_browsing_service()->SetUseTestUrlLoaderFactory(
+        true);
+    test_sb_factory()
+        ->test_safe_browsing_service()
+        ->GetTestUrlLoaderFactory()
+        ->SetInterceptor(base::BindRepeating(
+            &DownloadDeepScanningBrowserTest::InterceptRequest,
+            base::Unretained(this)));
+  }
+
+  void SendFcmMessage(const DeepScanningClientResponse& response) {
+    std::string encoded_proto;
+    base::Base64Encode(response.SerializeAsString(), &encoded_proto);
+    gcm::IncomingMessage gcm_message;
+    gcm_message.data["proto"] = encoded_proto;
+    binary_fcm_service()->OnMessage("app_id", gcm_message);
+  }
+
+  void AuthorizeForDeepScanning() {
+    BinaryUploadServiceFactory::GetForProfile(browser()->profile())
+        ->SetAuthForTesting(/*authorized=*/true);
+  }
+
+ private:
+  std::unique_ptr<KeyedService> CreateBinaryUploadService(
+      content::BrowserContext* browser_context) {
+    std::unique_ptr<FakeBinaryFCMService> binary_fcm_service =
+        std::make_unique<FakeBinaryFCMService>();
+    binary_fcm_service_ = binary_fcm_service.get();
+    Profile* profile = Profile::FromBrowserContext(browser_context);
+    return std::make_unique<BinaryUploadService>(
+        g_browser_process->safe_browsing_service()->GetURLLoaderFactory(),
+        profile, std::move(binary_fcm_service));
+  }
+
+  void InterceptRequest(const network::ResourceRequest& request) {
+    if (request.url ==
+        BinaryUploadService::GetUploadUrl(/*is_advanced_protection=*/true)) {
+      ASSERT_TRUE(GetUploadMetadata(network::GetUploadData(request),
+                                    &last_app_request_));
+      if (waiting_for_app_)
+        std::move(waiting_for_upload_closure_).Run();
+    }
+
+    if (request.url ==
+        BinaryUploadService::GetUploadUrl(/*is_advanced_protection=*/false)) {
+      ASSERT_TRUE(GetUploadMetadata(network::GetUploadData(request),
+                                    &last_enterprise_request_));
+      if (waiting_for_enterprise_)
+        std::move(waiting_for_upload_closure_).Run();
+    }
+  }
+
+  std::unique_ptr<TestSafeBrowsingServiceFactory> test_sb_factory_;
+  FakeBinaryFCMService* binary_fcm_service_;
+
+  bool waiting_for_app_;
+  DeepScanningClientRequest last_app_request_;
+
+  bool waiting_for_enterprise_;
+  DeepScanningClientRequest last_enterprise_request_;
+
+  base::OnceClosure waiting_for_upload_closure_;
+
+  base::flat_set<download::DownloadItem*> download_items_;
+};
+
+IN_PROC_BROWSER_TEST_F(DownloadDeepScanningBrowserTest,
+                       SafeDownloadHasCorrectDangerType) {
+  embedded_test_server()->ServeFilesFromDirectory(GetTestDataDirectory());
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  SetBinaryUploadServiceTestFactory();
+  SetUrlLoaderInterceptor();
+  ObserveDownloadManager();
+  AuthorizeForDeepScanning();
+
+  SetDMTokenForTesting(policy::DMToken::CreateValidTokenForTesting("dm_token"));
+  SetDlpPolicy(CheckContentComplianceValues::CHECK_DOWNLOADS);
+  SetMalwarePolicy(SendFilesForMalwareCheckValues::SEND_DOWNLOADS);
+
+  // The file is SAFE according to the metadata check
+  ClientDownloadResponse metadata_response;
+  metadata_response.set_verdict(ClientDownloadResponse::SAFE);
+  ExpectMetadataResponse(metadata_response);
+
+  // The DLP scan runs synchronously, but doesn't find anything.
+  DeepScanningClientResponse sync_response;
+  sync_response.mutable_dlp_scan_verdict()->set_status(
+      DlpDeepScanningVerdict::SUCCESS);
+  ExpectDeepScanSynchronousResponse(/*is_advanced_protection=*/false,
+                                    sync_response);
+
+  GURL url = embedded_test_server()->GetURL(
+      "/safe_browsing/download_protection/zipfile_two_archives.zip");
+  ui_test_utils::NavigateToURLWithDisposition(
+      browser(), url, WindowOpenDisposition::CURRENT_TAB,
+      ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
+
+  WaitForDeepScanRequest(/*is_advanced_protection=*/false);
+
+  // The malware scan finishes asynchronously, and doesn't find anything.
+  DeepScanningClientResponse async_response;
+  async_response.set_token(last_enterprise_request().request_token());
+  async_response.mutable_malware_scan_verdict()->set_verdict(
+      MalwareDeepScanningVerdict::CLEAN);
+  SendFcmMessage(async_response);
+
+  WaitForDownloadToFinish();
+
+  // The file should be deep scanned, and safe.
+  ASSERT_EQ(download_items().size(), 1u);
+  download::DownloadItem* item = *download_items().begin();
+  EXPECT_EQ(
+      item->GetDangerType(),
+      download::DownloadDangerType::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_SAFE);
+  EXPECT_EQ(item->GetState(), download::DownloadItem::COMPLETE);
+}
+
+}  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc b/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc
index 415a708..548e8ac 100644
--- a/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc
@@ -70,9 +70,11 @@
 #include "content/public/browser/notification_types.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
 #include "content/public/browser/security_style_explanations.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test_utils.h"
+#include "content/public/test/render_view_test.h"
 #include "content/public/test/test_navigation_observer.h"
 #include "content/public/test/test_utils.h"
 #include "net/cert/cert_verify_result.h"
@@ -95,7 +97,6 @@
 namespace safe_browsing {
 
 namespace {
-
 const char kEmptyPage[] = "/empty.html";
 const char kHTTPSPage[] = "/ssl/google.html";
 const char kMaliciousPage[] = "/safe_browsing/malware.html";
@@ -106,8 +107,115 @@
 const char kMaliciousIframe[] = "/safe_browsing/malware_iframe.html";
 const char kUnrelatedUrl[] = "https://www.google.com";
 
+}  // namespace
+
+enum Visibility { VISIBILITY_ERROR = -1, HIDDEN = 0, VISIBLE = 1 };
+
 bool AreCommittedInterstitialsEnabled() {
-  return base::FeatureList::IsEnabled(kCommittedSBInterstitials);
+  return base::FeatureList::IsEnabled(safe_browsing::kCommittedSBInterstitials);
+}
+
+bool IsShowingInterstitial(WebContents* contents) {
+  if (AreCommittedInterstitialsEnabled()) {
+    security_interstitials::SecurityInterstitialTabHelper* helper =
+        security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
+            contents);
+    return helper &&
+           (helper
+                ->GetBlockingPageForCurrentlyCommittedNavigationForTesting() !=
+            nullptr);
+  }
+  return InterstitialPage::GetInterstitialPage(contents) != nullptr;
+}
+
+content::RenderFrameHost* GetRenderFrameHost(Browser* browser) {
+  if (AreCommittedInterstitialsEnabled()) {
+    return browser->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
+  }
+  InterstitialPage* interstitial = InterstitialPage::GetInterstitialPage(
+      browser->tab_strip_model()->GetActiveWebContents());
+  return interstitial ? interstitial->GetMainFrame() : nullptr;
+}
+
+bool WaitForReady(Browser* browser) {
+  WebContents* contents = browser->tab_strip_model()->GetActiveWebContents();
+  if (AreCommittedInterstitialsEnabled()) {
+    if (!content::WaitForRenderFrameReady(contents->GetMainFrame())) {
+      return false;
+    }
+    return IsShowingInterstitial(contents);
+  }
+  content::WaitForInterstitialAttach(contents);
+  InterstitialPage* interstitial = contents->GetInterstitialPage();
+  if (!interstitial) {
+    return false;
+  }
+  return content::WaitForRenderFrameReady(interstitial->GetMainFrame());
+}
+
+Visibility GetVisibility(Browser* browser, const std::string& node_id) {
+  content::RenderFrameHost* rfh = GetRenderFrameHost(browser);
+  if (!rfh)
+    return VISIBILITY_ERROR;
+
+  // clang-format off
+  std::string jsFindVisibility = R"(
+    (function isNodeVisible(node) {
+      if (!node) return 'node not found';
+      if (node.offsetWidth === 0 || node.offsetHeight === 0) return false;
+      // Do not check opacity, since the css transition may actually leave
+      // opacity at 0 after it's been unhidden
+      if (node.classList.contains('hidden')) return false;
+      // Otherwise, we must check all parent nodes
+      var parentVisibility = isNodeVisible(node.parentElement);
+      if (parentVisibility === 'node not found') {
+        return true; // none of the parents are set invisible
+      }
+      return parentVisibility;
+    }(document.getElementById(')" + node_id + R"(')));)";
+  // clang-format on
+
+  base::Value value = content::ExecuteScriptAndGetValue(rfh, jsFindVisibility);
+
+  if (!value.is_bool())
+    return VISIBILITY_ERROR;
+
+  return value.GetBool() ? VISIBLE : HIDDEN;
+}
+
+bool Click(Browser* browser, const std::string& node_id) {
+  DCHECK(node_id == "primary-button" || node_id == "proceed-link" ||
+         node_id == "whitepaper-link" || node_id == "details-button" ||
+         node_id == "opt-in-checkbox")
+      << "Unexpected node_id: " << node_id;
+  content::RenderFrameHost* rfh = GetRenderFrameHost(browser);
+  if (!rfh)
+    return false;
+  // We don't use ExecuteScriptAndGetValue for this one, since clicking
+  // the button/link may navigate away before the injected javascript can
+  // reply, hanging the test.
+  rfh->ExecuteJavaScriptForTests(
+      base::ASCIIToUTF16("document.getElementById('" + node_id +
+                         "').click();\n"),
+      base::NullCallback());
+  return true;
+}
+
+bool ClickAndWaitForDetach(Browser* browser, const std::string& node_id) {
+  // We wait for interstitial_detached rather than nav_entry_committed, as
+  // going back from a main-frame safe browsing interstitial page will not
+  // cause a nav entry committed event.
+  content::TestNavigationObserver observer(
+      browser->tab_strip_model()->GetActiveWebContents());
+  if (!Click(browser, node_id))
+    return false;
+  if (AreCommittedInterstitialsEnabled()) {
+    observer.WaitForNavigationFinished();
+    return true;
+  }
+  content::WaitForInterstitialDetach(
+      browser->tab_strip_model()->GetActiveWebContents());
+  return true;
 }
 
 // A SafeBrowsingDatabaseManager class that allows us to inject the malicious
@@ -239,8 +347,6 @@
   DISALLOW_COPY_AND_ASSIGN(FakeSafeBrowsingUIManager);
 };
 
-}  // namespace
-
 class TestThreatDetailsFactory : public ThreatDetailsFactory {
  public:
   TestThreatDetailsFactory() : details_() {}
@@ -316,6 +422,28 @@
   bool wait_for_delete_;
 };
 
+void AssertNoInterstitial(Browser* browser, bool wait_for_delete) {
+  WebContents* contents = browser->tab_strip_model()->GetActiveWebContents();
+  if (AreCommittedInterstitialsEnabled()) {
+    ASSERT_FALSE(IsShowingInterstitial(contents));
+    return;
+  }
+
+  if (contents->ShowingInterstitialPage() && wait_for_delete) {
+    // We'll get notified when the interstitial is deleted.
+    TestSafeBrowsingBlockingPage* page =
+        static_cast<TestSafeBrowsingBlockingPage*>(
+            contents->GetInterstitialPage()->GetDelegateForTesting());
+    ASSERT_EQ(SafeBrowsingBlockingPage::kTypeForTesting,
+              page->GetTypeForTesting());
+    page->WaitForDelete();
+  }
+
+  // Can't use InterstitialPage::GetInterstitialPage() because that
+  // gets updated after the TestSafeBrowsingBlockingPage destructor
+  ASSERT_FALSE(contents->ShowingInterstitialPage());
+}
+
 class TestSafeBrowsingBlockingPageFactory
     : public SafeBrowsingBlockingPageFactory {
  public:
@@ -365,8 +493,6 @@
       public testing::WithParamInterface<
           testing::tuple<SBThreatType, bool, bool>> {
  public:
-  enum Visibility { VISIBILITY_ERROR = -1, HIDDEN = 0, VISIBLE = 1 };
-
   SafeBrowsingBlockingPageBrowserTest()
       : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
     std::map<std::string, std::string> parameters = {
@@ -402,9 +528,9 @@
 
   void TearDown() override {
     InProcessBrowserTest::TearDown();
-    SafeBrowsingBlockingPage::RegisterFactory(NULL);
-    SafeBrowsingService::RegisterFactory(NULL);
-    ThreatDetails::RegisterFactory(NULL);
+    SafeBrowsingBlockingPage::RegisterFactory(nullptr);
+    SafeBrowsingService::RegisterFactory(nullptr);
+    ThreatDetails::RegisterFactory(nullptr);
   }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
@@ -570,42 +696,6 @@
     interstitial_page->CommandReceived(base::NumberToString(command));
   }
 
-  void AssertNoInterstitial(bool wait_for_delete) {
-    WebContents* contents =
-        browser()->tab_strip_model()->GetActiveWebContents();
-    if (AreCommittedInterstitialsEnabled()) {
-      ASSERT_FALSE(IsShowingInterstitial(contents));
-      return;
-    }
-
-    if (contents->ShowingInterstitialPage() && wait_for_delete) {
-      // We'll get notified when the interstitial is deleted.
-      TestSafeBrowsingBlockingPage* page =
-          static_cast<TestSafeBrowsingBlockingPage*>(
-              contents->GetInterstitialPage()->GetDelegateForTesting());
-      ASSERT_EQ(SafeBrowsingBlockingPage::kTypeForTesting,
-                page->GetTypeForTesting());
-      page->WaitForDelete();
-    }
-
-    // Can't use InterstitialPage::GetInterstitialPage() because that
-    // gets updated after the TestSafeBrowsingBlockingPage destructor
-    ASSERT_FALSE(contents->ShowingInterstitialPage());
-  }
-
-  bool IsShowingInterstitial(WebContents* contents) {
-    if (AreCommittedInterstitialsEnabled()) {
-      security_interstitials::SecurityInterstitialTabHelper* helper =
-          security_interstitials::SecurityInterstitialTabHelper::
-              FromWebContents(contents);
-      return helper &&
-             (helper
-                  ->GetBlockingPageForCurrentlyCommittedNavigationForTesting() !=
-              nullptr);
-    }
-    return InterstitialPage::GetInterstitialPage(contents) != nullptr;
-  }
-
   void SetReportSentCallback(base::OnceClosure callback) {
     static_cast<FakeSafeBrowsingUIManager*>(
         factory_.test_safe_browsing_service()->ui_manager().get())
@@ -650,94 +740,23 @@
   }
 
   content::RenderFrameHost* GetRenderFrameHost() {
-    if (AreCommittedInterstitialsEnabled()) {
-      return browser()
-          ->tab_strip_model()
-          ->GetActiveWebContents()
-          ->GetMainFrame();
-    }
-    InterstitialPage* interstitial = InterstitialPage::GetInterstitialPage(
-        browser()->tab_strip_model()->GetActiveWebContents());
-    if (!interstitial)
-      return NULL;
-    return interstitial->GetMainFrame();
-  }
-
-  bool WaitForReady(Browser* browser) {
-    WebContents* contents = browser->tab_strip_model()->GetActiveWebContents();
-    if (AreCommittedInterstitialsEnabled()) {
-      if (!content::WaitForRenderFrameReady(contents->GetMainFrame())) {
-        return false;
-      }
-      return IsShowingInterstitial(contents);
-    }
-    content::WaitForInterstitialAttach(contents);
-    InterstitialPage* interstitial = contents->GetInterstitialPage();
-    if (!interstitial)
-      return false;
-    return content::WaitForRenderFrameReady(interstitial->GetMainFrame());
+    return ::safe_browsing::GetRenderFrameHost(browser());
   }
 
   Visibility GetVisibility(const std::string& node_id) {
-    content::RenderFrameHost* rfh = GetRenderFrameHost();
-    if (!rfh)
-      return VISIBILITY_ERROR;
-
-    // clang-format off
-    std::string jsFindVisibility = R"(
-      (function isNodeVisible(node) {
-        if (!node) return 'node not found';
-        if (node.offsetWidth === 0 || node.offsetHeight === 0) return false;
-        // Do not check opacity, since the css transition may actually leave
-        // opacity at 0 after it's been unhidden
-        if (node.classList.contains('hidden')) return false;
-        // Otherwise, we must check all parent nodes
-        var parentVisibility = isNodeVisible(node.parentElement);
-        if (parentVisibility === 'node not found') {
-          return true; // none of the parents are set invisible
-        }
-        return parentVisibility;
-      }(document.getElementById(')" + node_id + R"(')));)";
-    // clang-format on
-
-    base::Value value =
-        content::ExecuteScriptAndGetValue(rfh, jsFindVisibility);
-
-    if (!value.is_bool())
-      return VISIBILITY_ERROR;
-
-    return value.GetBool() ? VISIBLE : HIDDEN;
+    return ::safe_browsing::GetVisibility(browser(), node_id);
   }
 
   bool Click(const std::string& node_id) {
-    content::RenderFrameHost* rfh = GetRenderFrameHost();
-    if (!rfh)
-      return false;
-    // We don't use ExecuteScriptAndGetValue for this one, since clicking
-    // the button/link may navigate away before the injected javascript can
-    // reply, hanging the test.
-    rfh->ExecuteJavaScriptForTests(
-        base::ASCIIToUTF16("document.getElementById('" + node_id +
-                           "').click();\n"),
-        base::NullCallback());
-    return true;
+    return ::safe_browsing::Click(browser(), node_id);
   }
 
   bool ClickAndWaitForDetach(const std::string& node_id) {
-    // We wait for interstitial_detached rather than nav_entry_committed, as
-    // going back from a main-frame safe browsing interstitial page will not
-    // cause a nav entry committed event.
-    content::TestNavigationObserver observer(
-        browser()->tab_strip_model()->GetActiveWebContents());
-    if (!Click(node_id))
-      return false;
-    if (AreCommittedInterstitialsEnabled()) {
-      observer.WaitForNavigationFinished();
-      return true;
-    }
-    content::WaitForInterstitialDetach(
-        browser()->tab_strip_model()->GetActiveWebContents());
-    return true;
+    return ::safe_browsing::ClickAndWaitForDetach(browser(), node_id);
+  }
+
+  void AssertNoInterstitial(bool wait_for_delete) {
+    return ::safe_browsing::AssertNoInterstitial(browser(), wait_for_delete);
   }
 
   void TestReportingDisabledAndDontProceed(const GURL& url) {
@@ -956,7 +975,7 @@
   EXPECT_EQ(HIDDEN, GetVisibility("error-code"));
   EXPECT_TRUE(ClickAndWaitForDetach("primary-button"));
 
-  AssertNoInterstitial(false);   // Assert the interstitial is gone
+  AssertNoInterstitial(false);          // Assert the interstitial is gone
   EXPECT_EQ(GURL(url::kAboutBlankURL),  // Back to "about:blank"
             browser()->tab_strip_model()->GetActiveWebContents()->GetURL());
 }
@@ -1849,6 +1868,101 @@
             browser()->tab_strip_model()->GetActiveWebContents()->GetURL());
 }
 
+class SafeBrowsingBlockingPageDelayedWarningBrowserTest
+    : public InProcessBrowserTest,
+      public testing::WithParamInterface<
+          testing::tuple<bool /* IsolateAllSitesForTesting */,
+                         bool /* Committed interstitials enabled */>> {
+ public:
+  SafeBrowsingBlockingPageDelayedWarningBrowserTest() {
+    if (testing::get<1>(GetParam())) {
+      scoped_feature_list_.InitWithFeatures(
+          /*enabled_features=*/{kDelayedWarnings, kCommittedSBInterstitials},
+          /*disabled_features=*/{});
+    } else {
+      scoped_feature_list_.InitWithFeatures(
+          /*enabled_features=*/{kDelayedWarnings},
+          /*disabled_features=*/{});
+    }
+  }
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    InProcessBrowserTest::SetUpCommandLine(command_line);
+    if (testing::get<0>(GetParam()))
+      content::IsolateAllSitesForTesting(command_line);
+  }
+
+  void SetUpOnMainThread() override {
+    host_resolver()->AddRule("*", "127.0.0.1");
+    content::SetupCrossSiteRedirector(embedded_test_server());
+    ASSERT_TRUE(embedded_test_server()->Start());
+  }
+
+  void CreatedBrowserMainParts(
+      content::BrowserMainParts* browser_main_parts) override {
+    // Test UI manager and test database manager should be set before
+    // the browser is started but after threads are created.
+    factory_.SetTestUIManager(new FakeSafeBrowsingUIManager());
+    factory_.SetTestDatabaseManager(new FakeSafeBrowsingDatabaseManager());
+    SafeBrowsingService::RegisterFactory(&factory_);
+    SafeBrowsingBlockingPage::RegisterFactory(&blocking_page_factory_);
+    ThreatDetails::RegisterFactory(&details_factory_);
+  }
+
+  static bool TypeAndWaitForInterstitial(Browser* browser) {
+    // Type something. An interstitial should be shown.
+    content::WebContents* contents =
+        browser->tab_strip_model()->GetActiveWebContents();
+    content::TestNavigationObserver observer(contents);
+    content::NativeWebKeyboardEvent event(
+        blink::WebKeyboardEvent::kRawKeyDown,
+        blink::WebInputEvent::kNoModifiers,
+        blink::WebInputEvent::GetStaticTimeStampForTests());
+    event.text[0] = 'a';
+    content::RenderWidgetHost* rwh = contents->GetRenderViewHost()->GetWidget();
+    rwh->ForwardKeyboardEvent(event);
+
+    if (AreCommittedInterstitialsEnabled()) {
+      observer.WaitForNavigationFinished();
+    }
+    return WaitForReady(browser);
+  }
+
+ protected:
+  TestThreatDetailsFactory details_factory_;
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+  TestSafeBrowsingServiceFactory factory_;
+  TestSafeBrowsingBlockingPageFactory blocking_page_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(SafeBrowsingBlockingPageDelayedWarningBrowserTest);
+};
+
+IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageDelayedWarningBrowserTest,
+                       DelayedWarnings) {
+  // Navigate to a phishing site.
+  ui_test_utils::NavigateToURL(browser(),
+                               GURL(kChromeUISafeBrowsingMatchPhishingUrl));
+  WaitForReady(browser());
+  AssertNoInterstitial(browser(), true);
+
+  // Type something. An interstitial should be shown.
+  EXPECT_TRUE(TypeAndWaitForInterstitial(browser()));
+
+  EXPECT_TRUE(ClickAndWaitForDetach(browser(), "primary-button"));
+  AssertNoInterstitial(browser(), false);  // Assert the interstitial is gone
+  EXPECT_EQ(GURL(url::kAboutBlankURL),     // Back to "about:blank"
+            browser()->tab_strip_model()->GetActiveWebContents()->GetURL());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    SafeBrowsingBlockingPageWithDelayedWarningsBrowserTest,
+    SafeBrowsingBlockingPageDelayedWarningBrowserTest,
+    testing::Combine(
+        testing::Values(false, true), /* IsolateAllSitesForTesting */
+        testing::Values(false, true) /* Committed interstitials enabled */));
+
 // Test that SafeBrowsingBlockingPage properly decodes IDN URLs that are
 // displayed.
 class SafeBrowsingBlockingPageIDNTest
diff --git a/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service_unittest.cc b/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service_unittest.cc
index d92c765..591d4d12 100644
--- a/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service_unittest.cc
+++ b/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service_unittest.cc
@@ -79,6 +79,7 @@
     // before the NetworkService object..
     browser_process_->safe_browsing_service()->ShutDown();
     browser_process_->SetSafeBrowsingService(nullptr);
+    safe_browsing::SafeBrowsingServiceInterface::RegisterFactory(nullptr);
     base::RunLoop().RunUntilIdle();
   }
 
diff --git a/chrome/browser/safe_browsing/test_safe_browsing_service.cc b/chrome/browser/safe_browsing/test_safe_browsing_service.cc
index 0384e23..4488c37 100644
--- a/chrome/browser/safe_browsing/test_safe_browsing_service.cc
+++ b/chrome/browser/safe_browsing/test_safe_browsing_service.cc
@@ -16,7 +16,10 @@
 namespace safe_browsing {
 
 // TestSafeBrowsingService functions:
-TestSafeBrowsingService::TestSafeBrowsingService() {
+TestSafeBrowsingService::TestSafeBrowsingService()
+    : test_shared_loader_factory_(
+          base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+              &test_url_loader_factory_)) {
 #if BUILDFLAG(FULL_SAFE_BROWSING)
   services_delegate_ = ServicesDelegate::CreateForTest(this, this);
 #endif  // BUILDFLAG(FULL_SAFE_BROWSING)
@@ -34,6 +37,17 @@
   use_v4_local_db_manager_ = true;
 }
 
+void TestSafeBrowsingService::SetUseTestUrlLoaderFactory(
+    bool use_test_url_loader_factory) {
+  use_test_url_loader_factory_ = use_test_url_loader_factory;
+}
+
+network::TestURLLoaderFactory*
+TestSafeBrowsingService::GetTestUrlLoaderFactory() {
+  DCHECK(use_test_url_loader_factory_);
+  return &test_url_loader_factory_;
+}
+
 std::unique_ptr<SafeBrowsingService::StateSubscription>
 TestSafeBrowsingService::RegisterStateCallback(
     const base::Callback<void(void)>& callback) {
@@ -132,6 +146,13 @@
   return nullptr;
 }
 
+scoped_refptr<network::SharedURLLoaderFactory>
+TestSafeBrowsingService::GetURLLoaderFactory() {
+  if (use_test_url_loader_factory_)
+    return test_shared_loader_factory_;
+  return SafeBrowsingService::GetURLLoaderFactory();
+}
+
 // TestSafeBrowsingServiceFactory functions:
 TestSafeBrowsingServiceFactory::TestSafeBrowsingServiceFactory()
     : test_safe_browsing_service_(nullptr), use_v4_local_db_manager_(false) {}
diff --git a/chrome/browser/safe_browsing/test_safe_browsing_service.h b/chrome/browser/safe_browsing/test_safe_browsing_service.h
index 0a93225..d11f9d4 100644
--- a/chrome/browser/safe_browsing/test_safe_browsing_service.h
+++ b/chrome/browser/safe_browsing/test_safe_browsing_service.h
@@ -11,6 +11,8 @@
 #include "chrome/browser/safe_browsing/ui_manager.h"
 #include "components/safe_browsing/buildflags.h"
 #include "components/safe_browsing/core/db/v4_protocol_manager_util.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_url_loader_factory.h"
 
 namespace safe_browsing {
 class SafeBrowsingDatabaseManager;
@@ -61,8 +63,15 @@
   const scoped_refptr<SafeBrowsingDatabaseManager>& database_manager()
       const override;
   void UseV4LocalDatabaseManager();
+
+  // By default, the TestSafeBrowsing service uses a regular URLLoaderFactory.
+  // This function can be used to override that behavior, exposing a
+  // TestURLLoaderFactory for mocking network traffic.
+  void SetUseTestUrlLoaderFactory(bool use_test_url_loader_factory);
+
   std::unique_ptr<SafeBrowsingService::StateSubscription> RegisterStateCallback(
       const base::Callback<void(void)>& callback) override;
+  network::TestURLLoaderFactory* GetTestUrlLoaderFactory();
 
  protected:
   // SafeBrowsingService overrides
@@ -84,11 +93,16 @@
   IncidentReportingService* CreateIncidentReportingService() override;
   ResourceRequestDetector* CreateResourceRequestDetector() override;
 
+  scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory() override;
+
  private:
   std::unique_ptr<V4ProtocolConfig> v4_protocol_config_;
   std::string serialized_download_report_;
   scoped_refptr<SafeBrowsingDatabaseManager> test_database_manager_;
   bool use_v4_local_db_manager_ = false;
+  bool use_test_url_loader_factory_ = false;
+  network::TestURLLoaderFactory test_url_loader_factory_;
+  scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(TestSafeBrowsingService);
 };
diff --git a/chrome/browser/safe_browsing/ui_manager.cc b/chrome/browser/safe_browsing/ui_manager.cc
index 746025c..87d9e0d4 100644
--- a/chrome/browser/safe_browsing/ui_manager.cc
+++ b/chrome/browser/safe_browsing/ui_manager.cc
@@ -8,12 +8,14 @@
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/task/post_task.h"
 #include "base/threading/thread.h"
 #include "base/threading/thread_restrictions.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/history/history_service_factory.h"
 #include "chrome/browser/interstitials/enterprise_util.h"
 #include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
+#include "chrome/browser/prerender/prerender_contents.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/safe_browsing/safe_browsing_blocking_page.h"
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
@@ -24,10 +26,12 @@
 #include "components/prefs/pref_service.h"
 #include "components/safe_browsing/content/browser/threat_details.h"
 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
+#include "components/safe_browsing/core/features.h"
 #include "components/safe_browsing/core/ping_manager.h"
 #include "components/security_interstitials/content/security_interstitial_tab_helper.h"
 #include "components/security_interstitials/content/unsafe_resource_util.h"
 #include "components/security_interstitials/core/unsafe_resource.h"
+#include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/notification_service.h"
@@ -105,6 +109,44 @@
     observer.OnSafeBrowsingHit(resource);
 }
 
+// static
+void SafeBrowsingUIManager::StartDisplayingBlockingPage(
+    scoped_refptr<SafeBrowsingUIManager> ui_manager,
+    const security_interstitials::UnsafeResource& resource) {
+  content::WebContents* web_contents = resource.web_contents_getter.Run();
+  prerender::PrerenderContents* prerender_contents =
+      web_contents ? prerender::PrerenderContents::FromWebContents(web_contents)
+                   : nullptr;
+  if (!web_contents || prerender_contents) {
+    if (prerender_contents) {
+      prerender_contents->Destroy(prerender::FINAL_STATUS_SAFE_BROWSING);
+    }
+    // Tab is gone or it's being prerendered.
+    base::PostTask(FROM_HERE, {content::BrowserThread::IO},
+                   base::BindOnce(resource.callback, false /*proceed*/,
+                                  false /*showed_interstitial*/));
+    return;
+  }
+
+  // With committed interstitials, if this is a main frame load, we need to
+  // get the navigation URL and referrer URL from the navigation entry now,
+  // since they are required for threat reporting, and the entry will be
+  // destroyed once the request is failed.
+  if (base::FeatureList::IsEnabled(kCommittedSBInterstitials) &&
+      resource.IsMainPageLoadBlocked()) {
+    content::NavigationEntry* entry =
+        web_contents->GetController().GetPendingEntry();
+    if (entry) {
+      security_interstitials::UnsafeResource resource_copy(resource);
+      resource_copy.navigation_url = entry->GetURL();
+      resource_copy.referrer_url = entry->GetReferrer().url;
+      ui_manager->DisplayBlockingPage(resource_copy);
+      return;
+    }
+  }
+  ui_manager->DisplayBlockingPage(resource);
+}
+
 void SafeBrowsingUIManager::ShowBlockingPageForResource(
     const UnsafeResource& resource) {
   SafeBrowsingBlockingPage::ShowBlockingPage(this, resource);
diff --git a/chrome/browser/safe_browsing/ui_manager.h b/chrome/browser/safe_browsing/ui_manager.h
index 862415c9..43da1b3 100644
--- a/chrome/browser/safe_browsing/ui_manager.h
+++ b/chrome/browser/safe_browsing/ui_manager.h
@@ -59,6 +59,13 @@
   explicit SafeBrowsingUIManager(
       const scoped_refptr<SafeBrowsingService>& service);
 
+  // Displays a SafeBrowsing interstitial.
+  // |ui_manager| is the manager which eventually displays the blocking page.
+  // |resource| is the unsafe resource for which the warning is displayed.
+  static void StartDisplayingBlockingPage(
+      scoped_refptr<SafeBrowsingUIManager> ui_manager,
+      const UnsafeResource& resource);
+
   // Called to stop or shutdown operations on the UI thread. This may be called
   // multiple times during the life of the UIManager. Should be called
   // on UI thread. If shutdown is true, the manager is disabled permanently.
diff --git a/chrome/browser/safe_browsing/url_checker_delegate_impl.cc b/chrome/browser/safe_browsing/url_checker_delegate_impl.cc
index b038cea0..eb71847 100644
--- a/chrome/browser/safe_browsing/url_checker_delegate_impl.cc
+++ b/chrome/browser/safe_browsing/url_checker_delegate_impl.cc
@@ -13,6 +13,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_io_data.h"
 #include "chrome/browser/safe_browsing/ui_manager.h"
+#include "chrome/browser/safe_browsing/user_interaction_observer.h"
 #include "components/safe_browsing/buildflags.h"
 #include "components/safe_browsing/content/triggers/suspicious_site_trigger.h"
 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
@@ -40,41 +41,13 @@
   }
 }
 
-void StartDisplayingBlockingPage(
-    scoped_refptr<SafeBrowsingUIManager> ui_manager,
-    const security_interstitials::UnsafeResource& resource) {
-  content::WebContents* web_contents = resource.web_contents_getter.Run();
-  if (web_contents) {
-    prerender::PrerenderContents* prerender_contents =
-        prerender::PrerenderContents::FromWebContents(web_contents);
-    if (prerender_contents) {
-      prerender_contents->Destroy(prerender::FINAL_STATUS_SAFE_BROWSING);
-    } else {
-      // With committed interstitials, if this is a main frame load, we need to
-      // get the navigation URL and referrer URL from the navigation entry now,
-      // since they are required for threat reporting, and the entry will be
-      // destroyed once the request is failed.
-      if (base::FeatureList::IsEnabled(kCommittedSBInterstitials) &&
-          resource.IsMainPageLoadBlocked()) {
-        content::NavigationEntry* entry =
-            web_contents->GetController().GetPendingEntry();
-        if (entry) {
-          security_interstitials::UnsafeResource resource_copy(resource);
-          resource_copy.navigation_url = entry->GetURL();
-          resource_copy.referrer_url = entry->GetReferrer().url;
-          ui_manager->DisplayBlockingPage(resource_copy);
-          return;
-        }
-      }
-      ui_manager->DisplayBlockingPage(resource);
-      return;
-    }
-  }
-
-  // Tab is gone or it's being prerendered.
-  base::PostTask(FROM_HERE, {content::BrowserThread::IO},
-                 base::BindOnce(resource.callback, false /*proceed*/,
-                                false /*showed_interstitial*/));
+void CreateSafeBrowsingUserInteractionObserver(
+    const content::WebContents::Getter& web_contents_getter,
+    const security_interstitials::UnsafeResource& resource,
+    bool is_main_frame,
+    scoped_refptr<SafeBrowsingUIManager> ui_manager) {
+  SafeBrowsingUserInteractionObserver::CreateForWebContents(
+      web_contents_getter.Run(), resource, is_main_frame, ui_manager);
 }
 
 }  // namespace
@@ -114,7 +87,19 @@
     bool has_user_gesture) {
   base::PostTask(
       FROM_HERE, {content::BrowserThread::UI},
-      base::BindOnce(&StartDisplayingBlockingPage, ui_manager_, resource));
+      base::BindOnce(&SafeBrowsingUIManager::StartDisplayingBlockingPage,
+                     ui_manager_, resource));
+}
+
+// Starts displaying the SafeBrowsing interstitial page.
+void UrlCheckerDelegateImpl::
+    StartObservingInteractionsForDelayedBlockingPageHelper(
+        const security_interstitials::UnsafeResource& resource,
+        bool is_main_frame) {
+  base::PostTask(FROM_HERE, {content::BrowserThread::UI},
+                 base::BindOnce(&CreateSafeBrowsingUserInteractionObserver,
+                                resource.web_contents_getter, resource,
+                                is_main_frame, ui_manager_));
 }
 
 bool UrlCheckerDelegateImpl::IsUrlWhitelisted(const GURL& url) {
diff --git a/chrome/browser/safe_browsing/url_checker_delegate_impl.h b/chrome/browser/safe_browsing/url_checker_delegate_impl.h
index 0ab1467..eef5815 100644
--- a/chrome/browser/safe_browsing/url_checker_delegate_impl.h
+++ b/chrome/browser/safe_browsing/url_checker_delegate_impl.h
@@ -33,6 +33,10 @@
       const net::HttpRequestHeaders& headers,
       bool is_main_frame,
       bool has_user_gesture) override;
+  void StartObservingInteractionsForDelayedBlockingPageHelper(
+      const security_interstitials::UnsafeResource& resource,
+      bool is_main_frame) override;
+
   bool IsUrlWhitelisted(const GURL& url) override;
   bool ShouldSkipRequestCheck(const GURL& original_url,
                               int frame_tree_node_id,
diff --git a/chrome/browser/safe_browsing/user_interaction_observer.cc b/chrome/browser/safe_browsing/user_interaction_observer.cc
new file mode 100644
index 0000000..249349f5
--- /dev/null
+++ b/chrome/browser/safe_browsing/user_interaction_observer.cc
@@ -0,0 +1,103 @@
+// Copyright 2020 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/safe_browsing/user_interaction_observer.h"
+
+#include <string>
+
+#include "components/safe_browsing/core/features.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/web_contents.h"
+
+namespace {
+const char kWebContentsUserDataKey[] =
+    "web_contents_safe_browsing_user_interaction_observer";
+}
+
+namespace safe_browsing {
+
+SafeBrowsingUserInteractionObserver::SafeBrowsingUserInteractionObserver(
+    content::WebContents* web_contents,
+    const security_interstitials::UnsafeResource& resource,
+    bool is_main_frame,
+    scoped_refptr<SafeBrowsingUIManager> ui_manager)
+    : content::WebContentsObserver(web_contents),
+      web_contents_(web_contents),
+      resource_(resource),
+      ui_manager_(ui_manager) {
+  DCHECK(base::FeatureList::IsEnabled(kDelayedWarnings));
+  key_press_callback_ =
+      base::BindRepeating(&SafeBrowsingUserInteractionObserver::HandleKeyPress,
+                          base::Unretained(this));
+  // Pass a callback to the render widget host instead of implementing
+  // WebContentsObserver::DidGetUserInteraction(). The reason for this is that
+  // render widget host handles keyboard events earlier and the callback can
+  // indicate that it wants the key press to be ignored.
+  // (DidGetUserInteraction() can only observe and not cancel the event.)
+  web_contents->GetRenderViewHost()->GetWidget()->AddKeyPressEventCallback(
+      key_press_callback_);
+}
+
+SafeBrowsingUserInteractionObserver::~SafeBrowsingUserInteractionObserver() {
+  web_contents_->GetRenderViewHost()->GetWidget()->RemoveKeyPressEventCallback(
+      key_press_callback_);
+}
+
+// static
+void SafeBrowsingUserInteractionObserver::CreateForWebContents(
+    content::WebContents* web_contents,
+    const security_interstitials::UnsafeResource& resource,
+    bool is_main_frame,
+    scoped_refptr<SafeBrowsingUIManager> ui_manager) {
+  DCHECK(!FromWebContents(web_contents));
+  auto observer = std::make_unique<SafeBrowsingUserInteractionObserver>(
+      web_contents, resource, is_main_frame, ui_manager);
+  web_contents->SetUserData(kWebContentsUserDataKey, std::move(observer));
+}
+
+// static
+SafeBrowsingUserInteractionObserver*
+SafeBrowsingUserInteractionObserver::FromWebContents(
+    content::WebContents* web_contents) {
+  return static_cast<SafeBrowsingUserInteractionObserver*>(
+      web_contents->GetUserData(kWebContentsUserDataKey));
+}
+
+void SafeBrowsingUserInteractionObserver::RenderViewHostChanged(
+    content::RenderViewHost* old_host,
+    content::RenderViewHost* new_host) {
+  old_host->GetWidget()->RemoveKeyPressEventCallback(key_press_callback_);
+  new_host->GetWidget()->AddKeyPressEventCallback(key_press_callback_);
+}
+
+void SafeBrowsingUserInteractionObserver::WebContentsDestroyed() {
+  CleanUp();
+}
+
+void SafeBrowsingUserInteractionObserver::DidStartNavigation(
+    content::NavigationHandle* handle) {
+  // Ignore subframe navigations and same document navigations. These don't
+  // show full page interstitials.
+  if (!handle->IsInMainFrame() || handle->IsSameDocument()) {
+    return;
+  }
+  web_contents()->RemoveUserData(kWebContentsUserDataKey);
+}
+
+bool SafeBrowsingUserInteractionObserver::HandleKeyPress(
+    const content::NativeWebKeyboardEvent& event) {
+  CleanUp();
+  // Show the interstitial.
+  SafeBrowsingUIManager::StartDisplayingBlockingPage(ui_manager_, resource_);
+  // DO NOT add code past this point. |this| is destroyed.
+  return true;
+}
+
+void SafeBrowsingUserInteractionObserver::CleanUp() {
+  web_contents_->GetRenderViewHost()->GetWidget()->RemoveKeyPressEventCallback(
+      key_press_callback_);
+}
+
+}  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/user_interaction_observer.h b/chrome/browser/safe_browsing/user_interaction_observer.h
new file mode 100644
index 0000000..caab6cf
--- /dev/null
+++ b/chrome/browser/safe_browsing/user_interaction_observer.h
@@ -0,0 +1,66 @@
+// Copyright 2020 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_SAFE_BROWSING_USER_INTERACTION_OBSERVER_H_
+#define CHROME_BROWSER_SAFE_BROWSING_USER_INTERACTION_OBSERVER_H_
+
+#include "chrome/browser/safe_browsing/ui_manager.h"
+#include "components/security_interstitials/core/unsafe_resource.h"
+#include "content/public/browser/render_widget_host.h"
+#include "content/public/browser/web_contents_observer.h"
+
+#include <memory>
+
+namespace safe_browsing {
+
+// Observes user interactions and shows an interstitial if necessary.
+// Only created when an interstitial was about to be displayed but was delayed
+// due to the Delayed Warnings experiment. Deleted once the interstitial is
+// shown, or the tab is closed or navigated away.
+class SafeBrowsingUserInteractionObserver
+    : public base::SupportsUserData::Data,
+      public content::WebContentsObserver {
+ public:
+  // Creates an observer for given |web_contents|. |resource| is the unsafe
+  // resource for which a delayed interstitial will be displayed.
+  // |is_main_frame| is true if the interstitial is for the top frame. If false,
+  // it's for a subresource / subframe.
+  // |ui_manager| is the UIManager that shows the actual warning.
+  static void CreateForWebContents(
+      content::WebContents* web_contents,
+      const security_interstitials::UnsafeResource& resource,
+      bool is_main_frame,
+      scoped_refptr<SafeBrowsingUIManager> ui_manager);
+
+  // See CreateForWebContents() for parameters. These need to be public.
+  SafeBrowsingUserInteractionObserver(
+      content::WebContents* web_contents,
+      const security_interstitials::UnsafeResource& resource,
+      bool is_main_frame,
+      scoped_refptr<SafeBrowsingUIManager> ui_manager);
+  ~SafeBrowsingUserInteractionObserver() override;
+
+  // content::WebContentsObserver methods:
+  void RenderViewHostChanged(content::RenderViewHost* old_host,
+                             content::RenderViewHost* new_host) override;
+  void WebContentsDestroyed() override;
+  void DidStartNavigation(content::NavigationHandle* handle) override;
+
+ private:
+  static SafeBrowsingUserInteractionObserver* FromWebContents(
+      content::WebContents* web_contents);
+
+  bool HandleKeyPress(const content::NativeWebKeyboardEvent& event);
+  void CleanUp();
+
+  content::RenderWidgetHost::KeyPressEventCallback key_press_callback_;
+
+  content::WebContents* web_contents_;
+  security_interstitials::UnsafeResource resource_;
+  scoped_refptr<SafeBrowsingUIManager> ui_manager_;
+};
+
+}  // namespace safe_browsing
+
+#endif  // CHROME_BROWSER_SAFE_BROWSING_USER_INTERACTION_OBSERVER_H_
diff --git a/chrome/browser/send_tab_to_self/send_tab_to_self_desktop_util.cc b/chrome/browser/send_tab_to_self/send_tab_to_self_desktop_util.cc
index 4542486..5aec3cd 100644
--- a/chrome/browser/send_tab_to_self/send_tab_to_self_desktop_util.cc
+++ b/chrome/browser/send_tab_to_self/send_tab_to_self_desktop_util.cc
@@ -11,6 +11,8 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/send_tab_to_self/desktop_notification_handler.h"
 #include "chrome/browser/sync/send_tab_to_self_sync_service_factory.h"
+#include "chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.h"
+#include "components/send_tab_to_self/features.h"
 #include "components/send_tab_to_self/send_tab_to_self_model.h"
 #include "components/send_tab_to_self/send_tab_to_self_sync_service.h"
 #include "components/send_tab_to_self/target_device_info.h"
@@ -61,8 +63,13 @@
   const SendTabToSelfEntry* entry =
       model->AddEntry(shared_url, title, navigation_time, target_device_guid);
 
-  if (!show_notification)
+  if (!show_notification ||
+      base::FeatureList::IsEnabled(kSendTabToSelfOmniboxSendingAnimation)) {
+    SendTabToSelfBubbleController* controller = send_tab_to_self::
+        SendTabToSelfBubbleController::CreateOrGetFromWebContents(tab);
+    controller->ShowConfirmationMessage();
     return;
+  }
 
   if (entry) {
     DesktopNotificationHandler(profile).DisplaySendingConfirmation(
diff --git a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/ShareImageFileUtilsTest.java b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/ShareImageFileUtilsTest.java
index fcf0146..d2e3ea5 100644
--- a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/ShareImageFileUtilsTest.java
+++ b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/ShareImageFileUtilsTest.java
@@ -14,7 +14,6 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.net.Uri;
-import android.os.Build;
 import android.os.Environment;
 import android.provider.MediaStore;
 import android.support.test.filters.SmallTest;
@@ -36,6 +35,7 @@
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisableIf;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeActivityTestRule;
@@ -237,7 +237,7 @@
 
     @Test
     @SmallTest
-    @DisableIf.Build(sdk_is_less_than = Build.VERSION_CODES.LOLLIPOP, message = "crbug.com/1056059")
+    @DisabledTest(message = "crbug.com/1056059")
     public void testSaveBitmap() throws IOException, TimeoutException {
         String fileName = TEST_IMAGE_FILE_NAME + "_save_bitmap";
         ShareImageFileUtils.OnImageSaveListener listener =
diff --git a/chrome/browser/supervised_user/supervised_user_features.cc b/chrome/browser/supervised_user/supervised_user_features.cc
index f60d3da..f08d2916 100644
--- a/chrome/browser/supervised_user/supervised_user_features.cc
+++ b/chrome/browser/supervised_user/supervised_user_features.cc
@@ -11,7 +11,7 @@
 
 const base::Feature kSupervisedUserInitiatedExtensionInstall{
     "SupervisedUserInitiatedExtensionInstall",
-    base::FEATURE_DISABLED_BY_DEFAULT};
+    base::FEATURE_ENABLED_BY_DEFAULT};
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
 const base::Feature kSupervisedUserAllowlistExtensionInstall{
diff --git a/chrome/browser/supervised_user/supervised_user_url_filter.cc b/chrome/browser/supervised_user/supervised_user_url_filter.cc
index 370ec159..e856f8e 100644
--- a/chrome/browser/supervised_user/supervised_user_url_filter.cc
+++ b/chrome/browser/supervised_user/supervised_user_url_filter.cc
@@ -20,6 +20,7 @@
 #include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
 #include "base/task/post_task.h"
 #include "base/task/thread_pool.h"
 #include "base/task_runner_util.h"
@@ -29,6 +30,7 @@
 #include "chrome/common/chrome_features.h"
 #include "components/policy/core/browser/url_blacklist_manager.h"
 #include "components/policy/core/browser/url_util.h"
+#include "components/url_formatter/url_formatter.h"
 #include "components/url_matcher/url_matcher.h"
 #include "components/variations/service/variations_service.h"
 #include "content/public/browser/browser_thread.h"
@@ -244,8 +246,21 @@
 bool SupervisedUserURLFilter::HostMatchesPattern(
     const std::string& canonical_host,
     const std::string& pattern) {
-  std::string trimmed_pattern = pattern;
+  // If |canonical_host| starts with |www.| but |pattern| starts with neither
+  // |www.| nor |*.| then trim |www.| part of canonical host.
+  bool is_host_www =
+      base::StartsWith(canonical_host, "www.", base::CompareCase::SENSITIVE);
+  bool patern_accepts =
+      base::StartsWith(pattern, "www.", base::CompareCase::SENSITIVE) ||
+      base::StartsWith(pattern, "*.", base::CompareCase::SENSITIVE);
+
   std::string trimmed_host = canonical_host;
+  if (is_host_www && !patern_accepts) {
+    trimmed_host = base::UTF16ToASCII(
+        url_formatter::StripWWW(base::ASCIIToUTF16(canonical_host)));
+  }
+
+  std::string trimmed_pattern = pattern;
   if (base::EndsWith(pattern, ".*", base::CompareCase::SENSITIVE)) {
     size_t registry_length = GetCanonicalHostRegistryLength(
         trimmed_host, EXCLUDE_UNKNOWN_REGISTRIES, EXCLUDE_PRIVATE_REGISTRIES);
diff --git a/chrome/browser/supervised_user/supervised_user_url_filter.h b/chrome/browser/supervised_user/supervised_user_url_filter.h
index a0205eb..f14ead53 100644
--- a/chrome/browser/supervised_user/supervised_user_url_filter.h
+++ b/chrome/browser/supervised_user/supervised_user_url_filter.h
@@ -92,10 +92,11 @@
   //   or accounts.google.com).
   // - If the pattern ends with ".*", it matches the host on any known TLD
   //   (e.g. the pattern "google.*" would match google.com or google.co.uk).
-  // See the SupervisedUserURLFilterTest.HostMatchesPattern unit test for more
-  // examples.
-  // Asterisks in other parts of the pattern are not allowed.
-  // |host| and |pattern| are assumed to be normalized to lower-case.
+  // If the |host| starts with "www." but the |pattern| starts with neither
+  // "www." nor "*.", the function strips the "www." part of |host| and tries to
+  // match again. See the SupervisedUserURLFilterTest.HostMatchesPattern unit
+  // test for more examples. Asterisks in other parts of the pattern are not
+  // allowed. |host| and |pattern| are assumed to be normalized to lower-case.
   // This method is public for testing.
   static bool HostMatchesPattern(const std::string& canonical_host,
                                  const std::string& pattern);
diff --git a/chrome/browser/supervised_user/supervised_user_url_filter_unittest.cc b/chrome/browser/supervised_user/supervised_user_url_filter_unittest.cc
index 64dbc8d..df9ca707 100644
--- a/chrome/browser/supervised_user/supervised_user_url_filter_unittest.cc
+++ b/chrome/browser/supervised_user/supervised_user_url_filter_unittest.cc
@@ -336,6 +336,8 @@
 }
 
 TEST_F(SupervisedUserURLFilterTest, HostMatchesPattern) {
+  EXPECT_TRUE(SupervisedUserURLFilter::HostMatchesPattern("www.google.com",
+                                                          "google.com"));
   EXPECT_TRUE(
       SupervisedUserURLFilter::HostMatchesPattern("www.google.com",
                                                   "*.google.com"));
diff --git a/chrome/browser/sync/chrome_sync_client.cc b/chrome/browser/sync/chrome_sync_client.cc
index 04188ee..f5b4ab1 100644
--- a/chrome/browser/sync/chrome_sync_client.cc
+++ b/chrome/browser/sync/chrome_sync_client.cc
@@ -614,7 +614,8 @@
           ->change_processor()
           ->GetControllerDelegate();
     case syncer::WIFI_CONFIGURATIONS:
-      return WifiConfigurationSyncServiceFactory::GetForProfile(profile_)
+      return WifiConfigurationSyncServiceFactory::GetForProfile(profile_,
+                                                                /*create=*/true)
           ->GetControllerDelegate();
 #endif  // defined(OS_CHROMEOS)
     case syncer::SHARING_MESSAGE:
diff --git a/chrome/browser/sync/wifi_configuration_sync_service_factory.cc b/chrome/browser/sync/wifi_configuration_sync_service_factory.cc
index 34cc0f9..fc57f86 100644
--- a/chrome/browser/sync/wifi_configuration_sync_service_factory.cc
+++ b/chrome/browser/sync/wifi_configuration_sync_service_factory.cc
@@ -17,13 +17,14 @@
 
 // static
 chromeos::sync_wifi::WifiConfigurationSyncService*
-WifiConfigurationSyncServiceFactory::GetForProfile(Profile* profile) {
+WifiConfigurationSyncServiceFactory::GetForProfile(Profile* profile,
+                                                   bool create) {
   if (!ShouldRunInProfile(profile)) {
     return nullptr;
   }
 
   return static_cast<chromeos::sync_wifi::WifiConfigurationSyncService*>(
-      GetInstance()->GetServiceForBrowserContext(profile, true));
+      GetInstance()->GetServiceForBrowserContext(profile, create));
 }
 
 // static
diff --git a/chrome/browser/sync/wifi_configuration_sync_service_factory.h b/chrome/browser/sync/wifi_configuration_sync_service_factory.h
index 205b384a..846d348 100644
--- a/chrome/browser/sync/wifi_configuration_sync_service_factory.h
+++ b/chrome/browser/sync/wifi_configuration_sync_service_factory.h
@@ -27,7 +27,8 @@
     : public BrowserContextKeyedServiceFactory {
  public:
   static chromeos::sync_wifi::WifiConfigurationSyncService* GetForProfile(
-      Profile* profile);
+      Profile* profile,
+      bool create);
   static WifiConfigurationSyncServiceFactory* GetInstance();
   static bool ShouldRunInProfile(const Profile* profile);
 
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 276d572b..a6f6daef 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1260,10 +1260,6 @@
       "uma_browsing_activity_observer.h",
       "unload_controller.cc",
       "unload_controller.h",
-      "views/intent_picker_bubble_view.cc",
-      "views/intent_picker_bubble_view.h",
-      "views/location_bar/intent_picker_view.cc",
-      "views/location_bar/intent_picker_view.h",
       "webui/app_launcher_login_handler.cc",
       "webui/app_launcher_login_handler.h",
       "webui/app_management/app_management_page_handler.cc",
@@ -3051,6 +3047,8 @@
       "views/infobars/infobar_container_view.h",
       "views/infobars/infobar_view.cc",
       "views/infobars/infobar_view.h",
+      "views/intent_picker_bubble_view.cc",
+      "views/intent_picker_bubble_view.h",
       "views/javascript_tab_modal_dialog_view_views.cc",
       "views/javascript_tab_modal_dialog_view_views.h",
       "views/layout/interpolating_layout_manager.cc",
@@ -3069,6 +3067,8 @@
       "views/location_bar/find_bar_icon.h",
       "views/location_bar/icon_label_bubble_view.cc",
       "views/location_bar/icon_label_bubble_view.h",
+      "views/location_bar/intent_picker_view.cc",
+      "views/location_bar/intent_picker_view.h",
       "views/location_bar/keyword_hint_view.cc",
       "views/location_bar/keyword_hint_view.h",
       "views/location_bar/location_bar_bubble_delegate_view.cc",
@@ -3848,16 +3848,12 @@
       "app_list/search/search_result_ranker/app_launch_event_logger.h",
       "app_list/search/search_result_ranker/app_launch_event_logger_helper.cc",
       "app_list/search/search_result_ranker/app_launch_event_logger_helper.h",
-      "app_list/search/search_result_ranker/app_launch_predictor.cc",
-      "app_list/search/search_result_ranker/app_launch_predictor.h",
       "app_list/search/search_result_ranker/app_list_launch_metrics_provider.cc",
       "app_list/search/search_result_ranker/app_list_launch_metrics_provider.h",
       "app_list/search/search_result_ranker/app_list_launch_recorder.cc",
       "app_list/search/search_result_ranker/app_list_launch_recorder.h",
       "app_list/search/search_result_ranker/app_list_launch_recorder_util.cc",
       "app_list/search/search_result_ranker/app_list_launch_recorder_util.h",
-      "app_list/search/search_result_ranker/app_search_result_ranker.cc",
-      "app_list/search/search_result_ranker/app_search_result_ranker.h",
       "app_list/search/search_result_ranker/chip_ranker.cc",
       "app_list/search/search_result_ranker/chip_ranker.h",
       "app_list/search/search_result_ranker/frecency_store.cc",
@@ -3999,7 +3995,6 @@
       "//chrome/browser/chromeos/crostini:crostini_installer_types_mojom",
       "//chrome/browser/ui/app_list/search/cros_action_history:cros_action_proto",
       "//chrome/browser/ui/app_list/search/search_result_ranker:app_launch_event_logger_proto",
-      "//chrome/browser/ui/app_list/search/search_result_ranker:app_launch_predictor_proto",
       "//chrome/browser/ui/app_list/search/search_result_ranker:app_list_launch_recorder_proto",
       "//chrome/browser/ui/app_list/search/search_result_ranker:recurrence_ranker_proto",
       "//chrome/browser/ui/app_list/search/search_result_ranker:search_ranking_event_proto",
diff --git a/chrome/browser/ui/app_list/search/app_search_provider.cc b/chrome/browser/ui/app_list/search/app_search_provider.cc
index 33e5539..b95788fc5d 100644
--- a/chrome/browser/ui/app_list/search/app_search_provider.cc
+++ b/chrome/browser/ui/app_list/search/app_search_provider.cc
@@ -40,8 +40,6 @@
 #include "chrome/browser/ui/app_list/chrome_app_list_item.h"
 #include "chrome/browser/ui/app_list/internal_app/internal_app_metadata.h"
 #include "chrome/browser/ui/app_list/search/app_service_app_result.h"
-#include "chrome/browser/ui/app_list/search/search_result_ranker/app_search_result_ranker.h"
-#include "chrome/browser/ui/app_list/search/search_result_ranker/ranking_item_util.h"
 #include "chrome/common/string_matching/fuzzy_tokenized_string_match.h"
 #include "chrome/common/string_matching/tokenized_string.h"
 #include "chrome/common/string_matching/tokenized_string_match.h"
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/BUILD.gn b/chrome/browser/ui/app_list/search/search_result_ranker/BUILD.gn
index cdbb313..47bb735 100644
--- a/chrome/browser/ui/app_list/search/search_result_ranker/BUILD.gn
+++ b/chrome/browser/ui/app_list/search/search_result_ranker/BUILD.gn
@@ -8,10 +8,6 @@
   sources = [ "app_launch_event_logger.proto" ]
 }
 
-proto_library("app_launch_predictor_proto") {
-  sources = [ "app_launch_predictor.proto" ]
-}
-
 proto_library("app_list_launch_recorder_proto") {
   sources = [ "app_list_launch_recorder_state.proto" ]
 }
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.cc b/chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.cc
deleted file mode 100644
index 521673f..0000000
--- a/chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.cc
+++ /dev/null
@@ -1,339 +0,0 @@
-// 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/app_list/search/search_result_ranker/app_launch_predictor.h"
-
-#include <cmath>
-
-#include "ash/public/cpp/app_list/app_list_features.h"
-#include "base/logging.h"
-#include "base/metrics/field_trial_params.h"
-#include "base/stl_util.h"
-
-namespace app_list {
-namespace {
-
-constexpr int kHoursADay = 24;
-constexpr base::TimeDelta kSaveInternal = base::TimeDelta::FromHours(1);
-
-// A bin with index i has 5 adjacent bins as: i + 0, i + 1, i + 2, i + 22, and
-// i + 23 which stand for the bin i itself, 1 hour later, 2 hours later,
-// 2 hours earlier and 1 hour earlier. Each adjacent bin contributes to the
-// final Rank score with weights from BinWeightsFromFlagOrDefault();
-constexpr int kAdjacentHourBin[] = {0, 1, 2, 22, 23};
-
-}  // namespace
-
-MrfuAppLaunchPredictor::MrfuAppLaunchPredictor() = default;
-MrfuAppLaunchPredictor::~MrfuAppLaunchPredictor() = default;
-
-void MrfuAppLaunchPredictor::Train(const std::string& app_id) {
-  // Updates the score for this |app_id|.
-  ++num_of_trains_;
-  Score& score = scores_[app_id];
-  UpdateScore(&score);
-  score.last_score += 1.0f - decay_coeff_;
-}
-
-base::flat_map<std::string, float> MrfuAppLaunchPredictor::Rank() {
-  // Updates all scores and return app_id to score map.
-  base::flat_map<std::string, float> output;
-  for (auto& pair : scores_) {
-    UpdateScore(&pair.second);
-    output[pair.first] = pair.second.last_score;
-  }
-  return output;
-}
-
-const char MrfuAppLaunchPredictor::kPredictorName[] = "MrfuAppLaunchPredictor";
-const char* MrfuAppLaunchPredictor::GetPredictorName() const {
-  return kPredictorName;
-}
-
-bool MrfuAppLaunchPredictor::ShouldSave() {
-  // MrfuAppLaunchPredictor doesn't need materialization.
-  return false;
-}
-
-AppLaunchPredictorProto MrfuAppLaunchPredictor::ToProto() const {
-  // MrfuAppLaunchPredictor doesn't need materialization.
-  NOTREACHED();
-  return AppLaunchPredictorProto();
-}
-
-bool MrfuAppLaunchPredictor::FromProto(const AppLaunchPredictorProto& proto) {
-  // MrfuAppLaunchPredictor doesn't need materialization.
-  NOTREACHED();
-  return false;
-}
-
-void MrfuAppLaunchPredictor::UpdateScore(Score* score) {
-  // Updates last_score and num_of_trains_at_last_update.
-  const int trains_since_last_time =
-      num_of_trains_ - score->num_of_trains_at_last_update;
-  if (trains_since_last_time > 0) {
-    score->last_score *= std::pow(decay_coeff_, trains_since_last_time);
-    score->num_of_trains_at_last_update = num_of_trains_;
-  }
-}
-
-SerializedMrfuAppLaunchPredictor::SerializedMrfuAppLaunchPredictor()
-    : MrfuAppLaunchPredictor(), last_save_timestamp_(base::Time::Now()) {}
-
-SerializedMrfuAppLaunchPredictor::~SerializedMrfuAppLaunchPredictor() = default;
-
-const char SerializedMrfuAppLaunchPredictor::kPredictorName[] =
-    "SerializedMrfuAppLaunchPredictor";
-const char* SerializedMrfuAppLaunchPredictor::GetPredictorName() const {
-  return kPredictorName;
-}
-
-bool SerializedMrfuAppLaunchPredictor::ShouldSave() {
-  const base::Time now = base::Time::Now();
-  if (now - last_save_timestamp_ >= kSaveInternal) {
-    last_save_timestamp_ = now;
-    return true;
-  }
-  return false;
-}
-
-AppLaunchPredictorProto SerializedMrfuAppLaunchPredictor::ToProto() const {
-  AppLaunchPredictorProto output;
-  auto& predictor_proto =
-      *output.mutable_serialized_mrfu_app_launch_predictor();
-  predictor_proto.set_num_of_trains(num_of_trains_);
-  for (const auto& pair : scores_) {
-    auto& score_item = (*predictor_proto.mutable_scores())[pair.first];
-    score_item.set_last_score(pair.second.last_score);
-    score_item.set_num_of_trains_at_last_update(
-        pair.second.num_of_trains_at_last_update);
-  }
-  return output;
-}
-
-bool SerializedMrfuAppLaunchPredictor::FromProto(
-    const AppLaunchPredictorProto& proto) {
-  if (proto.predictor_case() !=
-      AppLaunchPredictorProto::kSerializedMrfuAppLaunchPredictor) {
-    return false;
-  }
-
-  const auto& predictor_proto = proto.serialized_mrfu_app_launch_predictor();
-  num_of_trains_ = predictor_proto.num_of_trains();
-
-  scores_.clear();
-  for (const auto& pair : predictor_proto.scores()) {
-    // Skip the case where the last_score has already dropped to 0.0f.
-    if (pair.second.last_score() == 0.0f)
-      continue;
-    auto& score_item = scores_[pair.first];
-    score_item.last_score = pair.second.last_score();
-    score_item.num_of_trains_at_last_update =
-        pair.second.num_of_trains_at_last_update();
-  }
-
-  return true;
-}
-
-HourAppLaunchPredictor::HourAppLaunchPredictor()
-    : last_save_timestamp_(base::Time::Now()),
-      bin_weights_(BinWeightsFromFlagOrDefault()) {}
-
-HourAppLaunchPredictor::~HourAppLaunchPredictor() = default;
-
-void HourAppLaunchPredictor::Train(const std::string& app_id) {
-  auto& frequency_table = (*proto_.mutable_hour_app_launch_predictor()
-                                ->mutable_binned_frequency_table())[GetBin()];
-
-  frequency_table.set_total_counts(frequency_table.total_counts() + 1);
-  (*frequency_table.mutable_frequency())[app_id] += 1;
-}
-
-base::flat_map<std::string, float> HourAppLaunchPredictor::Rank() {
-  base::flat_map<std::string, float> output;
-  const int bin = GetBin();
-  const bool is_weekend = bin >= kHoursADay;
-  const int hour = bin % kHoursADay;
-  const auto& frequency_table_map =
-      proto_.hour_app_launch_predictor().binned_frequency_table();
-
-  for (size_t i = 0; i < base::size(kAdjacentHourBin); ++i) {
-    // Finds adjacent bin and weight.
-    const int adj_bin =
-        (hour + kAdjacentHourBin[i]) % kHoursADay + kHoursADay * is_weekend;
-    const auto find_frequency_table = frequency_table_map.find(adj_bin);
-    if (find_frequency_table == frequency_table_map.end())
-      continue;
-
-    const auto& frequency_table = find_frequency_table->second;
-    const float weight = bin_weights_[i];
-
-    // Accumulates the frequency to the output.
-    if (frequency_table.total_counts() > 0) {
-      const int total_counts = frequency_table.total_counts();
-      for (const auto& pair : frequency_table.frequency()) {
-        output[pair.first] +=
-            static_cast<float>(pair.second) / total_counts * weight;
-      }
-    }
-  }
-  return output;
-}
-
-const char HourAppLaunchPredictor::kPredictorName[] = "HourAppLaunchPredictor";
-const char* HourAppLaunchPredictor::GetPredictorName() const {
-  return kPredictorName;
-}
-
-bool HourAppLaunchPredictor::ShouldSave() {
-  const base::Time now = base::Time::Now();
-  if (now - last_save_timestamp_ >= kSaveInternal) {
-    last_save_timestamp_ = now;
-    return true;
-  }
-  return false;
-}
-
-AppLaunchPredictorProto HourAppLaunchPredictor::ToProto() const {
-  return proto_;
-}
-
-bool HourAppLaunchPredictor::FromProto(const AppLaunchPredictorProto& proto) {
-  if (proto.predictor_case() !=
-      AppLaunchPredictorProto::kHourAppLaunchPredictor) {
-    return false;
-  }
-
-  const HourAppLaunchPredictorProto& predictor_proto =
-      proto.hour_app_launch_predictor();
-
-  const int today = base::Time::Now().ToDeltaSinceWindowsEpoch().InDays();
-
-  // If last_decay_timestamp is not set, just copy the proto.
-  if (!predictor_proto.has_last_decay_timestamp()) {
-    proto_ = proto;
-    proto_.mutable_hour_app_launch_predictor()->set_last_decay_timestamp(today);
-    return true;
-  }
-
-  // If last decay is within 7 days, just copy the proto.
-  if (today - predictor_proto.last_decay_timestamp() <= 7) {
-    proto_ = proto;
-    return true;
-  }
-
-  proto_.Clear();
-  for (const auto& table : predictor_proto.binned_frequency_table()) {
-    auto& new_table = (*proto_.mutable_hour_app_launch_predictor()
-                            ->mutable_binned_frequency_table())[table.first];
-
-    int total_counts = 0;
-    for (const auto& frequency : table.second.frequency()) {
-      const int new_frequency = frequency.second * kWeeklyDecayCoeff;
-      if (new_frequency > 0) {
-        total_counts += new_frequency;
-        (*new_table.mutable_frequency())[frequency.first] = new_frequency;
-      }
-    }
-    new_table.set_total_counts(total_counts);
-  }
-  proto_.mutable_hour_app_launch_predictor()->set_last_decay_timestamp(today);
-  return true;
-}
-
-int HourAppLaunchPredictor::GetBin() const {
-  base::Time::Exploded now;
-  base::Time::Now().LocalExplode(&now);
-
-  const bool is_weekend = now.day_of_week == 6 || now.day_of_week == 0;
-
-  // To distinguish workdays with weekends, we use now.hour for workdays, and
-  // now.hour + 24 for weekends.
-  if (!is_weekend) {
-    return now.hour;
-  } else {
-    return now.hour + kHoursADay;
-  }
-}
-
-std::vector<float> HourAppLaunchPredictor::BinWeightsFromFlagOrDefault() {
-  const std::vector<float> default_weights = {0.6, 0.15, 0.05, 0.05, 0.15};
-  std::vector<float> weights(5);
-
-  // Get weights for adjacent bins. Every weight has to be within [0.0, 1.0]
-  // And the sum weights[1] + ..., + weights[4] also needs to be in [0.0, 1.0]
-  // so that the weight[0] is set to be 1.0 - (weights[1] + ..., + weights[4]).
-  weights[1] = static_cast<float>(base::GetFieldTrialParamByFeatureAsDouble(
-      app_list_features::kEnableZeroStateAppsRanker, "weight_1_hour_later_bin",
-      -1.0));
-  if (weights[1] < 0.0 || weights[1] > 1.0)
-    return default_weights;
-
-  weights[2] = static_cast<float>(base::GetFieldTrialParamByFeatureAsDouble(
-      app_list_features::kEnableZeroStateAppsRanker, "weight_2_hour_later_bin",
-      -1.0));
-  if (weights[2] < 0.0 || weights[2] > 1.0)
-    return default_weights;
-
-  weights[3] = static_cast<float>(base::GetFieldTrialParamByFeatureAsDouble(
-      app_list_features::kEnableZeroStateAppsRanker,
-      "weight_2_hour_earlier_bin", -1.0));
-  if (weights[3] < 0.0 || weights[3] > 1.0)
-    return default_weights;
-
-  weights[4] = static_cast<float>(base::GetFieldTrialParamByFeatureAsDouble(
-      app_list_features::kEnableZeroStateAppsRanker,
-      "weight_1_hour_earlier_bin", -1.0));
-  if (weights[4] < 0.0 || weights[4] > 1.0)
-    return default_weights;
-
-  weights[0] = 1.0 - weights[1] - weights[2] - weights[3] - weights[4];
-  if (weights[0] < 0.0 || weights[0] > 1.0)
-    return default_weights;
-
-  return weights;
-}
-
-void FakeAppLaunchPredictor::SetShouldSave(bool should_save) {
-  should_save_ = should_save;
-}
-
-void FakeAppLaunchPredictor::Train(const std::string& app_id) {
-  // Increases 1.0 for rank score of app_id.
-  (*proto_.mutable_fake_app_launch_predictor()
-        ->mutable_rank_result())[app_id] += 1.0f;
-}
-
-base::flat_map<std::string, float> FakeAppLaunchPredictor::Rank() {
-  // Outputs proto_.fake_app_launch_predictor().rank_result() as Rank result.
-  base::flat_map<std::string, float> output;
-  for (const auto& pair : proto_.fake_app_launch_predictor().rank_result()) {
-    output[pair.first] = pair.second;
-  }
-  return output;
-}
-
-const char FakeAppLaunchPredictor::kPredictorName[] = "FakeAppLaunchPredictor";
-const char* FakeAppLaunchPredictor::GetPredictorName() const {
-  return kPredictorName;
-}
-
-bool FakeAppLaunchPredictor::ShouldSave() {
-  return should_save_;
-}
-
-AppLaunchPredictorProto FakeAppLaunchPredictor::ToProto() const {
-  return proto_;
-}
-
-bool FakeAppLaunchPredictor::FromProto(const AppLaunchPredictorProto& proto) {
-  if (proto.predictor_case() !=
-      AppLaunchPredictorProto::kFakeAppLaunchPredictor) {
-    return false;
-  }
-  proto_ = proto;
-  return true;
-}
-
-}  // namespace app_list
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.h b/chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.h
deleted file mode 100644
index 7d25c6cb..0000000
--- a/chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.h
+++ /dev/null
@@ -1,195 +0,0 @@
-// 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.
-
-#ifndef CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_RESULT_RANKER_APP_LAUNCH_PREDICTOR_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_RESULT_RANKER_APP_LAUNCH_PREDICTOR_H_
-
-#include <string>
-#include <vector>
-
-#include "base/containers/flat_map.h"
-#include "base/gtest_prod_util.h"
-#include "base/macros.h"
-#include "base/time/time.h"
-#include "chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.pb.h"
-
-namespace app_list {
-
-// AppLaunchPredictor is the interface implemented by all predictors. It defines
-// two basic public functions Train and Rank for training and inferencing.
-class AppLaunchPredictor {
- public:
-  virtual ~AppLaunchPredictor() = default;
-  // Trains on the |app_id| and (possibly) updates its internal representation.
-  virtual void Train(const std::string& app_id) = 0;
-  // Returns a map of app_id and score.
-  //  (1) Higher score means more relevant.
-  //  (2) Only returns a subset of app_ids seen by this predictor.
-  //  (3) The returned scores should be in range [0.0, 1.0] for
-  //      AppSearchProvider to handle.
-  virtual base::flat_map<std::string, float> Rank() = 0;
-  // Returns the name of the predictor.
-  virtual const char* GetPredictorName() const = 0;
-  // Whether the model should be saved on disk at this moment.
-  virtual bool ShouldSave() = 0;
-  // Converts the predictor to AppLaunchPredictorProto.
-  virtual AppLaunchPredictorProto ToProto() const = 0;
-  // Initializes the predictor with |proto|.
-  virtual bool FromProto(const AppLaunchPredictorProto& proto) = 0;
-};
-
-// MrfuAppLaunchPredictor is a simple AppLaunchPredictor that balances MRU (most
-// recently used) and MFU (most frequently used). It is adopted from LRFU cpu
-// cache algorithm.
-class MrfuAppLaunchPredictor : public AppLaunchPredictor {
- public:
-  MrfuAppLaunchPredictor();
-  ~MrfuAppLaunchPredictor() override;
-
-  // AppLaunchPredictor:
-  void Train(const std::string& app_id) override;
-  base::flat_map<std::string, float> Rank() override;
-  const char* GetPredictorName() const override;
-  bool ShouldSave() override;
-  AppLaunchPredictorProto ToProto() const override;
-  bool FromProto(const AppLaunchPredictorProto& proto) override;
-
-  // Name of the predictor;
-  static const char kPredictorName[];
-
- protected:
-  // Records last updates of the Score for an app.
-  struct Score {
-    int32_t num_of_trains_at_last_update = 0;
-    float last_score = 0.0f;
-  };
-
-  // Updates the Score to now.
-  void UpdateScore(Score* score);
-  // Map from app_id to its Score.
-  base::flat_map<std::string, Score> scores_;
-  // Increment 1 for each Train() call.
-  int32_t num_of_trains_ = 0;
-
- private:
-  FRIEND_TEST_ALL_PREFIXES(AppLaunchPredictorTest, MrfuAppLaunchPredictor);
-  friend class SerializedMrfuAppLaunchPredictorTest;
-
-  // Controls how much the score decays for each Train() call.
-  // This decay_coeff_ should be within [0.5f, 1.0f]. Setting it as 0.5f means
-  // MRU; setting as 1.0f means MFU;
-  // TODO(https://crbug.com/871674):
-  // (1) Set a better initial value based on real user data.
-  // (2) Dynamically change this coeff instead of setting it as constant.
-  static constexpr float decay_coeff_ = 0.8f;
-
-  DISALLOW_COPY_AND_ASSIGN(MrfuAppLaunchPredictor);
-};
-
-// SerializedMrfuAppLaunchPredictor is MrfuAppLaunchPredictor with supporting of
-// AppLaunchPredictor::ToProto and AppLaunchPredictor::FromProto.
-class SerializedMrfuAppLaunchPredictor : public MrfuAppLaunchPredictor {
- public:
-  SerializedMrfuAppLaunchPredictor();
-  ~SerializedMrfuAppLaunchPredictor() override;
-
-  // AppLaunchPredictor:
-  const char* GetPredictorName() const override;
-  bool ShouldSave() override;
-  AppLaunchPredictorProto ToProto() const override;
-  bool FromProto(const AppLaunchPredictorProto& proto) override;
-
-  // Name of the predictor;
-  static const char kPredictorName[];
-
- private:
-  // Last time the predictor was saved.
-  base::Time last_save_timestamp_;
-
-  DISALLOW_COPY_AND_ASSIGN(SerializedMrfuAppLaunchPredictor);
-};
-
-// HourAppLaunchPredictor is a AppLaunchPredictor that uses hour of the day as
-// bins, and uses app-launch frequency of in each bin as the Rank score.
-// For example, if it's 8:30 am right now, then only app-launches between 8am to
-// 9am in the last a few days are mainly considered.
-// NOTE 1: bins of nearby hours also contributes to the final score but less
-//         significient. For example if current time is 8am, then scores in 6am,
-//         7am, 9am, and 10am are also added to the final Rank score with
-//         smaller weights.
-// NOTE 2: workdays and weekends are put into different bins.
-class HourAppLaunchPredictor : public AppLaunchPredictor {
- public:
-  HourAppLaunchPredictor();
-  ~HourAppLaunchPredictor() override;
-
-  // AppLaunchPredictor:
-  void Train(const std::string& app_id) override;
-  base::flat_map<std::string, float> Rank() override;
-  const char* GetPredictorName() const override;
-  bool ShouldSave() override;
-  AppLaunchPredictorProto ToProto() const override;
-  bool FromProto(const AppLaunchPredictorProto& proto) override;
-
-  // Name of the predictor;
-  static const char kPredictorName[];
-
- private:
-  FRIEND_TEST_ALL_PREFIXES(HourAppLaunchPredictorTest, GetTheRightBin);
-  FRIEND_TEST_ALL_PREFIXES(HourAppLaunchPredictorTest, RankFromSingleBin);
-  FRIEND_TEST_ALL_PREFIXES(HourAppLaunchPredictorTest, RankFromMultipleBin);
-  FRIEND_TEST_ALL_PREFIXES(HourAppLaunchPredictorTest, CheckDefaultWeights);
-  FRIEND_TEST_ALL_PREFIXES(HourAppLaunchPredictorTest, SetWeightsFromFlag);
-  FRIEND_TEST_ALL_PREFIXES(HourAppLaunchPredictorTest, FromProtoDecay);
-
-  // Returns current bin index of this predictor.
-  int GetBin() const;
-
-  // Get weights of adjacent bins from flag which will be set using finch config
-  // for exploring possible options.
-  static std::vector<float> BinWeightsFromFlagOrDefault();
-
-  // The proto for this predictor.
-  AppLaunchPredictorProto proto_;
-  // Last time the predictor was saved.
-  base::Time last_save_timestamp_;
-  // Coefficient that controls the decay of previous record.
-  static constexpr float kWeeklyDecayCoeff = 0.8;
-  // Weights that are used to combine adjacent bins.
-  const std::vector<float> bin_weights_;
-
-  DISALLOW_COPY_AND_ASSIGN(HourAppLaunchPredictor);
-};
-
-// Predictor for testing AppSearchResultRanker only.
-class FakeAppLaunchPredictor : public AppLaunchPredictor {
- public:
-  FakeAppLaunchPredictor() = default;
-  ~FakeAppLaunchPredictor() override = default;
-
-  // Manually set |should_save_|;
-  void SetShouldSave(bool should_save);
-
-  // AppLaunchPredictor:
-  void Train(const std::string& app_id) override;
-  base::flat_map<std::string, float> Rank() override;
-  const char* GetPredictorName() const override;
-  bool ShouldSave() override;
-  AppLaunchPredictorProto ToProto() const override;
-  bool FromProto(const AppLaunchPredictorProto& proto) override;
-
-  // Name of the predictor;
-  static const char kPredictorName[];
-
- private:
-  bool should_save_ = false;
-  // The proto for this predictor.
-  AppLaunchPredictorProto proto_;
-
-  DISALLOW_COPY_AND_ASSIGN(FakeAppLaunchPredictor);
-};
-
-}  // namespace app_list
-
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_RESULT_RANKER_APP_LAUNCH_PREDICTOR_H_
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.proto b/chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.proto
deleted file mode 100644
index d56f3b2..0000000
--- a/chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.proto
+++ /dev/null
@@ -1,52 +0,0 @@
-// 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.
-
-syntax = "proto2";
-
-option optimize_for = LITE_RUNTIME;
-
-package app_list;
-
-message SerializedMrfuAppLaunchPredictorProto {
-  // Records last updates of the Score for an app.
-  message Score {
-    optional int32 num_of_trains_at_last_update = 1;
-    optional float last_score = 2;
-  }
-  // Map from app_id to its Score.
-  map<string, Score> scores = 1;
-  // Increment 1 for each Train() call.
-  optional int32 num_of_trains = 2;
-}
-
-// HourAppLaunchPredictorProto is used for materializing HourAppLaunchPredictor.
-message HourAppLaunchPredictorProto {
-  // A frequency table records app launches that happened in a particular bin.
-  message FrequencyTable {
-    // Total number of launches (within this bin), should equal to the sum of
-    // frequency below.
-    optional int32 total_counts = 1;
-    // Number of launches for each app (within this bin).
-    map<string, int32> frequency = 2;
-  }
-  // A map from bin indices to each FrequencyTable of that bin.
-  map<int32, FrequencyTable> binned_frequency_table = 1;
-  // Timestamp of last decay operation in days since windows epoch.
-  optional int32 last_decay_timestamp = 2;
-}
-
-// Used only for testing AppSearchResultRanker.
-message FakeAppLaunchPredictorProto {
-  map<string, float> rank_result = 1;
-}
-
-// AppLaunchPredictorProto contains one type of the predictor proto above.
-message AppLaunchPredictorProto {
-  oneof predictor {
-    FakeAppLaunchPredictorProto fake_app_launch_predictor = 1;
-    HourAppLaunchPredictorProto hour_app_launch_predictor = 2;
-    SerializedMrfuAppLaunchPredictorProto serialized_mrfu_app_launch_predictor =
-        3;
-  }
-}
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor_unittest.cc b/chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor_unittest.cc
deleted file mode 100644
index 2b1b185..0000000
--- a/chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor_unittest.cc
+++ /dev/null
@@ -1,316 +0,0 @@
-// 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/app_list/search/search_result_ranker/app_launch_predictor.h"
-
-#include "ash/public/cpp/app_list/app_list_features.h"
-#include "base/test/scoped_feature_list.h"
-#include "base/test/scoped_mock_clock_override.h"
-#include "chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor_test_util.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using testing::ElementsAre;
-using testing::UnorderedElementsAre;
-using testing::Pair;
-using testing::FloatEq;
-
-namespace app_list {
-
-namespace {
-
-constexpr char kTarget1[] = "Target1";
-constexpr char kTarget2[] = "Target2";
-
-}  // namespace
-
-TEST(AppLaunchPredictorTest, MrfuAppLaunchPredictor) {
-  MrfuAppLaunchPredictor predictor;
-  const float decay = MrfuAppLaunchPredictor::decay_coeff_;
-
-  predictor.Train(kTarget1);
-  const float score_1 = 1.0f - decay;
-  EXPECT_THAT(predictor.Rank(),
-              UnorderedElementsAre(Pair(kTarget1, FloatEq(score_1))));
-
-  predictor.Train(kTarget1);
-  const float score_2 = score_1 + score_1 * decay;
-  EXPECT_THAT(predictor.Rank(),
-              UnorderedElementsAre(Pair(kTarget1, FloatEq(score_2))));
-
-  predictor.Train(kTarget2);
-  const float score_3 = score_2 * decay;
-  EXPECT_THAT(predictor.Rank(),
-              UnorderedElementsAre(Pair(kTarget1, FloatEq(score_3)),
-                                   Pair(kTarget2, FloatEq(score_1))));
-}
-
-// Test Serialization logic of SerializedMrfuAppLaunchPredictor.
-class SerializedMrfuAppLaunchPredictorTest : public testing::Test {
- protected:
-  void SetUp() override {
-    score1_ = (1.0f - decay) * decay + (1.0f - decay);
-    score2_ = 1.0f - decay;
-
-    auto& predictor_proto =
-        *proto_.mutable_serialized_mrfu_app_launch_predictor();
-
-    predictor_proto.set_num_of_trains(3);
-    auto& item1 = (*predictor_proto.mutable_scores())[kTarget1];
-    item1.set_last_score(score1_);
-    item1.set_num_of_trains_at_last_update(2);
-
-    auto& item2 = (*predictor_proto.mutable_scores())[kTarget2];
-    item2.set_last_score(score2_);
-    item2.set_num_of_trains_at_last_update(3);
-  }
-
-  float score1_ = 0.0f;
-  float score2_ = 0.0f;
-  static constexpr float decay = MrfuAppLaunchPredictor::decay_coeff_;
-  AppLaunchPredictorProto proto_;
-};
-
-TEST_F(SerializedMrfuAppLaunchPredictorTest, ToProto) {
-  SerializedMrfuAppLaunchPredictor predictor;
-
-  predictor.Train(kTarget1);
-  predictor.Train(kTarget1);
-  predictor.Train(kTarget2);
-
-  // Check predictor.ToProto() is the same as proto_.
-  EXPECT_TRUE(EquivToProtoLite(predictor.ToProto(), proto_));
-}
-
-TEST_F(SerializedMrfuAppLaunchPredictorTest, FromProto) {
-  SerializedMrfuAppLaunchPredictor predictor;
-  EXPECT_TRUE(predictor.FromProto(proto_));
-
-  EXPECT_THAT(predictor.Rank(),
-              UnorderedElementsAre(Pair(kTarget1, FloatEq(score1_ * decay)),
-                                   Pair(kTarget2, FloatEq(score2_))));
-}
-
-class HourAppLaunchPredictorTest : public testing::Test {
- protected:
-  // Sets local time according to |day_of_week| and |hour_of_day|.
-  void SetLocalTime(const int day_of_week, const int hour_of_day) {
-    AdvanceToNextLocalSunday();
-    const auto advance = base::TimeDelta::FromDays(day_of_week) +
-                         base::TimeDelta::FromHours(hour_of_day);
-    if (advance > base::TimeDelta()) {
-      time_.Advance(advance);
-    }
-  }
-
-  base::ScopedMockClockOverride time_;
-
- private:
-  // Advances time to be 0am next Sunday.
-  void AdvanceToNextLocalSunday() {
-    base::Time::Exploded now;
-    base::Time::Now().LocalExplode(&now);
-    const auto advance = base::TimeDelta::FromDays(6 - now.day_of_week) +
-                         base::TimeDelta::FromHours(24 - now.hour);
-    if (advance > base::TimeDelta()) {
-      time_.Advance(advance);
-    }
-    base::Time::Now().LocalExplode(&now);
-    CHECK_EQ(now.day_of_week, 0);
-    CHECK_EQ(now.hour, 0);
-  }
-
-};
-
-// Checks HourAppLaunchPredictor::GetBin returns the right bin index for given
-// local time.
-TEST_F(HourAppLaunchPredictorTest, GetTheRightBin) {
-  HourAppLaunchPredictor predictor;
-
-  // Monday.
-  for (int i = 0; i <= 23; ++i) {
-    SetLocalTime(1, i);
-    EXPECT_EQ(predictor.GetBin(), i);
-  }
-
-  // Friday.
-  for (int i = 0; i <= 23; ++i) {
-    SetLocalTime(5, i);
-    EXPECT_EQ(predictor.GetBin(), i);
-  }
-
-  // Saturday.
-  for (int i = 0; i <= 23; ++i) {
-    SetLocalTime(6, i);
-    EXPECT_EQ(predictor.GetBin(), i + 24);
-  }
-
-  // Sunday.
-  for (int i = 0; i <= 23; ++i) {
-    SetLocalTime(0, i);
-    EXPECT_EQ(predictor.GetBin(), i + 24);
-  }
-}
-
-// Checks the apps are ranked based on frequency in a single bin.
-TEST_F(HourAppLaunchPredictorTest, RankFromSingleBin) {
-  HourAppLaunchPredictor predictor;
-  const auto& weights = HourAppLaunchPredictor::BinWeightsFromFlagOrDefault();
-
-  // Create a model that trained on kTarget1 3 times, and kTarget2 2 times.
-  SetLocalTime(1, 10);
-  predictor.Train(kTarget1);
-  SetLocalTime(2, 10);
-  predictor.Train(kTarget1);
-  SetLocalTime(3, 10);
-  predictor.Train(kTarget1);
-  SetLocalTime(4, 10);
-  predictor.Train(kTarget2);
-  SetLocalTime(5, 10);
-  predictor.Train(kTarget2);
-
-  // Train on weekend will not fail into the same bin.
-  SetLocalTime(6, 10);
-  predictor.Train(kTarget1);
-  SetLocalTime(0, 10);
-  predictor.Train(kTarget2);
-
-  SetLocalTime(1, 10);
-  EXPECT_THAT(predictor.Rank(),
-              UnorderedElementsAre(Pair(kTarget1, FloatEq(weights[0] * 0.6)),
-                                   Pair(kTarget2, FloatEq(weights[0] * 0.4))));
-}
-
-// Checks the apps are ranked based on linearly combined scores from adjacent
-// bins.
-TEST_F(HourAppLaunchPredictorTest, RankFromMultipleBin) {
-  HourAppLaunchPredictor predictor;
-  const auto& weights = HourAppLaunchPredictor::BinWeightsFromFlagOrDefault();
-
-  // For bin 10
-  SetLocalTime(1, 10);
-  predictor.Train(kTarget1);
-  predictor.Train(kTarget1);
-  SetLocalTime(2, 10);
-  predictor.Train(kTarget2);
-
-  // For bin 11
-  SetLocalTime(3, 11);
-  predictor.Train(kTarget1);
-  predictor.Train(kTarget2);
-
-  // FOr bin 12
-  SetLocalTime(5, 12);
-  predictor.Train(kTarget2);
-
-  // Train on weekend.
-  SetLocalTime(6, 10);
-  predictor.Train(kTarget1);
-  predictor.Train(kTarget2);
-  SetLocalTime(0, 11);
-  predictor.Train(kTarget2);
-
-  // Check workdays.
-  SetLocalTime(1, 10);
-  EXPECT_THAT(
-      predictor.Rank(),
-      UnorderedElementsAre(
-          Pair(kTarget1, FloatEq(weights[0] * 2.0 / 3.0 + weights[1] * 0.5)),
-          Pair(kTarget2, FloatEq(weights[0] * 1.0 / 3.0 + weights[1] * 0.5 +
-                                 weights[2] * 1.0))));
-
-  // Check weekends.
-  SetLocalTime(0, 9);
-  EXPECT_THAT(
-      predictor.Rank(),
-      UnorderedElementsAre(
-          Pair(kTarget1, FloatEq(weights[1] * 1.0 / 2.0)),
-          Pair(kTarget2, FloatEq(weights[1] * 1.0 / 2.0 + weights[2]))));
-}
-
-// Check the default weights are set correctly.
-TEST_F(HourAppLaunchPredictorTest, CheckDefaultWeights) {
-  base::test::ScopedFeatureList scoped_feature_list_;
-  scoped_feature_list_.InitAndEnableFeature(
-      app_list_features::kEnableZeroStateAppsRanker);
-
-  EXPECT_THAT(HourAppLaunchPredictor::BinWeightsFromFlagOrDefault(),
-              ElementsAre(FloatEq(0.6), FloatEq(0.15), FloatEq(0.05),
-                          FloatEq(0.05), FloatEq(0.15)));
-}
-
-// Checks that the weights are set from flag correctly.
-TEST_F(HourAppLaunchPredictorTest, SetWeightsFromFlag) {
-  base::test::ScopedFeatureList scoped_feature_list_;
-  scoped_feature_list_.InitAndEnableFeatureWithParameters(
-      app_list_features::kEnableZeroStateAppsRanker,
-      {{"weight_1_hour_later_bin", "0.1"},
-       {"weight_2_hour_later_bin", "0.2"},
-       {"weight_2_hour_earlier_bin", "0.22"},
-       {"weight_1_hour_earlier_bin", "0.23"}});
-
-  HourAppLaunchPredictor predictor;
-  const auto& weights = HourAppLaunchPredictor::BinWeightsFromFlagOrDefault();
-
-  EXPECT_THAT(weights, ElementsAre(FloatEq(0.25), FloatEq(0.1), FloatEq(0.2),
-                                   FloatEq(0.22), FloatEq(0.23)));
-
-  // For bin 0
-  SetLocalTime(1, 0);
-  predictor.Train(kTarget1);
-  predictor.Train(kTarget1);
-  predictor.Train(kTarget2);
-
-  // For bin 1
-  SetLocalTime(1, 1);
-  predictor.Train(kTarget1);
-  predictor.Train(kTarget2);
-
-  SetLocalTime(1, 0);
-  EXPECT_THAT(
-      predictor.Rank(),
-      UnorderedElementsAre(
-          Pair(kTarget1, FloatEq(weights[0] * 2.0 / 3.0 + weights[1] * 0.5)),
-          Pair(kTarget2, FloatEq(weights[0] * 1.0 / 3.0 + weights[1] * 0.5))));
-}
-
-// Checks FromProto applies decay correctly.
-TEST_F(HourAppLaunchPredictorTest, FromProtoDecay) {
-  HourAppLaunchPredictor predictor;
-  const int bin = predictor.GetBin();
-  const int frequency1 = 11;
-  const int frequency2 = 1;
-
-  AppLaunchPredictorProto proto;
-  auto& frequency_table = (*proto.mutable_hour_app_launch_predictor()
-                                ->mutable_binned_frequency_table())[bin];
-  (*frequency_table.mutable_frequency())[kTarget1] = frequency1;
-  (*frequency_table.mutable_frequency())[kTarget2] = frequency2;
-  frequency_table.set_total_counts(frequency1 + frequency2);
-
-  // FromProto will not decay since last_decay_timestamp is not set.
-  predictor.FromProto(proto);
-  proto.mutable_hour_app_launch_predictor()->set_last_decay_timestamp(
-      base::Time::Now().ToDeltaSinceWindowsEpoch().InDays());
-  EXPECT_TRUE(EquivToProtoLite(predictor.ToProto(), proto));
-
-  // FromProto will not decay since last_decay_timestamp is within 7 days.
-  time_.Advance(base::TimeDelta::FromDays(6));
-  predictor.FromProto(proto);
-  EXPECT_TRUE(EquivToProtoLite(predictor.ToProto(), proto));
-
-  // FromProto will decay since last_decay_timestamp is over 7 days.
-  time_.Advance(base::TimeDelta::FromDays(2));
-  predictor.FromProto(proto);
-  const int new_frequency1 =
-      static_cast<int>(frequency1 * HourAppLaunchPredictor::kWeeklyDecayCoeff);
-  frequency_table.mutable_frequency()->clear();
-  (*frequency_table.mutable_frequency())[kTarget1] = new_frequency1;
-  frequency_table.set_total_counts(new_frequency1);
-  proto.mutable_hour_app_launch_predictor()->set_last_decay_timestamp(
-      base::Time::Now().ToDeltaSinceWindowsEpoch().InDays());
-  EXPECT_TRUE(EquivToProtoLite(predictor.ToProto(), proto));
-}
-
-}  // namespace app_list
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/app_search_result_ranker.cc b/chrome/browser/ui/app_list/search/search_result_ranker/app_search_result_ranker.cc
deleted file mode 100644
index 9c30a08..0000000
--- a/chrome/browser/ui/app_list/search/search_result_ranker/app_search_result_ranker.cc
+++ /dev/null
@@ -1,167 +0,0 @@
-// 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/app_list/search/search_result_ranker/app_search_result_ranker.h"
-
-#include "ash/public/cpp/app_list/app_list_features.h"
-#include "base/bind.h"
-#include "base/files/file_util.h"
-#include "base/files/important_file_writer.h"
-#include "base/task/post_task.h"
-#include "base/task/task_traits.h"
-#include "base/task/thread_pool.h"
-#include "base/task_runner_util.h"
-#include "base/threading/scoped_blocking_call.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.h"
-#include "chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.pb.h"
-
-namespace app_list {
-namespace {
-
-constexpr char kAppLaunchPredictorFilename[] = "app_launch_predictor";
-
-// Returns a AppLaunchPredictor pointer based on the |predictor_name|.
-std::unique_ptr<AppLaunchPredictor> CreatePredictor(
-    const std::string& predictor_name) {
-  if (predictor_name == MrfuAppLaunchPredictor::kPredictorName)
-    return std::make_unique<MrfuAppLaunchPredictor>();
-
-  if (predictor_name == SerializedMrfuAppLaunchPredictor::kPredictorName)
-    return std::make_unique<SerializedMrfuAppLaunchPredictor>();
-
-  if (predictor_name == HourAppLaunchPredictor::kPredictorName)
-    return std::make_unique<HourAppLaunchPredictor>();
-
-  if (predictor_name == FakeAppLaunchPredictor::kPredictorName)
-    return std::make_unique<FakeAppLaunchPredictor>();
-
-  NOTREACHED();
-  return nullptr;
-}
-
-// Save |proto| to |predictor_filename|.
-void SaveToDiskOnWorkerThread(const base::FilePath& predictor_filename,
-                              const AppLaunchPredictorProto& proto) {
-
-  std::string proto_str;
-  if (!proto.SerializeToString(&proto_str)) {
-    LOG(ERROR)
-        << "Unable to serialize AppLaunchPredictorProto, not saving to disk.";
-    return;
-  }
-  bool write_result;
-  {
-    base::ScopedBlockingCall scoped_blocking_call(
-        FROM_HERE, base::BlockingType::MAY_BLOCK);
-    write_result = base::ImportantFileWriter::WriteFileAtomically(
-        predictor_filename, proto_str, "AppSearchResultRanker");
-  }
-  if (!write_result) {
-    LOG(ERROR) << "Error writing predictor file " << predictor_filename;
-  }
-}
-
-// Loads a AppLaunchPredictor from |predictor_filename|.
-std::unique_ptr<AppLaunchPredictor> LoadPredictorFromDiskOnWorkerThread(
-    const base::FilePath& predictor_filename,
-    const std::string predictor_name) {
-  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
-                                                base::BlockingType::MAY_BLOCK);
-
-  // Loads proto string from local disk.
-  std::string proto_str;
-  if (!base::ReadFileToString(predictor_filename, &proto_str))
-    return nullptr;
-
-  // Parses proto string as AppLaunchPredictorProto.
-  AppLaunchPredictorProto proto;
-  if (!proto.ParseFromString(proto_str))
-    return nullptr;
-
-  auto predictor = CreatePredictor(predictor_name);
-  // Initializes the |predictor_| from the |proto|.
-  if (!predictor->FromProto(proto))
-    return nullptr;
-
-  return predictor;
-}
-
-}  // namespace
-
-AppSearchResultRanker::AppSearchResultRanker(const base::FilePath& profile_path,
-                                             bool is_ephemeral_user)
-    : predictor_filename_(
-          profile_path.AppendASCII(kAppLaunchPredictorFilename)) {
-  if (!app_list_features::IsZeroStateAppsRankerEnabled()) {
-    LOG(ERROR) << "AppSearchResultRanker: ZeroStateAppsRanker is not enabled.";
-    return;
-  }
-  // TODO(charleszhao): remove these logs once the test review is done.
-  LOG(ERROR) << "AppSearchResultRanker::AppSearchResultRankerPredictorName "
-             << app_list_features::AppSearchResultRankerPredictorName();
-  predictor_ =
-      CreatePredictor(app_list_features::AppSearchResultRankerPredictorName());
-
-  // MrfuAppLaunchPredictor doesn't have materialization, so no loading from
-  // local disk.
-  if (predictor_->GetPredictorName() ==
-      MrfuAppLaunchPredictor::kPredictorName) {
-    load_from_disk_completed_ = true;
-    return;
-  }
-
-  // For ephemeral users, we disable AppSearchResultRanker to make finch
-  // experiment easier.
-  if (is_ephemeral_user)
-    return;
-
-  task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
-      {base::TaskPriority::BEST_EFFORT, base::MayBlock(),
-       base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
-
-  // Loads the predictor from disk asynchronously.
-  base::PostTaskAndReplyWithResult(
-      task_runner_.get(), FROM_HERE,
-      base::BindOnce(&LoadPredictorFromDiskOnWorkerThread, predictor_filename_,
-                     predictor_->GetPredictorName()),
-      base::BindOnce(&AppSearchResultRanker::OnLoadFromDiskComplete,
-                     weak_factory_.GetWeakPtr()));
-}
-
-AppSearchResultRanker::~AppSearchResultRanker() = default;
-
-void AppSearchResultRanker::Train(const std::string& app_id) {
-  if (load_from_disk_completed_) {
-    predictor_->Train(app_id);
-
-    if (predictor_->ShouldSave()) {
-      // Writes the predictor proto to disk asynchronously.
-      task_runner_->PostTask(
-          FROM_HERE,
-          base::BindOnce(&SaveToDiskOnWorkerThread, predictor_filename_,
-                         predictor_->ToProto()));
-    }
-  }
-}
-
-base::flat_map<std::string, float> AppSearchResultRanker::Rank() {
-  if (load_from_disk_completed_) {
-    return predictor_->Rank();
-  }
-
-  return {};
-}
-
-void AppSearchResultRanker::OnLoadFromDiskComplete(
-    std::unique_ptr<AppLaunchPredictor> predictor) {
-  if (predictor) {
-    predictor_.swap(predictor);
-  }
-  load_from_disk_completed_ = true;
-  LOG(ERROR) << "AppSearchResultRanker::OnLoadFromDiskComplete "
-             << predictor_->GetPredictorName();
-}
-
-}  // namespace app_list
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/app_search_result_ranker.h b/chrome/browser/ui/app_list/search/search_result_ranker/app_search_result_ranker.h
deleted file mode 100644
index 8b4164e7..0000000
--- a/chrome/browser/ui/app_list/search/search_result_ranker/app_search_result_ranker.h
+++ /dev/null
@@ -1,73 +0,0 @@
-// 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.
-
-#ifndef CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_RESULT_RANKER_APP_SEARCH_RESULT_RANKER_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_RESULT_RANKER_APP_SEARCH_RESULT_RANKER_H_
-
-#include <memory>
-#include <string>
-
-#include "base/containers/flat_map.h"
-#include "base/files/file_path.h"
-#include "base/macros.h"
-#include "base/memory/scoped_refptr.h"
-#include "base/memory/weak_ptr.h"
-#include "base/sequenced_task_runner.h"
-
-namespace app_list {
-
-class AppLaunchPredictor;
-
-// AppSearchResultRanker is the main class used to train and re-rank the app
-// launches.
-class AppSearchResultRanker {
- public:
-  // Construct a AppSearchResultRanker with profile. It (possibly)
-  // asynchronously loads model from disk from |profile_path|; and sets
-  // |load_from_disk_success_| to true when the loading finishes.
-  // The internal |predictor_| is constructed with param
-  // SearchResultRankerPredictorName() in "app_list_features.h".
-  // Ephemeral users are speically handled since their profiles are cleaned up
-  // after logging out.
-  AppSearchResultRanker(const base::FilePath& profile_path,
-                        bool is_ephemeral_user);
-
-  ~AppSearchResultRanker();
-
-  // Trains on the |app_id| and (possibly) updates its internal representation.
-  void Train(const std::string& app_id);
-  // Returns a map of app_id and score.
-  //  (1) Higher score means more relevant.
-  //  (2) Only returns a subset of app_ids seen by this predictor.
-  //  (3) The returned scores should be in range [0.0, 1.0] for
-  //      AppSearchProvider to handle.
-  base::flat_map<std::string, float> Rank();
-
- private:
-  FRIEND_TEST_ALL_PREFIXES(AppSearchResultRankerSerializationTest,
-                           LoadFromDiskSucceed);
-  FRIEND_TEST_ALL_PREFIXES(AppSearchResultRankerSerializationTest,
-                           LoadFromDiskFailIfNoFileExists);
-  FRIEND_TEST_ALL_PREFIXES(AppSearchResultRankerSerializationTest,
-                           LoadFromDiskFailWithInvalidProto);
-  FRIEND_TEST_ALL_PREFIXES(AppSearchResultRankerSerializationTest,
-                           SaveToDiskSucceed);
-
-  // Sets |predictor_| and |load_from_disk_completed_| when
-  // LoadPredictorFromDiskOnWorkerThread completes.
-  void OnLoadFromDiskComplete(std::unique_ptr<AppLaunchPredictor> predictor);
-
-  // Internal predictor used for train and rank.
-  std::unique_ptr<AppLaunchPredictor> predictor_;
-  bool load_from_disk_completed_ = false;
-  const base::FilePath predictor_filename_;
-  scoped_refptr<base::SequencedTaskRunner> task_runner_;
-  base::WeakPtrFactory<AppSearchResultRanker> weak_factory_{this};
-
-  DISALLOW_COPY_AND_ASSIGN(AppSearchResultRanker);
-};
-
-}  // namespace app_list
-
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_RESULT_RANKER_APP_SEARCH_RESULT_RANKER_H_
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/app_search_result_ranker_unittest.cc b/chrome/browser/ui/app_list/search/search_result_ranker/app_search_result_ranker_unittest.cc
deleted file mode 100644
index 9823ff9..0000000
--- a/chrome/browser/ui/app_list/search/search_result_ranker/app_search_result_ranker_unittest.cc
+++ /dev/null
@@ -1,216 +0,0 @@
-// 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/app_list/search/search_result_ranker/app_search_result_ranker.h"
-
-#include "ash/public/cpp/app_list/app_list_features.h"
-#include "base/files/file_util.h"
-#include "base/files/scoped_temp_dir.h"
-#include "base/test/scoped_feature_list.h"
-#include "base/test/task_environment.h"
-#include "chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.h"
-#include "chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.pb.h"
-#include "chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor_test_util.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using testing::UnorderedElementsAre;
-using testing::Pair;
-using testing::FloatEq;
-
-namespace app_list {
-
-namespace {
-
-constexpr char kTarget1[] = "Target1";
-constexpr char kTarget2[] = "Target2";
-constexpr bool kNotAnEphemeralUser = false;
-
-}  // namespace
-
-// Test flags of AppSearchResultRanker.
-class AppSearchResultRankerFlagTest : public testing::Test {
- protected:
-  void SetUp() override {
-    Test::SetUp();
-    // Creates file directory.
-    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
-  }
-
-  // Waits for all tasks in to finish.
-  void Wait() { task_environment_.RunUntilIdle(); }
-
-  base::test::TaskEnvironment task_environment_{
-      base::test::TaskEnvironment::MainThreadType::DEFAULT,
-      base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED};
-  base::ScopedTempDir temp_dir_;
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-TEST_F(AppSearchResultRankerFlagTest, TrainAndInfer) {
-  scoped_feature_list_.InitAndEnableFeatureWithParameters(
-      app_list_features::kEnableZeroStateAppsRanker,
-      {{"app_search_result_ranker_predictor_name",
-        FakeAppLaunchPredictor::kPredictorName}});
-
-  AppSearchResultRanker ranker(temp_dir_.GetPath(), kNotAnEphemeralUser);
-  Wait();
-  ranker.Train(kTarget1);
-  ranker.Train(kTarget2);
-  ranker.Train(kTarget2);
-
-  EXPECT_THAT(ranker.Rank(),
-              UnorderedElementsAre(Pair(kTarget1, FloatEq(1.0f)),
-                                   Pair(kTarget2, FloatEq(2.0f))));
-}
-
-TEST_F(AppSearchResultRankerFlagTest, EphemeralUsersAreDisabled) {
-  scoped_feature_list_.InitAndEnableFeatureWithParameters(
-      app_list_features::kEnableZeroStateAppsRanker,
-      {{"app_search_result_ranker_predictor_name",
-        FakeAppLaunchPredictor::kPredictorName}});
-
-  AppSearchResultRanker ranker(temp_dir_.GetPath(), !kNotAnEphemeralUser);
-  Wait();
-  ranker.Train(kTarget1);
-  ranker.Train(kTarget2);
-  ranker.Train(kTarget2);
-
-  EXPECT_TRUE(ranker.Rank().empty());
-}
-
-TEST_F(AppSearchResultRankerFlagTest, ReturnEmptyIfDisabled) {
-  scoped_feature_list_.InitWithFeatures(
-      {}, {app_list_features::kEnableZeroStateAppsRanker});
-
-  AppSearchResultRanker ranker(temp_dir_.GetPath(), kNotAnEphemeralUser);
-  Wait();
-  ranker.Train(kTarget1);
-  ranker.Train(kTarget2);
-
-  EXPECT_TRUE(ranker.Rank().empty());
-}
-
-// Test Serialization of AppSearchResultRanker.
-class AppSearchResultRankerSerializationTest
-    : public AppSearchResultRankerFlagTest {
- protected:
-  void SetUp() override {
-    AppSearchResultRankerFlagTest::SetUp();
-
-    predictor_filename_ =
-        temp_dir_.GetPath().AppendASCII("app_launch_predictor");
-
-    // Sets proto.
-    (*proto_.mutable_fake_app_launch_predictor()
-          ->mutable_rank_result())[kTarget1] = 1.0f;
-    (*proto_.mutable_fake_app_launch_predictor()
-          ->mutable_rank_result())[kTarget2] = 2.0f;
-
-    scoped_feature_list_.InitAndEnableFeatureWithParameters(
-        app_list_features::kEnableZeroStateAppsRanker,
-        {{"app_search_result_ranker_predictor_name",
-          FakeAppLaunchPredictor::kPredictorName}});
-  }
-
-  base::FilePath predictor_filename_;
-  AppLaunchPredictorProto proto_;
-};
-
-TEST_F(AppSearchResultRankerSerializationTest, LoadFromDiskSucceed) {
-  // Prepare file to be loaded.
-  const std::string proto_str = proto_.SerializeAsString();
-  EXPECT_NE(
-      base::WriteFile(predictor_filename_, proto_str.c_str(), proto_str.size()),
-      -1);
-  // Construct ranker.
-  AppSearchResultRanker ranker(temp_dir_.GetPath(), kNotAnEphemeralUser);
-
-  // Check that the file loading is executed in non-blocking way.
-  EXPECT_FALSE(ranker.load_from_disk_completed_);
-
-  // Wait for the loading to finish.
-  Wait();
-
-  // Check loading is complete.
-  EXPECT_TRUE(ranker.load_from_disk_completed_);
-
-  // Check predictor is loaded correctly.
-  EXPECT_THAT(ranker.Rank(),
-              UnorderedElementsAre(Pair(kTarget1, FloatEq((1.0f))),
-                                   Pair(kTarget2, FloatEq(2.0f))));
-}
-
-TEST_F(AppSearchResultRankerSerializationTest, LoadFromDiskFailIfNoFileExists) {
-  // Construct ranker.
-  AppSearchResultRanker ranker(temp_dir_.GetPath(), kNotAnEphemeralUser);
-  // Wait for the loading to finish.
-  Wait();
-
-  // Check loading is complete.
-  EXPECT_TRUE(ranker.load_from_disk_completed_);
-
-  // Check predictor is initialized.
-  EXPECT_TRUE(ranker.Rank().empty());
-}
-
-TEST_F(AppSearchResultRankerSerializationTest,
-       LoadFromDiskFailWithInvalidProto) {
-  const std::string wrong_proto = "abc";
-  // Prepare file to be loaded.
-  EXPECT_NE(base::WriteFile(predictor_filename_, wrong_proto.c_str(),
-                            wrong_proto.size()),
-            -1);
-
-  // Construct ranker.
-  AppSearchResultRanker ranker(temp_dir_.GetPath(), kNotAnEphemeralUser);
-  // Wait for the loading to finish.
-  Wait();
-
-  // Check loading is complete.
-  EXPECT_TRUE(ranker.load_from_disk_completed_);
-
-  // Check predictor is initialized since the proto is not decodable.
-  EXPECT_TRUE(ranker.Rank().empty());
-}
-
-TEST_F(AppSearchResultRankerSerializationTest, SaveToDiskSucceed) {
-  // Construct ranker.
-  AppSearchResultRanker ranker(temp_dir_.GetPath(), kNotAnEphemeralUser);
-  // Wait for the loading to finish.
-  Wait();
-
-  // Check loading is complete.
-  EXPECT_TRUE(ranker.load_from_disk_completed_);
-  // Check predictor is initialized.
-  EXPECT_TRUE(ranker.Rank().empty());
-
-  ranker.Train(kTarget1);
-  ranker.Train(kTarget2);
-
-  // Check the predictor file is not created.
-  EXPECT_FALSE(base::PathExists(predictor_filename_));
-
-  // Set should_save to true.
-  static_cast<FakeAppLaunchPredictor*>(ranker.predictor_.get())
-      ->SetShouldSave(true);
-
-  // Train and wait for the writing to finish.
-  ranker.Train(kTarget2);
-  Wait();
-
-  // Expect the predictor file is created.
-  EXPECT_TRUE(base::PathExists(predictor_filename_));
-
-  // Parse the content of the file.
-  std::string str_written;
-  EXPECT_TRUE(base::ReadFileToString(predictor_filename_, &str_written));
-  AppLaunchPredictorProto proto_written;
-  EXPECT_TRUE(proto_written.ParseFromString(str_written));
-
-  // Expect the content to be proto_.
-  EXPECT_TRUE(EquivToProtoLite(proto_written, proto_));
-}
-
-}  // namespace app_list
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/chip_ranker.cc b/chrome/browser/ui/app_list/search/search_result_ranker/chip_ranker.cc
index 4ad6918..a45b1dc 100644
--- a/chrome/browser/ui/app_list/search/search_result_ranker/chip_ranker.cc
+++ b/chrome/browser/ui/app_list/search/search_result_ranker/chip_ranker.cc
@@ -13,7 +13,6 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/search_result_ranker/app_search_result_ranker.h"
 #include "chrome/browser/ui/app_list/search/search_result_ranker/histogram_util.h"
 #include "chrome/browser/ui/app_list/search/search_result_ranker/ranking_item_util.h"
 #include "chrome/browser/ui/app_list/search/search_result_ranker/recurrence_ranker.h"
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/search_result_ranker.cc b/chrome/browser/ui/app_list/search/search_result_ranker/search_result_ranker.cc
index 0d934715..5f512851 100644
--- a/chrome/browser/ui/app_list/search/search_result_ranker/search_result_ranker.cc
+++ b/chrome/browser/ui/app_list/search/search_result_ranker/search_result_ranker.cc
@@ -29,7 +29,6 @@
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ui/app_list/search/cros_action_history/cros_action_recorder.h"
-#include "chrome/browser/ui/app_list/search/search_result_ranker/app_search_result_ranker.h"
 #include "chrome/browser/ui/app_list/search/search_result_ranker/histogram_util.h"
 #include "chrome/browser/ui/app_list/search/search_result_ranker/ranking_item_util.h"
 #include "chrome/browser/ui/app_list/search/search_result_ranker/recurrence_ranker.h"
diff --git a/chrome/browser/ui/app_list/search/tests/app_search_provider_unittest.cc b/chrome/browser/ui/app_list/search/tests/app_search_provider_unittest.cc
index f40b160..a383264 100644
--- a/chrome/browser/ui/app_list/search/tests/app_search_provider_unittest.cc
+++ b/chrome/browser/ui/app_list/search/tests/app_search_provider_unittest.cc
@@ -30,7 +30,6 @@
 #include "chrome/browser/ui/app_list/arc/arc_app_test.h"
 #include "chrome/browser/ui/app_list/arc/arc_default_app_list.h"
 #include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/search_result_ranker/app_search_result_ranker.h"
 #include "chrome/browser/ui/app_list/search/search_result_ranker/ranking_item_util.h"
 #include "chrome/browser/ui/app_list/test/fake_app_list_model_updater.h"
 #include "chrome/browser/ui/app_list/test/test_app_list_controller_delegate.h"
diff --git a/chrome/browser/ui/autofill/payments/save_card_bubble_controller_impl_unittest.cc b/chrome/browser/ui/autofill/payments/save_card_bubble_controller_impl_unittest.cc
index c7a2051..00c803c 100644
--- a/chrome/browser/ui/autofill/payments/save_card_bubble_controller_impl_unittest.cc
+++ b/chrome/browser/ui/autofill/payments/save_card_bubble_controller_impl_unittest.cc
@@ -39,6 +39,8 @@
 
 namespace autofill {
 
+const base::Time kArbitraryTime = base::Time::FromTimeT(1234567890);
+
 class TestSaveCardBubbleControllerImpl : public SaveCardBubbleControllerImpl {
  public:
   static void CreateForTesting(content::WebContents* web_contents) {
@@ -92,6 +94,7 @@
         ->SetInteger(
             prefs::kAutofillAcceptSaveCreditCardPromptState,
             prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_NONE);
+    test_clock_.SetNow(kArbitraryTime);
   }
 
   void SetLegalMessage(
diff --git a/chrome/browser/ui/bookmarks/bookmark_utils.cc b/chrome/browser/ui/bookmarks/bookmark_utils.cc
index 9b4eb05e..fcb7cec 100644
--- a/chrome/browser/ui/bookmarks/bookmark_utils.cc
+++ b/chrome/browser/ui/bookmarks/bookmark_utils.cc
@@ -22,17 +22,10 @@
 #include "components/user_prefs/user_prefs.h"
 #include "components/vector_icons/vector_icons.h"
 #include "content/public/browser/web_contents.h"
-#include "extensions/buildflags/buildflags.h"
 #include "ui/base/dragdrop/drag_drop_types.h"
 #include "ui/base/dragdrop/drop_target_event.h"
 #include "ui/base/pointer/touch_ui_controller.h"
 
-#if BUILDFLAG(ENABLE_EXTENSIONS)
-#include "chrome/browser/extensions/api/commands/command_service.h"
-#include "extensions/browser/extension_registry.h"
-#include "extensions/common/extension_set.h"
-#endif
-
 #if defined(TOOLKIT_VIEWS)
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/color_utils.h"
@@ -54,50 +47,6 @@
 
 namespace {
 
-// The ways in which extensions may customize the bookmark shortcut.
-enum BookmarkShortcutDisposition {
-  BOOKMARK_SHORTCUT_DISPOSITION_UNCHANGED,
-  BOOKMARK_SHORTCUT_DISPOSITION_REMOVED,
-  BOOKMARK_SHORTCUT_DISPOSITION_OVERRIDE_REQUESTED
-};
-
-// Indicates how the bookmark shortcut has been changed by extensions associated
-// with |profile|, if at all.
-BookmarkShortcutDisposition GetBookmarkShortcutDisposition(Profile* profile) {
-#if BUILDFLAG(ENABLE_EXTENSIONS)
-  extensions::CommandService* command_service =
-      extensions::CommandService::Get(profile);
-
-  extensions::ExtensionRegistry* registry =
-      extensions::ExtensionRegistry::Get(profile);
-  if (!registry)
-    return BOOKMARK_SHORTCUT_DISPOSITION_UNCHANGED;
-
-  const extensions::ExtensionSet& extension_set =
-      registry->enabled_extensions();
-
-  // This flag tracks whether any extension wants the disposition to be
-  // removed.
-  bool removed = false;
-  for (extensions::ExtensionSet::const_iterator i = extension_set.begin();
-       i != extension_set.end();
-       ++i) {
-    // Use the overridden disposition if any extension wants it.
-    if (command_service->RequestsBookmarkShortcutOverride(i->get()))
-      return BOOKMARK_SHORTCUT_DISPOSITION_OVERRIDE_REQUESTED;
-
-    if (!removed &&
-        extensions::CommandService::RemovesBookmarkShortcut(i->get())) {
-      removed = true;
-    }
-  }
-
-  if (removed)
-    return BOOKMARK_SHORTCUT_DISPOSITION_REMOVED;
-#endif
-  return BOOKMARK_SHORTCUT_DISPOSITION_UNCHANGED;
-}
-
 #if defined(TOOLKIT_VIEWS)
 // Image source that flips the supplied source image in RTL.
 class RTLFlipSource : public gfx::ImageSkiaSource {
@@ -188,32 +137,6 @@
              bookmarks::prefs::kShowAppsShortcutInBookmarkBar);
 }
 
-bool ShouldRemoveBookmarkThisTabUI(Profile* profile) {
-  return GetBookmarkShortcutDisposition(profile) ==
-         BOOKMARK_SHORTCUT_DISPOSITION_REMOVED;
-}
-
-bool ShouldRemoveBookmarkAllTabsUI(Profile* profile) {
-#if BUILDFLAG(ENABLE_EXTENSIONS)
-  extensions::ExtensionRegistry* registry =
-      extensions::ExtensionRegistry::Get(profile);
-  if (!registry)
-    return false;
-
-  const extensions::ExtensionSet& extension_set =
-      registry->enabled_extensions();
-
-  for (extensions::ExtensionSet::const_iterator i = extension_set.begin();
-       i != extension_set.end();
-       ++i) {
-    if (extensions::CommandService::RemovesBookmarkAllTabsShortcut(i->get()))
-      return true;
-  }
-#endif
-
-  return false;
-}
-
 int GetBookmarkDragOperation(content::BrowserContext* browser_context,
                              const BookmarkNode* node) {
   PrefService* prefs = user_prefs::UserPrefs::Get(browser_context);
diff --git a/chrome/browser/ui/bookmarks/bookmark_utils.h b/chrome/browser/ui/bookmarks/bookmark_utils.h
index 1626d25..747858d 100644
--- a/chrome/browser/ui/bookmarks/bookmark_utils.h
+++ b/chrome/browser/ui/bookmarks/bookmark_utils.h
@@ -64,14 +64,6 @@
 // Returns true if the Apps shortcut should be displayed in the bookmark bar.
 bool ShouldShowAppsShortcutInBookmarkBar(Profile* profile);
 
-// Whether the menu item and shortcut to bookmark a tab should be removed from
-// the user interface.
-bool ShouldRemoveBookmarkThisTabUI(Profile* profile);
-
-// Whether the menu item and shortcut to bookmark all tabs should be removed
-// from the user interface.
-bool ShouldRemoveBookmarkAllTabsUI(Profile* profile);
-
 // Returns the drag operations for the specified node.
 int GetBookmarkDragOperation(content::BrowserContext* browser_context,
                              const bookmarks::BookmarkNode* node);
diff --git a/chrome/browser/ui/browser_commands.cc b/chrome/browser/ui/browser_commands.cc
index e0e19b8..bcf9e2a 100644
--- a/chrome/browser/ui/browser_commands.cc
+++ b/chrome/browser/ui/browser_commands.cc
@@ -222,18 +222,6 @@
 namespace chrome {
 namespace {
 
-bool CanBookmarkCurrentTabInternal(const Browser* browser,
-                                   bool check_remove_bookmark_ui) {
-  BookmarkModel* model =
-      BookmarkModelFactory::GetForBrowserContext(browser->profile());
-  return browser_defaults::bookmarks_enabled &&
-         browser->profile()->GetPrefs()->GetBoolean(
-             bookmarks::prefs::kEditBookmarksEnabled) &&
-         model && model->loaded() && browser->is_type_normal() &&
-         (!check_remove_bookmark_ui ||
-          !chrome::ShouldRemoveBookmarkThisTabUI(browser->profile()));
-}
-
 #if BUILDFLAG(ENABLE_EXTENSIONS)
 const extensions::Extension* GetExtensionForBrowser(Browser* browser) {
   return extensions::ExtensionRegistry::Get(browser->profile())
@@ -1032,8 +1020,6 @@
 }
 
 void BookmarkCurrentTabAllowingExtensionOverrides(Browser* browser) {
-  DCHECK(!chrome::ShouldRemoveBookmarkThisTabUI(browser->profile()));
-
 #if BUILDFLAG(ENABLE_EXTENSIONS)
   const extensions::Extension* extension = nullptr;
   extensions::Command command;
@@ -1057,7 +1043,12 @@
 }
 
 bool CanBookmarkCurrentTab(const Browser* browser) {
-  return CanBookmarkCurrentTabInternal(browser, true);
+  BookmarkModel* model =
+      BookmarkModelFactory::GetForBrowserContext(browser->profile());
+  return browser_defaults::bookmarks_enabled &&
+         browser->profile()->GetPrefs()->GetBoolean(
+             bookmarks::prefs::kEditBookmarksEnabled) &&
+         model && model->loaded() && browser->is_type_normal();
 }
 
 void BookmarkAllTabs(Browser* browser) {
@@ -1071,8 +1062,7 @@
 
 bool CanBookmarkAllTabs(const Browser* browser) {
   return browser->tab_strip_model()->count() > 1 &&
-         !chrome::ShouldRemoveBookmarkAllTabsUI(browser->profile()) &&
-         CanBookmarkCurrentTabInternal(browser, false);
+         CanBookmarkCurrentTab(browser);
 }
 
 void SaveCreditCard(Browser* browser) {
diff --git a/chrome/browser/ui/extensions/application_launch.cc b/chrome/browser/ui/extensions/application_launch.cc
index 2958d23..e8b995b 100644
--- a/chrome/browser/ui/extensions/application_launch.cc
+++ b/chrome/browser/ui/extensions/application_launch.cc
@@ -6,15 +6,20 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 
 #include "apps/launcher.h"
 #include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
 #include "base/macros.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "chrome/browser/app_mode/app_mode_utils.h"
 #include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/app_service/launch_utils.h"
+#include "chrome/browser/apps/platform_apps/platform_app_launch.h"
 #include "chrome/browser/banners/app_banner_settings_helper.h"
 #include "chrome/browser/engagement/site_engagement_service.h"
 #include "chrome/browser/extensions/extension_service.h"
@@ -25,12 +30,14 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
 #include "chrome/browser/ui/browser_tabstrip.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/extensions/extension_enable_flow.h"
 #include "chrome/browser/ui/extensions/extension_enable_flow_delegate.h"
 #include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h"
+#include "chrome/browser/ui/web_applications/web_app_launch_manager.h"
 #include "chrome/browser/ui/web_applications/web_app_launch_utils.h"
 #include "chrome/browser/web_applications/components/file_handler_manager.h"
 #include "chrome/browser/web_applications/components/web_app_helpers.h"
@@ -515,3 +522,32 @@
       extensions::FeatureProvider::GetAPIFeature("app.runtime");
   return feature && feature->IsAvailableToExtension(extension).is_available();
 }
+
+void LaunchAppWithCallback(
+    Profile* profile,
+    const std::string& app_id,
+    const base::CommandLine& command_line,
+    const base::FilePath& current_directory,
+    base::OnceCallback<void(Browser* browser,
+                            apps::mojom::LaunchContainer container)> callback) {
+  apps::mojom::LaunchContainer container;
+  if (apps::OpenExtensionApplicationWindow(profile, app_id, command_line,
+                                           current_directory)) {
+    const extensions::Extension* extension =
+        extensions::ExtensionRegistry::Get(profile)->GetInstalledExtension(
+            app_id);
+    // TODO(crbug.com/1061843): Remove this when BMO launches.
+    if (extension && extension->from_bookmark())
+      web_app::RecordAppWindowLaunch(profile, app_id);
+
+    container = apps::mojom::LaunchContainer::kLaunchContainerWindow;
+  } else if (apps::OpenExtensionApplicationTab(profile, app_id)) {
+    container = apps::mojom::LaunchContainer::kLaunchContainerTab;
+  } else {
+    // Open an empty browser window as the app_id is invalid.
+    apps::CreateBrowserWithNewTabPage(profile);
+    container = apps::mojom::LaunchContainer::kLaunchContainerNone;
+  }
+  std::move(callback).Run(BrowserList::GetInstance()->GetLastActive(),
+                          container);
+}
diff --git a/chrome/browser/ui/extensions/application_launch.h b/chrome/browser/ui/extensions/application_launch.h
index 3baed70..52b87f5 100644
--- a/chrome/browser/ui/extensions/application_launch.h
+++ b/chrome/browser/ui/extensions/application_launch.h
@@ -11,6 +11,11 @@
 class Browser;
 class Profile;
 
+namespace base {
+class CommandLine;
+class FilePath;
+}  // namespace base
+
 namespace content {
 class WebContents;
 }
@@ -59,4 +64,17 @@
 // chrome.app.runtime.onLaunched event.
 bool CanLaunchViaEvent(const extensions::Extension* extension);
 
+// Attempt to open |app_id| in a new window or tab. Open an empty browser
+// window if unsuccessful. The user's preferred launch container for the app
+// (standalone window or browser tab) is used. |callback| will be called with
+// the container type used to open the app, kLaunchContainerNone if an empty
+// browser window was opened.
+void LaunchAppWithCallback(
+    Profile* profile,
+    const std::string& app_id,
+    const base::CommandLine& command_line,
+    const base::FilePath& current_directory,
+    base::OnceCallback<void(Browser* browser,
+                            apps::mojom::LaunchContainer container)> callback);
+
 #endif  // CHROME_BROWSER_UI_EXTENSIONS_APPLICATION_LAUNCH_H_
diff --git a/chrome/browser/ui/gtk/gtk_ui.cc b/chrome/browser/ui/gtk/gtk_ui.cc
index 5b57ded..ba30b1ca 100644
--- a/chrome/browser/ui/gtk/gtk_ui.cc
+++ b/chrome/browser/ui/gtk/gtk_ui.cc
@@ -1096,7 +1096,12 @@
   gint scale = gtk_widget_get_scale_factor(fake_window_);
   DCHECK_GT(scale, 0);
   gdouble resolution = gdk_screen_get_resolution(screen);
-  return resolution <= 0 ? scale : resolution * scale / kDefaultDPI;
+  const float scale_factor =
+      resolution <= 0 ? scale : resolution * scale / kDefaultDPI;
+
+  // Blacklist scaling factors <120% (crbug.com/484400) and round
+  // to 1 decimal to prevent rendering problems (crbug.com/485183).
+  return scale_factor < 1.2f ? 1.0f : roundf(scale_factor * 10) / 10;
 }
 
 void GtkUi::UpdateDeviceScaleFactor() {
diff --git a/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.cc b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.cc
index e1db7bc..0b4f494 100644
--- a/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.cc
+++ b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.cc
@@ -80,14 +80,18 @@
                                  SendTabToSelfClickResult::kClickItem);
   CreateNewEntry(web_contents_, target_device_name, target_device_guid, GURL(),
                  false);
-  show_message_ = true;
-  UpdateIcon();
 }
 
 void SendTabToSelfBubbleController::OnBubbleClosed() {
   send_tab_to_self_bubble_view_ = nullptr;
 }
 
+void SendTabToSelfBubbleController::ShowConfirmationMessage() {
+  show_message_ = true;
+  Browser* browser = chrome::FindBrowserWithWebContents(web_contents_);
+  browser->window()->UpdatePageActionIcon(PageActionIconType::kSendTabToSelf);
+}
+
 bool SendTabToSelfBubbleController::InitialSendAnimationShown() const {
   return GetProfile()->GetPrefs()->GetBoolean(
       prefs::kInitialSendAnimationShown);
@@ -113,11 +117,6 @@
   FetchDeviceInfo();
 }
 
-void SendTabToSelfBubbleController::UpdateIcon() {
-  Browser* browser = chrome::FindBrowserWithWebContents(web_contents_);
-  browser->window()->UpdatePageActionIcon(PageActionIconType::kSendTabToSelf);
-}
-
 void SendTabToSelfBubbleController::FetchDeviceInfo() {
   valid_devices_.clear();
   SendTabToSelfSyncService* service =
diff --git a/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.h b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.h
index 2468c1a..298f00e 100644
--- a/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.h
+++ b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.h
@@ -55,6 +55,9 @@
   // Close the bubble when the user click on the close button.
   void OnBubbleClosed();
 
+  // Shows the confirmation message in the omnibox.
+  void ShowConfirmationMessage();
+
   // Returns true if the initial "Send" animation that's displayed once per
   // profile was shown.
   bool InitialSendAnimationShown() const;
@@ -77,9 +80,6 @@
   FRIEND_TEST_ALL_PREFIXES(SendTabToSelfBubbleViewImplTest, PopulateScrollView);
   FRIEND_TEST_ALL_PREFIXES(SendTabToSelfBubbleViewImplTest, DevicePressed);
 
-  // Updates the omnibox icon if available.
-  void UpdateIcon();
-
   // Get information of valid devices.
   void FetchDeviceInfo();
 
diff --git a/chrome/browser/ui/startup/startup_browser_creator_impl.cc b/chrome/browser/ui/startup/startup_browser_creator_impl.cc
index d07c15d..72922150 100644
--- a/chrome/browser/ui/startup/startup_browser_creator_impl.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator_impl.cc
@@ -25,8 +25,9 @@
 #include "base/version.h"
 #include "build/branding_buildflags.h"
 #include "build/build_config.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/apps/apps_launch.h"
-#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/apps/platform_apps/install_chrome_app.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
@@ -580,8 +581,10 @@
 
   if (!app_id.empty()) {
     // Opens an empty browser window if the app_id is invalid.
-    apps::LaunchService::Get(profile)->LaunchApplication(
-        app_id, command_line_, cur_dir_, base::BindOnce(&FinalizeWebAppLaunch));
+    apps::AppServiceProxyFactory::GetForProfile(profile)
+        ->BrowserAppLauncher()
+        .LaunchAppWithCallback(app_id, cur_dir_,
+                               base::BindOnce(&FinalizeWebAppLaunch));
     return true;
   }
 
diff --git a/chrome/browser/ui/toolbar/app_menu_model.cc b/chrome/browser/ui/toolbar/app_menu_model.cc
index e2bc74f..dec6a892 100644
--- a/chrome/browser/ui/toolbar/app_menu_model.cc
+++ b/chrome/browser/ui/toolbar/app_menu_model.cc
@@ -698,12 +698,6 @@
       return app_menu_icon_controller_->GetTypeAndSeverity().type ==
              AppMenuIconController::IconType::UPGRADE_NOTIFICATION;
     }
-#if !defined(OS_LINUX) || defined(USE_AURA)
-    case IDC_BOOKMARK_THIS_TAB:
-      return !chrome::ShouldRemoveBookmarkThisTabUI(browser_->profile());
-    case IDC_BOOKMARK_ALL_TABS:
-      return !chrome::ShouldRemoveBookmarkAllTabsUI(browser_->profile());
-#endif
     default:
       return true;
   }
diff --git a/chrome/browser/ui/views/frame/browser_frame_mac.mm b/chrome/browser/ui/views/frame/browser_frame_mac.mm
index ec88c9d..1327e38a 100644
--- a/chrome/browser/ui/views/frame/browser_frame_mac.mm
+++ b/chrome/browser/ui/views/frame/browser_frame_mac.mm
@@ -10,7 +10,6 @@
 #include "chrome/browser/apps/app_shim/app_shim_manager_mac.h"
 #include "chrome/browser/global_keyboard_shortcuts_mac.h"
 #include "chrome/browser/media/router/media_router_feature.h"
-#include "chrome/browser/ui/bookmarks/bookmark_utils.h"
 #include "chrome/browser/ui/browser_command_controller.h"
 #include "chrome/browser/ui/browser_commands.h"
 #import "chrome/browser/ui/cocoa/browser_window_command_handler.h"
@@ -174,24 +173,6 @@
                                             : IDS_ENTER_FULLSCREEN_MAC));
       break;
     }
-    case IDC_BOOKMARK_THIS_TAB: {
-      // Extensions have the ability to hide the bookmark tab menu item.
-      // This only affects the bookmark tab menu item under the main menu.
-      // The bookmark tab menu item under the app menu has its visibility
-      // controlled by AppMenuModel.
-      result->new_hidden_state =
-          chrome::ShouldRemoveBookmarkThisTabUI(browser->profile());
-      break;
-    }
-    case IDC_BOOKMARK_ALL_TABS: {
-      // Extensions have the ability to hide the bookmark all tabs menu
-      // item.  This only affects the bookmark page menu item under the main
-      // menu.  The bookmark page menu item under the app menu has its
-      // visibility controlled by AppMenuModel.
-      result->new_hidden_state =
-          chrome::ShouldRemoveBookmarkAllTabsUI(browser->profile());
-      break;
-    }
     case IDC_SHOW_AS_TAB: {
       // Hide this menu option if the window is tabbed or is the devtools
       // window.
diff --git a/chrome/browser/ui/views/location_bar/star_view.cc b/chrome/browser/ui/views/location_bar/star_view.cc
index 0bd16eb..b5c4f0d 100644
--- a/chrome/browser/ui/views/location_bar/star_view.cc
+++ b/chrome/browser/ui/views/location_bar/star_view.cc
@@ -18,16 +18,12 @@
 #include "chrome/browser/ui/view_ids.h"
 #include "chrome/browser/ui/views/bookmarks/bookmark_bubble_view.h"
 #include "chrome/browser/ui/views/feature_promos/feature_promo_bubble_view.h"
-#include "chrome/common/extensions/manifest_handlers/ui_overrides_handler.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/bookmarks/common/bookmark_pref_names.h"
 #include "components/omnibox/browser/vector_icons.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/variations/variations_associated_data.h"
 #include "content/public/browser/web_contents.h"
-#include "extensions/common/extension_set.h"
-#include "extensions/common/feature_switch.h"
-#include "extensions/common/permissions/permissions_data.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/color_utils.h"
 #include "ui/gfx/paint_vector_icon.h"
@@ -61,8 +57,6 @@
                          page_action_icon_delegate),
       browser_(browser) {
   DCHECK(browser_);
-  extension_observer_.Add(
-      extensions::ExtensionRegistry::Get(browser_->profile()));
   edit_bookmarks_enabled_.Init(
       bookmarks::prefs::kEditBookmarksEnabled, browser_->profile()->GetPrefs(),
       base::BindRepeating(&StarView::EditBookmarksPrefUpdated,
@@ -89,8 +83,7 @@
 
 void StarView::UpdateImpl() {
   SetVisible(browser_defaults::bookmarks_enabled &&
-             edit_bookmarks_enabled_.GetValue() &&
-             !IsBookmarkStarHiddenByExtension());
+             edit_bookmarks_enabled_.GetValue());
 }
 
 void StarView::OnExecuting(PageActionIconView::ExecuteSource execute_source) {
@@ -147,39 +140,6 @@
   }
 }
 
-void StarView::OnExtensionLoaded(content::BrowserContext* browser_context,
-                                 const extensions::Extension* extension) {
-  if (extensions::UIOverrides::RemovesBookmarkButton(extension))
-    Update();
-}
-
-void StarView::OnExtensionUnloaded(content::BrowserContext* browser_context,
-                                   const extensions::Extension* extension,
-                                   extensions::UnloadedExtensionReason reason) {
-  if (extensions::UIOverrides::RemovesBookmarkButton(extension))
-    Update();
-}
-
 void StarView::EditBookmarksPrefUpdated() {
   Update();
 }
-
-bool StarView::IsBookmarkStarHiddenByExtension() const {
-  const extensions::ExtensionSet& extension_set =
-      extensions::ExtensionRegistry::Get(browser_->profile())
-          ->enabled_extensions();
-  for (const scoped_refptr<const extensions::Extension>& extension :
-       extension_set) {
-    if (!extensions::UIOverrides::RemovesBookmarkButton(extension.get()))
-      continue;
-    if (extension->permissions_data()->HasAPIPermission(
-            extensions::APIPermission::kBookmarkManagerPrivate)) {
-      return true;
-    }
-    if (extensions::FeatureSwitch::enable_override_bookmarks_ui()
-            ->IsEnabled()) {
-      return true;
-    }
-  }
-  return false;
-}
diff --git a/chrome/browser/ui/views/location_bar/star_view.h b/chrome/browser/ui/views/location_bar/star_view.h
index 97cacc82..b95676cd 100644
--- a/chrome/browser/ui/views/location_bar/star_view.h
+++ b/chrome/browser/ui/views/location_bar/star_view.h
@@ -9,8 +9,6 @@
 #include "base/scoped_observer.h"
 #include "chrome/browser/ui/views/page_action/page_action_icon_view.h"
 #include "components/prefs/pref_member.h"
-#include "extensions/browser/extension_registry.h"
-#include "extensions/browser/extension_registry_observer.h"
 #include "ui/views/widget/widget.h"
 #include "ui/views/widget/widget_observer.h"
 
@@ -18,9 +16,7 @@
 class CommandUpdater;
 
 // The star icon to show a bookmark bubble.
-class StarView : public PageActionIconView,
-                 public views::WidgetObserver,
-                 public extensions::ExtensionRegistryObserver {
+class StarView : public PageActionIconView, public views::WidgetObserver {
  public:
   StarView(CommandUpdater* command_updater,
            Browser* browser,
@@ -45,13 +41,6 @@
   // views::WidgetObserver:
   void OnWidgetDestroying(views::Widget* widget) override;
 
-  // extensions::ExtensionRegistryObserver:
-  void OnExtensionLoaded(content::BrowserContext* browser_context,
-                         const extensions::Extension* extension) override;
-  void OnExtensionUnloaded(content::BrowserContext* browser_context,
-                           const extensions::Extension* extension,
-                           extensions::UnloadedExtensionReason reason) override;
-
  private:
   void EditBookmarksPrefUpdated();
   bool IsBookmarkStarHiddenByExtension() const;
@@ -65,11 +54,6 @@
   ScopedObserver<views::Widget, views::WidgetObserver> bookmark_promo_observer_{
       this};
 
-  // Observes Extensions for changes to their |extensions::UIOverrides|.
-  ScopedObserver<extensions::ExtensionRegistry,
-                 extensions::ExtensionRegistryObserver>
-      extension_observer_{this};
-
   DISALLOW_COPY_AND_ASSIGN(StarView);
 };
 
diff --git a/chrome/browser/ui/views/location_bar/star_view_interactive_uitest.cc b/chrome/browser/ui/views/location_bar/star_view_interactive_uitest.cc
index d34f2296..a714cd49 100644
--- a/chrome/browser/ui/views/location_bar/star_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/location_bar/star_view_interactive_uitest.cc
@@ -10,14 +10,13 @@
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
-#include "chrome/browser/extensions/extension_browsertest.h"
-#include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/views/bookmarks/bookmark_bubble_view.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/page_action/page_action_icon_view.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
 #include "chrome/common/pref_names.h"
+#include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/interactive_test_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/bookmarks/browser/bookmark_model.h"
@@ -26,22 +25,15 @@
 #include "components/prefs/pref_service.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/test_utils.h"
-#include "extensions/common/extension_builder.h"
-#include "extensions/common/feature_switch.h"
 #include "ui/base/ui_base_switches.h"
 #include "ui/events/event_utils.h"
 #include "ui/views/animation/test/ink_drop_host_view_test_api.h"
 
 namespace {
 
-class StarViewTest : public extensions::ExtensionBrowserTest {
+class StarViewTest : public InProcessBrowserTest {
  public:
-  StarViewTest()
-      // In order to let a vanilla extension override the bookmark star, we have
-      // to enable the switch.
-      : enable_override_(
-            extensions::FeatureSwitch::enable_override_bookmarks_ui(),
-            true) {}
+  StarViewTest() = default;
   ~StarViewTest() override = default;
 
   PageActionIconView* GetStarIcon() {
@@ -51,8 +43,6 @@
   }
 
  private:
-  extensions::FeatureSwitch::ScopedOverride enable_override_;
-
   DISALLOW_COPY_AND_ASSIGN(StarViewTest);
 };
 
@@ -129,32 +119,4 @@
   }
 }
 
-// Test that installing an extension that overrides the bookmark star
-// successfully hides the star.
-IN_PROC_BROWSER_TEST_F(StarViewTest, ExtensionCanOverrideBookmarkStar) {
-  // By default, we should show the star.
-  EXPECT_TRUE(GetStarIcon()->GetVisible());
-
-  // Create and install an extension that overrides the bookmark star.
-  extensions::DictionaryBuilder chrome_ui_overrides;
-  chrome_ui_overrides.Set(
-      "bookmarks_ui",
-      extensions::DictionaryBuilder().Set("remove_button", true).Build());
-  scoped_refptr<const extensions::Extension> extension =
-      extensions::ExtensionBuilder()
-          .SetManifest(
-              extensions::DictionaryBuilder()
-                  .Set("name", "overrides star")
-                  .Set("manifest_version", 2)
-                  .Set("version", "0.1")
-                  .Set("description", "override the star")
-                  .Set("chrome_ui_overrides", chrome_ui_overrides.Build())
-                  .Build())
-          .Build();
-  extension_service()->AddExtension(extension.get());
-
-  // The star should now be hidden.
-  EXPECT_FALSE(GetStarIcon()->GetVisible());
-}
-
 }  // namespace
diff --git a/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.cc b/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.cc
index 2775be73..d5edd0c 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.cc
@@ -199,6 +199,14 @@
   DCHECK(!base::FeatureList::IsEnabled(omnibox::kWebUIOmniboxPopup))
       << "With the WebUI omnibox popup enabled, the code should not try to "
          "fetch the child result view.";
+
+  // TODO(tommycli): https://crbug.com/1063071
+  // Making this method public was a mistake. Outside callers have no idea about
+  // our internal state, and there's now a crash in this area. For now, let's
+  // return nullptr, but the ultimate fix is orinj's OmniboxPopupModel refactor.
+  if (i >= children().size())
+    return nullptr;
+
   return static_cast<OmniboxResultView*>(children()[i]);
 }
 
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
index 2fcd1eb..06c402c4 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
@@ -744,6 +744,12 @@
   if (selected_line == OmniboxPopupModel::kNoMatch)
     return nullptr;
 
+  // TODO(tommycli): https://crbug.com/1063071
+  // Diving into |popup_view_| was a mistake. Here's a hotfix to stop the crash,
+  // but the ultimate fix should be to move this logic into OmniboxPopupModel.
+  if (!popup_view_ || popup_view_->result_view_at(selected_line) == nullptr)
+    return nullptr;
+
   return popup_view_->result_view_at(selected_line)->GetSecondaryButton();
 }
 
@@ -792,6 +798,12 @@
   if (selected_line == OmniboxPopupModel::kNoMatch)
     return false;
 
+  // TODO(tommycli): https://crbug.com/1063071
+  // Diving into |popup_view_| was a mistake. Here's a hotfix to stop the crash,
+  // but the ultimate fix should be to move this logic into OmniboxPopupModel.
+  if (!popup_view_ || popup_view_->result_view_at(selected_line) == nullptr)
+    return false;
+
   return popup_view_->result_view_at(selected_line)
       ->MaybeTriggerSecondaryButton(event);
 }
diff --git a/chrome/browser/ui/views/payments/credit_card_editor_view_controller_browsertest.cc b/chrome/browser/ui/views/payments/credit_card_editor_view_controller_browsertest.cc
index 487820bfd..e96cfce9 100644
--- a/chrome/browser/ui/views/payments/credit_card_editor_view_controller_browsertest.cc
+++ b/chrome/browser/ui/views/payments/credit_card_editor_view_controller_browsertest.cc
@@ -277,7 +277,7 @@
             GetLabelText(static_cast<payments::DialogViewID>(
                 EditorViewController::GetInputFieldViewId(
                     autofill::CREDIT_CARD_NAME_FULL))));
-  EXPECT_EQ(base::ASCIIToUTF16("12/2020"),
+  EXPECT_EQ(card.ExpirationDateForDisplay(),
             GetLabelText(static_cast<payments::DialogViewID>(
                 EditorViewController::GetInputFieldViewId(
                     autofill::CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR))));
diff --git a/chrome/browser/ui/views/payments/payment_request_browsertest.cc b/chrome/browser/ui/views/payments/payment_request_browsertest.cc
index ea48e7b8..d6d466b 100644
--- a/chrome/browser/ui/views/payments/payment_request_browsertest.cc
+++ b/chrome/browser/ui/views/payments/payment_request_browsertest.cc
@@ -147,7 +147,10 @@
   WaitForObservedEvent();
 
   // The actual structure of the card response is unit-tested.
-  ExpectBodyContains({"4111111111111111", "Test User", "11", "2022"});
+  ExpectBodyContains(
+      {"4111111111111111", "Test User",
+       base::UTF16ToUTF8(card.Expiration2DigitMonthAsString()).c_str(),
+       base::UTF16ToUTF8(card.Expiration4DigitYearAsString()).c_str()});
   ExpectBodyContains({"John", "H.", "Doe", "Underworld", "666 Erebus St.",
                       "Apt 8", "Elysium", "CA", "91111", "US", "16502111111"});
 }
diff --git a/chrome/browser/ui/views/payments/payment_request_dialog_view.cc b/chrome/browser/ui/views/payments/payment_request_dialog_view.cc
index baed14a..de89a85d2 100644
--- a/chrome/browser/ui/views/payments/payment_request_dialog_view.cc
+++ b/chrome/browser/ui/views/payments/payment_request_dialog_view.cc
@@ -184,6 +184,7 @@
               request_->web_contents(), GetProfile(), url, std::move(callback)),
           &controller_map_),
       /* animate = */ !request_->skipped_payment_request_ui());
+  request_->OnPaymentHandlerOpenWindowCalled();
   HideProcessingSpinner();
 }
 
diff --git a/chrome/browser/ui/views/payments/payment_request_payment_response_browsertest.cc b/chrome/browser/ui/views/payments/payment_request_payment_response_browsertest.cc
index fe378e8..e331ea0 100644
--- a/chrome/browser/ui/views/payments/payment_request_payment_response_browsertest.cc
+++ b/chrome/browser/ui/views/payments/payment_request_payment_response_browsertest.cc
@@ -5,6 +5,7 @@
 #include <vector>
 
 #include "base/macros.h"
+#include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/views/payments/payment_request_browsertest_base.h"
@@ -45,10 +46,15 @@
   PayWithCreditCardAndWait(base::ASCIIToUTF16("123"));
 
   // Test that the card details were sent to the merchant.
-  ExpectBodyContains({"\"cardNumber\": \"4111111111111111\"",
-                      "\"cardSecurityCode\": \"123\"",
-                      "\"cardholderName\": \"Test User\"",
-                      "\"expiryMonth\": \"11\"", "\"expiryYear\": \"2022\""});
+  ExpectBodyContains(
+      {"\"cardNumber\": \"4111111111111111\"", "\"cardSecurityCode\": \"123\"",
+       "\"cardholderName\": \"Test User\"",
+       base::StringPrintf(
+           "\"expiryMonth\": \"%s\"",
+           base::UTF16ToUTF8(card.Expiration2DigitMonthAsString()).c_str()),
+       base::StringPrintf(
+           "\"expiryYear\": \"%s\"",
+           base::UTF16ToUTF8(card.Expiration4DigitYearAsString()).c_str())});
 
   // Test that the billing address was sent to the merchant.
   ExpectBodyContains(
diff --git a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_icon_view.cc b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_icon_view.cc
index 1f53232..5471e6fe 100644
--- a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_icon_view.cc
+++ b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_icon_view.cc
@@ -13,6 +13,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "components/omnibox/browser/omnibox_edit_model.h"
 #include "components/omnibox/browser/omnibox_view.h"
+#include "components/send_tab_to_self/features.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/strings/grit/ui_strings.h"
 
@@ -54,9 +55,13 @@
     return;
   }
 
-  if (GetVisible()) {
+  if (GetVisible() ||
+      base::FeatureList::IsEnabled(kSendTabToSelfOmniboxSendingAnimation)) {
     SendTabToSelfBubbleController* controller = GetController();
     if (controller && controller->show_message()) {
+      if (!GetVisible()) {
+        SetVisible(true);
+      }
       controller->set_show_message(false);
       if (initial_animation_state_ == AnimationState::kShowing &&
           label()->GetVisible()) {
@@ -67,7 +72,7 @@
         AnimateIn(IDS_BROWSER_SHARING_OMNIBOX_SENDING_LABEL);
       }
     }
-  } else if (omnibox_view->model()->has_focus() &&
+  } else if (!GetVisible() && omnibox_view->model()->has_focus() &&
              !omnibox_view->model()->user_input_in_progress()) {
     SendTabToSelfBubbleController* controller = GetController();
     // Shows the "Send" animation once per profile.
diff --git a/chrome/browser/ui/web_applications/web_app_launch_manager.h b/chrome/browser/ui/web_applications/web_app_launch_manager.h
index ac8a487..8408f4e 100644
--- a/chrome/browser/ui/web_applications/web_app_launch_manager.h
+++ b/chrome/browser/ui/web_applications/web_app_launch_manager.h
@@ -17,6 +17,11 @@
 struct AppLaunchParams;
 }  // namespace apps
 
+namespace base {
+class CommandLine;
+class FilePath;
+}  // namespace base
+
 namespace content {
 class WebContents;
 }  // namespace content
@@ -41,8 +46,8 @@
       const base::CommandLine& command_line,
       const base::FilePath& current_directory,
       base::OnceCallback<void(Browser* browser,
-                              apps::mojom::LaunchContainer container)> callback)
-      override;
+                              apps::mojom::LaunchContainer container)>
+          callback);
 
  private:
   void LaunchWebApplication(
diff --git a/chrome/browser/ui/webui/chromeos/login/assistant_optin_flow_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/assistant_optin_flow_screen_handler.cc
index fc4be59..0875097 100644
--- a/chrome/browser/ui/webui/chromeos/login/assistant_optin_flow_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/assistant_optin_flow_screen_handler.cc
@@ -275,6 +275,12 @@
   }
 }
 
+void AssistantOptInFlowScreenHandler::OnAssistantSettingsEnabled(bool enabled) {
+  // Close the opt-in screen is the Assistant is disabled.
+  if (!enabled)
+    HandleFlowFinished();
+}
+
 void AssistantOptInFlowScreenHandler::OnAssistantStatusChanged(
     ash::mojom::AssistantState state) {
   if (state != ash::mojom::AssistantState::NOT_READY) {
diff --git a/chrome/browser/ui/webui/chromeos/login/assistant_optin_flow_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/assistant_optin_flow_screen_handler.h
index bad0ee1c..5a43ecb 100644
--- a/chrome/browser/ui/webui/chromeos/login/assistant_optin_flow_screen_handler.h
+++ b/chrome/browser/ui/webui/chromeos/login/assistant_optin_flow_screen_handler.h
@@ -96,6 +96,7 @@
   void Initialize() override;
 
   // ash::AssistantStateObserver:
+  void OnAssistantSettingsEnabled(bool enabled) override;
   void OnAssistantStatusChanged(ash::mojom::AssistantState state) override;
 
   // Connect to assistant settings manager.
diff --git a/chrome/browser/ui/webui/settings/chromeos/os_settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/chromeos/os_settings_localized_strings_provider.cc
index 67bf544..212c0efc 100644
--- a/chrome/browser/ui/webui/settings/chromeos/os_settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/os_settings_localized_strings_provider.cc
@@ -1723,6 +1723,10 @@
       l10n_util::GetStringFUTF16(
           IDS_SETTINGS_SEARCH_NO_RESULTS_HELP,
           base::ASCIIToUTF16(chrome::kOsSettingsSearchHelpURL)));
+
+  html_source->AddBoolean(
+      "newOsSettingsSearch",
+      base::FeatureList::IsEnabled(chromeos::features::kNewOsSettingsSearch));
 }
 
 void AddDateTimeStrings(content::WebUIDataSource* html_source) {
diff --git a/chrome/browser/web_applications/components/external_install_options.cc b/chrome/browser/web_applications/components/external_install_options.cc
index cb664fc..615a1e2 100644
--- a/chrome/browser/web_applications/components/external_install_options.cc
+++ b/chrome/browser/web_applications/components/external_install_options.cc
@@ -37,7 +37,8 @@
                   add_to_quick_launch_bar, override_previous_user_uninstall,
                   bypass_service_worker_check, require_manifest,
                   force_reinstall, wait_for_windows_closed, install_placeholder,
-                  reinstall_placeholder, uninstall_and_replace) ==
+                  reinstall_placeholder, uninstall_and_replace,
+                  additional_search_terms) ==
          std::tie(other.url, other.user_display_mode, other.install_source,
                   other.add_to_applications_menu, other.add_to_desktop,
                   other.add_to_quick_launch_bar,
@@ -45,7 +46,7 @@
                   other.bypass_service_worker_check, other.require_manifest,
                   other.force_reinstall, other.wait_for_windows_closed,
                   other.install_placeholder, other.reinstall_placeholder,
-                  other.uninstall_and_replace);
+                  other.uninstall_and_replace, other.additional_search_terms);
 }
 
 std::ostream& operator<<(std::ostream& out,
@@ -72,7 +73,10 @@
              << "\n reinstall_placeholder: "
              << install_options.reinstall_placeholder
              << "\n uninstall_and_replace:\n  "
-             << base::JoinString(install_options.uninstall_and_replace, "\n  ");
+             << base::JoinString(install_options.uninstall_and_replace, "\n  ")
+             << "\n additional_search_terms:\n "
+             << base::JoinString(install_options.additional_search_terms,
+                                 "\n ");
 }
 
 InstallManager::InstallParams ConvertExternalInstallOptionsToParams(
@@ -91,6 +95,8 @@
       install_options.bypass_service_worker_check;
   params.require_manifest = install_options.require_manifest;
 
+  params.additional_search_terms = install_options.additional_search_terms;
+
   return params;
 }
 
diff --git a/chrome/browser/web_applications/components/external_install_options.h b/chrome/browser/web_applications/components/external_install_options.h
index 1a69eb50..d991274 100644
--- a/chrome/browser/web_applications/components/external_install_options.h
+++ b/chrome/browser/web_applications/components/external_install_options.h
@@ -83,6 +83,10 @@
   // A list of app_ids that the Web App System should attempt to uninstall and
   // replace with this app (e.g maintain shelf pins, app list positions).
   std::vector<AppId> uninstall_and_replace;
+
+  // Additional keywords that will be used by the OS when searching for the app.
+  // Only affects Chrome OS.
+  std::vector<std::string> additional_search_terms;
 };
 
 std::ostream& operator<<(std::ostream& out,
diff --git a/chrome/browser/web_applications/components/install_manager.cc b/chrome/browser/web_applications/components/install_manager.cc
index 22ea5a9..2142c69a 100644
--- a/chrome/browser/web_applications/components/install_manager.cc
+++ b/chrome/browser/web_applications/components/install_manager.cc
@@ -8,6 +8,12 @@
 
 namespace web_app {
 
+InstallManager::InstallParams::InstallParams() = default;
+
+InstallManager::InstallParams::~InstallParams() = default;
+
+InstallManager::InstallParams::InstallParams(const InstallParams&) = default;
+
 InstallManager::InstallManager(Profile* profile) : profile_(profile) {}
 
 InstallManager::~InstallManager() = default;
diff --git a/chrome/browser/web_applications/components/install_manager.h b/chrome/browser/web_applications/components/install_manager.h
index 7233c3c9..d85f4ac 100644
--- a/chrome/browser/web_applications/components/install_manager.h
+++ b/chrome/browser/web_applications/components/install_manager.h
@@ -99,6 +99,10 @@
 
   // These params are a subset of ExternalInstallOptions.
   struct InstallParams {
+    InstallParams();
+    ~InstallParams();
+    InstallParams(const InstallParams&);
+
     DisplayMode user_display_mode = DisplayMode::kUndefined;
 
     // URL to be used as start_url if manifest is unavailable.
@@ -110,6 +114,8 @@
 
     bool bypass_service_worker_check = false;
     bool require_manifest = false;
+
+    std::vector<std::string> additional_search_terms;
   };
   // Starts a background web app installation process for a given
   // |web_contents|.
diff --git a/chrome/browser/web_applications/proto/web_app.proto b/chrome/browser/web_applications/proto/web_app.proto
index c5634b9..f24bee9 100644
--- a/chrome/browser/web_applications/proto/web_app.proto
+++ b/chrome/browser/web_applications/proto/web_app.proto
@@ -82,4 +82,6 @@
   repeated int32 downloaded_icon_sizes = 11;
   // A list of file handlers.
   repeated WebAppFileHandlerProto file_handlers = 12;
+  // A list of additional search terms to use when searching for the app.
+  repeated string additional_search_terms = 13;
 }
diff --git a/chrome/browser/web_applications/system_web_app_manager.cc b/chrome/browser/web_applications/system_web_app_manager.cc
index 80f36dd8..d9960f9 100644
--- a/chrome/browser/web_applications/system_web_app_manager.cc
+++ b/chrome/browser/web_applications/system_web_app_manager.cc
@@ -169,6 +169,11 @@
   install_options.bypass_service_worker_check = true;
   install_options.force_reinstall = force_update;
   install_options.uninstall_and_replace = info.uninstall_and_replace;
+
+  const auto& search_terms = info.additional_search_terms;
+  std::transform(search_terms.begin(), search_terms.end(),
+                 std::back_inserter(install_options.additional_search_terms),
+                 [](int term) { return l10n_util::GetStringUTF8(term); });
   return install_options;
 }
 
diff --git a/chrome/browser/web_applications/system_web_app_manager_browsertest.cc b/chrome/browser/web_applications/system_web_app_manager_browsertest.cc
index 994c732..53cf771 100644
--- a/chrome/browser/web_applications/system_web_app_manager_browsertest.cc
+++ b/chrome/browser/web_applications/system_web_app_manager_browsertest.cc
@@ -650,24 +650,15 @@
 
 IN_PROC_BROWSER_TEST_P(SystemWebAppManagerAdditionalSearchTermsTest,
                        AdditionalSearchTerms) {
-  // TODO(crbug.com/1054195): Make the expectation unconditional.
-  const web_app::ProviderType provider = provider_type();
-
   WaitForSystemAppInstallAndLaunch(GetMockAppType());
   AppId app_id = GetManager().GetAppIdForSystemApp(GetMockAppType()).value();
 
   apps::AppServiceProxy* proxy =
       apps::AppServiceProxyFactory::GetForProfile(browser()->profile());
   proxy->AppRegistryCache().ForOneApp(
-      app_id, [provider](const apps::AppUpdate& update) {
-        // TODO(crbug.com/1054195): Unconditionally expect "Security".
-        if (provider == ProviderType::kBookmarkApps) {
-          EXPECT_EQ(std::vector<std::string>({"Security"}),
-                    update.AdditionalSearchTerms());
-        } else {
-          EXPECT_EQ(std::vector<std::string>({}),
-                    update.AdditionalSearchTerms());
-        }
+      app_id, [](const apps::AppUpdate& update) {
+        EXPECT_EQ(std::vector<std::string>({"Security"}),
+                  update.AdditionalSearchTerms());
       });
 }
 
diff --git a/chrome/browser/web_applications/web_app.cc b/chrome/browser/web_applications/web_app.cc
index 5dac424..4c869e8 100644
--- a/chrome/browser/web_applications/web_app.cc
+++ b/chrome/browser/web_applications/web_app.cc
@@ -162,6 +162,11 @@
   file_handlers_ = std::move(file_handlers);
 }
 
+void WebApp::SetAdditionalSearchTerms(
+    std::vector<std::string> additional_search_terms) {
+  additional_search_terms_ = std::move(additional_search_terms);
+}
+
 void WebApp::SetSyncData(SyncData sync_data) {
   sync_data_ = std::move(sync_data);
 }
@@ -205,6 +210,8 @@
     out << "  icon_size_on_disk: " << size << std::endl;
   for (const apps::FileHandler& file_handler : app.file_handlers_)
     out << "  file_handler: " << file_handler << std::endl;
+  for (const std::string& additional_search_term : app.additional_search_terms_)
+    out << "  additional_search_term: " << additional_search_term << std::endl;
 
   return out;
 }
@@ -226,13 +233,15 @@
                   app1.icon_infos_, app1.downloaded_icon_sizes_,
                   app1.display_mode_, app1.user_display_mode_,
                   app1.is_locally_installed_, app1.is_in_sync_install_,
-                  app1.file_handlers_, app1.sync_data_) ==
+                  app1.file_handlers_, app1.additional_search_terms_,
+                  app1.sync_data_) ==
          std::tie(app2.app_id_, app2.sources_, app2.name_, app2.launch_url_,
                   app2.description_, app2.scope_, app2.theme_color_,
                   app2.icon_infos_, app2.downloaded_icon_sizes_,
                   app2.display_mode_, app2.user_display_mode_,
                   app2.is_locally_installed_, app2.is_in_sync_install_,
-                  app2.file_handlers_, app2.sync_data_);
+                  app2.file_handlers_, app2.additional_search_terms_,
+                  app2.sync_data_);
 }
 
 bool operator!=(const WebApp& app1, const WebApp& app2) {
diff --git a/chrome/browser/web_applications/web_app.h b/chrome/browser/web_applications/web_app.h
index bddcba866..0469b28 100644
--- a/chrome/browser/web_applications/web_app.h
+++ b/chrome/browser/web_applications/web_app.h
@@ -74,6 +74,10 @@
 
   const apps::FileHandlers& file_handlers() const { return file_handlers_; }
 
+  const std::vector<std::string>& additional_search_terms() const {
+    return additional_search_terms_;
+  }
+
   // While local |name| and |theme_color| may vary from device to device, the
   // synced copies of these fields are replicated to all devices. The synced
   // copies are read by a device to generate a placeholder icon (if needed). Any
@@ -119,6 +123,8 @@
   // Performs sorting of |sizes| vector. Must be called rarely.
   void SetDownloadedIconSizes(std::vector<SquareSizePx> sizes);
   void SetFileHandlers(apps::FileHandlers file_handlers);
+  void SetAdditionalSearchTerms(
+      std::vector<std::string> additional_search_terms);
 
   void SetSyncData(SyncData sync_data);
 
@@ -149,6 +155,7 @@
   std::vector<WebApplicationIconInfo> icon_infos_;
   std::vector<SquareSizePx> downloaded_icon_sizes_;
   apps::FileHandlers file_handlers_;
+  std::vector<std::string> additional_search_terms_;
 
   SyncData sync_data_;
 };
diff --git a/chrome/browser/web_applications/web_app_database.cc b/chrome/browser/web_applications/web_app_database.cc
index a4284fb..8cf0baed 100644
--- a/chrome/browser/web_applications/web_app_database.cc
+++ b/chrome/browser/web_applications/web_app_database.cc
@@ -151,6 +151,12 @@
     }
   }
 
+  for (const auto& additional_search_term : web_app.additional_search_terms()) {
+    // Additional search terms should be sanitized before being added here.
+    DCHECK(!additional_search_term.empty());
+    local_data->add_additional_search_terms(additional_search_term);
+  }
+
   return local_data;
 }
 
@@ -299,6 +305,17 @@
   }
   web_app->SetFileHandlers(std::move(file_handlers));
 
+  std::vector<std::string> additional_search_terms;
+  for (const std::string& additional_search_term :
+       local_data.additional_search_terms()) {
+    if (additional_search_term.empty()) {
+      DLOG(ERROR) << "WebApp AdditionalSearchTerms proto action parse error";
+      return nullptr;
+    }
+    additional_search_terms.push_back(additional_search_term);
+  }
+  web_app->SetAdditionalSearchTerms(std::move(additional_search_terms));
+
   return web_app;
 }
 
diff --git a/chrome/browser/web_applications/web_app_database_unittest.cc b/chrome/browser/web_applications/web_app_database_unittest.cc
index 3f88259..9fbfdb81 100644
--- a/chrome/browser/web_applications/web_app_database_unittest.cc
+++ b/chrome/browser/web_applications/web_app_database_unittest.cc
@@ -117,6 +117,15 @@
 
     app->SetFileHandlers(CreateFileHandlers(suffix));
 
+    const int num_additional_search_terms = suffix & 7;
+    std::vector<std::string> additional_search_terms(
+        num_additional_search_terms);
+    for (int i = 0; i < num_additional_search_terms; ++i) {
+      additional_search_terms[i] =
+          "Foo_" + base::NumberToString(suffix) + "_" + base::NumberToString(i);
+    }
+    app->SetAdditionalSearchTerms(std::move(additional_search_terms));
+
     WebApp::SyncData sync_data;
     sync_data.name = "Sync" + name;
     sync_data.theme_color = synced_theme_color;
@@ -310,6 +319,7 @@
   EXPECT_TRUE(app->sync_data().name.empty());
   EXPECT_FALSE(app->sync_data().theme_color.has_value());
   EXPECT_TRUE(app->file_handlers().empty());
+  EXPECT_TRUE(app->additional_search_terms().empty());
   controller().RegisterApp(std::move(app));
 
   Registry registry = database_factory().ReadRegistry();
@@ -341,6 +351,7 @@
   EXPECT_TRUE(app_copy->sync_data().name.empty());
   EXPECT_FALSE(app_copy->sync_data().theme_color.has_value());
   EXPECT_TRUE(app_copy->file_handlers().empty());
+  EXPECT_TRUE(app_copy->additional_search_terms().empty());
 }
 
 TEST_F(WebAppDatabaseTest, WebAppWithManyIcons) {
diff --git a/chrome/browser/web_applications/web_app_install_finalizer.cc b/chrome/browser/web_applications/web_app_install_finalizer.cc
index cd85101..55404515 100644
--- a/chrome/browser/web_applications/web_app_install_finalizer.cc
+++ b/chrome/browser/web_applications/web_app_install_finalizer.cc
@@ -162,6 +162,7 @@
                                     : DisplayMode::kBrowser);
   }
 
+  web_app->SetAdditionalSearchTerms(web_app_info.additional_search_terms);
   web_app->AddSource(source);
   web_app->SetIsInSyncInstall(false);
 
diff --git a/chrome/browser/web_applications/web_app_install_task.cc b/chrome/browser/web_applications/web_app_install_task.cc
index d34e0dd78..1048ac7 100644
--- a/chrome/browser/web_applications/web_app_install_task.cc
+++ b/chrome/browser/web_applications/web_app_install_task.cc
@@ -456,6 +456,14 @@
     // redirected. Will be overridden by manifest values if present.
     DCHECK(install_params_->fallback_start_url.is_valid());
     web_app_info->app_url = install_params_->fallback_start_url;
+
+    // If `additional_search_terms` was a manifest property, it would be
+    // sanitized while parsing the manifest. Since it's not, we sanitize it
+    // here.
+    for (std::string& search_term : install_params_->additional_search_terms) {
+      if (!search_term.empty())
+        web_app_info->additional_search_terms.push_back(std::move(search_term));
+    }
   }
 
   data_retriever_->CheckInstallabilityAndRetrieveManifest(
diff --git a/chrome/common/OWNERS b/chrome/common/OWNERS
index 163695ae..b9db373a 100644
--- a/chrome/common/OWNERS
+++ b/chrome/common/OWNERS
@@ -62,3 +62,6 @@
 
 # Heap profiler
 per-file heap_profiler*=alph@chromium.org
+
+# Web Applications
+per-file web_application_info.*=file://chrome/browser/web_applications/OWNERS
diff --git a/chrome/common/extensions/api/autotest_private.idl b/chrome/common/extensions/api/autotest_private.idl
index 649cc99..78ad6c8d 100644
--- a/chrome/common/extensions/api/autotest_private.idl
+++ b/chrome/common/extensions/api/autotest_private.idl
@@ -459,6 +459,7 @@
     boolean isAnimating;
     boolean isOverflow;
     Bounds[] iconsBoundsInScreen;
+    boolean isShelfWidgetAnimating;
   };
 
   // Mapped to HotseatSwipeDescriptor in ash/public/cpp/shelf_ui_info.h.
diff --git a/chrome/common/extensions/api/notifications.idl b/chrome/common/extensions/api/notifications.idl
index 3100d6a..dc6c9502 100644
--- a/chrome/common/extensions/api/notifications.idl
+++ b/chrome/common/extensions/api/notifications.idl
@@ -175,7 +175,7 @@
     static void clear(DOMString notificationId,
                       optional ClearCallback callback);
 
-    // Retrieves all the notifications.
+    // Retrieves all the notifications of this app or extension.
     // |callback|: Returns the set of notification_ids currently in the system.
     static void getAll(GetAllCallback callback);
 
diff --git a/chrome/common/web_application_info.h b/chrome/common/web_application_info.h
index f6e4826..8a7edcb 100644
--- a/chrome/common/web_application_info.h
+++ b/chrome/common/web_application_info.h
@@ -88,6 +88,9 @@
 
   // The extensions and mime types the app can handle.
   std::vector<blink::Manifest::FileHandler> file_handlers;
+
+  // Additional search terms that users can use to find the app.
+  std::vector<std::string> additional_search_terms;
 };
 
 std::ostream& operator<<(std::ostream& out,
diff --git a/chrome/credential_provider/gaiacp/event_logs_upload_manager.cc b/chrome/credential_provider/gaiacp/event_logs_upload_manager.cc
index 63666de7..1dc72112 100644
--- a/chrome/credential_provider/gaiacp/event_logs_upload_manager.cc
+++ b/chrome/credential_provider/gaiacp/event_logs_upload_manager.cc
@@ -56,6 +56,9 @@
 // Maximum number of upload requests to make per upload invocation.
 constexpr int kMaxAllowedNumberOfUploadRequests = 5;
 
+// Maximum number of retries if a HTTP call to the backend fails.
+constexpr unsigned int kMaxNumHttpRetries = 3;
+
 // Maximum size of the log entries payload in bytes per HTTP request.
 // TODO (crbug.com/1043195): Change this to use an experiment flag once an
 // experiment framework for GCPW is available.
@@ -498,7 +501,7 @@
   hr = WinHttpUrlFetcher::BuildRequestAndFetchResultFromHttpService(
       EventLogsUploadManager::Get()->GetGcpwServiceUploadEventViewerLogsUrl(),
       access_token, {}, request_dict, kDefaultUploadLogsRequestTimeout,
-      &request_result);
+      kMaxNumHttpRetries, &request_result);
 
   if (FAILED(hr)) {
     LOGFN(ERROR) << "BuildRequestAndFetchResultFromHttpService hr="
@@ -506,13 +509,6 @@
     return hr;
   }
 
-  if (!request_result.has_value() ||
-      request_result->FindDictKey(kErrorKeyInRequestResult)) {
-    LOGFN(ERROR) << "error="
-                 << *request_result->FindDictKey(kErrorKeyInRequestResult);
-    return E_FAIL;
-  }
-
   // Store the chunk id which is the last uploaded event log id
   // in registry so we know where to start next time.
   SetGlobalFlag(kEventLogUploadLastUploadedIdRegKey, chunk_id);
diff --git a/chrome/credential_provider/gaiacp/gem_device_details_manager.cc b/chrome/credential_provider/gaiacp/gem_device_details_manager.cc
index 6f37a2a4..375923a9 100644
--- a/chrome/credential_provider/gaiacp/gem_device_details_manager.cc
+++ b/chrome/credential_provider/gaiacp/gem_device_details_manager.cc
@@ -49,6 +49,9 @@
 const char kMacAddressParameterName[] = "wlan_mac_addr";
 const char kUploadDeviceDetailsResponseDeviceResourceIdParameterName[] =
     "deviceResourceId";
+
+// Maximum number of retries if a HTTP call to the backend fails.
+constexpr unsigned int kMaxNumHttpRetries = 3;
 }  // namespace
 
 // static
@@ -127,7 +130,7 @@
   hr = WinHttpUrlFetcher::BuildRequestAndFetchResultFromHttpService(
       GemDeviceDetailsManager::Get()->GetGemServiceUploadDeviceDetailsUrl(),
       access_token, {}, *request_dict_, upload_device_details_request_timeout_,
-      &request_result);
+      kMaxNumHttpRetries, &request_result);
 
   if (FAILED(hr)) {
     LOGFN(ERROR) << "BuildRequestAndFetchResultFromHttpService hr="
@@ -135,13 +138,6 @@
     return E_FAIL;
   }
 
-  base::Value* error_detail =
-      request_result->FindDictKey(kErrorKeyInRequestResult);
-  if (error_detail) {
-    LOGFN(ERROR) << "error=" << *error_detail;
-    hr = E_FAIL;
-  }
-
   std::string* resource_id = request_result->FindStringKey(
       kUploadDeviceDetailsResponseDeviceResourceIdParameterName);
   if (resource_id) {
diff --git a/chrome/credential_provider/gaiacp/password_recovery_manager.cc b/chrome/credential_provider/gaiacp/password_recovery_manager.cc
index 5fa34af..437967ba 100644
--- a/chrome/credential_provider/gaiacp/password_recovery_manager.cc
+++ b/chrome/credential_provider/gaiacp/password_recovery_manager.cc
@@ -80,6 +80,9 @@
 
 constexpr size_t kSessionKeyLength = 32;
 
+// Maximum number of retries if a HTTP call to the backend fails.
+constexpr unsigned int kMaxNumHttpRetries = 3;
+
 bool Base64DecodeCryptographicKey(const std::string& cryptographic_key,
                                   std::string* out) {
   std::string cryptographic_key_copy;
@@ -275,7 +278,8 @@
   // |public_key| to be used for encryption.
   HRESULT hr = WinHttpUrlFetcher::BuildRequestAndFetchResultFromHttpService(
       PasswordRecoveryManager::Get()->GetEscrowServiceGenerateKeyPairUrl(),
-      access_token, {}, request_dict, request_timeout, &request_result);
+      access_token, {}, request_dict, request_timeout, kMaxNumHttpRetries,
+      &request_result);
 
   if (FAILED(hr)) {
     LOGFN(ERROR) << "BuildRequestAndFetchResultFromHttpService hr="
@@ -363,7 +367,8 @@
   HRESULT hr = WinHttpUrlFetcher::BuildRequestAndFetchResultFromHttpService(
       PasswordRecoveryManager::Get()->GetEscrowServiceGetPrivateKeyUrl(
           *resource_id),
-      access_token, {}, {}, request_timeout, &request_result);
+      access_token, {}, {}, request_timeout, kMaxNumHttpRetries,
+      &request_result);
 
   if (FAILED(hr)) {
     LOGFN(ERROR) << "BuildRequestAndFetchResultFromHttpService hr="
diff --git a/chrome/credential_provider/gaiacp/win_http_url_fetcher.cc b/chrome/credential_provider/gaiacp/win_http_url_fetcher.cc
index 0e20189..7e0fb1d3 100644
--- a/chrome/credential_provider/gaiacp/win_http_url_fetcher.cc
+++ b/chrome/credential_provider/gaiacp/win_http_url_fetcher.cc
@@ -10,6 +10,8 @@
 #include <atlconv.h>
 #include <process.h>
 
+#include <set>
+
 #include "base/base64.h"
 #include "base/containers/span.h"
 #include "base/json/json_reader.h"
@@ -20,6 +22,18 @@
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/credential_provider/gaiacp/logging.h"
+#include "chrome/credential_provider/gaiacp/mdm_utils.h"
+
+namespace {
+// Key name containing the HTTP error code within the dictionary returned by the
+// server in case of errors.
+constexpr char kHttpErrorCodeKeyNameInResponse[] = "code";
+
+// The HTTP response codes for which the request is re-tried on failure.
+const std::set<int> kRetryableHttpErrorCodes = {
+    503,  // Service Unavailable
+    504   // Gateway Timeout
+};
 
 // Self deleting http service requester. This class will try to make a query
 // using the given url fetcher. It will delete itself when the request is
@@ -41,11 +55,12 @@
 //       thread can self delete.
 class HttpServiceRequest {
  public:
-  explicit HttpServiceRequest(
-      std::unique_ptr<credential_provider::WinHttpUrlFetcher> fetcher)
-      : fetcher_(std::move(fetcher)) {
-    DCHECK(fetcher_);
-  }
+  static HttpServiceRequest* Create(
+      const GURL& request_url,
+      const std::string& access_token,
+      const std::vector<std::pair<std::string, std::string>>& headers,
+      const std::string& request_body,
+      const base::TimeDelta& request_timeout);
 
   // Tries to fetch the request stored in |fetcher_| in a background thread
   // within the given |request_timeout|. If the background thread returns before
@@ -103,6 +118,12 @@
   }
 
  private:
+  explicit HttpServiceRequest(
+      std::unique_ptr<credential_provider::WinHttpUrlFetcher> fetcher)
+      : fetcher_(std::move(fetcher)) {
+    DCHECK(fetcher_);
+  }
+
   void OrphanRequest() {
     bool delete_self = false;
     {
@@ -158,6 +179,44 @@
   bool is_processing_ = true;
 };
 
+HttpServiceRequest* HttpServiceRequest::Create(
+    const GURL& request_url,
+    const std::string& access_token,
+    const std::vector<std::pair<std::string, std::string>>& headers,
+    const std::string& request_body,
+    const base::TimeDelta& request_timeout) {
+  auto url_fetcher =
+      credential_provider::WinHttpUrlFetcher::Create(request_url);
+  if (!url_fetcher) {
+    LOGFN(ERROR) << "Could not create valid fetcher for url="
+                 << request_url.spec();
+    return nullptr;
+  }
+
+  url_fetcher->SetRequestHeader("Content-Type", "application/json");
+  url_fetcher->SetRequestHeader("Authorization",
+                                ("Bearer " + access_token).c_str());
+  for (auto& header : headers)
+    url_fetcher->SetRequestHeader(header.first.c_str(), header.second.c_str());
+
+  if (!request_body.empty()) {
+    HRESULT hr = url_fetcher->SetRequestBody(request_body.c_str());
+    if (FAILED(hr)) {
+      LOGFN(ERROR) << "fetcher.SetRequestBody hr="
+                   << credential_provider::putHR(hr);
+      return nullptr;
+    }
+  }
+
+  if (!request_timeout.is_zero()) {
+    url_fetcher->SetHttpRequestTimeout(request_timeout.InMilliseconds());
+  }
+
+  return (new HttpServiceRequest(std::move(url_fetcher)));
+}
+
+}  // namespace
+
 namespace credential_provider {
 
 // static
@@ -353,50 +412,56 @@
     const std::vector<std::pair<std::string, std::string>>& headers,
     const base::Value& request_dict,
     const base::TimeDelta& request_timeout,
+    unsigned int request_retries,
     base::Optional<base::Value>* request_result) {
   DCHECK(request_result);
-
-  auto url_fetcher = WinHttpUrlFetcher::Create(request_url);
-  if (!url_fetcher) {
-    LOGFN(ERROR) << "Could not create valid fetcher for url="
-                 << request_url.spec();
-    return E_FAIL;
-  }
-
-  url_fetcher->SetRequestHeader("Content-Type", "application/json");
-  url_fetcher->SetRequestHeader("Authorization",
-                                ("Bearer " + access_token).c_str());
-  for (auto& header : headers)
-    url_fetcher->SetRequestHeader(header.first.c_str(), header.second.c_str());
-
   HRESULT hr = S_OK;
 
+  std::string request_body;
   if (request_dict.is_dict()) {
-    std::string body;
-    if (!base::JSONWriter::Write(request_dict, &body)) {
+    if (!base::JSONWriter::Write(request_dict, &request_body)) {
       LOGFN(ERROR) << "base::JSONWriter::Write failed";
       return E_FAIL;
     }
+  }
 
-    hr = url_fetcher->SetRequestBody(body.c_str());
-    if (FAILED(hr)) {
-      LOGFN(ERROR) << "fetcher.SetRequestBody hr=" << putHR(hr);
+  for (unsigned int try_count = 0; try_count <= request_retries; ++try_count) {
+    HttpServiceRequest* request = HttpServiceRequest::Create(
+        request_url, access_token, headers, request_body, request_timeout);
+
+    if (!request)
       return E_FAIL;
+
+    auto extracted_param =
+        request->WaitForResponseFromHttpService(request_timeout);
+
+    if (!extracted_param) {
+      hr = E_FAIL;
+      continue;
     }
+    *request_result = std::move(extracted_param);
+
+    base::Value* error_detail =
+        (*request_result)->FindDictKey(kErrorKeyInRequestResult);
+    if (error_detail) {
+      hr = E_FAIL;
+      LOGFN(ERROR) << "error: " << *error_detail;
+
+      // If error code is known, retry only on retryable server errors.
+      base::Optional<int> error_code =
+          error_detail->FindIntKey(kHttpErrorCodeKeyNameInResponse);
+      if (error_code.has_value() &&
+          kRetryableHttpErrorCodes.find(error_code.value()) ==
+              kRetryableHttpErrorCodes.end())
+        break;
+
+      continue;
+    }
+
+    hr = S_OK;
+    break;
   }
 
-  if (!request_timeout.is_zero()) {
-    url_fetcher->SetHttpRequestTimeout(request_timeout.InMilliseconds());
-  }
-
-  auto extracted_param = (new HttpServiceRequest(std::move(url_fetcher)))
-                             ->WaitForResponseFromHttpService(request_timeout);
-
-  if (!extracted_param)
-    return E_FAIL;
-
-  *request_result = std::move(extracted_param);
-
   return hr;
 }
 }  // namespace credential_provider
diff --git a/chrome/credential_provider/gaiacp/win_http_url_fetcher.h b/chrome/credential_provider/gaiacp/win_http_url_fetcher.h
index c53c079..a22a519 100644
--- a/chrome/credential_provider/gaiacp/win_http_url_fetcher.h
+++ b/chrome/credential_provider/gaiacp/win_http_url_fetcher.h
@@ -30,13 +30,17 @@
   // value pairs to be sent with the request. |request_dict| is a dictionary of
   // json parameters to be sent with the request. This argument will be
   // converted to a json string and sent as the body of the request.
-  // |request_timeout| is the maximum time to wait for a response.
+  // |request_timeout| is the maximum time to wait for a response. If the HTTP
+  // request times out or fails with an error response code that signifies an
+  // internal server error (HTTP codes >= 500) then the request will be retried
+  // |request_retries| number of times before the call is marked failed.
   static HRESULT BuildRequestAndFetchResultFromHttpService(
       const GURL& request_url,
       std::string access_token,
       const std::vector<std::pair<std::string, std::string>>& headers,
       const base::Value& request_dict,
       const base::TimeDelta& request_timeout,
+      unsigned int request_retries,
       base::Optional<base::Value>* request_result);
 
   virtual ~WinHttpUrlFetcher();
diff --git a/chrome/credential_provider/gaiacp/win_http_url_fetcher_unittests.cc b/chrome/credential_provider/gaiacp/win_http_url_fetcher_unittests.cc
index 8daeca47..07e510c 100644
--- a/chrome/credential_provider/gaiacp/win_http_url_fetcher_unittests.cc
+++ b/chrome/credential_provider/gaiacp/win_http_url_fetcher_unittests.cc
@@ -13,14 +13,22 @@
 // Test the WinHttpUrlFetcher::BuildRequestAndFetchResultFromHttpService method
 // used to make various HTTP requests.
 // Parameters are:
-// bool  true:  HTTP call succeeds.
-//       false: Fails due to invalid response from server.
-class GcpWinHttpUrlFetcherTest : public GlsRunnerTestBase,
-                                 public ::testing::WithParamInterface<bool> {};
+// 1.  int - 0:  HTTP call succeeds.
+//           1:  Fails due to invalid response from server.
+//           2:  Fails due to a retryable HTTP error (like 503).
+//           3:  Fails due to a non-retryable HTTP error (like 404).
+// 2.  int - Number of retries allowed for a HTTP request.
+class GcpWinHttpUrlFetcherTest
+    : public GlsRunnerTestBase,
+      public ::testing::WithParamInterface<std::tuple<int, int>> {};
 
 TEST_P(GcpWinHttpUrlFetcherTest,
        BuildRequestAndFetchResultFromHttpServiceTest) {
-  bool invalid_response = GetParam();
+  bool valid_response = std::get<0>(GetParam()) == 0;
+  bool invalid_response = std::get<0>(GetParam()) == 1;
+  bool retryable_error_response = std::get<0>(GetParam()) == 2;
+  bool nonretryable_error_response = std::get<0>(GetParam()) == 3;
+  int num_retries = std::get<1>(GetParam());
 
   const int timeout_in_millis = 12000;
   const std::string header1 = "test-header-1";
@@ -42,23 +50,64 @@
   std::string expected_response;
   base::JSONWriter::Write(expected_result, &expected_response);
 
-  fake_http_url_fetcher_factory()->SetFakeResponse(
-      test_url, FakeWinHttpUrlFetcher::Headers(),
-      invalid_response ? "Invalid json response" : expected_response);
+  std::string response;
+  if (invalid_response) {
+    response = "Invalid json response";
+  } else if (retryable_error_response) {
+    response =
+        "{\n\"error\": {"
+        "\"code\": 503,\n"
+        "\"message\": \"Service unavailable\","
+        "\"status\": \"UNAVAILABLE\"\n}\n}";
+  } else if (nonretryable_error_response) {
+    response =
+        "{\n\"error\": {"
+        "\"code\": 403,\n"
+        "\"message\": \"The caller does not have permission\","
+        "\"status\": \"PERMISSION_DENIED\"\n}\n}";
+  } else {
+    response = expected_response;
+  }
+
+  if (num_retries == 0) {
+    fake_http_url_fetcher_factory()->SetFakeResponse(
+        test_url, FakeWinHttpUrlFetcher::Headers(), response);
+  } else {
+    fake_http_url_fetcher_factory()->SetFakeResponseForSpecifiedNumRequests(
+        test_url, FakeWinHttpUrlFetcher::Headers(), response, num_retries);
+    fake_http_url_fetcher_factory()->SetFakeResponseForSpecifiedNumRequests(
+        test_url, FakeWinHttpUrlFetcher::Headers(), expected_response, 1);
+  }
   fake_http_url_fetcher_factory()->SetCollectRequestData(true);
 
   HRESULT hr = WinHttpUrlFetcher::BuildRequestAndFetchResultFromHttpService(
       test_url, access_token, {{header1, header1_value}}, request,
-      request_timeout, &request_result);
+      request_timeout, num_retries, &request_result);
 
-  if (invalid_response) {
-    ASSERT_TRUE(FAILED(hr));
+  if (num_retries == 0) {
+    if (invalid_response || retryable_error_response ||
+        nonretryable_error_response) {
+      ASSERT_TRUE(FAILED(hr));
+    } else {
+      ASSERT_EQ(S_OK, hr);
+      ASSERT_EQ(expected_result, request_result.value());
+    }
   } else {
-    ASSERT_EQ(S_OK, hr);
-    ASSERT_EQ(expected_result, request_result.value());
+    if (nonretryable_error_response) {
+      ASSERT_TRUE(FAILED(hr));
+    } else {
+      ASSERT_EQ(S_OK, hr);
+      ASSERT_EQ(expected_result, request_result.value());
+    }
   }
 
-  ASSERT_TRUE(fake_http_url_fetcher_factory()->requests_created() > 0);
+  if (valid_response || nonretryable_error_response) {
+    ASSERT_EQ(1UL, fake_http_url_fetcher_factory()->requests_created());
+  } else {
+    ASSERT_EQ(num_retries + 1UL,
+              fake_http_url_fetcher_factory()->requests_created());
+  }
+
   for (size_t idx = 0;
        idx < fake_http_url_fetcher_factory()->requests_created(); ++idx) {
     FakeWinHttpUrlFetcherFactory::RequestData request_data =
@@ -78,7 +127,8 @@
 
 INSTANTIATE_TEST_SUITE_P(All,
                          GcpWinHttpUrlFetcherTest,
-                         ::testing::Values(true, false));
+                         ::testing::Combine(::testing::Values(0, 1, 2, 3),
+                                            ::testing::Values(0, 1, 3)));
 
 }  // namespace testing
 }  // namespace credential_provider
diff --git a/chrome/credential_provider/test/gcp_fakes.cc b/chrome/credential_provider/test/gcp_fakes.cc
index 8d098ae2..ee8e868 100644
--- a/chrome/credential_provider/test/gcp_fakes.cc
+++ b/chrome/credential_provider/test/gcp_fakes.cc
@@ -641,8 +641,26 @@
     const WinHttpUrlFetcher::Headers& headers,
     const std::string& response,
     HANDLE send_response_event_handle /*=INVALID_HANDLE_VALUE*/) {
-  fake_responses_[url] =
-      Response(headers, response, send_response_event_handle);
+  fake_responses_[url].clear();
+  fake_responses_[url].push_back(
+      Response(headers, response, send_response_event_handle));
+  remove_fake_response_when_created_ = false;
+}
+
+void FakeWinHttpUrlFetcherFactory::SetFakeResponseForSpecifiedNumRequests(
+    const GURL& url,
+    const WinHttpUrlFetcher::Headers& headers,
+    const std::string& response,
+    unsigned int num_requests,
+    HANDLE send_response_event_handle /* =INVALID_HANDLE_VALUE */) {
+  if (fake_responses_.find(url) == fake_responses_.end()) {
+    fake_responses_[url] = std::deque<Response>();
+  }
+  for (unsigned int i = 0; i < num_requests; ++i) {
+    fake_responses_[url].push_back(
+        Response(headers, response, send_response_event_handle));
+  }
+  remove_fake_response_when_created_ = true;
 }
 
 void FakeWinHttpUrlFetcherFactory::SetFakeFailedResponse(const GURL& url,
@@ -667,11 +685,17 @@
   FakeWinHttpUrlFetcher* fetcher = new FakeWinHttpUrlFetcher(std::move(url));
 
   if (fake_responses_.count(url) != 0) {
-    const Response& response = fake_responses_[url];
+    const Response& response = fake_responses_[url].front();
 
     fetcher->response_headers_ = response.headers;
     fetcher->response_ = response.response;
     fetcher->send_response_event_handle_ = response.send_response_event_handle;
+
+    if (remove_fake_response_when_created_) {
+      fake_responses_[url].pop_front();
+      if (fake_responses_[url].empty())
+        fake_responses_.erase(url);
+    }
   } else {
     DCHECK(failed_http_fetch_hr_.count(url) > 0);
     fetcher->response_hr_ = failed_http_fetch_hr_[url];
diff --git a/chrome/credential_provider/test/gcp_fakes.h b/chrome/credential_provider/test/gcp_fakes.h
index 5faf444f..2d2c973 100644
--- a/chrome/credential_provider/test/gcp_fakes.h
+++ b/chrome/credential_provider/test/gcp_fakes.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_CREDENTIAL_PROVIDER_TEST_GCP_FAKES_H_
 #define CHROME_CREDENTIAL_PROVIDER_TEST_GCP_FAKES_H_
 
+#include <deque>
 #include <map>
 #include <memory>
 #include <string>
@@ -296,12 +297,23 @@
   FakeWinHttpUrlFetcherFactory();
   ~FakeWinHttpUrlFetcherFactory();
 
+  // Sets the given |response| for any number of HTTP requests made for |url|.
   void SetFakeResponse(
       const GURL& url,
       const WinHttpUrlFetcher::Headers& headers,
       const std::string& response,
       HANDLE send_response_event_handle = INVALID_HANDLE_VALUE);
 
+  // Queues the given |response| for the specified |num_requests| number of HTTP
+  // requests made for |url|. Different responses for the URL can be set by
+  // calling this function multiple times with different responses.
+  void SetFakeResponseForSpecifiedNumRequests(
+      const GURL& url,
+      const WinHttpUrlFetcher::Headers& headers,
+      const std::string& response,
+      unsigned int num_requests,
+      HANDLE send_response_event_handle = INVALID_HANDLE_VALUE);
+
   // Sets the response as a failed http attempt. The return result
   // from http_url_fetcher.Fetch() would be set as the input HRESULT
   // to this method.
@@ -343,10 +355,11 @@
     HANDLE send_response_event_handle;
   };
 
-  std::map<GURL, Response> fake_responses_;
+  std::map<GURL, std::deque<Response>> fake_responses_;
   std::map<GURL, HRESULT> failed_http_fetch_hr_;
   size_t requests_created_ = 0;
   bool collect_request_data_ = false;
+  bool remove_fake_response_when_created_ = false;
   std::vector<RequestData> requests_data_;
 };
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 6cb0bdd2..82b4975 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1130,6 +1130,7 @@
       "../browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_views_browsertest.cc",
       "../browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.cc",
       "../browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.h",
+      "../browser/safe_browsing/download_protection/deep_scanning_browsertest.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",
@@ -1728,7 +1729,6 @@
         "../browser/extensions/chrome_app_api_browsertest.cc",
         "../browser/extensions/chrome_test_extension_loader_browsertest.cc",
         "../browser/extensions/chrome_theme_url_browsertest.cc",
-        "../browser/extensions/chrome_ui_overrides_browsertest.cc",
         "../browser/extensions/content_capabilities_browsertest.cc",
         "../browser/extensions/content_script_apitest.cc",
         "../browser/extensions/content_security_policy_apitest.cc",
@@ -4372,9 +4372,7 @@
       "../browser/ui/app_list/search/cros_action_history/cros_action_recorder_unittest.cc",
       "../browser/ui/app_list/search/launcher_search/launcher_search_icon_image_loader_unittest.cc",
       "../browser/ui/app_list/search/search_result_ranker/app_launch_event_logger_unittest.cc",
-      "../browser/ui/app_list/search/search_result_ranker/app_launch_predictor_unittest.cc",
       "../browser/ui/app_list/search/search_result_ranker/app_list_launch_metrics_provider_unittest.cc",
-      "../browser/ui/app_list/search/search_result_ranker/app_search_result_ranker_unittest.cc",
       "../browser/ui/app_list/search/search_result_ranker/chip_ranker_unittest.cc",
       "../browser/ui/app_list/search/search_result_ranker/frecency_store_unittest.cc",
       "../browser/ui/app_list/search/search_result_ranker/histogram_util_unittest.cc",
@@ -6831,8 +6829,8 @@
   testonly = true
 
   deps = [
+    "base:closure_compile",
     "data:closure_compile",
-    # TODO(crbug/1000989): Add a dep for base/js2gtest.js.
   ]
 }
 
diff --git a/chrome/test/base/BUILD.gn b/chrome/test/base/BUILD.gn
new file mode 100644
index 0000000..f06af47
--- /dev/null
+++ b/chrome/test/base/BUILD.gn
@@ -0,0 +1,14 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/closure_compiler/compile_js.gni")
+
+js_type_check("closure_compile") {
+  deps = [ ":js2gtest" ]
+}
+
+js_library("js2gtest") {
+  deps = [ "//chrome/test/data:test_api_js" ]
+  externs_list = [ "d8_externs.js" ]
+}
diff --git a/chrome/test/base/OWNERS b/chrome/test/base/OWNERS
index 5ab77afc..28da547c 100644
--- a/chrome/test/base/OWNERS
+++ b/chrome/test/base/OWNERS
@@ -7,6 +7,7 @@
 per-file javascript_browser_test.*=dtseng@chromium.org
 per-file js2gtest.*=dtseng@chromium.org
 per-file js2gtest.*=file://ui/webui/PLATFORM_OWNERS
+per-file *.js=file://ui/webui/PLATFORM_OWNERS
 
 per-file *javascript*=file://ui/webui/PLATFORM_OWNERS
 per-file *web_ui*=file://ui/webui/PLATFORM_OWNERS
diff --git a/chrome/test/base/d8_externs.js b/chrome/test/base/d8_externs.js
new file mode 100644
index 0000000..affe3ed
--- /dev/null
+++ b/chrome/test/base/d8_externs.js
@@ -0,0 +1,20 @@
+// Copyright 2020 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.
+
+/** @fileoverview
+ * Externs for d8, v8's shell at v8/src/d8/.
+ * @externs
+ */
+
+/** @param {...string} var_args */
+function print(var_args) {}
+
+/** @param {number} code */
+function quit(code) {}
+
+/**
+ * @param {string} path
+ * @return {string}
+ */
+function read(path) {}
diff --git a/chrome/test/base/js2gtest.js b/chrome/test/base/js2gtest.js
index de0be48..6939664e 100644
--- a/chrome/test/base/js2gtest.js
+++ b/chrome/test/base/js2gtest.js
@@ -24,7 +24,7 @@
   quit(-1);
 }
 
-[_,
+const [_,
  // Full path to the test input file, relative to the current working
  // directory.
  fullTestFilePath,
@@ -94,7 +94,7 @@
  * Helpful hint pointing back to the source js.
  * @type {string}
  */
-const argHint = '// ' + arguments.join(' ');
+const argHint = '// ' + Array.from(arguments).join(' ');
 
 /**
  * @type {Array<string>}
@@ -117,6 +117,7 @@
 /**
  * Generates the header of the cc file to stdout.
  * @param {string?} testFixture Name of test fixture.
+ * @this {!Object}
  */
 function maybeGenHeader(testFixture) {
   if (!needGenHeader) {
@@ -181,9 +182,7 @@
 }
 
 
-/**
- * @type {Array<{path: string, base: string>}
- */
+/** @type {!Array<string>} */
 const pathStack = [];
 
 
@@ -204,6 +203,7 @@
         'Only relative "foo/bar" or source-absolute "//foo/bar" paths are ' +
         'supported - not file-system absolute: "/foo/bar"');
     quit(-1);
+    return '';
   } else {
     // The include-file path is relative to the file that included it.
     const currentPath = pathStack[pathStack.length - 1];
@@ -252,8 +252,8 @@
    * Called by the javascript in the deps file to add modules and their
    * dependencies.
    * @param {string} path Relative path to the file.
-   * @param Array<string> provides Objects provided by this file.
-   * @param Array<string> requires Objects required by this file.
+   * @param {!Array<string>} provides Objects provided by this file.
+   * @param {!Array<string>} requires Objects required by this file.
    */
   goog.addDependency = function(path, provides, requires) {
     provides.forEach(function(provide) {
@@ -373,9 +373,10 @@
  * will invoke the |testBody| for |testFixture|.|testFunction|.
  * @param {string} testFixture The name of this test's fixture.
  * @param {string} testFunction The name of this test's function.
- * @param {Function} testBody The function body to execute for this test.
+ * @param {!Function} testBody The function body to execute for this test.
  * @param {string=} opt_preamble C++ to be generated before the TEST_F block.
  * Useful for including #ifdef blocks. See TEST_F_WITH_PREAMBLE.
+ * @this {!Object}
  */
 function TEST_F(testFixture, testFunction, testBody, opt_preamble) {
   maybeGenHeader(testFixture);
@@ -563,7 +564,7 @@
  *                 Useful for including #ifdef blocks.
  * @param {string} testFixture The name of this test's fixture.
  * @param {string} testFunction The name of this test's function.
- * @param {Function} testBody The function body to execute for this test.
+ * @param {!Function} testBody The function body to execute for this test.
  */
 function TEST_F_WITH_PREAMBLE(preamble, testFixture, testFunction, testBody) {
   TEST_F(testFixture, testFunction, testBody, preamble);
diff --git a/chrome/test/data/extensions/api_test/cross_origin_xhr/content_script/manifest.json b/chrome/test/data/extensions/api_test/cross_origin_xhr/content_script/manifest.json
index 1c533b1..8b67ac0 100644
--- a/chrome/test/data/extensions/api_test/cross_origin_xhr/content_script/manifest.json
+++ b/chrome/test/data/extensions/api_test/cross_origin_xhr/content_script/manifest.json
@@ -10,5 +10,5 @@
       "js": [ "content_script.js" ],
       "matches": [ "http://localhost/*" ]
   } ],
-  "permissions": ["http://a.com/", "http://*.b.com/", "ftp://127.0.0.1/*"]
+  "permissions": ["ftp://127.0.0.1/*"]
 }
diff --git a/chrome/test/data/extensions/api_test/cross_origin_xhr/content_script/test.js b/chrome/test/data/extensions/api_test/cross_origin_xhr/content_script/test.js
index 97ce1fa..d4e6f3f 100644
--- a/chrome/test/data/extensions/api_test/cross_origin_xhr/content_script/test.js
+++ b/chrome/test/data/extensions/api_test/cross_origin_xhr/content_script/test.js
@@ -45,21 +45,6 @@
     chrome.test.assertEq('injected', message);
 
     chrome.test.runTests([
-      function allowedOrigin() {
-        doReq('http://a.com', true);
-      },
-      function diallowedOrigin() {
-        doReq('http://c.com', false);
-      },
-      function allowedSubdomain() {
-        doReq('http://foo.b.com', true);
-      },
-      function noSubdomain() {
-        doReq('http://b.com', true);
-      },
-      function disallowedSubdomain() {
-        doReq('http://foob.com', false);
-      },
       // TODO(asargent): Explicitly create SSL test server and enable the test.
       // function disallowedSSL() {
       //   doReq('https://a.com', false);
diff --git a/chrome/test/data/extensions/api_test/keybinding/remove_bookmark_shortcut/background.js b/chrome/test/data/extensions/api_test/keybinding/remove_bookmark_shortcut/background.js
deleted file mode 100644
index e9a4164..0000000
--- a/chrome/test/data/extensions/api_test/keybinding/remove_bookmark_shortcut/background.js
+++ /dev/null
@@ -1,5 +0,0 @@
-// Copyright (c) 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-chrome.test.notifyPass();
diff --git a/chrome/test/data/extensions/api_test/keybinding/remove_bookmark_shortcut/manifest.json b/chrome/test/data/extensions/api_test/keybinding/remove_bookmark_shortcut/manifest.json
deleted file mode 100644
index 553a9e9..0000000
--- a/chrome/test/data/extensions/api_test/keybinding/remove_bookmark_shortcut/manifest.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-  "name": "An extension that removes the bookmark shortcut",
-  "version": "1.0",
-  "manifest_version": 2,
-  "background": {
-    "scripts": ["background.js"]
-  },
-  "permissions": ["activeTab"],
-  "chrome_ui_overrides": {
-    "bookmarks_ui": {
-      "remove_bookmark_shortcut": true
-    }
-  },
-  "commands": {
-    "_execute_browser_action": {
-      "suggested_key": "Ctrl+Shift+F"
-    }
-  }
-}
diff --git a/chrome/test/data/extensions/api_test/webstore_private/install_blocked_child.html b/chrome/test/data/extensions/api_test/webstore_private/install_blocked_child.html
new file mode 100644
index 0000000..b0892bf3
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webstore_private/install_blocked_child.html
@@ -0,0 +1,22 @@
+<!--
+ * Copyright 2016 The Chromium Authors. All rights reserved.  Use of this
+ * source code is governed by a BSD-style license that can be found in the
+ * LICENSE file.
+-->
+<script src="common.js"></script>
+<script>
+
+runTests([
+  function installBlockedChild() {
+    var manifest = getManifest();
+    var expectedError =
+        "Apps and extensions can only be modified by the manager (test_parent_0@google.com)."
+    chrome.webstorePrivate.beginInstallWithManifest3(
+        {id: appId, manifest: manifest},
+        callbackFail(expectedError, function(result) {
+      assertEq("blocked_for_child_account", result);
+    }));
+  },
+]);
+
+</script>
diff --git a/chrome/test/data/extensions/api_test/webstore_private/install_cancel_child.html b/chrome/test/data/extensions/api_test/webstore_private/install_cancel_child.html
new file mode 100644
index 0000000..777b320
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webstore_private/install_cancel_child.html
@@ -0,0 +1,21 @@
+<!--
+ * Copyright 2020 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.
+-->
+<script src="common.js"></script>
+<script>
+
+runTests([
+  function installCanceledChild() {
+    var manifest = getManifest();
+    chrome.webstorePrivate.beginInstallWithManifest3(
+        {id: appId, manifest: manifest},
+        function(result) {
+      assertEq(result, "user_cancelled");
+      succeed();
+    });
+  },
+]);
+
+</script>
diff --git a/chrome/test/data/extensions/api_test/webstore_private/install_child.html b/chrome/test/data/extensions/api_test/webstore_private/install_child.html
new file mode 100644
index 0000000..3c7487d0
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webstore_private/install_child.html
@@ -0,0 +1,23 @@
+<!--
+ * Copyright 2020 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.
+-->
+<script src="common.js"></script>
+<script>
+
+runTests([
+  function installChild() {
+    var manifest = getManifest();
+    chrome.webstorePrivate.beginInstallWithManifest3(
+        {id: appId, manifest: manifest},
+        callbackPass(function(result) {
+      assertEq("", result);
+
+      // Complete the installation
+      chrome.webstorePrivate.completeInstall(appId, callbackPass());
+    }));
+  },
+]);
+
+</script>
diff --git a/chrome/test/data/extensions/bookmarks_ui/manifest.json b/chrome/test/data/extensions/bookmarks_ui/manifest.json
deleted file mode 100644
index 9794387..0000000
--- a/chrome/test/data/extensions/bookmarks_ui/manifest.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-  "name": "chrome_ui_overrides.bookmarks_ui",
-  "version": "0.1",
-  "manifest_version": 2,
-  "description": "end-to-end browser test for chrome_ui_overrides.bookmarks_ui shortcut removal",
-  "chrome_ui_overrides": {
-    "bookmarks_ui": {
-      "remove_bookmark_shortcut": true,
-      "remove_bookmark_open_pages_shortcut": true
-    }
-  }
-}
diff --git a/chrome/test/data/webui/BUILD.gn b/chrome/test/data/webui/BUILD.gn
index cf4ec7c..5304b75b 100644
--- a/chrome/test/data/webui/BUILD.gn
+++ b/chrome/test/data/webui/BUILD.gn
@@ -2,7 +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/sanitizers/sanitizers.gni")
 import("//chrome/common/features.gni")
 import("//chrome/test/base/js2gtest.gni")
 import("//ui/webui/resources/tools/js_modulizer.gni")
@@ -214,6 +213,7 @@
     "$root_gen_dir/chrome/test/data/webui/mock_timer.m.js",
     "$root_gen_dir/chrome/test/data/webui/resources/list_property_update_behavior_tests.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/about_page_tests.m.js",
+    "$root_gen_dir/chrome/test/data/webui/settings/all_sites_tests.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/appearance_page_test.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/appearance_fonts_page_test.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/autofill_page_test.m.js",
@@ -250,10 +250,12 @@
     "$root_gen_dir/chrome/test/data/webui/settings/pref_util_tests.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/prefs_test_cases.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/prefs_tests.m.js",
+    "$root_gen_dir/chrome/test/data/webui/settings/protocol_handlers_tests.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/reset_page_test.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/search_engines_page_test.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/search_page_test.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/search_settings_test.m.js",
+    "$root_gen_dir/chrome/test/data/webui/settings/security_keys_subpage_test.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/settings_main_test.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/settings_menu_test.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/settings_slider_tests.m.js",
@@ -265,6 +267,7 @@
     "$root_gen_dir/chrome/test/data/webui/settings/test_about_page_browser_proxy.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/test_local_data_browser_proxy.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/test_metrics_browser_proxy.m.js",
+    "$root_gen_dir/chrome/test/data/webui/settings/site_data_test.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/site_data_details_subpage_tests.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/site_details_tests.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/site_details_permission_tests.m.js",
@@ -311,11 +314,6 @@
     ]
   }
   defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
-
-  # https://crbug.com/1013656
-  if (is_cfi) {
-    defines += [ "IS_CFI" ]
-  }
 }
 
 js2gtest("browser_tests_js_mojo_lite_webui") {
diff --git a/chrome/test/data/webui/cr_elements/cr_elements_browsertest.js b/chrome/test/data/webui/cr_elements/cr_elements_browsertest.js
index f40919b..9d91b74 100644
--- a/chrome/test/data/webui/cr_elements/cr_elements_browsertest.js
+++ b/chrome/test/data/webui/cr_elements/cr_elements_browsertest.js
@@ -94,6 +94,7 @@
 
   /** @override */
   extraLibraries: CrElementsBrowserTest.prototype.extraLibraries.concat([
+    '../test_util.js',
     'cr_toolbar_search_field_tests.js',
   ]),
 };
@@ -121,14 +122,7 @@
   ]),
 };
 
-// https://crbug.com/1013656 - Flaky on Linux CFI.
-GEN('#if defined(OS_LINUX) && defined(IS_CFI)');
-GEN('#define MAYBE_Drawer DISABLED_Drawer');
-GEN('#else');
-GEN('#define MAYBE_Drawer Drawer');
-GEN('#endif');
-
-TEST_F('CrElementsDrawerTest', 'MAYBE_Drawer', function() {
+TEST_F('CrElementsDrawerTest', 'Drawer', function() {
   mocha.run();
 });
 
diff --git a/chrome/test/data/webui/cr_elements/cr_elements_v3_browsertest.js b/chrome/test/data/webui/cr_elements/cr_elements_v3_browsertest.js
index 4ae0ba7d..1e025439 100644
--- a/chrome/test/data/webui/cr_elements/cr_elements_v3_browsertest.js
+++ b/chrome/test/data/webui/cr_elements/cr_elements_v3_browsertest.js
@@ -76,8 +76,8 @@
   }
 };
 
-// https://crbug.com/1008122 - Flaky on Linux CFI and Mac 10.10.
-GEN('#if (defined(OS_LINUX) && defined(IS_CFI)) || defined(OS_MACOSX)');
+// https://crbug.com/1008122 - Flaky on Mac 10.10.
+GEN('#if defined(OS_MACOSX)');
 GEN('#define MAYBE_Drawer DISABLED_Drawer');
 GEN('#else');
 GEN('#define MAYBE_Drawer Drawer');
diff --git a/chrome/test/data/webui/settings/BUILD.gn b/chrome/test/data/webui/settings/BUILD.gn
index 1fa8194..052ad04e 100644
--- a/chrome/test/data/webui/settings/BUILD.gn
+++ b/chrome/test/data/webui/settings/BUILD.gn
@@ -9,6 +9,7 @@
 js_modulizer("modulize") {
   input_files = [
     "about_page_tests.js",
+    "all_sites_tests.js",
     "appearance_fonts_page_test.js",
     "appearance_page_test.js",
     "autofill_page_test.js",
@@ -46,10 +47,12 @@
     "prefs_test_cases.js",
     "prefs_tests.js",
     "pref_util_tests.js",
+    "protocol_handlers_tests.js",
     "reset_page_test.js",
     "search_engines_page_test.js",
     "search_page_test.js",
     "search_settings_test.js",
+    "security_keys_subpage_test.js",
     "settings_animated_pages_test.js",
     "settings_main_test.js",
     "settings_menu_test.js",
@@ -58,6 +61,7 @@
     "settings_textarea_tests.js",
     "settings_toggle_button_tests.js",
     "settings_ui_tests.js",
+    "site_data_test.js",
     "site_data_details_subpage_tests.js",
     "site_details_tests.js",
     "site_details_permission_tests.js",
@@ -137,6 +141,7 @@
                          "sync_test_util.simulateStoredAccounts|simulateStoredAccounts",
                          "test_util.createContentSettingTypeToValuePair|createContentSettingTypeToValuePair",
                          "test_util.createDefaultContentSetting|createDefaultContentSetting",
+                         "test_util.createOriginInfo|createOriginInfo",
                          "test_util.createRawChooserException|createRawChooserException",
                          "test_util.createRawSiteException|createRawSiteException",
                          "test_util.createSiteGroup|createSiteGroup",
diff --git a/chrome/test/data/webui/settings/a11y/edit_dictionary_a11y_test.js b/chrome/test/data/webui/settings/a11y/edit_dictionary_a11y_test.js
index a3e79bed..b470cf4 100644
--- a/chrome/test/data/webui/settings/a11y/edit_dictionary_a11y_test.js
+++ b/chrome/test/data/webui/settings/a11y/edit_dictionary_a11y_test.js
@@ -46,14 +46,6 @@
   /** @override */
   name: 'EDIT_DICTIONARY',
 
-  /** @override */
-  axeOptions: Object.assign({}, SettingsAccessibilityTest.axeOptions, {
-    'rules': Object.assign({}, SettingsAccessibilityTest.axeOptions.rules, {
-      // TODO(crbug.com/1012370): Disable because of timeout in CFI build.
-      'hidden-content': {enabled: false},
-    }),
-  }),
-
   /** @type {settings.FakeLanguageSettingsPrivate} */
   languageSettingsPrivate_: null,
 
diff --git a/chrome/test/data/webui/settings/a11y/passwords_a11y_test.js b/chrome/test/data/webui/settings/a11y/passwords_a11y_test.js
index ee98ee5..c5d65c5 100644
--- a/chrome/test/data/webui/settings/a11y/passwords_a11y_test.js
+++ b/chrome/test/data/webui/settings/a11y/passwords_a11y_test.js
@@ -37,20 +37,7 @@
   passwordManager: null,
   /** @type {PasswordsSectionElement}*/
   passwordsSection: null,
-  // TODO(hcarmona): Create function that overrides defaults to simplify this.
-  axeOptions: Object.assign({}, SettingsAccessibilityTest.axeOptions, {
-    'rules': Object.assign({}, SettingsAccessibilityTest.axeOptions.rules, {
-      // TODO(hcarmona): Investigate flakyness and enable these tests.
-      // Disable rules flaky for CFI build.
-      'meta-viewport': {enabled: false},
-      'list': {enabled: false},
-      'frame-title': {enabled: false},
-      'label': {enabled: false},
-      'hidden-content': {enabled: false},
-      'aria-valid-attr-value': {enabled: false},
-      'button-name': {enabled: false},
-    }),
-  }),
+
   /** @override */
   setup: function() {
     return new Promise((resolve) => {
diff --git a/chrome/test/data/webui/settings/all_sites_tests.js b/chrome/test/data/webui/settings/all_sites_tests.js
index 1da58f3d..5bd9c41 100644
--- a/chrome/test/data/webui/settings/all_sites_tests.js
+++ b/chrome/test/data/webui/settings/all_sites_tests.js
@@ -2,6 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {beforeNextRender,flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+// #import {ContentSetting,ContentSettingsTypes,SiteSettingsPrefsBrowserProxyImpl,LocalDataBrowserProxyImpl} from 'chrome://settings/lazy_load.js';
+// #import {CrSettingsPrefs,routes, Router} from 'chrome://settings/settings.js';
+// #import {createContentSettingTypeToValuePair,createOriginInfo,createRawSiteException,createSiteGroup,createSiteSettingsPrefs} from 'chrome://test/settings/test_util.m.js';
+// #import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
+// #import {PromiseResolver} from 'chrome://resources/js/promise_resolver.m.js';
+// #import {TestLocalDataBrowserProxy} from 'chrome://test/settings/test_local_data_browser_proxy.m.js';
+// #import {TestSiteSettingsPrefsBrowserProxy} from 'chrome://test/settings/test_site_settings_prefs_browser_proxy.m.js';
+// clang-format on
+
 suite('AllSites', function() {
   const TEST_COOKIE_LIST = {
     id: 'example',
@@ -61,7 +72,7 @@
   // Initialize a site-list before each test.
   setup(async function() {
     PolymerTest.clearBody();
-    await settings.forceLazyLoaded();
+    /* #ignore */ await settings.forceLazyLoaded();
 
     prefsVarious = test_util.createSiteSettingsPrefs([], [
       test_util.createContentSettingTypeToValuePair(
@@ -403,7 +414,8 @@
     assertTrue(
         buttonType === 'cancel-button' || buttonType === 'action-button');
     Polymer.dom.flush();
-    siteEntries = testElement.$.listContainer.querySelectorAll('site-entry');
+    const siteEntries =
+        testElement.$.listContainer.querySelectorAll('site-entry');
     assertEquals(1, siteEntries.length);
     const overflowMenuButton = siteEntries[0].$.overflowMenuButton;
     assertFalse(overflowMenuButton.closest('.row-aligned').hidden);
@@ -497,7 +509,8 @@
     assertTrue(
         buttonType === 'cancel-button' || buttonType === 'action-button');
     Polymer.dom.flush();
-    siteEntries = testElement.$.listContainer.querySelectorAll('site-entry');
+    const siteEntries =
+        testElement.$.listContainer.querySelectorAll('site-entry');
     assertEquals(1, siteEntries.length);
     const overflowMenuButton = siteEntries[0].$.overflowMenuButton;
     assertFalse(overflowMenuButton.closest('.row-aligned').hidden);
@@ -567,7 +580,7 @@
       function() {
         // Test when one origin has permission settings and data, clear data
         // only clears the data and cookies.
-        siteGroup = JSON.parse(JSON.stringify(TEST_MULTIPLE_SITE_GROUP));
+        const siteGroup = JSON.parse(JSON.stringify(TEST_MULTIPLE_SITE_GROUP));
         siteGroup.origins[0].hasPermissionSettings = true;
         siteGroup.origins[0].usage = 100;
         siteGroup.origins[0].numCookies = 3;
@@ -587,7 +600,8 @@
     assertTrue(
         buttonType === 'cancel-button' || buttonType === 'action-button');
     Polymer.dom.flush();
-    siteEntries = testElement.$.listContainer.querySelectorAll('site-entry');
+    const siteEntries =
+        testElement.$.listContainer.querySelectorAll('site-entry');
     assertTrue(siteEntries.length >= 1);
     const clearAllButton =
         testElement.$.clearAllButton.querySelector('cr-button');
@@ -677,7 +691,8 @@
     assertTrue(
         buttonType === 'cancel-button' || buttonType === 'action-button');
     Polymer.dom.flush();
-    siteEntries = testElement.$.listContainer.querySelectorAll('site-entry');
+    const siteEntries =
+        testElement.$.listContainer.querySelectorAll('site-entry');
     assertEquals(1, siteEntries.length);
 
     const expandButton = siteEntries[0].$.expandIcon;
@@ -716,7 +731,7 @@
   }
 
   test('cancelling the confirm dialog on clear data works', function() {
-    siteGroup = JSON.parse(JSON.stringify(TEST_MULTIPLE_SITE_GROUP));
+    const siteGroup = JSON.parse(JSON.stringify(TEST_MULTIPLE_SITE_GROUP));
     testElement.siteGroupMap.set(siteGroup.etldPlus1, siteGroup);
     testElement.forceListUpdate_();
     assertEquals(1, testElement.filteredList_.length);
@@ -727,7 +742,7 @@
   });
 
   test('clear single origin data via overflow menu', function() {
-    siteGroup = JSON.parse(JSON.stringify(TEST_MULTIPLE_SITE_GROUP));
+    const siteGroup = JSON.parse(JSON.stringify(TEST_MULTIPLE_SITE_GROUP));
     siteGroup.origins[0].hasPermissionSettings = false;
     siteGroup.origins[0].usage = 100;
     siteGroup.origins[0].numCookies = 3;
@@ -742,7 +757,7 @@
   test(
       'clear single origin data via overflow menu (has permissions)',
       function() {
-        siteGroup = JSON.parse(JSON.stringify(TEST_MULTIPLE_SITE_GROUP));
+        const siteGroup = JSON.parse(JSON.stringify(TEST_MULTIPLE_SITE_GROUP));
         siteGroup.origins[0].hasPermissionSettings = true;
         siteGroup.origins[0].usage = 100;
         siteGroup.origins[0].numCookies = 3;
@@ -774,7 +789,8 @@
     assertTrue(
         buttonType === 'cancel-button' || buttonType === 'action-button');
     Polymer.dom.flush();
-    siteEntries = testElement.$.listContainer.querySelectorAll('site-entry');
+    const siteEntries =
+        testElement.$.listContainer.querySelectorAll('site-entry');
     assertEquals(1, siteEntries.length);
 
     const expandButton = siteEntries[0].$.expandIcon;
@@ -814,7 +830,7 @@
   }
 
   test('cancelling the confirm dialog on resetting settings works', function() {
-    siteGroup = JSON.parse(JSON.stringify(TEST_MULTIPLE_SITE_GROUP));
+    const siteGroup = JSON.parse(JSON.stringify(TEST_MULTIPLE_SITE_GROUP));
     testElement.siteGroupMap.set(siteGroup.etldPlus1, siteGroup);
     testElement.forceListUpdate_();
     assertEquals(1, testElement.filteredList_.length);
@@ -827,7 +843,7 @@
   test(
       'clear single origin permissions via overflow menu (no usage/cookies)',
       function() {
-        siteGroup = JSON.parse(JSON.stringify(TEST_MULTIPLE_SITE_GROUP));
+        const siteGroup = JSON.parse(JSON.stringify(TEST_MULTIPLE_SITE_GROUP));
         siteGroup.origins[0].hasPermissionSettings = true;
         siteGroup.origins[0].usage = 0;
         siteGroup.origins[0].numCookies = 0;
@@ -842,7 +858,7 @@
   test(
       'clear single origin permissions via overflow menu (has usage/cookies)',
       function() {
-        siteGroup = JSON.parse(JSON.stringify(TEST_MULTIPLE_SITE_GROUP));
+        const siteGroup = JSON.parse(JSON.stringify(TEST_MULTIPLE_SITE_GROUP));
         siteGroup.origins[0].hasPermissionSettings = true;
         siteGroup.origins[0].usage = 100;
         siteGroup.origins[0].numCookies = 10;
diff --git a/chrome/test/data/webui/settings/chromeos/BUILD.gn b/chrome/test/data/webui/settings/chromeos/BUILD.gn
index ef360e50..6ee3399 100644
--- a/chrome/test/data/webui/settings/chromeos/BUILD.gn
+++ b/chrome/test/data/webui/settings/chromeos/BUILD.gn
@@ -5,7 +5,10 @@
 import("//third_party/closure_compiler/compile_js.gni")
 
 js_type_check("closure_compile") {
-  deps = [ ":fake_user_action_recorder" ]
+  deps = [
+    ":fake_settings_search_handler",
+    ":fake_user_action_recorder",
+  ]
 }
 
 js_library("fake_user_action_recorder") {
@@ -14,3 +17,7 @@
     "//ui/webui/resources/js:cr",
   ]
 }
+
+js_library("fake_settings_search_handler") {
+  deps = [ "//ui/webui/resources/js:cr" ]
+}
diff --git a/chrome/test/data/webui/settings/chromeos/fake_settings_search_handler.js b/chrome/test/data/webui/settings/chromeos/fake_settings_search_handler.js
new file mode 100644
index 0000000..70c51b6
--- /dev/null
+++ b/chrome/test/data/webui/settings/chromeos/fake_settings_search_handler.js
@@ -0,0 +1,35 @@
+// Copyright 2020 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.
+
+/**
+ * @fileoverview Fake implementation of SettingsSearchHandler for testing.
+ */
+cr.define('settings', function() {
+  /**
+   * Fake implementation of chromeos.settings.mojom.SettingsSearchHandlerRemote.
+   */
+  class FakeSettingsSearchHandler {
+    constructor() {
+      /** @private {!Array<string>} */
+      this.fakeResults_ = [];
+    }
+
+    /**
+     * @param {!Array<string>} results fake results that will be returned
+     * when Search() is called.
+     */
+    setFakeResults(results) {
+      this.fakeResults_ = results;
+    }
+
+    /**
+     * @param {string} query fake query used to compile search results.
+     */
+    async search(query) {
+      return this.fakeResults_;
+    }
+  }
+
+  return {FakeSettingsSearchHandler: FakeSettingsSearchHandler};
+});
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
index d79194fc..31f37bb 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
@@ -712,6 +712,29 @@
   mocha.run();
 });
 
+// Tests for the new OS Settings Search Box
+// eslint-disable-next-line no-var
+var OSSettingsSearchBoxBrowserTest = class extends OSSettingsBrowserTest {
+  /** @override */
+  get featureList() {
+    return {enabled: ['chromeos::features::kNewOsSettingsSearch']};
+  }
+
+  /** @override */
+  get extraLibraries() {
+    return super.extraLibraries.concat([
+      BROWSER_SETTINGS_PATH + '../test_util.js',
+      'fake_settings_search_handler.js',
+      'fake_user_action_recorder.js',
+      'os_settings_search_box_test.js',
+    ]);
+  }
+};
+
+TEST_F('OSSettingsSearchBoxBrowserTest', 'AllJsTests', () => {
+  mocha.run();
+});
+
 // Tests for the side-nav menu.
 // eslint-disable-next-line no-var
 var OSSettingsMenuTest = class extends OSSettingsBrowserTest {
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_search_box_test.js b/chrome/test/data/webui/settings/chromeos/os_settings_search_box_test.js
new file mode 100644
index 0000000..df20183
--- /dev/null
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_search_box_test.js
@@ -0,0 +1,168 @@
+// Copyright 2020 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.
+
+/** @fileoverview Runs tests for the OS settings search box. */
+
+suite('OSSettingsSearchBox', () => {
+  /** @type {?OsToolbar} */
+  let toolbar;
+
+  /** @type {?OsSettingsSearchBox} */
+  let searchBox;
+
+  /** @type {?CrSearchFieldElement} */
+  let field;
+
+  /** @type {?IronDropdownElement} */
+  let dropDown;
+
+  /** @type {?IronListElement} */
+  let resultList;
+
+  /** @type {*} */
+  let settingsSearchHandler;
+
+  /** @type {?chromeos.settings.mojom.UserActionRecorderInterface} */
+  let userActionRecorder;
+
+  /** @param {string} term */
+  async function simulateSearch(term) {
+    field.$.searchInput.value = term;
+    field.onSearchTermInput();
+    field.onSearchTermSearch();
+    await settingsSearchHandler.search;
+    Polymer.dom.flush();
+  }
+
+  setup(function() {
+    toolbar = document.querySelector('os-settings-ui').$$('os-toolbar');
+    assertTrue(!!toolbar);
+    searchBox = toolbar.$$('os-settings-search-box');
+    assertTrue(!!searchBox);
+    field = searchBox.$$('cr-toolbar-search-field');
+    assertTrue(!!field);
+    dropDown = searchBox.$$('iron-dropdown');
+    assertTrue(!!dropDown);
+    resultList = searchBox.$$('iron-list');
+    assertTrue(!!resultList);
+
+    settingsSearchHandler = new settings.FakeSettingsSearchHandler();
+    searchBox.setSearchHandlerForTesting(settingsSearchHandler);
+
+    userActionRecorder = new settings.FakeUserActionRecorder();
+    settings.setUserActionRecorderForTesting(userActionRecorder);
+  });
+
+  teardown(async () => {
+    // Clear search field for next test.
+    await simulateSearch('');
+    settings.setUserActionRecorderForTesting(null);
+    searchBox.setSearchHandlerForTesting(undefined);
+  });
+
+  test('User action search event', async () => {
+    settingsSearchHandler.setFakeResults([]);
+
+    assertEquals(userActionRecorder.searchCount, 0);
+    await simulateSearch('query');
+    assertEquals(userActionRecorder.searchCount, 1);
+  });
+
+  test('Dropdown opens correctly when results are fetched', async () => {
+    // Closed dropdown if no results are returned.
+    settingsSearchHandler.setFakeResults([]);
+    assertFalse(dropDown.opened);
+    await simulateSearch('query 1');
+    assertFalse(dropDown.opened);
+    assertEquals(userActionRecorder.searchCount, 1);
+
+    // Open dropdown if results are returned.
+    settingsSearchHandler.setFakeResults(['result']);
+    await simulateSearch('query 2');
+    assertTrue(dropDown.opened);
+  });
+
+  test('Restore previous existing search results', async () => {
+    settingsSearchHandler.setFakeResults(['result 1']);
+    await simulateSearch('query');
+    assertTrue(dropDown.opened);
+    const resultRow = resultList.items[0];
+
+    // Child blur elements except field should not trigger closing of dropdown.
+    resultList.blur();
+    assertTrue(dropDown.opened);
+    dropDown.blur();
+    assertTrue(dropDown.opened);
+
+    // User clicks outside the search box, closing the dropdown.
+    searchBox.blur();
+    assertFalse(dropDown.opened);
+
+    // User clicks on input, restoring old results and opening dropdown.
+    field.$.searchInput.focus();
+    assertEquals('query', field.$.searchInput.value);
+    assertTrue(dropDown.opened);
+
+    // The same result row exists.
+    assertEquals(resultRow, resultList.items[0]);
+
+    // Search field is blurred, closing the dropdown.
+    field.$.searchInput.blur();
+    assertFalse(dropDown.opened);
+
+    // User clicks on input, restoring old results and opening dropdown.
+    field.$.searchInput.focus();
+    assertEquals('query', field.$.searchInput.value);
+    assertTrue(dropDown.opened);
+
+    // The same result row exists.
+    assertEquals(resultRow, resultList.items[0]);
+  });
+
+  test('Search result rows are selected correctly', async () => {
+    settingsSearchHandler.setFakeResults(['result 1', 'result 2']);
+    await simulateSearch('query');
+    assertTrue(dropDown.opened);
+    assertEquals(resultList.items.length, 2);
+
+    // The first row should be selected when results are fetched.
+    assertEquals(resultList.selectedItem, resultList.items[0]);
+
+    // Test ArrowUp and ArrowDown interaction with selecting.
+    const arrowUpEvent = new KeyboardEvent(
+        'keydown', {cancelable: true, key: 'ArrowUp', keyCode: 38});
+    const arrowDownEvent = new KeyboardEvent(
+        'keydown', {cancelable: true, key: 'ArrowDown', keyCode: 40});
+
+    // ArrowDown event should select next row.
+    searchBox.dispatchEvent(arrowDownEvent);
+    assertEquals(resultList.selectedItem, resultList.items[1]);
+
+    // If last row selected, ArrowDown brings select back to first row.
+    searchBox.dispatchEvent(arrowDownEvent);
+    assertEquals(resultList.selectedItem, resultList.items[0]);
+
+    // If first row selected, ArrowUp brings select back to last row.
+    searchBox.dispatchEvent(arrowUpEvent);
+    assertEquals(resultList.selectedItem, resultList.items[1]);
+
+    // ArrowUp should bring select previous row.
+    searchBox.dispatchEvent(arrowUpEvent);
+    assertEquals(resultList.selectedItem, resultList.items[0]);
+
+    // Test that ArrowLeft and ArrowRight do nothing.
+    const arrowLeftEvent = new KeyboardEvent(
+        'keydown', {cancelable: true, key: 'ArrowLeft', keyCode: 37});
+    const arrowRightEvent = new KeyboardEvent(
+        'keydown', {cancelable: true, key: 'ArrowRight', keyCode: 39});
+
+    // No change on ArrowLeft
+    searchBox.dispatchEvent(arrowLeftEvent);
+    assertEquals(resultList.selectedItem, resultList.items[0]);
+
+    // No change on ArrowRight
+    searchBox.dispatchEvent(arrowRightEvent);
+    assertEquals(resultList.selectedItem, resultList.items[0]);
+  });
+});
diff --git a/chrome/test/data/webui/settings/cr_settings_browsertest.js b/chrome/test/data/webui/settings/cr_settings_browsertest.js
index e04bb67..98453f01 100644
--- a/chrome/test/data/webui/settings/cr_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/cr_settings_browsertest.js
@@ -370,17 +370,9 @@
   ]),
 };
 
-// Flakily times out on Linux CFI.
-// TODO(crbug.com/1063723): Fix this by splitting up the test suite.
-GEN('#if defined(OS_LINUX) && defined(IS_CFI)');
-GEN('#define MAYBE_All DISABLED_All');
-GEN('#else');
-GEN('#define MAYBE_All All');
-GEN('#endif');
-TEST_F('CrSettingsPasswordsSectionTest', 'MAYBE_All', function() {
+TEST_F('CrSettingsPasswordsSectionTest', 'All', function() {
   mocha.run();
 });
-GEN('#undef MAYBE_All');
 
 /**
  * Test fixture for
@@ -1478,8 +1470,7 @@
 
 // Disabling on debug due to flaky timeout on Win7 Tests (dbg)(1) bot.
 // https://crbug.com/825304 - later for other platforms in crbug.com/1021219.
-// Disabling on Linux CFI due to flaky timeout (crbug.com/1031960).
-GEN('#if (!defined(NDEBUG)) || (defined(OS_LINUX) && defined(IS_CFI))');
+GEN('#if !defined(NDEBUG)');
 GEN('#define MAYBE_All DISABLED_All');
 GEN('#else');
 GEN('#define MAYBE_All All');
@@ -1488,6 +1479,8 @@
 TEST_F('CrSettingsSiteDetailsTest', 'MAYBE_All', function() {
   mocha.run();
 });
+GEN('#undef MAYBE_All');
+
 
 /**
  * Test fixture for
@@ -1761,6 +1754,7 @@
   ]),
 };
 
+// Disabled for flakiness, see https://crbug.com/1061249
 TEST_F('CrSettingsSiteDataTest', 'DISABLED_All', function() {
   mocha.run();
 });
@@ -2101,8 +2095,7 @@
 
 // Times out on Windows Tests (dbg). See https://crbug.com/651296.
 // Times out / crashes on chromium.linux/Linux Tests (dbg) crbug.com/667882
-// Times out on Linux CFI. See http://crbug.com/929288.
-GEN('#if !defined(NDEBUG) || (defined(OS_LINUX) && defined(IS_CFI))');
+GEN('#if !defined(NDEBUG)');
 GEN('#define MAYBE_MainPage DISABLED_MainPage');
 GEN('#else');
 GEN('#define MAYBE_MainPage MainPage');
diff --git a/chrome/test/data/webui/settings/cr_settings_interactive_ui_tests.js b/chrome/test/data/webui/settings/cr_settings_interactive_ui_tests.js
index 85d4da5..666aa47f 100644
--- a/chrome/test/data/webui/settings/cr_settings_interactive_ui_tests.js
+++ b/chrome/test/data/webui/settings/cr_settings_interactive_ui_tests.js
@@ -132,8 +132,8 @@
   ]),
 };
 
-// Fails on Linux CFI and Mac10.13 Tests (dbg) (see crbug/1063844).
-GEN('#if !(defined(OS_LINUX) && defined(IS_CFI)) && !(defined(OS_MACOSX) && !defined(NDEBUG))');
+// Fails on Mac10.13 Tests (dbg) (see crbug/1063844).
+GEN('#if !(defined(OS_MACOSX) && !defined(NDEBUG))');
 TEST_F('SettingsUIInteractiveTest', 'All', function() {
   mocha.run();
 });
diff --git a/chrome/test/data/webui/settings/cr_settings_v3_browsertest.js b/chrome/test/data/webui/settings/cr_settings_v3_browsertest.js
index 34a2eac..3bea7da 100644
--- a/chrome/test/data/webui/settings/cr_settings_v3_browsertest.js
+++ b/chrome/test/data/webui/settings/cr_settings_v3_browsertest.js
@@ -116,8 +116,7 @@
 // Copied from Polymer 2 version of tests:
 // Times out on Windows Tests (dbg). See https://crbug.com/651296.
 // Times out / crashes on chromium.linux/Linux Tests (dbg) crbug.com/667882
-// Times out on Linux CFI. See http://crbug.com/929288.
-GEN('#if !defined(NDEBUG) || (defined(OS_LINUX) && defined(IS_CFI))');
+GEN('#if !defined(NDEBUG)');
 GEN('#define MAYBE_MainPageV3 DISABLED_MainPageV3');
 GEN('#else');
 GEN('#define MAYBE_MainPageV3 MainPageV3');
@@ -277,7 +276,8 @@
   runMochaSuite('PersonalizationOptionsTests_AllBuilds_Old');
 });
 
-[['AppearanceFontsPage', 'appearance_fonts_page_test.m.js'],
+[['AllSites', 'all_sites_tests.m.js'],
+ ['AppearanceFontsPage', 'appearance_fonts_page_test.m.js'],
  ['AppearancePage', 'appearance_page_test.m.js'],
  ['AutofillPage', 'autofill_page_test.m.js'],
  ['BasicPage', 'basic_page_test.m.js'],
@@ -300,10 +300,14 @@
  ['PeoplePageSyncPage', 'people_page_sync_page_test.m.js'],
  ['Prefs', 'prefs_tests.m.js'],
  ['PrefUtil', 'pref_util_tests.m.js'],
+ ['ProtocolHandlers', 'protocol_handlers_tests.m.js'],
  ['ResetPage', 'reset_page_test.m.js'],
  ['SearchEngines', 'search_engines_page_test.m.js'],
  ['SearchPage', 'search_page_test.m.js'],
  ['Search', 'search_settings_test.m.js'],
+ ['SecurityKeysSubpage', 'security_keys_subpage_test.m.js'],
+ // Copied from P2 test: Disabled for flakiness, see https://crbug.com/1061249
+ ['SiteData', 'site_data_test.m.js', 'DISABLED_All'],
  ['SiteDataDetails', 'site_data_details_subpage_tests.m.js'],
  ['SiteDetailsPermission', 'site_details_permission_tests.m.js'],
  ['SiteEntry', 'site_entry_tests.m.js'],
diff --git a/chrome/test/data/webui/settings/protocol_handlers_tests.js b/chrome/test/data/webui/settings/protocol_handlers_tests.js
index c289519..4fba124b 100644
--- a/chrome/test/data/webui/settings/protocol_handlers_tests.js
+++ b/chrome/test/data/webui/settings/protocol_handlers_tests.js
@@ -2,6 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+// #import {SiteSettingsPrefsBrowserProxyImpl} from 'chrome://settings/lazy_load.js';
+// #import {TestSiteSettingsPrefsBrowserProxy} from 'chrome://test/settings/test_site_settings_prefs_browser_proxy.m.js';
+// clang-format on
+
 /** @fileoverview Suite of tests for protocol_handlers. */
 suite('ProtocolHandlers', function() {
   /**
@@ -65,7 +71,7 @@
   let browserProxy = null;
 
   setup(async function() {
-    await settings.forceLazyLoaded();
+    /* #ignore */ await settings.forceLazyLoaded();
     browserProxy = new TestSiteSettingsPrefsBrowserProxy();
     settings.SiteSettingsPrefsBrowserProxyImpl.instance_ = browserProxy;
   });
diff --git a/chrome/test/data/webui/settings/security_keys_subpage_test.js b/chrome/test/data/webui/settings/security_keys_subpage_test.js
index 63aed64..8364737 100644
--- a/chrome/test/data/webui/settings/security_keys_subpage_test.js
+++ b/chrome/test/data/webui/settings/security_keys_subpage_test.js
@@ -2,6 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {TestBrowserProxy} from 'chrome://test/test_browser_proxy.m.js';
+// #import {eventToPromise} from 'chrome://test/test_util.m.js';
+// #import {PromiseResolver} from 'chrome://resources/js/promise_resolver.m.js';
+// #import {SecurityKeysResetBrowserProxyImpl, SecurityKeysPINBrowserProxyImpl, SecurityKeysCredentialBrowserProxyImpl, SecurityKeysBioEnrollProxyImpl, ResetDialogPage, SetPINDialogPage, CredentialManagementDialogPage, BioEnrollDialogPage, SampleStatus, Ctap2Status} from 'chrome://settings/lazy_load.js';
+// #import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+// clang-format on
+
 /**
  * A base class for all security key subpage test browser proxies to
  * inherit from. Provides a |promiseMap_| that proxies can be used to
@@ -207,6 +215,7 @@
 suite('SecurityKeysResetDialog', function() {
   let dialog = null;
   let allDivs = null;
+  let browserProxy = null;
 
   setup(function() {
     browserProxy = new TestSecurityKeysResetBrowserProxy();
@@ -315,6 +324,7 @@
 suite('SecurityKeysSetPINDialog', function() {
   let dialog = null;
   let allDivs = null;
+  let browserProxy = null;
 
   setup(function() {
     browserProxy = new TestSecurityKeysPINBrowserProxy();
@@ -437,7 +447,7 @@
     const setPINResolver = new PromiseResolver();
     browserProxy.setResponseFor('setPIN', setPINResolver.promise);
     setNewPINEntries('1234', '1234');
-    ({oldPIN, newPIN} = await browserProxy.whenCalled('setPIN'));
+    const {oldPIN, newPIN} = await browserProxy.whenCalled('setPIN');
     assertTrue(dialog.$.pinSubmit.disabled);
     assertEquals(oldPIN, '');
     assertEquals(newPIN, '1234');
@@ -554,6 +564,7 @@
 suite('SecurityKeysCredentialManagement', function() {
   let dialog = null;
   let allDivs = null;
+  let browserProxy = null;
 
   setup(function() {
     browserProxy = new TestSecurityKeysCredentialBrowserProxy();
@@ -654,10 +665,10 @@
     assertEquals(dialog.$.credentialList.items, credentials);
 
     // Select two of the credentials and delete them.
-    Polymer.flush();
+    Polymer.dom.flush();
     assertTrue(dialog.$.confirmButton.disabled);
-    const checkboxes = Array.from(
-        Polymer.dom(dialog.$.credentialList).querySelectorAll('cr-checkbox'));
+    const checkboxes =
+        Array.from(dialog.$.credentialList.querySelectorAll('cr-checkbox'));
     assertEquals(checkboxes.length, 3);
     assertEquals(checkboxes.filter(el => el.checked).length, 0);
     checkboxes[1].click();
@@ -679,6 +690,7 @@
 suite('SecurityKeysBioEnrollment', function() {
   let dialog = null;
   let allDivs = null;
+  let browserProxy = null;
 
   setup(function() {
     browserProxy = new TestSecurityKeysBioEnrollProxy();
@@ -770,10 +782,8 @@
     assertEquals(dialog.$.enrollmentList.items, enrollments);
 
     // Delete the second enrollments and refresh the list.
-    Polymer.flush();
-    Polymer.dom(dialog.$.enrollmentList)
-        .querySelectorAll('cr-icon-button')[1]
-        .click();
+    Polymer.dom.flush();
+    dialog.$.enrollmentList.querySelectorAll('cr-icon-button')[1].click();
     const id = await browserProxy.whenCalled('deleteEnrollment');
     assertEquals(enrollments[1].id, id);
     enrollments.splice(1, 1);
@@ -834,7 +844,7 @@
     Polymer.dom.flush();
     assertFalse(dialog.$.arc.isComplete());
     assertFalse(dialog.$.cancelButton.hidden);
-    assert(dialog.$.confirmButton.hidden);
+    assertTrue(dialog.$.confirmButton.hidden);
 
     uiReady =
         test_util.eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
@@ -848,8 +858,8 @@
       },
     });
     await uiReady;
-    assert(dialog.$.arc.isComplete());
-    assert(dialog.$.cancelButton.hidden);
+    assertTrue(dialog.$.arc.isComplete());
+    assertTrue(dialog.$.cancelButton.hidden);
     assertFalse(dialog.$.confirmButton.hidden);
 
     // Proceeding brings up rename dialog page.
@@ -903,8 +913,8 @@
     await uiReady;
 
     assertShown(allDivs, dialog, 'enroll');
-    assert(dialog.$.cancelButton.disabled == false);
-    assert(dialog.$.cancelButton.hidden == false);
+    assertFalse(dialog.$.cancelButton.disabled);
+    assertFalse(dialog.$.cancelButton.hidden);
 
     uiReady =
         test_util.eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
diff --git a/chrome/test/data/webui/settings/site_data_test.js b/chrome/test/data/webui/settings/site_data_test.js
index 2bcc24f..16b9c12 100644
--- a/chrome/test/data/webui/settings/site_data_test.js
+++ b/chrome/test/data/webui/settings/site_data_test.js
@@ -2,6 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {eventToPromise} from 'chrome://test/test_util.m.js';
+// #import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+// #import {LocalDataBrowserProxyImpl} from 'chrome://settings/lazy_load.js';
+// #import {Router,routes} from 'chrome://settings/settings.js';
+// #import {TestLocalDataBrowserProxy} from 'chrome://test/settings/test_local_data_browser_proxy.m.js';
+// clang-format on
+
 suite('SiteDataTest', function() {
   /** @type {SiteDataElement} */
   let siteData;
diff --git a/chrome/test/data/webui/settings/test_site_settings_prefs_browser_proxy.js b/chrome/test/data/webui/settings/test_site_settings_prefs_browser_proxy.js
index edd8fef..91ed9cf0 100644
--- a/chrome/test/data/webui/settings/test_site_settings_prefs_browser_proxy.js
+++ b/chrome/test/data/webui/settings/test_site_settings_prefs_browser_proxy.js
@@ -5,7 +5,7 @@
 // clang-format off
 // #import {assert} from 'chrome://resources/js/assert.m.js';
 // #import {ContentSetting,SiteSettingSource} from 'chrome://settings/lazy_load.js';
-// #import {createSiteSettingsPrefs, getContentSettingsTypeFromChooserType} from 'chrome://test/settings/test_util.m.js';
+// #import {createSiteGroup,createSiteSettingsPrefs, getContentSettingsTypeFromChooserType} from 'chrome://test/settings/test_util.m.js';
 // #import {TestBrowserProxy} from 'chrome://test/test_browser_proxy.m.js';
 // clang-format on
 
diff --git a/chrome/test/data/webui/settings/test_util.js b/chrome/test/data/webui/settings/test_util.js
index 5f628e68..b8940b7 100644
--- a/chrome/test/data/webui/settings/test_util.js
+++ b/chrome/test/data/webui/settings/test_util.js
@@ -178,7 +178,7 @@
     };
   }
 
-  function createOriginInfo(origin, override) {
+  /* #export */ function createOriginInfo(origin, override) {
     if (override === undefined) {
       override = {};
     }
diff --git a/chrome/test/data/webui/test_api.js b/chrome/test/data/webui/test_api.js
index 1fbfcd1e..47e3f8d7 100644
--- a/chrome/test/data/webui/test_api.js
+++ b/chrome/test/data/webui/test_api.js
@@ -109,6 +109,9 @@
    */
   browsePreload: null,
 
+  /** @type {?string} */
+  webuiHost: null,
+
   /**
    * When set to a string value representing an html page in the test
    * directory, generate BrowsePrintPreload call, which will browse to a url
@@ -133,6 +136,9 @@
    */
   testGenPostamble: null,
 
+  /** @type {?function()} */
+  testGenCppIncludes: null,
+
   /**
    * When set to a non-null string, auto-generate typedef before generating
    * TEST*: {@code typedef typedefCppFixture testFixture}.
@@ -140,6 +146,19 @@
    */
   typedefCppFixture: 'WebUIBrowserTest',
 
+  /** @type {?Array<{switchName: string, switchValue: string}>} */
+  commandLineSwitches: null,
+
+  /** @type {?{enabled: !Array<string>, disabled: !Array<string>}} */
+  featureList: null,
+
+  /**
+   * @type {?Array<!{
+   *    featureName: string,
+   *    parameters: !Array<{name: string, value: string}>}>}
+   */
+  featuresWithParameters: null,
+
   /**
    * This should be initialized by the test fixture and can be referenced
    * during the test run. It holds any mocked handler methods.
diff --git a/chrome/test/include_js_tests.gni b/chrome/test/include_js_tests.gni
index d0df501..72a4398 100644
--- a/chrome/test/include_js_tests.gni
+++ b/chrome/test/include_js_tests.gni
@@ -3,5 +3,5 @@
 if (!is_android) {
   # js_tests don't work in cross builds, https://crbug.com/1010561
   include_js_tests =
-      !(is_asan || is_msan || is_tsan || (is_win && host_os != "win"))
+      !(is_asan || is_msan || is_tsan || is_cfi || (is_win && host_os != "win"))
 }
diff --git a/chrome/tools/build/mac/FILES.cfg b/chrome/tools/build/mac/FILES.cfg
index b270e6f..3f19bd2 100644
--- a/chrome/tools/build/mac/FILES.cfg
+++ b/chrome/tools/build/mac/FILES.cfg
@@ -136,11 +136,6 @@
     'archive': 'updater.zip',
   },
   {
-    'filename': 'updater_setup',
-    'buildtype': ['official'],
-    'archive': 'updater.zip',
-  },
-  {
     'filename': 'chrome/updater/.install',
     'buildtype': ['official'],
     'archive': 'updater.zip',
diff --git a/chrome/updater/BUILD.gn b/chrome/updater/BUILD.gn
index 324d75a..d6ea3fd 100644
--- a/chrome/updater/BUILD.gn
+++ b/chrome/updater/BUILD.gn
@@ -213,14 +213,10 @@
         "//chrome/common/mac:launchd",
         "//chrome/updater/mac:updater_bundle",
         "//chrome/updater/mac:updater_setup_tests",
-        "//chrome/updater/mac:updater_tests",
         "//chrome/updater/mac:xpc_names",
       ]
 
-      data_deps = [
-        "//chrome/updater/mac:updater_bundle",
-        "//chrome/updater/mac:updater_setup",
-      ]
+      data_deps = [ "//chrome/updater/mac:updater_bundle" ]
     }
 
     if (is_win || is_mac) {
diff --git a/chrome/updater/constants.cc b/chrome/updater/constants.cc
index 7bf0ac6f..16af159 100644
--- a/chrome/updater/constants.cc
+++ b/chrome/updater/constants.cc
@@ -25,6 +25,7 @@
 const char kEnableLoggingSwitch[] = "enable-logging";
 const char kLoggingModuleSwitch[] = "vmodule";
 const char kSingleProcessSwitch[] = "single-process";
+const char kAppIdSwitch[] = "appid";
 
 // URLs.
 const char kUpdaterJSONDefaultUrl[] =
diff --git a/chrome/updater/constants.h b/chrome/updater/constants.h
index 1799efa..f887baec 100644
--- a/chrome/updater/constants.h
+++ b/chrome/updater/constants.h
@@ -86,6 +86,9 @@
 // only and it may be removed at any time.
 extern const char kSingleProcessSwitch[];
 
+// Specifies the application that the Updater needs to install.
+extern const char kAppIdSwitch[];
+
 // URLs.
 //
 // Omaha server end point.
diff --git a/chrome/updater/mac/BUILD.gn b/chrome/updater/mac/BUILD.gn
index 83a68e72..9ccf4cf0 100644
--- a/chrome/updater/mac/BUILD.gn
+++ b/chrome/updater/mac/BUILD.gn
@@ -8,9 +8,9 @@
 
 group("mac") {
   deps = [
+    ":install_app",
     ":updater_bundle",
     ":updater_install_script",
-    ":updater_setup",
     "//chrome/updater/mac/signing",
   ]
 }
@@ -53,6 +53,7 @@
 
   sources = [ "main.cc" ]
   deps = [
+    ":install_app",
     ":network_fetcher_sources",
     ":updater_setup_sources",
     "//chrome/updater:lib",
@@ -97,13 +98,19 @@
   ]
 }
 
-executable("updater_setup") {
-  sources = [ "setup/main.cc" ]
+source_set("install_app") {
+  sources = [
+    "setup/install_app.cc",
+    "setup/install_app.h",
+  ]
 
   deps = [
-    ":updater_bundle",
     ":updater_setup_sources",
+    "//base",
+    "//chrome/updater:lib",
   ]
+
+  allow_circular_includes_from = [ "//chrome/updater:lib" ]
 }
 
 action("updater_install_script") {
@@ -143,7 +150,9 @@
   data = [ "//chrome/test/data/updater/updater_setup_test_dmg.dmg" ]
 
   deps = [
+    ":install_app",
     ":installer_sources",
+    ":updater_setup_sources",
     "//base",
     "//base/test:test_support",
     "//chrome/common:constants",
diff --git a/chrome/updater/mac/setup/install_app.cc b/chrome/updater/mac/setup/install_app.cc
new file mode 100644
index 0000000..99f7e2fd
--- /dev/null
+++ b/chrome/updater/mac/setup/install_app.cc
@@ -0,0 +1,35 @@
+// Copyright 2020 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/updater/mac/setup/install_app.h"
+
+#include "base/bind.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "chrome/updater/app/app.h"
+#include "chrome/updater/mac/setup/setup.h"
+
+namespace updater {
+
+namespace {
+
+class AppInstall : public App {
+ private:
+  ~AppInstall() override = default;
+  void FirstTaskRun() override;
+};
+
+void AppInstall::FirstTaskRun() {
+  base::ThreadPool::PostTaskAndReplyWithResult(
+      FROM_HERE, {base::MayBlock()}, base::BindOnce(&SetupUpdater),
+      base::BindOnce(&AppInstall::Shutdown, this));
+}
+
+}  // namespace
+
+scoped_refptr<App> AppInstallInstance() {
+  return AppInstance<AppInstall>();
+}
+
+}  // namespace updater
diff --git a/chrome/updater/mac/setup/install_app.h b/chrome/updater/mac/setup/install_app.h
new file mode 100644
index 0000000..0d9fe78
--- /dev/null
+++ b/chrome/updater/mac/setup/install_app.h
@@ -0,0 +1,18 @@
+// Copyright 2020 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_UPDATER_MAC_SETUP_INSTALL_APP_H_
+#define CHROME_UPDATER_MAC_SETUP_INSTALL_APP_H_
+
+#include "base/memory/scoped_refptr.h"
+
+namespace updater {
+
+class App;
+
+scoped_refptr<App> AppInstallInstance();
+
+}  // namespace updater
+
+#endif  // CHROME_UPDATER_MAC_SETUP_INSTALL_APP_H_
diff --git a/chrome/updater/mac/setup/main.cc b/chrome/updater/mac/setup/main.cc
deleted file mode 100644
index be7673d..0000000
--- a/chrome/updater/mac/setup/main.cc
+++ /dev/null
@@ -1,9 +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/updater/mac/setup/setup.h"
-
-int main(int argc, const char* argv[]) {
-  return updater::setup::UpdaterSetupMain(argc, argv);
-}
diff --git a/chrome/updater/mac/setup/setup.h b/chrome/updater/mac/setup/setup.h
index 14932c16..b21ec0b 100644
--- a/chrome/updater/mac/setup/setup.h
+++ b/chrome/updater/mac/setup/setup.h
@@ -7,14 +7,14 @@
 
 namespace updater {
 
+// Set up the updater by copying the bundle, creating launchd plists for
+// scheduled tasks and xpc service, and start both launchd jobs.
+int SetupUpdater();
+
+// Remove the launchd plists for scheduled tasks and xpc service. Delete the
+// updater bundle from its installed location.
 int Uninstall(bool is_machine);
 
-namespace setup {
-
-int UpdaterSetupMain(int argc, const char* const* argv);
-
-}  // namespace setup
-
 }  // namespace updater
 
 #endif  // CHROME_UPDATER_MAC_SETUP_SETUP_H_
diff --git a/chrome/updater/mac/setup/setup.mm b/chrome/updater/mac/setup/setup.mm
index 8c72620..0f174ad0 100644
--- a/chrome/updater/mac/setup/setup.mm
+++ b/chrome/updater/mac/setup/setup.mm
@@ -11,6 +11,7 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/logging.h"
+#include "base/mac/bundle_locations.h"
 #include "base/mac/foundation_util.h"
 #include "base/mac/scoped_nsobject.h"
 #include "base/path_service.h"
@@ -29,8 +30,6 @@
 
 namespace updater {
 
-namespace setup {
-
 namespace {
 const base::FilePath GetUpdateFolderName() {
   return base::FilePath(COMPANY_SHORTNAME_STRING)
@@ -43,51 +42,6 @@
   return base::FilePath("Contents/MacOS").AppendASCII(PRODUCT_FULLNAME_STRING);
 }
 
-void ThreadPoolStart() {
-  base::ThreadPoolInstance::CreateAndStartWithDefaultParams("UpdaterSetup");
-}
-
-void ThreadPoolStop() {
-  base::ThreadPoolInstance::Get()->Shutdown();
-}
-
-// The log file is created in DIR_LOCAL_APP_DATA or DIR_APP_DATA.
-void InitLogging(const base::CommandLine& command_line) {
-  logging::LoggingSettings settings;
-  base::FilePath log_dir;
-  updater::GetProductDirectory(&log_dir);
-  const auto log_file = log_dir.Append(FILE_PATH_LITERAL("updater_setup.log"));
-  settings.log_file_path = log_file.value().c_str();
-  settings.logging_dest = logging::LOG_TO_ALL;
-  logging::InitLogging(settings);
-  logging::SetLogItems(true,    // enable_process_id
-                       true,    // enable_thread_id
-                       true,    // enable_timestamp
-                       false);  // enable_tickcount
-  VLOG(1) << "Log file " << settings.log_file_path;
-}
-
-void InitializeUpdaterSetupMain() {
-  crash_reporter::InitializeCrashKeys();
-
-  static crash_reporter::CrashKeyString<16> crash_key_process_type(
-      "process_type");
-  crash_key_process_type.Set("updater_setup");
-
-  if (updater::CrashClient::GetInstance()->InitializeCrashReporting())
-    VLOG(1) << "Crash reporting initialized.";
-  else
-    VLOG(1) << "Crash reporting is not available.";
-
-  updater::StartCrashReporter(UPDATER_VERSION_STRING);
-
-  ThreadPoolStart();
-}
-
-void TerminateUpdaterSetupMain() {
-  ThreadPoolStop();
-}
-
 bool CopyBundle() {
   // Copy bundle to ~/Library/COMPANY_SHORTNAME_STRING/PRODUCT_FULLNAME_STRING.
   // e.g. ~/Library/Google/GoogleUpdater
@@ -102,11 +56,7 @@
     }
   }
 
-  base::FilePath this_executable_path;
-  base::PathService::Get(base::FILE_EXE, &this_executable_path);
-  const base::FilePath src_path =
-      this_executable_path.DirName().Append(GetUpdaterAppName());
-
+  const base::FilePath src_path = base::mac::OuterBundlePath();
   if (!base::CopyDirectory(src_path, dest_path, true)) {
     LOG(ERROR) << "Copying app to ~/Library failed";
     return false;
@@ -219,6 +169,8 @@
                                             CFSTR("Aqua"));
 }
 
+}  // namespace
+
 int SetupUpdater() {
   if (!CopyBundle())
     return -1;
@@ -238,47 +190,12 @@
   return 0;
 }
 
-}  // namespace
-
-int HandleUpdaterSetupCommands(const base::CommandLine* command_line) {
-  DCHECK(!command_line->HasSwitch(updater::kCrashHandlerSwitch));
-
-  if (command_line->HasSwitch(updater::kCrashMeSwitch)) {
-    LOG(FATAL) << "Crashing deliberately.";
-    return -100;
-  }
-
-  return SetupUpdater();
-}
-
-int UpdaterSetupMain(int argc, const char* const* argv) {
-  base::PlatformThread::SetName("UpdaterSetupMain");
-  base::AtExitManager exit_manager;
-
-  base::CommandLine::Init(argc, argv);
-  const auto* command_line = base::CommandLine::ForCurrentProcess();
-  if (command_line->HasSwitch(updater::kTestSwitch))
-    return 0;
-
-  InitLogging(*command_line);
-
-  if (command_line->HasSwitch(updater::kCrashHandlerSwitch))
-    return updater::CrashReporterMain();
-
-  InitializeUpdaterSetupMain();
-  const auto result = HandleUpdaterSetupCommands(command_line);
-  TerminateUpdaterSetupMain();
-  return result;
-}
-
-}  // namespace setup
-
 int Uninstall(bool is_machine) {
   ALLOW_UNUSED_LOCAL(is_machine);
-  if (!setup::RemoveFromLaunchd())
+  if (!RemoveFromLaunchd())
     return -1;
 
-  if (!setup::DeleteInstallFolder())
+  if (!DeleteInstallFolder())
     return -2;
 
   return 0;
diff --git a/chrome/updater/test/integration_tests_mac.mm b/chrome/updater/test/integration_tests_mac.mm
index 85fc865b..24ec7dd 100644
--- a/chrome/updater/test/integration_tests_mac.mm
+++ b/chrome/updater/test/integration_tests_mac.mm
@@ -31,13 +31,6 @@
       .Append(FILE_PATH_LITERAL(PRODUCT_FULLNAME_STRING));
 }
 
-base::FilePath GetInstallerPath() {
-  base::FilePath test_executable;
-  if (!base::PathService::Get(base::FILE_EXE, &test_executable))
-    return base::FilePath();
-  return test_executable.DirName().Append("updater_setup");
-}
-
 base::FilePath GetProductPath() {
   return base::mac::GetUserLibraryPath()
       .AppendASCII(COMPANY_SHORTNAME_STRING)
@@ -89,10 +82,12 @@
 }
 
 void Install() {
-  base::FilePath path = GetInstallerPath();
+  base::FilePath path = GetExecutablePath();
   ASSERT_FALSE(path.empty());
+  base::CommandLine command_line(path);
+  command_line.AppendSwitch("install");
   int exit_code = -1;
-  ASSERT_TRUE(Run(base::CommandLine(path), &exit_code));
+  ASSERT_TRUE(Run(command_line, &exit_code));
   EXPECT_EQ(0, exit_code);
 }
 
diff --git a/chrome/updater/updater.cc b/chrome/updater/updater.cc
index a5f709bb..2248889 100644
--- a/chrome/updater/updater.cc
+++ b/chrome/updater/updater.cc
@@ -27,6 +27,7 @@
 #endif
 
 #if defined(OS_MACOSX)
+#include "chrome/updater/mac/setup/install_app.h"
 #include "chrome/updater/server/mac/server.h"
 #endif
 
@@ -96,10 +97,10 @@
 #if defined(OS_WIN)
   if (command_line->HasSwitch(kComServiceSwitch))
     return ServiceMain::RunComService(command_line);
+#endif  // OS_WIN
 
   if (command_line->HasSwitch(kInstallSwitch))
     return AppInstallInstance()->Run();
-#endif  // OS_WIN
 
   if (command_line->HasSwitch(kUninstallSwitch))
     return AppUninstallInstance()->Run();
diff --git a/chrome/updater/win/install_app.cc b/chrome/updater/win/install_app.cc
index 2d51647..442dbc0 100644
--- a/chrome/updater/win/install_app.cc
+++ b/chrome/updater/win/install_app.cc
@@ -706,10 +706,6 @@
 
   void SetupDone(int result);
 
-  // TODO(sorin): remove the hardcoding of the application id.
-  // https://crbug.com/1014298
-  const std::string app_id_ = {kChromeAppId};
-
   scoped_refptr<Configurator> config_;
   scoped_refptr<InstallAppController> app_install_controller_;
 
@@ -747,7 +743,9 @@
 }
 
 void AppInstall::SetupDone(int result) {
-  if (result != 0) {
+  const auto app_id =
+      base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(kAppIdSwitch);
+  if (result != 0 || app_id.empty()) {
     Shutdown(result);
     return;
   }
@@ -757,7 +755,7 @@
 
   app_install_controller_ = base::MakeRefCounted<InstallAppController>(config_);
   app_install_controller_->InstallApp(
-      app_id_, base::BindOnce(&AppInstall::Shutdown, this));
+      app_id, base::BindOnce(&AppInstall::Shutdown, this));
 }
 
 scoped_refptr<App> AppInstallInstance() {
diff --git a/chromecast/graphics/accessibility/partial_magnification_controller_unittest.cc b/chromecast/graphics/accessibility/partial_magnification_controller_unittest.cc
index 2f1b074..456756d 100644
--- a/chromecast/graphics/accessibility/partial_magnification_controller_unittest.cc
+++ b/chromecast/graphics/accessibility/partial_magnification_controller_unittest.cc
@@ -63,9 +63,8 @@
   void SetUp() override {
     views::ViewsTestBase::SetUp();
 
-    screen_position_client_.reset(new wm::DefaultScreenPositionClient());
-    aura::client::SetScreenPositionClient(root_window(),
-                                          screen_position_client_.get());
+    screen_position_client_.reset(
+        new wm::DefaultScreenPositionClient(root_window()));
     controller_ =
         std::make_unique<PartialMagnificationController>(root_window());
   }
@@ -74,6 +73,7 @@
     // PartialMagnificationController needs to be deleted before the root window
     // is torn down by ViewsTestBase.
     controller_.reset();
+    screen_position_client_.reset();
 
     views::ViewsTestBase::TearDown();
   }
diff --git a/chromecast/graphics/cast_window_manager_aura.cc b/chromecast/graphics/cast_window_manager_aura.cc
index a89faa6..d7b2202 100644
--- a/chromecast/graphics/cast_window_manager_aura.cc
+++ b/chromecast/graphics/cast_window_manager_aura.cc
@@ -19,7 +19,6 @@
 #include "chromecast/graphics/rounded_window_corners.h"
 #include "ui/aura/client/default_capture_client.h"
 #include "ui/aura/client/focus_change_observer.h"
-#include "ui/aura/client/screen_position_client.h"
 #include "ui/aura/env.h"
 #include "ui/aura/layout_manager.h"
 #include "ui/aura/window.h"
@@ -227,12 +226,10 @@
   aura::client::SetWindowParentingClient(tree_window, this);
   capture_client_.reset(new aura::client::DefaultCaptureClient(tree_window));
 
-  screen_position_client_ = std::make_unique<wm::DefaultScreenPositionClient>();
-
   // TODO(seantopping): Is |root_window| different from |tree_window|?
   aura::Window* root_window = tree_window->GetRootWindow();
-  aura::client::SetScreenPositionClient(root_window,
-                                        screen_position_client_.get());
+  screen_position_client_ =
+      std::make_unique<wm::DefaultScreenPositionClient>(root_window);
 
   window_tree_host_->Show();
 
@@ -300,6 +297,7 @@
   capture_client_.reset();
   aura::client::SetWindowParentingClient(window_tree_host_->window(), nullptr);
   wm::SetActivationClient(window_tree_host_->window(), nullptr);
+  screen_position_client_.reset();
   aura::client::SetFocusClient(window_tree_host_->window(), nullptr);
   focus_client_.reset();
   system_gesture_event_handler_.reset();
diff --git a/chromecast/graphics/gestures/multiple_tap_detector_test.cc b/chromecast/graphics/gestures/multiple_tap_detector_test.cc
index ef5a3d3c..7753c6d 100644
--- a/chromecast/graphics/gestures/multiple_tap_detector_test.cc
+++ b/chromecast/graphics/gestures/multiple_tap_detector_test.cc
@@ -7,7 +7,6 @@
 #include "base/run_loop.h"
 #include "base/test/simple_test_tick_clock.h"
 #include "testing/gmock/include/gmock/gmock.h"
-#include "ui/aura/client/screen_position_client.h"
 #include "ui/aura/test/aura_test_base.h"
 #include "ui/aura/window.h"
 #include "ui/events/event_utils.h"
@@ -40,9 +39,8 @@
   void SetUp() override {
     aura::test::AuraTestBase::SetUp();
 
-    screen_position_client_.reset(new wm::DefaultScreenPositionClient());
-    aura::client::SetScreenPositionClient(root_window(),
-                                          screen_position_client_.get());
+    screen_position_client_.reset(
+        new wm::DefaultScreenPositionClient(root_window()));
 
     triple_tap_delegate_ = std::make_unique<MockMultipleTapDetectorDelegate>();
     triple_tap_detector_ = std::make_unique<MultipleTapDetector>(
@@ -59,6 +57,7 @@
   void TearDown() override {
     ui::SetEventTickClockForTesting(nullptr);
     triple_tap_detector_.reset();
+    screen_position_client_.reset();
     aura::test::AuraTestBase::TearDown();
   }
 
diff --git a/chromecast/graphics/gestures/side_swipe_detector_test.cc b/chromecast/graphics/gestures/side_swipe_detector_test.cc
index f3c3584..ebc6aad1 100644
--- a/chromecast/graphics/gestures/side_swipe_detector_test.cc
+++ b/chromecast/graphics/gestures/side_swipe_detector_test.cc
@@ -72,9 +72,8 @@
   void SetUp() override {
     aura::test::AuraTestBase::SetUp();
 
-    screen_position_client_.reset(new wm::DefaultScreenPositionClient());
-    aura::client::SetScreenPositionClient(root_window(),
-                                          screen_position_client_.get());
+    screen_position_client_.reset(
+        new wm::DefaultScreenPositionClient(root_window()));
 
     gesture_handler_ = std::make_unique<MockCastGestureHandler>();
     side_swipe_detector_ = std::make_unique<SideSwipeDetector>(
@@ -92,6 +91,7 @@
   void TearDown() override {
     side_swipe_detector_.reset();
     gesture_handler_.reset();
+    screen_position_client_.reset();
 
     aura::test::AuraTestBase::TearDown();
   }
diff --git a/chromeos/components/security_token_pin/error_generator_unittest.cc b/chromeos/components/security_token_pin/error_generator_unittest.cc
index 7a5f810..211d19a 100644
--- a/chromeos/components/security_token_pin/error_generator_unittest.cc
+++ b/chromeos/components/security_token_pin/error_generator_unittest.cc
@@ -24,7 +24,9 @@
  protected:
   SecurityTokenPinErrorGeneratorTest() { InitI18n(); }
 
-  ~SecurityTokenPinErrorGeneratorTest() override = default;
+  ~SecurityTokenPinErrorGeneratorTest() override {
+    ui::ResourceBundle::CleanupSharedInstance();
+  }
 
  private:
   // Initializes the i18n stack and loads the necessary strings. Uses a specific
diff --git a/chromeos/components/sync_wifi/BUILD.gn b/chromeos/components/sync_wifi/BUILD.gn
index ca8cdcc9..d28618b 100644
--- a/chromeos/components/sync_wifi/BUILD.gn
+++ b/chromeos/components/sync_wifi/BUILD.gn
@@ -95,6 +95,7 @@
     "//chromeos/services/network_config",
     "//chromeos/services/network_config:in_process_instance",
     "//chromeos/services/network_config/public/cpp:test_support",
+    "//components/prefs:test_support",
     "//components/sync:test_support",
     "//components/sync_preferences:test_support",
     "//components/user_manager:test_support",
diff --git a/chromeos/components/sync_wifi/fake_local_network_collector.cc b/chromeos/components/sync_wifi/fake_local_network_collector.cc
index d6e6866..0e83c5a 100644
--- a/chromeos/components/sync_wifi/fake_local_network_collector.cc
+++ b/chromeos/components/sync_wifi/fake_local_network_collector.cc
@@ -19,10 +19,10 @@
   std::move(callback).Run(networks_);
 }
 
-void FakeLocalNetworkCollector::GetSyncableNetwork(const NetworkIdentifier& id,
+void FakeLocalNetworkCollector::GetSyncableNetwork(const std::string& guid,
                                                    ProtoCallback callback) {
   for (sync_pb::WifiConfigurationSpecifics proto : networks_) {
-    if (NetworkIdentifier::FromProto(proto) == id) {
+    if (NetworkIdentifier::FromProto(proto).SerializeToString() == guid) {
       std::move(callback).Run(proto);
       return;
     }
@@ -31,6 +31,18 @@
   std::move(callback).Run(base::nullopt);
 }
 
+base::Optional<NetworkIdentifier>
+FakeLocalNetworkCollector::GetNetworkIdentifierFromGuid(
+    const std::string& guid) {
+  for (sync_pb::WifiConfigurationSpecifics proto : networks_) {
+    auto id = NetworkIdentifier::FromProto(proto);
+    if (id.SerializeToString() == guid) {
+      return id;
+    }
+  }
+  return base::nullopt;
+}
+
 void FakeLocalNetworkCollector::AddNetwork(
     sync_pb::WifiConfigurationSpecifics proto) {
   networks_.push_back(proto);
@@ -40,6 +52,9 @@
   networks_.clear();
 }
 
+void FakeLocalNetworkCollector::SetNetworkMetadataStore(
+    base::WeakPtr<NetworkMetadataStore> network_metadata_store) {}
+
 }  // namespace sync_wifi
 
-}  // namespace chromeos
\ No newline at end of file
+}  // namespace chromeos
diff --git a/chromeos/components/sync_wifi/fake_local_network_collector.h b/chromeos/components/sync_wifi/fake_local_network_collector.h
index d7d07a50..f4976aa2 100644
--- a/chromeos/components/sync_wifi/fake_local_network_collector.h
+++ b/chromeos/components/sync_wifi/fake_local_network_collector.h
@@ -24,11 +24,17 @@
 
   // sync_wifi::LocalNetworkCollector::
   void GetAllSyncableNetworks(ProtoListCallback callback) override;
-  void GetSyncableNetwork(const NetworkIdentifier& id,
+  // For test purposes, |guid| == serialized NetworkIdentifier.
+  void GetSyncableNetwork(const std::string& guid,
                           ProtoCallback callback) override;
+  // For test purposes, |guid| == serialized NetworkIdentifier.
+  base::Optional<NetworkIdentifier> GetNetworkIdentifierFromGuid(
+      const std::string& guid) override;
 
   void AddNetwork(sync_pb::WifiConfigurationSpecifics proto);
   void ClearNetworks();
+  void SetNetworkMetadataStore(
+      base::WeakPtr<NetworkMetadataStore> network_metadata_store) override;
 
  private:
   std::vector<sync_pb::WifiConfigurationSpecifics> networks_;
diff --git a/chromeos/components/sync_wifi/local_network_collector.h b/chromeos/components/sync_wifi/local_network_collector.h
index 95b3e1a0..f9e73b3 100644
--- a/chromeos/components/sync_wifi/local_network_collector.h
+++ b/chromeos/components/sync_wifi/local_network_collector.h
@@ -10,6 +10,7 @@
 
 #include "base/memory/weak_ptr.h"
 #include "base/optional.h"
+#include "chromeos/components/sync_wifi/network_identifier.h"
 #include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 
@@ -19,9 +20,9 @@
 
 namespace chromeos {
 
-namespace sync_wifi {
+class NetworkMetadataStore;
 
-class NetworkIdentifier;
+namespace sync_wifi {
 
 // Handles the retrieval, filtering, and conversion of local network
 // configurations to syncable protos.
@@ -46,8 +47,17 @@
   // Creates a WifiConfigurationSpecifics proto with the relevant network
   // details for the network with the given |id|.  If that network doesn't
   // exist or isn't syncable it will provide base::nullopt to the callback.
-  virtual void GetSyncableNetwork(const NetworkIdentifier& id,
+  virtual void GetSyncableNetwork(const std::string& guid,
                                   ProtoCallback callback) = 0;
+
+  // Retrieves the NetworkIdentifier for a given local network's |guid|
+  // if the network no longer exists it returns nullopt.
+  virtual base::Optional<NetworkIdentifier> GetNetworkIdentifierFromGuid(
+      const std::string& guid) = 0;
+
+  // Provides the metadata store which gets constructed later.
+  virtual void SetNetworkMetadataStore(
+      base::WeakPtr<NetworkMetadataStore> network_metadata_store) = 0;
 };
 
 }  // namespace sync_wifi
diff --git a/chromeos/components/sync_wifi/local_network_collector_impl.cc b/chromeos/components/sync_wifi/local_network_collector_impl.cc
index a233bf46..27d9b83 100644
--- a/chromeos/components/sync_wifi/local_network_collector_impl.cc
+++ b/chromeos/components/sync_wifi/local_network_collector_impl.cc
@@ -36,10 +36,8 @@
 }  // namespace
 
 LocalNetworkCollectorImpl::LocalNetworkCollectorImpl(
-    network_config::mojom::CrosNetworkConfig* cros_network_config,
-    NetworkMetadataStore* network_metadata_store)
-    : cros_network_config_(cros_network_config),
-      network_metadata_store_(network_metadata_store) {
+    network_config::mojom::CrosNetworkConfig* cros_network_config)
+    : cros_network_config_(cros_network_config) {
   cros_network_config_->AddObserver(
       cros_network_config_observer_receiver_.BindNewPipeAndPassRemote());
 
@@ -72,12 +70,12 @@
   }
 }
 
-void LocalNetworkCollectorImpl::GetSyncableNetwork(const NetworkIdentifier& id,
+void LocalNetworkCollectorImpl::GetSyncableNetwork(const std::string& guid,
                                                    ProtoCallback callback) {
   const network_config::mojom::NetworkStateProperties* network = nullptr;
   for (const network_config::mojom::NetworkStatePropertiesPtr& n :
        mojo_networks_) {
-    if (NetworkIdentifier::FromMojoNetwork(n) == id) {
+    if (n->guid == guid) {
       if (IsEligible(n)) {
         network = n.get();
       }
@@ -97,6 +95,23 @@
   StartGetNetworkDetails(network, request_guid);
 }
 
+base::Optional<NetworkIdentifier>
+LocalNetworkCollectorImpl::GetNetworkIdentifierFromGuid(
+    const std::string& guid) {
+  for (const network_config::mojom::NetworkStatePropertiesPtr& network :
+       mojo_networks_) {
+    if (network->guid == guid) {
+      return NetworkIdentifier::FromMojoNetwork(network);
+    }
+  }
+  return base::nullopt;
+}
+
+void LocalNetworkCollectorImpl::SetNetworkMetadataStore(
+    base::WeakPtr<NetworkMetadataStore> network_metadata_store) {
+  network_metadata_store_ = network_metadata_store;
+}
+
 std::string LocalNetworkCollectorImpl::InitializeRequest() {
   std::string request_guid = base::GenerateGUID();
   request_guid_to_complete_protos_[request_guid] =
diff --git a/chromeos/components/sync_wifi/local_network_collector_impl.h b/chromeos/components/sync_wifi/local_network_collector_impl.h
index f7aa0ae..3883367 100644
--- a/chromeos/components/sync_wifi/local_network_collector_impl.h
+++ b/chromeos/components/sync_wifi/local_network_collector_impl.h
@@ -38,18 +38,23 @@
   // LocalNetworkCollector:
 
   // |cros_network_config| and |network_metadata_store| must outlive this class.
-  LocalNetworkCollectorImpl(
-      network_config::mojom::CrosNetworkConfig* cros_network_config,
-      NetworkMetadataStore* network_metadata_store);
+  explicit LocalNetworkCollectorImpl(
+      network_config::mojom::CrosNetworkConfig* cros_network_config);
   ~LocalNetworkCollectorImpl() override;
 
   // Can only execute one request at a time.
   void GetAllSyncableNetworks(ProtoListCallback callback) override;
 
   // Can be called on multiple networks simultaneously.
-  void GetSyncableNetwork(const NetworkIdentifier& id,
+  void GetSyncableNetwork(const std::string& guid,
                           ProtoCallback callback) override;
 
+  base::Optional<NetworkIdentifier> GetNetworkIdentifierFromGuid(
+      const std::string& guid) override;
+
+  void SetNetworkMetadataStore(
+      base::WeakPtr<NetworkMetadataStore> network_metadata_store) override;
+
   // CrosNetworkConfigObserver:
   void OnNetworkStateListChanged() override;
   void OnActiveNetworksChanged(
@@ -100,7 +105,7 @@
   mojo::Receiver<chromeos::network_config::mojom::CrosNetworkConfigObserver>
       cros_network_config_observer_receiver_{this};
   std::vector<network_config::mojom::NetworkStatePropertiesPtr> mojo_networks_;
-  NetworkMetadataStore* network_metadata_store_;
+  base::WeakPtr<NetworkMetadataStore> network_metadata_store_;
 
   base::flat_map<std::string, std::vector<sync_pb::WifiConfigurationSpecifics>>
       request_guid_to_complete_protos_;
diff --git a/chromeos/components/sync_wifi/local_network_collector_impl_unittest.cc b/chromeos/components/sync_wifi/local_network_collector_impl_unittest.cc
index f9f7c9d..b06f62a8 100644
--- a/chromeos/components/sync_wifi/local_network_collector_impl_unittest.cc
+++ b/chromeos/components/sync_wifi/local_network_collector_impl_unittest.cc
@@ -19,6 +19,7 @@
 #include "chromeos/components/sync_wifi/test_data_generator.h"
 #include "chromeos/dbus/shill/fake_shill_simulated_result.h"
 #include "chromeos/network/network_handler.h"
+#include "chromeos/network/network_metadata_store.h"
 #include "chromeos/services/network_config/cros_network_config.h"
 #include "chromeos/services/network_config/in_process_instance.h"
 #include "chromeos/services/network_config/public/cpp/cros_network_config_test_helper.h"
@@ -58,8 +59,9 @@
     testing::Test::SetUp();
     helper()->SetUp();
     local_network_collector_ = std::make_unique<LocalNetworkCollectorImpl>(
-        remote_cros_network_config_.get(),
-        NetworkHandler::Get()->network_metadata_store());
+        remote_cros_network_config_.get());
+    local_network_collector_->SetNetworkMetadataStore(
+        NetworkHandler::Get()->network_metadata_store()->GetWeakPtr());
   }
 
   void TearDown() override {
@@ -144,30 +146,29 @@
 }
 
 TEST_F(LocalNetworkCollectorImplTest, TestGetSyncableNetwork) {
-  helper()->ConfigureWiFiNetwork(kFredSsid, /*is_secured=*/true,
-                                 /*in_profile=*/true, /*has_connected=*/true);
-
-  NetworkIdentifier id = GeneratePskNetworkId(kFredSsid);
+  std::string guid = helper()->ConfigureWiFiNetwork(
+      kFredSsid, /*is_secured=*/true,
+      /*in_profile=*/true, /*has_connected=*/true);
   local_network_collector()->GetSyncableNetwork(
-      id, base::BindOnce(&LocalNetworkCollectorImplTest::OnGetSyncableNetwork,
-                         base::Unretained(this), kFredSsid));
+      guid, base::BindOnce(&LocalNetworkCollectorImplTest::OnGetSyncableNetwork,
+                           base::Unretained(this), kFredSsid));
 }
 
 TEST_F(LocalNetworkCollectorImplTest, TestGetSyncableNetwork_DoesntExist) {
-  NetworkIdentifier id = GeneratePskNetworkId(kFredSsid);
   local_network_collector()->GetSyncableNetwork(
-      id, base::BindOnce(&LocalNetworkCollectorImplTest::OnGetSyncableNetwork,
-                         base::Unretained(this), std::string()));
+      "test_guid",
+      base::BindOnce(&LocalNetworkCollectorImplTest::OnGetSyncableNetwork,
+                     base::Unretained(this), std::string()));
 }
 
 TEST_F(LocalNetworkCollectorImplTest, TestGetSyncableNetwork_NeverConnected) {
-  helper()->ConfigureWiFiNetwork(kFredSsid, /*is_secured=*/true,
-                                 /*in_profile=*/true, /*has_connected=*/false);
+  std::string guid = helper()->ConfigureWiFiNetwork(
+      kFredSsid, /*is_secured=*/true,
+      /*in_profile=*/true, /*has_connected=*/false);
 
-  NetworkIdentifier id = GeneratePskNetworkId(kFredSsid);
   local_network_collector()->GetSyncableNetwork(
-      id, base::BindOnce(&LocalNetworkCollectorImplTest::OnGetSyncableNetwork,
-                         base::Unretained(this), std::string()));
+      guid, base::BindOnce(&LocalNetworkCollectorImplTest::OnGetSyncableNetwork,
+                           base::Unretained(this), std::string()));
 }
 
 }  // namespace sync_wifi
diff --git a/chromeos/components/sync_wifi/network_identifier.cc b/chromeos/components/sync_wifi/network_identifier.cc
index 2d6d91f..bc86888 100644
--- a/chromeos/components/sync_wifi/network_identifier.cc
+++ b/chromeos/components/sync_wifi/network_identifier.cc
@@ -8,6 +8,7 @@
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "chromeos/components/sync_wifi/network_type_conversions.h"
+#include "chromeos/network/network_state.h"
 #include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
 #include "components/sync/protocol/wifi_configuration_specifics.pb.h"
 #include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
@@ -48,6 +49,12 @@
   return NetworkIdentifier(pieces[0], pieces[1]);
 }
 
+// static
+NetworkIdentifier NetworkIdentifier::FromNetworkState(
+    const NetworkState* network) {
+  return NetworkIdentifier(network->GetHexSsid(), network->security_class());
+}
+
 NetworkIdentifier::NetworkIdentifier(const std::string& hex_ssid,
                                      const std::string& security_type)
     : security_type_(security_type) {
diff --git a/chromeos/components/sync_wifi/network_identifier.h b/chromeos/components/sync_wifi/network_identifier.h
index 1acd914..7dfc7a4 100644
--- a/chromeos/components/sync_wifi/network_identifier.h
+++ b/chromeos/components/sync_wifi/network_identifier.h
@@ -15,6 +15,8 @@
 
 namespace chromeos {
 
+class NetworkState;
+
 namespace sync_wifi {
 
 // A unique identifier for synced networks which contains the properties
@@ -25,6 +27,7 @@
       const sync_pb::WifiConfigurationSpecifics& specifics);
   static NetworkIdentifier FromMojoNetwork(
       const network_config::mojom::NetworkStatePropertiesPtr& network);
+  static NetworkIdentifier FromNetworkState(const NetworkState* network);
   // |serialized_string| is in the format of hex_ssid and security_type
   // concatenated with an underscore.  security_type is the shill constant
   // returned from NetworkState::security_class(). For example, it would be
diff --git a/chromeos/components/sync_wifi/network_test_helper.cc b/chromeos/components/sync_wifi/network_test_helper.cc
index 5e988fbf..b1ee0e1 100644
--- a/chromeos/components/sync_wifi/network_test_helper.cc
+++ b/chromeos/components/sync_wifi/network_test_helper.cc
@@ -77,10 +77,10 @@
   base::RunLoop().RunUntilIdle();
 }
 
-void NetworkTestHelper::ConfigureWiFiNetwork(const std::string& ssid,
-                                             bool is_secured,
-                                             bool in_profile,
-                                             bool has_connected) {
+std::string NetworkTestHelper::ConfigureWiFiNetwork(const std::string& ssid,
+                                                    bool is_secured,
+                                                    bool in_profile,
+                                                    bool has_connected) {
   std::string security_entry =
       is_secured ? R"("SecurityClass": "psk", "Passphrase": "secretsauce", )"
                  : R"("SecurityClass": "none", )";
@@ -88,12 +88,13 @@
       in_profile ? base::StringPrintf(R"("Profile": "%s", )",
                                       network_state_helper_->UserHash())
                  : std::string();
+  std::string guid = base::StringPrintf("%s_guid", ssid.c_str());
   std::string service_path =
       network_state_helper_->ConfigureService(base::StringPrintf(
-          R"({"GUID": "%s_guid", "Type": "wifi", "SSID": "%s",
+          R"({"GUID": "%s", "Type": "wifi", "SSID": "%s",
             %s "State": "ready", "Strength": 100,
             %s "AutoConnect": true, "Connectable": true})",
-          ssid.c_str(), ssid.c_str(), security_entry.c_str(),
+          guid.c_str(), ssid.c_str(), security_entry.c_str(),
           profile_entry.c_str()));
 
   base::RunLoop().RunUntilIdle();
@@ -102,6 +103,8 @@
     NetworkHandler::Get()->network_metadata_store()->ConnectSucceeded(
         service_path);
   }
+
+  return guid;
 }
 
 NetworkStateTestHelper* NetworkTestHelper::network_state_test_helper() {
diff --git a/chromeos/components/sync_wifi/network_test_helper.h b/chromeos/components/sync_wifi/network_test_helper.h
index b2c4e4c..afba862 100644
--- a/chromeos/components/sync_wifi/network_test_helper.h
+++ b/chromeos/components/sync_wifi/network_test_helper.h
@@ -29,10 +29,12 @@
   virtual ~NetworkTestHelper();
 
   void SetUp();
-  void ConfigureWiFiNetwork(const std::string& ssid,
-                            bool is_secured,
-                            bool in_profile,
-                            bool has_connected);
+
+  // Returns the |guid| of the newly configured network.
+  std::string ConfigureWiFiNetwork(const std::string& ssid,
+                                   bool is_secured,
+                                   bool in_profile,
+                                   bool has_connected);
 
   NetworkStateTestHelper* network_state_test_helper();
 
diff --git a/chromeos/components/sync_wifi/wifi_configuration_bridge.cc b/chromeos/components/sync_wifi/wifi_configuration_bridge.cc
index b144a53..82e5607d 100644
--- a/chromeos/components/sync_wifi/wifi_configuration_bridge.cc
+++ b/chromeos/components/sync_wifi/wifi_configuration_bridge.cc
@@ -11,10 +11,13 @@
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/optional.h"
+#include "base/strings/stringprintf.h"
 #include "base/time/clock.h"
 #include "base/time/time.h"
 #include "chromeos/components/sync_wifi/network_identifier.h"
 #include "chromeos/components/sync_wifi/synced_network_updater.h"
+#include "chromeos/network/network_configuration_handler.h"
+#include "chromeos/network/network_metadata_store.h"
 #include "components/device_event_log/device_event_log.h"
 #include "components/sync/model/entity_change.h"
 #include "components/sync/model/metadata_batch.h"
@@ -22,6 +25,7 @@
 #include "components/sync/model/model_type_change_processor.h"
 #include "components/sync/model/mutable_data_batch.h"
 #include "components/sync/protocol/model_type_state.pb.h"
+#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
 
 namespace chromeos {
 
@@ -41,18 +45,31 @@
 WifiConfigurationBridge::WifiConfigurationBridge(
     SyncedNetworkUpdater* synced_network_updater,
     LocalNetworkCollector* local_network_collector,
+    NetworkConfigurationHandler* network_configuration_handler,
     std::unique_ptr<syncer::ModelTypeChangeProcessor> change_processor,
     syncer::OnceModelTypeStoreFactory create_store_callback)
     : ModelTypeSyncBridge(std::move(change_processor)),
       synced_network_updater_(synced_network_updater),
-      local_network_collector_(local_network_collector) {
+      local_network_collector_(local_network_collector),
+      network_configuration_handler_(network_configuration_handler),
+      network_metadata_store_(nullptr) {
   std::move(create_store_callback)
       .Run(syncer::WIFI_CONFIGURATIONS,
            base::BindOnce(&WifiConfigurationBridge::OnStoreCreated,
                           weak_ptr_factory_.GetWeakPtr()));
+  if (network_configuration_handler_) {
+    network_configuration_handler_->AddObserver(this);
+  }
 }
 
-WifiConfigurationBridge::~WifiConfigurationBridge() {}
+WifiConfigurationBridge::~WifiConfigurationBridge() {
+  if (network_metadata_store_) {
+    network_metadata_store_->RemoveObserver(this);
+  }
+  if (network_configuration_handler_) {
+    network_configuration_handler_->RemoveObserver(this);
+  }
+}
 
 std::unique_ptr<syncer::MetadataChangeList>
 WifiConfigurationBridge::CreateMetadataChangeList() {
@@ -294,6 +311,100 @@
   return ids;
 }
 
+void WifiConfigurationBridge::OnFirstConnectionToNetwork(
+    const std::string& guid) {
+  if (network_metadata_store_->GetIsConfiguredBySync(guid)) {
+    // Don't have to upload a configuration that came from sync.
+    return;
+  }
+
+  local_network_collector_->GetSyncableNetwork(
+      guid, base::BindOnce(&WifiConfigurationBridge::SaveNetworkToSync,
+                           weak_ptr_factory_.GetWeakPtr()));
+}
+
+void WifiConfigurationBridge::SaveNetworkToSync(
+    base::Optional<sync_pb::WifiConfigurationSpecifics> proto) {
+  if (!proto) {
+    return;
+  }
+
+  std::unique_ptr<syncer::EntityData> entity_data =
+      GenerateWifiEntityData(*proto);
+  std::string storage_key = GetStorageKey(*entity_data);
+
+  std::unique_ptr<syncer::ModelTypeStore::WriteBatch> batch =
+      store_->CreateWriteBatch();
+  batch->WriteData(storage_key, proto->SerializeAsString());
+  change_processor()->Put(storage_key, std::move(entity_data),
+                          batch->GetMetadataChangeList());
+  entries_[storage_key] = *proto;
+  Commit(std::move(batch));
+}
+
+void WifiConfigurationBridge::OnBeforeConfigurationRemoved(
+    const std::string& service_path,
+    const std::string& guid) {
+  base::Optional<NetworkIdentifier> id =
+      local_network_collector_->GetNetworkIdentifierFromGuid(guid);
+  if (!id) {
+    return;
+  }
+
+  std::string storage_key = id->SerializeToString();
+  if (entries_.contains(storage_key))
+    pending_deletes_[guid] = storage_key;
+}
+
+void WifiConfigurationBridge::OnConfigurationRemoved(
+    const std::string& service_path,
+    const std::string& network_guid) {
+  LOG(ERROR) << "WifiConfigurationBridge::RemoveNetworkFromSync" << this;
+  if (!pending_deletes_.contains(network_guid))
+    return;
+
+  std::string storage_key = pending_deletes_[network_guid];
+
+  std::unique_ptr<syncer::ModelTypeStore::WriteBatch> batch =
+      store_->CreateWriteBatch();
+  batch->DeleteData(storage_key);
+  change_processor()->Delete(storage_key, batch->GetMetadataChangeList());
+  entries_.erase(storage_key);
+  Commit(std::move(batch));
+}
+
+void WifiConfigurationBridge::OnConfigurationModified(
+    const std::string& service_path,
+    const std::string& guid,
+    base::DictionaryValue* set_properties) {
+  if (!set_properties)
+    return;
+
+  if (network_metadata_store_->GetIsConfiguredBySync(guid)) {
+    // Don't have to upload a configuration that came from sync.
+    return;
+  }
+  if (set_properties->HasKey(shill::kAutoConnectProperty) ||
+      set_properties->HasKey(shill::kPriorityProperty) ||
+      set_properties->HasKey(shill::kProxyConfigProperty) ||
+      set_properties->FindPath(
+          base::StringPrintf("%s.%s", shill::kStaticIPConfigProperty,
+                             shill::kNameServersProperty))) {
+    local_network_collector_->GetSyncableNetwork(
+        guid, base::BindOnce(&WifiConfigurationBridge::SaveNetworkToSync,
+                             weak_ptr_factory_.GetWeakPtr()));
+  }
+}
+
+void WifiConfigurationBridge::SetNetworkMetadataStore(
+    base::WeakPtr<NetworkMetadataStore> network_metadata_store) {
+  if (network_metadata_store_) {
+    network_metadata_store->RemoveObserver(this);
+  }
+  network_metadata_store_ = network_metadata_store;
+  network_metadata_store->AddObserver(this);
+}
+
 }  // namespace sync_wifi
 
 }  // namespace chromeos
diff --git a/chromeos/components/sync_wifi/wifi_configuration_bridge.h b/chromeos/components/sync_wifi/wifi_configuration_bridge.h
index ccd37c7e..889e87f6 100644
--- a/chromeos/components/sync_wifi/wifi_configuration_bridge.h
+++ b/chromeos/components/sync_wifi/wifi_configuration_bridge.h
@@ -16,6 +16,8 @@
 #include "base/time/time.h"
 #include "chromeos/components/sync_wifi/local_network_collector.h"
 #include "chromeos/components/sync_wifi/synced_network_updater.h"
+#include "chromeos/network/network_configuration_observer.h"
+#include "chromeos/network/network_metadata_observer.h"
 #include "components/sync/base/model_type.h"
 #include "components/sync/model/model_type_store.h"
 #include "components/sync/model/model_type_sync_bridge.h"
@@ -26,15 +28,21 @@
 
 namespace chromeos {
 
+class NetworkConfigurationHandler;
+class NetworkMetadataStore;
+
 namespace sync_wifi {
 
 // Receives updates to network configurations from the Chrome sync back end and
 // from the system network stack and keeps both lists in sync.
-class WifiConfigurationBridge : public syncer::ModelTypeSyncBridge {
+class WifiConfigurationBridge : public syncer::ModelTypeSyncBridge,
+                                public NetworkConfigurationObserver,
+                                public NetworkMetadataObserver {
  public:
   WifiConfigurationBridge(
       SyncedNetworkUpdater* synced_network_updater,
       LocalNetworkCollector* local_network_collector,
+      NetworkConfigurationHandler* network_configuration_handler,
       std::unique_ptr<syncer::ModelTypeChangeProcessor> change_processor,
       syncer::OnceModelTypeStoreFactory create_store_callback);
   ~WifiConfigurationBridge() override;
@@ -53,9 +61,24 @@
   std::string GetClientTag(const syncer::EntityData& entity_data) override;
   std::string GetStorageKey(const syncer::EntityData& entity_data) override;
 
+  // NetworkMetadataObserver:
+  void OnFirstConnectionToNetwork(const std::string& guid) override;
+
+  // NetworkConfigurationObserver::
+  void OnConfigurationModified(const std::string& service_path,
+                               const std::string& guid,
+                               base::DictionaryValue* set_properties) override;
+  void OnBeforeConfigurationRemoved(const std::string& service_path,
+                                    const std::string& guid) override;
+  void OnConfigurationRemoved(const std::string& service_path,
+                              const std::string& guid) override;
+
   // Comes from |entries_| the in-memory map.
   std::vector<NetworkIdentifier> GetAllIdsForTesting();
 
+  void SetNetworkMetadataStore(
+      base::WeakPtr<NetworkMetadataStore> network_metadata_store);
+
  private:
   void Commit(std::unique_ptr<syncer::ModelTypeStore::WriteBatch> batch);
 
@@ -74,20 +97,31 @@
       syncer::EntityChangeList change_list,
       std::vector<sync_pb::WifiConfigurationSpecifics> local_network_list);
 
+  void SaveNetworkToSync(
+      base::Optional<sync_pb::WifiConfigurationSpecifics> proto);
+  void RemoveNetworkFromSync(
+      base::Optional<sync_pb::WifiConfigurationSpecifics> proto);
+
   // An in-memory list of the proto's that mirrors what is on the sync server.
   // This gets updated when changes are received from the server and after local
   // changes have been committed.  On initialization of this class, it is
   // populated with the contents of |store_|.
   base::flat_map<std::string, sync_pb::WifiConfigurationSpecifics> entries_;
 
+  // Map of network |guid| to |storage_key|.  After a network is deleted, we
+  // no longer have access to its metadata so this stores the necessary
+  // information to delete it from sync.
+  base::flat_map<std::string, std::string> pending_deletes_;
+
   // The on disk store of WifiConfigurationSpecifics protos that mirrors what
   // is on the sync server.  This gets updated when changes are received from
   // the server and after local changes have been committed to the server.
   std::unique_ptr<syncer::ModelTypeStore> store_;
 
   SyncedNetworkUpdater* synced_network_updater_;
-
   LocalNetworkCollector* local_network_collector_;
+  NetworkConfigurationHandler* network_configuration_handler_;
+  base::WeakPtr<NetworkMetadataStore> network_metadata_store_;
 
   base::WeakPtrFactory<WifiConfigurationBridge> weak_ptr_factory_{this};
 
diff --git a/chromeos/components/sync_wifi/wifi_configuration_bridge_unittest.cc b/chromeos/components/sync_wifi/wifi_configuration_bridge_unittest.cc
index 1f63555b..a5547d9e 100644
--- a/chromeos/components/sync_wifi/wifi_configuration_bridge_unittest.cc
+++ b/chromeos/components/sync_wifi/wifi_configuration_bridge_unittest.cc
@@ -16,6 +16,9 @@
 #include "chromeos/components/sync_wifi/network_identifier.h"
 #include "chromeos/components/sync_wifi/synced_network_updater.h"
 #include "chromeos/components/sync_wifi/test_data_generator.h"
+#include "chromeos/network/network_metadata_store.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
 #include "components/sync/model/data_batch.h"
 #include "components/sync/model/entity_change.h"
 #include "components/sync/model/metadata_batch.h"
@@ -24,8 +27,10 @@
 #include "components/sync/model_impl/in_memory_metadata_change_list.h"
 #include "components/sync/protocol/model_type_state.pb.h"
 #include "components/sync/test/test_matchers.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
 
 namespace chromeos {
 
@@ -46,12 +51,13 @@
 const char kSsidMeow[] = "meow";
 const char kSsidWoof[] = "woof";
 const char kSsidHonk[] = "honk";
+const char kSyncPsk[] = "sync_psk";
+const char kLocalPsk[] = "local_psk";
 
 syncer::EntityData GenerateWifiEntityData(
     const sync_pb::WifiConfigurationSpecifics& data) {
   syncer::EntityData entity_data;
-  entity_data.specifics.mutable_wifi_configuration()
-      ->CopyFrom(data);
+  entity_data.specifics.mutable_wifi_configuration()->CopyFrom(data);
   entity_data.name = data.hex_ssid();
   return entity_data;
 }
@@ -117,10 +123,24 @@
     ON_CALL(mock_processor_, IsTrackingMetadata()).WillByDefault(Return(true));
     synced_network_updater_ = std::make_unique<TestSyncedNetworkUpdater>();
     local_network_collector_ = std::make_unique<FakeLocalNetworkCollector>();
+
+    user_prefs_ =
+        std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
+    device_prefs_ = std::make_unique<TestingPrefServiceSimple>();
+    NetworkMetadataStore::RegisterPrefs(user_prefs_->registry());
+    NetworkMetadataStore::RegisterPrefs(device_prefs_->registry());
+    network_metadata_store_ = std::make_unique<NetworkMetadataStore>(
+        /*network_configuration_handler=*/nullptr,
+        /*network_connection_handler=*/nullptr,
+        /*network_state_handler=*/nullptr, user_prefs_.get(),
+        device_prefs_.get());
+
     bridge_ = std::make_unique<WifiConfigurationBridge>(
         synced_network_updater(), local_network_collector(),
+        /*network_configuration_handler=*/nullptr,
         mock_processor_.CreateForwardingProcessor(),
         syncer::ModelTypeStoreTestUtil::MoveStoreToFactory(std::move(store_)));
+    bridge_->SetNetworkMetadataStore(network_metadata_store_->GetWeakPtr());
   }
 
   void DisableBridge() {
@@ -166,28 +186,22 @@
   }
 
   const NetworkIdentifier& woof_network_id() const { return woof_network_id_; }
-
   const NetworkIdentifier& meow_network_id() const { return meow_network_id_; }
-
   const NetworkIdentifier& honk_network_id() const { return honk_network_id_; }
 
  private:
   base::test::TaskEnvironment task_environment_;
-
   std::unique_ptr<syncer::ModelTypeStore> store_;
-
   testing::NiceMock<syncer::MockModelTypeChangeProcessor> mock_processor_;
-
   std::unique_ptr<WifiConfigurationBridge> bridge_;
-
   std::unique_ptr<TestSyncedNetworkUpdater> synced_network_updater_;
-
   std::unique_ptr<FakeLocalNetworkCollector> local_network_collector_;
+  std::unique_ptr<TestingPrefServiceSimple> device_prefs_;
+  std::unique_ptr<sync_preferences::TestingPrefServiceSyncable> user_prefs_;
+  std::unique_ptr<NetworkMetadataStore> network_metadata_store_;
 
   const NetworkIdentifier woof_network_id_ = GeneratePskNetworkId(kSsidWoof);
-
   const NetworkIdentifier meow_network_id_ = GeneratePskNetworkId(kSsidMeow);
-
   const NetworkIdentifier honk_network_id_ = GeneratePskNetworkId(kSsidHonk);
 
   DISALLOW_COPY_AND_ASSIGN(WifiConfigurationBridgeTest);
@@ -307,8 +321,6 @@
   auto metadata_change_list =
       std::make_unique<syncer::InMemoryMetadataChangeList>();
   syncer::EntityChangeList entity_data;
-  const char kSyncPsk[] = "sync_psk";
-  const char kLocalPsk[] = "local_psk";
 
   WifiConfigurationSpecifics meow_sync =
       GenerateTestWifiSpecifics(meow_network_id(), kSyncPsk, /*timestamp=*/100);
@@ -359,6 +371,67 @@
   EXPECT_TRUE(VectorContainsProto(sync_networks, honk_sync));
 }
 
+TEST_F(WifiConfigurationBridgeTest, LocalFirstConnect) {
+  WifiConfigurationSpecifics meow_local =
+      GenerateTestWifiSpecifics(meow_network_id(), kSyncPsk, /*timestamp=*/100);
+  local_network_collector()->AddNetwork(meow_local);
+
+  std::string storage_key;
+  EXPECT_CALL(*processor(), Put(_, _, _))
+      .WillOnce(testing::SaveArg<0>(&storage_key));
+  bridge()->OnFirstConnectionToNetwork(meow_network_id().SerializeToString());
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(storage_key, meow_network_id().SerializeToString());
+}
+
+TEST_F(WifiConfigurationBridgeTest, LocalUpdate) {
+  WifiConfigurationSpecifics meow_local =
+      GenerateTestWifiSpecifics(meow_network_id(), kSyncPsk, /*timestamp=*/100);
+  local_network_collector()->AddNetwork(meow_local);
+
+  std::string storage_key;
+  EXPECT_CALL(*processor(), Put(_, _, _))
+      .WillOnce(testing::SaveArg<0>(&storage_key));
+  std::string guid = meow_network_id().SerializeToString();
+  base::DictionaryValue set_properties;
+  set_properties.SetBoolean(shill::kAutoConnectProperty, true);
+  bridge()->OnConfigurationModified("service_path", guid, &set_properties);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(storage_key, meow_network_id().SerializeToString());
+}
+
+TEST_F(WifiConfigurationBridgeTest, LocalUpdate_UntrackedField) {
+  WifiConfigurationSpecifics meow_local =
+      GenerateTestWifiSpecifics(meow_network_id(), kSyncPsk, /*timestamp=*/100);
+  local_network_collector()->AddNetwork(meow_local);
+
+  EXPECT_CALL(*processor(), Put(_, _, _)).Times(testing::Exactly(0));
+  std::string guid = meow_network_id().SerializeToString();
+  base::DictionaryValue set_properties;
+  set_properties.SetString(shill::kUIDataProperty, "random_change");
+  bridge()->OnConfigurationModified("service_path", guid, &set_properties);
+  base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(WifiConfigurationBridgeTest, LocalRemove) {
+  WifiConfigurationSpecifics meow_local =
+      GenerateTestWifiSpecifics(meow_network_id(), kSyncPsk, /*timestamp=*/100);
+  local_network_collector()->AddNetwork(meow_local);
+  std::string guid = meow_network_id().SerializeToString();
+
+  bridge()->OnFirstConnectionToNetwork(guid);
+  base::RunLoop().RunUntilIdle();
+
+  bridge()->OnBeforeConfigurationRemoved("service_path", guid);
+
+  std::string storage_key;
+  EXPECT_CALL(*processor(), Delete(_, _))
+      .WillOnce(testing::SaveArg<0>(&storage_key));
+  bridge()->OnConfigurationRemoved("service_path", guid);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(storage_key, meow_network_id().SerializeToString());
+}
+
 }  // namespace
 
 }  // namespace sync_wifi
diff --git a/chromeos/components/sync_wifi/wifi_configuration_sync_service.cc b/chromeos/components/sync_wifi/wifi_configuration_sync_service.cc
index e14083f8..7c20f38 100644
--- a/chromeos/components/sync_wifi/wifi_configuration_sync_service.cc
+++ b/chromeos/components/sync_wifi/wifi_configuration_sync_service.cc
@@ -15,6 +15,7 @@
 #include "chromeos/components/sync_wifi/timer_factory.h"
 #include "chromeos/components/sync_wifi/wifi_configuration_bridge.h"
 #include "chromeos/network/network_handler.h"
+#include "chromeos/network/network_metadata_store.h"
 #include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
 #include "components/sync/base/report_unrecoverable_error.h"
 #include "components/sync/model/model_type_store.h"
@@ -34,10 +35,11 @@
       std::make_unique<PendingNetworkConfigurationTrackerImpl>(pref_service),
       remote_cros_network_config_.get(), std::make_unique<TimerFactory>());
   collector_ = std::make_unique<LocalNetworkCollectorImpl>(
-      remote_cros_network_config_.get(),
-      NetworkHandler::Get()->network_metadata_store());
+      remote_cros_network_config_.get());
+  NetworkHandler* network_handler = NetworkHandler::Get();
   bridge_ = std::make_unique<sync_wifi::WifiConfigurationBridge>(
       updater_.get(), collector_.get(),
+      network_handler->network_configuration_handler(),
       std::make_unique<syncer::ClientTagBasedModelTypeProcessor>(
           syncer::WIFI_CONFIGURATIONS,
           base::BindRepeating(&syncer::ReportUnrecoverableError, channel)),
@@ -51,6 +53,12 @@
   return bridge_->change_processor()->GetControllerDelegate();
 }
 
+void WifiConfigurationSyncService::SetNetworkMetadataStore(
+    base::WeakPtr<NetworkMetadataStore> network_metadata_store) {
+  bridge_->SetNetworkMetadataStore(network_metadata_store);
+  collector_->SetNetworkMetadataStore(network_metadata_store);
+}
+
 }  // namespace sync_wifi
 
 }  // namespace chromeos
diff --git a/chromeos/components/sync_wifi/wifi_configuration_sync_service.h b/chromeos/components/sync_wifi/wifi_configuration_sync_service.h
index 6cfe06b..12fca400 100644
--- a/chromeos/components/sync_wifi/wifi_configuration_sync_service.h
+++ b/chromeos/components/sync_wifi/wifi_configuration_sync_service.h
@@ -23,6 +23,8 @@
 
 namespace chromeos {
 
+class NetworkMetadataStore;
+
 namespace sync_wifi {
 
 class LocalNetworkCollectorImpl;
@@ -40,6 +42,8 @@
   ~WifiConfigurationSyncService() override;
 
   base::WeakPtr<syncer::ModelTypeControllerDelegate> GetControllerDelegate();
+  void SetNetworkMetadataStore(
+      base::WeakPtr<NetworkMetadataStore> network_metadata_store);
 
  private:
   std::unique_ptr<WifiConfigurationBridge> bridge_;
diff --git a/chromeos/network/network_configuration_handler.cc b/chromeos/network/network_configuration_handler.cc
index 9c4a1e3..12c60c6b 100644
--- a/chromeos/network/network_configuration_handler.cc
+++ b/chromeos/network/network_configuration_handler.cc
@@ -422,6 +422,8 @@
   NET_LOG(USER) << "Remove Configuration: " << NetworkPathId(service_path)
                 << " from profiles: "
                 << (!profile_path.empty() ? profile_path : "all");
+  for (auto& observer : observers_)
+    observer.OnBeforeConfigurationRemoved(service_path, guid);
   ProfileEntryDeleter* deleter = new ProfileEntryDeleter(
       this, service_path, guid, callback, error_callback);
   if (!profile_path.empty())
diff --git a/chromeos/network/network_configuration_handler_unittest.cc b/chromeos/network/network_configuration_handler_unittest.cc
index e208b28..4992b70 100644
--- a/chromeos/network/network_configuration_handler_unittest.cc
+++ b/chromeos/network/network_configuration_handler_unittest.cc
@@ -95,6 +95,13 @@
   TestNetworkConfigurationObserver() = default;
 
   // NetworkConfigurationObserver
+  void OnBeforeConfigurationRemoved(const std::string& service_path,
+                                    const std::string& guid) override {
+    ASSERT_EQ(before_remove_configurations_.end(),
+              before_remove_configurations_.find(service_path));
+    before_remove_configurations_[service_path] = guid;
+  }
+
   void OnConfigurationRemoved(const std::string& service_path,
                               const std::string& guid) override {
     ASSERT_EQ(removed_configurations_.end(),
@@ -108,6 +115,11 @@
     updated_configurations_[service_path] = guid;
   }
 
+  bool HasCalledBeforeRemoveConfiguration(const std::string& service_path) {
+    return before_remove_configurations_.find(service_path) !=
+           before_remove_configurations_.end();
+  }
+
   bool HasRemovedConfiguration(const std::string& service_path) {
     return removed_configurations_.find(service_path) !=
            removed_configurations_.end();
@@ -119,6 +131,7 @@
   }
 
  private:
+  std::map<std::string, std::string> before_remove_configurations_;
   std::map<std::string, std::string> removed_configurations_;
   std::map<std::string, std::string> updated_configurations_;
 
@@ -669,6 +682,9 @@
 
   EXPECT_FALSE(
       network_configuration_observer->HasRemovedConfiguration(service_path));
+  EXPECT_FALSE(
+      network_configuration_observer->HasCalledBeforeRemoveConfiguration(
+          service_path));
 
   network_configuration_handler_->RemoveConfiguration(
       service_path, base::DoNothing(), base::Bind(&ErrorCallback));
@@ -676,6 +692,9 @@
 
   EXPECT_TRUE(
       network_configuration_observer->HasRemovedConfiguration(service_path));
+  EXPECT_TRUE(
+      network_configuration_observer->HasCalledBeforeRemoveConfiguration(
+          service_path));
 
   network_configuration_handler_->RemoveObserver(
       network_configuration_observer.get());
diff --git a/chromeos/network/network_configuration_observer.cc b/chromeos/network/network_configuration_observer.cc
index 4142020..f810947 100644
--- a/chromeos/network/network_configuration_observer.cc
+++ b/chromeos/network/network_configuration_observer.cc
@@ -15,6 +15,10 @@
     const std::string& guid,
     base::DictionaryValue* set_properties) {}
 
+void NetworkConfigurationObserver::OnBeforeConfigurationRemoved(
+    const std::string& service_path,
+    const std::string& guid) {}
+
 void NetworkConfigurationObserver::OnConfigurationRemoved(
     const std::string& service_path,
     const std::string& guid) {}
diff --git a/chromeos/network/network_configuration_observer.h b/chromeos/network/network_configuration_observer.h
index 59dd8451..4570268 100644
--- a/chromeos/network/network_configuration_observer.h
+++ b/chromeos/network/network_configuration_observer.h
@@ -24,6 +24,10 @@
                                        const std::string& guid,
                                        base::DictionaryValue* set_properties);
 
+  // Called before a delete is attempted.
+  virtual void OnBeforeConfigurationRemoved(const std::string& service_path,
+                                            const std::string& guid);
+
   // Called whenever a network configuration is removed. |service_path|
   // provides the Shill current identifier for the network. |guid| will be set
   // to the corresponding GUID for the network if known at the time of removal,
diff --git a/chromeos/network/network_metadata_store.h b/chromeos/network/network_metadata_store.h
index be54a1e..0ad50125 100644
--- a/chromeos/network/network_metadata_store.h
+++ b/chromeos/network/network_metadata_store.h
@@ -72,6 +72,10 @@
   void AddObserver(NetworkMetadataObserver* observer);
   void RemoveObserver(NetworkMetadataObserver* observer);
 
+  base::WeakPtr<NetworkMetadataStore> GetWeakPtr() {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
  private:
   void RemoveNetworkFromPref(const std::string& network_guid,
                              PrefService* pref_service);
@@ -88,6 +92,7 @@
   NetworkStateHandler* network_state_handler_;
   PrefService* profile_pref_service_;
   PrefService* device_pref_service_;
+  base::WeakPtrFactory<NetworkMetadataStore> weak_ptr_factory_{this};
 };
 
 }  // namespace chromeos
diff --git a/chromeos/profiles/airmont.afdo.newest.txt b/chromeos/profiles/airmont.afdo.newest.txt
index dd7b68d..571a247b 100644
--- a/chromeos/profiles/airmont.afdo.newest.txt
+++ b/chromeos/profiles/airmont.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-airmont-83-4085.6-1584959535-benchmark-82.0.4085.16-r1-redacted.afdo.xz
\ No newline at end of file
+chromeos-chrome-amd64-airmont-83-4085.6-1584959535-benchmark-83.0.4091.0-r2-redacted.afdo.xz
\ No newline at end of file
diff --git a/components/autofill/core/browser/autofill_test_utils.cc b/components/autofill/core/browser/autofill_test_utils.cc
index 511443c2..f0f8cc5 100644
--- a/components/autofill/core/browser/autofill_test_utils.cc
+++ b/components/autofill/core/browser/autofill_test_utils.cc
@@ -373,33 +373,29 @@
 
 CreditCard GetCreditCard() {
   CreditCard credit_card(base::GenerateGUID(), kEmptyOrigin);
-  // TODO(crbug/1059087): Change hardcoded year to NextYear.
   SetCreditCardInfo(&credit_card, "Test User", "4111111111111111" /* Visa */,
-                    "11", "2022", "1");
+                    NextMonth().c_str(), NextYear().c_str(), "1");
   return credit_card;
 }
 
 CreditCard GetCreditCard2() {
   CreditCard credit_card(base::GenerateGUID(), kEmptyOrigin);
-  // TODO(crbug/1059087): Change hardcoded year to NextYear.
   SetCreditCardInfo(&credit_card, "Someone Else", "378282246310005" /* AmEx */,
-                    "07", "2022", "1");
+                    NextMonth().c_str(), TenYearsFromNow().c_str(), "1");
   return credit_card;
 }
 
 CreditCard GetExpiredCreditCard() {
   CreditCard credit_card(base::GenerateGUID(), kEmptyOrigin);
-  // TODO(crbug/1059087): Change hardcoded year to NextYear.
   SetCreditCardInfo(&credit_card, "Test User", "4111111111111111" /* Visa */,
-                    "11", "2002", "1");
+                    NextMonth().c_str(), LastYear().c_str(), "1");
   return credit_card;
 }
 
 CreditCard GetIncompleteCreditCard() {
   CreditCard credit_card(base::GenerateGUID(), kEmptyOrigin);
-  // TODO(crbug/1059087): Change hardcoded year to NextYear.
-  SetCreditCardInfo(&credit_card, "", "4111111111111111" /* Visa */, "11",
-                    "2022", "1");
+  SetCreditCardInfo(&credit_card, "", "4111111111111111" /* Visa */,
+                    NextMonth().c_str(), NextYear().c_str(), "1");
   return credit_card;
 }
 
@@ -757,22 +753,22 @@
 
 std::string NextMonth() {
   base::Time::Exploded now;
-  base::Time::Now().LocalExplode(&now);
-  return base::NumberToString(now.month % 12 + 1);
+  AutofillClock::Now().LocalExplode(&now);
+  return base::StringPrintf("%02d", now.month % 12 + 1);
 }
 std::string LastYear() {
   base::Time::Exploded now;
-  base::Time::Now().LocalExplode(&now);
+  AutofillClock::Now().LocalExplode(&now);
   return base::NumberToString(now.year - 1);
 }
 std::string NextYear() {
   base::Time::Exploded now;
-  base::Time::Now().LocalExplode(&now);
+  AutofillClock::Now().LocalExplode(&now);
   return base::NumberToString(now.year + 1);
 }
 std::string TenYearsFromNow() {
   base::Time::Exploded now;
-  base::Time::Now().LocalExplode(&now);
+  AutofillClock::Now().LocalExplode(&now);
   return base::NumberToString(now.year + 10);
 }
 
diff --git a/components/autofill/core/browser/autofill_test_utils.h b/components/autofill/core/browser/autofill_test_utils.h
index 870be0f..49dd131 100644
--- a/components/autofill/core/browser/autofill_test_utils.h
+++ b/components/autofill/core/browser/autofill_test_utils.h
@@ -275,6 +275,7 @@
 
 std::string ObfuscatedCardDigitsAsUTF8(const std::string& str);
 
+// Returns 2-digit month string, like "02", "10".
 std::string NextMonth();
 std::string LastYear();
 std::string NextYear();
diff --git a/components/autofill/core/browser/payments/credit_card_save_manager_unittest.cc b/components/autofill/core/browser/payments/credit_card_save_manager_unittest.cc
index f7f53cd..6cbc240 100644
--- a/components/autofill/core/browser/payments/credit_card_save_manager_unittest.cc
+++ b/components/autofill/core/browser/payments/credit_card_save_manager_unittest.cc
@@ -74,8 +74,9 @@
 using UkmCardUploadDecisionType = ukm::builders::Autofill_CardUploadDecision;
 using UkmDeveloperEngagementType = ukm::builders::Autofill_DeveloperEngagement;
 
-const base::Time kArbitraryTime = base::Time::FromDoubleT(25);
-const base::Time kMuchLaterTime = base::Time::FromDoubleT(5000);
+// time_t representation of 9th Sep, 2001 01:46:40 GMT
+const base::Time kArbitraryTime = base::Time::FromTimeT(1000000000);
+const base::Time kMuchLaterTime = base::Time::FromTimeT(1234567890);
 
 // Used to configure form for |CreateTestCreditCardFormData|.
 struct CreditCardFormOptions {
diff --git a/components/background_task_scheduler/internal/android/java/src/org/chromium/components/background_task_scheduler/internal/BackgroundTaskSchedulerUma.java b/components/background_task_scheduler/internal/android/java/src/org/chromium/components/background_task_scheduler/internal/BackgroundTaskSchedulerUma.java
index 0cfdafa3..90d3677d 100644
--- a/components/background_task_scheduler/internal/android/java/src/org/chromium/components/background_task_scheduler/internal/BackgroundTaskSchedulerUma.java
+++ b/components/background_task_scheduler/internal/android/java/src/org/chromium/components/background_task_scheduler/internal/BackgroundTaskSchedulerUma.java
@@ -276,18 +276,26 @@
 
     @VisibleForTesting
     static Set<String> getCachedUmaEntries(SharedPreferences prefs) {
-        return prefs.getStringSet(KEY_CACHED_UMA, new HashSet<String>(1));
+        Set<String> cachedUmaEntries = prefs.getStringSet(KEY_CACHED_UMA, new HashSet<>());
+        return sanitizeEntrySet(cachedUmaEntries);
     }
 
     @VisibleForTesting
     static void updateCachedUma(SharedPreferences prefs, Set<String> cachedUma) {
         ThreadUtils.assertOnUiThread();
         SharedPreferences.Editor editor = prefs.edit();
-        editor.putStringSet(KEY_CACHED_UMA, cachedUma);
+        editor.putStringSet(KEY_CACHED_UMA, sanitizeEntrySet(cachedUma));
         editor.apply();
     }
 
     void assertNativeIsLoaded() {
         assert LibraryLoader.getInstance().isInitialized();
     }
+
+    private static Set<String> sanitizeEntrySet(Set<String> set) {
+        if (set != null && set.contains(null)) {
+            set.remove(null);
+        }
+        return set;
+    }
 }
diff --git a/components/background_task_scheduler/internal/android/junit/src/org/chromium/components/background_task_scheduler/internal/BackgroundTaskSchedulerUmaTest.java b/components/background_task_scheduler/internal/android/junit/src/org/chromium/components/background_task_scheduler/internal/BackgroundTaskSchedulerUmaTest.java
index a9f60ae5..b0fd24c 100644
--- a/components/background_task_scheduler/internal/android/junit/src/org/chromium/components/background_task_scheduler/internal/BackgroundTaskSchedulerUmaTest.java
+++ b/components/background_task_scheduler/internal/android/junit/src/org/chromium/components/background_task_scheduler/internal/BackgroundTaskSchedulerUmaTest.java
@@ -5,6 +5,7 @@
 package org.chromium.components.background_task_scheduler.internal;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
@@ -14,6 +15,8 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.content.SharedPreferences;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -28,6 +31,7 @@
 import org.chromium.components.background_task_scheduler.BackgroundTaskSchedulerExternalUma;
 import org.chromium.components.background_task_scheduler.TaskIds;
 
+import java.util.HashSet;
 import java.util.Set;
 
 /** Unit tests for {@link BackgroundTaskSchedulerUma}. */
@@ -150,6 +154,28 @@
 
     @Test
     @Feature({"BackgroundTaskScheduler"})
+    public void testCacheEvent_StringStartWithNpe() {
+        // Set up preferences with a null entry.
+        SharedPreferences.Editor editor = ContextUtils.getAppSharedPreferences().edit();
+        HashSet<String> setWithNullValue = new HashSet<>();
+        setWithNullValue.add(null);
+        editor.putStringSet(BackgroundTaskSchedulerUma.KEY_CACHED_UMA, setWithNullValue);
+        editor.apply();
+
+        Set<String> cachedUmaEntries = BackgroundTaskSchedulerUma.getCachedUmaEntries(
+                ContextUtils.getAppSharedPreferences());
+        assertTrue(cachedUmaEntries.isEmpty());
+
+        mUmaSpy.cacheEvent("NpeTestEvent", 77);
+
+        cachedUmaEntries = BackgroundTaskSchedulerUma.getCachedUmaEntries(
+                ContextUtils.getAppSharedPreferences());
+        assertTrue(cachedUmaEntries.contains("NpeTestEvent:77:1"));
+        assertFalse(cachedUmaEntries.contains(null));
+    }
+
+    @Test
+    @Feature({"BackgroundTaskScheduler"})
     public void testFlushStats() {
         doNothing().when(mUmaSpy).recordEnumeratedHistogram(anyString(), anyInt(), anyInt());
 
diff --git a/components/feed/core/v2/BUILD.gn b/components/feed/core/v2/BUILD.gn
index c6b96dc2..58e363b 100644
--- a/components/feed/core/v2/BUILD.gn
+++ b/components/feed/core/v2/BUILD.gn
@@ -68,6 +68,7 @@
   testonly = true
   sources = [
     "feed_network_impl_unittest.cc",
+    "feed_store_unittest.cc",
     "feed_stream_unittest.cc",
     "stream_model_unittest.cc",
     "stream_model_update_request_unittest.cc",
@@ -82,6 +83,7 @@
     "//base/test:test_support",
     "//components/feed/core:feed_core",
     "//components/feed/core/common:feed_core_common",
+    "//components/leveldb_proto:test_support",
     "//components/prefs:test_support",
     "//components/signin/public/identity_manager",
     "//components/signin/public/identity_manager:test_support",
diff --git a/components/feed/core/v2/feed_store.cc b/components/feed/core/v2/feed_store.cc
index f9d193d7..c7ef851 100644
--- a/components/feed/core/v2/feed_store.cc
+++ b/components/feed/core/v2/feed_store.cc
@@ -75,12 +75,22 @@
           leveldb_proto::ProtoDbType::FEED_STREAM_DATABASE,
           feed_directory,
           task_runner_)) {
-  database_->Init(base::BindOnce(&FeedStore::OnDatabaseInitialized,
-                                 weak_ptr_factory_.GetWeakPtr()));
+  Initialize();
+}
+
+FeedStore::FeedStore(
+    std::unique_ptr<leveldb_proto::ProtoDatabase<feedstore::Record>> database)
+    : database_(std::move(database)) {
+  Initialize();
 }
 
 FeedStore::~FeedStore() = default;
 
+void FeedStore::Initialize() {
+  database_->Init(base::BindOnce(&FeedStore::OnDatabaseInitialized,
+                                 weak_ptr_factory_.GetWeakPtr()));
+}
+
 void FeedStore::OnDatabaseInitialized(leveldb_proto::Enums::InitStatus status) {
   database_status_ = status;
 }
@@ -89,6 +99,10 @@
   return database_status_ == leveldb_proto::Enums::InitStatus::kOK;
 }
 
+bool FeedStore::IsInitializedForTesting() const {
+  return IsInitialized();
+}
+
 void FeedStore::ReadSingle(
     const std::string& key,
     base::OnceCallback<void(bool, std::unique_ptr<feedstore::Record>)>
@@ -129,7 +143,7 @@
     base::OnceCallback<void(std::unique_ptr<feedstore::StreamData>)> callback,
     bool success,
     std::unique_ptr<feedstore::Record> record) {
-  if (!success) {
+  if (!success || !record) {
     std::move(callback).Run(nullptr);
     return;
   }
@@ -138,16 +152,19 @@
 }
 
 void FeedStore::ReadContent(
-    std::vector<feedwire::ContentId> ids,
-    base::OnceCallback<
-        void(std::unique_ptr<std::vector<feedstore::Content>>,
-             std::unique_ptr<std::vector<feedstore::StreamSharedState>>)>
+    std::vector<feedwire::ContentId> content_ids,
+    std::vector<feedwire::ContentId> shared_state_ids,
+    base::OnceCallback<void(std::vector<feedstore::Content>,
+                            std::vector<feedstore::StreamSharedState>)>
         content_callback) {
   std::vector<std::string> key_vector;
-  key_vector.reserve(ids.size());
-  for (auto& content_id : ids)
+  key_vector.reserve(content_ids.size() + shared_state_ids.size());
+  for (auto& content_id : content_ids)
     key_vector.push_back(ContentKey(content_id));
 
+  for (auto& shared_state_id : shared_state_ids)
+    key_vector.push_back(SharedStateKey(shared_state_id));
+
   ReadMany(base::flat_set<std::string>(std::move(key_vector)),
            base::BindOnce(&FeedStore::OnReadContentFinished,
                           weak_ptr_factory_.GetWeakPtr(),
@@ -155,26 +172,25 @@
 }
 
 void FeedStore::OnReadContentFinished(
-    base::OnceCallback<void(
-        std::unique_ptr<std::vector<feedstore::Content>>,
-        std::unique_ptr<std::vector<feedstore::StreamSharedState>>)> callback,
+    base::OnceCallback<void(std::vector<feedstore::Content>,
+                            std::vector<feedstore::StreamSharedState>)>
+        callback,
     bool success,
     std::unique_ptr<std::vector<feedstore::Record>> records) {
-  if (!success) {
-    std::move(callback).Run(nullptr, nullptr);
+  if (!success || !records) {
+    std::move(callback).Run({}, {});
     return;
   }
 
-  auto content = std::make_unique<std::vector<feedstore::Content>>();
+  std::vector<feedstore::Content> content;
   // Most of records will be content.
-  content->reserve(records->size());
-  auto shared_states =
-      std::make_unique<std::vector<feedstore::StreamSharedState>>();
+  content.reserve(records->size());
+  std::vector<feedstore::StreamSharedState> shared_states;
   for (auto& record : *records) {
     if (record.data_case() == feedstore::Record::kContent)
-      content->push_back(std::move(record.content()));
+      content.push_back(std::move(record.content()));
     else if (record.data_case() == feedstore::Record::kSharedState)
-      shared_states->push_back(std::move(record.shared_state()));
+      shared_states.push_back(std::move(record.shared_state()));
   }
 
   std::move(callback).Run(std::move(content), std::move(shared_states));
@@ -194,7 +210,7 @@
         callback,
     bool success,
     std::unique_ptr<feedstore::Record> record) {
-  if (!success) {
+  if (!success || !record) {
     std::move(callback).Run(nullptr);
     return;
   }
diff --git a/components/feed/core/v2/feed_store.h b/components/feed/core/v2/feed_store.h
index 551ff21a..dfdc7506 100644
--- a/components/feed/core/v2/feed_store.h
+++ b/components/feed/core/v2/feed_store.h
@@ -20,8 +20,15 @@
  public:
   FeedStore(leveldb_proto::ProtoDatabaseProvider* database_provider,
             const base::FilePath& feed_directory);
+  FeedStore(const FeedStore&) = delete;
+  FeedStore& operator=(const FeedStore&) = delete;
   ~FeedStore();
 
+  // For testing.
+  explicit FeedStore(
+      std::unique_ptr<leveldb_proto::ProtoDatabase<feedstore::Record>>
+          database);
+
   // Read StreamData and pass it to stream_data_callback, or nullptr on failure.
   void ReadStreamData(
       base::OnceCallback<void(std::unique_ptr<feedstore::StreamData>)>
@@ -29,10 +36,10 @@
   // Read Content and StreamSharedStates and pass them to content_callback, or
   // nullptrs on failure.
   void ReadContent(
-      std::vector<feedwire::ContentId> ids,
-      base::OnceCallback<
-          void(std::unique_ptr<std::vector<feedstore::Content>>,
-               std::unique_ptr<std::vector<feedstore::StreamSharedState>>)>
+      std::vector<feedwire::ContentId> content_ids,
+      std::vector<feedwire::ContentId> shared_state_ids,
+      base::OnceCallback<void(std::vector<feedstore::Content>,
+                              std::vector<feedstore::StreamSharedState>)>
           content_callback);
 
   void ReadNextStreamState(
@@ -47,7 +54,11 @@
   // TODO(iwells): implement this
   // Deletes old records that are no longer needed
   // void RemoveOldData(base::OnceCallback<void(bool)> callback);
+
+  bool IsInitializedForTesting() const;
+
  private:
+  void Initialize();
   void OnDatabaseInitialized(leveldb_proto::Enums::InitStatus status);
   bool IsInitialized() const;
 
@@ -65,9 +76,9 @@
       bool success,
       std::unique_ptr<feedstore::Record> record);
   void OnReadContentFinished(
-      base::OnceCallback<void(
-          std::unique_ptr<std::vector<feedstore::Content>>,
-          std::unique_ptr<std::vector<feedstore::StreamSharedState>>)> callback,
+      base::OnceCallback<void(std::vector<feedstore::Content>,
+                              std::vector<feedstore::StreamSharedState>)>
+          callback,
       bool success,
       std::unique_ptr<std::vector<feedstore::Record>> records);
   void OnReadNextStreamStateFinished(
diff --git a/components/feed/core/v2/feed_store_unittest.cc b/components/feed/core/v2/feed_store_unittest.cc
new file mode 100644
index 0000000..59396072
--- /dev/null
+++ b/components/feed/core/v2/feed_store_unittest.cc
@@ -0,0 +1,317 @@
+// Copyright 2020 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 "components/feed/core/v2/feed_store.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/test/bind_test_util.h"
+#include "components/feed/core/proto/v2/wire/content_id.pb.h"
+#include "components/feed/core/v2/test/stream_builder.h"
+#include "components/leveldb_proto/testing/fake_db.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace feed {
+namespace {
+
+const char kNextPageToken[] = "next page token";
+const char kConsistencyToken[] = "consistency token";
+const int64_t kLastAddedTimeMs = 100;
+
+feedstore::StreamData MakeStreamData() {
+  feedstore::StreamData stream_data;
+  *stream_data.mutable_content_id() = MakeRootId();
+  stream_data.set_next_page_token(kNextPageToken);
+  stream_data.set_consistency_token(kConsistencyToken);
+  stream_data.set_last_added_time_millis(kLastAddedTimeMs);
+
+  std::vector<feedstore::DataOperation> operations =
+      MakeTypicalStreamOperations();
+  for (auto& operation : operations) {
+    *stream_data.add_structures() = std::move(operation.structure());
+  }
+
+  return stream_data;
+}
+
+std::string ContentIdToString(const feedwire::ContentId& content_id) {
+  return base::StrCat(
+      {"{content_domain: \"", content_id.content_domain(),
+       "\", id: ", base::NumberToString(content_id.id()), ", type: \"",
+       feedwire::ContentId::Type_Name(content_id.type()), "\"}"});
+}
+
+std::string KeyForContentId(base::StringPiece prefix,
+                            const feedwire::ContentId& content_id) {
+  return base::StrCat({prefix, content_id.content_domain(), ",",
+                       base::NumberToString(content_id.type()), ",",
+                       base::NumberToString(content_id.id())});
+}
+
+feedstore::Record RecordForContent(feedstore::Content content) {
+  feedstore::Record record;
+  *record.mutable_content() = std::move(content);
+  return record;
+}
+
+feedstore::Record RecordForSharedState(feedstore::StreamSharedState shared) {
+  feedstore::Record record;
+  *record.mutable_shared_state() = std::move(shared);
+  return record;
+}
+
+const char kRootKey[] = "S/0";
+
+}  // namespace
+
+class FeedStoreTest : public testing::Test {
+ public:
+  void MakeFeedStore(std::map<std::string, feedstore::Record> entries,
+                     leveldb_proto::Enums::InitStatus init_status =
+                         leveldb_proto::Enums::InitStatus::kOK) {
+    db_entries_ = std::move(entries);
+    auto fake_db =
+        std::make_unique<leveldb_proto::test::FakeDB<feedstore::Record>>(
+            &db_entries_);
+    fake_db_ = fake_db.get();
+    store_ = std::make_unique<FeedStore>(std::move(fake_db));
+    fake_db_->InitStatusCallback(init_status);
+  }
+
+  std::unique_ptr<FeedStore> store_;
+  std::map<std::string, feedstore::Record> db_entries_;
+  leveldb_proto::test::FakeDB<feedstore::Record>* fake_db_;
+};
+
+TEST_F(FeedStoreTest, InitSuccess) {
+  MakeFeedStore({});
+  EXPECT_TRUE(store_->IsInitializedForTesting());
+}
+
+TEST_F(FeedStoreTest, InitFailure) {
+  std::map<std::string, feedstore::Record> entries;
+  auto fake_db =
+      std::make_unique<leveldb_proto::test::FakeDB<feedstore::Record>>(
+          &entries);
+  leveldb_proto::test::FakeDB<feedstore::Record>* fake_db_raw = fake_db.get();
+
+  auto store = std::make_unique<FeedStore>(std::move(fake_db));
+  EXPECT_FALSE(store->IsInitializedForTesting());
+
+  fake_db_raw->InitStatusCallback(leveldb_proto::Enums::InitStatus::kError);
+  EXPECT_FALSE(store->IsInitializedForTesting());
+}
+
+TEST_F(FeedStoreTest, ReadStreamData) {
+  feedstore::Record record;
+  *record.mutable_stream_data() = MakeStreamData();
+  MakeFeedStore({{kRootKey, record}});
+
+  // Successful read
+  bool did_successful_read = false;
+  store_->ReadStreamData(base::BindLambdaForTesting(
+      [&](std::unique_ptr<feedstore::StreamData> stream_data) {
+        did_successful_read = true;
+        ASSERT_TRUE(stream_data);
+        EXPECT_EQ(ContentIdToString(stream_data->content_id()),
+                  ContentIdToString(record.stream_data().content_id()));
+        EXPECT_EQ(stream_data->structures_size(),
+                  record.stream_data().structures_size());
+        EXPECT_EQ(stream_data->next_page_token(),
+                  record.stream_data().next_page_token());
+        EXPECT_EQ(stream_data->consistency_token(),
+                  record.stream_data().consistency_token());
+        EXPECT_EQ(stream_data->last_added_time_millis(),
+                  record.stream_data().last_added_time_millis());
+        EXPECT_EQ(stream_data->next_action_id(),
+                  record.stream_data().next_action_id());
+      }));
+  fake_db_->GetCallback(true);
+  EXPECT_TRUE(did_successful_read);
+
+  // Failed read
+  bool did_failed_read = false;
+  store_->ReadStreamData(base::BindLambdaForTesting(
+      [&](std::unique_ptr<feedstore::StreamData> stream_data) {
+        did_failed_read = true;
+        EXPECT_FALSE(stream_data);
+      }));
+  fake_db_->GetCallback(false);
+  EXPECT_TRUE(did_failed_read);
+}
+
+TEST_F(FeedStoreTest, ReadNonexistentStreamData) {
+  MakeFeedStore({});
+
+  bool did_read = false;
+  store_->ReadStreamData(base::BindLambdaForTesting(
+      [&](std::unique_ptr<feedstore::StreamData> stream_data) {
+        did_read = true;
+        EXPECT_FALSE(stream_data);
+      }));
+  fake_db_->GetCallback(true);
+  EXPECT_TRUE(did_read);
+}
+
+TEST_F(FeedStoreTest, ReadNonexistentContentAndSharedStates) {
+  MakeFeedStore({});
+
+  bool did_read = false;
+  store_->ReadContent(
+      {MakeContentContentId(0)}, {MakeSharedStateContentId(0)},
+      base::BindLambdaForTesting(
+          [&](std::vector<feedstore::Content> content,
+              std::vector<feedstore::StreamSharedState> shared_states) {
+            did_read = true;
+            EXPECT_EQ(content.size(), 0ul);
+            EXPECT_EQ(shared_states.size(), 0ul);
+          }));
+  fake_db_->LoadCallback(true);
+  EXPECT_TRUE(did_read);
+}
+
+TEST_F(FeedStoreTest, ReadContentAndSharedStates) {
+  feedstore::Content content1 = MakeContent(1);
+  feedstore::Content content2 = MakeContent(2);
+  feedstore::StreamSharedState shared1 = MakeSharedState(1);
+  feedstore::StreamSharedState shared2 = MakeSharedState(2);
+
+  MakeFeedStore({{KeyForContentId("c/", content1.content_id()),
+                  RecordForContent(content1)},
+                 {KeyForContentId("c/", content2.content_id()),
+                  RecordForContent(content2)},
+                 {KeyForContentId("s/", shared1.content_id()),
+                  RecordForSharedState(shared1)},
+                 {KeyForContentId("s/", shared2.content_id()),
+                  RecordForSharedState(shared2)}});
+
+  std::vector<feedwire::ContentId> content_ids = {content1.content_id(),
+                                                  content2.content_id()};
+  std::vector<feedwire::ContentId> shared_state_ids = {shared1.content_id(),
+                                                       shared2.content_id()};
+
+  // Successful read
+  bool did_successful_read = false;
+  store_->ReadContent(
+      content_ids, shared_state_ids,
+      base::BindLambdaForTesting(
+          [&](std::vector<feedstore::Content> content,
+              std::vector<feedstore::StreamSharedState> shared_states) {
+            did_successful_read = true;
+            ASSERT_EQ(content.size(), 2ul);
+            EXPECT_EQ(ContentIdToString(content[0].content_id()),
+                      ContentIdToString(content1.content_id()));
+            EXPECT_EQ(content[0].frame(), content1.frame());
+
+            ASSERT_EQ(shared_states.size(), 2ul);
+            EXPECT_EQ(ContentIdToString(shared_states[0].content_id()),
+                      ContentIdToString(shared1.content_id()));
+            EXPECT_EQ(shared_states[0].shared_state_data(),
+                      shared1.shared_state_data());
+          }));
+  fake_db_->LoadCallback(true);
+  EXPECT_TRUE(did_successful_read);
+
+  // Failed read
+  bool did_failed_read = false;
+  store_->ReadContent(
+      content_ids, shared_state_ids,
+      base::BindLambdaForTesting(
+          [&](std::vector<feedstore::Content> content,
+              std::vector<feedstore::StreamSharedState> shared_states) {
+            did_failed_read = true;
+            EXPECT_EQ(content.size(), 0ul);
+            EXPECT_EQ(shared_states.size(), 0ul);
+          }));
+  fake_db_->LoadCallback(false);
+  EXPECT_TRUE(did_failed_read);
+}
+
+TEST_F(FeedStoreTest, ReadNextStreamState) {
+  feedstore::Record record;
+  feedstore::StreamAndContentState* next_stream_state =
+      record.mutable_next_stream_state();
+  *next_stream_state->mutable_stream_data() = MakeStreamData();
+  *next_stream_state->add_content() = MakeContent(0);
+  *next_stream_state->add_shared_state() = MakeSharedState(0);
+
+  MakeFeedStore({{"N", record}});
+
+  // Successful read
+  bool did_successful_read = false;
+  store_->ReadNextStreamState(base::BindLambdaForTesting(
+      [&](std::unique_ptr<feedstore::StreamAndContentState> result) {
+        did_successful_read = true;
+        ASSERT_TRUE(result);
+        EXPECT_TRUE(result->has_stream_data());
+        EXPECT_EQ(result->content_size(), 1);
+        EXPECT_EQ(result->shared_state_size(), 1);
+      }));
+  fake_db_->GetCallback(true);
+  EXPECT_TRUE(did_successful_read);
+
+  // Failed read
+  bool did_failed_read = false;
+  store_->ReadNextStreamState(base::BindLambdaForTesting(
+      [&](std::unique_ptr<feedstore::StreamAndContentState> result) {
+        did_failed_read = true;
+        EXPECT_FALSE(result);
+      }));
+  fake_db_->GetCallback(false);
+  EXPECT_TRUE(did_failed_read);
+}
+
+TEST_F(FeedStoreTest, WriteThenRead) {
+  MakeFeedStore({});
+
+  std::vector<feedstore::Record> records(4);
+  *records[0].mutable_stream_data() = MakeStreamData();
+  *records[1].mutable_content() = MakeContent(0);
+  *records[2].mutable_shared_state() = MakeSharedState(0);
+  *records[3].mutable_next_stream_state()->mutable_stream_data() =
+      MakeStreamData();
+
+  store_->Write(records, base::BindLambdaForTesting([](bool success) {}));
+  fake_db_->UpdateCallback(true);
+
+  bool did_read_stream_data = false;
+  store_->ReadStreamData(base::BindLambdaForTesting(
+      [&](std::unique_ptr<feedstore::StreamData> stream_data) {
+        did_read_stream_data = true;
+        ASSERT_TRUE(stream_data);
+        // Just make sure stream_data isn't a default empty StreamData.
+        EXPECT_TRUE(stream_data->has_content_id());
+      }));
+  fake_db_->GetCallback(true);
+  EXPECT_TRUE(did_read_stream_data);
+
+  bool did_read_content = false;
+  store_->ReadContent(
+      {records[1].content().content_id()},
+      {records[2].shared_state().content_id()},
+      base::BindLambdaForTesting(
+          [&](std::vector<feedstore::Content> content,
+              std::vector<feedstore::StreamSharedState> shared_states) {
+            did_read_content = true;
+            ASSERT_EQ(content.size(), 1ul);
+            EXPECT_TRUE(content[0].has_content_id());
+
+            ASSERT_EQ(shared_states.size(), 1ul);
+            EXPECT_TRUE(shared_states[0].has_content_id());
+          }));
+  fake_db_->LoadCallback(true);
+  EXPECT_TRUE(did_read_content);
+
+  bool did_read_next_stream_state = false;
+  store_->ReadNextStreamState(base::BindLambdaForTesting(
+      [&](std::unique_ptr<feedstore::StreamAndContentState> result) {
+        did_read_next_stream_state = true;
+        ASSERT_TRUE(result);
+        EXPECT_TRUE(result->has_stream_data());
+      }));
+  fake_db_->GetCallback(true);
+  EXPECT_TRUE(did_read_next_stream_state);
+}
+
+}  // namespace feed
diff --git a/components/feed/core/v2/test/stream_builder.cc b/components/feed/core/v2/test/stream_builder.cc
index d43c57c..c7721ad 100644
--- a/components/feed/core/v2/test/stream_builder.cc
+++ b/components/feed/core/v2/test/stream_builder.cc
@@ -29,6 +29,10 @@
   return MakeContentId(ContentId::FEATURE, "stories", id_number);
 }
 
+ContentId MakeSharedStateContentId(int id_number) {
+  return MakeContentId(ContentId::TYPE_UNDEFINED, "shared", id_number);
+}
+
 ContentId MakeRootId(int id_number) {
   return MakeContentId(ContentId::TYPE_UNDEFINED, "root", id_number);
 }
diff --git a/components/feed/core/v2/test/stream_builder.h b/components/feed/core/v2/test/stream_builder.h
index e0f73d3d..446baee 100644
--- a/components/feed/core/v2/test/stream_builder.h
+++ b/components/feed/core/v2/test/stream_builder.h
@@ -21,6 +21,7 @@
                         int id_number);
 ContentId MakeClusterId(int id_number);
 ContentId MakeContentContentId(int id_number);
+ContentId MakeSharedStateContentId(int id_number);
 ContentId MakeRootId(int id_number = 0);
 ContentId MakeSharedStateId(int id_number = 0);
 feedstore::StreamStructure MakeStream(int id_number = 0);
@@ -30,6 +31,7 @@
 feedstore::StreamStructure MakeRemove(ContentId id);
 feedstore::StreamStructure MakeClearAll();
 feedstore::Content MakeContent(int id_number);
+feedstore::StreamSharedState MakeSharedState(int id_number);
 feedstore::DataOperation MakeOperation(feedstore::StreamStructure structure);
 feedstore::DataOperation MakeOperation(feedstore::Content content);
 
diff --git a/components/omnibox/browser/autocomplete_result_unittest.cc b/components/omnibox/browser/autocomplete_result_unittest.cc
index 54aa4600..f8b25d2 100644
--- a/components/omnibox/browser/autocomplete_result_unittest.cc
+++ b/components/omnibox/browser/autocomplete_result_unittest.cc
@@ -73,13 +73,13 @@
 }
 
 // A simple AutocompleteProvider that does nothing.
-class MockAutocompleteProvider : public AutocompleteProvider {
+class FakeAutocompleteProvider : public AutocompleteProvider {
  public:
-  explicit MockAutocompleteProvider(Type type): AutocompleteProvider(type) {}
+  explicit FakeAutocompleteProvider(Type type) : AutocompleteProvider(type) {}
 
   void Start(const AutocompleteInput& input, bool minimal_changes) override {}
 
-  // For simplicity, |MockAutocompleteProvider|'s retrieved through
+  // For simplicity, |FakeAutocompleteProvider|'s retrieved through
   // |GetProvider| have types 0, 1, ... 5. This is fine for most tests, but for
   // tests where the provider type matters (e.g. tests that involve deduping
   // document suggestions), provider types need to be consistent with
@@ -87,7 +87,7 @@
   void SetType(Type type) { type_ = type; }
 
  private:
-  ~MockAutocompleteProvider() override {}
+  ~FakeAutocompleteProvider() override = default;
 };
 
 }  // namespace
@@ -117,8 +117,8 @@
 
     // Create the list of mock providers.  5 is enough.
     for (size_t i = 0; i < 5; ++i) {
-      mock_provider_list_.push_back(new MockAutocompleteProvider(
-              static_cast<AutocompleteProvider::Type>(i)));
+      mock_provider_list_.push_back(new FakeAutocompleteProvider(
+          static_cast<AutocompleteProvider::Type>(i)));
     }
   }
 
@@ -152,7 +152,7 @@
                                  const TestData* expected,
                                  size_t expected_size);
 
-  void SortMatchesAndVerfiyOrder(
+  void SortMatchesAndVerifyOrder(
       const std::string& input_text,
       OmniboxEventProto::PageClassification page_classification,
       const ACMatches& matches,
@@ -160,7 +160,7 @@
       const AutocompleteMatchTestData data[]);
 
   // Returns a (mock) AutocompleteProvider of given |provider_id|.
-  MockAutocompleteProvider* GetProvider(int provider_id) {
+  FakeAutocompleteProvider* GetProvider(int provider_id) {
     EXPECT_LT(provider_id, static_cast<int>(mock_provider_list_.size()));
     return mock_provider_list_[provider_id].get();
   }
@@ -172,7 +172,7 @@
   base::test::TaskEnvironment task_environment_;
 
   // For every provider mentioned in TestData, we need a mock provider.
-  std::vector<scoped_refptr<MockAutocompleteProvider> > mock_provider_list_;
+  std::vector<scoped_refptr<FakeAutocompleteProvider>> mock_provider_list_;
 
   DISALLOW_COPY_AND_ASSIGN(AutocompleteResultTest);
 };
@@ -245,7 +245,7 @@
   AssertResultMatches(current_result, expected, expected_size);
 }
 
-void AutocompleteResultTest::SortMatchesAndVerfiyOrder(
+void AutocompleteResultTest::SortMatchesAndVerifyOrder(
     const std::string& input_text,
     OmniboxEventProto::PageClassification page_classification,
     const ACMatches& matches,
@@ -1083,7 +1083,7 @@
   // is the default match despite demotion.
   // Make sure history-URL is the last match due to the logic which groups
   // searches and URLs together.
-  SortMatchesAndVerfiyOrder("a", OmniboxEventProto::HOME_PAGE, matches,
+  SortMatchesAndVerifyOrder("a", OmniboxEventProto::HOME_PAGE, matches,
                             {1, 2, 3, 0}, data);
 
   // However, in the fakebox/realbox, we do want to use the demoted score when
@@ -1092,10 +1092,10 @@
   // page classification of fakebox/realbox, and make sure history-title is now
   // demoted. We also make sure history-URL is the last match due to the logic
   // which groups searches and URLs together.
-  SortMatchesAndVerfiyOrder(
+  SortMatchesAndVerifyOrder(
       "a", OmniboxEventProto::INSTANT_NTP_WITH_FAKEBOX_AS_STARTING_FOCUS,
       matches, {3, 2, 0, 1}, data);
-  SortMatchesAndVerfiyOrder("a", OmniboxEventProto::NTP_REALBOX, matches,
+  SortMatchesAndVerifyOrder("a", OmniboxEventProto::NTP_REALBOX, matches,
                             {3, 2, 0, 1}, data);
 
   // Unless, the user's input looks like a URL, in which case we want to use
@@ -1103,11 +1103,11 @@
   // clearly trying to navigate. So here we re-sort with a page classification
   // of fakebox/realbox and an input that's a URL, and make sure history-title
   // is once again the default match.
-  SortMatchesAndVerfiyOrder(
+  SortMatchesAndVerifyOrder(
       "www.example.com",
       OmniboxEventProto::INSTANT_NTP_WITH_FAKEBOX_AS_STARTING_FOCUS, matches,
       {1, 2, 3, 0}, data);
-  SortMatchesAndVerfiyOrder("www.example.com", OmniboxEventProto::NTP_REALBOX,
+  SortMatchesAndVerifyOrder("www.example.com", OmniboxEventProto::NTP_REALBOX,
                             matches, {1, 2, 3, 0}, data);
 }
 
@@ -1221,7 +1221,7 @@
 
 struct EntityTestData {
   AutocompleteMatchType::Type type;
-  MockAutocompleteProvider* provider;
+  FakeAutocompleteProvider* provider;
   std::string destination_url;
   int relevance;
   bool allowed_to_be_default_match;
@@ -1701,15 +1701,15 @@
   ACMatches matches;
   PopulateAutocompleteMatches(data, base::size(data), &matches);
   matches[0].type = AutocompleteMatchType::DOCUMENT_SUGGESTION;
-  static_cast<MockAutocompleteProvider*>(matches[0].provider)
+  static_cast<FakeAutocompleteProvider*>(matches[0].provider)
       ->SetType(AutocompleteProvider::Type::TYPE_DOCUMENT);
   matches[1].type = AutocompleteMatchType::HISTORY_URL;
   matches[2].type = AutocompleteMatchType::DOCUMENT_SUGGESTION;
-  static_cast<MockAutocompleteProvider*>(matches[2].provider)
+  static_cast<FakeAutocompleteProvider*>(matches[2].provider)
       ->SetType(AutocompleteProvider::Type::TYPE_DOCUMENT);
   matches[3].type = AutocompleteMatchType::HISTORY_URL;
   matches[4].type = AutocompleteMatchType::DOCUMENT_SUGGESTION;
-  static_cast<MockAutocompleteProvider*>(matches[4].provider)
+  static_cast<FakeAutocompleteProvider*>(matches[4].provider)
       ->SetType(AutocompleteProvider::Type::TYPE_DOCUMENT);
   matches[5].type = AutocompleteMatchType::HISTORY_URL;
 
diff --git a/components/omnibox/browser/titled_url_match_utils_unittest.cc b/components/omnibox/browser/titled_url_match_utils_unittest.cc
index 81feb9d1..6bfbd52 100644
--- a/components/omnibox/browser/titled_url_match_utils_unittest.cc
+++ b/components/omnibox/browser/titled_url_match_utils_unittest.cc
@@ -25,14 +25,14 @@
 namespace {
 
 // A simple AutocompleteProvider that does nothing.
-class MockAutocompleteProvider : public AutocompleteProvider {
+class FakeAutocompleteProvider : public AutocompleteProvider {
  public:
-  explicit MockAutocompleteProvider(Type type) : AutocompleteProvider(type) {}
+  explicit FakeAutocompleteProvider(Type type) : AutocompleteProvider(type) {}
 
   void Start(const AutocompleteInput& input, bool minimal_changes) override {}
 
  private:
-  ~MockAutocompleteProvider() override {}
+  ~FakeAutocompleteProvider() override = default;
 };
 
 class MockTitledUrlNode : public bookmarks::TitledUrlNode {
@@ -78,8 +78,8 @@
   titled_url_match.title_match_positions = {{0, 3}};
   titled_url_match.url_match_positions = {{12, 15}};
 
-  scoped_refptr<MockAutocompleteProvider> provider =
-      new MockAutocompleteProvider(AutocompleteProvider::Type::TYPE_BOOKMARK);
+  scoped_refptr<FakeAutocompleteProvider> provider =
+      new FakeAutocompleteProvider(AutocompleteProvider::Type::TYPE_BOOKMARK);
   TestSchemeClassifier classifier;
   AutocompleteInput input(input_text, metrics::OmniboxEventProto::NTP,
                           classifier);
@@ -136,8 +136,8 @@
   // Don't capture the scheme, so that it doesn't match.
   titled_url_match.url_match_positions = match_positions;
 
-  scoped_refptr<MockAutocompleteProvider> provider =
-      new MockAutocompleteProvider(AutocompleteProvider::Type::TYPE_BOOKMARK);
+  scoped_refptr<FakeAutocompleteProvider> provider =
+      new FakeAutocompleteProvider(AutocompleteProvider::Type::TYPE_BOOKMARK);
   TestSchemeClassifier classifier;
   AutocompleteInput input(input_text, metrics::OmniboxEventProto::NTP,
                           classifier);
@@ -251,8 +251,8 @@
   titled_url_match.title_match_positions = {{9, 12}};
   titled_url_match.url_match_positions = {};
 
-  scoped_refptr<MockAutocompleteProvider> provider =
-      new MockAutocompleteProvider(AutocompleteProvider::Type::TYPE_BOOKMARK);
+  scoped_refptr<FakeAutocompleteProvider> provider =
+      new FakeAutocompleteProvider(AutocompleteProvider::Type::TYPE_BOOKMARK);
   TestSchemeClassifier classifier;
   AutocompleteInput input(input_text, metrics::OmniboxEventProto::NTP,
                           classifier);
diff --git a/components/page_load_metrics/browser/metrics_web_contents_observer.cc b/components/page_load_metrics/browser/metrics_web_contents_observer.cc
index 0fccdcc3..304f273 100644
--- a/components/page_load_metrics/browser/metrics_web_contents_observer.cc
+++ b/components/page_load_metrics/browser/metrics_web_contents_observer.cc
@@ -690,7 +690,8 @@
     const std::vector<mojom::ResourceDataUpdatePtr>& resources,
     mojom::FrameRenderDataUpdatePtr render_data,
     mojom::CpuTimingPtr cpu_timing,
-    mojom::DeferredResourceCountsPtr new_deferred_resource_data) {
+    mojom::DeferredResourceCountsPtr new_deferred_resource_data,
+    mojom::InputTimingPtr input_timing_delta) {
   // We may receive notifications from frames that have been navigated away
   // from. We simply ignore them.
   // TODO(crbug.com/1061060): We should not ignore page timings if the page is
@@ -737,7 +738,8 @@
     committed_load_->metrics_update_dispatcher()->UpdateMetrics(
         render_frame_host, std::move(timing), std::move(metadata),
         std::move(new_features), resources, std::move(render_data),
-        std::move(cpu_timing), std::move(new_deferred_resource_data));
+        std::move(cpu_timing), std::move(new_deferred_resource_data),
+        std::move(input_timing_delta));
   }
 }
 
@@ -748,12 +750,14 @@
     std::vector<mojom::ResourceDataUpdatePtr> resources,
     mojom::FrameRenderDataUpdatePtr render_data,
     mojom::CpuTimingPtr cpu_timing,
-    mojom::DeferredResourceCountsPtr new_deferred_resource_data) {
+    mojom::DeferredResourceCountsPtr new_deferred_resource_data,
+    mojom::InputTimingPtr input_timing_delta) {
   content::RenderFrameHost* render_frame_host =
       page_load_metrics_receiver_.GetCurrentTargetFrame();
   OnTimingUpdated(render_frame_host, std::move(timing), std::move(metadata),
                   std::move(new_features), resources, std::move(render_data),
-                  std::move(cpu_timing), std::move(new_deferred_resource_data));
+                  std::move(cpu_timing), std::move(new_deferred_resource_data),
+                  std::move(input_timing_delta));
 }
 
 bool MetricsWebContentsObserver::ShouldTrackMainFrameNavigation(
diff --git a/components/page_load_metrics/browser/metrics_web_contents_observer.h b/components/page_load_metrics/browser/metrics_web_contents_observer.h
index fe68929..99bf77b 100644
--- a/components/page_load_metrics/browser/metrics_web_contents_observer.h
+++ b/components/page_load_metrics/browser/metrics_web_contents_observer.h
@@ -163,7 +163,8 @@
       const std::vector<mojom::ResourceDataUpdatePtr>& resources,
       mojom::FrameRenderDataUpdatePtr render_data,
       mojom::CpuTimingPtr cpu_timing,
-      mojom::DeferredResourceCountsPtr new_deferred_resource_data);
+      mojom::DeferredResourceCountsPtr new_deferred_resource_data,
+      mojom::InputTimingPtr input_timing_delta);
 
   // Informs the observers of the currently committed load that the event
   // corresponding to |event_key| has occurred. This should not be called within
@@ -178,14 +179,14 @@
       content::NavigationHandle* navigation_handle);
 
   // page_load_metrics::mojom::PageLoadMetrics implementation.
-  void UpdateTiming(
-      mojom::PageLoadTimingPtr timing,
-      mojom::FrameMetadataPtr metadata,
-      mojom::PageLoadFeaturesPtr new_features,
-      std::vector<mojom::ResourceDataUpdatePtr> resources,
-      mojom::FrameRenderDataUpdatePtr render_data,
-      mojom::CpuTimingPtr cpu_timing,
-      mojom::DeferredResourceCountsPtr new_deferred_resource_data) override;
+  void UpdateTiming(mojom::PageLoadTimingPtr timing,
+                    mojom::FrameMetadataPtr metadata,
+                    mojom::PageLoadFeaturesPtr new_features,
+                    std::vector<mojom::ResourceDataUpdatePtr> resources,
+                    mojom::FrameRenderDataUpdatePtr render_data,
+                    mojom::CpuTimingPtr cpu_timing,
+                    mojom::DeferredResourceCountsPtr new_deferred_resource_data,
+                    mojom::InputTimingPtr input_timing) override;
 
   void HandleFailedNavigationForTrackedLoad(
       content::NavigationHandle* navigation_handle,
diff --git a/components/page_load_metrics/browser/metrics_web_contents_observer_unittest.cc b/components/page_load_metrics/browser/metrics_web_contents_observer_unittest.cc
index 2cc2d86..de6d186 100644
--- a/components/page_load_metrics/browser/metrics_web_contents_observer_unittest.cc
+++ b/components/page_load_metrics/browser/metrics_web_contents_observer_unittest.cc
@@ -112,7 +112,8 @@
         mojom::PageLoadFeaturesPtr(base::in_place),
         std::vector<mojom::ResourceDataUpdatePtr>(),
         mojom::FrameRenderDataUpdatePtr(base::in_place), timing.Clone(),
-        mojom::DeferredResourceCountsPtr(base::in_place));
+        mojom::DeferredResourceCountsPtr(base::in_place),
+        mojom::InputTimingPtr(base::in_place));
   }
 
   void SimulateTimingUpdate(const mojom::PageLoadTiming& timing,
@@ -137,7 +138,8 @@
         std::vector<mojom::ResourceDataUpdatePtr>(),
         mojom::FrameRenderDataUpdatePtr(base::in_place),
         mojom::CpuTimingPtr(base::in_place),
-        mojom::DeferredResourceCountsPtr(base::in_place));
+        mojom::DeferredResourceCountsPtr(base::in_place),
+        mojom::InputTimingPtr(base::in_place));
   }
 
   void AttachObserver() {
diff --git a/components/page_load_metrics/browser/observers/page_load_metrics_observer_tester.cc b/components/page_load_metrics/browser/observers/page_load_metrics_observer_tester.cc
index b927215b..9de891f7 100644
--- a/components/page_load_metrics/browser/observers/page_load_metrics_observer_tester.cc
+++ b/components/page_load_metrics/browser/observers/page_load_metrics_observer_tester.cc
@@ -111,7 +111,7 @@
   SimulatePageLoadTimingUpdate(
       timing, mojom::FrameMetadata(), mojom::PageLoadFeatures(),
       mojom::FrameRenderDataUpdate(), mojom::CpuTiming(),
-      mojom::DeferredResourceCounts(), rfh);
+      mojom::DeferredResourceCounts(), mojom::InputTiming(), rfh);
 }
 
 void PageLoadMetricsObserverTester::SimulateCpuTimingUpdate(
@@ -124,10 +124,26 @@
     content::RenderFrameHost* rfh) {
   auto timing = page_load_metrics::mojom::PageLoadTimingPtr(base::in_place);
   page_load_metrics::InitPageLoadTimingForTest(timing.get());
-  SimulatePageLoadTimingUpdate(*timing, mojom::FrameMetadata(),
-                               mojom::PageLoadFeatures(),
-                               mojom::FrameRenderDataUpdate(), cpu_timing,
-                               mojom::DeferredResourceCounts(), rfh);
+  SimulatePageLoadTimingUpdate(
+      *timing, mojom::FrameMetadata(), mojom::PageLoadFeatures(),
+      mojom::FrameRenderDataUpdate(), cpu_timing,
+      mojom::DeferredResourceCounts(), mojom::InputTiming(), rfh);
+}
+
+void PageLoadMetricsObserverTester::SimulateInputTimingUpdate(
+    const mojom::InputTiming& input_timing) {
+  SimulateInputTimingUpdate(input_timing, web_contents()->GetMainFrame());
+}
+
+void PageLoadMetricsObserverTester::SimulateInputTimingUpdate(
+    const mojom::InputTiming& input_timing,
+    content::RenderFrameHost* rfh) {
+  auto timing = page_load_metrics::mojom::PageLoadTimingPtr(base::in_place);
+  page_load_metrics::InitPageLoadTimingForTest(timing.get());
+  SimulatePageLoadTimingUpdate(
+      *timing, mojom::FrameMetadata(), mojom::PageLoadFeatures(),
+      mojom::FrameRenderDataUpdate(), mojom::CpuTiming(),
+      mojom::DeferredResourceCounts(), input_timing, rfh);
 }
 
 void PageLoadMetricsObserverTester::SimulateTimingAndMetadataUpdate(
@@ -136,7 +152,8 @@
   SimulatePageLoadTimingUpdate(
       timing, metadata, mojom::PageLoadFeatures(),
       mojom::FrameRenderDataUpdate(), mojom::CpuTiming(),
-      mojom::DeferredResourceCounts(), web_contents()->GetMainFrame());
+      mojom::DeferredResourceCounts(), mojom::InputTiming(),
+      web_contents()->GetMainFrame());
 }
 
 void PageLoadMetricsObserverTester::SimulateMetadataUpdate(
@@ -144,10 +161,10 @@
     content::RenderFrameHost* rfh) {
   mojom::PageLoadTiming timing;
   InitPageLoadTimingForTest(&timing);
-  SimulatePageLoadTimingUpdate(timing, metadata, mojom::PageLoadFeatures(),
-                               mojom::FrameRenderDataUpdate(),
-                               mojom::CpuTiming(),
-                               mojom::DeferredResourceCounts(), rfh);
+  SimulatePageLoadTimingUpdate(
+      timing, metadata, mojom::PageLoadFeatures(),
+      mojom::FrameRenderDataUpdate(), mojom::CpuTiming(),
+      mojom::DeferredResourceCounts(), mojom::InputTiming(), rfh);
 }
 
 void PageLoadMetricsObserverTester::SimulateFeaturesUpdate(
@@ -155,7 +172,8 @@
   SimulatePageLoadTimingUpdate(
       mojom::PageLoadTiming(), mojom::FrameMetadata(), new_features,
       mojom::FrameRenderDataUpdate(), mojom::CpuTiming(),
-      mojom::DeferredResourceCounts(), web_contents()->GetMainFrame());
+      mojom::DeferredResourceCounts(), mojom::InputTiming(),
+      web_contents()->GetMainFrame());
 }
 
 void PageLoadMetricsObserverTester::SimulateRenderDataUpdate(
@@ -170,7 +188,8 @@
   InitPageLoadTimingForTest(&timing);
   SimulatePageLoadTimingUpdate(
       timing, mojom::FrameMetadata(), mojom::PageLoadFeatures(), render_data,
-      mojom::CpuTiming(), mojom::DeferredResourceCounts(), rfh);
+      mojom::CpuTiming(), mojom::DeferredResourceCounts(), mojom::InputTiming(),
+      rfh);
 }
 
 void PageLoadMetricsObserverTester::SimulatePageLoadTimingUpdate(
@@ -180,11 +199,13 @@
     const mojom::FrameRenderDataUpdate& render_data,
     const mojom::CpuTiming& cpu_timing,
     const mojom::DeferredResourceCounts& new_deferred_resource_data,
+    const mojom::InputTiming& input_timing,
     content::RenderFrameHost* rfh) {
   metrics_web_contents_observer_->OnTimingUpdated(
       rfh, timing.Clone(), metadata.Clone(), new_features.Clone(),
       std::vector<mojom::ResourceDataUpdatePtr>(), render_data.Clone(),
-      cpu_timing.Clone(), new_deferred_resource_data.Clone());
+      cpu_timing.Clone(), new_deferred_resource_data.Clone(),
+      input_timing.Clone());
   // If sending the timing update caused the PageLoadMetricsUpdateDispatcher to
   // schedule a buffering timer, then fire it now so metrics are dispatched to
   // observers.
@@ -209,7 +230,8 @@
       mojom::PageLoadFeaturesPtr(base::in_place), resources,
       mojom::FrameRenderDataUpdatePtr(base::in_place),
       mojom::CpuTimingPtr(base::in_place),
-      mojom::DeferredResourceCountsPtr(base::in_place));
+      mojom::DeferredResourceCountsPtr(base::in_place),
+      mojom::InputTimingPtr(base::in_place));
 }
 
 void PageLoadMetricsObserverTester::SimulateLoadedResource(
diff --git a/components/page_load_metrics/browser/observers/page_load_metrics_observer_tester.h b/components/page_load_metrics/browser/observers/page_load_metrics_observer_tester.h
index 50f90161..84e19a8 100644
--- a/components/page_load_metrics/browser/observers/page_load_metrics_observer_tester.h
+++ b/components/page_load_metrics/browser/observers/page_load_metrics_observer_tester.h
@@ -91,6 +91,9 @@
   void SimulateCpuTimingUpdate(const mojom::CpuTiming& cpu_timing);
   void SimulateCpuTimingUpdate(const mojom::CpuTiming& cpu_timing,
                                content::RenderFrameHost* rfh);
+  void SimulateInputTimingUpdate(const mojom::InputTiming& input_timing);
+  void SimulateInputTimingUpdate(const mojom::InputTiming& input_timing,
+                                 content::RenderFrameHost* rfh);
   void SimulateTimingAndMetadataUpdate(const mojom::PageLoadTiming& timing,
                                        const mojom::FrameMetadata& metadata);
   void SimulateMetadataUpdate(const mojom::FrameMetadata& metadata,
@@ -166,6 +169,7 @@
       const mojom::FrameRenderDataUpdate& render_data,
       const mojom::CpuTiming& cpu_timing,
       const mojom::DeferredResourceCounts& new_deferred_resource_data,
+      const mojom::InputTiming& input_timing,
       content::RenderFrameHost* rfh);
 
   content::WebContents* web_contents() const { return web_contents_; }
diff --git a/components/page_load_metrics/browser/page_load_metrics_observer_delegate.h b/components/page_load_metrics/browser/page_load_metrics_observer_delegate.h
index 9f2091f9..00199f5f 100644
--- a/components/page_load_metrics/browser/page_load_metrics_observer_delegate.h
+++ b/components/page_load_metrics/browser/page_load_metrics_observer_delegate.h
@@ -98,6 +98,8 @@
   // such as subframe intersections is initialized to defaults.
   virtual const mojom::FrameMetadata& GetSubframeMetadata() const = 0;
   virtual const PageRenderData& GetPageRenderData() const = 0;
+  // InputTiming data accumulated across all frames.
+  virtual const mojom::InputTiming& GetPageInputTiming() const = 0;
   virtual const PageRenderData& GetMainFrameRenderData() const = 0;
   virtual const ui::ScopedVisibilityTracker& GetVisibilityTracker() const = 0;
   virtual const ResourceTracker& GetResourceTracker() const = 0;
diff --git a/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.cc b/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.cc
index d71204c..2c03cc5 100644
--- a/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.cc
+++ b/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.cc
@@ -347,22 +347,6 @@
     mojom::InteractiveTiming* target_interactive_timing =
         target_->interactive_timing.get();
 
-    target_interactive_timing->num_input_events +=
-        new_interactive_timing.num_input_events;
-
-    if (new_interactive_timing.total_input_delay.has_value()) {
-      target_interactive_timing->total_input_delay =
-          target_interactive_timing->total_input_delay.value_or(
-              base::TimeDelta()) +
-          new_interactive_timing.total_input_delay.value();
-    }
-
-    if (new_interactive_timing.total_adjusted_input_delay.has_value()) {
-      target_interactive_timing->total_adjusted_input_delay =
-          target_interactive_timing->total_adjusted_input_delay.value_or(
-              base::TimeDelta()) +
-          new_interactive_timing.total_adjusted_input_delay.value();
-    }
     if (MaybeUpdateTimeDelta(&target_interactive_timing->first_input_timestamp,
                              navigation_start_offset,
                              new_interactive_timing.first_input_timestamp)) {
@@ -409,7 +393,8 @@
       current_merged_page_timing_(CreatePageLoadTiming()),
       pending_merged_page_timing_(CreatePageLoadTiming()),
       main_frame_metadata_(mojom::FrameMetadata::New()),
-      subframe_metadata_(mojom::FrameMetadata::New()) {}
+      subframe_metadata_(mojom::FrameMetadata::New()),
+      page_input_timing_(mojom::InputTiming()) {}
 
 PageLoadMetricsUpdateDispatcher::~PageLoadMetricsUpdateDispatcher() {
   ShutDown();
@@ -436,7 +421,8 @@
     const std::vector<mojom::ResourceDataUpdatePtr>& resources,
     mojom::FrameRenderDataUpdatePtr render_data,
     mojom::CpuTimingPtr new_cpu_timing,
-    mojom::DeferredResourceCountsPtr new_deferred_resource_data) {
+    mojom::DeferredResourceCountsPtr new_deferred_resource_data,
+    mojom::InputTimingPtr input_timing_delta) {
   if (embedder_interface_->IsExtensionUrl(
           render_frame_host->GetLastCommittedURL())) {
     // Extensions can inject child frames into a page. We don't want to track
@@ -462,7 +448,7 @@
     UpdateSubFrameMetadata(render_frame_host, std::move(new_metadata));
     UpdateSubFrameTiming(render_frame_host, std::move(new_timing));
   }
-
+  UpdatePageInputTiming(*input_timing_delta);
   UpdatePageRenderData(*render_data);
   if (!is_main_frame) {
     // This path is just for the AMP metrics.
@@ -640,6 +626,14 @@
   client_->OnMainFrameMetadataChanged();
 }
 
+void PageLoadMetricsUpdateDispatcher::UpdatePageInputTiming(
+    const mojom::InputTiming& input_timing_delta) {
+  page_input_timing_.num_input_events += input_timing_delta.num_input_events;
+  page_input_timing_.total_input_delay += input_timing_delta.total_input_delay;
+  page_input_timing_.total_adjusted_input_delay +=
+      input_timing_delta.total_adjusted_input_delay;
+}
+
 void PageLoadMetricsUpdateDispatcher::UpdatePageRenderData(
     const mojom::FrameRenderDataUpdate& render_data) {
   page_render_data_.layout_shift_score += render_data.layout_shift_delta;
diff --git a/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h b/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h
index 5739c01..95649de8 100644
--- a/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h
+++ b/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h
@@ -144,7 +144,8 @@
       const std::vector<mojom::ResourceDataUpdatePtr>& resources,
       mojom::FrameRenderDataUpdatePtr render_data,
       mojom::CpuTimingPtr new_cpu_timing,
-      mojom::DeferredResourceCountsPtr new_deferred_resource_data);
+      mojom::DeferredResourceCountsPtr new_deferred_resource_data,
+      mojom::InputTimingPtr input_timing_delta);
 
   // This method is only intended to be called for PageLoadFeatures being
   // recorded directly from the browser process. Features coming from the
@@ -171,6 +172,9 @@
   const PageRenderData& main_frame_render_data() const {
     return main_frame_render_data_;
   }
+  const mojom::InputTiming& page_input_timing() const {
+    return page_input_timing_;
+  }
 
  private:
   using FrameTreeNodeId = int;
@@ -185,6 +189,7 @@
   void UpdateSubFrameMetadata(content::RenderFrameHost* render_frame_host,
                               mojom::FrameMetadataPtr subframe_metadata);
 
+  void UpdatePageInputTiming(const mojom::InputTiming& input_timing_delta);
   void UpdatePageRenderData(const mojom::FrameRenderDataUpdate& render_data);
   void UpdateMainFrameRenderData(
       const mojom::FrameRenderDataUpdate& render_data);
@@ -222,6 +227,9 @@
   mojom::FrameMetadataPtr main_frame_metadata_;
   mojom::FrameMetadataPtr subframe_metadata_;
 
+  // InputTiming data accumulated across all frames.
+  mojom::InputTiming page_input_timing_;
+
   PageRenderData page_render_data_;
   PageRenderData main_frame_render_data_;
 
diff --git a/components/page_load_metrics/browser/page_load_tracker.cc b/components/page_load_metrics/browser/page_load_tracker.cc
index f136027..e8d6cc91 100644
--- a/components/page_load_metrics/browser/page_load_tracker.cc
+++ b/components/page_load_metrics/browser/page_load_tracker.cc
@@ -790,6 +790,10 @@
   return metrics_update_dispatcher_.page_render_data();
 }
 
+const mojom::InputTiming& PageLoadTracker::GetPageInputTiming() const {
+  return metrics_update_dispatcher_.page_input_timing();
+}
+
 const PageRenderData& PageLoadTracker::GetMainFrameRenderData() const {
   return metrics_update_dispatcher_.main_frame_render_data();
 }
diff --git a/components/page_load_metrics/browser/page_load_tracker.h b/components/page_load_metrics/browser/page_load_tracker.h
index 4913899..5f253963 100644
--- a/components/page_load_metrics/browser/page_load_tracker.h
+++ b/components/page_load_metrics/browser/page_load_tracker.h
@@ -222,6 +222,7 @@
   const mojom::FrameMetadata& GetMainFrameMetadata() const override;
   const mojom::FrameMetadata& GetSubframeMetadata() const override;
   const PageRenderData& GetPageRenderData() const override;
+  const mojom::InputTiming& GetPageInputTiming() const override;
   const PageRenderData& GetMainFrameRenderData() const override;
   const ui::ScopedVisibilityTracker& GetVisibilityTracker() const override;
   const ResourceTracker& GetResourceTracker() const override;
diff --git a/components/page_load_metrics/common/page_load_metrics.mojom b/components/page_load_metrics/common/page_load_metrics.mojom
index a4718e6..0ba2b52 100644
--- a/components/page_load_metrics/common/page_load_metrics.mojom
+++ b/components/page_load_metrics/common/page_load_metrics.mojom
@@ -105,21 +105,8 @@
 
   // The timestamp of the event whose delay is reported as longest_input_delay.
   mojo_base.mojom.TimeDelta? longest_input_timestamp;
-
-  // The sum of all input delay.
-  mojo_base.mojom.TimeDelta? total_input_delay;
-
-  // The sum of all adjusted input delay. We adjust each input delay by
-  // subtracting a small number, e.g. 50ms. And if the subtraction result is
-  // negative, we will use 0ms instead.
-  mojo_base.mojom.TimeDelta? total_adjusted_input_delay;
-
-  // The number of user interactions, including click, tap, key press,
-  // cancellable touchstart, or pointer down followed by a pointer up.
-  uint64 num_input_events = 0;
 };
 
-
 // PageLoadTiming contains timing metrics associated with a page load. Many of
 // the metrics here are based on the Navigation Timing spec:
 // http://www.w3.org/TR/navigation-timing/.
@@ -274,6 +261,21 @@
   uint64 images_loaded_after_deferral = 0;
 };
 
+// Metrics about general input delay.
+struct InputTiming {
+  // The sum of all input delay.
+  mojo_base.mojom.TimeDelta total_input_delay;
+
+  // The sum of all adjusted input delay. We adjust each input delay by
+  // subtracting a small number, currently 50ms but subject to change in the
+  // future. And if the subtraction result is negative, we will use 0ms.
+  mojo_base.mojom.TimeDelta total_adjusted_input_delay;
+
+  // The number of user interactions, including click, tap, key press,
+  // cancellable touchstart, or pointer down followed by a pointer up.
+  uint64 num_input_events = 0;
+};
+
 // Sent from renderer to browser process when the PageLoadTiming for the
 // associated frame changed.
 interface PageLoadMetrics {
@@ -287,5 +289,6 @@
                array<ResourceDataUpdate> resources,
                FrameRenderDataUpdate render_data,
                CpuTiming cpu_load_timing,
-               DeferredResourceCounts new_deferred_resource_data);
+               DeferredResourceCounts new_deferred_resource_data,
+               InputTiming input_timing_delta);
 };
diff --git a/components/page_load_metrics/common/page_load_timing.cc b/components/page_load_metrics/common/page_load_timing.cc
index bae1d3d..6ec07e6 100644
--- a/components/page_load_metrics/common/page_load_timing.cc
+++ b/components/page_load_metrics/common/page_load_timing.cc
@@ -20,10 +20,15 @@
 
 bool IsEmpty(const page_load_metrics::mojom::InteractiveTiming& timing) {
   return !timing.first_input_delay && !timing.first_input_timestamp &&
-         !timing.longest_input_delay && !timing.longest_input_timestamp &&
-         !timing.total_input_delay && !timing.total_adjusted_input_delay &&
+         !timing.longest_input_delay && !timing.longest_input_timestamp;
+}
+
+bool IsEmpty(const page_load_metrics::mojom::InputTiming& timing) {
+  return !timing.total_input_delay.InMilliseconds() &&
+         !timing.total_adjusted_input_delay.InMilliseconds() &&
          !timing.num_input_events;
 }
+
 bool IsEmpty(const page_load_metrics::mojom::PaintTiming& timing) {
   return !timing.first_paint && !timing.first_image_paint &&
          !timing.first_contentful_paint && !timing.first_meaningful_paint &&
diff --git a/components/page_load_metrics/common/page_load_timing.h b/components/page_load_metrics/common/page_load_timing.h
index 6dbfb67..2520e20c 100644
--- a/components/page_load_metrics/common/page_load_timing.h
+++ b/components/page_load_metrics/common/page_load_timing.h
@@ -19,6 +19,7 @@
 bool IsEmpty(const mojom::PaintTiming& timing);
 bool IsEmpty(const mojom::ParseTiming& timing);
 bool IsEmpty(const mojom::PageLoadTiming& timing);
+bool IsEmpty(const mojom::InteractiveTiming& timing);
 
 void InitPageLoadTimingForTest(mojom::PageLoadTiming* timing);
 
diff --git a/components/page_load_metrics/renderer/fake_page_timing_sender.cc b/components/page_load_metrics/renderer/fake_page_timing_sender.cc
index a19c5b1..681e840 100644
--- a/components/page_load_metrics/renderer/fake_page_timing_sender.cc
+++ b/components/page_load_metrics/renderer/fake_page_timing_sender.cc
@@ -22,12 +22,16 @@
     std::vector<mojom::ResourceDataUpdatePtr> resources,
     const mojom::FrameRenderDataUpdate& render_data,
     const mojom::CpuTimingPtr& cpu_timing,
-    mojom::DeferredResourceCountsPtr new_deferred_resource_data) {
+    mojom::DeferredResourceCountsPtr new_deferred_resource_data,
+    const mojom::InputTimingPtr new_input_timing) {
   validator_->UpdateTiming(timing, metadata, new_features, resources,
-                           render_data, cpu_timing, new_deferred_resource_data);
+                           render_data, cpu_timing, new_deferred_resource_data,
+                           new_input_timing);
 }
 
-FakePageTimingSender::PageTimingValidator::PageTimingValidator() = default;
+FakePageTimingSender::PageTimingValidator::PageTimingValidator()
+    : expected_input_timing(mojom::InputTiming::New()),
+      actual_input_timing(mojom::InputTiming::New()) {}
 
 FakePageTimingSender::PageTimingValidator::~PageTimingValidator() {
   VerifyExpectedTimings();
@@ -57,6 +61,25 @@
   }
 }
 
+void FakePageTimingSender::PageTimingValidator::UpdateExpectedInputTiming(
+    const base::TimeDelta input_delay) {
+  expected_input_timing->num_input_events++;
+  expected_input_timing->total_input_delay += input_delay;
+  expected_input_timing->total_adjusted_input_delay +=
+      base::TimeDelta::FromMilliseconds(
+          std::max(int64_t(0), input_delay.InMilliseconds() - int64_t(50)));
+}
+void FakePageTimingSender::PageTimingValidator::VerifyExpectedInputTiming()
+    const {
+  ASSERT_EQ(expected_input_timing.is_null(), actual_input_timing.is_null());
+  ASSERT_EQ(expected_input_timing->num_input_events,
+            actual_input_timing->num_input_events);
+  ASSERT_EQ(expected_input_timing->total_input_delay,
+            actual_input_timing->total_input_delay);
+  ASSERT_EQ(expected_input_timing->total_adjusted_input_delay,
+            actual_input_timing->total_adjusted_input_delay);
+}
+
 void FakePageTimingSender::PageTimingValidator::VerifyExpectedCpuTimings()
     const {
   ASSERT_EQ(actual_cpu_timings_.size(), expected_cpu_timings_.size());
@@ -136,7 +159,8 @@
     const std::vector<mojom::ResourceDataUpdatePtr>& resources,
     const mojom::FrameRenderDataUpdate& render_data,
     const mojom::CpuTimingPtr& cpu_timing,
-    const mojom::DeferredResourceCountsPtr& new_deferred_resource_data) {
+    const mojom::DeferredResourceCountsPtr& new_deferred_resource_data,
+    const mojom::InputTimingPtr& new_input_timing) {
   actual_timings_.push_back(timing.Clone());
   if (!cpu_timing->task_time.is_zero()) {
     actual_cpu_timings_.push_back(cpu_timing.Clone());
@@ -155,6 +179,12 @@
   }
   actual_render_data_.layout_shift_delta = render_data.layout_shift_delta;
   actual_frame_intersection_update_ = metadata->intersection_update.Clone();
+
+  actual_input_timing->num_input_events += new_input_timing->num_input_events;
+  actual_input_timing->total_input_delay += new_input_timing->total_input_delay;
+  actual_input_timing->total_adjusted_input_delay +=
+      new_input_timing->total_adjusted_input_delay;
+
   VerifyExpectedTimings();
   VerifyExpectedCpuTimings();
   VerifyExpectedFeatures();
diff --git a/components/page_load_metrics/renderer/fake_page_timing_sender.h b/components/page_load_metrics/renderer/fake_page_timing_sender.h
index 9c432596..f099df6f 100644
--- a/components/page_load_metrics/renderer/fake_page_timing_sender.h
+++ b/components/page_load_metrics/renderer/fake_page_timing_sender.h
@@ -55,6 +55,8 @@
     // expected timings provided via ExpectCpuTiming.
     void VerifyExpectedCpuTimings() const;
 
+    void VerifyExpectedInputTiming() const;
+
     // PageLoad features that are expected to be sent through SendTiming()
     // should be passed via UpdateExpectedPageLoadFeatures.
     void UpdateExpectPageLoadFeatures(const blink::mojom::WebFeature feature);
@@ -68,6 +70,8 @@
       expected_render_data_ = render_data;
     }
 
+    void UpdateExpectedInputTiming(const base::TimeDelta input_delay);
+
     void UpdateExpectFrameIntersectionUpdate(
         const mojom::FrameIntersectionUpdate& frame_intersection_update) {
       expected_frame_intersection_update_ = frame_intersection_update.Clone();
@@ -96,7 +100,8 @@
         const std::vector<mojom::ResourceDataUpdatePtr>& resources,
         const mojom::FrameRenderDataUpdate& render_data,
         const mojom::CpuTimingPtr& cpu_timing,
-        const mojom::DeferredResourceCountsPtr& new_deferred_resource_data);
+        const mojom::DeferredResourceCountsPtr& new_deferred_resource_data,
+        const mojom::InputTimingPtr& input_timing);
 
    private:
     std::vector<mojom::PageLoadTimingPtr> expected_timings_;
@@ -111,19 +116,21 @@
     mojom::FrameRenderDataUpdate actual_render_data_;
     mojom::FrameIntersectionUpdatePtr expected_frame_intersection_update_;
     mojom::FrameIntersectionUpdatePtr actual_frame_intersection_update_;
+    mojom::InputTimingPtr expected_input_timing;
+    mojom::InputTimingPtr actual_input_timing;
     DISALLOW_COPY_AND_ASSIGN(PageTimingValidator);
   };
 
   explicit FakePageTimingSender(PageTimingValidator* validator);
   ~FakePageTimingSender() override;
-  void SendTiming(
-      const mojom::PageLoadTimingPtr& timing,
-      const mojom::FrameMetadataPtr& metadata,
-      mojom::PageLoadFeaturesPtr new_features,
-      std::vector<mojom::ResourceDataUpdatePtr> resources,
-      const mojom::FrameRenderDataUpdate& render_data,
-      const mojom::CpuTimingPtr& cpu_timing,
-      mojom::DeferredResourceCountsPtr new_deferred_resource_data) override;
+  void SendTiming(const mojom::PageLoadTimingPtr& timing,
+                  const mojom::FrameMetadataPtr& metadata,
+                  mojom::PageLoadFeaturesPtr new_features,
+                  std::vector<mojom::ResourceDataUpdatePtr> resources,
+                  const mojom::FrameRenderDataUpdate& render_data,
+                  const mojom::CpuTimingPtr& cpu_timing,
+                  mojom::DeferredResourceCountsPtr new_deferred_resource_data,
+                  mojom::InputTimingPtr new_input_timing) override;
 
  private:
   PageTimingValidator* const validator_;
diff --git a/components/page_load_metrics/renderer/metrics_render_frame_observer.cc b/components/page_load_metrics/renderer/metrics_render_frame_observer.cc
index 1377d2a87..b37fcf3b 100644
--- a/components/page_load_metrics/renderer/metrics_render_frame_observer.cc
+++ b/components/page_load_metrics/renderer/metrics_render_frame_observer.cc
@@ -45,19 +45,19 @@
         &page_load_metrics_);
   }
   ~MojoPageTimingSender() override = default;
-  void SendTiming(
-      const mojom::PageLoadTimingPtr& timing,
-      const mojom::FrameMetadataPtr& metadata,
-      mojom::PageLoadFeaturesPtr new_features,
-      std::vector<mojom::ResourceDataUpdatePtr> resources,
-      const mojom::FrameRenderDataUpdate& render_data,
-      const mojom::CpuTimingPtr& cpu_timing,
-      mojom::DeferredResourceCountsPtr new_deferred_resource_data) override {
+  void SendTiming(const mojom::PageLoadTimingPtr& timing,
+                  const mojom::FrameMetadataPtr& metadata,
+                  mojom::PageLoadFeaturesPtr new_features,
+                  std::vector<mojom::ResourceDataUpdatePtr> resources,
+                  const mojom::FrameRenderDataUpdate& render_data,
+                  const mojom::CpuTimingPtr& cpu_timing,
+                  mojom::DeferredResourceCountsPtr new_deferred_resource_data,
+                  mojom::InputTimingPtr input_timing_delta) override {
     DCHECK(page_load_metrics_);
     page_load_metrics_->UpdateTiming(
         timing->Clone(), metadata->Clone(), std::move(new_features),
         std::move(resources), render_data.Clone(), cpu_timing->Clone(),
-        std::move(new_deferred_resource_data));
+        std::move(new_deferred_resource_data), std::move(input_timing_delta));
   }
 
  private:
@@ -82,6 +82,14 @@
   SendMetrics();
 }
 
+void MetricsRenderFrameObserver::DidObserveInputDelay(
+    base::TimeDelta input_delay) {
+  if (!page_timing_metrics_sender_ || HasNoRenderFrame()) {
+    return;
+  }
+  page_timing_metrics_sender_->DidObserveInputDelay(input_delay);
+}
+
 void MetricsRenderFrameObserver::DidChangeCpuTiming(base::TimeDelta time) {
   if (!page_timing_metrics_sender_)
     return;
@@ -395,17 +403,6 @@
     timing->interactive_timing->longest_input_timestamp =
         ClampDelta((*perf.LongestInputTimestamp()).InSecondsF(), start);
   }
-  if (perf.TotalInputDelay() > 0.0) {
-    timing->interactive_timing->total_input_delay =
-        base::TimeDelta::FromSecondsD(perf.TotalInputDelay());
-  }
-  if (perf.TotalAdjustedInputDelay() > 0.0) {
-    timing->interactive_timing->total_adjusted_input_delay =
-        base::TimeDelta::FromSecondsD(perf.TotalAdjustedInputDelay());
-  }
-  if (perf.NumInputEvents() > 0) {
-    timing->interactive_timing->num_input_events = perf.NumInputEvents();
-  }
   if (perf.ResponseStart() > 0.0)
     timing->response_start = ClampDelta(perf.ResponseStart(), start);
   if (perf.DomContentLoadedEventStart() > 0.0) {
diff --git a/components/page_load_metrics/renderer/metrics_render_frame_observer.h b/components/page_load_metrics/renderer/metrics_render_frame_observer.h
index 559167c..0d82c407 100644
--- a/components/page_load_metrics/renderer/metrics_render_frame_observer.h
+++ b/components/page_load_metrics/renderer/metrics_render_frame_observer.h
@@ -45,6 +45,7 @@
 
   // RenderFrameObserver implementation
   void DidChangePerformanceTiming() override;
+  void DidObserveInputDelay(base::TimeDelta input_delay) override;
   void DidChangeCpuTiming(base::TimeDelta time) override;
   void DidObserveLoadingBehavior(blink::LoadingBehaviorFlag behavior) override;
   void DidObserveNewFeatureUsage(blink::mojom::WebFeature feature) override;
diff --git a/components/page_load_metrics/renderer/page_timing_metrics_sender.cc b/components/page_load_metrics/renderer/page_timing_metrics_sender.cc
index aefb357..a649d2f8 100644
--- a/components/page_load_metrics/renderer/page_timing_metrics_sender.cc
+++ b/components/page_load_metrics/renderer/page_timing_metrics_sender.cc
@@ -23,6 +23,7 @@
 
 namespace {
 const int kInitialTimerDelayMillis = 50;
+const int64_t kInputDelayAdjustmentMillis = int64_t(50);
 const base::Feature kPageLoadMetricsTimerDelayFeature{
     "PageLoadMetricsTimerDelay", base::FEATURE_DISABLED_BY_DEFAULT};
 }  // namespace
@@ -37,6 +38,7 @@
       timer_(std::move(timer)),
       last_timing_(std::move(initial_timing)),
       last_cpu_timing_(mojom::CpuTiming::New()),
+      input_timing_delta_(mojom::InputTiming::New()),
       metadata_(mojom::FrameMetadata::New()),
       new_features_(mojom::PageLoadFeatures::New()),
       new_deferred_resource_data_(mojom::DeferredResourceCounts::New()),
@@ -290,9 +292,12 @@
       page_resource_data_use_.erase(resource->resource_id());
     }
   }
+
   sender_->SendTiming(last_timing_, metadata_, std::move(new_features_),
                       std::move(resources), render_data_, last_cpu_timing_,
-                      std::move(new_deferred_resource_data_));
+                      std::move(new_deferred_resource_data_),
+                      std::move(input_timing_delta_));
+  input_timing_delta_ = mojom::InputTiming::New();
   new_deferred_resource_data_ = mojom::DeferredResourceCounts::New();
   new_features_ = mojom::PageLoadFeatures::New();
   metadata_->intersection_update.reset();
@@ -302,4 +307,15 @@
   render_data_.layout_shift_delta_before_input_or_scroll = 0;
 }
 
+void PageTimingMetricsSender::DidObserveInputDelay(
+    base::TimeDelta input_delay) {
+  input_timing_delta_->num_input_events++;
+  input_timing_delta_->total_input_delay += input_delay;
+  input_timing_delta_->total_adjusted_input_delay +=
+      base::TimeDelta::FromMilliseconds(
+          std::max(int64_t(0),
+                   input_delay.InMilliseconds() - kInputDelayAdjustmentMillis));
+  EnsureSendTimer();
+}
+
 }  // namespace page_load_metrics
diff --git a/components/page_load_metrics/renderer/page_timing_metrics_sender.h b/components/page_load_metrics/renderer/page_timing_metrics_sender.h
index b379d322..24df4c0 100644
--- a/components/page_load_metrics/renderer/page_timing_metrics_sender.h
+++ b/components/page_load_metrics/renderer/page_timing_metrics_sender.h
@@ -72,6 +72,7 @@
   void OnMainFrameDocumentIntersectionChanged(
       const blink::WebRect& intersect_rect);
 
+  void DidObserveInputDelay(base::TimeDelta input_delay);
   // Updates the timing information. Buffers |timing| to be sent over mojo
   // sometime 'soon'.
   void Update(
@@ -101,6 +102,7 @@
   std::unique_ptr<base::OneShotTimer> timer_;
   mojom::PageLoadTimingPtr last_timing_;
   mojom::CpuTimingPtr last_cpu_timing_;
+  mojom::InputTimingPtr input_timing_delta_;
 
   // The the sender keep track of metadata as it comes in, because the sender is
   // scoped to a single committed load.
diff --git a/components/page_load_metrics/renderer/page_timing_metrics_sender_unittest.cc b/components/page_load_metrics/renderer/page_timing_metrics_sender_unittest.cc
index 6ee825a..57b17a8b 100644
--- a/components/page_load_metrics/renderer/page_timing_metrics_sender_unittest.cc
+++ b/components/page_load_metrics/renderer/page_timing_metrics_sender_unittest.cc
@@ -140,6 +140,27 @@
   metrics_sender_->SendLatest();
 }
 
+TEST_F(PageTimingMetricsSenderTest, SendInputEvents) {
+  mojom::PageLoadTiming timing;
+  InitPageLoadTimingForTest(&timing);
+  base::TimeDelta input_delay_1 = base::TimeDelta::FromMilliseconds(40);
+  base::TimeDelta input_delay_2 = base::TimeDelta::FromMilliseconds(60);
+
+  metrics_sender_->Update(timing.Clone(),
+                          PageTimingMetadataRecorder::MonotonicTiming());
+  validator_.ExpectPageLoadTiming(timing);
+
+  metrics_sender_->DidObserveInputDelay(input_delay_1);
+  validator_.UpdateExpectedInputTiming(input_delay_1);
+
+  metrics_sender_->DidObserveInputDelay(input_delay_2);
+  validator_.UpdateExpectedInputTiming(input_delay_2);
+
+  // Fire the timer to trigger sending of features via an SendTiming call.
+  metrics_sender_->mock_timer()->Fire();
+  validator_.VerifyExpectedInputTiming();
+}
+
 TEST_F(PageTimingMetricsSenderTest, SendSingleFeature) {
   mojom::PageLoadTiming timing;
   InitPageLoadTimingForTest(&timing);
diff --git a/components/page_load_metrics/renderer/page_timing_sender.h b/components/page_load_metrics/renderer/page_timing_sender.h
index 38190a2..f839f61 100644
--- a/components/page_load_metrics/renderer/page_timing_sender.h
+++ b/components/page_load_metrics/renderer/page_timing_sender.h
@@ -21,7 +21,8 @@
       std::vector<mojom::ResourceDataUpdatePtr> resources,
       const mojom::FrameRenderDataUpdate& render_data,
       const mojom::CpuTimingPtr& cpu_timing,
-      mojom::DeferredResourceCountsPtr new_deferred_resource_data) = 0;
+      mojom::DeferredResourceCountsPtr new_deferred_resource_data,
+      mojom::InputTimingPtr input_timing_delta) = 0;
 };
 
 }  // namespace page_load_metrics
diff --git a/components/payments/content/payment_request.cc b/components/payments/content/payment_request.cc
index 62a88251..a55904d1 100644
--- a/components/payments/content/payment_request.cc
+++ b/components/payments/content/payment_request.cc
@@ -38,6 +38,7 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/origin_util.h"
+#include "services/metrics/public/cpp/ukm_source_id.h"
 
 namespace payments {
 namespace {
@@ -764,6 +765,14 @@
   return delegate_->IsIncognito();
 }
 
+void PaymentRequest::OnPaymentHandlerOpenWindowCalled() {
+  DCHECK(state_->selected_app());
+  // UKM for payment app origin should get recorded only when the origin of the
+  // invoked payment app is shown to the user.
+  journey_logger_.SetPaymentAppUkmSourceId(
+      state_->selected_app()->UkmSourceId());
+}
+
 void PaymentRequest::RecordFirstAbortReason(
     JourneyLogger::AbortReason abort_reason) {
   if (!has_recorded_completion_) {
diff --git a/components/payments/content/payment_request.h b/components/payments/content/payment_request.h
index 18ec889..8444bd9e 100644
--- a/components/payments/content/payment_request.h
+++ b/components/payments/content/payment_request.h
@@ -126,6 +126,9 @@
 
   bool IsIncognito() const;
 
+  // Called when the payment handler requests to open a payment handler window.
+  void OnPaymentHandlerOpenWindowCalled();
+
   content::WebContents* web_contents() { return web_contents_; }
 
   bool skipped_payment_request_ui() { return skipped_payment_request_ui_; }
diff --git a/components/payments/content/payment_response_helper_unittest.cc b/components/payments/content/payment_response_helper_unittest.cc
index 01e9874..96c67a2f 100644
--- a/components/payments/content/payment_response_helper_unittest.cc
+++ b/components/payments/content/payment_response_helper_unittest.cc
@@ -33,11 +33,11 @@
     test_personal_data_manager_.AddProfile(address_);
 
     // Set up the autofill payment app.
-    autofill::CreditCard visa_card = autofill::test::GetCreditCard();
-    visa_card.set_billing_address_id(address_.guid());
-    visa_card.set_use_count(5u);
+    visa_card_ = autofill::test::GetCreditCard();
+    visa_card_.set_billing_address_id(address_.guid());
+    visa_card_.set_use_count(5u);
     autofill_app_ = std::make_unique<AutofillPaymentApp>(
-        "visa", visa_card, billing_addresses_, "en-US",
+        "visa", visa_card_, billing_addresses_, "en-US",
         &test_payment_request_delegate_);
   }
   ~PaymentResponseHelperTest() override {}
@@ -90,6 +90,7 @@
   PaymentRequestSpec* spec() { return spec_.get(); }
   const mojom::PaymentResponsePtr& response() { return payment_response_; }
   autofill::AutofillProfile* test_address() { return &address_; }
+  const autofill::CreditCard& test_credit_card() { return visa_card_; }
   PaymentApp* test_app() { return autofill_app_.get(); }
   PaymentRequestDelegate* test_payment_request_delegate() {
     return &test_payment_request_delegate_;
@@ -103,6 +104,7 @@
 
   // Test data.
   autofill::AutofillProfile address_;
+  autofill::CreditCard visa_card_;
   const std::vector<autofill::AutofillProfile*> billing_addresses_;
   std::unique_ptr<AutofillPaymentApp> autofill_app_;
 };
@@ -119,22 +121,27 @@
                                test_address(), this);
   EXPECT_EQ("visa", response()->method_name);
   EXPECT_EQ(
-      "{\"billingAddress\":"
-      "{\"addressLine\":[\"666 Erebus St.\",\"Apt 8\"],"
-      "\"city\":\"Elysium\","
-      "\"country\":\"US\","
-      "\"dependentLocality\":\"\","
-      "\"organization\":\"Underworld\","
-      "\"phone\":\"16502111111\","
-      "\"postalCode\":\"91111\","
-      "\"recipient\":\"John H. Doe\","
-      "\"region\":\"CA\","
-      "\"sortingCode\":\"\"},"
-      "\"cardNumber\":\"4111111111111111\","
-      "\"cardSecurityCode\":\"123\","
-      "\"cardholderName\":\"Test User\","
-      "\"expiryMonth\":\"11\","
-      "\"expiryYear\":\"2022\"}",
+      base::StringPrintf(
+          "{\"billingAddress\":"
+          "{\"addressLine\":[\"666 Erebus St.\",\"Apt 8\"],"
+          "\"city\":\"Elysium\","
+          "\"country\":\"US\","
+          "\"dependentLocality\":\"\","
+          "\"organization\":\"Underworld\","
+          "\"phone\":\"16502111111\","
+          "\"postalCode\":\"91111\","
+          "\"recipient\":\"John H. Doe\","
+          "\"region\":\"CA\","
+          "\"sortingCode\":\"\"},"
+          "\"cardNumber\":\"4111111111111111\","
+          "\"cardSecurityCode\":\"123\","
+          "\"cardholderName\":\"Test User\","
+          "\"expiryMonth\":\"%s\","
+          "\"expiryYear\":\"%s\"}",
+          base::UTF16ToUTF8(test_credit_card().Expiration2DigitMonthAsString())
+              .c_str(),
+          base::UTF16ToUTF8(test_credit_card().Expiration4DigitYearAsString())
+              .c_str()),
       response()->stringified_details);
 }
 
@@ -157,22 +164,27 @@
                                test_address(), this);
   EXPECT_EQ("basic-card", response()->method_name);
   EXPECT_EQ(
-      "{\"billingAddress\":"
-      "{\"addressLine\":[\"666 Erebus St.\",\"Apt 8\"],"
-      "\"city\":\"Elysium\","
-      "\"country\":\"US\","
-      "\"dependentLocality\":\"\","
-      "\"organization\":\"Underworld\","
-      "\"phone\":\"16502111111\","
-      "\"postalCode\":\"91111\","
-      "\"recipient\":\"John H. Doe\","
-      "\"region\":\"CA\","
-      "\"sortingCode\":\"\"},"
-      "\"cardNumber\":\"4111111111111111\","
-      "\"cardSecurityCode\":\"123\","
-      "\"cardholderName\":\"Test User\","
-      "\"expiryMonth\":\"11\","
-      "\"expiryYear\":\"2022\"}",
+      base::StringPrintf(
+          "{\"billingAddress\":"
+          "{\"addressLine\":[\"666 Erebus St.\",\"Apt 8\"],"
+          "\"city\":\"Elysium\","
+          "\"country\":\"US\","
+          "\"dependentLocality\":\"\","
+          "\"organization\":\"Underworld\","
+          "\"phone\":\"16502111111\","
+          "\"postalCode\":\"91111\","
+          "\"recipient\":\"John H. Doe\","
+          "\"region\":\"CA\","
+          "\"sortingCode\":\"\"},"
+          "\"cardNumber\":\"4111111111111111\","
+          "\"cardSecurityCode\":\"123\","
+          "\"cardholderName\":\"Test User\","
+          "\"expiryMonth\":\"%s\","
+          "\"expiryYear\":\"%s\"}",
+          base::UTF16ToUTF8(test_credit_card().Expiration2DigitMonthAsString())
+              .c_str(),
+          base::UTF16ToUTF8(test_credit_card().Expiration4DigitYearAsString())
+              .c_str()),
       response()->stringified_details);
 }
 
diff --git a/components/payments/content/service_worker_payment_app.cc b/components/payments/content/service_worker_payment_app.cc
index 8d9b29ac..25fa9d7 100644
--- a/components/payments/content/service_worker_payment_app.cc
+++ b/components/payments/content/service_worker_payment_app.cc
@@ -531,4 +531,16 @@
   identity_callback_.Run(origin, registration_id);
 }
 
+ukm::SourceId ServiceWorkerPaymentApp::UkmSourceId() {
+  if (ukm_source_id_ == ukm::kInvalidSourceId) {
+    // At this point we know that the payment handler window is open for this
+    // app since this getter is called for the invoked app inside the
+    // PaymentRequest::OnPaymentHandlerOpenWindowCalled function.
+    ukm_source_id_ = content::PaymentAppProvider::GetInstance()
+                         ->GetSourceIdForPaymentAppFromScope(
+                             GURL(stored_payment_app_info_->scope).GetOrigin());
+  }
+  return ukm_source_id_;
+}
+
 }  // namespace payments
diff --git a/components/payments/content/service_worker_payment_app.h b/components/payments/content/service_worker_payment_app.h
index d58a692c..f6e51300 100644
--- a/components/payments/content/service_worker_payment_app.h
+++ b/components/payments/content/service_worker_payment_app.h
@@ -97,6 +97,7 @@
   bool HandlesPayerName() const override;
   bool HandlesPayerEmail() const override;
   bool HandlesPayerPhone() const override;
+  ukm::SourceId UkmSourceId() override;
 
   void set_payment_handler_host(
       mojo::PendingRemote<mojom::PaymentHandlerHost> payment_handler_host) {
@@ -153,6 +154,8 @@
   std::unique_ptr<WebAppInstallationInfo> installable_web_app_info_;
   std::string installable_enabled_method_;
 
+  ukm::SourceId ukm_source_id_ = ukm::kInvalidSourceId;
+
   base::WeakPtrFactory<ServiceWorkerPaymentApp> weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(ServiceWorkerPaymentApp);
diff --git a/components/payments/core/journey_logger.cc b/components/payments/core/journey_logger.cc
index 95188ee..92e7b95 100644
--- a/components/payments/core/journey_logger.cc
+++ b/components/payments/core/journey_logger.cc
@@ -82,10 +82,11 @@
 
 }  // namespace
 
-JourneyLogger::JourneyLogger(bool is_incognito, ukm::SourceId source_id)
+JourneyLogger::JourneyLogger(bool is_incognito,
+                             ukm::SourceId payment_request_source_id)
     : is_incognito_(is_incognito),
       events_(EVENT_INITIATED),
-      source_id_(source_id) {}
+      payment_request_source_id_(payment_request_source_id) {}
 
 JourneyLogger::~JourneyLogger() {
   // has_recorded_ is false in cases that the page gets closed. To see more
@@ -253,11 +254,11 @@
   base::UmaHistogramEnumeration(
       "PaymentRequest.TransactionAmount" + completion_suffix, transaction_size);
 
-  if (source_id_ == ukm::kInvalidSourceId)
+  if (payment_request_source_id_ == ukm::kInvalidSourceId)
     return;
 
   // Record the transaction amount in UKM.
-  ukm::builders::PaymentRequest_TransactionAmount(source_id_)
+  ukm::builders::PaymentRequest_TransactionAmount(payment_request_source_id_)
       .SetCompletionStatus(completed)
       .SetCategory(static_cast<int64_t>(transaction_size))
       .Record(ukm::UkmRecorder::Get());
@@ -353,14 +354,26 @@
   ValidateEventBits();
   base::UmaHistogramSparse("PaymentRequest.Events", events_);
 
-  if (source_id_ == ukm::kInvalidSourceId)
+  if (payment_request_source_id_ == ukm::kInvalidSourceId)
     return;
 
   // Record the events in UKM.
-  ukm::builders::PaymentRequest_CheckoutEvents(source_id_)
+  ukm::builders::PaymentRequest_CheckoutEvents(payment_request_source_id_)
       .SetCompletionStatus(completion_status)
       .SetEvents(events_)
       .Record(ukm::UkmRecorder::Get());
+
+  if (payment_app_source_id_ == ukm::kInvalidSourceId)
+    return;
+
+  // Record the events in UKM for payment app.
+  ukm::builders::PaymentApp_CheckoutEvents(payment_app_source_id_)
+      .SetCompletionStatus(completion_status)
+      .SetEvents(events_)
+      .Record(ukm::UkmRecorder::Get());
+
+  // Clear payment app source id since it gets deleted after recording.
+  payment_app_source_id_ = ukm::kInvalidSourceId;
 }
 
 void JourneyLogger::RecordTimeToCheckout(
@@ -490,4 +503,9 @@
   trigger_time_ = base::TimeTicks::Now();
 }
 
+void JourneyLogger::SetPaymentAppUkmSourceId(
+    ukm::SourceId payment_app_source_id) {
+  payment_app_source_id_ = payment_app_source_id;
+}
+
 }  // namespace payments
diff --git a/components/payments/core/journey_logger.h b/components/payments/core/journey_logger.h
index 69615f6..47cf99c 100644
--- a/components/payments/core/journey_logger.h
+++ b/components/payments/core/journey_logger.h
@@ -152,7 +152,7 @@
     kMaxValue = kRegularTransaction,
   };
 
-  JourneyLogger(bool is_incognito, ukm::SourceId source_id);
+  JourneyLogger(bool is_incognito, ukm::SourceId payment_request_source_id);
   ~JourneyLogger();
 
   // Increments the number of selection adds for the specified section.
@@ -215,6 +215,9 @@
   // Records when Payment Request .show is called.
   void SetTriggerTime();
 
+  // Sets the ukm source id of the selected app when it gets invoked.
+  void SetPaymentAppUkmSourceId(ukm::SourceId payment_app_source_id);
+
  private:
   static const int NUMBER_OF_SECTIONS = 3;
 
@@ -285,7 +288,8 @@
   // checkout duration.
   base::TimeTicks trigger_time_;
 
-  ukm::SourceId source_id_;
+  ukm::SourceId payment_request_source_id_;
+  ukm::SourceId payment_app_source_id_ = ukm::kInvalidSourceId;
 
   DISALLOW_COPY_AND_ASSIGN(JourneyLogger);
 };
diff --git a/components/payments/core/payment_app.cc b/components/payments/core/payment_app.cc
index 703024e..6cfe02e 100644
--- a/components/payments/core/payment_app.cc
+++ b/components/payments/core/payment_app.cc
@@ -62,6 +62,10 @@
   return app_method_names_;
 }
 
+ukm::SourceId PaymentApp::UkmSourceId() {
+  return ukm::kInvalidSourceId;
+}
+
 // static
 void PaymentApp::SortApps(std::vector<std::unique_ptr<PaymentApp>>* apps) {
   DCHECK(apps);
diff --git a/components/payments/core/payment_app.h b/components/payments/core/payment_app.h
index 0e245575..5cf771dc 100644
--- a/components/payments/core/payment_app.h
+++ b/components/payments/core/payment_app.h
@@ -15,6 +15,7 @@
 #include "build/build_config.h"
 #include "components/autofill/core/browser/data_model/credit_card.h"
 #include "components/payments/core/payer_data.h"
+#include "services/metrics/public/cpp/ukm_source_id.h"
 #include "ui/gfx/image/image_skia.h"
 
 namespace payments {
@@ -110,6 +111,8 @@
   int icon_resource_id() const { return icon_resource_id_; }
   Type type() const { return type_; }
 
+  virtual ukm::SourceId UkmSourceId();
+
  protected:
   PaymentApp(int icon_resource_id, Type type);
 
diff --git a/components/payments/core/payment_request_data_util_unittest.cc b/components/payments/core/payment_request_data_util_unittest.cc
index 1076648..b034fca 100644
--- a/components/payments/core/payment_request_data_util_unittest.cc
+++ b/components/payments/core/payment_request_data_util_unittest.cc
@@ -8,6 +8,7 @@
 
 #include "base/json/json_writer.h"
 #include "base/macros.h"
+#include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
 #include "build/build_config.h"
@@ -69,24 +70,26 @@
           ->ToDictionaryValue();
   std::string json_response;
   base::JSONWriter::Write(*response_value, &json_response);
-  EXPECT_EQ(
-      "{\"billingAddress\":"
-      "{\"addressLine\":[\"666 Erebus St.\",\"Apt 8\"],"
-      "\"city\":\"Elysium\","
-      "\"country\":\"US\","
-      "\"dependentLocality\":\"\","
-      "\"organization\":\"Underworld\","
-      "\"phone\":\"16502111111\","
-      "\"postalCode\":\"91111\","
-      "\"recipient\":\"John H. Doe\","
-      "\"region\":\"CA\","
-      "\"sortingCode\":\"\"},"
-      "\"cardNumber\":\"4111111111111111\","
-      "\"cardSecurityCode\":\"123\","
-      "\"cardholderName\":\"Test User\","
-      "\"expiryMonth\":\"11\","
-      "\"expiryYear\":\"2022\"}",
-      json_response);
+  EXPECT_EQ(base::StringPrintf(
+                "{\"billingAddress\":"
+                "{\"addressLine\":[\"666 Erebus St.\",\"Apt 8\"],"
+                "\"city\":\"Elysium\","
+                "\"country\":\"US\","
+                "\"dependentLocality\":\"\","
+                "\"organization\":\"Underworld\","
+                "\"phone\":\"16502111111\","
+                "\"postalCode\":\"91111\","
+                "\"recipient\":\"John H. Doe\","
+                "\"region\":\"CA\","
+                "\"sortingCode\":\"\"},"
+                "\"cardNumber\":\"4111111111111111\","
+                "\"cardSecurityCode\":\"123\","
+                "\"cardholderName\":\"Test User\","
+                "\"expiryMonth\":\"%s\","
+                "\"expiryYear\":\"%s\"}",
+                base::UTF16ToUTF8(card.Expiration2DigitMonthAsString()).c_str(),
+                base::UTF16ToUTF8(card.Expiration4DigitYearAsString()).c_str()),
+            json_response);
 }
 
 // A test fixture to check ParseSupportedMethods() returns empty supported
diff --git a/components/policy/core/browser/configuration_policy_handler.cc b/components/policy/core/browser/configuration_policy_handler.cc
index 20e2d97..d170260 100644
--- a/components/policy/core/browser/configuration_policy_handler.cc
+++ b/components/policy/core/browser/configuration_policy_handler.cc
@@ -93,18 +93,14 @@
 
 void ListPolicyHandler::ApplyPolicySettings(const policy::PolicyMap& policies,
                                             PrefValueMap* prefs) {
-  std::unique_ptr<base::ListValue> list;
-  if (CheckAndGetList(policies, nullptr, &list) && list)
+  base::Value list(base::Value::Type::NONE);
+  if (CheckAndGetList(policies, nullptr, &list) && list.is_list())
     ApplyList(std::move(list), prefs);
 }
 
-bool ListPolicyHandler::CheckAndGetList(
-    const policy::PolicyMap& policies,
-    policy::PolicyErrorMap* errors,
-    std::unique_ptr<base::ListValue>* filtered_list) {
-  if (filtered_list)
-    filtered_list->reset();
-
+bool ListPolicyHandler::CheckAndGetList(const policy::PolicyMap& policies,
+                                        policy::PolicyErrorMap* errors,
+                                        base::Value* filtered_list) {
   const base::Value* value = nullptr;
   if (!CheckAndGetValue(policies, errors, &value))
     return false;
@@ -115,7 +111,7 @@
   // Filter the list, rejecting any invalid strings.
   base::Value::ConstListView list = value->GetList();
   if (filtered_list)
-    *filtered_list = std::make_unique<base::ListValue>();
+    *filtered_list = base::Value(base::Value::Type::LIST);
   for (size_t list_index = 0; list_index < list.size(); ++list_index) {
     const base::Value& entry = list[list_index];
     if (entry.type() != list_entry_type_) {
@@ -135,7 +131,7 @@
     }
 
     if (filtered_list)
-      (*filtered_list)->Append(entry.CreateDeepCopy());
+      filtered_list->Append(entry.Clone());
   }
 
   return true;
diff --git a/components/policy/core/browser/configuration_policy_handler.h b/components/policy/core/browser/configuration_policy_handler.h
index 573a7a3e..0583453 100644
--- a/components/policy/core/browser/configuration_policy_handler.h
+++ b/components/policy/core/browser/configuration_policy_handler.h
@@ -128,8 +128,7 @@
 
   // Implement this method to apply the |filtered_list| of values of type
   // |list_entry_type_| as returned from CheckAndGetList() to |prefs|.
-  virtual void ApplyList(std::unique_ptr<base::ListValue> filtered_list,
-                         PrefValueMap* prefs) = 0;
+  virtual void ApplyList(base::Value filtered_list, PrefValueMap* prefs) = 0;
 
  private:
   // Checks whether the policy value is indeed a list, filters out all entries
@@ -138,7 +137,7 @@
   // filtered list entries if |errors| is not nullptr.
   bool CheckAndGetList(const policy::PolicyMap& policies,
                        policy::PolicyErrorMap* errors,
-                       std::unique_ptr<base::ListValue>* filtered_list);
+                       base::Value* filtered_list);
 
   // Expected value type for list entries. All other types are filtered out.
   base::Value::Type list_entry_type_;
diff --git a/components/policy/core/browser/configuration_policy_handler_unittest.cc b/components/policy/core/browser/configuration_policy_handler_unittest.cc
index 0bb1036..3849a735 100644
--- a/components/policy/core/browser/configuration_policy_handler_unittest.cc
+++ b/components/policy/core/browser/configuration_policy_handler_unittest.cc
@@ -115,11 +115,9 @@
       : ListPolicyHandler(kPolicyName, base::Value::Type::STRING) {}
 
  protected:
-  void ApplyList(std::unique_ptr<base::ListValue> filtered_list,
-                 PrefValueMap* prefs) override {
-    DCHECK(filtered_list);
-    prefs->SetValue(kTestPref,
-                    base::Value::FromUniquePtrValue(std::move(filtered_list)));
+  void ApplyList(base::Value filtered_list, PrefValueMap* prefs) override {
+    DCHECK(filtered_list.is_list());
+    prefs->SetValue(kTestPref, std::move(filtered_list));
   }
 };
 
diff --git a/components/safe_browsing/content/base_ui_manager.cc b/components/safe_browsing/content/base_ui_manager.cc
index e27a738..1df8979 100644
--- a/components/safe_browsing/content/base_ui_manager.cc
+++ b/components/safe_browsing/content/base_ui_manager.cc
@@ -254,20 +254,28 @@
                           ? resource.url
                           : GetNavigationEntryForResource(resource)->GetURL();
     AddUnsafeResource(unsafe_url, resource);
-    // With committed interstitials we just cancel the load from here, the
-    // actual interstitial will be shown from the
-    // SafeBrowsingNavigationThrottle.
+    // If the delayed warnings experiment is not enabled, with committed
+    // interstitials we just cancel the load from here, the actual interstitial
+    // will be shown from the SafeBrowsingNavigationThrottle.
     // showed_interstitial is set to false for subresources since this
     // cancellation doesn't correspond to the navigation that triggers the error
     // page (the call to LoadPostCommitErrorPage creates another navigation).
+    //
+    // If the experiment is enabled, the interstitial is shown below.
     if (!resource.callback.is_null()) {
       resource.callback_thread->PostTask(
           FROM_HERE,
           base::BindOnce(
-              resource.callback, /*proceed=*/false,
-              /*showed_interstitial=*/resource.IsMainPageLoadBlocked()));
+              resource.callback, false /* proceed */,
+              resource.IsMainPageLoadBlocked() /* showed_interstitial */));
     }
-    if (!resource.IsMainPageLoadBlocked() && !IsWhitelisted(resource)) {
+
+    if (!base::FeatureList::IsEnabled(safe_browsing::kDelayedWarnings)) {
+      DCHECK(!resource.is_delayed_warning);
+    }
+
+    if ((!resource.IsMainPageLoadBlocked() || resource.is_delayed_warning) &&
+        !IsWhitelisted(resource)) {
       // For subresource triggered interstitials, we trigger the error page
       // navigation from here since there will be no navigation to intercept
       // in the throttle.
diff --git a/components/safe_browsing/core/browser/safe_browsing_url_checker_impl.cc b/components/safe_browsing/core/browser/safe_browsing_url_checker_impl.cc
index f84a37fb..bd21644 100644
--- a/components/safe_browsing/core/browser/safe_browsing_url_checker_impl.cc
+++ b/components/safe_browsing/core/browser/safe_browsing_url_checker_impl.cc
@@ -13,6 +13,7 @@
 #include "components/safe_browsing/core/browser/url_checker_delegate.h"
 #include "components/safe_browsing/core/common/safebrowsing_constants.h"
 #include "components/safe_browsing/core/common/thread_utils.h"
+#include "components/safe_browsing/core/features.h"
 #include "components/safe_browsing/core/realtime/policy_engine.h"
 #include "components/safe_browsing/core/realtime/url_lookup_service.h"
 #include "components/safe_browsing/core/web_ui/constants.h"
@@ -135,6 +136,32 @@
   CheckUrlImpl(url, method, Notifier(std::move(callback)));
 }
 
+security_interstitials::UnsafeResource
+SafeBrowsingUrlCheckerImpl::MakeUnsafeResource(const GURL& url,
+                                               SBThreatType threat_type,
+                                               const ThreatMetadata& metadata) {
+  security_interstitials::UnsafeResource resource;
+  resource.url = url;
+  resource.original_url = urls_[0].url;
+  if (urls_.size() > 1) {
+    resource.redirect_urls.reserve(urls_.size() - 1);
+    for (size_t i = 1; i < urls_.size(); ++i)
+      resource.redirect_urls.push_back(urls_[i].url);
+  }
+  resource.is_subresource = resource_type_ != ResourceType::kMainFrame;
+  resource.is_subframe = resource_type_ == ResourceType::kSubFrame;
+  resource.threat_type = threat_type;
+  resource.threat_metadata = metadata;
+  resource.callback =
+      base::BindRepeating(&SafeBrowsingUrlCheckerImpl::OnBlockingPageComplete,
+                          weak_factory_.GetWeakPtr());
+  resource.callback_thread =
+      base::CreateSingleThreadTaskRunner(CreateTaskTraits(ThreadID::IO));
+  resource.web_contents_getter = web_contents_getter_;
+  resource.threat_source = database_manager_->GetThreatSource();
+  return resource;
+}
+
 void SafeBrowsingUrlCheckerImpl::OnCheckBrowseUrlResult(
     const GURL& url,
     SBThreatType threat_type,
@@ -154,6 +181,26 @@
 
   TRACE_EVENT_ASYNC_END1("safe_browsing", "CheckUrl", this, "url", url.spec());
 
+  if (base::FeatureList::IsEnabled(kDelayedWarnings)) {
+    // Delayed warnings experiment delays the warning until a user interaction
+    // happens. Create an interaction observer and continue like there wasn't
+    // a warning. The observer will create the interstitial when necessary.
+    security_interstitials::UnsafeResource unsafe_resource =
+        MakeUnsafeResource(url, threat_type, metadata);
+    unsafe_resource.is_delayed_warning = true;
+    url_checker_delegate_
+        ->StartObservingInteractionsForDelayedBlockingPageHelper(
+            unsafe_resource, resource_type_ == ResourceType::kMainFrame);
+
+    // Let the navigation continue.
+    threat_type = SB_THREAT_TYPE_SAFE;
+    state_ = STATE_DELAYED_BLOCKING_PAGE;
+    if (!RunNextCallback(true, false))
+      return;
+    // No need to call ProcessUrls, it'll return early.
+    return;
+  }
+
   if (threat_type == SB_THREAT_TYPE_SAFE ||
       threat_type == SB_THREAT_TYPE_SUSPICIOUS_SITE) {
     state_ = STATE_NONE;
@@ -186,25 +233,8 @@
 
   UMA_HISTOGRAM_ENUMERATION("SB2.ResourceTypes2.Unsafe", resource_type_);
 
-  security_interstitials::UnsafeResource resource;
-  resource.url = url;
-  resource.original_url = urls_[0].url;
-  if (urls_.size() > 1) {
-    resource.redirect_urls.reserve(urls_.size() - 1);
-    for (size_t i = 1; i < urls_.size(); ++i)
-      resource.redirect_urls.push_back(urls_[i].url);
-  }
-  resource.is_subresource = resource_type_ != ResourceType::kMainFrame;
-  resource.is_subframe = resource_type_ == ResourceType::kSubFrame;
-  resource.threat_type = threat_type;
-  resource.threat_metadata = metadata;
-  resource.callback =
-      base::BindRepeating(&SafeBrowsingUrlCheckerImpl::OnBlockingPageComplete,
-                          weak_factory_.GetWeakPtr());
-  resource.callback_thread =
-      base::CreateSingleThreadTaskRunner(CreateTaskTraits(ThreadID::IO));
-  resource.web_contents_getter = web_contents_getter_;
-  resource.threat_source = database_manager_->GetThreatSource();
+  security_interstitials::UnsafeResource resource =
+      MakeUnsafeResource(url, threat_type, metadata);
 
   state_ = STATE_DISPLAYING_BLOCKING_PAGE;
   url_checker_delegate_->StartDisplayingBlockingPageHelper(
@@ -238,9 +268,13 @@
 void SafeBrowsingUrlCheckerImpl::ProcessUrls() {
   DCHECK(CurrentlyOnThread(ThreadID::IO));
   DCHECK_NE(STATE_BLOCKED, state_);
+  if (!base::FeatureList::IsEnabled(kDelayedWarnings)) {
+    DCHECK_NE(STATE_DELAYED_BLOCKING_PAGE, state_);
+  }
 
   if (state_ == STATE_CHECKING_URL ||
-      state_ == STATE_DISPLAYING_BLOCKING_PAGE) {
+      state_ == STATE_DISPLAYING_BLOCKING_PAGE ||
+      state_ == STATE_DELAYED_BLOCKING_PAGE) {
     return;
   }
 
diff --git a/components/safe_browsing/core/browser/safe_browsing_url_checker_impl.h b/components/safe_browsing/core/browser/safe_browsing_url_checker_impl.h
index 5e4f087..2840117f 100644
--- a/components/safe_browsing/core/browser/safe_browsing_url_checker_impl.h
+++ b/components/safe_browsing/core/browser/safe_browsing_url_checker_impl.h
@@ -14,6 +14,7 @@
 #include "components/safe_browsing/core/common/safe_browsing_url_checker.mojom.h"
 #include "components/safe_browsing/core/db/database_manager.h"
 #include "components/safe_browsing/core/proto/realtimeapi.pb.h"
+#include "components/security_interstitials/core/unsafe_resource.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "net/http/http_request_headers.h"
 #include "url/gurl.h"
@@ -183,11 +184,19 @@
 
   void SetWebUIToken(int token);
 
+  security_interstitials::UnsafeResource MakeUnsafeResource(
+      const GURL& url,
+      SBThreatType threat_type,
+      const ThreatMetadata& metadata);
+
   enum State {
     // Haven't started checking or checking is complete.
     STATE_NONE,
     // We have one outstanding URL-check.
     STATE_CHECKING_URL,
+    // A warning must be shown, but it's delayed because of the Delayed Warnings
+    // experiment.
+    STATE_DELAYED_BLOCKING_PAGE,
     // We're displaying a blocking page.
     STATE_DISPLAYING_BLOCKING_PAGE,
     // The blocking page has returned *not* to proceed.
diff --git a/components/safe_browsing/core/browser/url_checker_delegate.h b/components/safe_browsing/core/browser/url_checker_delegate.h
index 1cbb75f..4482a5a 100644
--- a/components/safe_browsing/core/browser/url_checker_delegate.h
+++ b/components/safe_browsing/core/browser/url_checker_delegate.h
@@ -50,6 +50,12 @@
       bool is_main_frame,
       bool has_user_gesture) = 0;
 
+  // Starts observing user input events to display a SafeBrowsing interstitial
+  // page when an event is received.
+  virtual void StartObservingInteractionsForDelayedBlockingPageHelper(
+      const security_interstitials::UnsafeResource& resource,
+      bool is_main_frame) = 0;
+
   // A whitelisted URL is considered safe and therefore won't be checked with
   // the SafeBrowsing database.
   virtual bool IsUrlWhitelisted(const GURL& url) = 0;
diff --git a/components/safe_browsing/core/features.cc b/components/safe_browsing/core/features.cc
index e29b0f19..3c3b57c8 100644
--- a/components/safe_browsing/core/features.cc
+++ b/components/safe_browsing/core/features.cc
@@ -41,6 +41,9 @@
 const base::Feature kContentComplianceEnabled{
     "SafeBrowsingContentComplianceEnabled", base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kDelayedWarnings{"SafeBrowsingDelayedWarnings",
+                                     base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kDownloadRequestWithToken{
     "SafeBrowsingDownloadRequestWithToken", base::FEATURE_ENABLED_BY_DEFAULT};
 
@@ -135,6 +138,7 @@
     {&kAdSamplerTriggerFeature, false},
     {&kCaptureInlineJavascriptForGoogleAds, true},
     {&kCaptureSafetyNetId, true},
+    {&kDelayedWarnings, true},
     {&kCommittedSBInterstitials, true},
     {&kContentComplianceEnabled, true},
     {&kDownloadRequestWithToken, true},
diff --git a/components/safe_browsing/core/features.h b/components/safe_browsing/core/features.h
index be2f38c..89a4ad1 100644
--- a/components/safe_browsing/core/features.h
+++ b/components/safe_browsing/core/features.h
@@ -120,6 +120,9 @@
 // Controls whether Chrome uses new download warning UX.
 extern const base::Feature kUseNewDownloadWarnings;
 
+// Controls whether the delayed warning experiment is enabled.
+extern const base::Feature kDelayedWarnings;
+
 base::ListValue GetFeatureStatusList();
 
 // Returns whether or not to stop filling in the SyncAccountType and
diff --git a/components/security_interstitials/core/unsafe_resource.cc b/components/security_interstitials/core/unsafe_resource.cc
index 65234d1..2a75b2a 100644
--- a/components/security_interstitials/core/unsafe_resource.cc
+++ b/components/security_interstitials/core/unsafe_resource.cc
@@ -12,7 +12,8 @@
     : is_subresource(false),
       is_subframe(false),
       threat_type(safe_browsing::SB_THREAT_TYPE_SAFE),
-      threat_source(safe_browsing::ThreatSource::UNKNOWN) {}
+      threat_source(safe_browsing::ThreatSource::UNKNOWN),
+      is_delayed_warning(false) {}
 
 UnsafeResource::UnsafeResource(const UnsafeResource& other) = default;
 
diff --git a/components/security_interstitials/core/unsafe_resource.h b/components/security_interstitials/core/unsafe_resource.h
index 5f108a5..6283f90 100644
--- a/components/security_interstitials/core/unsafe_resource.h
+++ b/components/security_interstitials/core/unsafe_resource.h
@@ -59,6 +59,10 @@
   // |token| field is only set if |threat_type| is
   // SB_THREAT_TYPE_*_PASSWORD_REUSE.
   std::string token;
+
+  // If true, this UnsafeResource is created because of the Delayed Warnings
+  // experiment.
+  bool is_delayed_warning;
 };
 
 }  // namespace security_interstitials
diff --git a/components/send_tab_to_self/features.cc b/components/send_tab_to_self/features.cc
index bd1df36d..a601490 100644
--- a/components/send_tab_to_self/features.cc
+++ b/components/send_tab_to_self/features.cc
@@ -12,6 +12,9 @@
 const base::Feature kSendTabToSelfBroadcast{"SendTabToSelfBroadcast",
                                             base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kSendTabToSelfOmniboxSendingAnimation{
+    "SendTabToSelfOmniboxSendingAnimation", base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kSendTabToSelfWhenSignedIn{
     "SendTabToSelfWhenSignedIn", base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/components/send_tab_to_self/features.h b/components/send_tab_to_self/features.h
index 2689d2a2..50cf8a4 100644
--- a/components/send_tab_to_self/features.h
+++ b/components/send_tab_to_self/features.h
@@ -15,6 +15,10 @@
 // targeted to a specific device. This only affects the receiving side.
 extern const base::Feature kSendTabToSelfBroadcast;
 
+// If this feature is enabled, the Sending... animation will show in the omnibox
+// instead of sending Desktop OS notifications for contextual menu entry points.
+extern const base::Feature kSendTabToSelfOmniboxSendingAnimation;
+
 // If this feature is enabled, we will use signed-in, ephemeral data rather than
 // persistent sync data. Users who are signed in can use the feature regardless
 // of whether they have the sync feature enabled.
diff --git a/components/ukm/ukm_recorder_impl.cc b/components/ukm/ukm_recorder_impl.cc
index 94b712f..6b507d49 100644
--- a/components/ukm/ukm_recorder_impl.cc
+++ b/components/ukm/ukm_recorder_impl.cc
@@ -48,7 +48,8 @@
   return GetSourceIdType(source_id) == SourceIdType::NAVIGATION_ID ||
          GetSourceIdType(source_id) == SourceIdType::APP_ID ||
          GetSourceIdType(source_id) == SourceIdType::HISTORY_ID ||
-         GetSourceIdType(source_id) == SourceIdType::WEBAPK_ID;
+         GetSourceIdType(source_id) == SourceIdType::WEBAPK_ID ||
+         GetSourceIdType(source_id) == SourceIdType::PAYMENT_APP_ID;
 }
 
 // Gets the maximum number of Sources we'll keep in memory before discarding any
@@ -322,7 +323,8 @@
     // entries are logged only at source creation time.
     if (GetSourceIdType(kv.first) == base::UkmSourceId::Type::APP_ID ||
         GetSourceIdType(kv.first) == base::UkmSourceId::Type::HISTORY_ID ||
-        GetSourceIdType(kv.first) == base::UkmSourceId::Type::WEBAPK_ID) {
+        GetSourceIdType(kv.first) == base::UkmSourceId::Type::WEBAPK_ID ||
+        GetSourceIdType(kv.first) == SourceIdType::PAYMENT_APP_ID) {
       MarkSourceForDeletion(kv.first);
     }
     // If the source id is not whitelisted, don't send it unless it has
diff --git a/components/ukm/ukm_recorder_impl.h b/components/ukm/ukm_recorder_impl.h
index f5eeaa2..7c8e27b 100644
--- a/components/ukm/ukm_recorder_impl.h
+++ b/components/ukm/ukm_recorder_impl.h
@@ -136,6 +136,7 @@
   FRIEND_TEST_ALL_PREFIXES(UkmRecorderImplTest, IsSampledIn);
   FRIEND_TEST_ALL_PREFIXES(UkmRecorderImplTest, PurgeExtensionRecordings);
   FRIEND_TEST_ALL_PREFIXES(UkmRecorderImplTest, WebApkSourceUrl);
+  FRIEND_TEST_ALL_PREFIXES(UkmRecorderImplTest, PaymentAppScopeUrl);
 
   struct MetricAggregate {
     uint64_t total_count = 0;
diff --git a/components/ukm/ukm_recorder_impl_unittest.cc b/components/ukm/ukm_recorder_impl_unittest.cc
index 8794294a..9c1c397e 100644
--- a/components/ukm/ukm_recorder_impl_unittest.cc
+++ b/components/ukm/ukm_recorder_impl_unittest.cc
@@ -125,4 +125,22 @@
   EXPECT_EQ(SourceIdType::WEBAPK_ID, GetSourceIdType(id));
 }
 
+TEST(UkmRecorderImplTest, PaymentAppScopeUrl) {
+  base::test::TaskEnvironment env;
+  ukm::TestAutoSetUkmRecorder test_ukm_recorder;
+
+  GURL url("https://bobpay.com");
+  SourceId id = UkmRecorderImpl::GetSourceIdForPaymentAppFromScope(url);
+
+  ASSERT_NE(kInvalidSourceId, id);
+
+  const auto& sources = test_ukm_recorder.GetSources();
+  ASSERT_EQ(1ul, sources.size());
+  auto it = sources.find(id);
+  ASSERT_NE(sources.end(), it);
+  EXPECT_EQ(url, it->second->url());
+  EXPECT_EQ(1u, it->second->urls().size());
+  EXPECT_EQ(SourceIdType::PAYMENT_APP_ID, GetSourceIdType(id));
+}
+
 }  // namespace ukm
diff --git a/components/ukm/ukm_service_unittest.cc b/components/ukm/ukm_service_unittest.cc
index 903c6e85..994b2a7 100644
--- a/components/ukm/ukm_service_unittest.cc
+++ b/components/ukm/ukm_service_unittest.cc
@@ -1397,39 +1397,45 @@
   service.EnableRecording(/*extensions=*/false);
   service.EnableReporting();
 
-  // Seed some dummy sources.
-  SourceId id0 = ConvertToSourceId(0, SourceIdType::UKM);
-  recorder.UpdateSourceURL(id0, GURL("https://www.example0.com/"));
-  SourceId id1 =
+  // Seed some fake sources.
+  SourceId ukm_id = ConvertToSourceId(0, SourceIdType::UKM);
+  recorder.UpdateSourceURL(ukm_id, GURL("https://www.example0.com/"));
+  SourceId navigation_id =
       ConvertSourceIdToWhitelistedType(1, SourceIdType::NAVIGATION_ID);
-  recorder.UpdateSourceURL(id1, GURL("https://www.example1.com/"));
-  SourceId id2 = ConvertSourceIdToWhitelistedType(2, SourceIdType::APP_ID);
-  recorder.UpdateSourceURL(id2, GURL("https://www.example2.com/"));
-  SourceId id3 = ConvertSourceIdToWhitelistedType(3, SourceIdType::HISTORY_ID);
-  recorder.UpdateSourceURL(id3, GURL("https://www.example3.com/"));
-  SourceId id4 = ConvertSourceIdToWhitelistedType(4, SourceIdType::WEBAPK_ID);
-  recorder.UpdateSourceURL(id4, GURL("https://www.example3.com/"));
+  recorder.UpdateSourceURL(navigation_id, GURL("https://www.example1.com/"));
+  SourceId app_id = ConvertSourceIdToWhitelistedType(2, SourceIdType::APP_ID);
+  recorder.UpdateSourceURL(app_id, GURL("https://www.example2.com/"));
+  SourceId history_id =
+      ConvertSourceIdToWhitelistedType(3, SourceIdType::HISTORY_ID);
+  recorder.UpdateSourceURL(history_id, GURL("https://www.example3.com/"));
+  SourceId webapk_id =
+      ConvertSourceIdToWhitelistedType(4, SourceIdType::WEBAPK_ID);
+  recorder.UpdateSourceURL(webapk_id, GURL("https://www.example4.com/"));
+  SourceId payment_app_id =
+      ConvertSourceIdToWhitelistedType(5, SourceIdType::PAYMENT_APP_ID);
+  recorder.UpdateSourceURL(payment_app_id, GURL("https://www.example5.com/"));
 
   service.Flush();
   int logs_count = 0;
   EXPECT_EQ(++logs_count, GetPersistedLogCount());
 
-  // All sources are present except id0 of non-whitelisted UKM type.
+  // All sources are present except ukm_id of non-whitelisted UKM type.
   Report proto_report = GetPersistedReport();
-  ASSERT_EQ(4, proto_report.sources_size());
-  EXPECT_EQ(id1, proto_report.sources(0).id());
-  EXPECT_EQ(id2, proto_report.sources(1).id());
-  EXPECT_EQ(id3, proto_report.sources(2).id());
-  EXPECT_EQ(id4, proto_report.sources(3).id());
+  ASSERT_EQ(5, proto_report.sources_size());
+  EXPECT_EQ(navigation_id, proto_report.sources(0).id());
+  EXPECT_EQ(app_id, proto_report.sources(1).id());
+  EXPECT_EQ(history_id, proto_report.sources(2).id());
+  EXPECT_EQ(webapk_id, proto_report.sources(3).id());
+  EXPECT_EQ(payment_app_id, proto_report.sources(4).id());
 
   service.Flush();
   EXPECT_EQ(++logs_count, GetPersistedLogCount());
 
-  // Sources of APP_ID, HISTORY_ID and WEBAPK_ID types are not kept between
-  // reporting cycles, thus only 1 navigation type source remains.
+  // Sources of APP_ID, HISTORY_ID, WEBAPK_ID and PAYMENT_APP_ID types are not
+  // kept between reporting cycles, thus only 1 navigation type source remains.
   proto_report = GetPersistedReport();
   ASSERT_EQ(1, proto_report.sources_size());
-  EXPECT_EQ(id1, proto_report.sources(0).id());
+  EXPECT_EQ(navigation_id, proto_report.sources(0).id());
 }
 
 }  // namespace ukm
diff --git a/components/viz/common/display/renderer_settings.h b/components/viz/common/display/renderer_settings.h
index a27d84c..8bde479 100644
--- a/components/viz/common/display/renderer_settings.h
+++ b/components/viz/common/display/renderer_settings.h
@@ -43,9 +43,6 @@
 
   int slow_down_compositing_scale_factor = 1;
 
-  // The required minimum size for DrawQuad to apply Draw Occlusion on.
-  gfx::Size kMinimumDrawOcclusionSize = gfx::Size(60, 60);
-
   // The maximum number of occluding Rects to track during occlusion culling.
   int kMaximumOccluderComplexity = 10;
 
diff --git a/components/viz/service/display/display.cc b/components/viz/service/display/display.cc
index 71e0e4a..e7c7b5d 100644
--- a/components/viz/service/display/display.cc
+++ b/components/viz/service/display/display.cc
@@ -928,10 +928,6 @@
   cc::Region occlusion_in_target_space;
   cc::Region backdrop_filters_in_target_space;
   bool current_sqs_intersects_occlusion = false;
-  int minimum_draw_occlusion_height =
-      settings_.kMinimumDrawOcclusionSize.height() * device_scale_factor_;
-  int minimum_draw_occlusion_width =
-      settings_.kMinimumDrawOcclusionSize.width() * device_scale_factor_;
 
   base::flat_map<RenderPassId, gfx::Rect> backdrop_filter_rects;
   for (const auto& pass : frame->render_pass_list) {
@@ -969,9 +965,7 @@
     }
     // Also skip quad if the DrawQuad size is smaller than the
     // kMinimumDrawOcclusionSize; or the DrawQuad is inside a 3d object.
-    if ((quad->visible_rect.width() <= minimum_draw_occlusion_width &&
-         quad->visible_rect.height() <= minimum_draw_occlusion_height) ||
-        quad->shared_quad_state->sorting_context_id != 0) {
+    if (quad->shared_quad_state->sorting_context_id != 0) {
       ++quad;
       continue;
     }
diff --git a/components/viz/service/display/display_unittest.cc b/components/viz/service/display/display_unittest.cc
index 5b51257c..951829a 100644
--- a/components/viz/service/display/display_unittest.cc
+++ b/components/viz/service/display/display_unittest.cc
@@ -913,7 +913,6 @@
 
   RendererSettings settings;
   settings.minimum_fragments_reduced = 0;
-  settings.kMinimumDrawOcclusionSize.set_width(0);
   SetUpGpuDisplay(settings);
   StubDisplayClient client;
   display_->Initialize(&client, manager_.surface_manager());
@@ -1214,7 +1213,6 @@
 // defined by |rects[2]| will not be occluded (removed).
 TEST_F(DisplayTest, DrawOcclusionWithSingleOverlapBehindDisjointedDrawQuads) {
   RendererSettings settings;
-  settings.kMinimumDrawOcclusionSize.set_width(0);
   SetUpGpuDisplay(settings);
 
   StubDisplayClient client;
@@ -1272,7 +1270,6 @@
 // defined by |rects[2]| will not be occluded (removed).
 TEST_F(DisplayTest, DrawOcclusionWithMultipleOverlapBehindDisjointedDrawQuads) {
   RendererSettings settings;
-  settings.kMinimumDrawOcclusionSize.set_width(0);
   SetUpGpuDisplay(settings);
 
   StubDisplayClient client;
@@ -1328,7 +1325,6 @@
 // Check if draw occlusion removes DrawQuads that are not shown on screen.
 TEST_F(DisplayTest, CompositorFrameWithOverlapDrawQuad) {
   RendererSettings settings;
-  settings.kMinimumDrawOcclusionSize.set_width(0);
   SetUpGpuDisplay(settings);
 
   StubDisplayClient client;
@@ -1456,197 +1452,9 @@
   TearDownDisplay();
 }
 
-// Check if draw occlusion is not applied on DrawQuads that are smaller than
-// skip_rect size, such that DrawQuads that are smaller than the |skip_rect|
-// are drawn on the screen regardless is shown or not.
-TEST_F(DisplayTest, DrawOcclusionWithSkipRect) {
-  SetUpGpuDisplay(RendererSettings());
-
-  StubDisplayClient client;
-  display_->Initialize(&client, manager_.surface_manager());
-
-  CompositorFrame frame = MakeDefaultCompositorFrame();
-  gfx::Rect more_then_minimum_size(
-      RendererSettings().kMinimumDrawOcclusionSize);
-  more_then_minimum_size.set_width(more_then_minimum_size.width() + 1);
-
-  gfx::Rect minimum_size(RendererSettings().kMinimumDrawOcclusionSize);
-
-  gfx::Rect less_than_minimum_size(
-      RendererSettings().kMinimumDrawOcclusionSize);
-  less_than_minimum_size.set_width(more_then_minimum_size.width() - 1);
-  less_than_minimum_size.set_height(more_then_minimum_size.height() - 1);
-
-  gfx::Rect rect(0, 0, 100, 100);
-
-  bool is_clipped = false;
-  bool are_contents_opaque = true;
-  float opacity = 1.f;
-  SharedQuadState* shared_quad_state =
-      frame.render_pass_list.front()->CreateAndAppendSharedQuadState();
-  auto* quad = frame.render_pass_list.front()
-                   ->quad_list.AllocateAndConstruct<SolidColorDrawQuad>();
-  SharedQuadState* shared_quad_state2 =
-      frame.render_pass_list.front()->CreateAndAppendSharedQuadState();
-  auto* quad2 = frame.render_pass_list.front()
-                    ->quad_list.AllocateAndConstruct<SolidColorDrawQuad>();
-  // A small rect is hiding behind the bigger rect (|rect|), same picture for
-  // the following 3 tests.
-  // rects structure:         show on screen:
-  // +----+---+               +--------+
-  // |    |   |               |        |
-  // |----+   |               |        |
-  // |        |               |        |
-  // +--------+               +--------+
-  {
-    shared_quad_state->SetAll(gfx::Transform(), rect, rect, gfx::RRectF(), rect,
-                              is_clipped, are_contents_opaque, opacity,
-                              SkBlendMode::kSrcOver, 0);
-    shared_quad_state2->SetAll(
-        gfx::Transform(), more_then_minimum_size, more_then_minimum_size,
-        gfx::RRectF(), more_then_minimum_size, is_clipped, are_contents_opaque,
-        opacity, SkBlendMode::kSrcOver, 0);
-    quad->SetNew(shared_quad_state, rect, rect, SK_ColorBLACK, false);
-    quad2->SetNew(shared_quad_state2, more_then_minimum_size,
-                  more_then_minimum_size, SK_ColorBLACK, false);
-
-    EXPECT_EQ(2u, frame.render_pass_list.front()->quad_list.size());
-    display_->RemoveOverdrawQuads(&frame);
-
-    // |more_then_minimum_size| rect is not shown on screen. Since its size is
-    // slightly larger than the skip_rect size, draw occlusion is applied on
-    // |more_then_minimum_size| and it's removed from the compositor frame.
-    EXPECT_EQ(1u, frame.render_pass_list.front()->quad_list.size());
-    EXPECT_EQ(rect.ToString(), frame.render_pass_list.front()
-                                   ->quad_list.ElementAt(0)
-                                   ->visible_rect.ToString());
-  }
-
-  {
-    shared_quad_state->SetAll(gfx::Transform(), rect, rect, gfx::RRectF(), rect,
-                              is_clipped, are_contents_opaque, opacity,
-                              SkBlendMode::kSrcOver, 0);
-    shared_quad_state2->SetAll(gfx::Transform(), minimum_size, minimum_size,
-                               gfx::RRectF(), minimum_size, is_clipped,
-                               are_contents_opaque, opacity,
-                               SkBlendMode::kSrcOver, 0);
-    quad2 = frame.render_pass_list.front()
-                ->quad_list.AllocateAndConstruct<SolidColorDrawQuad>();
-    quad->SetNew(shared_quad_state, rect, rect, SK_ColorBLACK, false);
-    quad2->SetNew(shared_quad_state2, minimum_size, minimum_size, SK_ColorBLACK,
-                  false);
-
-    EXPECT_EQ(2u, frame.render_pass_list.front()->quad_list.size());
-    display_->RemoveOverdrawQuads(&frame);
-
-    // |minimum_size| rect is not shown on screen. Since its size is the same
-    // as skip_rect size, draw occlusion is not applied on this rect.  So it is
-    // not removed from compositor frame.
-    EXPECT_EQ(2u, frame.render_pass_list.front()->quad_list.size());
-    EXPECT_EQ(rect.ToString(), frame.render_pass_list.front()
-                                   ->quad_list.ElementAt(0)
-                                   ->visible_rect.ToString());
-    EXPECT_EQ(minimum_size.ToString(), frame.render_pass_list.front()
-                                           ->quad_list.ElementAt(1)
-                                           ->visible_rect.ToString());
-  }
-
-  {
-    shared_quad_state->SetAll(gfx::Transform(), rect, rect, gfx::RRectF(), rect,
-                              is_clipped, are_contents_opaque, opacity,
-                              SkBlendMode::kSrcOver, 0);
-    shared_quad_state2->SetAll(
-        gfx::Transform(), less_than_minimum_size, less_than_minimum_size,
-        gfx::RRectF(), less_than_minimum_size, is_clipped, are_contents_opaque,
-        opacity, SkBlendMode::kSrcOver, 0);
-    quad->SetNew(shared_quad_state, rect, rect, SK_ColorBLACK, false);
-    quad2->SetNew(shared_quad_state2, less_than_minimum_size,
-                  less_than_minimum_size, SK_ColorBLACK, false);
-
-    EXPECT_EQ(2u, frame.render_pass_list.front()->quad_list.size());
-    display_->RemoveOverdrawQuads(&frame);
-
-    // |less_than_minimum_size| rect is not shown on screen. Since its size is
-    // less than skip_rect size, draw occlusion is not applied on this rect.
-    // So it is not removed from compositor frame.
-    EXPECT_EQ(2u, frame.render_pass_list.front()->quad_list.size());
-    EXPECT_EQ(rect.ToString(), frame.render_pass_list.front()
-                                   ->quad_list.ElementAt(0)
-                                   ->visible_rect.ToString());
-    EXPECT_EQ(less_than_minimum_size.ToString(), frame.render_pass_list.front()
-                                                     ->quad_list.ElementAt(1)
-                                                     ->visible_rect.ToString());
-  }
-
-  TearDownDisplay();
-}
-
-// Check if draw occlusion is not applied on DrawQuads that are smaller than
-// skip_rect size, such that DrawQuads that are smaller than the |skip_rect|
-// cannot occlude other quads behind it.
-TEST_F(DisplayTest, OcclusionIgnoringSkipRect) {
-  SetUpGpuDisplay(RendererSettings());
-
-  StubDisplayClient client;
-  display_->Initialize(&client, manager_.surface_manager());
-
-  CompositorFrame frame = MakeDefaultCompositorFrame();
-  gfx::Rect rect1(0, 0, 50, 50);
-  gfx::Rect rect2(50, 0, 50, 50);
-  gfx::Rect rect3(0, 0, 50, 90);
-
-  bool is_clipped = false;
-  bool are_contents_opaque = true;
-  float opacity = 1.f;
-  SharedQuadState* shared_quad_state =
-      frame.render_pass_list.front()->CreateAndAppendSharedQuadState();
-  auto* quad = frame.render_pass_list.front()
-                   ->quad_list.AllocateAndConstruct<SolidColorDrawQuad>();
-  SharedQuadState* shared_quad_state2 =
-      frame.render_pass_list.front()->CreateAndAppendSharedQuadState();
-  auto* quad2 = frame.render_pass_list.front()
-                    ->quad_list.AllocateAndConstruct<SolidColorDrawQuad>();
-  SharedQuadState* shared_quad_state3 =
-      frame.render_pass_list.front()->CreateAndAppendSharedQuadState();
-  auto* quad3 = frame.render_pass_list.front()
-                    ->quad_list.AllocateAndConstruct<SolidColorDrawQuad>();
-
-  shared_quad_state->SetAll(gfx::Transform(), rect1, rect1, gfx::RRectF(),
-                            rect1, is_clipped, are_contents_opaque, opacity,
-                            SkBlendMode::kSrcOver, 0);
-  shared_quad_state2->SetAll(gfx::Transform(), rect2, rect2, gfx::RRectF(),
-                             rect2, is_clipped, are_contents_opaque, opacity,
-                             SkBlendMode::kSrcOver, 0);
-  shared_quad_state3->SetAll(gfx::Transform(), rect3, rect3, gfx::RRectF(),
-                             rect3, is_clipped, are_contents_opaque, opacity,
-                             SkBlendMode::kSrcOver, 0);
-  quad->SetNew(shared_quad_state, rect1, rect1, SK_ColorBLACK, false);
-  quad2->SetNew(shared_quad_state2, rect2, rect2, SK_ColorBLACK, false);
-  quad3->SetNew(shared_quad_state3, rect3, rect3, SK_ColorBLACK, false);
-
-  EXPECT_EQ(3u, frame.render_pass_list.front()->quad_list.size());
-  display_->RemoveOverdrawQuads(&frame);
-
-  // |quad3| is not shown on screen because is hiding behind the occlusion rect
-  // formed by |quad1| and |quad2|. Since the |visible_rect| in both |quad1|
-  // and |quad2| are smaller than the skip rect, they cannot be used to occlude
-  // |quad3|. So no draw quad is removed in compositor frame by draw occlusion.
-  EXPECT_EQ(3u, frame.render_pass_list.front()->quad_list.size());
-  EXPECT_EQ(rect1.ToString(), frame.render_pass_list.front()
-                                  ->quad_list.ElementAt(0)
-                                  ->visible_rect.ToString());
-  EXPECT_EQ(rect2.ToString(), frame.render_pass_list.front()
-                                  ->quad_list.ElementAt(1)
-                                  ->visible_rect.ToString());
-  EXPECT_EQ(rect3.ToString(), frame.render_pass_list.front()
-                                  ->quad_list.ElementAt(2)
-                                  ->visible_rect.ToString());
-  TearDownDisplay();
-}
 // Check if draw occlusion works well with scale change transformer.
 TEST_F(DisplayTest, CompositorFrameWithTransformer) {
   RendererSettings settings;
-  settings.kMinimumDrawOcclusionSize.set_width(0);
   SetUpGpuDisplay(settings);
 
   StubDisplayClient client;
@@ -2153,7 +1961,6 @@
 //  +-----+                                  +----+     reduced weight
 TEST_F(DisplayTest, CompositorFrameWithRotation) {
   RendererSettings settings;
-  settings.kMinimumDrawOcclusionSize.set_width(0);
   SetUpGpuDisplay(settings);
 
   StubDisplayClient client;
@@ -2282,7 +2089,6 @@
 // preserves 2d axis alignment.
 TEST_F(DisplayTest, CompositorFrameWithPerspective) {
   RendererSettings settings;
-  settings.kMinimumDrawOcclusionSize.set_width(0);
   SetUpGpuDisplay(settings);
 
   StubDisplayClient client;
@@ -2358,7 +2164,6 @@
 // Check if draw occlusion works with transparent DrawQuads.
 TEST_F(DisplayTest, CompositorFrameWithOpacityChange) {
   RendererSettings settings;
-  settings.kMinimumDrawOcclusionSize.set_width(0);
   SetUpGpuDisplay(settings);
 
   StubDisplayClient client;
@@ -2425,7 +2230,6 @@
 
 TEST_F(DisplayTest, CompositorFrameWithOpaquenessChange) {
   RendererSettings settings;
-  settings.kMinimumDrawOcclusionSize.set_width(0);
   SetUpGpuDisplay(settings);
 
   StubDisplayClient client;
@@ -2493,7 +2297,6 @@
 // Test if draw occlusion skips 3d objects. https://crbug.com/833748
 TEST_F(DisplayTest, CompositorFrameZTranslate) {
   RendererSettings settings;
-  settings.kMinimumDrawOcclusionSize.set_width(0);
   SetUpGpuDisplay(settings);
 
   StubDisplayClient client;
@@ -2548,7 +2351,6 @@
 
 TEST_F(DisplayTest, CompositorFrameWithTranslateTransformer) {
   RendererSettings settings;
-  settings.kMinimumDrawOcclusionSize.set_width(0);
   SetUpGpuDisplay(settings);
 
   StubDisplayClient client;
@@ -2669,7 +2471,6 @@
 
 TEST_F(DisplayTest, CompositorFrameWithCombinedSharedQuadState) {
   RendererSettings settings;
-  settings.kMinimumDrawOcclusionSize.set_width(0);
   SetUpGpuDisplay(settings);
 
   StubDisplayClient client;
@@ -2797,7 +2598,6 @@
 
 TEST_F(DisplayTest, CompositorFrameWithMultipleRenderPass) {
   RendererSettings settings;
-  settings.kMinimumDrawOcclusionSize.set_width(0);
   SetUpGpuDisplay(settings);
 
   StubDisplayClient client;
@@ -2942,7 +2742,6 @@
 
 TEST_F(DisplayTest, CompositorFrameWithClip) {
   RendererSettings settings;
-  settings.kMinimumDrawOcclusionSize.set_width(0);
   SetUpGpuDisplay(settings);
 
   StubDisplayClient client;
@@ -3059,7 +2858,6 @@
 // Check if draw occlusion works with copy requests in root RenderPass only.
 TEST_F(DisplayTest, CompositorFrameWithCopyRequest) {
   RendererSettings settings;
-  settings.kMinimumDrawOcclusionSize.set_width(0);
   SetUpGpuDisplay(settings);
 
   StubDisplayClient client;
@@ -3107,7 +2905,6 @@
 
 TEST_F(DisplayTest, CompositorFrameWithRenderPass) {
   RendererSettings settings;
-  settings.kMinimumDrawOcclusionSize.set_width(0);
   SetUpGpuDisplay(settings);
 
   StubDisplayClient client;
@@ -3287,7 +3084,6 @@
 
 TEST_F(DisplayTest, CompositorFrameWithMultipleDrawQuadInSharedQuadState) {
   RendererSettings settings;
-  settings.kMinimumDrawOcclusionSize.set_width(0);
   SetUpGpuDisplay(settings);
 
   StubDisplayClient client;
@@ -3465,7 +3261,6 @@
 
 TEST_F(DisplayTest, CompositorFrameWithNonInvertibleTransform) {
   RendererSettings settings;
-  settings.kMinimumDrawOcclusionSize.set_width(0);
   SetUpGpuDisplay(settings);
 
   StubDisplayClient client;
diff --git a/components/viz/service/frame_sinks/video_capture/OWNERS b/components/viz/service/frame_sinks/video_capture/OWNERS
index e837b5f..12fd84a 100644
--- a/components/viz/service/frame_sinks/video_capture/OWNERS
+++ b/components/viz/service/frame_sinks/video_capture/OWNERS
@@ -1,3 +1,4 @@
 miu@chromium.org
+mfoltz@chromium.org
 
 # COMPONENT: Internals>Media>ScreenCapture
diff --git a/components/viz/service/main/BUILD.gn b/components/viz/service/main/BUILD.gn
index 9fa3198..6e0b1a9 100644
--- a/components/viz/service/main/BUILD.gn
+++ b/components/viz/service/main/BUILD.gn
@@ -33,6 +33,7 @@
     "//services/metrics/public/cpp:metrics_cpp",
     "//services/metrics/public/mojom",
     "//services/service_manager/public/cpp",
+    "//services/tracing/public/cpp",
     "//services/viz/privileged/mojom",
     "//ui/gfx:memory_buffer",
     "//ui/gl/init",
diff --git a/components/viz/service/main/DEPS b/components/viz/service/main/DEPS
index ace31d4..0075f301 100644
--- a/components/viz/service/main/DEPS
+++ b/components/viz/service/main/DEPS
@@ -16,6 +16,7 @@
   "+services/metrics/public",
   "+services/network/public/mojom",
   "+services/service_manager/public/cpp",
+  "+services/tracing/public/cpp",
   "+services/viz/privileged/mojom",
 ]
 
diff --git a/components/viz/service/main/viz_compositor_thread_runner_impl.cc b/components/viz/service/main/viz_compositor_thread_runner_impl.cc
index 1d710a7..58a329b 100644
--- a/components/viz/service/main/viz_compositor_thread_runner_impl.cc
+++ b/components/viz/service/main/viz_compositor_thread_runner_impl.cc
@@ -27,6 +27,7 @@
 #include "gpu/ipc/command_buffer_task_executor.h"
 #include "gpu/ipc/scheduler_sequence.h"
 #include "gpu/ipc/service/gpu_memory_buffer_factory.h"
+#include "services/tracing/public/cpp/stack_sampling/tracing_sampler_profiler.h"
 #include "ui/gfx/switches.h"
 
 #if BUILDFLAG(USE_VIZ_DEVTOOLS)
@@ -74,6 +75,12 @@
 #endif  // !defined(OS_MACOSX)
 
   CHECK(thread->StartWithOptions(thread_options));
+
+  // Setup tracing sampler profiler as early as possible.
+  thread->task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&tracing::TracingSamplerProfiler::CreateOnChildThread));
+
   return thread;
 #endif  // !defined(OS_ANDROID)
 }
diff --git a/content/browser/accessibility/cross_platform_accessibility_browsertest.cc b/content/browser/accessibility/cross_platform_accessibility_browsertest.cc
index 5867f144..454ad740 100644
--- a/content/browser/accessibility/cross_platform_accessibility_browsertest.cc
+++ b/content/browser/accessibility/cross_platform_accessibility_browsertest.cc
@@ -1161,5 +1161,59 @@
   EXPECT_EQ(text->GetId(), anchor_waiter.event_target_id());
 }
 
+IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest,
+                       IFrameContentHadFocus_ThenRootDocumentGainedFocus) {
+  // Start by loading a document with iframes.
+  LoadInitialAccessibilityTreeFromHtmlFilePath(
+      "/accessibility/html/iframe-padding.html");
+  WaitForAccessibilityTreeToContainNodeWithName(shell()->web_contents(),
+                                                "Second Button");
+
+  // Get the root BrowserAccessibilityManager and BrowserAccessibility node.
+  BrowserAccessibilityManager* root_accessibility_manager = GetManager();
+  ASSERT_NE(nullptr, root_accessibility_manager);
+  BrowserAccessibility* root_browser_accessibility =
+      root_accessibility_manager->GetRoot();
+  ASSERT_NE(nullptr, root_browser_accessibility);
+  ASSERT_EQ(ax::mojom::Role::kRootWebArea,
+            root_browser_accessibility->GetRole());
+
+  // Focus the button within the iframe.
+  {
+    BrowserAccessibility* leaf_iframe_browser_accessibility =
+        root_browser_accessibility->InternalDeepestLastChild();
+    ASSERT_NE(nullptr, leaf_iframe_browser_accessibility);
+    ASSERT_EQ(ax::mojom::Role::kIframe,
+              leaf_iframe_browser_accessibility->GetRole());
+    BrowserAccessibility* second_iframe_root_browser_accessibility =
+        leaf_iframe_browser_accessibility->PlatformGetChild(0);
+    ASSERT_NE(nullptr, second_iframe_root_browser_accessibility);
+    ASSERT_EQ(ax::mojom::Role::kRootWebArea,
+              second_iframe_root_browser_accessibility->GetRole());
+    BrowserAccessibility* second_button = FindNodeByRole(
+        second_iframe_root_browser_accessibility, ax::mojom::Role::kButton);
+    ASSERT_NE(nullptr, second_button);
+
+    AccessibilityNotificationWaiter waiter(
+        shell()->web_contents(), ui::kAXModeComplete, ax::mojom::Event::kFocus);
+    second_iframe_root_browser_accessibility->manager()->SetFocus(
+        *second_button);
+    waiter.WaitForNotification();
+    ASSERT_EQ(second_button, root_accessibility_manager->GetFocus());
+  }
+
+  // Focusing the root Document should cause the iframe content to blur.
+  // The Document Element becomes implicitly focused when the focus is cleared,
+  // so there will not be a focus event.
+  {
+    AccessibilityNotificationWaiter waiter(
+        shell()->web_contents(), ui::kAXModeComplete, ax::mojom::Event::kBlur);
+    root_accessibility_manager->SetFocus(*root_browser_accessibility);
+    waiter.WaitForNotification();
+    ASSERT_EQ(root_browser_accessibility,
+              root_accessibility_manager->GetFocus());
+  }
+}
+
 #endif
 }  // namespace content
diff --git a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
index 6094d85..a2866ad 100644
--- a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
@@ -2357,17 +2357,16 @@
 }
 
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
-                       DisplayLockingActivatableActivated) {
-  RunDisplayLockingTest(FILE_PATH_LITERAL("activatable-activated.html"));
-}
-
-IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
                        DisplayLockingNonActivatable) {
   RunDisplayLockingTest(FILE_PATH_LITERAL("non-activatable.html"));
 }
 
-// crbug.com/1043480: disabled due to flakiness.
-IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, DISABLED_DisplayLockingAll) {
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
+                       DisplayLockingViewportActivation) {
+  RunDisplayLockingTest(FILE_PATH_LITERAL("viewport-activation.html"));
+}
+
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, DisplayLockingAll) {
   RunDisplayLockingTest(FILE_PATH_LITERAL("all.html"));
 }
 
diff --git a/content/browser/media/OWNERS b/content/browser/media/OWNERS
index b0460a9..67e2794 100644
--- a/content/browser/media/OWNERS
+++ b/content/browser/media/OWNERS
@@ -1,6 +1,7 @@
 file://media/OWNERS
 olka@chromium.org
 miu@chromium.org
+mfoltz@chromium.org
 
 per-file media_devices_*=guidou@chromium.org
 per-file midi_*=toyoshim@chromium.org
diff --git a/content/browser/media/capture/OWNERS b/content/browser/media/capture/OWNERS
index e909a05..8275bb0 100644
--- a/content/browser/media/capture/OWNERS
+++ b/content/browser/media/capture/OWNERS
@@ -1,4 +1,5 @@
 miu@chromium.org
+mfoltz@chromium.org
 sergeyu@chromium.org
 wez@chromium.org
 
diff --git a/content/browser/payments/payment_app_provider_impl.cc b/content/browser/payments/payment_app_provider_impl.cc
index 02678e3..3b9a9bfc 100644
--- a/content/browser/payments/payment_app_provider_impl.cc
+++ b/content/browser/payments/payment_app_provider_impl.cc
@@ -984,6 +984,12 @@
   return true;
 }
 
+ukm::SourceId PaymentAppProviderImpl::GetSourceIdForPaymentAppFromScope(
+    const GURL& sw_scope) {
+  return ukm::UkmRecorder::GetSourceIdForPaymentAppFromScope(
+      sw_scope.GetOrigin());
+}
+
 PaymentAppProviderImpl::PaymentAppProviderImpl() = default;
 
 PaymentAppProviderImpl::~PaymentAppProviderImpl() = default;
diff --git a/content/browser/payments/payment_app_provider_impl.h b/content/browser/payments/payment_app_provider_impl.h
index b6d2a44..d1db238 100644
--- a/content/browser/payments/payment_app_provider_impl.h
+++ b/content/browser/payments/payment_app_provider_impl.h
@@ -62,6 +62,8 @@
                                     const GURL& sw_js_url,
                                     const GURL& sw_scope,
                                     std::string* error_message) override;
+  ukm::SourceId GetSourceIdForPaymentAppFromScope(
+      const GURL& sw_scope) override;
 
  private:
   PaymentAppProviderImpl();
diff --git a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
index ae198a21..b02b9ed 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
@@ -77,7 +77,6 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/aura/client/aura_constants.h"
-#include "ui/aura/client/screen_position_client.h"
 #include "ui/aura/client/window_parenting_client.h"
 #include "ui/aura/env.h"
 #include "ui/aura/layout_manager.h"
@@ -1001,11 +1000,9 @@
 // Checks that a popup is positioned correctly relative to its parent using
 // screen coordinates.
 TEST_F(RenderWidgetHostViewAuraTest, PositionChildPopup) {
-  wm::DefaultScreenPositionClient screen_position_client;
-
   aura::Window* window = parent_view_->GetNativeView();
-  aura::Window* root = window->GetRootWindow();
-  aura::client::SetScreenPositionClient(root, &screen_position_client);
+  wm::DefaultScreenPositionClient screen_position_client(
+      window->GetRootWindow());
 
   parent_view_->SetBounds(gfx::Rect(10, 10, 800, 600));
   gfx::Rect bounds_in_screen = parent_view_->GetViewBounds();
@@ -1033,8 +1030,6 @@
   view_->SetSize(gfx::Size(120, 120));
   gfx::Point new_origin = window->bounds().origin();
   EXPECT_EQ(original_origin.ToString(), new_origin.ToString());
-
-  aura::client::SetScreenPositionClient(root, nullptr);
 }
 
 // Checks that moving parent sends new screen bounds.
@@ -4843,8 +4838,7 @@
       view_->GetNativeView(), parent_view_->GetNativeView()->GetRootWindow(),
       gfx::Rect());
   aura::Window* root_window = parent_view_->GetNativeView()->GetRootWindow();
-  wm::DefaultScreenPositionClient screen_position_client;
-  aura::client::SetScreenPositionClient(root_window, &screen_position_client);
+  wm::DefaultScreenPositionClient screen_position_client(root_window);
 
   const gfx::Rect orig_view_bounds = gfx::Rect(0, 300, 400, 200);
   const gfx::Rect shifted_view_bounds = gfx::Rect(0, 200, 400, 200);
@@ -4872,8 +4866,6 @@
 
   // Window should be restored.
   EXPECT_EQ(view_->GetNativeView()->bounds(), orig_view_bounds);
-
-  aura::client::SetScreenPositionClient(root_window, nullptr);
 }
 #endif  // defined(OS_CHROMEOS)
 
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index 5be521e..f0ae355b 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -2017,8 +2017,16 @@
   DISALLOW_COPY_AND_ASSIGN(ScrollObserver);
 };
 
+// crbug.com/825629
+#if defined(OS_ANDROID)
+#define MAYBE_ScrollBubblingFromNestedOOPIFTest \
+  DISABLED_ScrollBubblingFromNestedOOPIFTest
+#else
+#define MAYBE_ScrollBubblingFromNestedOOPIFTest \
+  ScrollBubblingFromNestedOOPIFTest
+#endif
 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
-                       ScrollBubblingFromNestedOOPIFTest) {
+                       MAYBE_ScrollBubblingFromNestedOOPIFTest) {
   ui::GestureConfiguration::GetInstance()->set_scroll_debounce_interval_in_ms(
       0);
   GURL main_url(embedded_test_server()->GetURL(
diff --git a/content/browser/webauth/authenticator_common.cc b/content/browser/webauth/authenticator_common.cc
index 27587d8..5b791c0 100644
--- a/content/browser/webauth/authenticator_common.cc
+++ b/content/browser/webauth/authenticator_common.cc
@@ -9,10 +9,8 @@
 #include <utility>
 #include <vector>
 
-#include "base/base64url.h"
 #include "base/bind.h"
 #include "base/command_line.h"
-#include "base/json/json_writer.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/rand_util.h"
@@ -319,6 +317,10 @@
   auto common_info = blink::mojom::CommonCredentialInfo::New();
   common_info->client_data_json.assign(client_data_json.begin(),
                                        client_data_json.end());
+  if (response_data.android_client_data_ext()) {
+    DCHECK(base::FeatureList::IsEnabled(device::kWebAuthPhoneSupport));
+    common_info->client_data_json = *response_data.android_client_data_ext();
+  }
   common_info->raw_id = response_data.raw_credential_id();
   common_info->id = response_data.GetId();
   response->info = std::move(common_info);
@@ -381,6 +383,10 @@
   auto common_info = blink::mojom::CommonCredentialInfo::New();
   common_info->client_data_json.assign(client_data_json.begin(),
                                        client_data_json.end());
+  if (response_data.android_client_data_ext()) {
+    DCHECK(base::FeatureList::IsEnabled(device::kWebAuthPhoneSupport));
+    common_info->client_data_json = *response_data.android_client_data_ext();
+  }
   common_info->raw_id = response_data.raw_credential_id();
   common_info->id = response_data.GetId();
   response->info = std::move(common_info);
@@ -397,56 +403,6 @@
   return response;
 }
 
-std::string Base64UrlEncode(const base::span<const uint8_t> input) {
-  std::string ret;
-  base::Base64UrlEncode(
-      base::StringPiece(reinterpret_cast<const char*>(input.data()),
-                        input.size()),
-      base::Base64UrlEncodePolicy::OMIT_PADDING, &ret);
-  return ret;
-}
-
-// ToJSONString encodes |in| as a JSON string, using the specific escaping rules
-// required by https://github.com/w3c/webauthn/pull/1375.
-std::string ToJSONString(const std::string& in) {
-  std::string ret;
-  ret.reserve(in.size() + 2);
-  ret.push_back('"');
-
-  const char* const in_bytes = in.data();
-  // ICU uses |int32_t| for lengths.
-  const int32_t length = base::checked_cast<int32_t>(in.size());
-  int32_t offset = 0;
-
-  while (offset < length) {
-    const int32_t prior_offset = offset;
-    // Input strings must be valid UTF-8.
-    uint32_t codepoint;
-    CHECK(base::ReadUnicodeCharacter(in_bytes, length, &offset, &codepoint));
-    // offset is updated by |ReadUnicodeCharacter| to index the last byte of the
-    // codepoint. Increment it to index the first byte of the next codepoint for
-    // the subsequent iteration.
-    offset++;
-
-    if (codepoint == 0x20 || codepoint == 0x21 ||
-        (codepoint >= 0x23 && codepoint <= 0x5b) || codepoint >= 0x5d) {
-      ret.append(&in_bytes[prior_offset], &in_bytes[offset]);
-    } else if (codepoint == 0x22) {
-      ret.append("\\\"");
-    } else if (codepoint == 0x5c) {
-      ret.append("\\\\");
-    } else {
-      static const char hextable[17] = "0123456789abcdef";
-      ret.append("\\u00");
-      ret.push_back(hextable[codepoint >> 4]);
-      ret.push_back(hextable[codepoint & 15]);
-    }
-  }
-
-  ret.push_back('"');
-  return ret;
-}
-
 bool IsUserVerifyingPlatformAuthenticatorAvailableImpl(
     AuthenticatorRequestClientDelegate* delegate,
     device::FidoDiscoveryFactory* discovery_factory,
@@ -706,49 +662,6 @@
 }
 
 // static
-std::string AuthenticatorCommon::SerializeCollectedClientDataToJson(
-    const std::string& type,
-    const std::string& origin,
-    base::span<const uint8_t> challenge,
-    bool is_cross_origin,
-    bool use_legacy_u2f_type_key /* = false */) {
-  std::string ret;
-  ret.reserve(128);
-
-  if (use_legacy_u2f_type_key) {
-    ret.append(R"({"typ":)");
-  } else {
-    ret.append(R"({"type":)");
-  }
-  ret.append(ToJSONString(type));
-
-  ret.append(R"(,"challenge":)");
-  ret.append(ToJSONString(Base64UrlEncode(challenge)));
-
-  ret.append(R"(,"origin":)");
-  ret.append(ToJSONString(origin));
-
-  if (is_cross_origin) {
-    ret.append(R"(,"crossOrigin":true)");
-  } else {
-    ret.append(R"(,"crossOrigin":false)");
-  }
-
-  if (base::RandDouble() < 0.2) {
-    // An extra key is sometimes added to ensure that RPs do not make
-    // unreasonably specific assumptions about the clientData JSON. This is
-    // done in the fashion of
-    // https://tools.ietf.org/html/draft-ietf-tls-grease
-    ret.append(R"(,"extra_keys_may_be_added_here":")");
-    ret.append(
-        "do not compare clientDataJSON against a template. See "
-        "https://goo.gl/yabPex\"");
-  }
-
-  ret.append("}");
-  return ret;
-}
-
 // mojom::Authenticator
 void AuthenticatorCommon::MakeCredential(
     url::Origin caller_origin,
@@ -903,23 +816,17 @@
       WebAuthRequestSecurityChecker::OriginIsCryptoTokenExtension(
           caller_origin_);
 
-  // Save client data to return with the authenticator response.
-  // TODO(kpaulhamus): Fetch and add the Channel ID/Token Binding ID public key
-  // used to communicate with the origin.
-  if (origin_is_crypto_token_extension) {
-    // As Cryptotoken validates the origin, accept the relying party id as the
-    // origin from requests originating from Cryptotoken. The origin is provided
-    // in Cryptotoken requests as the relying party name, which should be used
-    // as part of client data.
-    client_data_json_ = SerializeCollectedClientDataToJson(
-        client_data::kU2fRegisterType, *options->relying_party.name,
-        std::move(options->challenge), /*is_cross_origin=*/false,
-        /*use_legacy_u2f_type_key=*/true);
-  } else {
-    client_data_json_ = SerializeCollectedClientDataToJson(
-        client_data::kCreateType, caller_origin_.Serialize(),
-        std::move(options->challenge), is_cross_origin);
-  }
+  // Cryptotoken provides the sender origin for register requests in the
+  // |relying_party| |name| attribute. (The |id| attribute contains the AppID.)
+  client_data_json_ =
+      origin_is_crypto_token_extension
+          ? device::SerializeCollectedClientDataToJson(
+                client_data::kU2fRegisterType, *options->relying_party.name,
+                options->challenge, /*is_cross_origin=*/false,
+                /*use_legacy_u2f_type_key=*/true)
+          : device::SerializeCollectedClientDataToJson(
+                client_data::kCreateType, caller_origin_.Serialize(),
+                options->challenge, is_cross_origin);
 
   // Cryptotoken requests should be proxied without UI.
   if (origin_is_crypto_token_extension || disable_ui_)
@@ -940,6 +847,18 @@
   // On dual protocol CTAP2/U2F devices, force credential creation over U2F.
   ctap_make_credential_request_->is_u2f_only = origin_is_crypto_token_extension;
 
+  if (base::FeatureList::IsEnabled(device::kWebAuthPhoneSupport) &&
+      !origin_is_crypto_token_extension && !is_cross_origin) {
+    // Send the unhashed origin and challenge to caBLEv2 authenticators, because
+    // the Android API requires them. It does not accept clientDataJSON or its
+    // hash.
+    // NOTE: Because Android has no way of building a clientDataJSON for
+    // cross-origin requests, we don't create the extension for those. This
+    // problem will go away once we add clientDataHash inputs to Android.
+    ctap_make_credential_request_->android_client_data_ext.emplace(
+        client_data::kCreateType, caller_origin_, options->challenge);
+  }
+
   // Compute the effective attestation conveyance preference and set
   // |attestation_requested_| for showing the attestation consent prompt later.
   ::device::AttestationConveyancePreference attestation = options->attestation;
@@ -1035,19 +954,17 @@
       WebAuthRequestSecurityChecker::OriginIsCryptoTokenExtension(
           caller_origin_);
 
-  // Save client data to return with the authenticator response.
-  if (origin_is_crypto_token_extension) {
-    // As Cryptotoken validates the origin, accept the relying party id as the
-    // origin from requests originating from Cryptotoken.
-    client_data_json_ = SerializeCollectedClientDataToJson(
-        client_data::kU2fSignType, options->relying_party_id,
-        std::move(options->challenge), /*is_cross_origin=*/false,
-        /*use_legacy_u2f_type_key=*/true);
-  } else {
-    client_data_json_ = SerializeCollectedClientDataToJson(
-        client_data::kGetType, caller_origin_.Serialize(),
-        std::move(options->challenge), is_cross_origin);
-  }
+  // Cryptotoken provides the sender origin for U2F sign requests in the
+  // |relying_party_id| attribute.
+  client_data_json_ =
+      origin_is_crypto_token_extension
+          ? device::SerializeCollectedClientDataToJson(
+                client_data::kU2fSignType, options->relying_party_id,
+                options->challenge, /*is_cross_origin=*/false,
+                /*use_legacy_u2f_type_key=*/true)
+          : device::SerializeCollectedClientDataToJson(
+                client_data::kGetType, caller_origin_.Serialize(),
+                options->challenge, is_cross_origin);
 
   // Cryptotoken requests should be proxied without UI.
   if (origin_is_crypto_token_extension || disable_ui_)
@@ -1087,6 +1004,17 @@
       client_data_json_, std::move(options), app_id_,
       browser_context()->IsOffTheRecord());
   ctap_get_assertion_request_->is_u2f_only = origin_is_crypto_token_extension;
+  if (base::FeatureList::IsEnabled(device::kWebAuthPhoneSupport) &&
+      !origin_is_crypto_token_extension && !is_cross_origin) {
+    // Send the unhashed origin and challenge to caBLEv2 authenticators, because
+    // the Android API requires them. It does not accept clientDataJSON or its
+    // hash.
+    // NOTE: Because Android has no way of building a clientDataJSON for
+    // cross-origin requests, we don't create the extension for those. This
+    // problem will go away once we add clientDataHash inputs to Android.
+    ctap_get_assertion_request_->android_client_data_ext.emplace(
+        client_data::kGetType, caller_origin_, options->challenge);
+  }
 
   StartGetAssertionRequest(/*allow_skipping_pin_touch=*/true);
 }
@@ -1273,7 +1201,7 @@
         InvokeCallbackAndCleanup(
             std::move(make_credential_response_callback_),
             blink::mojom::AuthenticatorStatus::SUCCESS,
-            CreateMakeCredentialResponse(std::move(client_data_json_),
+            CreateMakeCredentialResponse(client_data_json_,
                                          std::move(*response_data),
                                          *attestation_erasure),
             Focus::kDoCheck);
@@ -1337,12 +1265,12 @@
     attestation_erasure = AttestationErasureOption::kEraseAttestationAndAaguid;
   }
 
-  InvokeCallbackAndCleanup(std::move(make_credential_response_callback_),
-                           blink::mojom::AuthenticatorStatus::SUCCESS,
-                           CreateMakeCredentialResponse(
-                               std::move(client_data_json_),
-                               std::move(response_data), attestation_erasure),
-                           Focus::kDoCheck);
+  InvokeCallbackAndCleanup(
+      std::move(make_credential_response_callback_),
+      blink::mojom::AuthenticatorStatus::SUCCESS,
+      CreateMakeCredentialResponse(client_data_json_, std::move(response_data),
+                                   attestation_erasure),
+      Focus::kDoCheck);
 }
 
 void AuthenticatorCommon::OnSignResponse(
@@ -1462,8 +1390,8 @@
   InvokeCallbackAndCleanup(
       std::move(get_assertion_response_callback_),
       blink::mojom::AuthenticatorStatus::SUCCESS,
-      CreateGetAssertionResponse(std::move(client_data_json_),
-                                 std::move(response), echo_appid_extension));
+      CreateGetAssertionResponse(client_data_json_, std::move(response),
+                                 echo_appid_extension));
   return;
 }
 
diff --git a/content/browser/webauth/authenticator_common.h b/content/browser/webauth/authenticator_common.h
index 2ef69eb..c6f9c05 100644
--- a/content/browser/webauth/authenticator_common.h
+++ b/content/browser/webauth/authenticator_common.h
@@ -22,6 +22,7 @@
 #include "device/fido/authenticator_get_assertion_response.h"
 #include "device/fido/authenticator_make_credential_response.h"
 #include "device/fido/authenticator_selection_criteria.h"
+#include "device/fido/client_data.h"
 #include "device/fido/ctap_get_assertion_request.h"
 #include "device/fido/ctap_make_credential_request.h"
 #include "device/fido/fido_constants.h"
@@ -114,20 +115,6 @@
 
   bool IsFocused() const;
 
-  // Builds the CollectedClientData[1] dictionary with the given values,
-  // serializes it to JSON, and returns the resulting string. For legacy U2F
-  // requests coming from the CryptoToken U2F extension, modifies the object key
-  // 'type' as required[2].
-  // [1] https://w3c.github.io/webauthn/#dictdef-collectedclientdata
-  // [2]
-  // https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html#client-data
-  static std::string SerializeCollectedClientDataToJson(
-      const std::string& type,
-      const std::string& origin,
-      base::span<const uint8_t> challenge,
-      bool is_cross_origin,
-      bool use_legacy_u2f_type_key = false);
-
   // Callback to handle the async response from a U2fDevice.
   void OnRegisterResponse(
       device::MakeCredentialStatus status_code,
diff --git a/content/browser/webauth/authenticator_impl_unittest.cc b/content/browser/webauth/authenticator_impl_unittest.cc
index 80977e7..1f201ecf 100644
--- a/content/browser/webauth/authenticator_impl_unittest.cc
+++ b/content/browser/webauth/authenticator_impl_unittest.cc
@@ -466,7 +466,7 @@
   }
 
   std::string GetTestClientDataJSON(std::string type) {
-    return AuthenticatorCommon::SerializeCollectedClientDataToJson(
+    return device::SerializeCollectedClientDataToJson(
         std::move(type), GetTestOrigin().Serialize(), GetTestChallengeBytes(),
         /*is_cross_origin*/ false);
   }
@@ -475,8 +475,8 @@
                                       const std::string& origin,
                                       base::span<const uint8_t> challenge,
                                       bool is_cross_origin) {
-    return AuthenticatorCommon::SerializeCollectedClientDataToJson(
-        type, origin, challenge, is_cross_origin);
+    return device::SerializeCollectedClientDataToJson(type, origin, challenge,
+                                                      is_cross_origin);
   }
 
   AuthenticatorStatus TryAuthenticationWithAppId(const std::string& origin,
diff --git a/content/common/content_navigation_policy.cc b/content/common/content_navigation_policy.cc
index da00bec..aa1f35b 100644
--- a/content/common/content_navigation_policy.cc
+++ b/content/common/content_navigation_policy.cc
@@ -88,12 +88,7 @@
 }
 
 std::string GetRenderDocumentLevelName(RenderDocumentLevel level) {
-  for (size_t i = 0; i < render_document_level.option_count; ++i) {
-    if (level == render_document_level.options[i].value)
-      return render_document_level.options[i].name;
-  }
-  NOTREACHED();
-  return "";
+  return render_document_level.GetName(level);
 }
 
 bool CreateNewHostForSameSiteSubframe() {
diff --git a/content/public/browser/payment_app_provider.h b/content/public/browser/payment_app_provider.h
index 140ccc587..ef29f48 100644
--- a/content/public/browser/payment_app_provider.h
+++ b/content/public/browser/payment_app_provider.h
@@ -13,6 +13,7 @@
 #include "base/callback_forward.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/stored_payment_app.h"
+#include "services/metrics/public/cpp/ukm_recorder.h"
 #include "third_party/blink/public/mojom/payments/payment_app.mojom.h"
 
 class SkBitmap;
@@ -103,6 +104,11 @@
                                             const GURL& sw_scope,
                                             std::string* error_message) = 0;
 
+  // Gets the ukm source id for a payment app with |sw_scope|.
+  // This must ONLY be called when payment app window has been opened.
+  virtual ukm::SourceId GetSourceIdForPaymentAppFromScope(
+      const GURL& sw_scope) = 0;
+
  protected:
   virtual ~PaymentAppProvider() = default;
 };
diff --git a/content/public/renderer/render_frame_observer.h b/content/public/renderer/render_frame_observer.h
index 156ba73..172b2a2 100644
--- a/content/public/renderer/render_frame_observer.h
+++ b/content/public/renderer/render_frame_observer.h
@@ -152,6 +152,9 @@
   // Notifications when |PerformanceTiming| data becomes available
   virtual void DidChangePerformanceTiming() {}
 
+  // Notifications When an input delay data becomes available.
+  virtual void DidObserveInputDelay(base::TimeDelta input_delay) {}
+
   // Notifications when a cpu timing update becomes available, when a frame
   // has performed at least 100ms of tasks.
   virtual void DidChangeCpuTiming(base::TimeDelta time) {}
diff --git a/content/renderer/media/OWNERS b/content/renderer/media/OWNERS
index cefa19db..43747118 100644
--- a/content/renderer/media/OWNERS
+++ b/content/renderer/media/OWNERS
@@ -1,5 +1,6 @@
 file://media/OWNERS
 miu@chromium.org
+mfoltz@chromium.org
 
 # WebRTC OWNERS.
 hbos@chromium.org
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index e964089..1d37c33 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -4867,6 +4867,11 @@
     observer.DidChangePerformanceTiming();
 }
 
+void RenderFrameImpl::DidObserveInputDelay(base::TimeDelta input_delay) {
+  for (auto& observer : observers_) {
+    observer.DidObserveInputDelay(input_delay);
+  }
+}
 void RenderFrameImpl::DidChangeCpuTiming(base::TimeDelta time) {
   for (auto& observer : observers_)
     observer.DidChangeCpuTiming(time);
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index ac3e90f..3b58f29d 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -735,6 +735,7 @@
   void DidDisplayContentWithCertificateErrors() override;
   void DidRunContentWithCertificateErrors() override;
   void DidChangePerformanceTiming() override;
+  void DidObserveInputDelay(base::TimeDelta input_delay) override;
   void DidChangeCpuTiming(base::TimeDelta time) override;
   void DidObserveLoadingBehavior(blink::LoadingBehaviorFlag behavior) override;
   void DidObserveNewFeatureUsage(blink::mojom::WebFeature feature) override;
diff --git a/content/renderer/render_frame_impl_browsertest.cc b/content/renderer/render_frame_impl_browsertest.cc
index 01fa265..0e5dc9fb9 100644
--- a/content/renderer/render_frame_impl_browsertest.cc
+++ b/content/renderer/render_frame_impl_browsertest.cc
@@ -526,8 +526,6 @@
   gfx::Point viewport_offset(7, -11);
   blink::WebRect viewport_intersection(0, 11, 200, 89);
 
-  // TODO(crbug/1062006): Change to viewport_offset when blink-side changes are
-  // relanded with the chromeos-thinlto fix.
   blink::WebRect mainframe_intersection(0, 0, 200, 140);
   blink::FrameOcclusionState occlusion_state =
       blink::FrameOcclusionState::kUnknown;
@@ -537,7 +535,8 @@
   frame_widget()->OnMessageReceived(set_viewport_intersection_message);
   // Setting a new frame intersection in a local frame triggers the render frame
   // observer call.
-  EXPECT_EQ(observer.last_intersection_rect(), blink::WebRect(0, 0, 200, 140));
+  EXPECT_EQ(observer.last_intersection_rect(),
+            blink::WebRect(7, -11, 200, 140));
 }
 
 // Used to annotate the source of an interface request.
diff --git a/content/shell/browser/shell_platform_data_aura.cc b/content/shell/browser/shell_platform_data_aura.cc
index 71e741c5..9f1d6fa 100644
--- a/content/shell/browser/shell_platform_data_aura.cc
+++ b/content/shell/browser/shell_platform_data_aura.cc
@@ -108,8 +108,7 @@
   host_->window()->Show();
   host_->window()->SetLayoutManager(new FillLayout(host_->window()));
 
-  focus_client_.reset(new aura::test::TestFocusClient());
-  aura::client::SetFocusClient(host_->window(), focus_client_.get());
+  focus_client_.reset(new aura::test::TestFocusClient(host_->window()));
 
   new wm::DefaultActivationClient(host_->window());
   capture_client_.reset(
diff --git a/content/test/data/accessibility/display-locking/activatable-activated-expected-blink.txt b/content/test/data/accessibility/display-locking/activatable-activated-expected-blink.txt
deleted file mode 100644
index 688ea53..0000000
--- a/content/test/data/accessibility/display-locking/activatable-activated-expected-blink.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-rootWebArea
-++genericContainer ignored
-++++genericContainer name='Done'
-++++++genericContainer
-++++++++genericContainer
-++++++++++staticText name='child'
-++++++++++++inlineTextBox name='child'
-++++++++genericContainer
-++++++++++staticText name='nested locked element!'
-++++++++++++inlineTextBox name='nested locked element!'
-++++++++genericContainer
diff --git a/content/test/data/accessibility/display-locking/activatable-activated.html b/content/test/data/accessibility/display-locking/activatable-activated.html
deleted file mode 100644
index 48180f6c..0000000
--- a/content/test/data/accessibility/display-locking/activatable-activated.html
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-@BLINK-ALLOW:offscreen
-@WAIT-FOR:Done
--->
-<div id=target aria-label="Working">
-  <div id="locked" style="subtree-visibility: auto">
-    <div>child</div>
-    <div id="nested" style="subtree-visibility: auto">nested locked element!</div>
-    <div id="nonActivatable" style="subtree-visibility: hidden">nested non activatable locked element</div>
-  </div>
-</div>
-
-<script>
-async function runTest() {
-  // Force layout, then activate.
-  locked.getBoundingClientRect();
-  locked.scrollIntoView();
-  // Double-rAF to ensure that both the outer and nested elements have enough
-  // time to process intersection observations.
-  requestAnimationFrame(
-    () => requestAnimationFrame(
-      () => target.setAttribute("aria-label", "Done")));
-}
-onload = () => requestAnimationFrame(runTest);
-</script>
diff --git a/content/test/data/accessibility/display-locking/activatable.html b/content/test/data/accessibility/display-locking/activatable.html
index 0690f93..f49d116 100644
--- a/content/test/data/accessibility/display-locking/activatable.html
+++ b/content/test/data/accessibility/display-locking/activatable.html
@@ -2,9 +2,9 @@
 @BLINK-ALLOW:offscreen
 -->
 <div>
-  <div id="locked" style="subtree-visibility: hidden-matchable">
+  <div id="locked" style="subtree-visibility: auto">
     <div>child</div>
-    <div id="nested" style="subtree-visibility: hidden-matchable">nested locked element!</div>
+    <div id="nested" style="subtree-visibility: auto">nested locked element!</div>
     <div id="nonActivatable" style="subtree-visibility: hidden">nested non activatable locked element</div>
   </div>
 </div>
diff --git a/content/test/data/accessibility/display-locking/all-committed-expected-blink.txt b/content/test/data/accessibility/display-locking/all-committed-expected-blink.txt
index 2db604e..851b7d1c 100644
--- a/content/test/data/accessibility/display-locking/all-committed-expected-blink.txt
+++ b/content/test/data/accessibility/display-locking/all-committed-expected-blink.txt
@@ -2,19 +2,24 @@
 ++genericContainer ignored
 ++++genericContainer ignored
 ++++++genericContainer
-++++++++genericContainer
-++++++++++staticText name='child'
-++++++++++++inlineTextBox name='child'
-++++++++genericContainer
-++++++++++staticText name='nested locked element!'
-++++++++++++inlineTextBox name='nested locked element!'
-++++++++genericContainer
-++++++++++staticText name='nested non activatable locked element'
-++++++++++++inlineTextBox name='nested non activatable locked element'
-++++++++presentational ignored
-++++++++++listItem ignored
-++++++++++++staticText name='role=presentation item'
-++++++++++++++inlineTextBox name='role=presentation item'
-++++++++genericContainer ignored invisible name='visibility:hidden text'
-++++++++genericContainer ignored invisible
-++++++++++staticText ignored invisible name='aria-hidden text'
+++++++++staticText name='spacer so that everything below will be offscreen (and won't get viewport-activated)'
+++++++++++inlineTextBox name='spacer so that everything below will be offscreen (and won't get viewport-activated)'
+++++++genericContainer offscreen
+++++++++genericContainer offscreen
+++++++++++staticText offscreen name='child text will be in AX tree but without layout'
+++++++++++++inlineTextBox offscreen name='child text will be in AX tree but without layout'
+++++++++genericContainer offscreen
+++++++++++staticText offscreen name='nested activatable locked element will be in AX tree but without layout'
+++++++++++++inlineTextBox offscreen name='nested activatable locked element will be in AX tree but without layout'
+++++++staticText offscreen name='normal text 1'
+++++++++inlineTextBox offscreen name='normal text 1'
+++++++genericContainer offscreen
+++++++++staticText offscreen name='nested non-viewport-activatable locked element will not be in AX tree'
+++++++++++inlineTextBox offscreen name='nested non-viewport-activatable locked element will not be in AX tree'
+++++++staticText offscreen name='normal text 2'
+++++++++inlineTextBox offscreen name='normal text 2'
+++++++genericContainer offscreen
+++++++++staticText offscreen name='nested non-activatable locked element will not be in AX tree'
+++++++++++inlineTextBox offscreen name='nested non-activatable locked element will not be in AX tree'
+++++++staticText offscreen name='normal text 3'
+++++++++inlineTextBox offscreen name='normal text 3'
diff --git a/content/test/data/accessibility/display-locking/all-committed.html b/content/test/data/accessibility/display-locking/all-committed.html
index 6fce397..85d6337 100644
--- a/content/test/data/accessibility/display-locking/all-committed.html
+++ b/content/test/data/accessibility/display-locking/all-committed.html
@@ -2,26 +2,28 @@
 @BLINK-ALLOW:offscreen
 -->
 <div>
-  <div id="locked" style="subtree-visibility: hidden-matchable">
-    <div>child</div>
-    <div id="nested" style="subtree-visibility: hidden-matchable">nested locked element!</div>
-    <div id="nonActivatable" style="subtree-visibility: hidden">nested non activatable locked element</div>
-    <!--
-      TODO(rakina): Make display:none, visibility:hidden, aria-hidden nodes
-      in locked subtrees get ignored for accessibility/marked invisible.
-    -->
-    <div style="display:none;">display:none text</div>
-    <ul role="presentation">
-      <li>role=presentation item</li>
-    </ul>
-    <div style="visibility:hidden;">visibility:hidden text</div>
-    <div aria-hidden="true">aria-hidden text</div>
+  <div style="height:10000px;">spacer so that everything below will be offscreen (and won't get viewport-activated)</div>
+  <div id="locked" style="subtree-visibility: auto">
+    <div>child text will be in AX tree but without layout</div>
+    <div id="nested" style="subtree-visibility: auto">
+      nested activatable locked element will be in AX tree but without layout
+    </div>
   </div>
+  normal text 1
+  <div id="nonViewportActivatable" style="subtree-visibility: hidden-matchable">
+    nested non-viewport-activatable locked element will not be in AX tree
+  </div>
+  normal text 2
+  <div id="nonActivatable" style="subtree-visibility: hidden">
+    nested non-activatable locked element will not be in AX tree
+  </div>
+  normal text 3
 </div>
 
 <script>
   // Force layout, then commit everything.
   locked.removeAttribute("style");
   nested.removeAttribute("style");
+  nonViewportActivatable.removeAttribute("style");
   nonActivatable.removeAttribute("style");
 </script>
diff --git a/content/test/data/accessibility/display-locking/all-expected-blink.txt b/content/test/data/accessibility/display-locking/all-expected-blink.txt
index 001cff1..81766c8 100644
--- a/content/test/data/accessibility/display-locking/all-expected-blink.txt
+++ b/content/test/data/accessibility/display-locking/all-expected-blink.txt
@@ -2,27 +2,22 @@
 ++genericContainer ignored
 ++++genericContainer ignored
 ++++++genericContainer
-++++++++staticText name='<newline>    '
-++++++++genericContainer
-++++++++++staticText name='child'
-++++++++staticText name='<newline>    '
-++++++++genericContainer
-++++++++++staticText name='nested locked element!'
-++++++++staticText name='<newline>    '
-++++++++genericContainer
-++++++++staticText name='<newline>    '
-++++++++staticText name='<newline>    '
-++++++++genericContainer invisible
-++++++++++staticText name='display:none text'
-++++++++staticText name='<newline>    '
-++++++++staticText name='<newline>      '
-++++++++genericContainer
-++++++++++staticText name='role=presentation item'
-++++++++staticText name='<newline>    '
-++++++++staticText name='<newline>    '
-++++++++genericContainer invisible
-++++++++++staticText name='visibility:hidden text'
-++++++++staticText name='<newline>    '
-++++++++genericContainer invisible
-++++++++++staticText invisible name='aria-hidden text'
-++++++++staticText name='<newline>  '
+++++++++staticText name='spacer so that everything below will be offscreen (and won't get viewport-activated)'
+++++++++++inlineTextBox name='spacer so that everything below will be offscreen (and won't get viewport-activated)'
+++++++genericContainer offscreen
+++++++++staticText offscreen name='<newline>    '
+++++++++genericContainer offscreen
+++++++++++staticText offscreen name='child text will be in AX tree but without layout'
+++++++++staticText offscreen name='<newline>    '
+++++++++genericContainer offscreen
+++++++++++staticText offscreen name='<newline>      nested activatable locked element will be in AX tree but without layout<newline>    '
+++++++++staticText offscreen name='<newline>  '
+++++++staticText offscreen name='normal text 1'
+++++++++inlineTextBox offscreen name='normal text 1'
+++++++genericContainer offscreen
+++++++staticText offscreen name='normal text 2'
+++++++++inlineTextBox offscreen name='normal text 2'
+++++++genericContainer offscreen
+++++++staticText offscreen name='normal text 3'
+++++++++inlineTextBox offscreen name='normal text 3'
+
diff --git a/content/test/data/accessibility/display-locking/all.html b/content/test/data/accessibility/display-locking/all.html
index ff6d166..1bc37cc 100644
--- a/content/test/data/accessibility/display-locking/all.html
+++ b/content/test/data/accessibility/display-locking/all.html
@@ -2,19 +2,20 @@
 @BLINK-ALLOW:offscreen
 -->
 <div>
-  <div id="locked" style="subtree-visibility: hidden-matchable">
-    <div>child</div>
-    <div id="nested" style="subtree-visibility: hidden-matchable">nested locked element!</div>
-    <div id="nonActivatable" style="subtree-visibility: hidden">nested non activatable locked element</div>
-    <!--
-      TODO(rakina): Make display:none, visibility:hidden, aria-hidden nodes
-      in locked subtrees get ignored for accessibility/marked invisible.
-    -->
-    <div style="display:none;">display:none text</div>
-    <ul role="presentation">
-      <li>role=presentation item</li>
-    </ul>
-    <div style="visibility:hidden;">visibility:hidden text</div>
-    <div aria-hidden="true">aria-hidden text</div>
+  <div style="height:10000px;">spacer so that everything below will be offscreen (and won't get viewport-activated)</div>
+  <div id="locked" style="subtree-visibility: auto">
+    <div>child text will be in AX tree but without layout</div>
+    <div id="nested" style="subtree-visibility: auto">
+      nested activatable locked element will be in AX tree but without layout
+    </div>
   </div>
+  normal text 1
+  <div id="nonViewportActivatable" style="subtree-visibility: hidden-matchable">
+    nested non-viewport-activatable locked element will not be in AX tree
+  </div>
+  normal text 2
+  <div id="nonActivatable" style="subtree-visibility: hidden">
+    nested non-activatable locked element will not be in AX tree
+  </div>
+  normal text 3
 </div>
diff --git a/content/test/data/accessibility/display-locking/viewport-activation-expected-blink.txt b/content/test/data/accessibility/display-locking/viewport-activation-expected-blink.txt
new file mode 100644
index 0000000..f0d1a10
--- /dev/null
+++ b/content/test/data/accessibility/display-locking/viewport-activation-expected-blink.txt
@@ -0,0 +1,21 @@
+rootWebArea
+++genericContainer ignored
+++++genericContainer ignored
+++++++genericContainer
+++++++++staticText offscreen name='initial spacer, will initially make everything below this far away from the viewport'
+++++++++++inlineTextBox offscreen name='initial spacer, will initially make everything below this far away from the viewport'
+++++++genericContainer
+++++++++staticText name='This text will get viewport-activated because it's in the viewport, and will be in AX tree with layout.'
+++++++++++inlineTextBox name='This text will get viewport-activated because it's in the viewport, and will be in AX tree with layout.'
+++++++genericContainer
+++++++++staticText name='This text is also in viewport.'
+++++++++++inlineTextBox name='This text is also in viewport.'
+++++++genericContainer
+++++++++staticText name='spacer so that everything below will be offscreen (and won't get viewport-activated)'
+++++++++++inlineTextBox name='spacer so that everything below will be offscreen (and won't get viewport-activated)'
+++++++genericContainer
+++++++++staticText offscreen name='<newline>    This text will not get viewport-activated, and will be in AX tree but without layout.<newline>  '
+++++++genericContainer offscreen
+++++++++staticText offscreen name='doneActivating'
+++++++++++inlineTextBox offscreen name='doneActivating'
+
diff --git a/content/test/data/accessibility/display-locking/viewport-activation.html b/content/test/data/accessibility/display-locking/viewport-activation.html
new file mode 100644
index 0000000..9a385aa1
--- /dev/null
+++ b/content/test/data/accessibility/display-locking/viewport-activation.html
@@ -0,0 +1,35 @@
+<!--
+@BLINK-ALLOW:offscreen
+@WAIT-FOR:doneActivating
+-->
+<div>
+  <div id="initialSpacer" style="height: 10000px;">
+    initial spacer, will initially make everything below this far away from the viewport
+  </div>
+  <div id="inViewport" style="subtree-visibility: auto">
+    This text will get viewport-activated because it's in the viewport, and will be in AX tree with layout.
+  </div>
+  <div id="alsoInViewport" style="subtree-visibility: auto">
+    This text is also in viewport.
+  </div>
+  <div style="height: 10000px;">
+    spacer so that everything below will be offscreen (and won't get viewport-activated)
+  </div>
+  <div id="wayBelowViewport" style="subtree-visibility: auto">
+    This text will not get viewport-activated, and will be in AX tree but without layout.
+  </div>
+  <div id="statusDiv"></div>
+</div>
+<script>
+  // Viewport activation happens on the next frame, so we'll wait until then before ending.
+  window.addEventListener("scroll", () => {
+    window.requestAnimationFrame(() => {
+      window.requestAnimationFrame(() => {
+        statusDiv.innerText = "doneActivating";
+      });
+    });
+  });
+  // Scroll so that #inViewport will be at the top of the viewport.
+  window.scrollTo(0, initialSpacer.offsetHeight);
+</script>
+
diff --git a/content/test/gpu/gpu_tests/skia_gold_integration_test_base.py b/content/test/gpu/gpu_tests/skia_gold_integration_test_base.py
index f72e4765..6f7d48c 100644
--- a/content/test/gpu/gpu_tests/skia_gold_integration_test_base.py
+++ b/content/test/gpu/gpu_tests/skia_gold_integration_test_base.py
@@ -171,6 +171,12 @@
       help='Don\'t use the service account provided by LUCI for authentication '
            'for Skia Gold, instead relying on gsutil to be pre-authenticated. '
            'Meant for testing locally instead of on the bots.')
+    parser.add_option(
+      '--bypass-skia-gold-functionality',
+      action='store_true', default=False,
+      help='Bypass all interaction with Skia Gold, effectively disabling the '
+           'image comparison portion of any tests that use Gold. Only meant to '
+           'be used in case a Gold outage occurs and cannot be fixed quickly.')
 
   @classmethod
   def ResetGpuInfo(cls):
@@ -375,6 +381,10 @@
       page: the GPU PixelTestPage object for the test.
       build_id_args: a list of build-identifying flags and values.
     """
+    if self.GetParsedCommandLineOptions().bypass_skia_gold_functionality:
+      logging.warning('Not actually comparing with Gold due to '
+                      '--bypass-skia-gold-functionality being present.')
+      return
     if not isinstance(build_id_args, list) or '--commit' not in build_id_args:
       raise Exception('Requires build args to be specified, including --commit')
 
diff --git a/content/test/gpu/gpu_tests/test_expectations/context_lost_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/context_lost_expectations.txt
index cfc5f2a..a013a9f 100644
--- a/content/test/gpu/gpu_tests/test_expectations/context_lost_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/context_lost_expectations.txt
@@ -34,6 +34,7 @@
 crbug.com/965268 [ android qualcomm-adreno-(tm)-418 ] ContextLost_WebGLUnblockedAfterUserInitiatedReload [ Skip ]
 crbug.com/880078 [ android ] ContextLost_WorkerRAFAfterGPUCrash [ Failure ]
 crbug.com/880078 [ android ] ContextLost_WorkerRAFAfterGPUCrash_OOPD [ Failure ]
+crbug.com/1064853 [ android qualcomm-adreno-(tm)-418 ] ContextLost_WebGLContextLostFromSelectElement [ Skip ]
 
 # Nexus 6
 # The Nexus 6 times out on these tests while waiting for the JS to complete
diff --git a/device/fido/BUILD.gn b/device/fido/BUILD.gn
index 9e9f1a6..3902a8210 100644
--- a/device/fido/BUILD.gn
+++ b/device/fido/BUILD.gn
@@ -61,6 +61,8 @@
     "cable/noise.h",
     "cable/v2_handshake.cc",
     "cable/v2_handshake.h",
+    "client_data.cc",
+    "client_data.h",
     "credential_management.cc",
     "credential_management.h",
     "credential_management_handler.cc",
diff --git a/device/fido/authenticator_get_assertion_response.h b/device/fido/authenticator_get_assertion_response.h
index 4f60316..d212879 100644
--- a/device/fido/authenticator_get_assertion_response.h
+++ b/device/fido/authenticator_get_assertion_response.h
@@ -60,6 +60,13 @@
     return num_credentials_;
   }
 
+  const base::Optional<std::vector<uint8_t>>& android_client_data_ext() const {
+    return android_client_data_ext_;
+  }
+  void set_android_client_data_ext(const std::vector<uint8_t>& data) {
+    android_client_data_ext_ = data;
+  }
+
  private:
   base::Optional<PublicKeyCredentialDescriptor> credential_;
   AuthenticatorData authenticator_data_;
@@ -67,6 +74,10 @@
   base::Optional<PublicKeyCredentialUserEntity> user_entity_;
   base::Optional<uint8_t> num_credentials_;
 
+  // If not base::nullopt, the content of the googleAndroidClientData extension
+  // authenticator output.
+  base::Optional<std::vector<uint8_t>> android_client_data_ext_;
+
   DISALLOW_COPY_AND_ASSIGN(AuthenticatorGetAssertionResponse);
 };
 
diff --git a/device/fido/authenticator_make_credential_response.cc b/device/fido/authenticator_make_credential_response.cc
index 8022cb3e..35598e58 100644
--- a/device/fido/authenticator_make_credential_response.cc
+++ b/device/fido/authenticator_make_credential_response.cc
@@ -11,6 +11,7 @@
 #include "device/fido/attestation_statement_formats.h"
 #include "device/fido/attested_credential_data.h"
 #include "device/fido/authenticator_data.h"
+#include "device/fido/client_data.h"
 #include "device/fido/ec_public_key.h"
 #include "device/fido/fido_parsing_utils.h"
 
@@ -107,6 +108,10 @@
   map.emplace(1, object.attestation_statement().format_name());
   map.emplace(2, object.authenticator_data().SerializeToByteArray());
   map.emplace(3, AsCBOR(object.attestation_statement()));
+  if (response.android_client_data_ext()) {
+    map.emplace(kAndroidClientDataExtOutputKey,
+                cbor::Value(*response.android_client_data_ext()));
+  }
   auto encoded_bytes = cbor::Writer::Write(cbor::Value(std::move(map)));
   DCHECK(encoded_bytes);
   return std::move(*encoded_bytes);
diff --git a/device/fido/authenticator_make_credential_response.h b/device/fido/authenticator_make_credential_response.h
index 050f3a4..7f232da 100644
--- a/device/fido/authenticator_make_credential_response.h
+++ b/device/fido/authenticator_make_credential_response.h
@@ -71,6 +71,13 @@
     return transport_used_;
   }
 
+  const base::Optional<std::vector<uint8_t>>& android_client_data_ext() const {
+    return android_client_data_ext_;
+  }
+  void set_android_client_data_ext(const std::vector<uint8_t>& data) {
+    android_client_data_ext_ = data;
+  }
+
  private:
   AttestationObject attestation_object_;
 
@@ -78,6 +85,10 @@
   // nullopt for cases where we cannot determine the transport (Windows).
   base::Optional<FidoTransportProtocol> transport_used_;
 
+  // If not base::nullopt, the content of the googleAndroidClientData extension
+  // authenticator output.
+  base::Optional<std::vector<uint8_t>> android_client_data_ext_;
+
   DISALLOW_COPY_AND_ASSIGN(AuthenticatorMakeCredentialResponse);
 };
 
diff --git a/device/fido/authenticator_supported_options.h b/device/fido/authenticator_supported_options.h
index c0f2938..6396ef8 100644
--- a/device/fido/authenticator_supported_options.h
+++ b/device/fido/authenticator_supported_options.h
@@ -85,6 +85,9 @@
   // Indicates whether the authenticator is capable of handling built in user
   // verification based tokens.
   bool supports_uv_token = false;
+  // Indicates whether the authenticator supports an extension for passing
+  // information from the collectedClientData structure with a CTAP request.
+  bool supports_android_client_data_ext;
 };
 
 COMPONENT_EXPORT(DEVICE_FIDO)
diff --git a/device/fido/client_data.cc b/device/fido/client_data.cc
new file mode 100644
index 0000000..5ee8a6dc
--- /dev/null
+++ b/device/fido/client_data.cc
@@ -0,0 +1,218 @@
+// Copyright 2020 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 "device/fido/client_data.h"
+
+#include "base/base64url.h"
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/rand_util.h"
+#include "base/strings/utf_string_conversion_utils.h"
+#include "components/device_event_log/device_event_log.h"
+#include "url/gurl.h"
+
+namespace device {
+
+namespace {
+
+std::string Base64UrlEncode(const base::span<const uint8_t> input) {
+  std::string ret;
+  base::Base64UrlEncode(
+      base::StringPiece(reinterpret_cast<const char*>(input.data()),
+                        input.size()),
+      base::Base64UrlEncodePolicy::OMIT_PADDING, &ret);
+  return ret;
+}
+
+// ToJSONString encodes |in| as a JSON string, using the specific escaping rules
+// required by https://github.com/w3c/webauthn/pull/1375.
+std::string ToJSONString(base::StringPiece in) {
+  std::string ret;
+  ret.reserve(in.size() + 2);
+  ret.push_back('"');
+
+  const char* const in_bytes = in.data();
+  // ICU uses |int32_t| for lengths.
+  const int32_t length = base::checked_cast<int32_t>(in.size());
+  int32_t offset = 0;
+
+  while (offset < length) {
+    const int32_t prior_offset = offset;
+    // Input strings must be valid UTF-8.
+    uint32_t codepoint;
+    CHECK(base::ReadUnicodeCharacter(in_bytes, length, &offset, &codepoint));
+    // offset is updated by |ReadUnicodeCharacter| to index the last byte of the
+    // codepoint. Increment it to index the first byte of the next codepoint for
+    // the subsequent iteration.
+    offset++;
+
+    if (codepoint == 0x20 || codepoint == 0x21 ||
+        (codepoint >= 0x23 && codepoint <= 0x5b) || codepoint >= 0x5d) {
+      ret.append(&in_bytes[prior_offset], &in_bytes[offset]);
+    } else if (codepoint == 0x22) {
+      ret.append("\\\"");
+    } else if (codepoint == 0x5c) {
+      ret.append("\\\\");
+    } else {
+      static const char hextable[17] = "0123456789abcdef";
+      ret.append("\\u00");
+      ret.push_back(hextable[codepoint >> 4]);
+      ret.push_back(hextable[codepoint & 15]);
+    }
+  }
+
+  ret.push_back('"');
+  return ret;
+}
+
+}  // namespace
+
+std::string SerializeCollectedClientDataToJson(
+    const std::string& type,
+    const std::string& origin,
+    base::span<const uint8_t> challenge,
+    bool is_cross_origin,
+    bool use_legacy_u2f_type_key /* = false */) {
+  std::string ret;
+  ret.reserve(128);
+
+  if (use_legacy_u2f_type_key) {
+    ret.append(R"({"typ":)");
+  } else {
+    ret.append(R"({"type":)");
+  }
+  ret.append(ToJSONString(type));
+
+  ret.append(R"(,"challenge":)");
+  ret.append(ToJSONString(Base64UrlEncode(challenge)));
+
+  ret.append(R"(,"origin":)");
+  ret.append(ToJSONString(origin));
+
+  if (is_cross_origin) {
+    ret.append(R"(,"crossOrigin":true)");
+  } else {
+    ret.append(R"(,"crossOrigin":false)");
+  }
+
+  if (base::RandDouble() < 0.2) {
+    // An extra key is sometimes added to ensure that RPs do not make
+    // unreasonably specific assumptions about the clientData JSON. This is
+    // done in the fashion of
+    // https://tools.ietf.org/html/draft-ietf-tls-grease
+    ret.append(R"(,"extra_keys_may_be_added_here":")");
+    ret.append(
+        "do not compare clientDataJSON against a template. See "
+        "https://goo.gl/yabPex\"");
+  }
+
+  ret.append("}");
+  return ret;
+}
+
+// static
+base::Optional<AndroidClientDataExtensionInput>
+AndroidClientDataExtensionInput::Parse(const cbor::Value& value) {
+  if (!value.is_map()) {
+    return base::nullopt;
+  }
+  const cbor::Value::MapValue& map = value.GetMap();
+  if (map.size() != 3) {
+    return base::nullopt;
+  }
+  AndroidClientDataExtensionInput ext;
+  for (const auto& pair : map) {
+    if (!pair.first.is_integer()) {
+      return base::nullopt;
+    }
+    switch (pair.first.GetInteger()) {
+      case 1:
+        if (!pair.second.is_string()) {
+          return base::nullopt;
+        }
+        ext.type = pair.second.GetString();
+        break;
+      case 2:
+        if (!pair.second.is_string()) {
+          return base::nullopt;
+        }
+        ext.origin = url::Origin::Create(GURL(pair.second.GetString()));
+        if (ext.origin.opaque() ||
+            ext.origin.Serialize() != pair.second.GetString()) {
+          return base::nullopt;
+        }
+        break;
+      case 3:
+        if (!pair.second.is_bytestring()) {
+          return base::nullopt;
+        }
+        ext.challenge = pair.second.GetBytestring();
+        break;
+      default:
+        return base::nullopt;
+    }
+  }
+  return ext;
+}
+
+AndroidClientDataExtensionInput::AndroidClientDataExtensionInput() = default;
+AndroidClientDataExtensionInput::AndroidClientDataExtensionInput(
+    std::string type_,
+    url::Origin origin_,
+    std::vector<uint8_t> challenge_)
+    : type(type_), origin(origin_), challenge(challenge_) {}
+AndroidClientDataExtensionInput::AndroidClientDataExtensionInput(
+    const AndroidClientDataExtensionInput&) = default;
+AndroidClientDataExtensionInput::AndroidClientDataExtensionInput(
+    AndroidClientDataExtensionInput&&) = default;
+
+AndroidClientDataExtensionInput& AndroidClientDataExtensionInput::operator=(
+    const AndroidClientDataExtensionInput&) = default;
+AndroidClientDataExtensionInput& AndroidClientDataExtensionInput::operator=(
+    AndroidClientDataExtensionInput&&) = default;
+
+AndroidClientDataExtensionInput::~AndroidClientDataExtensionInput() = default;
+
+cbor::Value AsCBOR(const AndroidClientDataExtensionInput& ext) {
+  cbor::Value::MapValue map;
+  map[cbor::Value(1)] = cbor::Value(ext.type);
+  map[cbor::Value(2)] = cbor::Value(ext.origin.Serialize());
+  map[cbor::Value(3)] = cbor::Value(ext.challenge);
+  return cbor::Value(map);
+}
+
+bool IsValidAndroidClientDataJSON(
+    const device::AndroidClientDataExtensionInput& extension_input,
+    base::StringPiece android_client_data_json) {
+  base::Optional<base::Value> client_data =
+      base::JSONReader::Read(android_client_data_json);
+  if (!client_data || !client_data->is_dict()) {
+    FIDO_LOG(ERROR) << "Invalid androidClientData extension: "
+                    << android_client_data_json;
+    return false;
+  }
+  const base::DictionaryValue& client_data_dict =
+      base::Value::AsDictionaryValue(*client_data);
+  std::string type;
+  std::string challenge;
+  std::string origin;
+  std::string android_package_name;
+  if (client_data_dict.size() != 4 ||
+      !client_data_dict.GetString("type", &type) ||
+      type != extension_input.type ||
+      !client_data_dict.GetString("challenge", &challenge) ||
+      challenge != Base64UrlEncode(extension_input.challenge) ||
+      !client_data_dict.GetString("origin", &origin) ||
+      origin != extension_input.origin.Serialize() ||
+      !client_data_dict.GetString("androidPackageName",
+                                  &android_package_name)) {
+    FIDO_LOG(ERROR) << "Invalid androidClientData extension: "
+                    << android_client_data_json;
+    return false;
+  }
+  return true;
+}
+
+}  // namespace device
diff --git a/device/fido/client_data.h b/device/fido/client_data.h
new file mode 100644
index 0000000..f8651733
--- /dev/null
+++ b/device/fido/client_data.h
@@ -0,0 +1,72 @@
+// Copyright 2020 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 DEVICE_FIDO_CLIENT_DATA_H_
+#define DEVICE_FIDO_CLIENT_DATA_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/containers/span.h"
+#include "base/strings/string_piece.h"
+#include "components/cbor/values.h"
+#include "url/origin.h"
+
+namespace device {
+
+// The map key for inserting the googleAndroidClientDataExtension output into a
+// CTAP2 makeCredential or getAssertion response.
+constexpr int kAndroidClientDataExtOutputKey = 0xf0;
+
+// Builds the CollectedClientData[1] dictionary with the given values,
+// serializes it to JSON, and returns the resulting string. For legacy U2F
+// requests coming from the CryptoToken U2F extension, modifies the object key
+// 'type' as required[2].
+// [1] https://w3c.github.io/webauthn/#dictdef-collectedclientdata
+// [2]
+// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html#client-data
+COMPONENT_EXPORT(DEVICE_FIDO)
+std::string SerializeCollectedClientDataToJson(
+    const std::string& type,
+    const std::string& origin,
+    base::span<const uint8_t> challenge,
+    bool is_cross_origin,
+    bool use_legacy_u2f_type_key = false);
+
+// AndroidClientDataExtensionInput contains data for an extension sent to the
+// Clank caBLEv2 authenticator that are required due to the Android FIDO API not
+// supporting the CTAP2 clientDataHash input parameter.
+struct COMPONENT_EXPORT(DEVICE_FIDO) AndroidClientDataExtensionInput {
+  static base::Optional<AndroidClientDataExtensionInput> Parse(
+      const cbor::Value& value);
+
+  AndroidClientDataExtensionInput();
+  AndroidClientDataExtensionInput(std::string type,
+                                  url::Origin origin,
+                                  std::vector<uint8_t> challenge);
+  AndroidClientDataExtensionInput(const AndroidClientDataExtensionInput&);
+  AndroidClientDataExtensionInput(AndroidClientDataExtensionInput&&);
+
+  AndroidClientDataExtensionInput& operator=(
+      const AndroidClientDataExtensionInput&);
+  AndroidClientDataExtensionInput& operator=(AndroidClientDataExtensionInput&&);
+
+  ~AndroidClientDataExtensionInput();
+
+  std::string type;
+  url::Origin origin;
+  std::vector<uint8_t> challenge;
+};
+
+cbor::Value AsCBOR(const AndroidClientDataExtensionInput& ext);
+
+bool IsValidAndroidClientDataJSON(
+    const device::AndroidClientDataExtensionInput& extension_input,
+    base::StringPiece android_client_data_json);
+
+}  // namespace device
+
+#endif  // DEVICE_FIDO_CLIENT_DATA_H_
diff --git a/device/fido/ctap_get_assertion_request.cc b/device/fido/ctap_get_assertion_request.cc
index f34010f..7224951 100644
--- a/device/fido/ctap_get_assertion_request.cc
+++ b/device/fido/ctap_get_assertion_request.cc
@@ -84,6 +84,27 @@
     request.allow_list = std::move(allow_list);
   }
 
+  const auto extensions_it = request_map.find(cbor::Value(4));
+  if (extensions_it != request_map.end()) {
+    if (!extensions_it->second.is_map()) {
+      return base::nullopt;
+    }
+
+    const cbor::Value::MapValue& extensions = extensions_it->second.GetMap();
+
+    const auto android_client_data_ext_it =
+        extensions.find(cbor::Value(device::kExtensionAndroidClientData));
+    if (android_client_data_ext_it != extensions.end()) {
+      base::Optional<AndroidClientDataExtensionInput> android_client_data_ext =
+          AndroidClientDataExtensionInput::Parse(
+              android_client_data_ext_it->second);
+      if (!android_client_data_ext) {
+        return base::nullopt;
+      }
+      request.android_client_data_ext = std::move(*android_client_data_ext);
+    }
+  }
+
   const auto option_it = request_map.find(cbor::Value(5));
   if (option_it != request_map.end()) {
     if (!option_it->second.is_map())
@@ -166,6 +187,13 @@
     cbor_map[cbor::Value(3)] = cbor::Value(std::move(allow_list_array));
   }
 
+  if (request.android_client_data_ext) {
+    cbor::Value::MapValue extensions;
+    extensions.emplace(kExtensionAndroidClientData,
+                       AsCBOR(*request.android_client_data_ext));
+    cbor_map[cbor::Value(4)] = cbor::Value(std::move(extensions));
+  }
+
   if (request.pin_auth) {
     cbor_map[cbor::Value(6)] = cbor::Value(*request.pin_auth);
   }
diff --git a/device/fido/ctap_get_assertion_request.h b/device/fido/ctap_get_assertion_request.h
index 94c1f5d1..1226606 100644
--- a/device/fido/ctap_get_assertion_request.h
+++ b/device/fido/ctap_get_assertion_request.h
@@ -17,6 +17,7 @@
 #include "base/optional.h"
 #include "crypto/sha2.h"
 #include "device/fido/cable/cable_discovery_data.h"
+#include "device/fido/client_data.h"
 #include "device/fido/fido_constants.h"
 #include "device/fido/public_key_credential_descriptor.h"
 
@@ -64,6 +65,8 @@
 
   bool is_incognito_mode = false;
   bool is_u2f_only = false;
+
+  base::Optional<AndroidClientDataExtensionInput> android_client_data_ext;
 };
 
 struct CtapGetNextAssertionRequest {};
diff --git a/device/fido/ctap_make_credential_request.cc b/device/fido/ctap_make_credential_request.cc
index 4fb4832c..8c92c10 100644
--- a/device/fido/ctap_make_credential_request.cc
+++ b/device/fido/ctap_make_credential_request.cc
@@ -112,7 +112,7 @@
       return base::nullopt;
     }
 
-    const auto& extensions = extensions_it->second.GetMap();
+    const cbor::Value::MapValue& extensions = extensions_it->second.GetMap();
     const auto hmac_secret_it =
         extensions.find(cbor::Value(kExtensionHmacSecret));
     if (hmac_secret_it != extensions.end()) {
@@ -144,6 +144,18 @@
           return base::nullopt;
       }
     }
+
+    const auto android_client_data_ext_it =
+        extensions.find(cbor::Value(device::kExtensionAndroidClientData));
+    if (android_client_data_ext_it != extensions.end()) {
+      base::Optional<AndroidClientDataExtensionInput> android_client_data_ext =
+          AndroidClientDataExtensionInput::Parse(
+              android_client_data_ext_it->second);
+      if (!android_client_data_ext) {
+        return base::nullopt;
+      }
+      request.android_client_data_ext = std::move(*android_client_data_ext);
+    }
   }
 
   const auto option_it = request_map.find(cbor::Value(7));
@@ -244,6 +256,11 @@
                        static_cast<uint8_t>(request.cred_protect->first));
   }
 
+  if (request.android_client_data_ext) {
+    extensions.emplace(kExtensionAndroidClientData,
+                       AsCBOR(*request.android_client_data_ext));
+  }
+
   if (!extensions.empty()) {
     cbor_map[cbor::Value(6)] = cbor::Value(std::move(extensions));
   }
diff --git a/device/fido/ctap_make_credential_request.h b/device/fido/ctap_make_credential_request.h
index c4416e3..0dd6c28 100644
--- a/device/fido/ctap_make_credential_request.h
+++ b/device/fido/ctap_make_credential_request.h
@@ -15,6 +15,7 @@
 #include "base/containers/span.h"
 #include "base/macros.h"
 #include "base/optional.h"
+#include "device/fido/client_data.h"
 #include "device/fido/fido_constants.h"
 #include "device/fido/public_key_credential_descriptor.h"
 #include "device/fido/public_key_credential_params.h"
@@ -83,6 +84,8 @@
   // provided by the target authenticator for the MakeCredential request to be
   // sent.
   base::Optional<std::pair<CredProtect, bool>> cred_protect;
+
+  base::Optional<AndroidClientDataExtensionInput> android_client_data_ext;
 };
 
 // Serializes MakeCredential request parameter into CBOR encoded map with
diff --git a/device/fido/device_response_converter.cc b/device/fido/device_response_converter.cc
index 1362b13..2355b0a 100644
--- a/device/fido/device_response_converter.cc
+++ b/device/fido/device_response_converter.cc
@@ -20,6 +20,8 @@
 #include "components/device_event_log/device_event_log.h"
 #include "device/fido/authenticator_data.h"
 #include "device/fido/authenticator_supported_options.h"
+#include "device/fido/client_data.h"
+#include "device/fido/features.h"
 #include "device/fido/fido_constants.h"
 #include "device/fido/opaque_attestation_statement.h"
 
@@ -88,11 +90,20 @@
   if (it == decoded_map.end() || !it->second.is_map())
     return base::nullopt;
 
-  return AuthenticatorMakeCredentialResponse(
+  AuthenticatorMakeCredentialResponse response(
       transport_used,
       AttestationObject(std::move(*authenticator_data),
                         std::make_unique<OpaqueAttestationStatement>(
                             format, it->second.Clone())));
+
+  if (base::FeatureList::IsEnabled(kWebAuthPhoneSupport)) {
+    it = decoded_map.find(CBOR(kAndroidClientDataExtOutputKey));
+    if (it != decoded_map.end() && it->second.is_bytestring()) {
+      response.set_android_client_data_ext(it->second.GetBytestring());
+    }
+  }
+
+  return response;
 }
 
 base::Optional<AuthenticatorGetAssertionResponse> ReadCTAPGetAssertionResponse(
@@ -144,7 +155,14 @@
     response.SetNumCredentials(it->second.GetUnsigned());
   }
 
-  return base::Optional<AuthenticatorGetAssertionResponse>(std::move(response));
+  if (base::FeatureList::IsEnabled(kWebAuthPhoneSupport)) {
+    it = response_map.find(CBOR(kAndroidClientDataExtOutputKey));
+    if (it != response_map.end() && it->second.is_bytestring()) {
+      response.set_android_client_data_ext(it->second.GetBytestring());
+    }
+  }
+
+  return response;
 }
 
 base::Optional<AuthenticatorGetInfoResponse> ReadCTAPGetInfoResponse(
@@ -229,6 +247,8 @@
       const std::string& extension_str = extension.GetString();
       if (extension_str == kExtensionCredProtect) {
         options.supports_cred_protect = true;
+      } else if (extension_str == kExtensionAndroidClientData) {
+        options.supports_android_client_data_ext = true;
       }
       extensions.push_back(extension_str);
     }
diff --git a/device/fido/fido_constants.cc b/device/fido/fido_constants.cc
index cd002f50..04c5af25 100644
--- a/device/fido/fido_constants.cc
+++ b/device/fido/fido_constants.cc
@@ -65,6 +65,7 @@
 
 const char kExtensionHmacSecret[] = "hmac-secret";
 const char kExtensionCredProtect[] = "credProtect";
+const char kExtensionAndroidClientData[] = "googleAndroidClientData";
 
 const base::TimeDelta kBleDevicePairingModeWaitingInterval =
     base::TimeDelta::FromSeconds(2);
diff --git a/device/fido/fido_constants.h b/device/fido/fido_constants.h
index 78c0c3f1..c64f740 100644
--- a/device/fido/fido_constants.h
+++ b/device/fido/fido_constants.h
@@ -350,6 +350,8 @@
 
 COMPONENT_EXPORT(DEVICE_FIDO) extern const char kExtensionHmacSecret[];
 COMPONENT_EXPORT(DEVICE_FIDO) extern const char kExtensionCredProtect[];
+COMPONENT_EXPORT(DEVICE_FIDO)
+extern const char kExtensionAndroidClientData[];
 
 // Maximum number of seconds the browser waits for Bluetooth authenticator to
 // send packets that advertises that the device is in pairing mode before
diff --git a/device/fido/get_assertion_request_handler.cc b/device/fido/get_assertion_request_handler.cc
index 924942406..c1067e88 100644
--- a/device/fido/get_assertion_request_handler.cc
+++ b/device/fido/get_assertion_request_handler.cc
@@ -81,7 +81,9 @@
 
 bool ResponseValid(const FidoAuthenticator& authenticator,
                    const CtapGetAssertionRequest& request,
-                   const AuthenticatorGetAssertionResponse& response) {
+                   const AuthenticatorGetAssertionResponse& response,
+                   const base::Optional<AndroidClientDataExtensionInput>&
+                       android_client_data_ext_in) {
   if (response.GetRpIdHash() !=
           fido_parsing_utils::CreateSHA256Hash(request.rp_id) &&
       (!request.app_id ||
@@ -164,6 +166,18 @@
     return false;
   }
 
+  if (response.android_client_data_ext() &&
+      (!android_client_data_ext_in || !authenticator.Options() ||
+       !authenticator.Options()->supports_android_client_data_ext ||
+       !IsValidAndroidClientDataJSON(
+           *android_client_data_ext_in,
+           base::StringPiece(reinterpret_cast<const char*>(
+                                 response.android_client_data_ext()->data()),
+                             response.android_client_data_ext()->size())))) {
+    FIDO_LOG(ERROR) << "Invalid androidClientData extension";
+    return false;
+  }
+
   return true;
 }
 
@@ -245,6 +259,13 @@
     request_.user_verification = UserVerificationRequirement::kRequired;
   }
 
+  // Only send the googleAndroidClientData extension to authenticators that
+  // support it.
+  if (request_.android_client_data_ext) {
+    android_client_data_ext_ = *request_.android_client_data_ext;
+    request_.android_client_data_ext.reset();
+  }
+
   FIDO_LOG(EVENT) << "Starting GetAssertion flow";
   Start();
 }
@@ -310,6 +331,10 @@
     } else {
       request.user_verification = UserVerificationRequirement::kDiscouraged;
     }
+    if (android_client_data_ext_ && authenticator->Options() &&
+        authenticator->Options()->supports_android_client_data_ext) {
+      request.android_client_data_ext = *android_client_data_ext_;
+    }
   }
 
   ReportGetAssertionRequestTransport(authenticator);
@@ -440,7 +465,8 @@
     return;
   }
 
-  if (!response || !ResponseValid(*authenticator, request_, *response)) {
+  if (!response || !ResponseValid(*authenticator, request_, *response,
+                                  android_client_data_ext_)) {
     FIDO_LOG(ERROR) << "Failing assertion request due to bad response from "
                     << authenticator->GetDisplayName();
     std::move(completion_callback_)
@@ -496,7 +522,8 @@
     return;
   }
 
-  if (!ResponseValid(*authenticator, request_, *response)) {
+  if (!ResponseValid(*authenticator, request_, *response,
+                     android_client_data_ext_)) {
     FIDO_LOG(ERROR) << "Failing assertion request due to bad response from "
                     << authenticator->GetDisplayName();
     std::move(completion_callback_)
@@ -734,6 +761,11 @@
   // Do not do internal UV again.
   request.user_verification = UserVerificationRequirement::kDiscouraged;
 
+  if (android_client_data_ext_ && authenticator_->Options() &&
+      authenticator_->Options()->supports_android_client_data_ext) {
+    request.android_client_data_ext = *android_client_data_ext_;
+  }
+
   ReportGetAssertionRequestTransport(authenticator_);
 
   authenticator_->GetAssertion(
diff --git a/device/fido/get_assertion_request_handler.h b/device/fido/get_assertion_request_handler.h
index 417fee5..2235627 100644
--- a/device/fido/get_assertion_request_handler.h
+++ b/device/fido/get_assertion_request_handler.h
@@ -106,6 +106,7 @@
   CompletionCallback completion_callback_;
   State state_ = State::kWaitingForTouch;
   CtapGetAssertionRequest request_;
+  base::Optional<AndroidClientDataExtensionInput> android_client_data_ext_;
   // If true, and if at the time the request is dispatched to the first
   // authenticator no other authenticators are available, the request handler
   // will skip the initial touch that is usually required to select a PIN
diff --git a/device/fido/make_credential_request_handler.cc b/device/fido/make_credential_request_handler.cc
index c8e2d09..5d4d347d 100644
--- a/device/fido/make_credential_request_handler.cc
+++ b/device/fido/make_credential_request_handler.cc
@@ -242,6 +242,13 @@
   transport_availability_info().request_type =
       FidoRequestHandlerBase::RequestType::kMakeCredential;
 
+  // Only send the googleAndroidClientData extension to authenticators that
+  // support it.
+  if (request_.android_client_data_ext) {
+    android_client_data_ext_ = *request_.android_client_data_ext;
+    request_.android_client_data_ext.reset();
+  }
+
   // Set the rk, uv and attachment fields, which were only initialized to
   // default values up to here.  TODO(martinkr): Initialize these fields earlier
   // (in AuthenticatorImpl) and get rid of the separate
@@ -363,6 +370,10 @@
         !authenticator->Options()->supports_cred_protect) {
       request.cred_protect.reset();
     }
+    if (android_client_data_ext_ && authenticator->Options() &&
+        authenticator->Options()->supports_android_client_data_ext) {
+      request.android_client_data_ext = *android_client_data_ext_;
+    }
   }
 
   ReportMakeCredentialRequestTransport(authenticator);
@@ -488,6 +499,21 @@
     return;
   }
 
+  if (response->android_client_data_ext() &&
+      (!android_client_data_ext_ || !authenticator->Options() ||
+       !authenticator->Options()->supports_android_client_data_ext ||
+       !IsValidAndroidClientDataJSON(
+           *android_client_data_ext_,
+           base::StringPiece(reinterpret_cast<const char*>(
+                                 response->android_client_data_ext()->data()),
+                             response->android_client_data_ext()->size())))) {
+    FIDO_LOG(ERROR) << "Invalid androidClientData extension";
+    std::move(completion_callback_)
+        .Run(MakeCredentialStatus::kAuthenticatorResponseInvalid, base::nullopt,
+             authenticator);
+    return;
+  }
+
   if (authenticator->AuthenticatorTransport()) {
     base::UmaHistogramEnumeration(
         "WebAuthentication.MakeCredentialResponseTransport",
@@ -762,6 +788,10 @@
       !authenticator_->Options()->supports_cred_protect) {
     request.cred_protect.reset();
   }
+  if (android_client_data_ext_ && authenticator_->Options() &&
+      authenticator_->Options()->supports_android_client_data_ext) {
+    request.android_client_data_ext = *android_client_data_ext_;
+  }
 
   ReportMakeCredentialRequestTransport(authenticator_);
 
diff --git a/device/fido/make_credential_request_handler.h b/device/fido/make_credential_request_handler.h
index 4cdf5b2..459a4c0c 100644
--- a/device/fido/make_credential_request_handler.h
+++ b/device/fido/make_credential_request_handler.h
@@ -16,6 +16,7 @@
 #include "base/sequence_checker.h"
 #include "device/fido/authenticator_make_credential_response.h"
 #include "device/fido/authenticator_selection_criteria.h"
+#include "device/fido/client_data.h"
 #include "device/fido/ctap_make_credential_request.h"
 #include "device/fido/fido_constants.h"
 #include "device/fido/fido_request_handler_base.h"
@@ -113,6 +114,8 @@
   State state_ = State::kWaitingForTouch;
   CtapMakeCredentialRequest request_;
   AuthenticatorSelectionCriteria authenticator_selection_criteria_;
+  base::Optional<AndroidClientDataExtensionInput> android_client_data_ext_;
+
   // If true, the request handler may skip the first touch to select a device
   // that will require a PIN.
   bool allow_skipping_pin_touch_;
diff --git a/docs/linux/development.md b/docs/linux/development.md
index 8a837d7..a9507c8 100644
--- a/docs/linux/development.md
+++ b/docs/linux/development.md
@@ -33,7 +33,7 @@
 
 ## Contributing code
 
-See [Contributing code](contributing.md).
+See [Contributing code](../contributing.md).
 
 ## Debugging
 
diff --git a/docs/security/faq.md b/docs/security/faq.md
index 5689f15..23b3daff 100644
--- a/docs/security/faq.md
+++ b/docs/security/faq.md
@@ -649,6 +649,34 @@
 *    [This document](https://www.chromium.org/developers/design-documents/idn-in-google-chrome)
 describes Chrome's IDN policy in detail.
 
+<a name="TOC-Chrome-silently-syncs-extensions-across-devices.-Is-this-a-security-vulnerability-"></a>
+## Chrome silently syncs extensions across devices. Is this a security vulnerability?
+
+If an attacker has access to one of a victim's devices, the attacker can install
+an extension which will be synced to the victim's other sync-enabled
+devices. Similarly, an attacker who phishes a victim's Google credentials can
+sign in to Chrome as the victim and install an extension, which will be synced
+to the victim's other sync-enabled devices. Sync thereby enables an attacker to
+elevate phished credentials or physical access to persistent access on all of a
+victim's sync-enabled devices.
+
+To mitigate this issue, Chrome only syncs extensions that have been installed
+from the Chrome Web Store. Extensions in the Chrome Web Store are monitored for
+abusive behavior.
+
+In the future, we may pursue further mitigations. However, because an attacker
+must already have the victim's Google credentials and/or [physical access to a
+device](#TOC-Why-aren-t-physically-local-attacks-in-Chrome-s-threat-model), we
+don't consider this attack a security vulnerability.
+
+We **do** consider it a vulnerability if an attacker can get an extension to
+sync to a victim's device without either of the above preconditions. For
+example, we consider it a vulnerability if an attacker could craft a request to
+Google's sync servers that causes an extension to be installed to a user's
+device, or if an attacker could entice a victim to visit a webpage that causes
+an extension to be installed on their device(s). Please report such bugs via
+https://bugs.chromium.org/p/chromium/issues/entry?template=Security+Bug.
+
 ## TODO
 
 *    https://dev.chromium.org/Home/chromium-security/client-identification-mechanisms
diff --git a/extensions/browser/api/crash_report_private/crash_report_private_api.cc b/extensions/browser/api/crash_report_private/crash_report_private_api.cc
index 973f6e6c..ed996bc1 100644
--- a/extensions/browser/api/crash_report_private/crash_report_private_api.cc
+++ b/extensions/browser/api/crash_report_private/crash_report_private_api.cc
@@ -8,13 +8,14 @@
 #include "base/strings/stringprintf.h"
 #include "base/system/sys_info.h"
 #include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
 #include "base/time/default_clock.h"
 #include "components/crash/core/app/client_upload_info.h"
 #include "components/feedback/anonymizer_tool.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/storage_partition.h"
-#include "extensions/common/api/crash_report_private.h"
 #include "net/base/escape.h"
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/cpp/simple_url_loader.h"
@@ -203,10 +204,6 @@
     ~CrashReportPrivateReportErrorFunction() = default;
 
 ExtensionFunction::ResponseAction CrashReportPrivateReportErrorFunction::Run() {
-  // Do not report errors if the user did not give consent for crash reporting.
-  if (!crash_reporter::GetClientCollectStatsConsent())
-    return RespondNow(NoArguments());
-
   // Ensure we don't send too many crash reports. Limit to one report per hour.
   if (!g_last_called_time.is_null() &&
       g_clock->Now() - g_last_called_time < base::TimeDelta::FromHours(1)) {
@@ -217,21 +214,40 @@
   const auto params = crash_report_private::ReportError::Params::Create(*args_);
   EXTENSION_FUNCTION_VALIDATE(params.get());
 
+  // Consent checking may be blocking, so do it on a separate thread to avoid
+  // blocking the UI thread.
+  PostTaskAndReplyWithResult(
+      FROM_HERE, {base::ThreadPool(), base::MayBlock()},
+      base::BindOnce(&crash_reporter::GetClientCollectStatsConsent),
+      base::BindOnce(
+          &CrashReportPrivateReportErrorFunction::OnConsentCheckCompleted, this,
+          std::move(params->info)));
+
+  return RespondLater();
+}
+
+void CrashReportPrivateReportErrorFunction::OnConsentCheckCompleted(
+    crash_report_private::ErrorInfo info,
+    bool consented) {
+  // Do not report errors if the user did not give consent for crash reporting.
+  if (!consented) {
+    Respond(NoArguments());
+    return;
+  }
+
   scoped_refptr<network::SharedURLLoaderFactory> loader_factory =
       content::BrowserContext::GetDefaultStoragePartition(browser_context())
           ->GetURLLoaderFactoryForBrowserProcess();
 
   // Don't anonymize the report on the UI thread as it can take some time.
   PostTaskAndReplyWithResult(
-      FROM_HERE, base::BindOnce(&AnonymizeErrorMessage, params->info.message),
+      FROM_HERE, base::BindOnce(&AnonymizeErrorMessage, info.message),
       base::BindOnce(
-          &ReportJavaScriptError, std::move(loader_factory),
-          std::move(params->info),
+          &ReportJavaScriptError, std::move(loader_factory), std::move(info),
           base::BindOnce(
               &CrashReportPrivateReportErrorFunction::OnReportComplete, this)));
-  g_last_called_time = base::Time::Now();
 
-  return RespondLater();
+  g_last_called_time = base::Time::Now();
 }
 
 void CrashReportPrivateReportErrorFunction::OnReportComplete() {
diff --git a/extensions/browser/api/crash_report_private/crash_report_private_api.h b/extensions/browser/api/crash_report_private/crash_report_private_api.h
index f91819f4..0a7c7af 100644
--- a/extensions/browser/api/crash_report_private/crash_report_private_api.h
+++ b/extensions/browser/api/crash_report_private/crash_report_private_api.h
@@ -9,6 +9,7 @@
 
 #include "extensions/browser/extension_function.h"
 #include "extensions/browser/extension_function_histogram_value.h"
+#include "extensions/common/api/crash_report_private.h"
 
 namespace base {
 class Clock;
@@ -28,6 +29,8 @@
   ResponseAction Run() override;
 
  private:
+  void OnConsentCheckCompleted(crash_report_private::ErrorInfo info,
+                               bool consented);
   void OnReportComplete();
 
   DISALLOW_COPY_AND_ASSIGN(CrashReportPrivateReportErrorFunction);
diff --git a/extensions/browser/api/crash_report_private/crash_report_private_apitest.cc b/extensions/browser/api/crash_report_private/crash_report_private_apitest.cc
index d53409f..ceeecd4 100644
--- a/extensions/browser/api/crash_report_private/crash_report_private_apitest.cc
+++ b/extensions/browser/api/crash_report_private/crash_report_private_apitest.cc
@@ -4,6 +4,7 @@
 
 #include "base/system/sys_info.h"
 #include "base/test/simple_test_clock.h"
+#include "base/threading/scoped_blocking_call.h"
 #include "components/crash/core/app/crash_reporter_client.h"
 #include "content/public/test/browser_task_environment.h"
 #include "extensions/browser/api/crash_report_private/crash_report_private_api.h"
@@ -26,7 +27,17 @@
 constexpr const char* kTestCrashEndpoint = "/crash";
 
 class MockCrashReporterClient : public crash_reporter::CrashReporterClient {
-  bool GetCollectStatsConsent() override { return true; }
+ public:
+  void set_consented(bool consented) { consented_ = consented; }
+
+ private:
+  bool GetCollectStatsConsent() override {
+    // In production, GetCollectStatsConsent may be blocking due to file reads.
+    // Simulate this in our tests as well.
+    base::ScopedBlockingCall scoped_blocking_call(
+        FROM_HERE, base::BlockingType::MAY_BLOCK);
+    return consented_;
+  }
   void GetProductNameAndVersion(std::string* product_name,
                                 std::string* version,
                                 std::string* channel) override {
@@ -34,6 +45,8 @@
     *version = "1.2.3.4";
     *channel = "Stable";
   }
+
+  bool consented_ = true;
 };
 
 std::string GetOsVersion() {
@@ -102,6 +115,7 @@
   };
 
   const Extension* extension_;
+  MockCrashReporterClient client_;
   Report last_report_;
 
  private:
@@ -121,7 +135,6 @@
     return http_response;
   }
 
-  MockCrashReporterClient client_;
   DISALLOW_COPY_AND_ASSIGN(CrashReportPrivateApiTest);
 };
 
@@ -259,4 +272,26 @@
                                               extension_->id(), kTestScript));
 }
 
+// Ensures that reportError checks user consent for data collection on the
+// correct thread and correctly handles the case where consent is not given.
+IN_PROC_BROWSER_TEST_F(CrashReportPrivateApiTest, NoConsent) {
+  constexpr char kTestScript[] = R"(
+    chrome.crashReportPrivate.reportError({
+        message: "hi",
+        url: "http://www.test.com",
+      },
+      () => {
+        window.domAutomationController.send(chrome.runtime.lastError ?
+            chrome.runtime.lastError.message : "")
+      });
+  )";
+
+  client_.set_consented(false);
+  EXPECT_EQ("", ExecuteScriptInBackgroundPage(browser_context(),
+                                              extension_->id(), kTestScript));
+  // The server should not receive any reports.
+  EXPECT_EQ(last_report_.query, "");
+  EXPECT_EQ(last_report_.content, "");
+}
+
 }  // namespace extensions
diff --git a/extensions/common/url_pattern_unittest.cc b/extensions/common/url_pattern_unittest.cc
index f699d03..12b9bdf9 100644
--- a/extensions/common/url_pattern_unittest.cc
+++ b/extensions/common/url_pattern_unittest.cc
@@ -202,7 +202,9 @@
   EXPECT_FALSE(pattern.match_all_urls());
   EXPECT_EQ("/foo*bar", pattern.path());
   EXPECT_TRUE(pattern.MatchesURL(GURL("http://google.com/foobar")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("http://www.google.com/foobar")));
   EXPECT_TRUE(pattern.MatchesURL(GURL("http://www.google.com/foo?bar")));
+  EXPECT_FALSE(pattern.MatchesURL(GURL("http://wwwgoogle.com/foobar")));
   EXPECT_TRUE(pattern.MatchesURL(
       GURL("http://monkey.images.google.com/foooobar")));
   EXPECT_FALSE(pattern.MatchesURL(GURL("http://yahoo.com/foobar")));
diff --git a/extensions/shell/browser/root_window_controller.cc b/extensions/shell/browser/root_window_controller.cc
index 3da1e72b..1743e2f 100644
--- a/extensions/shell/browser/root_window_controller.cc
+++ b/extensions/shell/browser/root_window_controller.cc
@@ -7,7 +7,6 @@
 #include "extensions/browser/app_window/app_window.h"
 #include "extensions/browser/app_window/native_app_window.h"
 #include "extensions/shell/browser/shell_app_delegate.h"
-#include "ui/aura/client/screen_position_client.h"
 #include "ui/aura/layout_manager.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_tracker.h"
@@ -68,7 +67,7 @@
 // coordinates using the offset of the root window in screen coordinates.
 class ScreenPositionClient : public wm::DefaultScreenPositionClient {
  public:
-  ScreenPositionClient() = default;
+  using DefaultScreenPositionClient::DefaultScreenPositionClient;
   ~ScreenPositionClient() override = default;
 
   // wm::DefaultScreenPositionClient:
@@ -98,9 +97,7 @@
     DesktopDelegate* desktop_delegate,
     const gfx::Rect& bounds,
     content::BrowserContext* browser_context)
-    : desktop_delegate_(desktop_delegate),
-      browser_context_(browser_context),
-      screen_position_client_(std::make_unique<ScreenPositionClient>()) {
+    : desktop_delegate_(desktop_delegate), browser_context_(browser_context) {
   DCHECK(desktop_delegate_);
   DCHECK(browser_context_);
 
@@ -110,8 +107,8 @@
   host_->window()->Show();
 
   aura::client::SetWindowParentingClient(host_->window(), this);
-  aura::client::SetScreenPositionClient(host_->window(),
-                                        screen_position_client_.get());
+  screen_position_client_ =
+      std::make_unique<ScreenPositionClient>(host_->window());
 
   // Ensure the window fills the display.
   host_->window()->SetLayoutManager(new FillLayout(host_->window()));
@@ -122,6 +119,9 @@
 
 RootWindowController::~RootWindowController() {
   CloseAppWindows();
+  // The screen position client holds a pointer to the root window, so free it
+  // before destroying the window tree host.
+  screen_position_client_.reset();
   DestroyWindowTreeHost();
 }
 
diff --git a/fuchsia/engine/browser/web_engine_permission_delegate.cc b/fuchsia/engine/browser/web_engine_permission_delegate.cc
index ae20cb9..e57a7b0 100644
--- a/fuchsia/engine/browser/web_engine_permission_delegate.cc
+++ b/fuchsia/engine/browser/web_engine_permission_delegate.cc
@@ -54,8 +54,8 @@
     content::PermissionType permission,
     const GURL& requesting_origin,
     const GURL& embedding_origin) {
-  // TODO(crbug.com/922833): Update PermissionControllerDelegate to pass
-  // RenderFrameHost.
+  // TODO(crbug.com/1063094): Implement when the PermissionManager protocol is
+  // defined and implemented.
   NOTIMPLEMENTED() << ": " << static_cast<int>(permission);
 }
 
@@ -63,9 +63,11 @@
     content::PermissionType permission,
     const GURL& requesting_origin,
     const GURL& embedding_origin) {
-  // GetPermissionStatus() is deprecated and it's not expected to be called in
-  // WebEngine.
-  NOTREACHED();
+  // Although GetPermissionStatusForFrame() should be used for most permissions,
+  // some use cases (e.g., BACKGROUND_SYNC) do not have a frame.
+  //
+  // TODO(crbug.com/1063094): Handle frame-less permission status checks in the
+  // PermissionManager API. Until then, reject such requests.
   return blink::mojom::PermissionStatus::DENIED;
 }
 
@@ -85,7 +87,7 @@
     content::RenderFrameHost* render_frame_host,
     const GURL& requesting_origin,
     base::RepeatingCallback<void(blink::mojom::PermissionStatus)> callback) {
-  // TODO(crbug.com/922833): Implement permission status subscription. It's
+  // TODO(crbug.com/1063094): Implement permission status subscription. It's
   // used in blink to emit PermissionStatus.onchange notifications.
   NOTIMPLEMENTED() << ": " << static_cast<int>(permission);
   return content::PermissionController::kNoPendingOperation;
@@ -93,5 +95,7 @@
 
 void WebEnginePermissionDelegate::UnsubscribePermissionStatusChange(
     int subscription_id) {
+  // TODO(crbug.com/1063094): Implement permission status subscription. It's
+  // used in blink to emit PermissionStatus.onchange notifications.
   NOTREACHED();
 }
diff --git a/gpu/command_buffer/service/external_vk_image_backing.cc b/gpu/command_buffer/service/external_vk_image_backing.cc
index 91dca6e..6cea0c6 100644
--- a/gpu/command_buffer/service/external_vk_image_backing.cc
+++ b/gpu/command_buffer/service/external_vk_image_backing.cc
@@ -84,23 +84,6 @@
 static_assert(base::size(kFormatTable) == (viz::RESOURCE_FORMAT_MAX + 1),
               "kFormatTable does not handle all cases.");
 
-GrVkImageInfo CreateGrVkImageInfo(SharedContextState* context_state,
-                                  VulkanImage* image,
-                                  bool use_protected_memory) {
-  VkPhysicalDevice physical_device = context_state->vk_context_provider()
-                                         ->GetDeviceQueue()
-                                         ->GetVulkanPhysicalDevice();
-  GrVkYcbcrConversionInfo gr_ycbcr_info = CreateGrVkYcbcrConversionInfo(
-      physical_device, image->image_tiling(), image->ycbcr_info());
-  GrVkAlloc alloc(image->device_memory(), 0 /* offset */, image->device_size(),
-                  0 /* flags */);
-  return GrVkImageInfo(
-      image->image(), alloc, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_LAYOUT_UNDEFINED,
-      image->format(), 1 /* levelCount */, VK_QUEUE_FAMILY_IGNORED,
-      use_protected_memory ? GrProtected::kYes : GrProtected::kNo,
-      gr_ycbcr_info);
-}
-
 uint32_t FindMemoryTypeIndex(SharedContextState* context_state,
                              const VkMemoryRequirements& requirements,
                              VkMemoryPropertyFlags flags) {
@@ -350,12 +333,9 @@
                                       false /* is_thread_safe */),
       context_state_(context_state),
       image_(std::move(image)),
-      backend_texture_(
-          size.width(),
-          size.height(),
-          CreateGrVkImageInfo(context_state_,
-                              image_.get(),
-                              usage & SHARED_IMAGE_USAGE_PROTECTED)),
+      backend_texture_(size.width(),
+                       size.height(),
+                       CreateGrVkImageInfo(image_.get())),
       command_pool_(command_pool) {}
 
 ExternalVkImageBacking::~ExternalVkImageBacking() {
diff --git a/gpu/command_buffer/service/raster_decoder.cc b/gpu/command_buffer/service/raster_decoder.cc
index 94d390a..9b3bf4b 100644
--- a/gpu/command_buffer/service/raster_decoder.cc
+++ b/gpu/command_buffer/service/raster_decoder.cc
@@ -2514,10 +2514,13 @@
 
   raster_canvas_ = nullptr;
 
+  // The DDL pins memory for the recorded ops so it must be kept alive until its
+  // flushed.
+  std::unique_ptr<SkDeferredDisplayList> ddl;
   if (use_ddl_) {
     TRACE_EVENT0("gpu",
                  "RasterDecoderImpl::DoEndRasterCHROMIUM::DetachAndDrawDDL");
-    auto ddl = recorder_->detach();
+    ddl = recorder_->detach();
     recorder_ = nullptr;
     sk_surface_->draw(ddl.get());
   }
@@ -2540,6 +2543,7 @@
                                      flush_info);
     DCHECK(result == GrSemaphoresSubmitted::kYes || end_semaphores_.empty());
     end_semaphores_.clear();
+    ddl.reset();
   }
 
   shared_context_state_->UpdateSkiaOwnedMemorySize();
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 a28fa0d..1c1ad3b5 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer.cc
@@ -277,18 +277,11 @@
     DCHECK(vulkan_image_);
     DCHECK(context_state_);
     DCHECK(context_state_->vk_context_provider());
-    // Create backend texture from the VkImage.
-    GrVkAlloc alloc(vulkan_image_->device_memory(), 0 /* offset */,
-                    vulkan_image_->device_size(), 0 /* flags */);
-    GrVkImageInfo vk_info(vulkan_image_->image(), alloc,
-                          vulkan_image_->image_tiling(),
-                          VK_IMAGE_LAYOUT_UNDEFINED, vulkan_image_->format(),
-                          1 /* levelCount */, VK_QUEUE_FAMILY_EXTERNAL);
-
     // TODO(bsalomon): Determine whether it makes sense to attempt to reuse this
     // if the vk_info stays the same on subsequent calls.
     promise_texture_ = SkPromiseImageTexture::Make(
-        GrBackendTexture(size().width(), size().height(), vk_info));
+        GrBackendTexture(size().width(), size().height(),
+                         CreateGrVkImageInfo(vulkan_image_.get())));
     DCHECK(promise_texture_);
   }
 
diff --git a/gpu/command_buffer/service/shared_image_representation.cc b/gpu/command_buffer/service/shared_image_representation.cc
index 93133a1..e17f9f5 100644
--- a/gpu/command_buffer/service/shared_image_representation.cc
+++ b/gpu/command_buffer/service/shared_image_representation.cc
@@ -31,7 +31,7 @@
     GLenum mode,
     AllowUnclearedAccess allow_uncleared) {
   if (allow_uncleared != AllowUnclearedAccess::kYes && !IsCleared()) {
-    LOG(ERROR) << "Attempt to access an uninitialized ShardImage";
+    LOG(ERROR) << "Attempt to access an uninitialized SharedImage";
     return nullptr;
   }
 
@@ -108,7 +108,7 @@
     std::vector<GrBackendSemaphore>* end_semaphores,
     AllowUnclearedAccess allow_uncleared) {
   if (allow_uncleared != AllowUnclearedAccess::kYes && !IsCleared()) {
-    LOG(ERROR) << "Attempt to write to an uninitialized ShardImage";
+    LOG(ERROR) << "Attempt to write to an uninitialized SharedImage";
     return nullptr;
   }
 
@@ -148,7 +148,7 @@
     std::vector<GrBackendSemaphore>* begin_semaphores,
     std::vector<GrBackendSemaphore>* end_semaphores) {
   if (!IsCleared()) {
-    LOG(ERROR) << "Attempt to read from an uninitialized ShardImage";
+    LOG(ERROR) << "Attempt to read from an uninitialized SharedImage";
     return nullptr;
   }
 
@@ -171,7 +171,7 @@
 std::unique_ptr<SharedImageRepresentationOverlay::ScopedReadAccess>
 SharedImageRepresentationOverlay::BeginScopedReadAccess(bool needs_gl_image) {
   if (!IsCleared()) {
-    LOG(ERROR) << "Attempt to read from an uninitialized ShardImage";
+    LOG(ERROR) << "Attempt to read from an uninitialized SharedImage";
     return nullptr;
   }
 
@@ -198,7 +198,7 @@
     WGPUTextureUsage usage,
     AllowUnclearedAccess allow_uncleared) {
   if (allow_uncleared != AllowUnclearedAccess::kYes && !IsCleared()) {
-    LOG(ERROR) << "Attempt to access an uninitialized ShardImage";
+    LOG(ERROR) << "Attempt to access an uninitialized SharedImage";
     return nullptr;
   }
 
diff --git a/gpu/command_buffer/service/shared_image_video.cc b/gpu/command_buffer/service/shared_image_video.cc
index 9e683e2..ba043e3 100644
--- a/gpu/command_buffer/service/shared_image_video.cc
+++ b/gpu/command_buffer/service/shared_image_video.cc
@@ -287,22 +287,11 @@
       DCHECK_EQ(static_cast<int32_t>(vulkan_image_->image_tiling()),
                 static_cast<int32_t>(VK_IMAGE_TILING_OPTIMAL));
 
-      GrVkYcbcrConversionInfo gr_ycbcr_info = CreateGrVkYcbcrConversionInfo(
-          device_queue->GetVulkanPhysicalDevice(), VK_IMAGE_TILING_OPTIMAL,
-          vulkan_image_->ycbcr_info());
-      // Create backend texture from the VkImage.
-      GrVkAlloc alloc(vulkan_image_->device_memory(), 0 /* offset */,
-                      vulkan_image_->device_size(), 0 /* flags */);
-      GrVkImageInfo vk_info(vulkan_image_->image(), alloc,
-                            vulkan_image_->image_tiling(),
-                            VK_IMAGE_LAYOUT_UNDEFINED, vulkan_image_->format(),
-                            1 /* levelCount */, VK_QUEUE_FAMILY_EXTERNAL,
-                            GrProtected::kNo, gr_ycbcr_info);
-
       // TODO(bsalomon): Determine whether it makes sense to attempt to reuse
       // this if the vk_info stays the same on subsequent calls.
       promise_texture_ = SkPromiseImageTexture::Make(
-          GrBackendTexture(size().width(), size().height(), vk_info));
+          GrBackendTexture(size().width(), size().height(),
+                           CreateGrVkImageInfo(vulkan_image_.get())));
       DCHECK(promise_texture_);
     }
     return promise_texture_;
diff --git a/gpu/command_buffer/service/skia_utils.cc b/gpu/command_buffer/service/skia_utils.cc
index e4e4ad4..b79adbb 100644
--- a/gpu/command_buffer/service/skia_utils.cc
+++ b/gpu/command_buffer/service/skia_utils.cc
@@ -20,6 +20,7 @@
 #include "gpu/vulkan/vulkan_device_queue.h"
 #include "gpu/vulkan/vulkan_fence_helper.h"
 #include "gpu/vulkan/vulkan_function_pointers.h"
+#include "gpu/vulkan/vulkan_image.h"
 #endif
 
 namespace gpu {
@@ -167,6 +168,20 @@
 }
 
 #if BUILDFLAG(ENABLE_VULKAN)
+GrVkImageInfo CreateGrVkImageInfo(VulkanImage* image) {
+  DCHECK(image);
+  VkPhysicalDevice physical_device =
+      image->device_queue()->GetVulkanPhysicalDevice();
+  GrVkYcbcrConversionInfo gr_ycbcr_info = CreateGrVkYcbcrConversionInfo(
+      physical_device, image->image_tiling(), image->ycbcr_info());
+  GrVkAlloc alloc(image->device_memory(), /*offset=*/0, image->device_size(),
+                  /*flags=*/0);
+  bool is_protected = image->flags() & VK_IMAGE_CREATE_PROTECTED_BIT;
+  return GrVkImageInfo(
+      image->image(), alloc, image->image_tiling(), image->image_layout(),
+      image->format(), /*levelCount=*/1, image->queue_family_index(),
+      is_protected ? GrProtected::kYes : GrProtected::kNo, gr_ycbcr_info);
+}
 
 GrVkYcbcrConversionInfo CreateGrVkYcbcrConversionInfo(
     VkPhysicalDevice physical_device,
diff --git a/gpu/command_buffer/service/skia_utils.h b/gpu/command_buffer/service/skia_utils.h
index a383163..0208f72b 100644
--- a/gpu/command_buffer/service/skia_utils.h
+++ b/gpu/command_buffer/service/skia_utils.h
@@ -32,6 +32,10 @@
 
 namespace gpu {
 
+#if BUILDFLAG(ENABLE_VULKAN)
+class VulkanImage;
+#endif
+
 namespace gles2 {
 class FeatureInfo;
 }  // namespace gles2
@@ -74,6 +78,8 @@
                                       sk_sp<SkSurface> sk_surface);
 
 #if BUILDFLAG(ENABLE_VULKAN)
+GPU_GLES2_EXPORT GrVkImageInfo CreateGrVkImageInfo(VulkanImage* image);
+
 GPU_GLES2_EXPORT GrVkYcbcrConversionInfo CreateGrVkYcbcrConversionInfo(
     VkPhysicalDevice physical_device,
     VkImageTiling tiling,
diff --git a/gpu/vulkan/vulkan_image.cc b/gpu/vulkan/vulkan_image.cc
index 2fcbed2..be991ca 100644
--- a/gpu/vulkan/vulkan_image.cc
+++ b/gpu/vulkan/vulkan_image.cc
@@ -209,12 +209,13 @@
   device_queue_ = device_queue;
   size_ = size;
   format_ = format;
+  flags_ = flags;
   image_tiling_ = image_tiling;
 
   VkImageCreateInfo create_info = {
       .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
       .pNext = vk_image_create_info_next,
-      .flags = flags,
+      .flags = flags_,
       .imageType = VK_IMAGE_TYPE_2D,
       .format = format_,
       .extent = {size.width(), size.height(), 1},
@@ -226,7 +227,7 @@
       .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
       .queueFamilyIndexCount = 0,
       .pQueueFamilyIndices = nullptr,
-      .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+      .initialLayout = image_layout_,
   };
   VkDevice vk_device = device_queue->GetVulkanDevice();
   VkResult result =
@@ -486,6 +487,9 @@
     return false;
   }
 
+  // VkImage is imported from external.
+  queue_family_index_ = VK_QUEUE_FAMILY_EXTERNAL;
+
   if (ahb_format_props.format == VK_FORMAT_UNDEFINED) {
     ycbcr_info_.emplace(VK_FORMAT_UNDEFINED, ahb_format_props.externalFormat,
                         ahb_format_props.suggestedYcbcrModel,
@@ -494,6 +498,7 @@
                         ahb_format_props.suggestedYChromaOffset,
                         ahb_format_props.formatFeatures);
   }
+
   return true;
 #endif  // defined(OS_ANDROID)
 }
diff --git a/gpu/vulkan/vulkan_image.h b/gpu/vulkan/vulkan_image.h
index d386563..2c24fb5 100644
--- a/gpu/vulkan/vulkan_image.h
+++ b/gpu/vulkan/vulkan_image.h
@@ -83,11 +83,17 @@
   zx::vmo GetMemoryZirconHandle();
 #endif
 
+  VulkanDeviceQueue* device_queue() const { return device_queue_; }
   const gfx::Size& size() const { return size_; }
   VkFormat format() const { return format_; }
+  VkImageCreateFlags flags() const { return flags_; }
   VkDeviceSize device_size() const { return device_size_; }
   uint32_t memory_type_index() const { return memory_type_index_; }
   VkImageTiling image_tiling() const { return image_tiling_; }
+  VkImageLayout image_layout() const { return image_layout_; }
+  void set_image_layout(VkImageLayout layout) { image_layout_ = layout; }
+  uint32_t queue_family_index() const { return queue_family_index_; }
+  void set_queue_family_index(uint32_t index) { queue_family_index_ = index; }
   const base::Optional<VulkanYCbCrInfo>& ycbcr_info() const {
     return ycbcr_info_;
   }
@@ -123,9 +129,12 @@
   VulkanDeviceQueue* device_queue_ = nullptr;
   gfx::Size size_;
   VkFormat format_ = VK_FORMAT_UNDEFINED;
+  VkImageCreateFlags flags_ = 0;
   VkDeviceSize device_size_ = 0;
   uint32_t memory_type_index_ = 0;
   VkImageTiling image_tiling_ = VK_IMAGE_TILING_OPTIMAL;
+  VkImageLayout image_layout_ = VK_IMAGE_LAYOUT_UNDEFINED;
+  uint32_t queue_family_index_ = VK_QUEUE_FAMILY_IGNORED;
   base::Optional<VulkanYCbCrInfo> ycbcr_info_;
   VkImage image_ = VK_NULL_HANDLE;
   VkDeviceMemory device_memory_ = VK_NULL_HANDLE;
diff --git a/ios/chrome/browser/policy/BUILD.gn b/ios/chrome/browser/policy/BUILD.gn
index a6f4f730..e3433a77 100644
--- a/ios/chrome/browser/policy/BUILD.gn
+++ b/ios/chrome/browser/policy/BUILD.gn
@@ -20,6 +20,7 @@
 
   deps = [
     "//base",
+    "//components/password_manager/core/common",
     "//components/policy:generated",
     "//components/policy/core/common",
     "//ios/chrome/browser",
@@ -74,6 +75,7 @@
     ":test_support",
     "//base",
     "//base/test:test_support",
+    "//components/password_manager/core/common",
     "//components/pref_registry",
     "//components/prefs",
     "//components/sync_preferences",
diff --git a/ios/chrome/browser/policy/configuration_policy_handler_list_factory.mm b/ios/chrome/browser/policy/configuration_policy_handler_list_factory.mm
index 1e6572e..7fed64e 100644
--- a/ios/chrome/browser/policy/configuration_policy_handler_list_factory.mm
+++ b/ios/chrome/browser/policy/configuration_policy_handler_list_factory.mm
@@ -6,6 +6,7 @@
 
 #include "base/bind.h"
 #include "base/logging.h"
+#include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/policy/core/browser/configuration_policy_handler.h"
 #include "components/policy/core/browser/configuration_policy_handler_list.h"
 #include "components/policy/core/browser/configuration_policy_handler_parameters.h"
@@ -23,8 +24,12 @@
 namespace {
 
 const PolicyToPreferenceMapEntry kSimplePolicyMap[] = {
+    {policy::key::kPasswordManagerEnabled,
+     password_manager::prefs::kCredentialsEnableService,
+     base::Value::Type::BOOLEAN},
     {policy::key::kSearchSuggestEnabled, prefs::kSearchSuggestEnabled,
-     base::Value::Type::BOOLEAN}};
+     base::Value::Type::BOOLEAN},
+};
 
 void PopulatePolicyHandlerParameters(
     policy::PolicyHandlerParameters* parameters) {}
diff --git a/ios/chrome/browser/policy/policy_unittest.mm b/ios/chrome/browser/policy/policy_unittest.mm
index db926ec8..752e744 100644
--- a/ios/chrome/browser/policy/policy_unittest.mm
+++ b/ios/chrome/browser/policy/policy_unittest.mm
@@ -10,6 +10,7 @@
 #include "base/run_loop.h"
 #include "base/test/task_environment.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/policy/core/common/mock_configuration_policy_provider.h"
 #include "components/policy/core/common/policy_map.h"
 #include "components/policy/core/common/policy_types.h"
@@ -131,3 +132,33 @@
   EXPECT_TRUE(pref_service_->IsManagedPreference(prefs::kSearchSuggestEnabled));
   EXPECT_FALSE(pref_service_->GetBoolean(prefs::kSearchSuggestEnabled));
 }
+
+// Tests that the PasswordManagerEnabled preference is correctly managed by
+// policy.
+TEST_F(PolicyTest, TestPasswordManagerEnabled) {
+  EXPECT_FALSE(pref_service_->IsManagedPreference(
+      password_manager::prefs::kCredentialsEnableService));
+
+  policy::PolicyMap values;
+  // Setting the policy to true should set the pref to true.
+  values.Set(policy::key::kPasswordManagerEnabled,
+             policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_MACHINE,
+             policy::POLICY_SOURCE_PLATFORM,
+             std::make_unique<base::Value>(true), nullptr);
+  policy_provider_.UpdateChromePolicy(values);
+  EXPECT_TRUE(pref_service_->IsManagedPreference(
+      password_manager::prefs::kCredentialsEnableService));
+  EXPECT_TRUE(pref_service_->GetBoolean(
+      password_manager::prefs::kCredentialsEnableService));
+
+  // Setting the policy to false should set the pref to false.
+  values.Set(policy::key::kPasswordManagerEnabled,
+             policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_MACHINE,
+             policy::POLICY_SOURCE_PLATFORM,
+             std::make_unique<base::Value>(false), nullptr);
+  policy_provider_.UpdateChromePolicy(values);
+  EXPECT_TRUE(pref_service_->IsManagedPreference(
+      password_manager::prefs::kCredentialsEnableService));
+  EXPECT_FALSE(pref_service_->GetBoolean(
+      password_manager::prefs::kCredentialsEnableService));
+}
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/BUILD.gn b/ios/chrome/browser/ui/autofill/manual_fill/BUILD.gn
index 799cebb..de8ee5e8 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/BUILD.gn
+++ b/ios/chrome/browser/ui/autofill/manual_fill/BUILD.gn
@@ -266,6 +266,7 @@
   deps = [
     "//base",
     "//base/test:test_support",
+    "//components/autofill/core/browser:test_support",
     "//ios/chrome/app/strings:ios_strings_grit",
     "//ios/chrome/browser/ui/autofill:eg_test_support+eg2",
     "//ios/chrome/browser/ui/settings/autofill:feature_flags",
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/card_view_controller_egtest.mm b/ios/chrome/browser/ui/autofill/manual_fill/card_view_controller_egtest.mm
index b949c6d..95e895a 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/card_view_controller_egtest.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/card_view_controller_egtest.mm
@@ -5,6 +5,7 @@
 #include "base/ios/ios_util.h"
 #include "base/strings/sys_string_conversions.h"
 #import "base/test/ios/wait_util.h"
+#include "components/autofill/core/browser/autofill_test_utils.h"
 #import "ios/chrome/browser/ui/autofill/autofill_app_interface.h"
 #import "ios/chrome/browser/ui/settings/autofill/features.h"
 #include "ios/chrome/grit/ios_strings.h"
@@ -44,8 +45,12 @@
 
 NSString* kLocalCardNumber = @"4111111111111111";
 NSString* kLocalCardHolder = @"Test User";
-NSString* kLocalCardExpirationMonth = @"11";
-NSString* kLocalCardExpirationYear = @"22";
+// The local card's expiration month and year (only the last two digits) are set
+// with next month and next year.
+NSString* kLocalCardExpirationMonth =
+    base::SysUTF8ToNSString(autofill::test::NextMonth());
+NSString* kLocalCardExpirationYear =
+    base::SysUTF8ToNSString(autofill::test::NextYear().substr(2, 2));
 
 // Unicode characters used in card number:
 //  - 0x0020 - Space.
diff --git a/ios/chrome/browser/ui/settings/autofill/BUILD.gn b/ios/chrome/browser/ui/settings/autofill/BUILD.gn
index 3318029..ed05c75 100644
--- a/ios/chrome/browser/ui/settings/autofill/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/autofill/BUILD.gn
@@ -149,6 +149,7 @@
     ":constants",
     ":feature_flags",
     "//base",
+    "//components/autofill/core/browser:test_support",
     "//components/strings:components_strings_grit",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/ui/autofill:eg_test_support+eg2",
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 8ab228c..13c394e2 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
@@ -6,6 +6,7 @@
 
 #include "base/strings/sys_string_conversions.h"
 #import "base/test/ios/wait_util.h"
+#include "components/autofill/core/browser/autofill_test_utils.h"
 #include "components/strings/grit/components_strings.h"
 #import "ios/chrome/browser/ui/autofill/autofill_app_interface.h"
 #import "ios/chrome/browser/ui/settings/autofill/autofill_constants.h"
@@ -43,8 +44,10 @@
 const DisplayStringIDToExpectedResult kExpectedFields[] = {
     {IDS_IOS_AUTOFILL_CARDHOLDER, @"Test User"},
     {IDS_IOS_AUTOFILL_CARD_NUMBER, @"4111111111111111"},
-    {IDS_IOS_AUTOFILL_EXP_MONTH, @"11"},
-    {IDS_IOS_AUTOFILL_EXP_YEAR, @"2022"}};
+    {IDS_IOS_AUTOFILL_EXP_MONTH,
+     base::SysUTF8ToNSString(autofill::test::NextMonth())},
+    {IDS_IOS_AUTOFILL_EXP_YEAR,
+     base::SysUTF8ToNSString(autofill::test::NextYear())}};
 
 NSString* const kCreditCardLabelTemplate = @"Test User, %@";
 
diff --git a/ios/chrome/common/credential_provider/OWNERS b/ios/chrome/common/credential_provider/OWNERS
new file mode 100644
index 0000000..b2af2796
--- /dev/null
+++ b/ios/chrome/common/credential_provider/OWNERS
@@ -0,0 +1,5 @@
+javierrobles@chromium.org
+djean@chromium.org
+
+# TEAM: ios-directory-owners@chromium.org
+# OS: iOS
diff --git a/media/audio/OWNERS b/media/audio/OWNERS
index c13e013..db515cd 100644
--- a/media/audio/OWNERS
+++ b/media/audio/OWNERS
@@ -6,5 +6,6 @@
 
 # Mirroring (and related glue) OWNERS.
 miu@chromium.org
+mfoltz@chromium.org
 
 # COMPONENT: Blink>Media>Audio
diff --git a/media/base/mock_filters.h b/media/base/mock_filters.h
index e788fec..86c77b3 100644
--- a/media/base/mock_filters.h
+++ b/media/base/mock_filters.h
@@ -324,6 +324,8 @@
   MOCK_METHOD1(StartPlayingFrom, void(base::TimeDelta));
   MOCK_METHOD0(OnTimeProgressing, void());
   MOCK_METHOD0(OnTimeStopped, void());
+  MOCK_METHOD1(SetLatencyHint,
+               void(base::Optional<base::TimeDelta> latency_hint));
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockVideoRenderer);
diff --git a/media/base/video_renderer.h b/media/base/video_renderer.h
index 7a7fc29..bfbc676c 100644
--- a/media/base/video_renderer.h
+++ b/media/base/video_renderer.h
@@ -7,6 +7,7 @@
 
 #include "base/callback_forward.h"
 #include "base/macros.h"
+#include "base/optional.h"
 #include "media/base/media_export.h"
 #include "media/base/pipeline_status.h"
 #include "media/base/time_source.h"
@@ -63,6 +64,12 @@
   virtual void OnTimeProgressing() = 0;
   virtual void OnTimeStopped() = 0;
 
+  // Sets a hint indicating target latency. See comment in header for
+  // media::Renderer::SetLatencyHint().
+  // |latency_hint| may be nullopt to indicate the hint has been cleared
+  // (restore UA default).
+  virtual void SetLatencyHint(base::Optional<base::TimeDelta> latency_hint) = 0;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(VideoRenderer);
 };
diff --git a/media/capture/content/OWNERS b/media/capture/content/OWNERS
index e837b5f..12fd84a 100644
--- a/media/capture/content/OWNERS
+++ b/media/capture/content/OWNERS
@@ -1,3 +1,4 @@
 miu@chromium.org
+mfoltz@chromium.org
 
 # COMPONENT: Internals>Media>ScreenCapture
diff --git a/media/renderers/renderer_impl.cc b/media/renderers/renderer_impl.cc
index 2a0dcaca..98b8c80 100644
--- a/media/renderers/renderer_impl.cc
+++ b/media/renderers/renderer_impl.cc
@@ -199,7 +199,8 @@
   DCHECK(!latency_hint || (*latency_hint >= base::TimeDelta()));
   DCHECK(task_runner_->BelongsToCurrentThread());
 
-  // TODO(chcunningham): Plumb to video renderer in a follow up CL.
+  if (video_renderer_)
+    video_renderer_->SetLatencyHint(latency_hint);
 
   if (audio_renderer_)
     audio_renderer_->SetLatencyHint(latency_hint);
diff --git a/media/renderers/video_renderer_impl.cc b/media/renderers/video_renderer_impl.cc
index e703824..b89509ce 100644
--- a/media/renderers/video_renderer_impl.cc
+++ b/media/renderers/video_renderer_impl.cc
@@ -29,6 +29,16 @@
 
 namespace {
 
+// Maximum number of frames we will buffer, regardless of their "effectiveness".
+// See HaveReachedBufferingCap(). The value was historically described in terms
+// of |min_buffered_frames_| as follows:
+// = 3 * high_water_mark(min_buffered_frames_),
+// = 3 * (2 * limits::kMaxVideoFrames)
+// = 3 * 2 * 4
+// Today, |min_buffered_frames_| can go down (as low as 1) and up in response to
+// SetLatencyHint(), so we needed to peg this with a constant.
+constexpr int kAbsoluteMaxFrames = 24;
+
 // Used for UMA stats, only add numbers to end!
 enum VideoFrameColorSpaceUMA {
   Unknown = 0,
@@ -130,7 +140,8 @@
       have_renderered_frames_(false),
       last_frame_opaque_(false),
       painted_first_frame_(false),
-      min_buffered_frames_(limits::kMaxVideoFrames) {
+      min_buffered_frames_(limits::kMaxVideoFrames),
+      max_buffered_frames_(limits::kMaxVideoFrames) {
   DCHECK(create_video_decoders_cb_);
 }
 
@@ -186,8 +197,10 @@
   algorithm_->Reset();
   painted_first_frame_ = false;
 
-  // Reset preroll capacity so seek time is not penalized.
-  min_buffered_frames_ = limits::kMaxVideoFrames;
+  // Reset preroll capacity so seek time is not penalized. |latency_hint_|
+  // and |low_delay_| mode disable automatic preroll adjustments.
+  if (!latency_hint_.has_value() && !low_delay_)
+    min_buffered_frames_ = max_buffered_frames_ = limits::kMaxVideoFrames;
 }
 
 void VideoRendererImpl::StartPlayingFrom(base::TimeDelta timestamp) {
@@ -239,11 +252,15 @@
   }
 
   low_delay_ = ShouldUseLowDelayMode(demuxer_stream_);
-
   UMA_HISTOGRAM_BOOLEAN("Media.VideoRenderer.LowDelay", low_delay_);
-  if (low_delay_)
+  if (low_delay_) {
     MEDIA_LOG(DEBUG, media_log_) << "Video rendering in low delay mode.";
 
+    // "Low delay mode" means only one frame must be buffered to transition to
+    // BUFFERING_HAVE_ENOUGH.
+    min_buffered_frames_ = 1;
+  }
+
   // Always post |init_cb_| because |this| could be destroyed if initialization
   // failed.
   init_cb_ = BindToCurrentLoop(std::move(init_cb));
@@ -478,12 +495,116 @@
 
     // If we've underflowed, increase the number of frames required to reach
     // BUFFERING_HAVE_ENOUGH upon resume; this will help prevent us from
-    // repeatedly underflowing.
-    const size_t kMaxBufferedFrames = 2 * limits::kMaxVideoFrames;
-    if (min_buffered_frames_ < kMaxBufferedFrames) {
-      ++min_buffered_frames_;
-      DVLOG(2) << "Increased min buffered frames to " << min_buffered_frames_;
+    // repeatedly underflowing. Providing a |latency_hint_| or enabling
+    // |low_delay_| mode disables automatic increases. In these cases the site
+    // is expressing a desire to manually control/minimize the buffering
+    // threshold for HAVE_ENOUGH.
+    const size_t kMaxUnderflowGrowth = 2 * limits::kMaxVideoFrames;
+    if (!latency_hint_.has_value() && !low_delay_) {
+      DCHECK_EQ(min_buffered_frames_, max_buffered_frames_);
+
+      if (min_buffered_frames_ < kMaxUnderflowGrowth) {
+        min_buffered_frames_++;
+        DVLOG(2) << __func__ << " Underflow! Increased min_buffered_frames_: "
+                 << min_buffered_frames_;
+      }
     }
+
+    // Increase |max_buffered_frames_| irrespective of |latency_hint_| and
+    // |low_delay_| mode. Unlike |min_buffered_frames_|, this does not affect
+    // the buffering threshold for HAVE_ENOUGH. When max > min, the renderer can
+    // buffer frames _beyond_ the HAVE_ENOUGH threshold (assuming decoder is
+    // fast enough), which still helps reduce the likelihood of repeat
+    // underflow.
+    if (max_buffered_frames_ < kMaxUnderflowGrowth) {
+      max_buffered_frames_++;
+      DVLOG(2) << __func__ << " Underflow! Increased max_buffered_frames_: "
+               << max_buffered_frames_;
+    }
+  }
+}
+
+void VideoRendererImpl::SetLatencyHint(
+    base::Optional<base::TimeDelta> latency_hint) {
+  base::AutoLock auto_lock(lock_);
+
+  latency_hint_ = latency_hint;
+
+  // Permanently disable implicit |low_delay_| mode. Apps using latencyHint
+  // are taking manual control of how buffering works. Unsetting the hint
+  // will make rendering behave as if |low_delay_| were never set.
+  low_delay_ = false;
+
+  if (!latency_hint_.has_value()) {
+    // Restore default values.
+    // NOTE kMaxVideoFrames the default max, not the max overall.
+    min_buffered_frames_ = max_buffered_frames_ = limits::kMaxVideoFrames;
+    MEDIA_LOG(DEBUG, media_log_)
+        << "Video latency hint cleared. Default buffer size ("
+        << min_buffered_frames_ << " frames) restored";
+  } else if (latency_hint_->is_zero()) {
+    // Zero is a special case implying the bare minimum buffering (1 frame).
+    // We apply the hint here outside of UpdateLatencyHintBufferingCaps_Locked()
+    // to avoid needless churn since the "bare minimum" buffering doesn't
+    // fluctuate with changes to FPS.
+    min_buffered_frames_ = 1;
+    max_buffered_frames_ = limits::kMaxVideoFrames;
+    MEDIA_LOG(DEBUG, media_log_)
+        << "Video latency hint set:" << *latency_hint << ". "
+        << "Effective buffering latency: 1 frame";
+  } else {
+    // Non-zero latency hints are set here. This method will also be called
+    // for each frame in case |average_frame_druation| changes, facilitating
+    // re-computation of how many frames we should buffer to achieve the target
+    // latency. |is_latency_hint_media_logged_| ensures that we only MEDIA_LOG
+    // on the first application of this hint.
+    is_latency_hint_media_logged_ = false;
+    UpdateLatencyHintBufferingCaps_Locked(algorithm_->average_frame_duration());
+  }
+}
+
+void VideoRendererImpl::UpdateLatencyHintBufferingCaps_Locked(
+    base::TimeDelta average_frame_duration) {
+  lock_.AssertAcquired();
+
+  // NOTE: this method may be called for every frame. Only perform trivial
+  // tasks.
+
+  // This method should only be called for non-zero latency hints. Zero is hard
+  // coded to 1 frame inside SetLatencyHint().
+  DCHECK(latency_hint_.has_value() && !latency_hint_->is_zero());
+
+  // For hints > 0, we need |average_frame_duration| to determine how many
+  // frames would yield the specified target latency. This method will be called
+  // again as |average_frame_duration| changes.
+  if (average_frame_duration.is_zero())
+    return;
+
+  int latency_hint_frames =
+      std::round(latency_hint_->InMicrosecondsF() /
+                 average_frame_duration.InMicrosecondsF());
+
+  std::string clamp_string;
+  if (latency_hint_frames > kAbsoluteMaxFrames) {
+    min_buffered_frames_ = kAbsoluteMaxFrames;
+    clamp_string = " (clamped to max)";
+  } else if (latency_hint_frames < 1) {
+    min_buffered_frames_ = 1;
+    clamp_string = " (clamped to min)";
+  } else {
+    min_buffered_frames_ = latency_hint_frames;
+  }
+
+  // Use initial capacity limit if possible. Increase if needed.
+  max_buffered_frames_ = std::max(min_buffered_frames_,
+                                  static_cast<size_t>(limits::kMaxVideoFrames));
+
+  if (!is_latency_hint_media_logged_) {
+    is_latency_hint_media_logged_ = true;
+    MEDIA_LOG(DEBUG, media_log_)
+        << "Video latency hint set:" << *latency_hint_ << ". "
+        << "Effective buffering latency:"
+        << (min_buffered_frames_ * average_frame_duration) << clamp_string;
   }
 }
 
@@ -525,7 +646,7 @@
     received_end_of_stream_ = true;
     fps_estimator_.Reset();
     ReportFrameRateIfNeeded_Locked();
-  } else if ((low_delay_ || cant_read) && is_before_start_time) {
+  } else if ((min_buffered_frames_ == 1 || cant_read) && is_before_start_time) {
     // Don't accumulate frames that are earlier than the start time if we
     // won't have a chance for a better frame, otherwise we could declare
     // HAVE_ENOUGH_DATA and start playback prematurely.
@@ -556,6 +677,34 @@
     AddReadyFrame_Locked(std::move(frame));
   }
 
+  // Attempt to purge bad frames in case of underflow or backgrounding.
+  RemoveFramesForUnderflowOrBackgroundRendering();
+
+  // Paint the first frame if possible and necessary. Paint ahead of
+  // HAVE_ENOUGH_DATA to ensure the user sees the frame as early as possible.
+  // Paint before calling algorithm_->average_frame_duration(), as the call to
+  // Render() will trigger internal duration updates.
+  //
+  // We want to paint the first frame under two conditions: Either (1) we have
+  // enough frames to know it's definitely the first frame or (2) there may be
+  // no more frames coming (sometimes unless we paint one of them).
+  //
+  // We have to check both effective_frames_queued() and |is_before_start_time|
+  // since prior to the clock starting effective_frames_queued() is a guess.
+  //
+  // NOTE: Do this before using algorithm_->average_frame_duration(). This
+  // initial render will update the duration to be non-zero when provided by
+  // frame metadata.
+  if (!sink_started_ && !painted_first_frame_ && algorithm_->frames_queued() &&
+      (received_end_of_stream_ || cant_read ||
+       (algorithm_->effective_frames_queued() && !is_before_start_time))) {
+    scoped_refptr<VideoFrame> first_frame =
+        algorithm_->Render(base::TimeTicks(), base::TimeTicks(), nullptr);
+    CheckForMetadataChanges(first_frame->format(), first_frame->natural_size());
+    sink_->PaintSingleFrame(first_frame);
+    painted_first_frame_ = true;
+  }
+
   // Update average frame duration.
   base::TimeDelta frame_duration = algorithm_->average_frame_duration();
   if (frame_duration != kNoTimestamp &&
@@ -566,30 +715,16 @@
   }
   ReportFrameRateIfNeeded_Locked();
 
-  // Attempt to purge bad frames in case of underflow or backgrounding.
-  RemoveFramesForUnderflowOrBackgroundRendering();
-
   // Update any statistics since the last call.
   UpdateStats_Locked();
 
-  // Paint the first frame if possible and necessary. Paint ahead of
-  // HAVE_ENOUGH_DATA to ensure the user sees the frame as early as possible.
-  //
-  // We want to paint the first frame under two conditions: Either (1) we have
-  // enough frames to know it's definitely the first frame or (2) there may be
-  // no more frames coming (sometimes unless we paint one of them).
-  //
-  // We have to check both effective_frames_queued() and |is_before_start_time|
-  // since prior to the clock starting effective_frames_queued() is a guess.
-  if (!sink_started_ && !painted_first_frame_ && algorithm_->frames_queued() &&
-      (received_end_of_stream_ || cant_read ||
-       (algorithm_->effective_frames_queued() && !is_before_start_time))) {
-    scoped_refptr<VideoFrame> first_frame =
-        algorithm_->Render(base::TimeTicks(), base::TimeTicks(), nullptr);
-    CheckForMetadataChanges(first_frame->format(), first_frame->natural_size());
-    sink_->PaintSingleFrame(first_frame);
-    painted_first_frame_ = true;
-  }
+  // Update hint-driven buffering caps to use the latest average frame duration.
+  // NOTE: Do this before updating the buffering state below, as it may affect
+  // the outcome of HaveEnoughData_Locked().
+  // TODO(chcunningham): Duration from |algorithm_| is affected by playback
+  // rate. Consider using wall clock frame duration instead.
+  if (latency_hint_.has_value() && !latency_hint_->is_zero())
+    UpdateLatencyHintBufferingCaps_Locked(frame_duration);
 
   // Signal buffering state if we've met our conditions.
   if (buffering_state_ == BUFFERING_HAVE_NOTHING && HaveEnoughData_Locked())
@@ -610,7 +745,7 @@
   if (received_end_of_stream_)
     return true;
 
-  if (HaveReachedBufferingCap())
+  if (HaveReachedBufferingCap(min_buffered_frames_))
     return true;
 
   // If we've decoded any frames since the last render, signal have enough to
@@ -618,8 +753,10 @@
   if (was_background_rendering_ && last_frame_ready_time_ >= last_render_time_)
     return true;
 
-  if (!low_delay_ && video_decoder_stream_->CanReadWithoutStalling())
+  if (min_buffered_frames_ > 1 &&
+      video_decoder_stream_->CanReadWithoutStalling()) {
     return false;
+  }
 
   // Note: We still require an effective frame in the stalling case since this
   // method is also used to inform TransitionToHaveNothing_Locked() and thus
@@ -685,7 +822,7 @@
   if (pending_read_ || received_end_of_stream_)
     return;
 
-  if (HaveReachedBufferingCap())
+  if (HaveReachedBufferingCap(max_buffered_frames_))
     return;
 
   switch (state_) {
@@ -764,14 +901,14 @@
   client_->OnVideoFrameRateChange(current_fps);
 }
 
-bool VideoRendererImpl::HaveReachedBufferingCap() const {
+bool VideoRendererImpl::HaveReachedBufferingCap(size_t buffering_cap) const {
   DCHECK(task_runner_->BelongsToCurrentThread());
 
   // When the display rate is less than the frame rate, the effective frames
   // queued may be much smaller than the actual number of frames queued.  Here
   // we ensure that frames_queued() doesn't get excessive.
-  return algorithm_->effective_frames_queued() >= min_buffered_frames_ ||
-         algorithm_->frames_queued() >= 3 * min_buffered_frames_;
+  return algorithm_->effective_frames_queued() >= buffering_cap ||
+         algorithm_->frames_queued() >= kAbsoluteMaxFrames;
 }
 
 void VideoRendererImpl::StartSink() {
diff --git a/media/renderers/video_renderer_impl.h b/media/renderers/video_renderer_impl.h
index bfb3d5e..de528fc 100644
--- a/media/renderers/video_renderer_impl.h
+++ b/media/renderers/video_renderer_impl.h
@@ -71,6 +71,7 @@
   void StartPlayingFrom(base::TimeDelta timestamp) override;
   void OnTimeProgressing() override;
   void OnTimeStopped() override;
+  void SetLatencyHint(base::Optional<base::TimeDelta> latency_hint) override;
 
   void SetTickClockForTesting(const base::TickClock* tick_clock);
   size_t frames_queued_for_testing() const {
@@ -79,6 +80,8 @@
   size_t effective_frames_queued_for_testing() const {
     return algorithm_->effective_frames_queued();
   }
+  int min_buffered_frames_for_testing() const { return min_buffered_frames_; }
+  int max_buffered_frames_for_testing() const { return max_buffered_frames_; }
 
   // VideoRendererSink::RenderCallback implementation.
   scoped_refptr<VideoFrame> Render(base::TimeTicks deadline_min,
@@ -137,8 +140,14 @@
   // reported, and tracks what the most recently reported frame rate was.
   void ReportFrameRateIfNeeded_Locked();
 
-  // Returns true if there is no more room for additional buffered frames.
-  bool HaveReachedBufferingCap() const;
+  // Update |min_buffered_frames_| and |max_buffered_frames_| using the latest
+  // |average_frame_duration|. Should only be called when |latency_hint_| > 0.
+  void UpdateLatencyHintBufferingCaps_Locked(
+      base::TimeDelta average_frame_duration);
+
+  // Returns true if algorithm_->effective_frames_queued() >= |buffering_cap|,
+  // or when the number of ineffective frames >= kAbsoluteMaxFrames.
+  bool HaveReachedBufferingCap(size_t buffering_cap) const;
 
   // Starts or stops |sink_| respectively. Do not call while |lock_| is held.
   void StartSink();
@@ -303,22 +312,35 @@
   // Indicates if we've painted the first valid frame after StartPlayingFrom().
   bool painted_first_frame_;
 
-  // Current minimum and maximum for buffered frames. |min_buffered_frames_| is
-  // the number of frames required to transition from BUFFERING_HAVE_NOTHING to
+  // The number of frames required to transition from BUFFERING_HAVE_NOTHING to
   // BUFFERING_HAVE_ENOUGH.
   size_t min_buffered_frames_;
 
+  // The maximum number of frames to buffer. Always >= |min_buffered_frames_|.
+  // May be greater-than when |latency_hint_| set that decreases the minimum
+  // buffering limit.
+  size_t max_buffered_frames_;
+
   // Last Render() and last FrameReady() times respectively. Used to avoid
   // triggering underflow when background rendering.
   base::TimeTicks last_render_time_;
   base::TimeTicks last_frame_ready_time_;
 
-  // Running average of frame durations, without changes due to playback rate.
+  // Running average of frame durations.
   FrameRateEstimator fps_estimator_;
 
   // Last FPS, if any, reported to the client.
   base::Optional<int> last_reported_fps_;
 
+  // Value saved from last call to SetLatencyHint(). Used to recompute buffering
+  // limits as framerate fluctuates.
+  base::Optional<base::TimeDelta> latency_hint_;
+
+  // When latency_hint_ > 0, we make regular adjustments to buffering caps as
+  // |algorithm_->average_frame_duration()| fluctuates, but we only want to emit
+  // one MEDIA_LOG.
+  bool is_latency_hint_media_logged_ = false;
+
   // NOTE: Weak pointers must be invalidated before all other member variables.
   base::WeakPtrFactory<VideoRendererImpl> weak_factory_{this};
 
diff --git a/media/renderers/video_renderer_impl_unittest.cc b/media/renderers/video_renderer_impl_unittest.cc
index 8eeb090..093dccb 100644
--- a/media/renderers/video_renderer_impl_unittest.cc
+++ b/media/renderers/video_renderer_impl_unittest.cc
@@ -106,11 +106,25 @@
 
     demuxer_stream_.set_video_decoder_config(TestVideoConfig::Normal());
 
-    // We expect these to be called but we don't care how/when.
-    EXPECT_CALL(demuxer_stream_, OnRead(_))
-        .WillRepeatedly(RunOnceCallback<0>(
-            DemuxerStream::kOk,
-            scoped_refptr<DecoderBuffer>(new DecoderBuffer(0))));
+    // We expect these to be called but we don't care how/when. Tests can
+    // customize the provided buffer returned via MakeDecoderBuffer().
+    ON_CALL(demuxer_stream_, OnRead(_))
+        .WillByDefault(Invoke(this, &VideoRendererImplTest::MakeDecoderBuffer));
+  }
+
+  void MakeDecoderBuffer(DemuxerStream::ReadCB& read_cb) {
+    scoped_refptr<DecoderBuffer> decoder_buffer(new DecoderBuffer(0));
+
+    // Set |decoder_buffer| timestamp such that it won't match any of the
+    // times provided to QueueFrames(). Otherwise the default timestamp of 0 may
+    // match some frames and not others, which causes non-uniform handling in
+    // DecoderStreamTraits.
+    decoder_buffer->set_timestamp(kNoTimestamp);
+
+    // Test hook for to specify a custom buffer duration.
+    decoder_buffer->set_duration(buffer_duration_);
+
+    std::move(read_cb).Run(DemuxerStream::kOk, decoder_buffer);
   }
 
   ~VideoRendererImplTest() override = default;
@@ -337,6 +351,9 @@
 
   WallClockTimeSource time_source_;
 
+  // Duration set on DecoderBuffers. See MakeDecoderBuffer().
+  base::TimeDelta buffer_duration_;
+
  private:
   void DecodeRequested(scoped_refptr<DecoderBuffer> buffer,
                        VideoDecoder::DecodeCB& decode_cb) {
@@ -600,6 +617,7 @@
   EXPECT_CALL(mock_cb_, OnStatisticsUpdate(_)).Times(AnyNumber());
   EXPECT_CALL(mock_cb_, OnVideoNaturalSizeChange(_)).Times(1);
   EXPECT_CALL(mock_cb_, OnVideoOpacityChange(_)).Times(1);
+  EXPECT_CALL(mock_cb_, OnVideoFrameRateChange(base::Optional<int>(100)));
   StartPlayingFrom(59);
   Destroy();
 }
@@ -613,6 +631,7 @@
   EXPECT_CALL(mock_cb_, OnStatisticsUpdate(_)).Times(AnyNumber());
   EXPECT_CALL(mock_cb_, OnVideoNaturalSizeChange(_)).Times(1);
   EXPECT_CALL(mock_cb_, OnVideoOpacityChange(_)).Times(1);
+  EXPECT_CALL(mock_cb_, OnVideoFrameRateChange(base::Optional<int>(100)));
   StartPlayingFrom(61);
   Destroy();
 }
@@ -1185,7 +1204,13 @@
   Destroy();
 }
 
-enum class UnderflowTestType { NORMAL, LOW_DELAY, CANT_READ_WITHOUT_STALLING };
+enum class UnderflowTestType {
+  // Renderer will require a default amount of buffering to reach HAVE_ENOUGH.
+  NORMAL,
+  // Both of these require only a single frame to reach HAVE_ENOUGH.
+  LOW_DELAY,
+  CANT_READ_WITHOUT_STALLING
+};
 
 class UnderflowTest
     : public VideoRendererImplTest,
@@ -1194,13 +1219,13 @@
  protected:
   void SetUp() override { std::tie(test_type, underflow_type) = GetParam(); }
 
-  void BasicUnderflowTest(UnderflowTestType type,
-                          BufferingStateChangeReason underflow_type) {
-    InitializeWithLowDelay(type == UnderflowTestType::LOW_DELAY);
-    if (type == UnderflowTestType::CANT_READ_WITHOUT_STALLING)
+  void TestBufferToHaveEnoughThenUnderflow() {
+    InitializeWithLowDelay(test_type == UnderflowTestType::LOW_DELAY);
+
+    if (test_type == UnderflowTestType::CANT_READ_WITHOUT_STALLING)
       ON_CALL(*decoder_, CanReadWithoutStalling()).WillByDefault(Return(false));
 
-    QueueFrames("0 30 60 90");
+    QueueFrames("0 20 40 60");
 
     {
       WaitableMessageLoopEvent event;
@@ -1217,6 +1242,8 @@
       Mock::VerifyAndClearExpectations(&mock_cb_);
     }
 
+    // Start playing.
+    time_source_.StartTicking();
     renderer_->OnTimeProgressing();
 
     // Advance time slightly, but enough to exceed the duration of the last
@@ -1225,18 +1252,13 @@
     {
       SCOPED_TRACE("Waiting for frame drops");
       WaitableMessageLoopEvent event;
+      EXPECT_CALL(mock_cb_, FrameReceived(HasTimestampMatcher(20))).Times(0);
 
-      // Note: Starting the TimeSource will cause the old VideoRendererImpl to
-      // start rendering frames on its own thread, so the first frame may be
-      // received.
-      time_source_.StartTicking();
-      EXPECT_CALL(mock_cb_, FrameReceived(HasTimestampMatcher(30))).Times(0);
-
-      EXPECT_CALL(mock_cb_, FrameReceived(HasTimestampMatcher(60))).Times(0);
-      EXPECT_CALL(mock_cb_, FrameReceived(HasTimestampMatcher(90)))
+      EXPECT_CALL(mock_cb_, FrameReceived(HasTimestampMatcher(40))).Times(0);
+      EXPECT_CALL(mock_cb_, FrameReceived(HasTimestampMatcher(60)))
           .WillOnce(RunOnceClosure(event.GetClosure()));
       EXPECT_CALL(mock_cb_, OnStatisticsUpdate(_)).Times(AnyNumber());
-      AdvanceTimeInMs(91);
+      AdvanceTimeInMs(61);
 
       event.RunAndWait();
       Mock::VerifyAndClearExpectations(&mock_cb_);
@@ -1252,69 +1274,7 @@
       EXPECT_CALL(mock_cb_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING,
                                                    underflow_type))
           .WillOnce(RunOnceClosure(event.GetClosure()));
-      AdvanceTimeInMs(30);
-      event.RunAndWait();
-      Mock::VerifyAndClearExpectations(&mock_cb_);
-    }
-
-    // Simulate delayed buffering state callbacks.
-    renderer_->OnTimeStopped();
-    renderer_->OnTimeProgressing();
-
-    // Receiving end of stream should signal having enough.
-    {
-      SCOPED_TRACE("Waiting for BUFFERING_HAVE_ENOUGH");
-      WaitableMessageLoopEvent event;
-      EXPECT_CALL(mock_cb_, OnStatisticsUpdate(_)).Times(AnyNumber());
-      EXPECT_CALL(mock_cb_,
-                  OnBufferingStateChange(BUFFERING_HAVE_ENOUGH,
-                                         BUFFERING_CHANGE_REASON_UNKNOWN))
-          .WillOnce(RunOnceClosure(event.GetClosure()));
-      EXPECT_CALL(mock_cb_, OnEnded());
-      SatisfyPendingDecodeWithEndOfStream();
-      event.RunAndWait();
-    }
-
-    Destroy();
-  }
-
-  void UnderflowRecoveryTest(UnderflowTestType type,
-                             BufferingStateChangeReason underflow_type) {
-    InitializeWithLowDelay(type == UnderflowTestType::LOW_DELAY);
-    if (type == UnderflowTestType::CANT_READ_WITHOUT_STALLING)
-      ON_CALL(*decoder_, CanReadWithoutStalling()).WillByDefault(Return(false));
-
-    QueueFrames("0 20 40 60");
-    {
-      WaitableMessageLoopEvent event;
-      EXPECT_CALL(mock_cb_, FrameReceived(HasTimestampMatcher(0)));
-      EXPECT_CALL(mock_cb_,
-                  OnBufferingStateChange(BUFFERING_HAVE_ENOUGH,
-                                         BUFFERING_CHANGE_REASON_UNKNOWN))
-          .WillOnce(RunOnceClosure(event.GetClosure()));
-      EXPECT_CALL(mock_cb_, OnStatisticsUpdate(_)).Times(AnyNumber());
-      EXPECT_CALL(mock_cb_, OnVideoNaturalSizeChange(_)).Times(1);
-      EXPECT_CALL(mock_cb_, OnVideoOpacityChange(_)).Times(1);
-      StartPlayingFrom(0);
-      event.RunAndWait();
-      Mock::VerifyAndClearExpectations(&mock_cb_);
-    }
-
-    renderer_->OnTimeProgressing();
-    time_source_.StartTicking();
-
-    // Advance time, this should cause have nothing to be signaled.
-    {
-      SCOPED_TRACE("Waiting for BUFFERING_HAVE_NOTHING");
-      WaitableMessageLoopEvent event;
-      EXPECT_CALL(demuxer_stream_, IsReadPending())
-          .WillOnce(Return(underflow_type == DEMUXER_UNDERFLOW));
-      EXPECT_CALL(mock_cb_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING,
-                                                   underflow_type))
-          .WillOnce(RunOnceClosure(event.GetClosure()));
-      EXPECT_CALL(mock_cb_, FrameReceived(HasTimestampMatcher(60))).Times(1);
-      EXPECT_CALL(mock_cb_, OnStatisticsUpdate(_)).Times(AnyNumber());
-      AdvanceTimeInMs(79);
+      AdvanceTimeInMs(18);
       event.RunAndWait();
       Mock::VerifyAndClearExpectations(&mock_cb_);
     }
@@ -1325,43 +1285,86 @@
     EXPECT_EQ(0u, renderer_->frames_queued_for_testing());
     ASSERT_TRUE(IsReadPending());
 
-    // Queue some frames, satisfy reads, and make sure expired frames are gone
-    // when the renderer paints the first frame.
-    {
-      SCOPED_TRACE("Waiting for BUFFERING_HAVE_ENOUGH");
-      WaitableMessageLoopEvent event;
-      EXPECT_CALL(mock_cb_, FrameReceived(HasTimestampMatcher(80))).Times(1);
-      EXPECT_CALL(mock_cb_, OnStatisticsUpdate(_)).Times(AnyNumber());
-      EXPECT_CALL(mock_cb_,
-                  OnBufferingStateChange(BUFFERING_HAVE_ENOUGH,
-                                         BUFFERING_CHANGE_REASON_UNKNOWN))
-          .WillOnce(RunOnceClosure(event.GetClosure()));
-      EXPECT_CALL(mock_cb_, OnVideoFrameRateChange(base::Optional<int>(50)));
-
-      // Note: In the normal underflow case we queue 5 frames here instead of
-      // four since the underflow increases the number of required frames to
-      // reach the have enough state.
-      if (type == UnderflowTestType::NORMAL)
-        QueueFrames("80 100 120 140 160");
-      else
-        QueueFrames("40 60 80 90 100");
-      SatisfyPendingDecode();
-      event.RunAndWait();
+    // Stopping time signals a confirmed underflow to VRI. Verify updates to
+    // buffering limits.
+    switch (test_type) {
+      // In the normal and cant_read modes, min and max buffered frames should
+      // always be equal, and both should increase upon underflow.
+      case UnderflowTestType::NORMAL:
+      case UnderflowTestType::CANT_READ_WITHOUT_STALLING:
+        EXPECT_EQ(renderer_->min_buffered_frames_for_testing(),
+                  limits::kMaxVideoFrames + 1);
+        EXPECT_EQ(renderer_->max_buffered_frames_for_testing(),
+                  limits::kMaxVideoFrames + 1);
+        break;
+      // In low_delay mode only the max should increase while min remains 1.
+      case UnderflowTestType::LOW_DELAY:
+        EXPECT_EQ(renderer_->min_buffered_frames_for_testing(), 1);
+        EXPECT_EQ(renderer_->max_buffered_frames_for_testing(),
+                  limits::kMaxVideoFrames + 1);
+        break;
     }
-
-    Destroy();
   }
 
   UnderflowTestType test_type;
   BufferingStateChangeReason underflow_type;
 };
 
-TEST_P(UnderflowTest, BasicUnderflowTest) {
-  BasicUnderflowTest(test_type, underflow_type);
+TEST_P(UnderflowTest, UnderflowAndEosTest) {
+  TestBufferToHaveEnoughThenUnderflow();
+
+  // Receiving end of stream should signal having enough.
+  {
+    SCOPED_TRACE("Waiting for BUFFERING_HAVE_ENOUGH");
+    WaitableMessageLoopEvent event;
+    EXPECT_CALL(mock_cb_, OnStatisticsUpdate(_)).Times(AnyNumber());
+    EXPECT_CALL(mock_cb_,
+                OnBufferingStateChange(BUFFERING_HAVE_ENOUGH,
+                                       BUFFERING_CHANGE_REASON_UNKNOWN))
+        .WillOnce(RunOnceClosure(event.GetClosure()));
+    EXPECT_CALL(mock_cb_, OnEnded());
+    SatisfyPendingDecodeWithEndOfStream();
+    event.RunAndWait();
+  }
+
+  Destroy();
 }
 
-TEST_P(UnderflowTest, UnderflowRecoveryTest) {
-  UnderflowRecoveryTest(test_type, underflow_type);
+TEST_P(UnderflowTest, UnderflowAndRecoverTest) {
+  TestBufferToHaveEnoughThenUnderflow();
+
+  // Queue some frames, satisfy reads, and make sure expired frames are gone
+  // when the renderer paints the first frame.
+  {
+    SCOPED_TRACE("Waiting for BUFFERING_HAVE_ENOUGH");
+    WaitableMessageLoopEvent event;
+    EXPECT_CALL(mock_cb_, FrameReceived(HasTimestampMatcher(80))).Times(1);
+    EXPECT_CALL(mock_cb_, OnStatisticsUpdate(_)).Times(AnyNumber());
+    EXPECT_CALL(mock_cb_,
+                OnBufferingStateChange(BUFFERING_HAVE_ENOUGH,
+                                       BUFFERING_CHANGE_REASON_UNKNOWN))
+        .WillOnce(RunOnceClosure(event.GetClosure()));
+
+    switch (test_type) {
+      // In the normal underflow case we queue 5 frames here instead of four
+      // since the underflow increases the number of required frames to reach
+      // the have enough state.
+      case UnderflowTestType::NORMAL:
+        QueueFrames("80 100 120 140 160");
+        EXPECT_CALL(mock_cb_, OnVideoFrameRateChange(base::Optional<int>(50)));
+        break;
+      // In either of these modes the HAVE_ENOUGH transition should still
+      // occur with a single frame.
+      case UnderflowTestType::LOW_DELAY:
+      case UnderflowTestType::CANT_READ_WITHOUT_STALLING:
+        QueueFrames("80");
+        break;
+    }
+    SatisfyPendingDecode();
+    event.RunAndWait();
+  }
+
+  Destroy();
 }
 
 INSTANTIATE_TEST_SUITE_P(
@@ -1372,4 +1375,333 @@
                    UnderflowTestType::CANT_READ_WITHOUT_STALLING),
             Values(DEMUXER_UNDERFLOW, DECODER_UNDERFLOW)));
 
+class VideoRendererLatencyHintTest : public VideoRendererImplTest {
+ public:
+  void VerifyDefaultRebufferingBehavior(int start_playing_from) {
+    // Keep it simple. Only call this if you're starting from empty.
+    DCHECK_EQ(renderer_->effective_frames_queued_for_testing(), 0u);
+
+    // Initial frames should trigger various callbacks.
+    EXPECT_CALL(mock_cb_,
+                FrameReceived(HasTimestampMatcher(start_playing_from)));
+    EXPECT_CALL(mock_cb_, OnStatisticsUpdate(_)).Times(AnyNumber());
+    EXPECT_CALL(mock_cb_, OnVideoNaturalSizeChange(_)).Times(AnyNumber());
+    EXPECT_CALL(mock_cb_, OnVideoOpacityChange(_)).Times(AnyNumber());
+    EXPECT_CALL(mock_cb_, OnVideoFrameRateChange(_)).Times(AnyNumber());
+
+    // Queue 3 frames, 20 msec apart. Stop 1 shy of min_buffered_frames_.
+    ASSERT_EQ(renderer_->min_buffered_frames_for_testing(), 4);
+    int frame_time = start_playing_from;
+    for (int i = 0; i < 3; i++) {
+      QueueFrames(base::NumberToString(frame_time));
+      frame_time += 20;
+    }
+
+    // Verify no transition to HAVE_ENOUGH since 3 < |min_buffered_frames_|
+    EXPECT_CALL(mock_cb_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH, _))
+        .Times(0);
+
+    StartPlayingFrom(start_playing_from);
+    base::RunLoop().RunUntilIdle();
+    EXPECT_EQ(renderer_->effective_frames_queued_for_testing(), 3u);
+    Mock::VerifyAndClearExpectations(&mock_cb_);
+
+    // Queuing one extra frame should trigger the transition.
+    QueueFrames(base::NumberToString(frame_time));
+    SatisfyPendingDecode();
+    EXPECT_CALL(mock_cb_, OnStatisticsUpdate(_)).Times(AnyNumber());
+    EXPECT_CALL(mock_cb_, OnVideoFrameRateChange(_)).Times(AnyNumber());
+    EXPECT_CALL(mock_cb_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH, _));
+    base::RunLoop().RunUntilIdle();
+    Mock::VerifyAndClearExpectations(&mock_cb_);
+  }
+};
+
+// Test default HaveEnough transition when no latency hint is set.
+TEST_F(VideoRendererLatencyHintTest, HaveEnough_NoLatencyHint) {
+  Initialize();
+  VerifyDefaultRebufferingBehavior(0);
+  Destroy();
+}
+
+// Test early HaveEnough transition when low latency hint is set.
+TEST_F(VideoRendererLatencyHintTest, HaveEnough_LowLatencyHint) {
+  Initialize();
+
+  // Set latencyHint to bare minimum.
+  renderer_->SetLatencyHint(base::TimeDelta());
+
+  // Initial frames should trigger various callbacks.
+  EXPECT_CALL(mock_cb_, FrameReceived(HasTimestampMatcher(0)));
+  EXPECT_CALL(mock_cb_, OnStatisticsUpdate(_)).Times(1);
+  EXPECT_CALL(mock_cb_, OnVideoNaturalSizeChange(_)).Times(1);
+  EXPECT_CALL(mock_cb_, OnVideoOpacityChange(_)).Times(1);
+  EXPECT_CALL(mock_cb_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH, _));
+
+  // Only 1 frame should be needed to trigger have enough.
+  ASSERT_EQ(renderer_->min_buffered_frames_for_testing(), 1);
+  QueueFrames("0");
+
+  StartPlayingFrom(0);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(renderer_->effective_frames_queued_for_testing(), 1u);
+  Mock::VerifyAndClearExpectations(&mock_cb_);
+
+  // Verify latency hint doesn't reduce our ability to buffer beyond the
+  // 1-frame HAVE_ENOUGH (i.e. don't throttle decoding in the name of latency).
+  EXPECT_EQ(renderer_->max_buffered_frames_for_testing(), 4);
+  EXPECT_CALL(mock_cb_, OnStatisticsUpdate(_)).Times(3);
+  QueueFrames("10 20 30");
+  WaitForPendingDecode();
+  SatisfyPendingDecode();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(renderer_->frames_queued_for_testing(), 4u);
+
+  // Unset latencyHint, to verify default behavior.
+  renderer_->SetLatencyHint(base::nullopt);
+
+  // Flush to return to clean slate.
+  EXPECT_CALL(mock_cb_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING, _));
+  Flush();
+
+  VerifyDefaultRebufferingBehavior(1000);
+
+  Destroy();
+}
+
+// Test late HaveEnough transition when high latency hint is set.
+TEST_F(VideoRendererLatencyHintTest, HaveEnough_HighLatencyHint) {
+  Initialize();
+
+  // We must provide a |buffer_duration_| for the latencyHint to take effect
+  // immediately. The VideoRendererAlgorithm will eventually provide a PTS-delta
+  // duration, but not until after we've started rendering.
+  buffer_duration_ = base::TimeDelta::FromMilliseconds(30);
+
+  // Set latencyHint to a large value.
+  renderer_->SetLatencyHint(base::TimeDelta::FromMilliseconds(400));
+
+  // Initial frames should trigger various callbacks.
+  EXPECT_CALL(mock_cb_, FrameReceived(HasTimestampMatcher(0)));
+  EXPECT_CALL(mock_cb_, OnVideoNaturalSizeChange(_)).Times(1);
+  EXPECT_CALL(mock_cb_, OnVideoOpacityChange(_)).Times(1);
+  EXPECT_CALL(mock_cb_, OnStatisticsUpdate(_)).Times(AnyNumber());
+
+  // Queue 12 frames, each 30 ms apart. At this framerate, 400ms rounds to 13
+  // frames, so 12 frames should be 1 shy of the HaveEnough threshold.
+  EXPECT_CALL(mock_cb_, OnVideoFrameRateChange(base::Optional<int>(33)));
+  EXPECT_CALL(mock_cb_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH, _))
+      .Times(0);
+  QueueFrames("0 30 60 90 120 150 180 210 240 270 300 330");
+
+  StartPlayingFrom(0);
+  base::RunLoop().RunUntilIdle();
+  ASSERT_EQ(renderer_->min_buffered_frames_for_testing(), 13);
+  EXPECT_EQ(renderer_->effective_frames_queued_for_testing(), 12u);
+  Mock::VerifyAndClearExpectations(&mock_cb_);
+
+  // Queue 1 additional frame and verify HaveEnough threshold is reached.
+  EXPECT_CALL(mock_cb_, OnStatisticsUpdate(_)).Times(AnyNumber());
+  EXPECT_CALL(mock_cb_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH, _));
+  QueueFrames("360");
+  SatisfyPendingDecode();
+  base::RunLoop().RunUntilIdle();
+  Mock::VerifyAndClearExpectations(&mock_cb_);
+
+  // Unset latencyHint, to verify default behavior.
+  renderer_->SetLatencyHint(base::nullopt);
+
+  // Flush to return to clean slate.
+  EXPECT_CALL(mock_cb_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING, _));
+  Flush();
+  Mock::VerifyAndClearExpectations(&mock_cb_);
+
+  VerifyDefaultRebufferingBehavior(1000);
+
+  Destroy();
+}
+
+// Test updates to buffering limits upon underflow when latency hint set.
+TEST_F(VideoRendererLatencyHintTest,
+       LatencyHintUnderflowUpdatesMaxBufferingLimit) {
+  // Enable low delay mode. Low delay mode is tested separately.
+  InitializeWithLowDelay(true);
+  EXPECT_EQ(renderer_->min_buffered_frames_for_testing(), 1);
+
+  // We must provide a |buffer_duration_| for the latencyHint to take effect
+  // immediately. The VideoRendererAlgorithm will eventually provide a PTS-delta
+  // duration, but not until after we've started rendering.
+  buffer_duration_ = base::TimeDelta::FromMilliseconds(30);
+
+  // Set latency hint to a medium value.
+  renderer_->SetLatencyHint(base::TimeDelta::FromMilliseconds(200));
+
+  // Queue up enough frames to trigger HAVE_ENOUGH. Each frame is 30 ms apart.
+  // At this spacing, 200ms rounds to 7 frames.
+  EXPECT_CALL(mock_cb_, FrameReceived(HasTimestampMatcher(0)));
+  EXPECT_CALL(mock_cb_, OnVideoNaturalSizeChange(_)).Times(1);
+  EXPECT_CALL(mock_cb_, OnVideoOpacityChange(_)).Times(1);
+  EXPECT_CALL(mock_cb_, OnStatisticsUpdate(_)).Times(AnyNumber());
+  EXPECT_CALL(mock_cb_, OnVideoFrameRateChange(base::Optional<int>(33)));
+  EXPECT_CALL(mock_cb_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH, _));
+  QueueFrames("0 30 60 90 120 150 180");
+  StartPlayingFrom(0);
+  base::RunLoop().RunUntilIdle();
+  ASSERT_EQ(renderer_->min_buffered_frames_for_testing(), 7);
+  EXPECT_EQ(renderer_->effective_frames_queued_for_testing(), 7u);
+  Mock::VerifyAndClearExpectations(&mock_cb_);
+
+  // Advance time to trigger HAVE_NOTHING (underflow).
+  {
+    SCOPED_TRACE("Waiting for BUFFERING_HAVE_NOTHING");
+    WaitableMessageLoopEvent event;
+    EXPECT_CALL(mock_cb_, FrameReceived(HasTimestampMatcher(180)));
+    EXPECT_CALL(demuxer_stream_, IsReadPending()).WillOnce(Return(true));
+    EXPECT_CALL(mock_cb_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING,
+                                                 DEMUXER_UNDERFLOW))
+        .WillOnce(RunOnceClosure(event.GetClosure()));
+    renderer_->OnTimeProgressing();
+    time_source_.StartTicking();
+    AdvanceTimeInMs(300);
+    event.RunAndWait();
+    Mock::VerifyAndClearExpectations(&mock_cb_);
+  }
+
+  // Simulate delayed buffering state callbacks.
+  time_source_.StopTicking();
+  renderer_->OnTimeStopped();
+
+  // When latency hint set the max should increase while min remains steady
+  // (user controls the min via hint).
+  EXPECT_EQ(renderer_->min_buffered_frames_for_testing(), 7);
+  EXPECT_EQ(renderer_->max_buffered_frames_for_testing(), 7 + 1);
+
+  Destroy();
+}
+
+// Test that latency hint overrides low delay mode.
+TEST_F(VideoRendererLatencyHintTest, LatencyHintOverridesLowDelay) {
+  // Enable low delay mode. Low delay mode is tested separately.
+  InitializeWithLowDelay(true);
+  EXPECT_EQ(renderer_->min_buffered_frames_for_testing(), 1);
+
+  // We must provide a |buffer_duration_| for the latencyHint to take effect
+  // immediately. The VideoRendererAlgorithm will eventually provide a PTS-delta
+  // duration, but not until after we've started rendering.
+  buffer_duration_ = base::TimeDelta::FromMilliseconds(30);
+
+  // Set latency hint to a medium value.
+  renderer_->SetLatencyHint(base::TimeDelta::FromMilliseconds(200));
+
+  // Initial frames should trigger various callbacks.
+  EXPECT_CALL(mock_cb_, FrameReceived(HasTimestampMatcher(0)));
+  EXPECT_CALL(mock_cb_, OnVideoNaturalSizeChange(_)).Times(1);
+  EXPECT_CALL(mock_cb_, OnVideoOpacityChange(_)).Times(1);
+  EXPECT_CALL(mock_cb_, OnStatisticsUpdate(_)).Times(AnyNumber());
+
+  // Queue 6 frames, each 30 ms apart. At this spacing, 200ms rounds to
+  // 7 frames, so 6 frames should be 1 shy of the HaveEnough threshold. Verify
+  // that HAVE_ENOUGH is not triggered in spite of being initialized with low
+  // delay mode.
+  EXPECT_CALL(mock_cb_, OnVideoFrameRateChange(base::Optional<int>(33)));
+  EXPECT_CALL(mock_cb_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH, _))
+      .Times(0);
+  QueueFrames("0 30 60 90 120 150");
+  StartPlayingFrom(0);
+  base::RunLoop().RunUntilIdle();
+  ASSERT_EQ(renderer_->min_buffered_frames_for_testing(), 7);
+  EXPECT_EQ(renderer_->effective_frames_queued_for_testing(), 6u);
+  Mock::VerifyAndClearExpectations(&mock_cb_);
+
+  // Queue 1 additional frame and verify HaveEnough threshold is reached.
+  EXPECT_CALL(mock_cb_, OnStatisticsUpdate(_)).Times(AnyNumber());
+  EXPECT_CALL(mock_cb_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH, _));
+  QueueFrames("180");
+  SatisfyPendingDecode();
+  base::RunLoop().RunUntilIdle();
+  Mock::VerifyAndClearExpectations(&mock_cb_);
+
+  // Unset latencyHint, to verify default behavior. NOTE: low delay mode is not
+  // restored when latency hint unset.
+  renderer_->SetLatencyHint(base::nullopt);
+
+  // Flush to return to clean slate.
+  EXPECT_CALL(mock_cb_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING, _));
+  Flush();
+  Mock::VerifyAndClearExpectations(&mock_cb_);
+
+  VerifyDefaultRebufferingBehavior(1000);
+
+  Destroy();
+}
+
+// Test that !CanReadWithoutStalling() overrides latency hint.
+TEST_F(VideoRendererLatencyHintTest,
+       CantReadWithoutStallingOverridesLatencyHint) {
+  Initialize();
+
+  // Let decoder indicate that it CANT read without stalling, meaning we should
+  // enter HAVE_ENOUGH with just one effective frame (waiting for more frames
+  // will stall the decoder).
+  ON_CALL(*decoder_, CanReadWithoutStalling()).WillByDefault(Return(false));
+
+  // We must provide a |buffer_duration_| for the latencyHint to take effect
+  // immediately. The VideoRendererAlgorithm will eventually provide a PTS-delta
+  // duration, but not until after we've started rendering.
+  buffer_duration_ = base::TimeDelta::FromMilliseconds(30);
+
+  // Set latency hint to a medium value. At a spacing of 30ms this would set
+  // the HAVE_ENOUGH threshold to 4 frames.
+  renderer_->SetLatencyHint(base::TimeDelta::FromMilliseconds(200));
+
+  // Initial frames should trigger various callbacks.
+  EXPECT_CALL(mock_cb_, FrameReceived(HasTimestampMatcher(0)));
+  EXPECT_CALL(mock_cb_, OnVideoNaturalSizeChange(_)).Times(1);
+  EXPECT_CALL(mock_cb_, OnVideoOpacityChange(_)).Times(1);
+  EXPECT_CALL(mock_cb_, OnStatisticsUpdate(_)).Times(AnyNumber());
+
+  // Queue 1 frame. This is well short of what the latency hint would require,
+  // but we CANT READ WITHOUT STALLING, so expect a transition to HAVE_ENOUGH
+  // after just 1 frame.
+  EXPECT_CALL(mock_cb_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH, _));
+  QueueFrames("0");
+  StartPlayingFrom(0);
+  base::RunLoop().RunUntilIdle();
+  ASSERT_EQ(renderer_->min_buffered_frames_for_testing(), 7);
+  EXPECT_EQ(renderer_->effective_frames_queued_for_testing(), 1u);
+  Mock::VerifyAndClearExpectations(&mock_cb_);
+
+  // Queue some additional frames, verify buffering state holds at HAVE_ENOUGH.
+  QueueFrames("30 60 90 120");
+  EXPECT_CALL(mock_cb_, OnStatisticsUpdate(_)).Times(AnyNumber());
+  EXPECT_CALL(mock_cb_, OnBufferingStateChange(_, _)).Times(0);
+  // SatisfyPendingDecode();
+  base::RunLoop().RunUntilIdle();
+  Mock::VerifyAndClearExpectations(&mock_cb_);
+
+  // Unset latency hint to verify 1-frame HAVE_ENOUGH threshold is maintained.
+  renderer_->SetLatencyHint(base::nullopt);
+
+  // Flush to return to clean slate.
+  EXPECT_CALL(mock_cb_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING, _));
+  Flush();
+  Mock::VerifyAndClearExpectations(&mock_cb_);
+
+  // Expect HAVE_ENOUGH (and various other callbacks) again.
+  EXPECT_CALL(mock_cb_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH, _));
+  EXPECT_CALL(mock_cb_, FrameReceived(HasTimestampMatcher(1000)));
+  EXPECT_CALL(mock_cb_, OnStatisticsUpdate(_)).Times(AnyNumber());
+  EXPECT_CALL(mock_cb_, OnVideoFrameRateChange(_)).Times(AnyNumber());
+
+  // Queue 1 frame.
+  QueueFrames("1000");
+  StartPlayingFrom(1000);
+  base::RunLoop().RunUntilIdle();
+  ASSERT_EQ(renderer_->min_buffered_frames_for_testing(), 4);
+  EXPECT_EQ(renderer_->effective_frames_queued_for_testing(), 1u);
+  Mock::VerifyAndClearExpectations(&mock_cb_);
+
+  Destroy();
+}
+
 }  // namespace media
diff --git a/mojo/public/tools/bindings/chromium_bindings_configuration.gni b/mojo/public/tools/bindings/chromium_bindings_configuration.gni
index 98c78e5..2eb03f7 100644
--- a/mojo/public/tools/bindings/chromium_bindings_configuration.gni
+++ b/mojo/public/tools/bindings/chromium_bindings_configuration.gni
@@ -34,10 +34,7 @@
   "//services/resource_coordinator/public/cpp/typemaps.gni",
   "//services/service_manager/public/cpp/typemaps.gni",
   "//services/tracing/public/mojom/typemaps.gni",
-  "//services/viz/privileged/cpp/typemaps.gni",
-  "//services/viz/privileged/mojom/compositing/typemaps.gni",
   "//services/viz/public/cpp/compositing/typemaps.gni",
-  "//services/viz/public/cpp/hit_test/typemaps.gni",
   "//skia/public/mojom/typemaps.gni",
   "//third_party/blink/common/typemaps.gni",
   "//third_party/blink/public/public_typemaps.gni",
diff --git a/net/BUILD.gn b/net/BUILD.gn
index d64896d..b135b51b 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -611,8 +611,6 @@
       "disk_cache/blockfile/storage_block-inl.h",
       "disk_cache/blockfile/storage_block.h",
       "disk_cache/blockfile/stress_support.h",
-      "disk_cache/blockfile/trace.cc",
-      "disk_cache/blockfile/trace.h",
       "disk_cache/cache_util.cc",
       "disk_cache/cache_util.h",
       "disk_cache/disk_cache.cc",
diff --git a/net/base/features.cc b/net/base/features.cc
index 2e41e84..169f251 100644
--- a/net/base/features.cc
+++ b/net/base/features.cc
@@ -57,11 +57,11 @@
 const base::FeatureParam<int> kEsniDnsMaxRelativeAdditionalWaitPercent{
     &kRequestEsniDnsRecords, "EsniDnsMaxRelativeAdditionalWaitPercent", 5};
 
-const base::Feature kSameSiteByDefaultCookies{
-    "SameSiteByDefaultCookies", base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kSameSiteByDefaultCookies{"SameSiteByDefaultCookies",
+                                              base::FEATURE_ENABLED_BY_DEFAULT};
 
 const base::Feature kCookiesWithoutSameSiteMustBeSecure{
-    "CookiesWithoutSameSiteMustBeSecure", base::FEATURE_DISABLED_BY_DEFAULT};
+    "CookiesWithoutSameSiteMustBeSecure", base::FEATURE_ENABLED_BY_DEFAULT};
 
 const base::Feature kShortLaxAllowUnsafeThreshold{
     "ShortLaxAllowUnsafeThreshold", base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/net/disk_cache/blockfile/backend_impl.cc b/net/disk_cache/blockfile/backend_impl.cc
index 342795d3..f845fa78 100644
--- a/net/disk_cache/blockfile/backend_impl.cc
+++ b/net/disk_cache/blockfile/backend_impl.cc
@@ -31,6 +31,7 @@
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "base/trace_event/process_memory_dump.h"
+#include "base/trace_event/trace_event.h"
 #include "net/base/net_errors.h"
 #include "net/disk_cache/backend_cleanup_tracker.h"
 #include "net/disk_cache/blockfile/disk_format.h"
@@ -172,7 +173,9 @@
       consider_evicting_at_op_end_(false),
       net_log_(net_log),
       done_(base::WaitableEvent::ResetPolicy::MANUAL,
-            base::WaitableEvent::InitialState::NOT_SIGNALED) {}
+            base::WaitableEvent::InitialState::NOT_SIGNALED) {
+  TRACE_EVENT0("disk_cache", "BackendImpl::BackendImpl");
+}
 
 BackendImpl::BackendImpl(
     const base::FilePath& path,
@@ -200,9 +203,12 @@
       consider_evicting_at_op_end_(false),
       net_log_(net_log),
       done_(base::WaitableEvent::ResetPolicy::MANUAL,
-            base::WaitableEvent::InitialState::NOT_SIGNALED) {}
+            base::WaitableEvent::InitialState::NOT_SIGNALED) {
+  TRACE_EVENT0("disk_cache", "BackendImpl::BackendImpl");
+}
 
 BackendImpl::~BackendImpl() {
+  TRACE_EVENT0("disk_cache", "BackendImpl::~BackendImpl");
   if (user_flags_ & kNoRandom) {
     // This is a unit test, so we want to be strict about not leaking entries
     // and completing all the work.
@@ -232,6 +238,8 @@
 }
 
 int BackendImpl::SyncInit() {
+  TRACE_EVENT0("disk_cache", "BackendImpl::SyncInit");
+
 #if defined(NET_BUILD_STRESS_CACHE)
   // Start evictions right away.
   up_ticks_ = kTrimDelay * 2;
@@ -252,12 +260,10 @@
   bool should_create_timer = false;
   if (!restarted_) {
     buffer_bytes_ = 0;
-    trace_object_ = TraceObject::GetTraceObject();
     should_create_timer = true;
   }
 
   init_ = true;
-  Trace("Init");
 
   if (data_->header.experiment != NO_EXPERIMENT &&
       GetCacheType() != net::DISK_CACHE) {
@@ -344,7 +350,8 @@
 
 void BackendImpl::CleanupCache() {
   DCHECK(background_queue_.BackgroundIsCurrentSequence());
-  Trace("Backend Cleanup");
+  TRACE_EVENT0("disk_cache", "BackendImpl::CleanupCache");
+
   eviction_.Stop();
   timer_.reset();
 
@@ -417,6 +424,8 @@
 
 int BackendImpl::SyncDoomEntriesBetween(const base::Time initial_time,
                                         const base::Time end_time) {
+  TRACE_EVENT0("disk_cache", "BackendImpl::SyncDoomEntriesBetween");
+
   DCHECK_NE(net::APP_CACHE, GetCacheType());
   if (end_time.is_null())
     return SyncDoomEntriesSince(initial_time);
@@ -449,6 +458,8 @@
 }
 
 int BackendImpl::SyncCalculateSizeOfAllEntries() {
+  TRACE_EVENT0("disk_cache", "BackendImpl::SyncCalculateSizeOfAllEntries");
+
   DCHECK_NE(net::APP_CACHE, GetCacheType());
   if (disabled_)
     return net::ERR_FAILED;
@@ -459,6 +470,8 @@
 // We use OpenNextEntryImpl to retrieve elements from the cache, until we get
 // entries that are too old.
 int BackendImpl::SyncDoomEntriesSince(const base::Time initial_time) {
+  TRACE_EVENT0("disk_cache", "BackendImpl::SyncDoomEntriesSince");
+
   DCHECK_NE(net::APP_CACHE, GetCacheType());
   if (disabled_)
     return net::ERR_FAILED;
@@ -485,6 +498,8 @@
 
 int BackendImpl::SyncOpenNextEntry(Rankings::Iterator* iterator,
                                    scoped_refptr<EntryImpl>* next_entry) {
+  TRACE_EVENT0("disk_cache", "BackendImpl::SyncOpenNextEntry");
+
   *next_entry = OpenNextEntryImpl(iterator);
   return (*next_entry) ? net::OK : net::ERR_FAILED;
 }
@@ -507,12 +522,13 @@
 }
 
 scoped_refptr<EntryImpl> BackendImpl::OpenEntryImpl(const std::string& key) {
+  TRACE_EVENT0("disk_cache", "BackendImpl::OpenEntryImpl");
+
   if (disabled_)
     return nullptr;
 
   TimeTicks start = TimeTicks::Now();
   uint32_t hash = base::PersistentHash(key);
-  Trace("Open hash 0x%x", hash);
 
   bool error;
   scoped_refptr<EntryImpl> cache_entry =
@@ -535,8 +551,6 @@
   eviction_.OnOpenEntry(cache_entry.get());
   entry_count_++;
 
-  Trace("Open hash 0x%x end: 0x%x", hash,
-        cache_entry->entry()->address().value());
   CACHE_UMA(AGE_MS, "OpenTime", 0, start);
   CACHE_UMA(COUNTS_10000, "AllOpenBySize.Hit", 0, current_size);
   CACHE_UMA(HOURS, "AllOpenByTotalHours.Hit", 0,
@@ -548,12 +562,13 @@
 }
 
 scoped_refptr<EntryImpl> BackendImpl::CreateEntryImpl(const std::string& key) {
+  TRACE_EVENT0("disk_cache", "BackendImpl::CreateEntryImpl");
+
   if (disabled_ || key.empty())
     return nullptr;
 
   TimeTicks start = TimeTicks::Now();
   uint32_t hash = base::PersistentHash(key);
-  Trace("Create hash 0x%x", hash);
 
   scoped_refptr<EntryImpl> parent;
   Addr entry_address(data_->table[hash & mask_]);
@@ -638,7 +653,6 @@
 
   CACHE_UMA(AGE_MS, "CreateTime", 0, start);
   stats_.OnEvent(Stats::CREATE_HIT);
-  Trace("create entry hit ");
   FlushIndex();
   return cache_entry;
 }
@@ -746,6 +760,7 @@
 }
 
 bool BackendImpl::CreateExternalFile(Addr* address) {
+  TRACE_EVENT0("disk_cache", "BackendImpl::CreateExternalFile");
   int file_number = data_->header.last_file + 1;
   Addr file_address(0);
   bool success = false;
@@ -827,8 +842,6 @@
       MatchEntry(key, hash, true, entry_addr, &error);
   CacheAddr child(entry->GetNextAddress());
 
-  Trace("Doom entry 0x%p", entry);
-
   if (!entry->doomed()) {
     // We may have doomed this entry from within MatchEntry.
     eviction_.OnDoomEntry(entry);
@@ -892,7 +905,6 @@
 
   DCHECK_NE(ENTRY_NORMAL, entry->entry()->Data()->state);
 
-  Trace("Remove entry 0x%p", entry);
   eviction_.OnDestroyEntry(entry);
   DecreaseNumEntries();
 }
@@ -1524,6 +1536,8 @@
 }
 
 void BackendImpl::RestartCache(bool failure) {
+  TRACE_EVENT0("disk_cache", "BackendImpl::RestartCache");
+
   int64_t errors = stats_.GetCounter(Stats::FATAL_ERROR);
   int64_t full_dooms = stats_.GetCounter(Stats::DOOM_CACHE);
   int64_t partial_dooms = stats_.GetCounter(Stats::DOOM_RECENT);
@@ -1632,11 +1646,6 @@
   // Prevent overwriting the dirty flag on the destructor.
   cache_entry->SetDirtyFlag(GetCurrentEntryId());
 
-  if (cache_entry->dirty()) {
-    Trace("Dirty entry 0x%p 0x%x", reinterpret_cast<void*>(cache_entry.get()),
-          address.value());
-  }
-
   open_entries_[address.value()] = cache_entry.get();
 
   cache_entry->BeginLogging(net_log_, false);
@@ -1649,6 +1658,8 @@
                                                  bool find_parent,
                                                  Addr entry_addr,
                                                  bool* match_error) {
+  TRACE_EVENT0("disk_cache", "BackendImpl::MatchEntry");
+
   Addr address(data_->table[hash & mask_]);
   scoped_refptr<EntryImpl> cache_entry, parent_entry;
   bool found = false;
@@ -1662,7 +1673,6 @@
     if (visited.find(address.value()) != visited.end()) {
       // It's possible for a buggy version of the code to write a loop. Just
       // break it.
-      Trace("Hash collision loop 0x%x", address.value());
       address.set_value(0);
       parent_entry->SetNextAddress(address);
     }
@@ -1689,16 +1699,11 @@
         data_->table[hash & mask_] = child.value();
       }
 
-      Trace("MatchEntry dirty %d 0x%x 0x%x", find_parent, entry_addr.value(),
-            address.value());
-
       if (!error) {
         // It is important to call DestroyInvalidEntry after removing this
         // entry from the table.
         DestroyInvalidEntry(cache_entry.get());
         cache_entry = nullptr;
-      } else {
-        Trace("NewEntry failed on MatchEntry 0x%x", address.value());
       }
 
       // Restart the search.
@@ -1713,7 +1718,6 @@
         cache_entry = nullptr;
       found = true;
       if (find_parent && entry_addr.value() != address.value()) {
-        Trace("Entry not on the index 0x%x", address.value());
         *match_error = true;
         parent_entry = nullptr;
       }
@@ -1816,7 +1820,6 @@
   if (ENTRY_NORMAL == deleted_entry->entry()->Data()->state) {
     deleted_entry = nullptr;
     stats_.OnEvent(Stats::CREATE_MISS);
-    Trace("create entry miss ");
     return nullptr;
   }
 
@@ -1827,13 +1830,11 @@
   entry_count_++;
 
   stats_.OnEvent(Stats::RESURRECT_HIT);
-  Trace("Resurrect entry hit ");
   return deleted_entry;
 }
 
 void BackendImpl::DestroyInvalidEntry(EntryImpl* entry) {
   LOG(WARNING) << "Destroying invalid entry.";
-  Trace("Destroying invalid entry 0x%p", entry);
 
   entry->SetPointerForInvalidEntry(GetCurrentEntryId());
 
@@ -2098,7 +2099,6 @@
     }
   }
 
-  Trace("CheckAllEntries End");
   if (num_entries + num_dirty != data_->header.num_entries) {
     LOG(ERROR) << "Number of entries " << num_entries << " " << num_dirty <<
                   " " << data_->header.num_entries;
diff --git a/net/disk_cache/blockfile/backend_impl.h b/net/disk_cache/blockfile/backend_impl.h
index a48da439e..3a7e77a 100644
--- a/net/disk_cache/blockfile/backend_impl.h
+++ b/net/disk_cache/blockfile/backend_impl.h
@@ -22,7 +22,6 @@
 #include "net/disk_cache/blockfile/rankings.h"
 #include "net/disk_cache/blockfile/stats.h"
 #include "net/disk_cache/blockfile/stress_support.h"
-#include "net/disk_cache/blockfile/trace.h"
 #include "net/disk_cache/disk_cache.h"
 
 namespace base {
@@ -426,7 +425,6 @@
   Stats stats_;  // Usage statistics.
   std::unique_ptr<base::RepeatingTimer> timer_;  // Usage timer.
   base::WaitableEvent done_;  // Signals the end of background work.
-  scoped_refptr<TraceObject> trace_object_;  // Initializes internal tracing.
   base::WeakPtrFactory<BackendImpl> ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(BackendImpl);
diff --git a/net/disk_cache/blockfile/block_files.cc b/net/disk_cache/blockfile/block_files.cc
index e4df9dcd..ebce45c 100644
--- a/net/disk_cache/blockfile/block_files.cc
+++ b/net/disk_cache/blockfile/block_files.cc
@@ -15,7 +15,6 @@
 #include "base/time/time.h"
 #include "net/disk_cache/blockfile/file_lock.h"
 #include "net/disk_cache/blockfile/stress_support.h"
-#include "net/disk_cache/blockfile/trace.h"
 #include "net/disk_cache/cache_util.h"
 
 using base::TimeTicks;
@@ -339,7 +338,6 @@
 
   Addr address(block_type, block_count, file_header.FileId(), index);
   block_address->set_value(address.value());
-  Trace("CreateBlock 0x%x", address.value());
   return true;
 }
 
@@ -356,8 +354,6 @@
   if (!file)
     return;
 
-  Trace("DeleteBlock 0x%x", address.value());
-
   size_t size = address.BlockSize() * address.num_blocks();
   size_t offset = address.start_block() * address.BlockSize() +
                   kBlockHeaderSize;
diff --git a/net/disk_cache/blockfile/entry_impl.cc b/net/disk_cache/blockfile/entry_impl.cc
index a55ed15..668101ca 100644
--- a/net/disk_cache/blockfile/entry_impl.cc
+++ b/net/disk_cache/blockfile/entry_impl.cc
@@ -431,7 +431,6 @@
 bool EntryImpl::CreateEntry(Addr node_address,
                             const std::string& key,
                             uint32_t hash) {
-  Trace("Create entry In");
   EntryStore* entry_store = entry_.Data();
   RankingsNode* node = node_.Data();
   memset(entry_store, 0, sizeof(EntryStore) * entry_.address().num_blocks());
@@ -472,7 +471,6 @@
   backend_->ModifyStorageSize(0, static_cast<int32_t>(key.size()));
   CACHE_UMA(COUNTS, "KeySize", 0, static_cast<int32_t>(key.size()));
   node->dirty = backend_->GetCurrentEntryId();
-  Log("Create Entry ");
   return true;
 }
 
@@ -976,7 +974,6 @@
     node_.clear_modified();
     return;
   }
-  Log("~EntryImpl in");
 
   // Save the sparse info to disk. This will generate IO for this entry and
   // maybe for a child entry, so it is important to do it before deleting this
@@ -1018,7 +1015,6 @@
     }
   }
 
-  Trace("~EntryImpl out 0x%p", reinterpret_cast<void*>(this));
   net_log_.EndEvent(net::NetLogEventType::DISK_CACHE_ENTRY_IMPL);
   backend_->OnEntryDestroyEnd();
 }
@@ -1151,11 +1147,9 @@
   int entry_size = entry_.Data()->data_size[index];
   bool extending = entry_size < offset + buf_len;
   truncate = truncate && entry_size > offset + buf_len;
-  Trace("To PrepareTarget 0x%x", entry_.address().value());
   if (!PrepareTarget(index, offset, buf_len, truncate))
     return net::ERR_FAILED;
 
-  Trace("From PrepareTarget 0x%x", entry_.address().value());
   if (extending || truncate)
     UpdateSize(index, entry_size, offset + buf_len);
 
@@ -1614,21 +1608,6 @@
   }
 }
 
-void EntryImpl::Log(const char* msg) {
-  int dirty = 0;
-  if (node_.HasData()) {
-    dirty = node_.Data()->dirty;
-  }
-
-  Trace("%s 0x%p 0x%x 0x%x", msg, reinterpret_cast<void*>(this),
-        entry_.address().value(), node_.address().value());
-
-  Trace("  data: 0x%x 0x%x 0x%x", entry_.Data()->data_addr[0],
-        entry_.Data()->data_addr[1], entry_.Data()->long_key);
-
-  Trace("  doomed: %d 0x%x", doomed_, dirty);
-}
-
 }  // namespace disk_cache
 
 #undef CACHE_UMA_BACKEND_IMPL_OBJ  // undef for jumbo builds
diff --git a/net/disk_cache/blockfile/entry_impl.h b/net/disk_cache/blockfile/entry_impl.h
index 616673e..24da1f9 100644
--- a/net/disk_cache/blockfile/entry_impl.h
+++ b/net/disk_cache/blockfile/entry_impl.h
@@ -292,9 +292,6 @@
   // actual cleanup.
   void GetData(int index, char** buffer, Addr* address);
 
-  // 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_;
diff --git a/net/disk_cache/blockfile/eviction.cc b/net/disk_cache/blockfile/eviction.cc
index fdc2169..1ff15ab5 100644
--- a/net/disk_cache/blockfile/eviction.cc
+++ b/net/disk_cache/blockfile/eviction.cc
@@ -41,12 +41,12 @@
 #include "base/strings/string_util.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
+#include "base/trace_event/trace_event.h"
 #include "net/disk_cache/blockfile/backend_impl.h"
 #include "net/disk_cache/blockfile/disk_format.h"
 #include "net/disk_cache/blockfile/entry_impl.h"
 #include "net/disk_cache/blockfile/experiments.h"
 #include "net/disk_cache/blockfile/histogram_macros.h"
-#include "net/disk_cache/blockfile/trace.h"
 
 // Provide a BackendImpl object to macros from histogram_macros.h.
 #define CACHE_UMA_BACKEND_IMPL_OBJ backend_
@@ -113,6 +113,7 @@
 }
 
 void Eviction::TrimCache(bool empty) {
+  TRACE_EVENT0("disk_cache", "Eviction::TrimCache");
   if (backend_->disabled_ || trimming_)
     return;
 
@@ -122,7 +123,6 @@
   if (new_eviction_)
     return TrimCacheV2(empty);
 
-  Trace("*** Trim Cache ***");
   trimming_ = true;
   TimeTicks start = TimeTicks::Now();
   Rankings::ScopedRankingsBlock node(rankings_);
@@ -163,7 +163,6 @@
   CACHE_UMA(COUNTS, "TrimItemsV1", 0, deleted_entries);
 
   trimming_ = false;
-  Trace("*** Trim Cache end ***");
   return;
 }
 
@@ -206,6 +205,8 @@
 }
 
 void Eviction::TrimDeletedList(bool empty) {
+  TRACE_EVENT0("disk_cache", "Eviction::TrimDeletedList");
+
   DCHECK(test_mode_ && new_eviction_);
   TrimDeleted(empty);
 }
@@ -284,10 +285,8 @@
 bool Eviction::EvictEntry(CacheRankingsBlock* node, bool empty,
                           Rankings::List list) {
   scoped_refptr<EntryImpl> entry = backend_->GetEnumeratedEntry(node, list);
-  if (!entry) {
-    Trace("NewEntry failed on Trim 0x%x", node->address().value());
+  if (!entry)
     return false;
-  }
 
   ReportTrimTimes(entry.get());
   if (empty || !new_eviction_) {
@@ -311,7 +310,8 @@
 // -----------------------------------------------------------------------
 
 void Eviction::TrimCacheV2(bool empty) {
-  Trace("*** Trim Cache ***");
+  TRACE_EVENT0("disk_cache", "Eviction::TrimCacheV2");
+
   trimming_ = true;
   TimeTicks start = TimeTicks::Now();
 
@@ -389,7 +389,6 @@
   }
   CACHE_UMA(COUNTS, "TrimItemsV2", 0, deleted_entries);
 
-  Trace("*** Trim Cache end ***");
   trimming_ = false;
   return;
 }
@@ -489,7 +488,8 @@
 // This is a minimal implementation that just discards the oldest nodes.
 // TODO(rvargas): Do something better here.
 void Eviction::TrimDeleted(bool empty) {
-  Trace("*** Trim Deleted ***");
+  TRACE_EVENT0("disk_cache", "Eviction::TrimDeleted");
+
   if (backend_->disabled_)
     return;
 
@@ -517,17 +517,14 @@
 
   CACHE_UMA(AGE_MS, "TotalTrimDeletedTime", 0, start);
   CACHE_UMA(COUNTS, "TrimDeletedItems", 0, deleted_entries);
-  Trace("*** Trim Deleted end ***");
   return;
 }
 
 bool Eviction::RemoveDeletedNode(CacheRankingsBlock* node) {
   scoped_refptr<EntryImpl> entry =
       backend_->GetEnumeratedEntry(node, Rankings::DELETED);
-  if (!entry) {
-    Trace("NewEntry failed on Trim 0x%x", node->address().value());
+  if (!entry)
     return false;
-  }
 
   bool doomed = (entry->entry()->Data()->state == ENTRY_DOOMED);
   entry->entry()->Data()->state = ENTRY_DOOMED;
diff --git a/net/disk_cache/blockfile/rankings.cc b/net/disk_cache/blockfile/rankings.cc
index 2a3344a..7c6bbbc 100644
--- a/net/disk_cache/blockfile/rankings.cc
+++ b/net/disk_cache/blockfile/rankings.cc
@@ -246,7 +246,6 @@
 }
 
 void Rankings::Insert(CacheRankingsBlock* node, bool modified, List list) {
-  Trace("Insert 0x%x l %d", node->address().value(), list);
   DCHECK(node->HasData());
   Addr& my_head = heads_[list];
   Addr& my_tail = tails_[list];
@@ -317,8 +316,6 @@
 //    3. a(x, a), r(a, r), head(x), tail(a)           prev.Store()
 //    4. a(x, a), r(0, 0), head(x), tail(a)           next.Store()
 void Rankings::Remove(CacheRankingsBlock* node, List list, bool strict) {
-  Trace("Remove 0x%x (0x%x 0x%x) l %d", node->address().value(),
-        node->Data()->next, node->Data()->prev, list);
   DCHECK(node->HasData());
 
   Addr next_addr(node->Data()->next);
@@ -642,25 +639,18 @@
     return;
   }
 
-  Trace("CompleteTransaction 0x%x", node_addr.value());
-
   CacheRankingsBlock node(backend_->File(node_addr), node_addr);
   if (!node.Load())
     return;
 
   node.Store();
 
-  Addr& my_head = heads_[control_data_->operation_list];
-  Addr& my_tail = tails_[control_data_->operation_list];
-
   // We want to leave the node inside the list. The entry must me marked as
   // dirty, and will be removed later. Otherwise, we'll get assertions when
   // attempting to remove the dirty entry.
   if (INSERT == control_data_->operation) {
-    Trace("FinishInsert h:0x%x t:0x%x", my_head.value(), my_tail.value());
     FinishInsert(&node);
   } else if (REMOVE == control_data_->operation) {
-    Trace("RevertRemove h:0x%x t:0x%x", my_head.value(), my_tail.value());
     RevertRemove(&node);
   } else {
     NOTREACHED();
@@ -753,15 +743,11 @@
     return true;
   }
 
-  Trace("CheckLinks 0x%x (0x%x 0x%x)", node_addr,
-        prev->Data()->next, next->Data()->prev);
-
   if (node_addr != prev->address().value() &&
       node_addr != next->address().value() &&
       prev->Data()->next == next->address().value() &&
       next->Data()->prev == prev->address().value()) {
     // The list is actually ok, node is wrong.
-    Trace("node 0x%x out of list %d", node_addr, list);
     node->Data()->next = 0;
     node->Data()->prev = 0;
     node->Store();
@@ -860,8 +846,6 @@
 bool Rankings::IsHead(CacheAddr addr, List* list) const {
   for (int i = 0; i < LAST_ELEMENT; i++) {
     if (addr == heads_[i].value()) {
-      if (*list != i)
-        Trace("Changing list %d to %d", *list, i);
       *list = static_cast<List>(i);
       return true;
     }
@@ -872,8 +856,6 @@
 bool Rankings::IsTail(CacheAddr addr, List* list) const {
   for (int i = 0; i < LAST_ELEMENT; i++) {
     if (addr == tails_[i].value()) {
-      if (*list != i)
-        Trace("Changing list %d to %d", *list, i);
       *list = static_cast<List>(i);
       return true;
     }
diff --git a/net/disk_cache/blockfile/storage_block-inl.h b/net/disk_cache/blockfile/storage_block-inl.h
index 903d65c..d976443 100644
--- a/net/disk_cache/blockfile/storage_block-inl.h
+++ b/net/disk_cache/blockfile/storage_block-inl.h
@@ -12,7 +12,6 @@
 
 #include "base/hash/hash.h"
 #include "base/logging.h"
-#include "net/disk_cache/blockfile/trace.h"
 
 namespace disk_cache {
 
@@ -145,7 +144,6 @@
     }
   }
   LOG(WARNING) << "Failed data load.";
-  Trace("Failed data load.");
   return false;
 }
 
@@ -158,7 +156,6 @@
     }
   }
   LOG(ERROR) << "Failed data store.";
-  Trace("Failed data store.");
   return false;
 }
 
@@ -174,7 +171,6 @@
     }
   }
   LOG(WARNING) << "Failed data load.";
-  Trace("Failed data load.");
   return false;
 }
 
@@ -188,7 +184,6 @@
     }
   }
   LOG(ERROR) << "Failed data store.";
-  Trace("Failed data store.");
   return false;
 }
 
diff --git a/net/disk_cache/blockfile/stress_support.h b/net/disk_cache/blockfile/stress_support.h
index 5383c5d..2f07de8 100644
--- a/net/disk_cache/blockfile/stress_support.h
+++ b/net/disk_cache/blockfile/stress_support.h
@@ -13,11 +13,6 @@
 // to ensure that we are not producing corrupt entries.
 // #define NET_BUILD_STRESS_CACHE 1
 
-// Uncomment this line to direct the in-memory disk cache tracing to the base
-// logging system. On Windows this option will enable ETW (Event Tracing for
-// Windows) so logs across multiple runs can be collected.
-// #define DISK_CACHE_TRACE_TO_LOG 1
-
 // Uncomment this line to perform extended integrity checks during init. It is
 // not recommended to enable this option unless some corruption is being tracked
 // down.
diff --git a/net/disk_cache/blockfile/trace.cc b/net/disk_cache/blockfile/trace.cc
deleted file mode 100644
index 0b451be0..0000000
--- a/net/disk_cache/blockfile/trace.cc
+++ /dev/null
@@ -1,193 +0,0 @@
-// Copyright (c) 2011 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 "net/disk_cache/blockfile/trace.h"
-
-#include <stdarg.h>
-#include <stdio.h>
-#if defined(OS_WIN)
-#include <windows.h>
-#endif
-
-#include "base/lazy_instance.h"
-#include "base/logging.h"
-#include "base/synchronization/lock.h"
-#include "net/disk_cache/blockfile/stress_support.h"
-
-// Change this value to 1 to enable tracing on a release build. By default,
-// tracing is enabled only on debug builds.
-#define ENABLE_TRACING 0
-
-#ifndef NDEBUG
-#undef ENABLE_TRACING
-#define ENABLE_TRACING 1
-#endif
-
-namespace {
-
-const int kEntrySize = 12 * sizeof(size_t);
-#if defined(NET_BUILD_STRESS_CACHE)
-const int kNumberOfEntries = 500000;
-#else
-const int kNumberOfEntries = 5000;  // 240 KB on 32bit, 480 KB on 64bit
-#endif
-
-bool s_trace_enabled = false;
-base::LazyInstance<base::Lock>::Leaky s_lock = LAZY_INSTANCE_INITIALIZER;
-
-struct TraceBuffer {
-  int num_traces;
-  int current;
-  char buffer[kNumberOfEntries][kEntrySize];
-};
-
-#if ENABLE_TRACING
-void DebugOutput(const char* msg) {
-#if defined(OS_WIN)
-  OutputDebugStringA(msg);
-#else
-  NOTIMPLEMENTED();
-#endif
-}
-#endif  // ENABLE_TRACING
-
-}  // namespace
-
-namespace disk_cache {
-
-// s_trace_buffer and s_trace_object are not singletons because I want the
-// buffer to be destroyed and re-created when the last user goes away, and it
-// must be straightforward to access the buffer from the debugger.
-static TraceObject* s_trace_object = nullptr;
-
-// Static.
-TraceObject* TraceObject::GetTraceObject() {
-  base::AutoLock lock(s_lock.Get());
-
-  if (s_trace_object)
-    return s_trace_object;
-
-  s_trace_object = new TraceObject();
-  return s_trace_object;
-}
-
-TraceObject::TraceObject() {
-  InitTrace();
-}
-
-TraceObject::~TraceObject() {
-  DestroyTrace();
-}
-
-void TraceObject::EnableTracing(bool enable) {
-  base::AutoLock lock(s_lock.Get());
-  s_trace_enabled = enable;
-}
-
-#if ENABLE_TRACING
-
-static TraceBuffer* s_trace_buffer = nullptr;
-
-void InitTrace(void) {
-  s_trace_enabled = true;
-  if (s_trace_buffer)
-    return;
-
-  s_trace_buffer = new TraceBuffer;
-  memset(s_trace_buffer, 0, sizeof(*s_trace_buffer));
-}
-
-void DestroyTrace(void) {
-  base::AutoLock lock(s_lock.Get());
-
-  delete s_trace_buffer;
-  s_trace_buffer = nullptr;
-  s_trace_object = nullptr;
-}
-
-void Trace(const char* format, ...) {
-  if (!s_trace_buffer || !s_trace_enabled)
-    return;
-
-  va_list ap;
-  va_start(ap, format);
-  char line[kEntrySize + 2];
-
-#if defined(OS_WIN)
-  vsprintf_s(line, format, ap);
-#else
-  vsnprintf(line, kEntrySize, format, ap);
-#endif
-
-#if defined(DISK_CACHE_TRACE_TO_LOG)
-  line[kEntrySize] = '\0';
-  LOG(INFO) << line;
-#endif
-
-  va_end(ap);
-
-  {
-    base::AutoLock lock(s_lock.Get());
-    if (!s_trace_buffer || !s_trace_enabled)
-      return;
-
-    memcpy(s_trace_buffer->buffer[s_trace_buffer->current], line, kEntrySize);
-
-    s_trace_buffer->num_traces++;
-    s_trace_buffer->current++;
-    if (s_trace_buffer->current == kNumberOfEntries)
-      s_trace_buffer->current = 0;
-  }
-}
-
-// Writes the last num_traces to the debugger output.
-void DumpTrace(int num_traces) {
-  DCHECK(s_trace_buffer);
-  DebugOutput("Last traces:\n");
-
-  if (num_traces > kNumberOfEntries || num_traces < 0)
-    num_traces = kNumberOfEntries;
-
-  if (s_trace_buffer->num_traces) {
-    char line[kEntrySize + 2];
-
-    int current = s_trace_buffer->current - num_traces;
-    if (current < 0)
-      current += kNumberOfEntries;
-
-    for (int i = 0; i < num_traces; i++) {
-      memcpy(line, s_trace_buffer->buffer[current], kEntrySize);
-      line[kEntrySize] = '\0';
-      size_t length = strlen(line);
-      if (length) {
-        line[length] = '\n';
-        line[length + 1] = '\0';
-        DebugOutput(line);
-      }
-
-      current++;
-      if (current ==  kNumberOfEntries)
-        current = 0;
-    }
-  }
-
-  DebugOutput("End of Traces\n");
-}
-
-#else  // ENABLE_TRACING
-
-void InitTrace(void) {
-  return;
-}
-
-void DestroyTrace(void) {
-  s_trace_object = NULL;
-}
-
-void Trace(const char* format, ...) {
-}
-
-#endif  // ENABLE_TRACING
-
-}  // namespace disk_cache
diff --git a/net/disk_cache/blockfile/trace.h b/net/disk_cache/blockfile/trace.h
deleted file mode 100644
index 2cc2fa1..0000000
--- a/net/disk_cache/blockfile/trace.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (c) 2011 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.
-
-// This file provides support for basic in-memory tracing of short events. We
-// keep a static circular buffer where we store the last traced events, so we
-// can review the cache recent behavior should we need it.
-
-#ifndef NET_DISK_CACHE_BLOCKFILE_TRACE_H_
-#define NET_DISK_CACHE_BLOCKFILE_TRACE_H_
-
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "net/base/net_export.h"
-
-namespace disk_cache {
-
-// Create and destroy the tracing buffer.
-void InitTrace(void);
-void DestroyTrace(void);
-
-// Simple class to handle the trace buffer lifetime. Any object interested in
-// tracing should keep a reference to the object returned by GetTraceObject().
-class TraceObject : public base::RefCountedThreadSafe<TraceObject> {
-  friend class base::RefCountedThreadSafe<TraceObject>;
-
- public:
-  static TraceObject* GetTraceObject();
-  void EnableTracing(bool enable);
-
- private:
-  TraceObject();
-  ~TraceObject();
-  DISALLOW_COPY_AND_ASSIGN(TraceObject);
-};
-
-// Traces to the internal buffer.
-NET_EXPORT_PRIVATE void Trace(const char* format, ...);
-
-}  // namespace disk_cache
-
-#endif  // NET_DISK_CACHE_BLOCKFILE_TRACE_H_
diff --git a/net/quic/quic_flags_list.h b/net/quic/quic_flags_list.h
index 0e9f5036..5c2bc6ef 100644
--- a/net/quic/quic_flags_list.h
+++ b/net/quic/quic_flags_list.h
@@ -396,3 +396,9 @@
 // If true, use blackhole detector in QuicConnection to detect path degrading
 // and network blackhole.
 QUIC_FLAG(bool, FLAGS_quic_reloadable_flag_quic_use_blackhole_detector, false)
+
+// If true, use idle network detector to detect handshake timeout and idle
+// network timeout.
+QUIC_FLAG(bool,
+          FLAGS_quic_reloadable_flag_quic_use_idle_network_detector,
+          false)
diff --git a/net/quic/quic_stream_factory.cc b/net/quic/quic_stream_factory.cc
index 60f1e486..f076703 100644
--- a/net/quic/quic_stream_factory.cc
+++ b/net/quic/quic_stream_factory.cc
@@ -45,6 +45,7 @@
 #include "net/quic/quic_chromium_connection_helper.h"
 #include "net/quic/quic_chromium_packet_reader.h"
 #include "net/quic/quic_chromium_packet_writer.h"
+#include "net/quic/quic_client_session_cache.h"
 #include "net/quic/quic_context.h"
 #include "net/quic/quic_crypto_client_stream_factory.h"
 #include "net/quic/quic_http_stream.h"
@@ -324,8 +325,9 @@
  public:
   QuicCryptoClientConfigOwner(
       std::unique_ptr<quic::ProofVerifier> proof_verifier,
+      std::unique_ptr<QuicClientSessionCache> session_cache,
       QuicStreamFactory* quic_stream_factory)
-      : config_(std::move(proof_verifier)),
+      : config_(std::move(proof_verifier), std::move(session_cache)),
         quic_stream_factory_(quic_stream_factory) {
     DCHECK(quic_stream_factory_);
   }
@@ -2277,7 +2279,7 @@
               cert_verifier_, ct_policy_enforcer_, transport_security_state_,
               cert_transparency_verifier_,
               HostsFromOrigins(params_.origins_to_force_quic_on)),
-          this);
+          std::make_unique<QuicClientSessionCache>(), this);
 
   quic::QuicCryptoClientConfig* crypto_config = crypto_config_owner->config();
   crypto_config->set_user_agent_id(params_.user_agent_id);
diff --git a/net/third_party/quiche/BUILD.gn b/net/third_party/quiche/BUILD.gn
index 3370121..5585047 100644
--- a/net/third_party/quiche/BUILD.gn
+++ b/net/third_party/quiche/BUILD.gn
@@ -428,6 +428,8 @@
       "src/quic/core/quic_flow_controller.h",
       "src/quic/core/quic_framer.cc",
       "src/quic/core/quic_framer.h",
+      "src/quic/core/quic_idle_network_detector.cc",
+      "src/quic/core/quic_idle_network_detector.h",
       "src/quic/core/quic_interval.h",
       "src/quic/core/quic_interval_deque.h",
       "src/quic/core/quic_interval_set.h",
@@ -1315,6 +1317,7 @@
     "src/quic/core/quic_error_codes_test.cc",
     "src/quic/core/quic_flow_controller_test.cc",
     "src/quic/core/quic_framer_test.cc",
+    "src/quic/core/quic_idle_network_detector_test.cc",
     "src/quic/core/quic_interval_deque_test.cc",
     "src/quic/core/quic_interval_set_test.cc",
     "src/quic/core/quic_interval_test.cc",
diff --git a/net/tools/stress_cache/stress_cache.cc b/net/tools/stress_cache/stress_cache.cc
index 0603c1c..3e6aaa1b 100644
--- a/net/tools/stress_cache/stress_cache.cc
+++ b/net/tools/stress_cache/stress_cache.cc
@@ -43,7 +43,6 @@
 #include "net/base/test_completion_callback.h"
 #include "net/disk_cache/blockfile/backend_impl.h"
 #include "net/disk_cache/blockfile/stress_support.h"
-#include "net/disk_cache/blockfile/trace.h"
 #include "net/disk_cache/disk_cache.h"
 #include "net/disk_cache/disk_cache_test_util.h"
 
@@ -386,20 +385,6 @@
   base::debug::BreakDebugger();
 }
 
-bool MessageHandler(int severity, const char* file, int line,
-                    size_t message_start, const std::string& str) {
-  const size_t kMaxMessageLen = 48;
-  char message[kMaxMessageLen];
-  size_t len = std::min(str.length() - message_start, kMaxMessageLen - 1);
-
-  memcpy(message, str.c_str() + message_start, len);
-  message[len] = '\0';
-#if !defined(DISK_CACHE_TRACE_TO_LOG)
-  disk_cache::Trace("%s", message);
-#endif
-  return false;
-}
-
 // -----------------------------------------------------------------------
 
 #if defined(OS_WIN)
@@ -418,7 +403,6 @@
 
   logging::ScopedLogAssertHandler scoped_assert_handler(
       base::Bind(CrashHandler));
-  logging::SetLogMessageHandler(MessageHandler);
 
 #if defined(OS_WIN)
   logging::LogEventProvider::Initialize(kStressCacheTraceProviderName);
diff --git a/services/device/generic_sensor/platform_sensor_provider.cc b/services/device/generic_sensor/platform_sensor_provider.cc
index 8b8c6916..f1f7159 100644
--- a/services/device/generic_sensor/platform_sensor_provider.cc
+++ b/services/device/generic_sensor/platform_sensor_provider.cc
@@ -48,8 +48,13 @@
   // this Windows version has yet to be released, Win10 is being
   // provisionally used for testing. This also means sensors will
   // stream if this implementation path is enabled.
+
+  // Note the fork occurs specifically on the 19H1 build of Win10
+  // because a previous version (RS5) contains an access violation
+  // issue in the WinRT APIs which causes the client code to crash.
+  // See http://crbug.com/1063124
   return base::FeatureList::IsEnabled(features::kWinrtSensorsImplementation) &&
-         base::win::GetVersion() >= base::win::Version::WIN10;
+         base::win::GetVersion() >= base::win::Version::WIN10_19H1;
 }
 #endif
 
diff --git a/services/device/public/cpp/device_features.cc b/services/device/public/cpp/device_features.cc
index 4f05b0c..07072a57 100644
--- a/services/device/public/cpp/device_features.cc
+++ b/services/device/public/cpp/device_features.cc
@@ -13,7 +13,7 @@
 // Enables usage of the Windows.Devices.Sensors WinRT API for the sensor
 // backend instead of the ISensor API on Windows.
 const base::Feature kWinrtSensorsImplementation{
-    "WinrtSensorsImplementation", base::FEATURE_DISABLED_BY_DEFAULT};
+    "WinrtSensorsImplementation", base::FEATURE_ENABLED_BY_DEFAULT};
 // Enables usage of the Windows.Devices.Geolocation WinRT API for the
 // LocationProvider instead of the NetworkLocationProvider on Windows.
 const base::Feature kWinrtGeolocationImplementation{
diff --git a/services/metrics/public/cpp/ukm_recorder.cc b/services/metrics/public/cpp/ukm_recorder.cc
index b38376ea..7424a3c7 100644
--- a/services/metrics/public/cpp/ukm_recorder.cc
+++ b/services/metrics/public/cpp/ukm_recorder.cc
@@ -32,6 +32,16 @@
 }
 
 // static
+ukm::SourceId UkmRecorder::GetSourceIdForPaymentAppFromScope(
+    const GURL& service_worker_scope) {
+  ukm::SourceId source_id = base::UkmSourceId::FromOtherId(
+                                GetNewSourceID(), SourceIdType::PAYMENT_APP_ID)
+                                .ToInt64();
+  ukm::UkmRecorder::Get()->UpdateSourceURL(source_id, service_worker_scope);
+  return source_id;
+}
+
+// static
 ukm::SourceId UkmRecorder::GetSourceIdForWebApkManifestUrl(
     const GURL& manifest_url) {
   ukm::SourceId source_id =
diff --git a/services/metrics/public/cpp/ukm_recorder.h b/services/metrics/public/cpp/ukm_recorder.h
index 107f9b0..132072b 100644
--- a/services/metrics/public/cpp/ukm_recorder.h
+++ b/services/metrics/public/cpp/ukm_recorder.h
@@ -28,6 +28,10 @@
 class UkmRecorderInterface;
 }  // namespace metrics
 
+namespace content {
+class PaymentAppProviderImpl;
+}  // namespace content
+
 namespace ukm {
 
 class DelegatingUkmRecorder;
@@ -72,6 +76,12 @@
   // method should only be called by WebApkUkmRecorder class.
   static SourceId GetSourceIdForWebApkManifestUrl(const GURL& manifest_url);
 
+  // Gets new source Id for PAYMENT_APP_ID type and updates the source url to
+  // the scope of the app. This method should only be called by
+  // PaymentAppProviderImpl class when the payment app window is opened.
+  static SourceId GetSourceIdForPaymentAppFromScope(
+      const GURL& service_worker_scope);
+
  private:
   friend DelegatingUkmRecorder;
   friend TestRecordingHelper;
@@ -79,6 +89,7 @@
   friend blink::Document;
   friend metrics::UkmRecorderInterface;
   friend PermissionUmaUtil;
+  friend content::PaymentAppProviderImpl;
 
   // WebApkUkmRecorder records metrics about installed Webapps. Instead of using
   // the current main frame URL, we want to record the URL of the Webapp
diff --git a/services/metrics/public/cpp/ukm_source_id.cc b/services/metrics/public/cpp/ukm_source_id.cc
index d1c20bc..fdba55e 100644
--- a/services/metrics/public/cpp/ukm_source_id.cc
+++ b/services/metrics/public/cpp/ukm_source_id.cc
@@ -15,11 +15,13 @@
 }
 
 SourceId ConvertToSourceId(int64_t other_id, SourceIdType id_type) {
-  // DCHECK is to restrict the usage of WEBAPK_ID, WebApk should use
-  // |UkmRecorder::GetSourceIdForWebApkManifestUrl()| instead.
+  // DCHECK is to restrict the usage of WEBAPK_ID and PAYMENT_APP_ID. WebApk and
+  // Payment apps should use |UkmRecorder::GetSourceIdForWebApkManifestUrl()|
+  // and |UkmRecorder::GetSourceIdForPaymentAppFromScope()| instead.
   // TODO(crbug.com/1046964): Ideally we should restrict
   // UkmSourceId::FromOtherId() as well.
   DCHECK(id_type != SourceIdType::WEBAPK_ID);
+  DCHECK(id_type != SourceIdType::PAYMENT_APP_ID);
   return base::UkmSourceId::FromOtherId(other_id, id_type).ToInt64();
 }
 
diff --git a/services/viz/privileged/cpp/OWNERS b/services/viz/privileged/cpp/OWNERS
index c4f73a0..d5fefd8 100644
--- a/services/viz/privileged/cpp/OWNERS
+++ b/services/viz/privileged/cpp/OWNERS
@@ -1,4 +1,2 @@
-per-file *.typemap=set noparent
-per-file *.typemap=file://ipc/SECURITY_OWNERS
 per-file *_mojom_traits*.*=set noparent
 per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
diff --git a/services/viz/privileged/cpp/context_lost_reason.typemap b/services/viz/privileged/cpp/context_lost_reason.typemap
deleted file mode 100644
index 55ffcdbb..0000000
--- a/services/viz/privileged/cpp/context_lost_reason.typemap
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//services/viz/privileged/mojom/gl/context_lost_reason.mojom"
-public_headers = [ "//gpu/command_buffer/common/constants.h" ]
-traits_headers =
-    [ "//services/viz/privileged/cpp/context_lost_reason_traits.h" ]
-public_deps = [
-  "//gpu/command_buffer/common",
-]
-
-type_mappings =
-    [ "viz.mojom.ContextLostReason=::gpu::error::ContextLostReason" ]
diff --git a/services/viz/privileged/cpp/context_lost_reason_traits.h b/services/viz/privileged/cpp/context_lost_reason_traits.h
index 7be0c33..761b20b1 100644
--- a/services/viz/privileged/cpp/context_lost_reason_traits.h
+++ b/services/viz/privileged/cpp/context_lost_reason_traits.h
@@ -6,7 +6,7 @@
 #define SERVICES_VIZ_PRIVILEGED_CPP_CONTEXT_LOST_REASON_TRAITS_H_
 
 #include "gpu/command_buffer/common/constants.h"
-#include "services/viz/privileged/mojom/gl/context_lost_reason.mojom.h"
+#include "services/viz/privileged/mojom/gl/context_lost_reason.mojom-shared.h"
 
 namespace mojo {
 
diff --git a/services/viz/privileged/cpp/overlay_strategy.typemap b/services/viz/privileged/cpp/overlay_strategy.typemap
deleted file mode 100644
index 8ee3d48a..0000000
--- a/services/viz/privileged/cpp/overlay_strategy.typemap
+++ /dev/null
@@ -1,14 +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.
-
-mojom = "//services/viz/privileged/mojom/compositing/overlay_strategy.mojom"
-public_headers = [ "//components/viz/common/display/overlay_strategy.h" ]
-traits_headers =
-    [ "//services/viz/privileged/cpp/overlay_strategy_mojom_traits.h" ]
-public_deps = [
-  "//components/viz/common",
-  #"//services/viz/privileged/mojom/compositing"
-]
-
-type_mappings = [ "viz.mojom.OverlayStrategy=::viz::OverlayStrategy" ]
diff --git a/services/viz/privileged/cpp/overlay_strategy_mojom_traits.h b/services/viz/privileged/cpp/overlay_strategy_mojom_traits.h
index dbd6a4ab..9b381e6 100644
--- a/services/viz/privileged/cpp/overlay_strategy_mojom_traits.h
+++ b/services/viz/privileged/cpp/overlay_strategy_mojom_traits.h
@@ -6,7 +6,7 @@
 #define SERVICES_VIZ_PRIVILEGED_CPP_OVERLAY_STRATEGY_MOJOM_TRAITS_H_
 
 #include "components/viz/common/display/overlay_strategy.h"
-#include "services/viz/privileged/mojom/compositing/overlay_strategy.mojom.h"
+#include "services/viz/privileged/mojom/compositing/overlay_strategy.mojom-shared.h"
 
 namespace mojo {
 
diff --git a/services/viz/privileged/cpp/typemaps.gni b/services/viz/privileged/cpp/typemaps.gni
deleted file mode 100644
index fed7dfa..0000000
--- a/services/viz/privileged/cpp/typemaps.gni
+++ /dev/null
@@ -1,4 +0,0 @@
-typemaps = [
-  "//services/viz/privileged/cpp/context_lost_reason.typemap",
-  "//services/viz/privileged/cpp/overlay_strategy.typemap",
-]
diff --git a/services/viz/privileged/mojom/compositing/BUILD.gn b/services/viz/privileged/mojom/compositing/BUILD.gn
index 75e2ff88..53af38d0 100644
--- a/services/viz/privileged/mojom/compositing/BUILD.gn
+++ b/services/viz/privileged/mojom/compositing/BUILD.gn
@@ -36,4 +36,21 @@
   if (use_x11) {
     enabled_features += [ "use_x11" ]
   }
+
+  cpp_typemaps = [
+    {
+      types = [
+        {
+          mojom = "viz.mojom.RendererSettings"
+          cpp = "::viz::RendererSettings"
+        },
+      ]
+      traits_headers = [ "//services/viz/privileged/mojom/compositing/renderer_settings_mojom_traits.h" ]
+      traits_sources = [ "//services/viz/privileged/mojom/compositing/renderer_settings_mojom_traits.cc" ]
+      traits_public_deps = [
+        "//cc",
+        "//ui/gfx/geometry/mojom",
+      ]
+    },
+  ]
 }
diff --git a/services/viz/privileged/mojom/compositing/OWNERS b/services/viz/privileged/mojom/compositing/OWNERS
index ae29a36aa..1feb514 100644
--- a/services/viz/privileged/mojom/compositing/OWNERS
+++ b/services/viz/privileged/mojom/compositing/OWNERS
@@ -2,5 +2,3 @@
 per-file *.mojom=file://ipc/SECURITY_OWNERS
 per-file *_mojom_traits*.*=set noparent
 per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
-per-file *.typemap=set noparent
-per-file *.typemap=file://ipc/SECURITY_OWNERS
diff --git a/services/viz/privileged/mojom/compositing/renderer_settings.typemap b/services/viz/privileged/mojom/compositing/renderer_settings.typemap
deleted file mode 100644
index 4993e50..0000000
--- a/services/viz/privileged/mojom/compositing/renderer_settings.typemap
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//services/viz/privileged/mojom/compositing/renderer_settings.mojom"
-public_headers = [ "//components/viz/common/display/renderer_settings.h" ]
-traits_headers = [ "//services/viz/privileged/mojom/compositing/renderer_settings_mojom_traits.h" ]
-deps = [
-  "//cc",
-]
-public_deps = [
-  "//ui/gfx/geometry/mojom",
-]
-sources = [
-  "renderer_settings_mojom_traits.cc",
-]
-type_mappings = [ "viz.mojom.RendererSettings=::viz::RendererSettings" ]
diff --git a/services/viz/privileged/mojom/compositing/renderer_settings_mojom_traits.h b/services/viz/privileged/mojom/compositing/renderer_settings_mojom_traits.h
index a22d21f..5cb17bd 100644
--- a/services/viz/privileged/mojom/compositing/renderer_settings_mojom_traits.h
+++ b/services/viz/privileged/mojom/compositing/renderer_settings_mojom_traits.h
@@ -9,7 +9,7 @@
 #include "build/build_config.h"
 #include "components/viz/common/display/renderer_settings.h"
 #include "services/viz/privileged/cpp/overlay_strategy_mojom_traits.h"
-#include "services/viz/privileged/mojom/compositing/renderer_settings.mojom.h"
+#include "services/viz/privileged/mojom/compositing/renderer_settings.mojom-shared.h"
 #include "ui/gfx/geometry/mojom/geometry_mojom_traits.h"
 
 #if defined(USE_OZONE)
diff --git a/services/viz/privileged/mojom/compositing/typemaps.gni b/services/viz/privileged/mojom/compositing/typemaps.gni
deleted file mode 100644
index f9e68e3c2..0000000
--- a/services/viz/privileged/mojom/compositing/typemaps.gni
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-typemaps =
-    [ "//services/viz/privileged/mojom/compositing/renderer_settings.typemap" ]
diff --git a/services/viz/privileged/mojom/gl/BUILD.gn b/services/viz/privileged/mojom/gl/BUILD.gn
index e0a4a98..3ee4a5a 100644
--- a/services/viz/privileged/mojom/gl/BUILD.gn
+++ b/services/viz/privileged/mojom/gl/BUILD.gn
@@ -30,4 +30,29 @@
   if (!is_android) {
     enabled_features += [ "is_not_android" ]
   }
+
+  cpp_typemaps = [
+    {
+      types = [
+        {
+          mojom = "viz.mojom.ContextLostReason"
+          cpp = "::gpu::error::ContextLostReason"
+        },
+      ]
+      traits_headers =
+          [ "//services/viz/privileged/cpp/context_lost_reason_traits.h" ]
+      traits_public_deps = [ "//gpu/command_buffer/common" ]
+    },
+    {
+      types = [
+        {
+          mojom = "viz.mojom.OverlayStrategy"
+          cpp = "::viz::OverlayStrategy"
+        },
+      ]
+      traits_headers =
+          [ "//services/viz/privileged/cpp/overlay_strategy_mojom_traits.h" ]
+      traits_public_deps = [ "//components/viz/common" ]
+    },
+  ]
 }
diff --git a/services/viz/privileged/mojom/mojom_traits_unittest.cc b/services/viz/privileged/mojom/mojom_traits_unittest.cc
index bffe4fc..d1625920 100644
--- a/services/viz/privileged/mojom/mojom_traits_unittest.cc
+++ b/services/viz/privileged/mojom/mojom_traits_unittest.cc
@@ -5,7 +5,7 @@
 #include <utility>
 
 #include "components/viz/common/display/renderer_settings.h"
-#include "services/viz/privileged/mojom/compositing/renderer_settings_mojom_traits.h"
+#include "services/viz/privileged/mojom/compositing/renderer_settings.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/gfx/mojom/color_space_mojom_traits.h"
 
diff --git a/services/viz/public/cpp/hit_test/OWNERS b/services/viz/public/cpp/hit_test/OWNERS
index 7aebc8abb..d5fefd8 100644
--- a/services/viz/public/cpp/hit_test/OWNERS
+++ b/services/viz/public/cpp/hit_test/OWNERS
@@ -1,4 +1,2 @@
 per-file *_mojom_traits*.*=set noparent
 per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
-per-file *.typemap=set noparent
-per-file *.typemap=file://ipc/SECURITY_OWNERS
diff --git a/services/viz/public/cpp/hit_test/aggregated_hit_test_region.typemap b/services/viz/public/cpp/hit_test/aggregated_hit_test_region.typemap
deleted file mode 100644
index 24fa0173..0000000
--- a/services/viz/public/cpp/hit_test/aggregated_hit_test_region.typemap
+++ /dev/null
@@ -1,16 +0,0 @@
-# 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.
-
-mojom = "//services/viz/public/mojom/hit_test/aggregated_hit_test_region.mojom"
-public_headers =
-    [ "//components/viz/common/hit_test/aggregated_hit_test_region.h" ]
-deps = [
-  "//components/viz/common",
-]
-traits_headers = [ "//services/viz/public/cpp/hit_test/aggregated_hit_test_region_mojom_traits.h" ]
-sources = [
-  "//services/viz/public/cpp/hit_test/aggregated_hit_test_region_mojom_traits.cc",
-]
-type_mappings =
-    [ "viz.mojom.AggregatedHitTestRegion=::viz::AggregatedHitTestRegion" ]
diff --git a/services/viz/public/cpp/hit_test/hit_test_region_list.typemap b/services/viz/public/cpp/hit_test/hit_test_region_list.typemap
deleted file mode 100644
index 55961fc..0000000
--- a/services/viz/public/cpp/hit_test/hit_test_region_list.typemap
+++ /dev/null
@@ -1,18 +0,0 @@
-# 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.
-
-mojom = "//services/viz/public/mojom/hit_test/hit_test_region_list.mojom"
-public_headers = [ "//components/viz/common/hit_test/hit_test_region_list.h" ]
-deps = [
-  "//components/viz/common",
-]
-traits_headers =
-    [ "//services/viz/public/cpp/hit_test/hit_test_region_list_mojom_traits.h" ]
-sources = [
-  "//services/viz/public/cpp/hit_test/hit_test_region_list_mojom_traits.cc",
-]
-type_mappings = [
-  "viz.mojom.HitTestRegion=::viz::HitTestRegion",
-  "viz.mojom.HitTestRegionList=::viz::HitTestRegionList[move_only]",
-]
diff --git a/services/viz/public/cpp/hit_test/typemaps.gni b/services/viz/public/cpp/hit_test/typemaps.gni
deleted file mode 100644
index 94af9c8..0000000
--- a/services/viz/public/cpp/hit_test/typemaps.gni
+++ /dev/null
@@ -1,8 +0,0 @@
-# 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.
-
-typemaps = [
-  "//services/viz/public/cpp/hit_test/aggregated_hit_test_region.typemap",
-  "//services/viz/public/cpp/hit_test/hit_test_region_list.typemap",
-]
diff --git a/services/viz/public/mojom/BUILD.gn b/services/viz/public/mojom/BUILD.gn
index a08d025..0f3d732a 100644
--- a/services/viz/public/mojom/BUILD.gn
+++ b/services/viz/public/mojom/BUILD.gn
@@ -58,4 +58,40 @@
   export_class_attribute_blink = "BLINK_PLATFORM_EXPORT"
   export_define_blink = "BLINK_PLATFORM_IMPLEMENTATION=1"
   export_header_blink = "third_party/blink/public/platform/web_common.h"
+
+  cpp_typemaps = [
+    {
+      types = [
+        {
+          mojom = "viz.mojom.AggregatedHitTestRegion"
+          cpp = "::viz::AggregatedHitTestRegion"
+        },
+      ]
+      traits_headers = [ "//services/viz/public/cpp/hit_test/aggregated_hit_test_region_mojom_traits.h" ]
+      traits_sources = [ "//services/viz/public/cpp/hit_test/aggregated_hit_test_region_mojom_traits.cc" ]
+      traits_public_deps = [
+        "//components/viz/common",
+        "//ui/gfx/geometry/mojom",
+      ]
+    },
+    {
+      types = [
+        {
+          mojom = "viz.mojom.HitTestRegion"
+          cpp = "::viz::HitTestRegion"
+        },
+        {
+          mojom = "viz.mojom.HitTestRegionList"
+          cpp = "::viz::HitTestRegionList"
+          move_only = true
+        },
+      ]
+      traits_headers = [ "//services/viz/public/cpp/hit_test/hit_test_region_list_mojom_traits.h" ]
+      traits_sources = [ "//services/viz/public/cpp/hit_test/hit_test_region_list_mojom_traits.cc" ]
+      traits_public_deps = [
+        "//components/viz/common",
+        "//ui/gfx/geometry/mojom",
+      ]
+    },
+  ]
 }
diff --git a/skia/config/SkUserConfig.h b/skia/config/SkUserConfig.h
index f5ccdaaa..66b4b03c 100644
--- a/skia/config/SkUserConfig.h
+++ b/skia/config/SkUserConfig.h
@@ -218,10 +218,6 @@
 #define SK_SUPPORT_LEGACY_AAA_CHOICE
 #endif
 
-#ifndef SK_USE_LEGACY_SRGB_COLOR_FILTER
-#define SK_USE_LEGACY_SRGB_COLOR_FILTER
-#endif
-
 // We're turning this off indefinitely,
 // until we can figure out some fundamental problems with its approach.
 //
diff --git a/testing/buildbot/filters/android.emulator.content_browsertests.filter b/testing/buildbot/filters/android.emulator.content_browsertests.filter
index 7ed82ec..c2f3b49 100644
--- a/testing/buildbot/filters/android.emulator.content_browsertests.filter
+++ b/testing/buildbot/filters/android.emulator.content_browsertests.filter
@@ -15,3 +15,6 @@
 
 # crbug.com/1058570
 -WebRtcGetUserMediaBrowserTest.ApplyConstraintsNonDevice
+
+# crbug.com/1065184
+-RenderFrameHostImplBrowserTest.CheckIsCurrentBeforeAndAfterUnload
diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD.gn
index 44d2ffd..5a365d5 100644
--- a/third_party/android_deps/BUILD.gn
+++ b/third_party/android_deps/BUILD.gn
@@ -381,7 +381,14 @@
   ]
   skip_jetify = true
   ignore_aidl = true
+
+  # Manifest and proguard config have just one entry: Adding (and -keep'ing)
+  # android:appComponentFactory="androidx.core.app.CoreComponentFactory"
+  # Chrome doesn't use this feature and it causes a scary stack trace to be
+  # shown when incremental_install=true.
+  ignore_manifest = true
   ignore_proguard_configs = true
+  custom_package = "androidx.core"
 }
 
 # This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
diff --git a/third_party/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy b/third_party/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy
index 94ee9f9..813395b 100644
--- a/third_party/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy
+++ b/third_party/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy
@@ -263,20 +263,29 @@
                 sb.append('  jar_excluded_patterns = ["META-INF/proguard/*"]\n')
                 break
             case 'androidx_core_core':
-                sb.append('  ignore_proguard_configs = true\n')
-                // Target has AIDL, but we don't support it yet: http://crbug.com/644439
+                sb.append('\n')
+                sb.append('  # Target has AIDL, but we do not support it yet: http://crbug.com/644439\n')
                 sb.append('  ignore_aidl = true\n')
+                sb.append('\n')
+                sb.append('  # Manifest and proguard config have just one entry: Adding (and -keep\'ing\n')
+                sb.append('  # android:appComponentFactory="androidx.core.app.CoreComponentFactory"\n')
+                sb.append('  # Chrome does not use this feature and it causes a scary stack trace to be\n')
+                sb.append('  # shown when incremental_install=true.\n')
+                sb.append('  ignore_manifest = true\n')
+                sb.append('  ignore_proguard_configs = true\n')
+                sb.append('  custom_package = "androidx.core"\n')
                 break
             case 'androidx_media_media':
             case 'androidx_versionedparcelable_versionedparcelable':
             case 'com_android_support_support_compat':
             case 'com_android_support_support_media_compat':
             case 'com_android_support_versionedparcelable':
-                // Target has AIDL, but we don't support it yet: http://crbug.com/644439
+                sb.append('\n')
+                sb.append('  # Target has AIDL, but we do not support it yet: http://crbug.com/644439\n')
                 sb.append('  ignore_aidl = true\n')
                 break
             case 'androidx_test_uiautomator_uiautomator':
-	        sb.append('  deps = [":androidx_test_runner_java"]\n')
+                sb.append('  deps = [":androidx_test_runner_java"]\n')
                 break
             case 'com_android_support_mediarouter_v7':
                 sb.append('  # https://crbug.com/1000382\n')
@@ -300,17 +309,20 @@
             case 'android_arch_lifecycle_viewmodel':
             case 'androidx_lifecycle_lifecycle_runtime':
             case 'androidx_lifecycle_lifecycle_viewmodel':
+                sb.append('\n')
                 sb.append('  # https://crbug.com/887942#c1\n')
                 sb.append('  ignore_proguard_configs = true\n')
                 break
             case 'com_android_support_coordinatorlayout':
             case 'androidx_coordinatorlayout_coordinatorlayout':
+                sb.append('\n')
                 sb.append('  # https:crbug.com/954584\n')
                 sb.append('  ignore_proguard_configs = true\n')
                 break
             case 'com_android_support_design':
             case 'com_google_android_material_material':
-                // Reduce binary size. https:crbug.com/954584
+                sb.append('\n')
+                sb.append('  # Reduce binary size. https:crbug.com/954584\n')
                 sb.append('  ignore_proguard_configs = true\n')
                 break
             case 'com_android_support_support_annotations':
@@ -332,15 +344,17 @@
                 sb.append('  extract_native_libraries = true\n')
                 break
             case 'com_google_guava_guava':
-                // Need to exclude class and replace it with class library as
-                // com_google_guava_listenablefuture has support_androids=true.
+                sb.append('\n')
+                sb.append('  # Need to exclude class and replace it with class library as\n')
+                sb.append('  # com_google_guava_listenablefuture has support_androids=true.\n')
                 sb.append('  deps += [":com_google_guava_listenablefuture_java"]\n')
                 sb.append('  jar_excluded_patterns = ["*/ListenableFuture.class"]\n')
                 break
             case 'com_google_code_findbugs_jsr305':
             case 'com_google_guava_listenablefuture':
             case 'com_googlecode_java_diff_utils_diffutils':
-                // Needed to break dependency cycle for errorprone_plugin_java.
+                sb.append('\n')
+                sb.append('  # Needed to break dependency cycle for errorprone_plugin_java.\n')
                 sb.append('  no_build_hooks = true\n')
                 break
             case 'androidx_test_rules':
diff --git a/third_party/blink/public/web/web_local_frame_client.h b/third_party/blink/public/web/web_local_frame_client.h
index d821def..8a2f1ab 100644
--- a/third_party/blink/public/web/web_local_frame_client.h
+++ b/third_party/blink/public/web/web_local_frame_client.h
@@ -497,6 +497,9 @@
   // A performance timing event (e.g. first paint) occurred
   virtual void DidChangePerformanceTiming() {}
 
+  // An Input Event observed.
+  virtual void DidObserveInputDelay(base::TimeDelta input_delay) {}
+
   // A cpu task or tasks completed.  Triggered when at least 100ms of wall time
   // was spent in tasks on the frame.
   virtual void DidChangeCpuTiming(base::TimeDelta time) {}
diff --git a/third_party/blink/public/web/web_performance.h b/third_party/blink/public/web/web_performance.h
index 730f436..ab7c1b2 100644
--- a/third_party/blink/public/web/web_performance.h
+++ b/third_party/blink/public/web/web_performance.h
@@ -102,9 +102,6 @@
   BLINK_EXPORT base::Optional<base::TimeDelta> FirstInputTimestamp() const;
   BLINK_EXPORT base::Optional<base::TimeDelta> LongestInputDelay() const;
   BLINK_EXPORT base::Optional<base::TimeDelta> LongestInputTimestamp() const;
-  BLINK_EXPORT double TotalInputDelay() const;
-  BLINK_EXPORT double TotalAdjustedInputDelay() const;
-  BLINK_EXPORT uint64_t NumInputEvents() const;
   BLINK_EXPORT double ParseStart() const;
   BLINK_EXPORT double ParseStop() const;
   BLINK_EXPORT double ParseBlockedOnScriptLoadDuration() const;
diff --git a/third_party/blink/renderer/core/css/css_properties.json5 b/third_party/blink/renderer/core/css/css_properties.json5
index fea7ee0..787e2b5a 100644
--- a/third_party/blink/renderer/core/css/css_properties.json5
+++ b/third_party/blink/renderer/core/css/css_properties.json5
@@ -466,6 +466,7 @@
       priority: "Animation",
       typedom_types: ["Time"],
       separator: ",",
+      valid_for_marker: true,
     },
     {
       name: "animation-direction",
@@ -479,6 +480,7 @@
       },
       priority: "Animation",
       separator: ",",
+      valid_for_marker: true,
     },
     {
       name: "animation-duration",
@@ -491,6 +493,7 @@
       priority: "Animation",
       typedom_types: ["Time"],
       separator: ",",
+      valid_for_marker: true,
     },
     {
       name: "animation-fill-mode",
@@ -503,6 +506,7 @@
       keywords: ["none", "forwards", "backwards", "both"],
       typedom_types: ["Keyword"],
       separator: ",",
+      valid_for_marker: true,
     },
     {
       name: "animation-iteration-count",
@@ -517,6 +521,7 @@
       keywords: ["infinite"],
       typedom_types: ["Keyword", "Number"],
       separator: ",",
+      valid_for_marker: true,
     },
     {
       name: "animation-name",
@@ -528,7 +533,8 @@
       priority: "Animation",
       keywords: ["none"],
       typedom_types: ["Keyword"],
-      separator: ","
+      separator: ",",
+      valid_for_marker: true,
     },
     {
       name: "animation-play-state",
@@ -541,6 +547,7 @@
       keywords: ["running", "paused"],
       typedom_types: ["Keyword"],
       separator: ",",
+      valid_for_marker: true,
     },
     {
       name: "animation-timing-function",
@@ -565,6 +572,7 @@
       ],
       typedom_types: ["Keyword"],
       separator: ",",
+      valid_for_marker: true,
     },
     {
       name: "transition-delay",
@@ -576,6 +584,7 @@
       priority: "Animation",
       typedom_types: ["Time"],
       separator: ",",
+      valid_for_marker: true,
     },
     {
       name: "transition-duration",
@@ -587,6 +596,7 @@
         attribute: "Duration",
       },
       priority: "Animation",
+      valid_for_marker: true,
     },
     {
       name: "transition-property",
@@ -597,7 +607,8 @@
       },
       priority: "Animation",
       keywords: ["none"],
-      typedom_types: ["Keyword"]
+      typedom_types: ["Keyword"],
+      valid_for_marker: true,
     },
     {
       name: "transition-timing-function",
@@ -621,6 +632,7 @@
         "step-end"],
       typedom_types: ["Keyword"],
       separator: ",",
+      valid_for_marker: true,
     },
 
     // High Priority and all other font properties.
diff --git a/third_party/blink/renderer/core/css/resolver/style_adjuster.cc b/third_party/blink/renderer/core/css/resolver/style_adjuster.cc
index 84d7bfa..49f61af 100644
--- a/third_party/blink/renderer/core/css/resolver/style_adjuster.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_adjuster.cc
@@ -222,7 +222,6 @@
     style.SetMarginEnd(Length::Fixed(margins.second));
   } else {
     // Outside list markers should generate a block container.
-    DCHECK_EQ(style.Display(), EDisplay::kInline);
     style.SetDisplay(EDisplay::kInlineBlock);
 
     // Do not break inside the marker, and honor the trailing spaces.
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.cc b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
index de7a32a..ac4742a 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
@@ -1399,6 +1399,8 @@
     });
     if (IsForcedColorsModeEnabled(state))
       filter = filter.Add(CSSProperty::kIsAffectedByForcedColors, true);
+    if (state.Style()->StyleType() == kPseudoIdMarker)
+      filter = filter.Add(CSSProperty::kValidForMarker, false);
     filter = filter.Add(CSSProperty::kAnimation, true);
     cascade->Analyze(interpolations, filter);
     cascade->Apply(&match_result, &interpolations, filter);
@@ -1449,6 +1451,23 @@
   return nullptr;
 }
 
+static bool PassesPropertyFilter(ValidPropertyFilter valid_property_filter,
+                                 CSSPropertyID property,
+                                 const Document& document) {
+  switch (valid_property_filter) {
+    case ValidPropertyFilter::kNoFilter:
+      return true;
+    case ValidPropertyFilter::kFirstLetter:
+      return CSSProperty::Get(property).IsValidForFirstLetter();
+    case ValidPropertyFilter::kCue:
+      return CSSProperty::Get(property).IsValidForCue();
+    case ValidPropertyFilter::kMarker:
+      return CSSProperty::Get(property).IsValidForMarker();
+  }
+  NOTREACHED();
+  return true;
+}
+
 template <CSSPropertyPriority priority>
 void StyleResolver::ApplyAnimatedStandardProperties(
     StyleResolverState& state,
@@ -1469,6 +1488,10 @@
         entry.key.GetCSSProperty().IsAffectedByForcedColors() &&
         state.Style()->ForcedColorAdjust() != EForcedColorAdjust::kNone)
       continue;
+    if (state.Style()->StyleType() == kPseudoIdMarker &&
+        !PassesPropertyFilter(ValidPropertyFilter::kMarker, property,
+                              state.GetDocument()))
+      continue;
     const Interpolation& interpolation = *entry.value.front();
     if (IsA<InvalidatableInterpolation>(interpolation)) {
       CSSInterpolationTypesMap map(state.GetDocument().GetPropertyRegistry(),
@@ -1481,23 +1504,6 @@
   }
 }
 
-static bool PassesPropertyFilter(ValidPropertyFilter valid_property_filter,
-                                 CSSPropertyID property,
-                                 const Document& document) {
-  switch (valid_property_filter) {
-    case ValidPropertyFilter::kNoFilter:
-      return true;
-    case ValidPropertyFilter::kFirstLetter:
-      return CSSProperty::Get(property).IsValidForFirstLetter();
-    case ValidPropertyFilter::kCue:
-      return CSSProperty::Get(property).IsValidForCue();
-    case ValidPropertyFilter::kMarker:
-      return CSSProperty::Get(property).IsValidForMarker();
-  }
-  NOTREACHED();
-  return true;
-}
-
 static inline void ApplyProperty(const CSSProperty& property,
                                  StyleResolverState& state,
                                  const CSSValue& value,
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_context.h b/third_party/blink/renderer/core/display_lock/display_lock_context.h
index 667567c..ad076b8 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_context.h
+++ b/third_party/blink/renderer/core/display_lock/display_lock_context.h
@@ -40,7 +40,8 @@
   // Shorthands
   kViewport = static_cast<uint16_t>(kSelection) |
               static_cast<uint16_t>(kUserFocus) |
-              static_cast<uint16_t>(kViewportIntersection),
+              static_cast<uint16_t>(kViewportIntersection) |
+              static_cast<uint16_t>(kAccessibility),
   kAny = static_cast<uint16_t>(kAccessibility) |
          static_cast<uint16_t>(kFindInPage) |
          static_cast<uint16_t>(kFragmentNavigation) |
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_utilities.cc b/third_party/blink/renderer/core/display_lock/display_lock_utilities.cc
index 7289620e..5944de5 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_utilities.cc
+++ b/third_party/blink/renderer/core/display_lock/display_lock_utilities.cc
@@ -296,22 +296,24 @@
   return nullptr;
 }
 
-bool DisplayLockUtilities::IsInNonActivatableLockedSubtree(const Node& node) {
-  if (!RuntimeEnabledFeatures::CSSSubtreeVisibilityEnabled() ||
+bool DisplayLockUtilities::IsInUnlockedOrActivatableSubtree(
+    const Node& node,
+    DisplayLockActivationReason activation_reason) {
+  if (!RuntimeEnabledFeatures::CSSSubtreeVisibilityEnabled(
+          node.GetExecutionContext()) ||
       node.GetDocument().LockedDisplayLockCount() == 0 ||
       node.GetDocument().DisplayLockBlockingAllActivationCount() == 0 ||
       !node.CanParticipateInFlatTree()) {
-    return false;
+    return true;
   }
 
   for (auto* element = NearestLockedExclusiveAncestor(node); element;
        element = NearestLockedExclusiveAncestor(*element)) {
-    if (!element->GetDisplayLockContext()->IsActivatable(
-            DisplayLockActivationReason::kAny)) {
-      return true;
+    if (!element->GetDisplayLockContext()->IsActivatable(activation_reason)) {
+      return false;
     }
   }
-  return false;
+  return true;
 }
 
 bool DisplayLockUtilities::IsInLockedSubtreeCrossingFrames(
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_utilities.h b/third_party/blink/renderer/core/display_lock/display_lock_utilities.h
index 89300d4..3eb26666 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_utilities.h
+++ b/third_party/blink/renderer/core/display_lock/display_lock_utilities.h
@@ -70,8 +70,22 @@
   static Element* NearestLockedInclusiveAncestor(const LayoutObject& object);
   static Element* NearestLockedExclusiveAncestor(const LayoutObject& object);
 
-  // Whether this node has non-activatable locked exclusive ancestors or not.
-  static bool IsInNonActivatableLockedSubtree(const Node& node);
+  // Returns true if |node| is not in a locked subtree, or if it's possible to
+  // activate all of the locked ancestors for |activation_reason|.
+  static bool IsInUnlockedOrActivatableSubtree(
+      const Node& node,
+      DisplayLockActivationReason activation_reason =
+          DisplayLockActivationReason::kAny);
+
+  // Returns true if |node| is in a locked subtree, and at least one of its
+  // locked ancestors can't be activated with |activation_reason|. In other
+  // words, this node should be treated as if it's not in the tree for
+  // |activation_reason|.
+  static bool ShouldIgnoreNodeDueToDisplayLock(
+      const Node& node,
+      DisplayLockActivationReason activation_reason) {
+    return !IsInUnlockedOrActivatableSubtree(node, activation_reason);
+  }
 
   // Returns true if the element is in a locked subtree (or is self-locked with
   // no self-updates). This crosses frames while navigating the ancestor chain.
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index acca70bd1..9801e9a 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -5201,6 +5201,7 @@
       element->SetComputedStyle(std::move(pseudo_style));
     else
       GetElementRareData()->SetPseudoElement(kPseudoIdFirstLetter, nullptr);
+    element->ClearNeedsStyleRecalc();
     return;
   }
 
diff --git a/third_party/blink/renderer/core/exported/local_frame_client_impl.cc b/third_party/blink/renderer/core/exported/local_frame_client_impl.cc
index a06e5240..22919e8 100644
--- a/third_party/blink/renderer/core/exported/local_frame_client_impl.cc
+++ b/third_party/blink/renderer/core/exported/local_frame_client_impl.cc
@@ -724,6 +724,12 @@
     web_frame_->Client()->DidChangePerformanceTiming();
 }
 
+void LocalFrameClientImpl::DidObserveInputDelay(base::TimeDelta input_delay) {
+  if (web_frame_->Client()) {
+    web_frame_->Client()->DidObserveInputDelay(input_delay);
+  }
+}
+
 void LocalFrameClientImpl::DidChangeCpuTiming(base::TimeDelta time) {
   if (web_frame_->Client())
     web_frame_->Client()->DidChangeCpuTiming(time);
diff --git a/third_party/blink/renderer/core/exported/local_frame_client_impl.h b/third_party/blink/renderer/core/exported/local_frame_client_impl.h
index 8ff5c7ff..77194e8e 100644
--- a/third_party/blink/renderer/core/exported/local_frame_client_impl.h
+++ b/third_party/blink/renderer/core/exported/local_frame_client_impl.h
@@ -140,6 +140,7 @@
   void DidDisplayContentWithCertificateErrors() override;
   void DidRunContentWithCertificateErrors() override;
   void DidChangePerformanceTiming() override;
+  void DidObserveInputDelay(base::TimeDelta) override;
   void DidChangeCpuTiming(base::TimeDelta) override;
   void DidObserveLoadingBehavior(LoadingBehaviorFlag) override;
   void DidObserveNewFeatureUsage(mojom::WebFeature) override;
diff --git a/third_party/blink/renderer/core/exported/web_frame_test.cc b/third_party/blink/renderer/core/exported/web_frame_test.cc
index 1c0ccb7..c8d7b26 100644
--- a/third_party/blink/renderer/core/exported/web_frame_test.cc
+++ b/third_party/blink/renderer/core/exported/web_frame_test.cc
@@ -13127,7 +13127,7 @@
   ASSERT_TRUE(widget);
   gfx::Point viewport_offset(7, -11);
   WebRect viewport_intersection(0, 11, 200, 89);
-  WebRect mainframe_intersection(0, 0, 200, 140);
+  WebRect mainframe_intersection(7, -11, 200, 140);
   FrameOcclusionState occlusion_state = FrameOcclusionState::kUnknown;
   widget->SetRemoteViewportIntersection(
       {viewport_offset, viewport_intersection, mainframe_intersection,
diff --git a/third_party/blink/renderer/core/exported/web_performance.cc b/third_party/blink/renderer/core/exported/web_performance.cc
index f927ef9..e29242e 100644
--- a/third_party/blink/renderer/core/exported/web_performance.cc
+++ b/third_party/blink/renderer/core/exported/web_performance.cc
@@ -212,17 +212,6 @@
   return private_->timing()->LongestInputTimestamp();
 }
 
-double WebPerformance::TotalInputDelay() const {
-  return MillisecondsToSeconds(private_->timing()->TotalInputDelay());
-}
-
-double WebPerformance::TotalAdjustedInputDelay() const {
-  return MillisecondsToSeconds(private_->timing()->TotalAdjustedInputDelay());
-}
-uint64_t WebPerformance::NumInputEvents() const {
-  return private_->timing()->NumInputEvents();
-}
-
 double WebPerformance::ParseStart() const {
   return MillisecondsToSeconds(private_->timing()->ParseStart());
 }
diff --git a/third_party/blink/renderer/core/frame/frame_view.cc b/third_party/blink/renderer/core/frame/frame_view.cc
index 1855e03..0591058 100644
--- a/third_party/blink/renderer/core/frame/frame_view.cc
+++ b/third_party/blink/renderer/core/frame/frame_view.cc
@@ -159,20 +159,8 @@
       }
     }
 
-    PhysicalRect mainframe_intersection_rect;
-    if (!geometry.UnclippedIntersectionRect().IsEmpty()) {
-      mainframe_intersection_rect = PhysicalRect::EnclosingRect(
-          matrix.ProjectQuad(FloatRect(geometry.UnclippedIntersectionRect()))
-              .BoundingBox());
-
-      if (mainframe_intersection_rect.IsEmpty()) {
-        mainframe_document_intersection = IntRect(
-            FlooredIntPoint(mainframe_intersection_rect.offset), IntSize());
-      } else {
-        mainframe_document_intersection =
-            EnclosingIntRect(mainframe_intersection_rect);
-      }
-    }
+    mainframe_document_intersection =
+        EnclosingIntRect(geometry.UnclippedIntersectionRect());
   } else if (occlusion_state == FrameOcclusionState::kGuaranteedNotOccluded) {
     // If the parent LocalFrameView is throttled and out-of-date, then we can't
     // get any useful information.
@@ -186,6 +174,11 @@
 
   UpdateFrameVisibility(!viewport_intersection.IsEmpty());
 
+  if (ShouldReportMainFrameIntersection()) {
+    GetFrame().Client()->OnMainFrameDocumentIntersectionChanged(
+        mainframe_document_intersection);
+  }
+
   // We don't throttle 0x0 or display:none iframes, because in practice they are
   // sometimes used to drive UI logic.
   bool hidden_for_throttling = viewport_intersection.IsEmpty() &&
diff --git a/third_party/blink/renderer/core/frame/frame_view.h b/third_party/blink/renderer/core/frame/frame_view.h
index 73d183d22..aa5e42a 100644
--- a/third_party/blink/renderer/core/frame/frame_view.h
+++ b/third_party/blink/renderer/core/frame/frame_view.h
@@ -17,7 +17,11 @@
 class Frame;
 struct IntrinsicSizingInfo;
 
-class CORE_EXPORT FrameView : public EmbeddedContentView {
+// clang::lto_visibility_public is necessary to prevent the compiler from
+// performing a vtable optimization that crashes the renderer. See
+// crbug.com/1062006.
+class CORE_EXPORT [[clang::lto_visibility_public]] FrameView
+    : public EmbeddedContentView {
  public:
   FrameView(const IntRect& frame_rect) : EmbeddedContentView(frame_rect) {}
   ~FrameView() override = default;
@@ -40,6 +44,7 @@
   bool CanThrottleRenderingForPropagation() const;
 
   bool IsFrameView() const override { return true; }
+  virtual bool ShouldReportMainFrameIntersection() const { return false; }
 
   Frame& GetFrame() const;
   blink::mojom::FrameVisibility GetFrameVisibility() const {
diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc
index 2fdd3d5..038e421 100644
--- a/third_party/blink/renderer/core/frame/local_frame.cc
+++ b/third_party/blink/renderer/core/frame/local_frame.cc
@@ -1491,8 +1491,14 @@
   // Notify the render frame observers when the main frame intersection changes.
   if (intersection_state_.main_frame_document_intersection !=
       intersection_state.main_frame_document_intersection) {
+    // Put the main frame document intersection in the coordinate system of the
+    // viewport.
+    IntRect offset_main_frame_intersection =
+        intersection_state.main_frame_document_intersection;
+    offset_main_frame_intersection.MoveBy(
+        IntPoint(intersection_state.viewport_offset));
     Client()->OnMainFrameDocumentIntersectionChanged(
-        intersection_state.main_frame_document_intersection);
+        offset_main_frame_intersection);
   }
 
   bool can_skip_sticky_frame_tracking =
diff --git a/third_party/blink/renderer/core/frame/local_frame_client.h b/third_party/blink/renderer/core/frame/local_frame_client.h
index 7843fbbf..5cd03293 100644
--- a/third_party/blink/renderer/core/frame/local_frame_client.h
+++ b/third_party/blink/renderer/core/frame/local_frame_client.h
@@ -181,6 +181,8 @@
 
   // Will be called when |PerformanceTiming| events are updated
   virtual void DidChangePerformanceTiming() {}
+  // Will be called when an |InputEvent| is observed.
+  virtual void DidObserveInputDelay(base::TimeDelta input_delay) {}
 
   // Will be called when |CpuTiming| events are updated
   virtual void DidChangeCpuTiming(base::TimeDelta time) {}
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 38b4190..ded89456 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -3703,12 +3703,6 @@
   }
 }
 
-void LocalFrameView::SetViewportIntersection(
-    const ViewportIntersectionState& intersection_state) {
-  GetFrame().Client()->OnMainFrameDocumentIntersectionChanged(
-      intersection_state.main_frame_document_intersection);
-}
-
 PhysicalOffset LocalFrameView::ViewportToFrame(
     const PhysicalOffset& point_in_viewport) const {
   PhysicalOffset point_in_root_frame = PhysicalOffset::FromFloatPointRound(
@@ -4419,11 +4413,22 @@
   // This is the top-level frame, so no mapping necessary.
   if (frame_->IsMainFrame())
     return true;
-  bool result = rect.InclusiveIntersect(PhysicalRect(
-      apply_overflow_clip ? frame_->RemoteViewportIntersection()
-                          : frame_->RemoteMainFrameDocumentIntersection()));
-  if (result)
+  bool result;
+  if (apply_overflow_clip) {
+    result = rect.InclusiveIntersect(
+        PhysicalRect(frame_->RemoteViewportIntersection()));
+    if (result)
+      rect.Move(PhysicalOffset(GetFrame().RemoteViewportOffset()));
+  } else {
+    // If we are not applying the overflow clip, the mapping should be in the
+    // remote viewport's coordinate system. Map rect to the remote viewport's
+    // coordinate system prior to intersecting.
+    // RemoteMainFrameDocumentIntersection is in the remote viewport's
+    // coordinate system.
     rect.Move(PhysicalOffset(GetFrame().RemoteViewportOffset()));
+    result = rect.InclusiveIntersect(
+        PhysicalRect(frame_->RemoteMainFrameDocumentIntersection()));
+  }
   return result;
 }
 
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 fe91ce4..e4362082 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.h
+++ b/third_party/blink/renderer/core/frame/local_frame_view.h
@@ -552,6 +552,7 @@
   void Hide() override;
 
   bool IsLocalFrameView() const override { return true; }
+  bool ShouldReportMainFrameIntersection() const override { return true; }
 
   void Trace(Visitor*) override;
   void NotifyPageThatContentAreaWillPaint() const;
@@ -702,7 +703,7 @@
   void ParentVisibleChanged() override;
   void NotifyFrameRectsChangedIfNeeded();
   void SetViewportIntersection(
-      const ViewportIntersectionState& intersection_state) override;
+      const ViewportIntersectionState& intersection_state) override {}
   void VisibilityForThrottlingChanged() override;
   bool LifecycleUpdatesThrottled() const override {
     return lifecycle_updates_throttled_;
diff --git a/third_party/blink/renderer/core/intersection_observer/intersection_geometry.cc b/third_party/blink/renderer/core/intersection_observer/intersection_geometry.cc
index 586abe8f..7527e93 100644
--- a/third_party/blink/renderer/core/intersection_observer/intersection_geometry.cc
+++ b/third_party/blink/renderer/core/intersection_observer/intersection_geometry.cc
@@ -335,9 +335,6 @@
               .Inverse();
       intersection_rect_ = PhysicalRect::EnclosingRect(
           matrix.ProjectQuad(FloatRect(intersection_rect_)).BoundingBox());
-      unclipped_intersection_rect_ = PhysicalRect::EnclosingRect(
-          matrix.ProjectQuad(FloatRect(unclipped_intersection_rect_))
-              .BoundingBox());
       // intersection_rect_ is in the coordinate system of the implicit root;
       // map it down the to absolute coordinates for the target's document.
     } else {
@@ -348,10 +345,6 @@
           root_geometry.root_to_document_transform
               .MapQuad(FloatQuad(FloatRect(intersection_rect_)))
               .BoundingBox());
-      unclipped_intersection_rect_ = PhysicalRect::EnclosingRect(
-          root_geometry.root_to_document_transform
-              .MapQuad(FloatQuad(FloatRect(unclipped_intersection_rect_)))
-              .BoundingBox());
     }
   } else {
     intersection_rect_ = PhysicalRect();
diff --git a/third_party/blink/renderer/core/intersection_observer/intersection_geometry.h b/third_party/blink/renderer/core/intersection_observer/intersection_geometry.h
index 35e33f4..854c98c 100644
--- a/third_party/blink/renderer/core/intersection_observer/intersection_geometry.h
+++ b/third_party/blink/renderer/core/intersection_observer/intersection_geometry.h
@@ -100,6 +100,9 @@
 
   PhysicalRect TargetRect() const { return target_rect_; }
   PhysicalRect IntersectionRect() const { return intersection_rect_; }
+
+  // The intersection rect without applying viewport clipping in the coordinate
+  // system of the root's viewport.
   PhysicalRect UnclippedIntersectionRect() const {
     return unclipped_intersection_rect_;
   }
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc
index 0989d16..237ac7bc 100644
--- a/third_party/blink/renderer/core/loader/document_loader.cc
+++ b/third_party/blink/renderer/core/loader/document_loader.cc
@@ -389,6 +389,11 @@
   }
 }
 
+void DocumentLoader::DidObserveInputDelay(base::TimeDelta input_delay) {
+  if (frame_ && state_ >= kCommitted) {
+    GetLocalFrameClient().DidObserveInputDelay(input_delay);
+  }
+}
 void DocumentLoader::DidObserveLoadingBehavior(LoadingBehaviorFlag behavior) {
   if (frame_) {
     DCHECK_GE(state_, kCommitted);
diff --git a/third_party/blink/renderer/core/loader/document_loader.h b/third_party/blink/renderer/core/loader/document_loader.h
index 0deff31..31fc575 100644
--- a/third_party/blink/renderer/core/loader/document_loader.h
+++ b/third_party/blink/renderer/core/loader/document_loader.h
@@ -154,6 +154,7 @@
       const;
 
   void DidChangePerformanceTiming();
+  void DidObserveInputDelay(base::TimeDelta input_delay);
   void DidObserveLoadingBehavior(LoadingBehaviorFlag);
   void UpdateForSameDocumentNavigation(const KURL&,
                                        SameDocumentNavigationSource,
diff --git a/third_party/blink/renderer/core/loader/interactive_detector.cc b/third_party/blink/renderer/core/loader/interactive_detector.cc
index 52d98d6..8ce504fc 100644
--- a/third_party/blink/renderer/core/loader/interactive_detector.cc
+++ b/third_party/blink/renderer/core/loader/interactive_detector.cc
@@ -153,18 +153,6 @@
   return page_event_times_.longest_input_timestamp;
 }
 
-uint64_t InteractiveDetector::GetNumInputEvents() const {
-  return page_event_times_.num_input_events;
-}
-
-base::TimeDelta InteractiveDetector::GetTotalInputDelay() const {
-  return page_event_times_.total_input_delay;
-}
-
-base::TimeDelta InteractiveDetector::GetTotalAdjustedInputDelay() const {
-  return page_event_times_.total_adjusted_input_delay;
-}
-
 bool InteractiveDetector::PageWasBackgroundedSinceEvent(
     base::TimeTicks event_time) {
   DCHECK(GetSupplementable());
@@ -235,17 +223,14 @@
     event_timestamp = event_platform_timestamp;
   }
 
-  page_event_times_.num_input_events++;
-  page_event_times_.total_input_delay += delay;
-  page_event_times_.total_adjusted_input_delay +=
-      base::TimeDelta::FromMilliseconds(
-          std::max(delay.InMilliseconds() - 50, int64_t(0)));
   pending_pointerdown_delay_ = base::TimeDelta();
   pending_pointerdown_timestamp_ = base::TimeTicks();
+  bool interactive_timing_metrics_changed = false;
 
   if (!page_event_times_.first_input_delay.has_value()) {
     page_event_times_.first_input_delay = delay;
     page_event_times_.first_input_timestamp = event_timestamp;
+    interactive_timing_metrics_changed = true;
 
     if (delay > kFirstInputDelayTraceEventThreshold) {
       // Emit a trace event to highlight long first input delays.
@@ -275,6 +260,9 @@
   ukm::builders::InputEvent(source_id)
       .SetInteractiveTiming_InputDelay(delay.InMilliseconds())
       .Record(GetUkmRecorder());
+  if (GetSupplementable()->Loader()) {
+    GetSupplementable()->Loader()->DidObserveInputDelay(delay);
+  }
 
   UMA_HISTOGRAM_CUSTOM_TIMES(kHistogramInputDelay, delay,
                              base::TimeDelta::FromMilliseconds(1),
@@ -291,10 +279,12 @@
       !PageWasBackgroundedSinceEvent(event_timestamp)) {
     page_event_times_.longest_input_delay = delay;
     page_event_times_.longest_input_timestamp = event_timestamp;
+    interactive_timing_metrics_changed = true;
   }
 
-  if (GetSupplementable()->Loader())
+  if (GetSupplementable()->Loader() && interactive_timing_metrics_changed) {
     GetSupplementable()->Loader()->DidChangePerformanceTiming();
+  }
 }
 
 void InteractiveDetector::BeginNetworkQuietPeriod(
diff --git a/third_party/blink/renderer/core/loader/interactive_detector.h b/third_party/blink/renderer/core/loader/interactive_detector.h
index 3b504a2..09e8622e 100644
--- a/third_party/blink/renderer/core/loader/interactive_detector.h
+++ b/third_party/blink/renderer/core/loader/interactive_detector.h
@@ -100,15 +100,6 @@
   // GetLongestInputDelay().
   base::Optional<base::TimeTicks> GetLongestInputTimestamp() const;
 
-  // The number of user interactions.
-  uint64_t GetNumInputEvents() const;
-
-  // The sum of all input delay.
-  base::TimeDelta GetTotalInputDelay() const;
-
-  // The sum of all adjusted input delay.
-  base::TimeDelta GetTotalAdjustedInputDelay() const;
-
   // Process an input event, updating first_input_delay and
   // first_input_timestamp if needed.
   void HandleForInputDelay(const Event&,
@@ -151,9 +142,6 @@
     base::Optional<base::TimeDelta> longest_input_delay;
     base::Optional<base::TimeTicks> first_input_timestamp;
     base::Optional<base::TimeTicks> longest_input_timestamp;
-    base::TimeDelta total_input_delay;
-    base::TimeDelta total_adjusted_input_delay;
-    uint64_t num_input_events;
   } page_event_times_;
 
   struct VisibilityChangeEvent {
diff --git a/third_party/blink/renderer/core/loader/interactive_detector_test.cc b/third_party/blink/renderer/core/loader/interactive_detector_test.cc
index 19ac39d..ecd729b 100644
--- a/third_party/blink/renderer/core/loader/interactive_detector_test.cc
+++ b/third_party/blink/renderer/core/loader/interactive_detector_test.cc
@@ -584,30 +584,6 @@
       delay.InMilliseconds());
 }
 
-TEST_F(InteractiveDetectorTest, TotalInputDelay) {
-  Event event_1;
-  event_1.SetTrusted(true);
-  event_1.SetType(event_type_names::kClick);
-  base::TimeDelta delay_1 = base::TimeDelta::FromMilliseconds(10);
-  base::TimeTicks processing_start_1 = Now() + delay_1;
-  base::TimeTicks event_platform_timestamp_1 = Now();
-  GetDetector()->HandleForInputDelay(event_1, event_platform_timestamp_1,
-                                     processing_start_1);
-  Event event_2;
-  event_2.SetTrusted(true);
-  event_2.SetType(event_type_names::kClick);
-  base::TimeDelta delay_2 = base::TimeDelta::FromMilliseconds(60);
-  base::TimeTicks processing_start_2 = Now() + delay_2;
-  base::TimeTicks event_platform_timestamp_2 = Now();
-  GetDetector()->HandleForInputDelay(event_2, event_platform_timestamp_2,
-                                     processing_start_2);
-
-  EXPECT_EQ(uint64_t(2), GetDetector()->GetNumInputEvents());
-  EXPECT_EQ(int64_t(70), GetDetector()->GetTotalInputDelay().InMilliseconds());
-  EXPECT_EQ(int64_t(10),
-            GetDetector()->GetTotalAdjustedInputDelay().InMilliseconds());
-}
-
 // In tests for Total Blocking Time (TBT) we call SetTimeToInteractive() instead
 // of allowing TimeToInteractive to occur because the computation is gated
 // behind tracing being enabled, which means that they won't run by default. In
diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.cc b/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.cc
index 1581092c..ce7d9b18 100644
--- a/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.cc
+++ b/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.cc
@@ -138,7 +138,12 @@
   if (auto* scrollable_area = layer->GetScrollableArea()) {
     if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
       bool force_prefer_compositing_to_lcd_text =
-          reasons != CompositingReason::kNone;
+          reasons != CompositingReason::kNone ||
+          // In CompositeAfterPaint though we don't treat hidden backface as
+          // a direct compositing reason, it's very likely that the object will
+          // be composited, and it also indicates preference of compositing,
+          // so we prefer composited scrolling here.
+          style.BackfaceVisibility() == EBackfaceVisibility::kHidden;
 
       if (!force_prefer_compositing_to_lcd_text && object.IsLayoutView()) {
         if (auto* owner_object = object.GetFrame()->OwnerLayoutObject()) {
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index b8b50fdf..cf5d358 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -422,6 +422,12 @@
     if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
       if (direct_compositing_reasons != CompositingReason::kNone)
         return true;
+      // In CompositeAfterPaint though we don't treat hidden backface as
+      // a direct compositing reason, it's very likely that the object will
+      // be composited, so a paint offset translation will be beneficial.
+      if (box_model.StyleRef().BackfaceVisibility() ==
+          EBackfaceVisibility::kHidden)
+        return true;
     } else if (box_model.GetCompositingState() == kPaintsIntoOwnBacking) {
       return true;
     }
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
index 8402f5b7..1805b3d 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
@@ -5158,16 +5158,10 @@
   ASSERT_NE(nullptr, target_properties);
   const auto* paint_offset_translation =
       target_properties->PaintOffsetTranslation();
-  if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
-    EXPECT_EQ(nullptr, paint_offset_translation);
-    EXPECT_EQ(PhysicalOffset(60, 50), target->FirstFragment().PaintOffset());
-  } else {
-    // For SPv1, |target| is composited so we created PaintOffsetTranslation.
-    ASSERT_NE(nullptr, paint_offset_translation);
-    EXPECT_EQ(FloatSize(60, 50), paint_offset_translation->Translation2D());
-    EXPECT_EQ(TransformPaintPropertyNode::BackfaceVisibility::kInherited,
-              paint_offset_translation->GetBackfaceVisibilityForTesting());
-  }
+  ASSERT_NE(nullptr, paint_offset_translation);
+  EXPECT_EQ(FloatSize(60, 50), paint_offset_translation->Translation2D());
+  EXPECT_EQ(TransformPaintPropertyNode::BackfaceVisibility::kInherited,
+            paint_offset_translation->GetBackfaceVisibilityForTesting());
 
   const auto* transform = target_properties->Transform();
   ASSERT_NE(nullptr, transform);
diff --git a/third_party/blink/renderer/core/timing/performance_timing.cc b/third_party/blink/renderer/core/timing/performance_timing.cc
index 902bb05a..a661f3f 100644
--- a/third_party/blink/renderer/core/timing/performance_timing.cc
+++ b/third_party/blink/renderer/core/timing/performance_timing.cc
@@ -448,29 +448,6 @@
       interactive_detector->GetLongestInputTimestamp());
 }
 
-uint64_t PerformanceTiming::TotalInputDelay() const {
-  const InteractiveDetector* interactive_detector = GetInteractiveDetector();
-  if (!interactive_detector)
-    return 0;
-
-  return ToIntegerMilliseconds(interactive_detector->GetTotalInputDelay());
-}
-
-uint64_t PerformanceTiming::TotalAdjustedInputDelay() const {
-  const InteractiveDetector* interactive_detector = GetInteractiveDetector();
-  if (!interactive_detector)
-    return 0;
-
-  return ToIntegerMilliseconds(
-      interactive_detector->GetTotalAdjustedInputDelay());
-}
-uint64_t PerformanceTiming::NumInputEvents() const {
-  const InteractiveDetector* interactive_detector = GetInteractiveDetector();
-  if (!interactive_detector)
-    return 0;
-
-  return interactive_detector->GetNumInputEvents();
-}
 uint64_t PerformanceTiming::ParseStart() const {
   const DocumentParserTiming* timing = GetDocumentParserTiming();
   if (!timing)
diff --git a/third_party/blink/renderer/core/timing/performance_timing.h b/third_party/blink/renderer/core/timing/performance_timing.h
index 32e5068..29ce0e4d 100644
--- a/third_party/blink/renderer/core/timing/performance_timing.h
+++ b/third_party/blink/renderer/core/timing/performance_timing.h
@@ -139,12 +139,6 @@
   base::Optional<base::TimeDelta> LongestInputDelay() const;
   // The timestamp of the event whose delay is reported by LongestInputDelay().
   base::Optional<base::TimeDelta> LongestInputTimestamp() const;
-  // The sum of all input delay.
-  uint64_t TotalInputDelay() const;
-  // The sum of all adjusted input delay.
-  uint64_t TotalAdjustedInputDelay() const;
-  // The number of user interactions.
-  uint64_t NumInputEvents() const;
 
   uint64_t ParseStart() const;
   uint64_t ParseStop() const;
diff --git a/third_party/blink/renderer/core/workers/worker_thread_test.cc b/third_party/blink/renderer/core/workers/worker_thread_test.cc
index 7a1eddc..a0da94e 100644
--- a/third_party/blink/renderer/core/workers/worker_thread_test.cc
+++ b/third_party/blink/renderer/core/workers/worker_thread_test.cc
@@ -492,8 +492,7 @@
   EXPECT_EQ(ExitCode::kGracefullyTerminated, GetExitCode());
 }
 
-// Disabled due to flakiness: https://crbug.com/935231
-TEST_F(WorkerThreadTest, DISABLED_TerminateWorkerWhileChildIsLoading) {
+TEST_F(WorkerThreadTest, TerminateWorkerWhileChildIsLoading) {
   ExpectReportingCalls();
   Start();
   worker_thread_->WaitForInit();
diff --git a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
index e5a5570..b6fdfb64 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
@@ -79,6 +79,7 @@
 #include "third_party/blink/renderer/core/layout/layout_object.h"
 #include "third_party/blink/renderer/core/layout/layout_table.h"
 #include "third_party/blink/renderer/core/layout/layout_view.h"
+#include "third_party/blink/renderer/core/page/focus_controller.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/svg/svg_element.h"
 #include "third_party/blink/renderer/core/svg/svg_svg_element.h"
@@ -372,7 +373,8 @@
   }
 
   if (DisplayLockUtilities::NearestLockedExclusiveAncestor(*GetNode())) {
-    if (DisplayLockUtilities::IsInNonActivatableLockedSubtree(*GetNode())) {
+    if (DisplayLockUtilities::ShouldIgnoreNodeDueToDisplayLock(
+            *GetNode(), DisplayLockActivationReason::kAccessibility)) {
       if (ignored_reasons)
         ignored_reasons->push_back(IgnoredReason(kAXNotRendered));
       return true;
@@ -3139,7 +3141,21 @@
 
   Document* document = GetDocument();
   if (IsWebArea()) {
-    document->ClearFocusedElement();
+    // If another Frame has focused content (e.g. nested iframe), then we
+    // need to clear focus for the other Document Frame.
+    // Here we set the focused element via the FocusController so that the
+    // other Frame loses focus, and the target Document Element gains focus.
+    // This fixes a scenario with Narrator Item Navigation when the user
+    // navigates from the outer UI to the document when the last focused
+    // element was within a nested iframe before leaving the document frame.
+    Page* page = document->GetPage();
+    // Elements inside a portal should not be focusable.
+    if (page && !page->InsidePortal()) {
+      page->GetFocusController().SetFocusedElement(document->documentElement(),
+                                                   document->GetFrame());
+    } else {
+      document->ClearFocusedElement();
+    }
     return true;
   }
 
diff --git a/third_party/blink/renderer/platform/graphics/paint/drawing_display_item.h b/third_party/blink/renderer/platform/graphics/paint/drawing_display_item.h
index 636a26c..a6fa2cd 100644
--- a/third_party/blink/renderer/platform/graphics/paint/drawing_display_item.h
+++ b/third_party/blink/renderer/platform/graphics/paint/drawing_display_item.h
@@ -37,6 +37,8 @@
   bool Equals(const DisplayItem& other) const final;
 
   bool KnownToBeOpaque() const {
+    if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
+      return false;
     if (!known_to_be_opaque_.has_value())
       known_to_be_opaque_.emplace(CalculateKnownToBeOpaque(record_.get()));
     return *known_to_be_opaque_;
diff --git a/third_party/blink/web_tests/FlagExpectations/composite-after-paint b/third_party/blink/web_tests/FlagExpectations/composite-after-paint
index d59e2037..dd4f1bec 100644
--- a/third_party/blink/web_tests/FlagExpectations/composite-after-paint
+++ b/third_party/blink/web_tests/FlagExpectations/composite-after-paint
@@ -75,23 +75,14 @@
 # Raster invalidation doesn't work for huge layers. 
 paint/invalidation/raster-under-invalidation-checking.html [ Failure ]
 
-# backface-visibility:hidden doesn't trigger composited scrolling.
+# No composited scrolling for overflow:hidden (on marquee).
 compositing/overflow/do-not-repaint-if-scrolling-composited-layers.html [ Failure ]
-paint/invalidation/compositing/scrolling-neg-z-index-descendants.html [ Failure ]
-paint/invalidation/scroll/overflow-hidden-yet-scrolled-with-custom-scrollbar.html [ Failure ]
-paint/invalidation/scroll/overflow-hidden-yet-scrolled.html [ Failure ]
 
 # We paint the iframe's content background in the scrolling layer, causing invalidation on scroll.
 paint/invalidation/scroll/iframe-scroll-repaint.html [ Failure ]
 # will-transform on descendant doesn't trigger compositing of iframe. 
 paint/invalidation/scroll/composited-iframe-scroll-repaint.html [ Failure ]
 
-# backface-visiblity:hidden is not a direct compositing reason.
-# Extra raster invalidation caused by offset change, etc.
-paint/invalidation/compositing/should-not-repaint-move-backface-hidden.html [ Failure ]
-paint/invalidation/position/relative-positioned-movement-repaint.html [ Failure ]
-paint/invalidation/compositing/should-not-repaint-composited-descendants.html [ Failure ]
-
 # Extra layers for non-fast scrolling areas.
 compositing/overflow/textarea-scroll-touch.html [ Failure ]
 
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 836f970..838be19 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1784,8 +1784,6 @@
 crbug.com/995106 external/wpt/css/css-pseudo/first-letter-exclude-inline-marker.html [ Failure ]
 crbug.com/995106 external/wpt/css/css-pseudo/first-letter-exclude-inline-child-marker.html [ Failure ]
 
-crbug.com/1054509 external/wpt/css/css-transitions/non-rendered-element-004.tentative.html [ Skip ]
-
 crbug.com/1058822 virtual/dark-color-scheme/external/wpt/css/css-color-adjust/rendering/dark-color-scheme/color-scheme-iframe-background-mismatch-alpha.html [ Failure ]
 crbug.com/1058822 virtual/dark-color-scheme/external/wpt/css/css-color-adjust/rendering/dark-color-scheme/color-scheme-iframe-background-mismatch-opaque.html [ Failure ]
 
@@ -3200,8 +3198,6 @@
 crbug.com/626703 [ Mac ] external/wpt/css/css-sizing/clone-intrinsic-size.html [ Failure ]
 crbug.com/626703 [ Win ] external/wpt/css/css-sizing/clone-intrinsic-size.html [ Failure ]
 crbug.com/626703 [ Retina ] external/wpt/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-screenx-screeny.html [ Timeout ]
-crbug.com/626703 [ Mac10.12 ] external/wpt/media-source/mediasource-config-change-webm-av-framesize.html [ Timeout ]
-crbug.com/626703 [ Mac10.12 ] external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-duration.html [ Timeout ]
 crbug.com/626703 [ Mac10.12 ] external/wpt/preload/download-resources.html [ Failure Timeout ]
 crbug.com/626703 [ Mac10.13 ] external/wpt/preload/download-resources.html [ Failure Timeout ]
 crbug.com/626703 [ Retina ] external/wpt/preload/download-resources.html [ Failure Timeout ]
@@ -3285,7 +3281,6 @@
 crbug.com/626703 external/wpt/css/css-lists/li-list-item-counter.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-006.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-004.html [ Failure ]
-crbug.com/626703 virtual/threaded/external/wpt/css/css-animations/animationevent-marker-pseudoelement.html [ Timeout ]
 crbug.com/626703 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-007.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-lists/counter-set-001.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-text/text-transform/text-transform-multiple-001.html [ Failure ]
@@ -3296,7 +3291,6 @@
 crbug.com/626703 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-008.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-animations/animationevent-marker-pseudoelement.html [ Timeout ]
 crbug.com/626703 external/wpt/css/css-text/overflow-wrap/overflow-wrap-normal-keep-all-001.html [ Failure ]
-crbug.com/626703 [ Mac10.12 ] external/wpt/media-source/mediasource-config-change-webm-a-bitrate.html [ Timeout ]
 crbug.com/626703 external/wpt/html/semantics/embedded-content/media-elements/src_object_blob.html [ Timeout ]
 crbug.com/626703 external/wpt/css/css-lists/list-item-definition.html [ Failure ]
 crbug.com/626703 [ Win7 ] external/wpt/webrtc/RTCPeerConnection-ondatachannel.html [ Failure Timeout ]
@@ -4755,9 +4749,6 @@
 
 # Sheriff failures 2017-07-03
 crbug.com/708994 http/tests/security/cross-frame-mouse-source-capabilities.html [ Timeout Pass ]
-
-crbug.com/746128 [ Win7 Debug ] media/controls/video-enter-exit-fullscreen-without-hovering-doesnt-show-controls.html [ Failure ]
-crbug.com/746128 [ Win7 Debug ] virtual/audio-service/media/controls/video-enter-exit-fullscreen-without-hovering-doesnt-show-controls.html [ Failure ]
 crbug.com/746128 [ Mac ] media/controls/video-enter-exit-fullscreen-without-hovering-doesnt-show-controls.html [ Failure Pass ]
 crbug.com/746128 [ Mac ] virtual/audio-service/media/controls/video-enter-exit-fullscreen-without-hovering-doesnt-show-controls.html [ Failure Pass ]
 
@@ -6022,7 +6013,6 @@
 crbug.com/1003055 virtual/threaded/external/wpt/css/css-scroll-snap/scroll-target-snap-002.html [ Failure ]
 
 # Sheriff 2019-09-12
-crbug.com/1011515 [ Win7 ] fast/harness/internals-observe-gc.html [ Failure ]
 crbug.com/731018 [ Mac ] external/wpt/accelerometer/Accelerometer.https.html [ Pass Failure ]
 crbug.com/731018 [ Mac ] external/wpt/gyroscope/Gyroscope.https.html [ Pass Failure ]
 crbug.com/731018 [ Mac ] external/wpt/orientation-sensor/AbsoluteOrientationSensor.https.html [ Pass Failure ]
diff --git a/third_party/blink/web_tests/WPTOverrideExpectations b/third_party/blink/web_tests/WPTOverrideExpectations
index 969bede..985f340 100644
--- a/third_party/blink/web_tests/WPTOverrideExpectations
+++ b/third_party/blink/web_tests/WPTOverrideExpectations
@@ -216,7 +216,6 @@
 external/wpt/css/css-animations/CSSAnimation-ready.tentative.html [ Failure Pass ]
 external/wpt/css/css-animations/CSSPseudoElement-getAnimations.tentative.html [ Failure ]
 external/wpt/css/css-animations/Element-getAnimations-dynamic-changes.tentative.html [ Failure Pass ] # wpt_subtest_failure
-external/wpt/css/css-animations/animationevent-marker-pseudoelement.html [ Timeout ] # wpt_subtest_failure
 external/wpt/css/css-animations/event-dispatch.tentative.html [ Pass Timeout ] # wpt_subtest_failure
 external/wpt/css/css-backgrounds/background-size/vector/background-size-vector-024.html [ Pass Failure ]
 external/wpt/css/css-box/parsing/min-height-invalid.html [ Failure ]
diff --git a/third_party/blink/web_tests/android/ClankWPTOverrideExpectations b/third_party/blink/web_tests/android/ClankWPTOverrideExpectations
index 7bbf814a..91136c4e 100644
--- a/third_party/blink/web_tests/android/ClankWPTOverrideExpectations
+++ b/third_party/blink/web_tests/android/ClankWPTOverrideExpectations
@@ -475,7 +475,6 @@
 crbug.com/1050754 external/wpt/css/css-animations/animation-before-initial-box-construction-001.html [ Failure ]
 crbug.com/1050754 external/wpt/css/css-animations/AnimationEffect-getComputedTiming.tentative.html [ Failure ]
 crbug.com/1050754 external/wpt/css/css-animations/AnimationEffect-updateTiming.tentative.html [ Failure ]
-crbug.com/1050754 external/wpt/css/css-animations/animationevent-marker-pseudoelement.html [ Timeout ]
 crbug.com/1050754 external/wpt/css/css-animations/CSSAnimation-animationName.tentative.html [ Failure ]
 crbug.com/1050754 external/wpt/css/css-animations/CSSAnimation-canceling.tentative.html [ Failure ]
 crbug.com/1050754 external/wpt/css/css-animations/CSSAnimation-compositeOrder.tentative.html [ Failure ]
diff --git a/third_party/blink/web_tests/android/WeblayerWPTOverrideExpectations b/third_party/blink/web_tests/android/WeblayerWPTOverrideExpectations
index f0835e6..f1b1054b 100644
--- a/third_party/blink/web_tests/android/WeblayerWPTOverrideExpectations
+++ b/third_party/blink/web_tests/android/WeblayerWPTOverrideExpectations
@@ -371,7 +371,6 @@
 crbug.com/1050754 external/wpt/css/compositing/mix-blend-mode/mix-blend-mode-creates-stacking-context.html [ Failure ]
 crbug.com/1050754 external/wpt/css/compositing/parsing/background-blend-mode-computed.html [ Failure ]
 crbug.com/1050754 external/wpt/css/css-animations/AnimationEffect-updateTiming.tentative.html [ Failure ]
-crbug.com/1050754 external/wpt/css/css-animations/animationevent-marker-pseudoelement.html [ Timeout ]
 crbug.com/1050754 external/wpt/css/css-animations/CSSAnimation-compositeOrder.tentative.html [ Failure ]
 crbug.com/1050754 external/wpt/css/css-animations/CSSAnimation-effect.tentative.html [ Timeout ]
 crbug.com/1050754 external/wpt/css/css-animations/CSSAnimation-ready.tentative.html [ Failure ]
diff --git a/third_party/blink/web_tests/android/WebviewWPTOverrideExpectations b/third_party/blink/web_tests/android/WebviewWPTOverrideExpectations
index 925a258..cd196940 100644
--- a/third_party/blink/web_tests/android/WebviewWPTOverrideExpectations
+++ b/third_party/blink/web_tests/android/WebviewWPTOverrideExpectations
@@ -507,7 +507,6 @@
 external/wpt/css/css-animations/animation-before-initial-box-construction-001.html [ Failure ]
 external/wpt/css/css-animations/AnimationEffect-getComputedTiming.tentative.html [ Failure ]
 external/wpt/css/css-animations/AnimationEffect-updateTiming.tentative.html [ Failure ]
-external/wpt/css/css-animations/animationevent-marker-pseudoelement.html [ Timeout ]
 external/wpt/css/css-animations/CSSAnimation-animationName.tentative.html [ Failure ]
 external/wpt/css/css-animations/CSSAnimation-canceling.tentative.html [ Failure ]
 external/wpt/css/css-animations/CSSAnimation-compositeOrder.tentative.html [ Failure ]
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_7.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_7.json
index 28b7f94..a04bea44 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_7.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_7.json
@@ -44637,6 +44637,18 @@
      {}
     ]
    ],
+   "css/css-flexbox/cross-axis-scrollbar.html": [
+    [
+     "css/css-flexbox/cross-axis-scrollbar.html",
+     [
+      [
+       "/css/css-flexbox/reference/cross-axis-scrollbar-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-flexbox/css-box-justify-content.html": [
     [
      "css/css-flexbox/css-box-justify-content.html",
@@ -49118,7 +49130,7 @@
      "css/css-flexbox/gap-009-ltr.html",
      [
       [
-       "/css/css-flexbox/gap-008-ltr-ref.html",
+       "/css/css-flexbox/gap-009-ltr-ref.html",
        "=="
       ]
      ],
@@ -49130,7 +49142,7 @@
      "css/css-flexbox/gap-010-ltr.html",
      [
       [
-       "/css/css-flexbox/gap-008-ltr-ref.html",
+       "/css/css-flexbox/gap-010-ltr-ref.html",
        "=="
       ]
      ],
@@ -139664,9 +139676,6 @@
    "css/css-animations/CSSAnimation-compositeOrder.tentative-expected.txt": [
     []
    ],
-   "css/css-animations/CSSAnimation-effect.tentative-expected.txt": [
-    []
-   ],
    "css/css-animations/CSSAnimation-ready.tentative-expected.txt": [
     []
    ],
@@ -142163,6 +142172,9 @@
    "css/css-flexbox/reference/content-height-with-scrollbars-ref.html": [
     []
    ],
+   "css/css-flexbox/reference/cross-axis-scrollbar-ref.html": [
+    []
+   ],
    "css/css-flexbox/reference/css-box-justify-content-ref.html": [
     []
    ],
@@ -150602,6 +150614,9 @@
    "css/css-pseudo/parsing/marker-supported-properties-expected.txt": [
     []
    ],
+   "css/css-pseudo/parsing/marker-supported-properties-in-animation-expected.txt": [
+    []
+   ],
    "css/css-pseudo/placeholder-input-number-notref.html": [
     []
    ],
@@ -154862,9 +154877,6 @@
    "css/css-transitions/CSSTransition-effect.tentative-expected.txt": [
     []
    ],
-   "css/css-transitions/Document-getAnimations.tentative-expected.txt": [
-    []
-   ],
    "css/css-transitions/KeyframeEffect-getKeyframes.tentative-expected.txt": [
     []
    ],
@@ -219134,12 +219146,24 @@
      {}
     ]
    ],
+   "css/css-flexbox/inline-flex.html": [
+    [
+     "css/css-flexbox/inline-flex.html",
+     {}
+    ]
+   ],
    "css/css-flexbox/inline-flexbox-wrap-vertically-width-calculation.html": [
     [
      "css/css-flexbox/inline-flexbox-wrap-vertically-width-calculation.html",
      {}
     ]
    ],
+   "css/css-flexbox/intrinsic-min-width-applies-with-fixed-width.html": [
+    [
+     "css/css-flexbox/intrinsic-min-width-applies-with-fixed-width.html",
+     {}
+    ]
+   ],
    "css/css-flexbox/order_value.html": [
     [
      "css/css-flexbox/order_value.html",
@@ -224492,6 +224516,12 @@
      {}
     ]
    ],
+   "css/css-pseudo/parsing/marker-supported-properties-in-animation.html": [
+    [
+     "css/css-pseudo/parsing/marker-supported-properties-in-animation.html",
+     {}
+    ]
+   ],
    "css/css-pseudo/parsing/marker-supported-properties.html": [
     [
      "css/css-pseudo/parsing/marker-supported-properties.html",
@@ -339014,9 +339044,9 @@
      {}
     ]
    ],
-   "css/css-flexbox/justify-content_space-between.html": [
+   "css/css-flexbox/justify-content_space-between-001.html": [
     [
-     "css/css-flexbox/justify-content_space-between.html",
+     "css/css-flexbox/justify-content_space-between-001.html",
      {}
     ]
    ],
@@ -373091,10 +373121,6 @@
    "d55db9a2d117f54cebd447d9bf5ef9f44ab7309a",
    "testharness"
   ],
-  "css/css-animations/CSSAnimation-effect.tentative-expected.txt": [
-   "194b7bce92958bcf4232bed45f87096f2e8a465b",
-   "support"
-  ],
   "css/css-animations/CSSAnimation-effect.tentative.html": [
    "fadcaa129ab2c4da612add9951bf99b447fe948d",
    "testharness"
@@ -373132,7 +373158,7 @@
    "testharness"
   ],
   "css/css-animations/Document-getAnimations.tentative-expected.txt": [
-   "5d923ba5cb4d02c2316c0189b520293795e37081",
+   "5337475efa989cee768211e37c44e5542f33e9ea",
    "support"
   ],
   "css/css-animations/Document-getAnimations.tentative.html": [
@@ -373536,7 +373562,7 @@
    "testharness"
   ],
   "css/css-animations/event-dispatch.tentative-expected.txt": [
-   "0906f8b2f77356ef7eea18dc3b728b8afcfba3b9",
+   "a4512f67d28b1cdd814fa152b6d8fef4fe153e82",
    "support"
   ],
   "css/css-animations/event-dispatch.tentative.html": [
@@ -373544,7 +373570,7 @@
    "testharness"
   ],
   "css/css-animations/event-order.tentative-expected.txt": [
-   "69df99e5e7f3358362bea8451af5a75dede55d4e",
+   "a1bc0ba1d25d22cf0cbc8ad41723921843c0ec78",
    "support"
   ],
   "css/css-animations/event-order.tentative.html": [
@@ -381579,6 +381605,10 @@
    "5a63322da7dbeb007aaace719cdc652e30338b9c",
    "reftest"
   ],
+  "css/css-flexbox/cross-axis-scrollbar.html": [
+   "6bb325175562a231af8177e8d12f0bc35ac187a6",
+   "reftest"
+  ],
   "css/css-flexbox/css-box-justify-content.html": [
    "d5c7244f08dcad0b0955290804ec5959754a963d",
    "reftest"
@@ -383964,7 +383994,7 @@
    "support"
   ],
   "css/css-flexbox/gap-009-ltr.html": [
-   "43a4cefc72e80fc594793cfc115f1f7bc8bbb2e8",
+   "462b5b69c66a8f28c7c066152f1a24954a485f80",
    "reftest"
   ],
   "css/css-flexbox/gap-010-ltr-ref.html": [
@@ -383972,7 +384002,7 @@
    "support"
   ],
   "css/css-flexbox/gap-010-ltr.html": [
-   "f57a167e16df0e55f1edc6625d9550e9e8e326fd",
+   "85dba78db5a90ec124f2179adedf2e7f357fa5fd",
    "reftest"
   ],
   "css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-content-center.html": [
@@ -384299,6 +384329,10 @@
    "b84b9afc0b67b83d3d3e2abb73c9b7e22aeb3cbe",
    "reftest"
   ],
+  "css/css-flexbox/inline-flex.html": [
+   "1f21ae35a898b0fee9bda8fd1c14cbc4c2c205ef",
+   "testharness"
+  ],
   "css/css-flexbox/inline-flexbox-wrap-vertically-width-calculation.html": [
    "e9010cf96cff8d131e0319168d7570285331dd2a",
    "testharness"
@@ -384383,6 +384417,10 @@
    "684233223b82c7105a9550e4957597acc0153e75",
    "manual"
   ],
+  "css/css-flexbox/intrinsic-min-width-applies-with-fixed-width.html": [
+   "080169b52d7fdf39cbf6ff970c3100480e46d2a3",
+   "testharness"
+  ],
   "css/css-flexbox/item-with-max-height-and-scrollbar.html": [
    "167417a2563eaf54650f8347584e7e5b53d13903",
    "reftest"
@@ -384427,7 +384465,7 @@
    "c0dba54e50c09f66ea90aae7778f7b201de8d5d8",
    "visual"
   ],
-  "css/css-flexbox/justify-content_space-between.html": [
+  "css/css-flexbox/justify-content_space-between-001.html": [
    "7abfd4a6c3c3e9d80b2cea059ac7e2a5463523fe",
    "visual"
   ],
@@ -384727,6 +384765,10 @@
    "8a1484f6934dc3e30aae299380c82308cd1fec42",
    "support"
   ],
+  "css/css-flexbox/reference/cross-axis-scrollbar-ref.html": [
+   "f0a3225502e3da036ab28d89fb03c0441b6c3862",
+   "support"
+  ],
   "css/css-flexbox/reference/css-box-justify-content-ref.html": [
    "e8377473fdef6f93bdf0e1e0e78fd33f01c93e82",
    "support"
@@ -405868,11 +405910,19 @@
    "reftest"
   ],
   "css/css-pseudo/parsing/marker-supported-properties-expected.txt": [
-   "ae332b152bd19f6a05a2b972a74f762c6cf5f337",
+   "79c4baa4684c3468a52cc0659ea82b2dbcf8d525",
    "support"
   ],
+  "css/css-pseudo/parsing/marker-supported-properties-in-animation-expected.txt": [
+   "ccc3ee3b5cda07fae2766ab7f7bd233f784b4f04",
+   "support"
+  ],
+  "css/css-pseudo/parsing/marker-supported-properties-in-animation.html": [
+   "faf7c904fb04460ad08cc7b8a0e9d3504bbfe158",
+   "testharness"
+  ],
   "css/css-pseudo/parsing/marker-supported-properties.html": [
-   "7f8eacbc2ce4874392f1e1651ae2f60894563215",
+   "e27decc133424ba01b96ff7960ecbe62d5033077",
    "testharness"
   ],
   "css/css-pseudo/parsing/tree-abiding-pseudo-elements.html": [
@@ -423715,10 +423765,6 @@
    "8eb284107d350fb2a5da1b176aa213f807cd212f",
    "testharness"
   ],
-  "css/css-transitions/Document-getAnimations.tentative-expected.txt": [
-   "50c89edbdd431cb895687f9c77f5e0e4fd0f907e",
-   "support"
-  ],
   "css/css-transitions/Document-getAnimations.tentative.html": [
    "cd97acfd5ec76c7585d5356c86b3832bb0b7bd37",
    "testharness"
@@ -524756,7 +524802,7 @@
    "wdspec"
   ],
   "webdriver/tests/perform_actions/pointer.py": [
-   "49468a73aed9e6e1da341e47c16968b0d204fa43",
+   "a752203587bed5ebcfa50599f0548feb69ca4c98",
    "wdspec"
   ],
   "webdriver/tests/perform_actions/pointer_contextmenu.py": [
@@ -524800,7 +524846,7 @@
    "support"
   ],
   "webdriver/tests/perform_actions/support/test_actions_wdspec.html": [
-   "6f844cd255a075d31caf1c19957af3d6ac833778",
+   "39a8876e54ad183b900acbb552a5930b5b4b83fd",
    "support"
   ],
   "webdriver/tests/perform_actions/user_prompts.py": [
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/Document-getAnimations.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-animations/Document-getAnimations.tentative-expected.txt
index 5d923ba5..5337475 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-animations/Document-getAnimations.tentative-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/css/css-animations/Document-getAnimations.tentative-expected.txt
@@ -14,6 +14,6 @@
 PASS CSS Animations canceled via the API are not returned
 PASS CSS Animations canceled and restarted via the API are returned
 PASS CSS Animations targetting (pseudo-)elements should have correct order after sorting
-FAIL CSS Animations targetting (pseudo-)elements should have correct order after sorting (::marker) assert_equals: CSS animations on both pseudo-elements and elements are returned expected 5 but got 4
+PASS CSS Animations targetting (pseudo-)elements should have correct order after sorting (::marker)
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/event-order.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-animations/event-order.tentative-expected.txt
index 69df99e..a1bc0ba 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-animations/event-order.tentative-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/css/css-animations/event-order.tentative-expected.txt
@@ -1,7 +1,7 @@
 This is a testharness.js-based test.
 PASS Same events are ordered by elements
 PASS Same events on pseudo-elements follow the prescribed order
-FAIL Same events on pseudo-elements follow the prescribed order (::marker) assert_equals: Number of events received (4) should match expected number (5) (expected: animationstart, animationstart, animationstart, animationstart, animationstart, actual: animationstart, animationstart, animationstart, animationstart) expected 5 but got 4
+PASS Same events on pseudo-elements follow the prescribed order (::marker)
 FAIL Start and iteration events are ordered by time assert_equals: Event #1 types should match (expected: animationiteration, animationstart, actual: animationstart, animationiteration) expected "animationiteration" but got "animationstart"
 FAIL Iteration and end events are ordered by time assert_equals: Event #1 types should match (expected: animationiteration, animationend, actual: animationend, animationiteration) expected "animationiteration" but got "animationend"
 FAIL Start and end events are sorted correctly when fired simultaneously assert_equals: Event #1 targets should match expected Element node <div style="animation: anim 100s 2"></div> but got Element node <div style="animation: anim 100s 100s"></div>
diff --git a/third_party/blink/web_tests/css3/flexbox/inline-flex.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/inline-flex.html
similarity index 62%
rename from third_party/blink/web_tests/css3/flexbox/inline-flex.html
rename to third_party/blink/web_tests/external/wpt/css/css-flexbox/inline-flex.html
index 23f6f50..1f21ae3 100644
--- a/third_party/blink/web_tests/css3/flexbox/inline-flex.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/inline-flex.html
@@ -1,5 +1,9 @@
 <!DOCTYPE html>
 <html>
+<title>CSS Flexbox: Ensure proper formatting with display: inline-flex</title>
+<link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#flex-containers">
+<link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#flex-property">
+<meta name="assert" content="This test checks that inline-flex generates a flex container box that is inline-level when placed in flow layout.">
 <style>
 #testcase > div {
     height: 50px;
@@ -11,9 +15,9 @@
     flex: 1;
 }
 </style>
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<script src="../../resources/check-layout-th.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
 <body onload="checkLayout('#testcase')">
 <div id=log></div>
 <p>This test passes if the three green boxes are on the same horizontal line.</p>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-pseudo/first-letter-text-and-display-change.html b/third_party/blink/web_tests/external/wpt/css/css-pseudo/first-letter-text-and-display-change.html
new file mode 100644
index 0000000..d50da5f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-pseudo/first-letter-text-and-display-change.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<title>CSS Test: Change display for the ::first-letter container while replacing text node</title>
+<link rel="help" href="https://drafts.csswg.org/css-pseudo/#first-letter-pseudo">
+<link rel="match" href="../reference/pass_if_letter_uppercase.html">
+<style>
+  #container::first-letter { text-transform: uppercase }
+  #container.ib { display: inline-block }
+</style>
+<body>
+  <p>Test passes if the letter "F" in the words "Filler Text" below is in upper-case.</p>
+  <div id="container">Test not run</div>
+</body>
+<script>
+  container.offsetTop;
+  container.className = "ib";
+  container.appendChild(document.createTextNode("filler Text"));
+  container.firstChild.remove();
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-pseudo/parsing/marker-supported-properties-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-pseudo/parsing/marker-supported-properties-expected.txt
index ae332b1..79c4baa4 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-pseudo/parsing/marker-supported-properties-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/css/css-pseudo/parsing/marker-supported-properties-expected.txt
@@ -21,6 +21,20 @@
 PASS Property unicode-bidi value 'plaintext' in ::marker
 PASS Property direction value 'rtl' in ::marker
 PASS Property content value '"foo"' in ::marker
+PASS Property animation value '1s linear 2s infinite alternate forwards paused anim' in ::marker
+PASS Property animation-delay value '1s' in ::marker
+PASS Property animation-direction value 'alternate' in ::marker
+PASS Property animation-duration value '2s' in ::marker
+PASS Property animation-fill-mode value 'forwards' in ::marker
+PASS Property animation-iteration-count value 'infinite' in ::marker
+PASS Property animation-name value 'anim' in ::marker
+PASS Property animation-play-state value 'paused' in ::marker
+PASS Property animation-timing-function value 'linear' in ::marker
+PASS Property transition value 'display 1s linear 2s' in ::marker
+PASS Property transition-delay value '1s' in ::marker
+PASS Property transition-duration value '2s' in ::marker
+PASS Property transition-property value 'display' in ::marker
+PASS Property transition-timing-function value 'linear' in ::marker
 PASS Property display value 'none' in ::marker
 PASS Property position value 'absolute' in ::marker
 PASS Property float value 'right' in ::marker
diff --git a/third_party/blink/web_tests/external/wpt/css/css-pseudo/parsing/marker-supported-properties-in-animation-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-pseudo/parsing/marker-supported-properties-in-animation-expected.txt
new file mode 100644
index 0000000..ccc3ee3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-pseudo/parsing/marker-supported-properties-in-animation-expected.txt
@@ -0,0 +1,64 @@
+This is a testharness.js-based test.
+Found 60 tests; 49 PASS, 11 FAIL, 0 TIMEOUT, 0 NOTRUN.
+FAIL Animation of font in ::marker assert_in_array: value "italic small-caps 500 ultra-expanded 15px Ahem" not in array ["italic small-caps 500 expanded 15px Ahem", "italic small-caps 500 expanded 15px/normal Ahem"]
+PASS Animation of font-family in ::marker
+PASS Animation of font-feature-settings in ::marker
+PASS Animation of font-kerning in ::marker
+PASS Animation of font-size in ::marker
+PASS Animation of font-size-adjust in ::marker
+FAIL Animation of font-stretch in ::marker assert_in_array: value "200%" not in array ["expanded", "125%"]
+PASS Animation of font-style in ::marker
+FAIL Animation of font-synthesis in ::marker assert_true: font-synthesis doesn't seem to be supported in the computed style expected true got false
+PASS Animation of font-variant in ::marker
+PASS Animation of font-variant-caps in ::marker
+PASS Animation of font-variant-east-asian in ::marker
+PASS Animation of font-variant-ligatures in ::marker
+PASS Animation of font-variant-numeric in ::marker
+FAIL Animation of font-variant-position in ::marker assert_true: font-variant-position doesn't seem to be supported in the computed style expected true got false
+PASS Animation of font-weight in ::marker
+FAIL Animation of white-space in ::marker assert_equals: expected "nowrap" but got "pre"
+PASS Animation of color in ::marker
+FAIL Animation of text-combine-upright in ::marker assert_equals: expected "none" but got "all"
+PASS Animation of unicode-bidi in ::marker
+PASS Animation of direction in ::marker
+PASS Animation of content in ::marker
+PASS Animation of display in ::marker
+PASS Animation of position in ::marker
+PASS Animation of float in ::marker
+PASS Animation of list-style in ::marker
+PASS Animation of list-style-image in ::marker
+PASS Animation of list-style-position in ::marker
+PASS Animation of list-style-type in ::marker
+PASS Animation of line-height in ::marker
+FAIL Transition of font in ::marker assert_in_array: value "italic small-caps 500 ultra-expanded 15px Ahem" not in array ["italic small-caps 500 expanded 15px Ahem", "italic small-caps 500 expanded 15px/normal Ahem"]
+PASS Transition of font-family in ::marker
+PASS Transition of font-feature-settings in ::marker
+PASS Transition of font-kerning in ::marker
+PASS Transition of font-size in ::marker
+PASS Transition of font-size-adjust in ::marker
+FAIL Transition of font-stretch in ::marker assert_in_array: value "200%" not in array ["expanded", "125%"]
+PASS Transition of font-style in ::marker
+FAIL Transition of font-synthesis in ::marker assert_true: font-synthesis doesn't seem to be supported in the computed style expected true got false
+PASS Transition of font-variant in ::marker
+PASS Transition of font-variant-caps in ::marker
+PASS Transition of font-variant-east-asian in ::marker
+PASS Transition of font-variant-ligatures in ::marker
+PASS Transition of font-variant-numeric in ::marker
+FAIL Transition of font-variant-position in ::marker assert_true: font-variant-position doesn't seem to be supported in the computed style expected true got false
+PASS Transition of font-weight in ::marker
+FAIL Transition of white-space in ::marker assert_equals: expected "nowrap" but got "pre"
+PASS Transition of color in ::marker
+PASS Transition of text-combine-upright in ::marker
+PASS Transition of unicode-bidi in ::marker
+PASS Transition of direction in ::marker
+PASS Transition of content in ::marker
+PASS Transition of display in ::marker
+PASS Transition of position in ::marker
+PASS Transition of float in ::marker
+PASS Transition of list-style in ::marker
+PASS Transition of list-style-image in ::marker
+PASS Transition of list-style-position in ::marker
+PASS Transition of list-style-type in ::marker
+PASS Transition of line-height in ::marker
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-pseudo/parsing/marker-supported-properties-in-animation.html b/third_party/blink/web_tests/external/wpt/css/css-pseudo/parsing/marker-supported-properties-in-animation.html
new file mode 100644
index 0000000..faf7c90
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-pseudo/parsing/marker-supported-properties-in-animation.html
@@ -0,0 +1,292 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Pseudo-Elements Test: Supported properties in ::marker animations</title>
+<link rel="help" href="https://drafts.csswg.org/css-pseudo-4/#marker-pseudo">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#keyframes">
+<link rel="help" href="https://drafts.csswg.org/css-transitions-1/#transitions">
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<meta name="assert" content="This test checks ::marker supports animations and transitions, but only for the properties that apply to ::marker." />
+<style id="style"></style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<ul>
+  <li id="target">target</li>
+</ul>
+<script>
+const interpolationTests = [
+  // ::marker supports all font properties.
+  {
+    property: "font",
+    from: "oblique normal 100 ultra-condensed 5px / 20px serif",
+    to: "italic small-caps 900 ultra-expanded 25px / 50px Ahem",
+    midPoint: ["italic small-caps 500 expanded 15px Ahem", "italic small-caps 500 expanded 15px/normal Ahem"],
+  },
+  {
+    property: "font-family",
+    from: "serif",
+    to: "Ahem",
+    midPoint: "Ahem",
+  },
+  {
+    property: "font-feature-settings",
+    from: "'c2sc'",
+    to: "'smcp'",
+    midPoint: "\"smcp\"",
+  },
+  {
+    property: "font-kerning",
+    from: "normal",
+    to: "none",
+    midPoint: "none",
+  },
+  {
+    property: "font-size",
+    from: "5px",
+    to: "25px",
+    midPoint: "15px",
+  },
+  {
+    property: "font-size-adjust",
+    from: "1",
+    to: "3",
+    midPoint: "2",
+  },
+  {
+    property: "font-stretch",
+    from: "ultra-condensed",
+    to: "ultra-expanded",
+    midPoint: ["expanded", "125%"],
+  },
+  {
+    property: "font-style",
+    from: "oblique",
+    to: "italic",
+    midPoint: "italic",
+  },
+  {
+    property: "font-synthesis",
+    from: "weight",
+    to: "none",
+    midPoint: "none",
+  },
+  {
+    property: "font-variant",
+    from: "unicase",
+    to: "small-caps",
+    midPoint: "small-caps",
+  },
+  {
+    property: "font-variant-caps",
+    from: "unicase",
+    to: "small-caps",
+    midPoint: "small-caps",
+  },
+  {
+    property: "font-variant-east-asian",
+    from: "proportional-width",
+    to: "full-width",
+    midPoint: "full-width",
+  },
+  {
+    property: "font-variant-ligatures",
+    from: "no-historical-ligatures",
+    to: "historical-ligatures",
+    midPoint: "historical-ligatures",
+  },
+  {
+    property: "font-variant-numeric",
+    from: "ordinal",
+    to: "slashed-zero",
+    midPoint: "slashed-zero",
+  },
+  {
+    property: "font-variant-position",
+    from: "super",
+    to: "sub",
+    midPoint: "sub",
+  },
+  {
+    property: "font-weight",
+    from: "100",
+    to: "900",
+    midPoint: "500",
+  },
+
+  // ::marker supports `white-space`
+  {
+    property: "white-space",
+    from: "pre-line",
+    to: "nowrap",
+    midPoint: "nowrap",
+  },
+
+  // ::marker supports `color`
+  {
+    property: "color",
+    from: "rgb(0, 100, 200)",
+    to: "rgb(100, 200, 0)",
+    midPoint: "rgb(50, 150, 100)",
+  },
+
+  // ::marker supports `text-combine-upright`, `unicode-bidi` and `direction`,
+  // but they are not animatable.
+  {
+    property: "text-combine-upright",
+    from: "all",
+    to: "all",
+    midPoint: null,
+  },
+  {
+    property: "unicode-bidi",
+    from: "embed",
+    to: "plaintext",
+    midPoint: null,
+  },
+  {
+    property: "direction",
+    from: "rtl",
+    to: "rtl",
+    midPoint: null,
+  },
+
+  // ::marker supports `content`
+  {
+    property: "content",
+    from: "'foo'",
+    to: "'bar'",
+    midPoint: "\"bar\"",
+  },
+
+  // ::marker does NOT support layout properties
+  {
+    property: "display",
+    from: "flex",
+    to: "none",
+    midPoint: ["block", "inline", "inline-block"],
+  },
+  {
+    property: "position",
+    from: "fixed",
+    to: "absolute",
+    midPoint: "static",
+  },
+  {
+    property: "float",
+    from: "left",
+    to: "right",
+    midPoint: "none",
+  },
+
+  // ::marker does NOT support list properties despite being affected by them,
+  // they apply to the list item instead.
+  {
+    property: "list-style",
+    from: "inside url('foo') square",
+    to: "inside url('bar') decimal",
+    midPoint: "outside none disc",
+  },
+  {
+    property: "list-style-image",
+    from: "url('foo')",
+    to: "url('bar')",
+    midPoint: "none",
+  },
+  {
+    property: "list-style-position",
+    from: "inside",
+    to: "inside",
+    midPoint: "outside",
+  },
+  {
+    property: "list-style-type",
+    from: "square",
+    to: "decimal",
+    midPoint: "disc",
+  },
+
+  // ::marker does NOT support `line-height` because, despite being a
+  // longhand of `font`, it's not a font property.
+  {
+    property: "line-height",
+    from: "20px",
+    to: "50px",
+    midPoint: "normal",
+  },
+];
+
+const target = document.getElementById("target");
+const styleElement = document.getElementById("style");
+const markerStyle = getComputedStyle(target, "::marker");
+
+function check({property, from, to, midPoint}) {
+  assert_true(property in markerStyle, property + " doesn't seem to be supported in the computed style");
+  assert_true(CSS.supports(property, from), `'${from}' is a supported value for ${property}.`);
+  assert_true(CSS.supports(property, to), `'${to}' is a supported value for ${property}.`);
+  const computed = markerStyle.getPropertyValue(property);
+  if (Array.isArray(midPoint)) {
+    assert_in_array(computed, midPoint);
+  } else {
+    assert_equals(computed, midPoint);
+  }
+}
+
+function testAnimations(interpolationTests) {
+  styleElement.textContent = `
+    ::marker {
+      animation: anim 2s -1s paused linear;
+    }
+    @keyframes anim {
+      from {}
+      to {}
+    }
+  `;
+  const keyframes = styleElement.sheet.cssRules[1];
+  const fromStyle = keyframes.cssRules[0].style;
+  const toStyle = keyframes.cssRules[1].style;
+  for (let {property, from, to, midPoint} of interpolationTests) {
+    fromStyle.cssText = "";
+    toStyle.cssText = "";
+    if (midPoint == null) {
+      midPoint = markerStyle.getPropertyValue(property);
+    }
+    fromStyle.setProperty(property, from);
+    toStyle.setProperty(property, to);
+    test(() => {
+      check({property, from, to, midPoint});
+    }, `Animation of ${property} in ::marker`);
+  }
+}
+
+function testTransitions(interpolationTests) {
+  styleElement.textContent = `
+    .transition::marker {
+      transition: all 2s -1s linear;
+    }
+    .from::marker {}
+    .to::marker {}
+  `;
+  const fromStyle = styleElement.sheet.cssRules[1].style;
+  const toStyle = styleElement.sheet.cssRules[2].style;
+  for (let {property, from, to, midPoint} of interpolationTests) {
+    fromStyle.cssText = "";
+    toStyle.cssText = "";
+    if (midPoint == null) {
+      midPoint = to;
+    }
+    fromStyle.setProperty(property, from);
+    toStyle.setProperty(property, to);
+    target.className = "from";
+    markerStyle.width;
+    target.classList.add("transition");
+    markerStyle.width;
+    target.classList.add("to");
+    test(() => {
+      check({property, from, to, midPoint});
+    }, `Transition of ${property} in ::marker`);
+  }
+}
+
+testAnimations(interpolationTests);
+testTransitions(interpolationTests);
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-pseudo/parsing/marker-supported-properties.html b/third_party/blink/web_tests/external/wpt/css/css-pseudo/parsing/marker-supported-properties.html
index 7f8eacb..e27decc 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-pseudo/parsing/marker-supported-properties.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-pseudo/parsing/marker-supported-properties.html
@@ -45,6 +45,24 @@
 // ::marker supports `content`
 test_pseudo_computed_value("::marker", "content", "\"foo\"");
 
+// ::marker supports animation properties.
+test_pseudo_computed_value("::marker", "animation", "1s linear 2s infinite alternate forwards paused anim");
+test_pseudo_computed_value("::marker", "animation-delay", "1s");
+test_pseudo_computed_value("::marker", "animation-direction", "alternate");
+test_pseudo_computed_value("::marker", "animation-duration", "2s");
+test_pseudo_computed_value("::marker", "animation-fill-mode", "forwards");
+test_pseudo_computed_value("::marker", "animation-iteration-count", "infinite");
+test_pseudo_computed_value("::marker", "animation-name", "anim");
+test_pseudo_computed_value("::marker", "animation-play-state", "paused");
+test_pseudo_computed_value("::marker", "animation-timing-function", "linear");
+
+// ::marker supports transition properties.
+test_pseudo_computed_value("::marker", "transition", "display 1s linear 2s");
+test_pseudo_computed_value("::marker", "transition-delay", "1s");
+test_pseudo_computed_value("::marker", "transition-duration", "2s");
+test_pseudo_computed_value("::marker", "transition-property", "display");
+test_pseudo_computed_value("::marker", "transition-timing-function", "linear");
+
 // ::marker does NOT support layout properties
 test_pseudo_computed_value("::marker", "display", "none", ["block", "inline", "inline-block"]);
 test_pseudo_computed_value("::marker", "position", "absolute", "static");
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transitions/Document-getAnimations.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-transitions/Document-getAnimations.tentative-expected.txt
deleted file mode 100644
index 50c89ed..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-transitions/Document-getAnimations.tentative-expected.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-This is a testharness.js-based test.
-PASS getAnimations for non-animated content
-PASS getAnimations for CSS Transitions
-PASS CSS Transitions targetting (pseudo-)elements should have correct order after sorting
-FAIL CSS Transitions targetting (pseudo-)elements should have correct order after sorting (::marker) assert_equals: CSS transition on both pseudo-elements and elements are returned expected 5 but got 4
-PASS Transitions are not returned after they have finished
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/perform_actions/pointer.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/perform_actions/pointer.py
index 49468a73..a752203 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/perform_actions/pointer.py
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/perform_actions/pointer.py
@@ -141,3 +141,34 @@
     final_rect = drag_target.rect
     assert initial_rect["x"] + dx == final_rect["x"]
     assert initial_rect["y"] + dy == final_rect["y"]
+
+
+@pytest.mark.parametrize("drag_duration", [0, 300, 800])
+def test_drag_and_drop_with_draggable_element(session,
+                       test_actions_page,
+                       mouse_chain,
+                       drag_duration):
+    drag_target = session.find.css("#draggable", all=False)
+    drop_target = session.find.css("#droppable", all=False)
+    # Conclude chain with extra move to allow time for last queued
+    # coordinate-update of drag_target and to test that drag_target is "dropped".
+    mouse_chain \
+        .pointer_move(0, 0, origin=drag_target) \
+        .pointer_down() \
+        .pointer_move(50,
+                      25,
+                      duration=drag_duration,
+                      origin=drop_target) \
+        .pointer_up() \
+        .pointer_move(80, 50, duration=100, origin="pointer") \
+        .perform()
+    # mouseup that ends the drag is at the expected destination
+    e = get_events(session)
+    assert e[1]["type"] == "dragstart", "Events captured were {}".format(e)
+    assert e[2]["type"] == "dragover", "Events captured were {}".format(e)
+    drag_events_captured = [ev["type"] for ev in e if ev["type"].startswith("drag") or ev["type"].startswith("drop")]
+    assert "dragend" in drag_events_captured
+    assert "dragenter" in drag_events_captured
+    assert "dragexit" in drag_events_captured
+    assert "dragleave" in drag_events_captured
+    assert "drop" in drag_events_captured
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/perform_actions/support/test_actions_wdspec.html b/third_party/blink/web_tests/external/wpt/webdriver/tests/perform_actions/support/test_actions_wdspec.html
index 6f844cd..39a8876 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/perform_actions/support/test_actions_wdspec.html
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/perform_actions/support/test_actions_wdspec.html
@@ -9,6 +9,7 @@
       #resultContainer { width: 600px; height: 60px; }
       .area { width: 100px; height: 50px; background-color: #ccc; }
       .block { width: 5px; height: 5px; border: solid 1px red; }
+      .box { display: flex;}
       #dragArea { position: relative; }
       #dragTarget { position: absolute; top:22px; left:47px;}
     </style>
@@ -167,8 +168,20 @@
           window.addEventListener("mousemove", move(pointer, 15, 15, 30));
           // drag and drop
           els.dragArea = document.getElementById("dragArea");
+          els.dragArea.addEventListener("dragstart", recordPointerEvent);
           els.dragTarget = document.getElementById("dragTarget");
           els.dragTarget.addEventListener("mousedown", grabOnce);
+
+          var draggable = document.getElementById("draggable");
+          draggable.addEventListener("dragstart", recordPointerEvent);
+          draggable.addEventListener("dragenter", recordPointerEvent);
+          draggable.addEventListener("dragend", recordPointerEvent);
+          draggable.addEventListener("dragleave", recordPointerEvent);
+          draggable.addEventListener("dragover", recordPointerEvent);
+          draggable.addEventListener("dragexit", recordPointerEvent);
+
+          var droppable = document.getElementById("droppable");
+          droppable.addEventListener("drop", recordPointerEvent);
         });
     </script>
 </head>
@@ -189,6 +202,13 @@
       <div id="dragTarget" class="block"></div>
     </div>
   </div>
+  <div>
+    <h2>draggable</h2>
+    <div class=box>
+      <div id=draggable draggable="true" class="area"></div>&nbsp;
+      <div id=droppable dropzone="true" class="area"></div>
+    </div>
+  </div>
   <div id="resultContainer">
     <h2>Events</h2>
     <div id="events"></div>
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/overflow/scroll-parent-with-non-stacking-context-composited-ancestor-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/overflow/scroll-parent-with-non-stacking-context-composited-ancestor-expected.txt
index 2342f3a..17f8b82 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/overflow/scroll-parent-with-non-stacking-context-composited-ancestor-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/overflow/scroll-parent-with-non-stacking-context-composited-ancestor-expected.txt
@@ -8,11 +8,11 @@
     },
     {
       "name": "LayoutNGBlockFlow DIV id='intervening'",
-      "position": [98, 90],
       "bounds": [300, 300],
       "contentsOpaque": true,
       "backfaceVisibility": "hidden",
-      "backgroundColor": "#FFEFD5"
+      "backgroundColor": "#FFEFD5",
+      "transform": 1
     },
     {
       "name": "LayoutNGBlockFlow DIV id='scroller'",
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/background/background-resize-height-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/background/background-resize-height-expected.txt
index 44c7e3d3..2ed0523 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/background/background-resize-height-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/background/background-resize-height-expected.txt
@@ -53,12 +53,12 @@
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV class='test image repeat-round'",
-      "position": [508, 8],
       "bounds": [60, 44],
       "backfaceVisibility": "hidden",
       "invalidations": [
         [0, 0, 60, 44]
-      ]
+      ],
+      "transform": 1
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV class='test generated'",
@@ -108,6 +108,17 @@
         [0, 0, 60, 44]
       ]
     }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [508, 8, 0, 1]
+      ]
+    }
   ]
 }
 
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/fixed-scroll-in-empty-root-layer-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/fixed-scroll-in-empty-root-layer-expected.png
deleted file mode 100644
index 355f16ad..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/fixed-scroll-in-empty-root-layer-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/fixed-scroll-in-empty-root-layer-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/fixed-scroll-in-empty-root-layer-expected.txt
index 4e21c7e..1be6e4b3 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/fixed-scroll-in-empty-root-layer-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/fixed-scroll-in-empty-root-layer-expected.txt
@@ -14,12 +14,11 @@
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV",
-      "position": [0, 250],
       "bounds": [100, 100],
       "contentsOpaque": true,
       "backfaceVisibility": "hidden",
       "backgroundColor": "#FF0000",
-      "transform": 1
+      "transform": 2
     },
     {
       "name": "LayoutNGBlockFlow HTML",
@@ -31,7 +30,7 @@
       "name": "LayoutNGBlockFlow (positioned) DIV",
       "bounds": [100, 100],
       "backgroundColor": "#008000",
-      "transform": 2
+      "transform": 3
     }
   ],
   "transforms": [
@@ -46,6 +45,16 @@
     },
     {
       "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, 250, 0, 1]
+      ]
+    },
+    {
+      "id": 3,
       "transform": [
         [1, 0, 0, 0],
         [0, 1, 0, 0],
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/scrolling-neg-z-index-descendants-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/scrolling-neg-z-index-descendants-expected.txt
new file mode 100644
index 0000000..c05afda4
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/scrolling-neg-z-index-descendants-expected.txt
@@ -0,0 +1,60 @@
+{
+  "layers": [
+    {
+      "name": "Scrolling background of LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutNGBlockFlow (relative positioned) DIV id='neg-z'",
+      "position": [1, 11],
+      "bounds": [100, 410],
+      "backfaceVisibility": "hidden",
+      "backgroundColor": "#008000",
+      "transform": 2
+    },
+    {
+      "name": "LayoutNGBlockFlow HTML",
+      "position": [8, 8],
+      "bounds": [784, 302],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutNGBlockFlow (relative positioned) DIV id='container'",
+      "bounds": [102, 302],
+      "backfaceVisibility": "hidden",
+      "transform": 1
+    },
+    {
+      "name": "LayoutNGBlockFlow (relative positioned) DIV id='container'",
+      "position": [1, 1],
+      "bounds": [100, 300],
+      "drawsContent": false,
+      "backfaceVisibility": "hidden",
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, -100, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/should-not-repaint-composited-descendants-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/should-not-repaint-composited-descendants-expected.txt
new file mode 100644
index 0000000..b7df2895
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/should-not-repaint-composited-descendants-expected.txt
@@ -0,0 +1,50 @@
+{
+  "layers": [
+    {
+      "name": "Scrolling background of LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutNGBlockFlow (positioned) DIV id='composited-box'",
+      "position": [30, 30],
+      "bounds": [20, 70],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "invalidations": [
+        [0, 0, 20, 70]
+      ],
+      "transform": 1
+    },
+    {
+      "name": "LayoutNGBlockFlow DIV class='composited child'",
+      "bounds": [50, 50],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "transform": 2
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, 50, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/should-not-repaint-composited-z-index-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/should-not-repaint-composited-z-index-expected.txt
index 9da69a76..1f91c5a 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/should-not-repaint-composited-z-index-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/should-not-repaint-composited-z-index-expected.txt
@@ -8,11 +8,22 @@
     },
     {
       "name": "LayoutNGBlockFlow DIV id='composited-box'",
-      "position": [8, 8],
       "bounds": [100, 100],
       "contentsOpaque": true,
       "backfaceVisibility": "hidden",
-      "backgroundColor": "#008000"
+      "backgroundColor": "#008000",
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
     }
   ]
 }
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/should-not-repaint-move-backface-hidden-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/should-not-repaint-move-backface-hidden-expected.txt
new file mode 100644
index 0000000..2781d1de
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/should-not-repaint-move-backface-hidden-expected.txt
@@ -0,0 +1,30 @@
+{
+  "layers": [
+    {
+      "name": "Scrolling background of LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutNGBlockFlow (positioned) DIV id='target'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backfaceVisibility": "hidden",
+      "backgroundColor": "#008000",
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 200, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/multicol/multicol-as-paint-container-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/multicol/multicol-as-paint-container-expected.txt
index f3f1125..df27eb02 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/multicol/multicol-as-paint-container-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/multicol/multicol-as-paint-container-expected.txt
@@ -8,12 +8,24 @@
     },
     {
       "name": "LayoutBlockFlow DIV id='target'",
-      "position": [8, -172],
+      "position": [0, -180],
       "bounds": [630, 520],
       "backfaceVisibility": "hidden",
       "invalidations": [
         [0, 0, 625, 360],
         [0, 180, 625, 340]
+      ],
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
       ]
     }
   ]
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/position/relative-positioned-movement-repaint-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/position/relative-positioned-movement-repaint-expected.txt
new file mode 100644
index 0000000..00f92ad
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/position/relative-positioned-movement-repaint-expected.txt
@@ -0,0 +1,28 @@
+{
+  "layers": [
+    {
+      "name": "Scrolling background of LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutNGBlockFlow (relative positioned) DIV id='block'",
+      "bounds": [402, 62],
+      "backfaceVisibility": "hidden",
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [68, 10, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/scroll/fixed-under-composited-absolute-scrolled-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/scroll/fixed-under-composited-absolute-scrolled-expected.txt
index d2a6a3a..7241421 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/scroll/fixed-under-composited-absolute-scrolled-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/scroll/fixed-under-composited-absolute-scrolled-expected.txt
@@ -14,12 +14,11 @@
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV id='absolute'",
-      "position": [8, 2000],
       "bounds": [1, 1],
       "contentsOpaque": true,
       "backfaceVisibility": "hidden",
       "backgroundColor": "#FF0000",
-      "transform": 1
+      "transform": 2
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV id='fixed'",
@@ -39,6 +38,16 @@
         [0, 0, 1, 0],
         [0, -400, 0, 1]
       ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 2000, 0, 1]
+      ]
     }
   ]
 }
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/scroll/fixed-with-border-under-composited-absolute-scrolled-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/scroll/fixed-with-border-under-composited-absolute-scrolled-expected.txt
index 462914a..e812ba3 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/scroll/fixed-with-border-under-composited-absolute-scrolled-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/scroll/fixed-with-border-under-composited-absolute-scrolled-expected.txt
@@ -14,12 +14,11 @@
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV id='absolute'",
-      "position": [8, 2000],
       "bounds": [1, 1],
       "contentsOpaque": true,
       "backfaceVisibility": "hidden",
       "backgroundColor": "#FF0000",
-      "transform": 1
+      "transform": 2
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV id='fixed'",
@@ -39,6 +38,16 @@
         [0, 0, 1, 0],
         [0, -400, 0, 1]
       ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 2000, 0, 1]
+      ]
     }
   ]
 }
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/scroll/overflow-hidden-yet-scrolled-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/scroll/overflow-hidden-yet-scrolled-expected.txt
new file mode 100644
index 0000000..c9019e0
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/scroll/overflow-hidden-yet-scrolled-expected.txt
@@ -0,0 +1,31 @@
+{
+  "layers": [
+    {
+      "name": "Scrolling background of LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutNGBlockFlow DIV id='scroller'",
+      "bounds": [302, 302],
+      "backfaceVisibility": "hidden",
+      "invalidations": [
+        [1, 201, 100, 100]
+      ],
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/scroll/overflow-hidden-yet-scrolled-with-custom-scrollbar-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/scroll/overflow-hidden-yet-scrolled-with-custom-scrollbar-expected.txt
new file mode 100644
index 0000000..c9019e0
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/scroll/overflow-hidden-yet-scrolled-with-custom-scrollbar-expected.txt
@@ -0,0 +1,31 @@
+{
+  "layers": [
+    {
+      "name": "Scrolling background of LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutNGBlockFlow DIV id='scroller'",
+      "bounds": [302, 302],
+      "backfaceVisibility": "hidden",
+      "invalidations": [
+        [1, 201, 100, 100]
+      ],
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/scroll/scroll-stacking-context-backface-visiblity-leaves-traces-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/scroll/scroll-stacking-context-backface-visiblity-leaves-traces-expected.txt
index e2af358..54eaed1 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/scroll/scroll-stacking-context-backface-visiblity-leaves-traces-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/scroll/scroll-stacking-context-backface-visiblity-leaves-traces-expected.txt
@@ -14,18 +14,17 @@
     },
     {
       "name": "LayoutNGBlockFlow (relative positioned) HEADER",
-      "position": [8, 28],
       "bounds": [769, 20],
       "drawsContent": false,
       "backfaceVisibility": "hidden",
-      "transform": 1
+      "transform": 2
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV id='searchbar'",
       "bounds": [150, 150],
       "contentsOpaque": true,
       "backgroundColor": "#800080",
-      "transform": 2
+      "transform": 3
     }
   ],
   "transforms": [
@@ -40,6 +39,16 @@
     },
     {
       "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 28, 0, 1]
+      ]
+    },
+    {
+      "id": 3,
       "transform": [
         [1, 0, 0, 0],
         [0, 1, 0, 0],
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/stacking-context-lost-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/stacking-context-lost-expected.txt
index 96447ca..8c0be2d 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/stacking-context-lost-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/stacking-context-lost-expected.txt
@@ -8,13 +8,24 @@
     },
     {
       "name": "LayoutNGBlockFlow (relative positioned) DIV id='outer'",
-      "position": [278, 278],
       "bounds": [100, 100],
       "contentsOpaque": true,
       "backfaceVisibility": "hidden",
       "backgroundColor": "#008000",
       "invalidations": [
         [0, 0, 100, 100]
+      ],
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [278, 278, 0, 1]
       ]
     }
   ]
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/svg/transform-focus-ring-repaint-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/svg/transform-focus-ring-repaint-expected.txt
index 8954e2b..9016fdc8 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/svg/transform-focus-ring-repaint-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/svg/transform-focus-ring-repaint-expected.txt
@@ -8,19 +8,20 @@
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV id='target'",
-      "position": [6, 56],
+      "position": [-2, -2],
       "bounds": [204, 204],
       "backfaceVisibility": "hidden",
       "backgroundColor": "#FFFF00",
       "invalidations": [
         [0, 0, 204, 204]
-      ]
+      ],
+      "transform": 1
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV",
       "bounds": [440, 300],
       "backgroundColor": "#0000FF",
-      "transform": 2
+      "transform": 3
     }
   ],
   "transforms": [
@@ -30,13 +31,23 @@
         [1, 0, 0, 0],
         [0, 1, 0, 0],
         [0, 0, 1, 0],
-        [108, 158, 0, 1]
+        [8, 58, 0, 1]
       ]
     },
     {
       "id": 2,
       "parent": 1,
       "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 100, 0, 1]
+      ]
+    },
+    {
+      "id": 3,
+      "parent": 2,
+      "transform": [
         [0, 1, 0, 0],
         [-1, 0, 0, 0],
         [0, 0, 1, 0],
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/vertical-rl-as-paint-container-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/vertical-rl-as-paint-container-expected.txt
index 590c932..1adb49e 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/vertical-rl-as-paint-container-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/vertical-rl-as-paint-container-expected.txt
@@ -8,11 +8,22 @@
     },
     {
       "name": "LayoutNGBlockFlow DIV id='target'",
-      "position": [8, 8],
       "bounds": [600, 400],
       "backfaceVisibility": "hidden",
       "invalidations": [
         [520, 0, 80, 340]
+      ],
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
       ]
     }
   ]
diff --git a/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/compositing/should-not-repaint-composited-descendants-expected.txt b/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/compositing/should-not-repaint-composited-descendants-expected.txt
index f8dd35e..b155170 100644
--- a/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/compositing/should-not-repaint-composited-descendants-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/compositing/should-not-repaint-composited-descendants-expected.txt
@@ -11,7 +11,6 @@
       "position": [30, 30],
       "bounds": [20, 70],
       "contentsOpaque": true,
-      "backfaceVisibility": "hidden",
       "backgroundColor": "#008000",
       "invalidations": [
         [0, 0, 20, 70]
@@ -20,12 +19,10 @@
     },
     {
       "name": "LayoutBlockFlow DIV class='composited child'",
-      "position": [0, 50],
       "bounds": [50, 50],
       "contentsOpaque": true,
-      "backfaceVisibility": "hidden",
       "backgroundColor": "#008000",
-      "transform": 1
+      "transform": 2
     }
   ],
   "transforms": [
@@ -37,6 +34,16 @@
         [0, 0, 1, 0],
         [8, 8, 0, 1]
       ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, 50, 0, 1]
+      ]
     }
   ]
 }
diff --git a/third_party/blink/web_tests/inspector-protocol/accessibility/accessibility-getFullAXTree-display-locked-expected.txt b/third_party/blink/web_tests/inspector-protocol/accessibility/accessibility-getFullAXTree-display-locked-expected.txt
index 38282be..355c8010 100644
--- a/third_party/blink/web_tests/inspector-protocol/accessibility/accessibility-getFullAXTree-display-locked-expected.txt
+++ b/third_party/blink/web_tests/inspector-protocol/accessibility/accessibility-getFullAXTree-display-locked-expected.txt
@@ -3,6 +3,9 @@
 WebArea
   generic
     generic
+      text "spacer"
+        InlineTextBox
+    generic
       text "locked"
       generic
         text "child"
@@ -18,6 +21,7 @@
       text "text"
     generic
     generic
+    generic
       text "normal text"
         InlineTextBox
 
diff --git a/third_party/blink/web_tests/inspector-protocol/accessibility/accessibility-getFullAXTree-display-locked.js b/third_party/blink/web_tests/inspector-protocol/accessibility/accessibility-getFullAXTree-display-locked.js
index a970ca07..0fa2e1b 100644
--- a/third_party/blink/web_tests/inspector-protocol/accessibility/accessibility-getFullAXTree-display-locked.js
+++ b/third_party/blink/web_tests/inspector-protocol/accessibility/accessibility-getFullAXTree-display-locked.js
@@ -1,15 +1,17 @@
 (async function(testRunner) {
   var {page, session, dp} = await testRunner.startHTML(`
-    <div id='activatable' style='subtree-visibility: hidden-matchable'>
+    <div style='height: 10000px;'>spacer</div>
+    <div id='activatable' style='subtree-visibility: auto'>
       locked
       <div id='child'>
         child
         <div id='grandChild'>grandChild</div>
       </div>
       <div id='invisible' style='display:none'>invisible</div>
-      <div id='nested' style='subtree-visibility: hidden-matchable'>nested</div>
+      <div id='nested' style='subtree-visibility: auto'>nested</div>
       text
     </div>
+    <div id='nonViewportActivatable' style='subtree-visibility: hidden-matchable'>nonViewportActivatable text</div>
     <div id='nonActivatable' style='subtree-visibility: hidden'>nonActivatable text</div>
     <div id='normal'>normal text</div>
   `, 'Tests accessibility values of display locked nodes');
diff --git a/third_party/blink/web_tests/paint/invalidation/compositing/should-not-repaint-composited-descendants-expected.txt b/third_party/blink/web_tests/paint/invalidation/compositing/should-not-repaint-composited-descendants-expected.txt
index 0514dff..cbf9fd3 100644
--- a/third_party/blink/web_tests/paint/invalidation/compositing/should-not-repaint-composited-descendants-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/compositing/should-not-repaint-composited-descendants-expected.txt
@@ -11,7 +11,6 @@
       "position": [30, 30],
       "bounds": [20, 70],
       "contentsOpaque": true,
-      "backfaceVisibility": "hidden",
       "backgroundColor": "#008000",
       "invalidations": [
         [0, 0, 20, 70]
@@ -20,12 +19,10 @@
     },
     {
       "name": "LayoutNGBlockFlow DIV class='composited child'",
-      "position": [0, 50],
       "bounds": [50, 50],
       "contentsOpaque": true,
-      "backfaceVisibility": "hidden",
       "backgroundColor": "#008000",
-      "transform": 1
+      "transform": 2
     }
   ],
   "transforms": [
@@ -37,6 +34,16 @@
         [0, 0, 1, 0],
         [8, 8, 0, 1]
       ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, 50, 0, 1]
+      ]
     }
   ]
 }
diff --git a/third_party/blink/web_tests/paint/invalidation/compositing/should-not-repaint-composited-descendants.html b/third_party/blink/web_tests/paint/invalidation/compositing/should-not-repaint-composited-descendants.html
index aded658..798a0f86 100644
--- a/third_party/blink/web_tests/paint/invalidation/compositing/should-not-repaint-composited-descendants.html
+++ b/third_party/blink/web_tests/paint/invalidation/compositing/should-not-repaint-composited-descendants.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <style>
 #composited-box {
-    backface-visibility: hidden;
+    will-change: transform;
     position: absolute;
     background-color: green;
     clip: rect(40px, 110px, 110px, 40px);
@@ -12,7 +12,7 @@
     background-color: green;
 }
 .composited {
-    backface-visibility: hidden;
+    will-change: transform;
 }
 </style>
 <script src="../resources/text-based-repaint.js"></script>
diff --git a/third_party/blink/web_tests/platform/linux/virtual/scalefactor200/css3/filters/effect-reference-colorspace-hw-expected.png b/third_party/blink/web_tests/platform/linux/virtual/scalefactor200/css3/filters/effect-reference-colorspace-hw-expected.png
index 1b59c37..7d45612f 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/scalefactor200/css3/filters/effect-reference-colorspace-hw-expected.png
+++ b/third_party/blink/web_tests/platform/linux/virtual/scalefactor200/css3/filters/effect-reference-colorspace-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/css3/filters/effect-reference-colorspace-hw-expected.png b/third_party/blink/web_tests/platform/mac/css3/filters/effect-reference-colorspace-hw-expected.png
index 8f619dc..7accaf9 100644
--- a/third_party/blink/web_tests/platform/mac/css3/filters/effect-reference-colorspace-hw-expected.png
+++ b/third_party/blink/web_tests/platform/mac/css3/filters/effect-reference-colorspace-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/scalefactor200/css3/filters/effect-reference-colorspace-hw-expected.png b/third_party/blink/web_tests/platform/mac/virtual/scalefactor200/css3/filters/effect-reference-colorspace-hw-expected.png
index af640be..43a89000 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/scalefactor200/css3/filters/effect-reference-colorspace-hw-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/scalefactor200/css3/filters/effect-reference-colorspace-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-colorspace-hw-expected.png b/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-colorspace-hw-expected.png
index 91333a6..5b04ab45 100644
--- a/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-colorspace-hw-expected.png
+++ b/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-colorspace-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-colorspace-hw-expected.png b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-colorspace-hw-expected.png
index 55cdb79..3950b50 100644
--- a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-colorspace-hw-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-colorspace-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/serial/serialPort_readable_gc.html b/third_party/blink/web_tests/serial/serialPort_readable_gc.html
index 1a485f64..3c270ad 100644
--- a/third_party/blink/web_tests/serial/serialPort_readable_gc.html
+++ b/third_party/blink/web_tests/serial/serialPort_readable_gc.html
@@ -29,10 +29,7 @@
     port.readable.pipeTo(writable);
   })();
 
-  for (let i = 0; i < 50; ++i) {
-    GCController.collect();
-    await new Promise(resolve => setTimeout(resolve, 0));
-  }
+  GCController.collectAll();
 
   const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
   fakePort.write(data);
diff --git a/tools/mb/mb.py b/tools/mb/mb.py
index 094e171..0782960 100755
--- a/tools/mb/mb.py
+++ b/tools/mb/mb.py
@@ -659,8 +659,11 @@
     #
     # TODO(dpranke): Also, add support for sharding and merging results.
     dimensions = []
+    swarming_pool = ''
     for k, v in self._DefaultDimensions() + self.args.dimensions:
       dimensions += ['-d', k, v]
+      if k == 'pool':
+        swarming_pool = v
 
     archive_json_path = self.ToSrcRelPath(
         '%s/%s.archive.json' % (build_dir, target))
@@ -724,7 +727,10 @@
           '-S', swarming_server,
           '--tags=purpose:user-debug-mb',
       ] + dimensions
-    self._AddBaseSoftware(cmd)
+    # TODO(crbug.com/812428): Remove this once all pools have migrated to task
+    # templates.
+    if not swarming_pool.endswith('.template'):
+      self._AddBaseSoftware(cmd)
     if self.args.extra_args:
       cmd += ['--'] + self.args.extra_args
     self.Print('')
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 416e182a..298b882 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -14941,6 +14941,9 @@
   <int value="33" label="Network panel film strip started recording"/>
   <int value="34" label="Coverage report was filtered"/>
   <int value="35" label="Coverage started (per block)"/>
+  <int value="36" label="Settings opened via gear icon"/>
+  <int value="37" label="Settings opened via menu"/>
+  <int value="38" label="Settings opened via command menu"/>
 </enum>
 
 <enum name="DevToolsBackgroundService">
@@ -15008,6 +15011,14 @@
   <int value="26" label="Drawer - Live Heap Profile"/>
   <int value="27" label="Drawer - Quick source"/>
   <int value="28" label="Drawer - Request blocking"/>
+  <int value="29" label="Settings - Preferences"/>
+  <int value="30" label="Settings - Workspace"/>
+  <int value="31" label="Settings - Experiments"/>
+  <int value="32" label="Settings - Blackbox"/>
+  <int value="33" label="Settings - Devices"/>
+  <int value="34" label="Settings - Throttling Conditions"/>
+  <int value="35" label="Settings - Emulation Geolocations"/>
+  <int value="36" label="Settings - Shortcuts"/>
 </enum>
 
 <enum name="DevToolsSetting">
@@ -39540,6 +39551,7 @@
   <int value="414114078" label="LitePageServerPreviews:disabled"/>
   <int value="415154056" label="enable-physical-keyboard-autocorrect"/>
   <int value="415395210" label="TrimOnMemoryPressure:enabled"/>
+  <int value="416691040" label="SendTabToSelfOmniboxSendingAnimation:disabled"/>
   <int value="416887895" label="enable-password-change-support"/>
   <int value="417709910"
       label="AutofillSendExperimentIdsInPaymentsRPCs:disabled"/>
@@ -40178,6 +40190,7 @@
   <int value="1203795051" label="PassiveMixedContentWarning:enabled"/>
   <int value="1203821857" label="Vulkan:disabled"/>
   <int value="1205849612" label="enable-sync-synced-notifications"/>
+  <int value="1205929554" label="SendTabToSelfOmniboxSendingAnimation:enabled"/>
   <int value="1210343926" label="enable-drop-sync-credential"/>
   <int value="1211284676" label="V8NoTurbo:enabled"/>
   <int value="1211756417"
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 0f1bb94..63130b30 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -25605,8 +25605,9 @@
 </histogram>
 
 <histogram name="Compositing.Display.Draw.Occlusion.Calculation.Time"
-    units="microseconds" expires_after="M82">
+    units="microseconds" expires_after="2020-08-23">
   <owner>yiyix@chromium.org</owner>
+  <owner>chromeos-gfx@chromium.org</owner>
   <summary>
     Time spent to remove invisible quads from the quad_list in CompositorFrame.
 
@@ -140512,7 +140513,7 @@
 </histogram>
 
 <histogram name="Scheduler.CancelableTaskTracker.TaskDuration2" units="ms"
-    expires_after="2020-05-01">
+    expires_after="2020-08-01">
 <!-- Name completed by histogram_suffixes name="CancelableTaskTrackerDurationTypes" -->
 
   <owner>wez@chromium.org</owner>
@@ -140524,7 +140525,7 @@
 </histogram>
 
 <histogram name="Scheduler.CancelableTaskTracker.TaskStatus"
-    enum="CancelableTaskStatus" expires_after="2020-05-01">
+    enum="CancelableTaskStatus" expires_after="2020-08-01">
   <owner>wez@chromium.org</owner>
   <owner>scheduler-dev@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index ff77ed1e..3696af9c 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -8367,6 +8367,31 @@
   </metric>
 </event>
 
+<event name="PaymentApp.CheckoutEvents">
+  <owner>sahel@chromium.org</owner>
+  <owner>web-payments-team@google.com</owner>
+  <summary>
+    Records checkout events as well as completion status for the invoked payment
+    app when its origin is shown inside the payment handler modal window. This
+    metric uses the specific PAYMENT_APP_ID ukm_source_id which is generated
+    from the app's origin.
+  </summary>
+  <metric name="CompletionStatus" enum="PaymentRequestFlowCompletionStatus">
+    <summary>
+      How the Payment Request ended. This metric will only get recorded if a
+      non-autofill payment app gets invoked to handle the request.
+    </summary>
+  </metric>
+  <metric name="Events">
+    <summary>
+      Bitfield representing the events that occurred during the Payment Request.
+      This metric will only get recorded if a non-autofill payment app gets
+      invoked to handle the request. Values defined in the Event enum of
+      components/payments/core/journey_logger.h.
+    </summary>
+  </metric>
+</event>
+
 <event name="PaymentRequest.CheckoutEvents">
   <owner>sebsg@chromium.org</owner>
   <metric name="CompletionStatus">
diff --git a/ui/aura/demo/demo_main.cc b/ui/aura/demo/demo_main.cc
index 2baf62a2..fe5050a 100644
--- a/ui/aura/demo/demo_main.cc
+++ b/ui/aura/demo/demo_main.cc
@@ -197,8 +197,7 @@
       test_screen->CreateHostForPrimaryDisplay());
   std::unique_ptr<DemoWindowParentingClient> window_parenting_client(
       new DemoWindowParentingClient(host->window()));
-  aura::test::TestFocusClient focus_client;
-  aura::client::SetFocusClient(host->window(), &focus_client);
+  aura::test::TestFocusClient focus_client(host->window());
 
   // Create a hierarchy of test windows.
   gfx::Rect window1_bounds(100, 100, 400, 400);
diff --git a/ui/aura/test/aura_test_helper.cc b/ui/aura/test/aura_test_helper.cc
index 87d6170..e9894c8a 100644
--- a/ui/aura/test/aura_test_helper.cc
+++ b/ui/aura/test/aura_test_helper.cc
@@ -83,8 +83,6 @@
   setup_called_ = true;
 
   wm_state_ = std::make_unique<wm::WMState>();
-  // Needs to be before creating WindowTreeClient.
-  focus_client_ = std::make_unique<TestFocusClient>();
 
   if (!env) {
     // Some tests suites create Env globally rather than per test.
@@ -125,7 +123,7 @@
 
   Window* root_window = GetContext();
   new wm::DefaultActivationClient(root_window);  // Manages own lifetime.
-  client::SetFocusClient(root_window, focus_client_.get());
+  focus_client_ = std::make_unique<TestFocusClient>(root_window);
   capture_client_ = std::make_unique<client::DefaultCaptureClient>(root_window);
   parenting_client_ = std::make_unique<TestWindowParentingClient>(root_window);
 
@@ -140,16 +138,14 @@
   g_instance = nullptr;
   teardown_called_ = true;
   parenting_client_.reset();
-  client::SetFocusClient(GetContext(), nullptr);
   capture_client_.reset();
+  focus_client_.reset();
   host_.reset();
 
   if (display::Screen::GetScreen() == test_screen_.get())
     display::Screen::SetScreenInstance(nullptr);
   test_screen_.reset();
 
-  focus_client_.reset();
-
   ui::ShutdownInputMethodForTesting();
 
   context_factories_.reset();
diff --git a/ui/aura/test/test_focus_client.cc b/ui/aura/test/test_focus_client.cc
index b32122f..afcf4dc 100644
--- a/ui/aura/test/test_focus_client.cc
+++ b/ui/aura/test/test_focus_client.cc
@@ -13,12 +13,16 @@
 ////////////////////////////////////////////////////////////////////////////////
 // TestFocusClient, public:
 
-TestFocusClient::TestFocusClient()
-    : focused_window_(NULL),
+TestFocusClient::TestFocusClient(Window* root_window)
+    : root_window_(root_window),
+      focused_window_(nullptr),
       observer_manager_(this) {
+  DCHECK(root_window_);
+  client::SetFocusClient(root_window_, this);
 }
 
 TestFocusClient::~TestFocusClient() {
+  client::SetFocusClient(root_window_, nullptr);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/ui/aura/test/test_focus_client.h b/ui/aura/test/test_focus_client.h
index 1cca6ed8..aa1435b 100644
--- a/ui/aura/test/test_focus_client.h
+++ b/ui/aura/test/test_focus_client.h
@@ -18,7 +18,7 @@
 class TestFocusClient : public client::FocusClient,
                         public WindowObserver {
  public:
-  TestFocusClient();
+  explicit TestFocusClient(Window* root_window);
   ~TestFocusClient() override;
 
  private:
@@ -32,6 +32,7 @@
   // Overridden from WindowObserver:
   void OnWindowDestroying(Window* window) override;
 
+  Window* root_window_;
   Window* focused_window_;
   ScopedObserver<Window, WindowObserver> observer_manager_;
   base::ObserverList<aura::client::FocusChangeObserver>::Unchecked
diff --git a/ui/file_manager/file_manager/foreground/elements/files_quick_view.css b/ui/file_manager/file_manager/foreground/elements/files_quick_view.css
index a2a1ea32..8cd0ad89 100644
--- a/ui/file_manager/file_manager/foreground/elements/files_quick_view.css
+++ b/ui/file_manager/file_manager/foreground/elements/files_quick_view.css
@@ -185,8 +185,7 @@
   width: 36px;
 }
 
-/* TODO(files-ng): files-ng rule that gets applied to non-files-ng */
-:host-context(html.pointer-active) cr-button:not(:active):hover {
+:host-context(html.pointer-active.files-ng) cr-button:not(:active):hover {
   --hover-bg-color: none;
   cursor: unset;
 }
@@ -255,7 +254,7 @@
   display: none;
 }
 
-:host([files-ng]) #info-button[toogle] {
+:host([files-ng]) #info-button[aria-pressed=true] {
   background-color: rgba(255, 255, 255, 12%);
 }
 
@@ -268,8 +267,7 @@
   font-weight: bold;
 }
 
-/* TODO(files-ng): files-ng rule that gets applied to non-files-ng */
-:host-context(html.focus-outline-visible) cr-button:not(:active):focus {
+:host-context(html.focus-outline-visible.files-ng) cr-button:not(:active):focus {
   border: 2px solid var(--google-blue-300);
 }
 
diff --git a/ui/file_manager/file_manager/foreground/elements/files_quick_view.html b/ui/file_manager/file_manager/foreground/elements/files_quick_view.html
index 2c60d50b..b18d616 100644
--- a/ui/file_manager/file_manager/foreground/elements/files_quick_view.html
+++ b/ui/file_manager/file_manager/foreground/elements/files_quick_view.html
@@ -34,7 +34,7 @@
          </cr-button>
            <files-icon-button toggles id="metadata-button" on-tap="onMetadataButtonTap_" active="{{metadataBoxActive}}" aria-label="$i18n{QUICK_VIEW_TOGGLE_METADATA_BOX_BUTTON_LABEL}" tabindex="0" has-tooltip>
            </files-icon-button>
-           <cr-button id="info-button" on-click="onMetadataButtonTap_" aria-label="$i18n{QUICK_VIEW_TOGGLE_METADATA_BOX_BUTTON_LABEL}" has-tooltip toogle>
+           <cr-button id="info-button" on-click="onMetadataButtonTap_" aria-pressed="{{metadataBoxActive}}" aria-label="$i18n{QUICK_VIEW_TOGGLE_METADATA_BOX_BUTTON_LABEL}" has-tooltip>
             <span class="icon"></span>
           </cr-button>
         </div>
diff --git a/ui/file_manager/file_manager/foreground/elements/files_quick_view.js b/ui/file_manager/file_manager/foreground/elements/files_quick_view.js
index edc2561..3a01b27d 100644
--- a/ui/file_manager/file_manager/foreground/elements/files_quick_view.js
+++ b/ui/file_manager/file_manager/foreground/elements/files_quick_view.js
@@ -182,21 +182,20 @@
   },
 
   /**
-   * TODO(992824): investigate the this.$.innerContentPanel.focus() since the
-   * has no tabindex it seems, and so the focus() call is ignored.
+   * See the changes on crbug.com/641587, but crbug.com/779044#c11 later undid
+   * that work. So the focus remains on the metadata button when clicked after
+   * the crbug.com/779044 "ghost focus" fix.
+   *
+   * crbug.com/641587 mentions a different UI behavior, that was wanted to fix
+   * that bug. TODO(files-ng): UX to resolve the correct behavior needed here.
    *
    * @param {!Event} event tap event.
    *
    * @private
    */
   onMetadataButtonTap_: function(event) {
-    // Set focus back to innerContent panel so that pressing space key next
-    // closes Quick View.
-    this.$.innerContentPanel.focus();
-
     if (this.hasAttribute('files-ng')) {
       this.metadataBoxActive = !this.metadataBoxActive;
-      event.target.toggleAttribute('toogle', this.metadataBoxActive);
     }
   },
 
diff --git a/ui/gfx/color_space_win.cc b/ui/gfx/color_space_win.cc
index 262bb4a..5d316af5 100644
--- a/ui/gfx/color_space_win.cc
+++ b/ui/gfx/color_space_win.cc
@@ -138,6 +138,10 @@
 DXGI_COLOR_SPACE_TYPE ColorSpaceWin::GetDXGIColorSpace(
     const ColorSpace& color_space,
     bool force_yuv) {
+  // Treat invalid color space as sRGB.
+  if (!color_space.IsValid())
+    return DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
+
   if (color_space.GetMatrixID() == gfx::ColorSpace::MatrixID::RGB &&
       !force_yuv) {
     // For RGB, we default to FULL
@@ -223,16 +227,10 @@
   }
 }
 
-DXGI_FORMAT ColorSpaceWin::GetDXGIFormat(const gfx::ColorSpace& color_space,
-                                         bool needs_alpha) {
-  // The PQ transfer function needs 10 bits. If we need an alpha channel, then
-  // we will need to bump to 16 bits.
-  if (color_space.GetTransferID() == gfx::ColorSpace::TransferID::SMPTEST2084) {
-    if (needs_alpha)
-      return DXGI_FORMAT_R16G16B16A16_UNORM;
-    else
-      return DXGI_FORMAT_R10G10B10A2_UNORM;
-  }
+DXGI_FORMAT ColorSpaceWin::GetDXGIFormat(const gfx::ColorSpace& color_space) {
+  // The PQ transfer function needs 10 bits.
+  if (color_space.GetTransferID() == gfx::ColorSpace::TransferID::SMPTEST2084)
+    return DXGI_FORMAT_R10G10B10A2_UNORM;
 
   // Non-PQ HDR color spaces use half-float.
   if (color_space.IsHDR())
diff --git a/ui/gfx/color_space_win.h b/ui/gfx/color_space_win.h
index cce9d2b..734ab3d 100644
--- a/ui/gfx/color_space_win.h
+++ b/ui/gfx/color_space_win.h
@@ -37,8 +37,7 @@
 
   // Get DXGI format for swap chain. This will default to 8-bit, but will use
   // 10-bit or half-float for HDR color spaces.
-  static DXGI_FORMAT GetDXGIFormat(const gfx::ColorSpace& color_space,
-                                   bool needs_alpha);
+  static DXGI_FORMAT GetDXGIFormat(const gfx::ColorSpace& color_space);
 
   static D3D11_VIDEO_PROCESSOR_COLOR_SPACE GetD3D11ColorSpace(
       const ColorSpace& color_space);
diff --git a/ui/gl/android/android_surface_control_compat.cc b/ui/gl/android/android_surface_control_compat.cc
index 765a79f..c0a2e48 100644
--- a/ui/gl/android/android_surface_control_compat.cc
+++ b/ui/gl/android/android_surface_control_compat.cc
@@ -14,6 +14,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/no_destructor.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/system/sys_info.h"
 #include "base/trace_event/trace_event.h"
 #include "ui/gfx/color_space.h"
 
@@ -287,6 +288,12 @@
 bool SurfaceControl::IsSupported() {
   if (!base::android::BuildInfo::GetInstance()->is_at_least_q())
     return false;
+
+  // GLFence cannot be created successfully on emulator, and it is needed by
+  // Android surface control.
+  if (base::SysInfo::GetAndroidHardwareEGL() == "emulation")
+    return false;
+
   CHECK(SurfaceControlMethods::Get().supported);
   return true;
 }
diff --git a/ui/gl/direct_composition_child_surface_win.cc b/ui/gl/direct_composition_child_surface_win.cc
index 93a85f27..f132887 100644
--- a/ui/gl/direct_composition_child_surface_win.cc
+++ b/ui/gl/direct_composition_child_surface_win.cc
@@ -267,8 +267,7 @@
     return false;
   }
 
-  DXGI_FORMAT dxgi_format =
-      gfx::ColorSpaceWin::GetDXGIFormat(color_space_, has_alpha_);
+  DXGI_FORMAT dxgi_format = gfx::ColorSpaceWin::GetDXGIFormat(color_space_);
 
   if (!dcomp_surface_ && enable_dc_layers_) {
     TRACE_EVENT2("gpu", "DirectCompositionChildSurfaceWin::CreateSurface",
@@ -308,9 +307,8 @@
     desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
     desc.Scaling = DXGI_SCALING_STRETCH;
     desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
-    desc.AlphaMode = (has_alpha_ || enable_dc_layers_)
-                         ? DXGI_ALPHA_MODE_PREMULTIPLIED
-                         : DXGI_ALPHA_MODE_IGNORE;
+    desc.AlphaMode =
+        has_alpha_ ? DXGI_ALPHA_MODE_PREMULTIPLIED : DXGI_ALPHA_MODE_IGNORE;
     desc.Flags = DirectCompositionSurfaceWin::AllowTearing()
                      ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING
                      : 0;
@@ -331,8 +329,10 @@
     }
     Microsoft::WRL::ComPtr<IDXGISwapChain3> swap_chain;
     if (SUCCEEDED(swap_chain_.As(&swap_chain))) {
-      swap_chain->SetColorSpace1(
+      hr = swap_chain->SetColorSpace1(
           gfx::ColorSpaceWin::GetDXGIColorSpace(color_space_));
+      DCHECK(SUCCEEDED(hr))
+          << "SetColorSpace1 failed with error " << std::hex << hr;
     }
   }
 
@@ -418,8 +418,7 @@
 
   // ResizeBuffers can't change alpha blending mode.
   if (swap_chain_ && resize_only) {
-    DXGI_FORMAT format =
-        gfx::ColorSpaceWin::GetDXGIFormat(color_space_, has_alpha_);
+    DXGI_FORMAT format = gfx::ColorSpaceWin::GetDXGIFormat(color_space_);
     UINT flags = DirectCompositionSurfaceWin::AllowTearing()
                      ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING
                      : 0;
diff --git a/ui/gl/gl_surface_egl.cc b/ui/gl/gl_surface_egl.cc
index 17a6259..65d0cc4 100644
--- a/ui/gl/gl_surface_egl.cc
+++ b/ui/gl/gl_surface_egl.cc
@@ -961,16 +961,19 @@
 
   // The native fence sync extension is a bit complicated. It's reported as
   // present for ChromeOS, but Android currently doesn't report this extension
-  // even when it's present, and older devices may export a useless wrapper
-  // function. See crbug.com/775707 for details. In short, if the symbol is
-  // present and we're on Android N or newer, assume that it's usable even if
-  // the extension wasn't reported.
+  // even when it's present, and older devices and Android emulator may export
+  // a useless wrapper function. See crbug.com/775707 for details. In short, if
+  // the symbol is present and we're on Android N or newer and we are not on
+  // Android emulator, assume that it's usable even if the extension wasn't
+  // reported.
   g_egl_android_native_fence_sync_supported =
       HasEGLExtension("EGL_ANDROID_native_fence_sync");
 #if defined(OS_ANDROID)
-  if (base::android::BuildInfo::GetInstance()->sdk_int() >=
+  if (!g_egl_android_native_fence_sync_supported &&
+      base::android::BuildInfo::GetInstance()->sdk_int() >=
           base::android::SDK_VERSION_NOUGAT &&
-      g_driver_egl.fn.eglDupNativeFenceFDANDROIDFn) {
+      g_driver_egl.fn.eglDupNativeFenceFDANDROIDFn &&
+      base::SysInfo::GetAndroidHardwareEGL() != "emulation") {
     g_egl_android_native_fence_sync_supported = true;
   }
 #endif
diff --git a/ui/native_theme/common_theme.cc b/ui/native_theme/common_theme.cc
index e0c2d90..3173a71 100644
--- a/ui/native_theme/common_theme.cc
+++ b/ui/native_theme/common_theme.cc
@@ -395,7 +395,10 @@
     case NativeTheme::kColorId_TextfieldReadOnlyColor: {
       const SkColor bg = base_theme->GetSystemColor(
           NativeTheme::kColorId_TextfieldReadOnlyBackground, color_scheme);
-      return color_utils::BlendForMinContrast(gfx::kGoogleGrey600, bg).color;
+      const SkColor fg = base_theme->GetSystemColor(
+          NativeTheme::kColorId_TextfieldDefaultColor, color_scheme);
+      return color_utils::BlendForMinContrast(gfx::kGoogleGrey600, bg, fg)
+          .color;
     }
     case NativeTheme::kColorId_TextfieldSelectionBackgroundFocused:
       return gfx::kGoogleBlue200;
diff --git a/ui/native_theme/native_theme.cc b/ui/native_theme/native_theme.cc
index fbfc8ade..370c3ab 100644
--- a/ui/native_theme/native_theme.cc
+++ b/ui/native_theme/native_theme.cc
@@ -165,19 +165,17 @@
 
   // TODO(http://crbug.com/1057754): Remove the below restrictions.
   if (base::FeatureList::IsEnabled(features::kColorProviderRedirection) &&
-      color_scheme == NativeTheme::ColorScheme::kLight) {
-    if (!color_provider_) {
-      // Lazy init the color provider as it makes USER32 calls underneath on
-      // Windows, which isn't permitted on renderers.
-      // TODO(http://crbug.com/1057754): Handle dark and high contrast modes.
-      color_provider_ = ColorProviderManager::Get().GetColorProviderFor(
-          ColorProviderManager::ColorMode::kLight,
-          ColorProviderManager::ContrastMode::kNormal);
-    }
+      color_scheme != NativeTheme::ColorScheme::kPlatformHighContrast) {
+    auto color_mode = (color_scheme == NativeTheme::ColorScheme::kDark)
+                          ? ColorProviderManager::ColorMode::kDark
+                          : ColorProviderManager::ColorMode::kLight;
+    // TODO(http://crbug.com/1057754): Handle high contrast modes.
+    auto* color_provider = ColorProviderManager::Get().GetColorProviderFor(
+        color_mode, ColorProviderManager::ContrastMode::kNormal);
     auto color_id_map = NativeThemeColorIdToColorIdMap();
     auto result = color_id_map.find(color_id);
     if (result != color_id_map.cend())
-      return color_provider_->GetColor(result->second);
+      return color_provider->GetColor(result->second);
   }
   return GetAuraColor(color_id, this, color_scheme);
 }
diff --git a/ui/native_theme/native_theme.h b/ui/native_theme/native_theme.h
index 380a8e7..45757a9 100644
--- a/ui/native_theme/native_theme.h
+++ b/ui/native_theme/native_theme.h
@@ -29,8 +29,6 @@
 
 namespace ui {
 
-class ColorProvider;
-
 // This class supports drawing UI controls (like buttons, text fields, lists,
 // comboboxes, etc) that look like the native UI controls of the underlying
 // platform, such as Windows or Linux. It also supplies default colors for
@@ -480,7 +478,6 @@
   bool is_high_contrast_ = false;
   PreferredColorScheme preferred_color_scheme_ =
       PreferredColorScheme::kNoPreference;
-  mutable ColorProvider* color_provider_ = nullptr;
 
   DISALLOW_COPY_AND_ASSIGN(NativeTheme);
 };
diff --git a/ui/views/controls/webview/OWNERS b/ui/views/controls/webview/OWNERS
index db57a47..d2e5686 100644
--- a/ui/views/controls/webview/OWNERS
+++ b/ui/views/controls/webview/OWNERS
@@ -1,3 +1,6 @@
-miu@chromium.org
 sadrul@chromium.org
 sky@chromium.org
+
+# For "EmbedFullscreenWidgetMode" changes. (Component: UI>Browser>TabCapture)
+miu@chromium.org
+mfoltz@chromium.org
diff --git a/ui/views/corewm/tooltip_controller_unittest.cc b/ui/views/corewm/tooltip_controller_unittest.cc
index 712e3fc..9ac9d77 100644
--- a/ui/views/corewm/tooltip_controller_unittest.cc
+++ b/ui/views/corewm/tooltip_controller_unittest.cc
@@ -11,7 +11,6 @@
 #include "ui/accessibility/ax_enums.mojom.h"
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/aura/client/cursor_client.h"
-#include "ui/aura/client/screen_position_client.h"
 #include "ui/aura/client/window_types.h"
 #include "ui/aura/env.h"
 #include "ui/aura/test/aura_test_base.h"
@@ -485,17 +484,17 @@
 
   void SetUp() override {
     TooltipControllerTest::SetUp();
-    aura::client::SetScreenPositionClient(GetRootWindow(),
-                                          &screen_position_client_);
+    screen_position_client_ =
+        std::make_unique<wm::DefaultScreenPositionClient>(GetRootWindow());
   }
 
   void TearDown() override {
-    aura::client::SetScreenPositionClient(GetRootWindow(), nullptr);
+    screen_position_client_.reset();
     TooltipControllerTest::TearDown();
   }
 
  private:
-  wm::DefaultScreenPositionClient screen_position_client_;
+  std::unique_ptr<wm::DefaultScreenPositionClient> screen_position_client_;
   std::unique_ptr<display::Screen> desktop_screen_;
 
   DISALLOW_COPY_AND_ASSIGN(TooltipControllerCaptureTest);
diff --git a/ui/views/test/views_test_helper_aura.cc b/ui/views/test/views_test_helper_aura.cc
index 8621b3e1..73c202a7 100644
--- a/ui/views/test/views_test_helper_aura.cc
+++ b/ui/views/test/views_test_helper_aura.cc
@@ -21,9 +21,7 @@
   gfx::NativeWindow root_window = GetContext();
   if (root_window && !aura::client::GetScreenPositionClient(root_window)) {
     screen_position_client_ =
-        std::make_unique<wm::DefaultScreenPositionClient>();
-    aura::client::SetScreenPositionClient(root_window,
-                                          screen_position_client_.get());
+        std::make_unique<wm::DefaultScreenPositionClient>(root_window);
   }
 }
 
@@ -37,12 +35,9 @@
     // So, although it's optional, check the root window to detect failures
     // before they hit the CQ on other platforms.
     DCHECK(root_window->children().empty()) << "Not all windows were closed.";
-
-    if (screen_position_client_.get() ==
-        aura::client::GetScreenPositionClient(root_window))
-      aura::client::SetScreenPositionClient(root_window, nullptr);
   }
 
+  screen_position_client_.reset();
   aura_test_helper_.TearDown();
 
   const wm::CaptureController* const controller = wm::CaptureController::Get();
diff --git a/ui/views/widget/desktop_aura/desktop_screen_position_client.cc b/ui/views/widget/desktop_aura/desktop_screen_position_client.cc
index b889969e..2406d29 100644
--- a/ui/views/widget/desktop_aura/desktop_screen_position_client.cc
+++ b/ui/views/widget/desktop_aura/desktop_screen_position_client.cc
@@ -22,15 +22,7 @@
 
 }  // namespace
 
-DesktopScreenPositionClient::DesktopScreenPositionClient(
-    aura::Window* root_window)
-    : root_window_(root_window) {
-  aura::client::SetScreenPositionClient(root_window_, this);
-}
-
-DesktopScreenPositionClient::~DesktopScreenPositionClient() {
-  aura::client::SetScreenPositionClient(root_window_, nullptr);
-}
+DesktopScreenPositionClient::~DesktopScreenPositionClient() = default;
 
 void DesktopScreenPositionClient::SetBounds(aura::Window* window,
                                             const gfx::Rect& bounds,
diff --git a/ui/views/widget/desktop_aura/desktop_screen_position_client.h b/ui/views/widget/desktop_aura/desktop_screen_position_client.h
index 33fbdc2..586ac07 100644
--- a/ui/views/widget/desktop_aura/desktop_screen_position_client.h
+++ b/ui/views/widget/desktop_aura/desktop_screen_position_client.h
@@ -16,7 +16,7 @@
 class VIEWS_EXPORT DesktopScreenPositionClient
     : public wm::DefaultScreenPositionClient {
  public:
-  explicit DesktopScreenPositionClient(aura::Window* root_window);
+  using DefaultScreenPositionClient::DefaultScreenPositionClient;
   ~DesktopScreenPositionClient() override;
 
   // aura::client::DefaultScreenPositionClient:
@@ -25,8 +25,6 @@
                  const display::Display& display) override;
 
  private:
-  aura::Window* root_window_;
-
   DISALLOW_COPY_AND_ASSIGN(DesktopScreenPositionClient);
 };
 
diff --git a/ui/wm/core/coordinate_conversion_unittest.cc b/ui/wm/core/coordinate_conversion_unittest.cc
index 13d077a..871eb195 100644
--- a/ui/wm/core/coordinate_conversion_unittest.cc
+++ b/ui/wm/core/coordinate_conversion_unittest.cc
@@ -5,7 +5,6 @@
 #include "ui/wm/core/coordinate_conversion.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
-#include "ui/aura/client/screen_position_client.h"
 #include "ui/aura/test/aura_test_base.h"
 #include "ui/aura/test/test_windows.h"
 #include "ui/wm/core/default_screen_position_client.h"
@@ -15,8 +14,7 @@
 typedef aura::test::AuraTestBase CoordinateConversionTest;
 
 TEST_F(CoordinateConversionTest, ConvertRect) {
-  DefaultScreenPositionClient screen_position_client;
-  aura::client::SetScreenPositionClient(root_window(), &screen_position_client);
+  DefaultScreenPositionClient screen_position_client(root_window());
   aura::Window* w = aura::test::CreateTestWindowWithBounds(
       gfx::Rect(10, 20, 100, 200), root_window());
 
@@ -35,8 +33,6 @@
   gfx::Rect r4(-10, -20, 100, 200);
   ConvertRectToScreen(w, &r4);
   EXPECT_EQ("0,0 100x200", r4.ToString());
-
-  aura::client::SetScreenPositionClient(root_window(), nullptr);
 }
 
 }  // namespace wm
diff --git a/ui/wm/core/default_screen_position_client.cc b/ui/wm/core/default_screen_position_client.cc
index 36f8ea7..1c532c27 100644
--- a/ui/wm/core/default_screen_position_client.cc
+++ b/ui/wm/core/default_screen_position_client.cc
@@ -11,10 +11,15 @@
 
 namespace wm {
 
-DefaultScreenPositionClient::DefaultScreenPositionClient() {
+DefaultScreenPositionClient::DefaultScreenPositionClient(
+    aura::Window* root_window)
+    : root_window_(root_window) {
+  DCHECK(root_window_);
+  aura::client::SetScreenPositionClient(root_window_, this);
 }
 
 DefaultScreenPositionClient::~DefaultScreenPositionClient() {
+  aura::client::SetScreenPositionClient(root_window_, nullptr);
 }
 
 void DefaultScreenPositionClient::ConvertPointToScreen(
diff --git a/ui/wm/core/default_screen_position_client.h b/ui/wm/core/default_screen_position_client.h
index d1bf747..2e70ce2 100644
--- a/ui/wm/core/default_screen_position_client.h
+++ b/ui/wm/core/default_screen_position_client.h
@@ -16,7 +16,7 @@
 class WM_CORE_EXPORT DefaultScreenPositionClient
     : public aura::client::ScreenPositionClient {
  public:
-  DefaultScreenPositionClient();
+  explicit DefaultScreenPositionClient(aura::Window* root_window);
   ~DefaultScreenPositionClient() override;
 
   // aura::client::ScreenPositionClient overrides:
@@ -35,6 +35,8 @@
   virtual gfx::Point GetOriginInScreen(const aura::Window* root_window);
 
  private:
+  aura::Window* root_window_;
+
   DISALLOW_COPY_AND_ASSIGN(DefaultScreenPositionClient);
 };
 
diff --git a/ui/wm/core/ime_util_chromeos_unittest.cc b/ui/wm/core/ime_util_chromeos_unittest.cc
index 24fbb50..b4510da 100644
--- a/ui/wm/core/ime_util_chromeos_unittest.cc
+++ b/ui/wm/core/ime_util_chromeos_unittest.cc
@@ -23,13 +23,12 @@
 
   void SetUp() override {
     AuraTestBase::SetUp();
-    screen_position_client_ = std::make_unique<DefaultScreenPositionClient>();
-    aura::client::SetScreenPositionClient(root_window(),
-                                          screen_position_client_.get());
+    screen_position_client_ =
+        std::make_unique<DefaultScreenPositionClient>(root_window());
   }
 
   void TearDown() override {
-    aura::client::SetScreenPositionClient(root_window(), nullptr);
+    screen_position_client_.reset();
     AuraTestBase::TearDown();
   }
 
diff --git a/ui/wm/test/wm_test_helper.cc b/ui/wm/test/wm_test_helper.cc
index 844c4d37..a0c43c6 100644
--- a/ui/wm/test/wm_test_helper.cc
+++ b/ui/wm/test/wm_test_helper.cc
@@ -32,8 +32,8 @@
 
   aura::client::SetWindowParentingClient(host_->window(), this);
 
-  focus_client_ = std::make_unique<aura::test::TestFocusClient>();
-  aura::client::SetFocusClient(host_->window(), focus_client_.get());
+  focus_client_ =
+      std::make_unique<aura::test::TestFocusClient>(host_->window());
 
   root_window_event_filter_ = std::make_unique<wm::CompoundEventFilter>();
   host_->window()->AddPreTargetHandler(root_window_event_filter_.get());
diff --git a/weblayer/browser/safe_browsing/url_checker_delegate_impl.cc b/weblayer/browser/safe_browsing/url_checker_delegate_impl.cc
index e7f3fb2..cf88f33 100644
--- a/weblayer/browser/safe_browsing/url_checker_delegate_impl.cc
+++ b/weblayer/browser/safe_browsing/url_checker_delegate_impl.cc
@@ -43,6 +43,13 @@
           base::Unretained(this), resource));
 }
 
+void UrlCheckerDelegateImpl::
+    StartObservingInteractionsForDelayedBlockingPageHelper(
+        const security_interstitials::UnsafeResource& resource,
+        bool is_main_frame) {
+  NOTREACHED() << "Delayed warnings aren't implemented for WebLayer";
+}
+
 void UrlCheckerDelegateImpl::StartDisplayingDefaultBlockingPage(
     const security_interstitials::UnsafeResource& resource) {
   content::WebContents* web_contents = resource.web_contents_getter.Run();
diff --git a/weblayer/browser/safe_browsing/url_checker_delegate_impl.h b/weblayer/browser/safe_browsing/url_checker_delegate_impl.h
index fc5fa47..55814185 100644
--- a/weblayer/browser/safe_browsing/url_checker_delegate_impl.h
+++ b/weblayer/browser/safe_browsing/url_checker_delegate_impl.h
@@ -37,6 +37,9 @@
       const net::HttpRequestHeaders& headers,
       bool is_main_frame,
       bool has_user_gesture) override;
+  void StartObservingInteractionsForDelayedBlockingPageHelper(
+      const security_interstitials::UnsafeResource& resource,
+      bool is_main_frame) override;
   bool IsUrlWhitelisted(const GURL& url) override;
   bool ShouldSkipRequestCheck(const GURL& original_url,
                               int frame_tree_node_id,